@tutti-os/tutti-cli
v0.1.4
Published
Tutti account login, Room discovery and Task commands
Keywords
Readme
Tutti public CLI
独立的 TypeScript CLI,使用 Node.js 24+。npm 包名为 @tutti-os/tutti-cli,安装后命令为 tutti-cli。
直接以当前用户身份调用 Tutti 服务;运行 CLI 的机器无需安装 Computer 或启动 desktopd。
触发 Agent 执行时,由服务端路由到所选 Agent 对应的 Computer,执行可用性和权限由服务端判断。
构建和安装
从 Tutti 仓库根目录执行:
pnpm install --frozen-lockfile
pnpm --filter @tutti-os/tutti-cli build
cd apps/tutti-cli
npm pack
npm install --global ./tutti-os-tutti-cli-0.1.0-beta.2.tgz
tutti-cli --help包内包含编译后的 JavaScript 和从仓库唯一默认配置生成的配置快照,无运行时 npm 依赖。
也可以在源码目录直接运行 node apps/tutti-cli/src/main.ts --help。
本地构建和打包不会自动发布 npm 包。
GitHub Actions 发布
合入 main 后,在 Actions → Tutti CLI Release → Run workflow 选择 main:
| 输入 | beta 包 | 正式包 |
| --------- | ----------------- | ------------ |
| channel | beta | production |
| version | 如 0.1.0-beta.3 | 如 0.1.0 |
| npm 标签 | beta | latest |
dry_run=true 只构建、测试和保存 tarball,不发布。版本号必须未被发布,且 beta 只能是
X.Y.Z-beta.N、正式版只能是 X.Y.Z。版本只写入该次运行的 package.json,不自动提交回仓库;
CLI 的 --version 和登录 clientVersion 都读取同一份包信息。发布源固定为触发时的 main commit。
流程依次完成版本校验、类型检查、源码接口测试、打包、安装 tarball 后再次测试;随后独立发布 job 验证下载产物的 SHA1/SHA512,使用组织级 npm 凭据发布同一 tarball,并核对注册表版本、标签和校验值。 产物保留 30 天,版本、来源 commit 和安装命令显示在运行摘要中。发布串行执行,不取消已开始的发布。 注册表可见性最多等待约 2 分钟;超时只报告待核实,不重复发布。重跑前应先检查 npm,已存在版本会被拒绝。
# 只验证下一个 beta 包
gh workflow run tutti-cli-release.yml --ref main -f channel=beta -f version=0.1.0-beta.3 -f dry_run=true
# 发布 beta 包
gh workflow run tutti-cli-release.yml --ref main -f channel=beta -f version=0.1.0-beta.3
# 发布正式包
gh workflow run tutti-cli-release.yml --ref main -f channel=production -f version=0.1.0仓库已获组织级 Actions Secret NPM_TOKEN 的访问授权,流水线直接复用它;Token 只在发包步骤
映射到 NODE_AUTH_TOKEN,构建、依赖安装和测试步骤均不注入。发包前先校验凭据有效,随后发布已验证的产物。
该 Token 需有效、具有 @tutti-os/tutti-cli 写权限,并允许 CI 非交互发布;如果 npm 要求交互式二次验证,
流水线会失败,需要管理员更新组织 Secret 的权限配置。Secret 对仓库可用不代表这些权限已验证,首次真实发布仍需验收。
发布目标固定为 https://registry.npmjs.org,不使用组织的 NPM_REGISTRY_URL 改写已公开包的目标 registry。
当前实现不要求新增 npm Trusted Publisher,也不自动回退到其他凭据。后续如需改为 OIDC,可单独迁移,
参考 npm Trusted Publishing 文档。
登录
tutti-cli login
tutti-cli whoami --json
tutti-cli logoutlogin 打开现有 Web 登录页面,复用已有账号登录方式。--no-browser 仅打印登录 URL,
用户在本机浏览器打开后继续;它不是设备码登录,浏览器必须能访问运行 CLI 的本机回调。
登录最长等待 5 分钟,Ctrl+C 取消并释放监听器。
使用 Web 已有的 cli_bridge:绑定 127.0.0.1:38473–38492 中的空闲端口,生成随机
attempt/token/device ID;接收浏览器的一次性 transfer code,向 Account 服务兑换独立 WebSession,
查询账号身份后原子保存。浏览器可以给 state 增加 provider/nonce,原有 handoff 身份字段必须保持一致。
沿用 bridge 的 appCallbackUrl=tutti-amp://login/callback 兼容元数据,不调用该应用协议。
GET 回调成功保存登录态后,以 303 跳回同一站点的 /auth/cli/result?status=success;
已验证回调的兑换失败与用户取消分别返回 status=error、status=cancelled。
回跳只携带结果状态,设置 no-store 与 no-referrer,不转发凭据或 OAuth state;POST 完成接口继续返回原 JSON。
发布此版本前,须先部署 tsh-web/apps/auth 的结果页;旧 CLI 仍沿用本地结果页。
拒绝错误 Origin、Host、state、重复完成和超限 body。CLI 不读取浏览器 Cookie,也不读取 Computer 的凭据。
凭据默认保存在 ~/.tutti-cli/session.json;POSIX 目录权限为 0700,文件为 0600。
Windows 使用用户主目录的访问控制,POSIX mode 检查不等价于 Windows ACL 验证。
登录失败保留此前的会话;退出先撤销服务端会话再删除本地文件,服务端不可用时保留文件供再次退出。
HTTP 请求禁止跟随重定向,凭据只发往保存时对应的站点。whoami 和登录成功输出不包含会话密钥。
开发环境可显式设置:
TUTTI_CLI_ORIGIN=https://your-tutti-host.example tutti-cli login
TUTTI_CLI_STATE_DIR=/private/path/tutti-cli tutti-cli login自定义 origin 必须承载 /auth/login、/auth/cli/result、/api/account 和 /api/desktop 入口。
默认站点为 https://tutti.sh;只允许 HTTPS,开发接口测试额外允许 loopback HTTP。
切换站点须重新登录或设置另一份独立状态目录,不能把某站点的会话发送到另一站点。
查询房间
tutti-cli room list
tutti-cli room list --type local
tutti-cli room list --type cloud
tutti-cli room list --page-size 20 --json
tutti-cli room list --page-size 20 --page-token "上一页的 nextPageToken" --json
tutti-cli room list --all --type local --json使用 CLI 当前登录用户的房间目录,包含自己创建及有权访问的房间;默认只读一页,每页 100 条。
--page-size 可设置为 1–200,--page-token 原样传入上次返回的游标。
--all 从第一页开始读取全部分页,可与 --page-size 配合,但不能与 --page-token 同时使用。
默认按 Local rooms / Cloud rooms 分组展示本次结果的数量、房间 ID 和名称,本地房间有绑定 Computer ID 时一并显示。
--type all|local|cloud 默认 all,对本次读取的页筛选,不依赖本机 Computer 是否安装或在线。
服务端不提供类型筛选参数,因此筛选后可能不足 page-size 条甚至为空;只要有 nextPageToken 就仍有后续页。
使用 --all --type local|cloud 可获取该类型的完整列表。
本地指 Native directory Room,也包含绑定在其他电脑上的房间;云端指 Cloud Room,沿用 Web 的分类语义。
--json 输出单行 { "rooms": [...], "nextPageToken": "..." },无下一页时游标为空字符串;
文本模式也显示下一页游标。rooms 中每项包含 id、name、type(local 或 cloud),
以及接口提供的可选 nativeComputerId。空列表返回 rooms: [];文本模式显示对应分组数量为 0,不能据此判断已到最后一页。
分类使用服务端 executionLocationKind 的数字或 protobuf 名称;缺失为协议默认云端。
未知分类、非法响应、重复分页游标或任一分页失败均报错,不把部分结果作为完整列表输出。
此命令只查询目录,不启动 Room runtime,也不读取本机文件或 Computer 凭据。
创建和修改 Task
用 tutti-cli room list 获取 roomId;所有 Task 命令显式指定 --room-id,权限仍由服务端按 Room 成员身份判断。
tutti-cli task create --room-id ROOM_ID --title "排查构建失败" \
--description "复现步骤" --priority high --json
tutti-cli task edit --room-id ROOM_ID --task-id TASK_UUID \
--title "修复构建失败" --description-file ./description.md --json
tutti-cli task edit --room-id ROOM_ID --task-id TASK_UUID --description "" --json
tutti-cli task show --room-id ROOM_ID --task-id TASK_UUID --json- 创建默认使用
todo、未指派语义;可通过--assignee-user-id指派房间成员,不启动 Agent。优先级为none(默认)、low、medium、high、urgent;--project-id可关联同 Room Project。 - 标题 trim 后为 1–512 个 Unicode 字符。描述是纯文本,保留换行,转换成既有 Task document。
- 修改至少指定标题、描述或负责人。
--description与--description-file互斥;文件为 UTF-8,最大 1 MiB,-从 stdin 读取。未指定字段保持原值,显式空描述清空内容。 - 修改只发送包含改动字段的
updateMask,没有先读取再覆盖的过程。 --operation-id UUID默认生成 UUID;创建时同时作为 Task ID。失败会输出该 ID,写入从不自动重试。不确定的写入用相同 ID 和相同输入重试;也可先task show核实结果。--json输出单行服务端 JSON;默认输出缩进 JSON。错误写 stderr,退出码1,取消为130。未知或不适用的参数明确报错。
修改 Task 状态
# 把已有任务改回“待办”
tutti-cli task status --room-id ROOM_ID --task-id TASK_UUID --status todo --json
tutti-cli task status --room-id ROOM_ID --task-id TASK_UUID \
--status in_review --operation-id OPERATION_UUID --json--status 必填,支持 todo、in_progress、in_review、blocked、done。
此全局 CLI 使用当前登录用户的成员权限;与仅能汇报当前 Agent Task 状态的执行内 CLI 分开。
复用看板的 POST /v2/rooms/{roomId}/tasks/{taskId}:move(RoomTaskService_MoveRoomTask),
不通过普通编辑接口修改状态。状态迁移不会启动或中断 Agent,不改变负责人、标题、描述或关闭状态;
已关闭 Task、权限不足或幂等输入冲突等错误由服务端拒绝,CLI 不自动重新打开任务或重试。
请求显式发送 confirmActiveExecution=false,与 Web 一致,避免旧服务端中断活动执行。
--operation-id UUID 省略时生成 UUID;失败保留 Task ID 和操作 ID,退出码为 1。
结果不确定时先用 task show 核实,再以相同操作 ID 和输入重试;新操作使用新的 ID。
成功输出完整服务端回执(--json 为单行 JSON)。幂等重放返回 Task 的当前状态,可能包含之后的状态变更,
因此回执状态不一定仍是原操作请求的状态。task create 和 task edit 的参数与默认行为保持不变。
指派 Task 负责人
先查询房间成员,选择准确的 userId;显示名称可能重名,不作为指派身份:
tutti-cli room members --room-id ROOM_ID --json
# 创建时实际指派负责人
tutti-cli task create --room-id ROOM_ID --title "修复文件链接无法打开" \
--assignee-user-id USER_ID --json
# 修改已有 Task 的负责人,可单独使用,也可与标题、描述修改一起提交
tutti-cli task edit --room-id ROOM_ID --task-id TASK_UUID \
--assignee-user-id USER_ID --json
# 取消指派(包括此前指派给 Agent 的 Task)
tutti-cli task edit --room-id ROOM_ID --task-id TASK_UUID --unassign --jsonroom members 返回 { roomId, members: [{ userId, displayName, role }] },角色为 owner 或 collaborator。
复用看板的房间成员列表接口,一次读取完整列表,接口没有分页参数;不会输出分享链接、密码等其他分享设置。
成员没有显示名称时,displayName 使用用户 ID。权限不足或响应不完整时明确报错。
--assignee-user-id 与 --unassign 互斥,创建和修改都支持;不传时创建保持未指派,修改保留原负责人。
用户 ID 是不透明身份,不要求 UUID;不得用用户名替代。人员指派写入真正的 assignee.memberUserId,
取消写入 assignee.unassigned=true;修改通过 updateMask=assignee 精确更新,不覆盖其他字段。
Task 只有一个负责人,换成人员会替换此前的人员或 Agent 指派;它不会启动 Agent,也不是取消已有执行的命令。
服务端按当前房间成员身份及操作权限校验目标,CLI 不用过期目录代替写入时的权限判断。
沿用 --operation-id 幂等语义,不自动重试写入;成功回执必须确认请求的负责人或未指派状态。
标签与 Bug 登记
标签属于房间。先查询或创建标签,再使用返回的 labelId 给 Bug Task 打标签:
tutti-cli label list --room-id ROOM_ID --json
tutti-cli label create --room-id ROOM_ID --name "Bug" --color '#EF4444' --json
tutti-cli task create --room-id ROOM_ID --title "文件链接无法打开" \
--description "复现步骤及预期结果" --priority high \
--assignee-user-id USER_ID --label-id BUG_LABEL_UUID --label-id WEB_LABEL_UUID --json
# 给已有 Task 添加或移除一个标签,保留其他标签
tutti-cli task label add --room-id ROOM_ID --task-id TASK_UUID --label-id LABEL_UUID --json
tutti-cli task label remove --room-id ROOM_ID --task-id TASK_UUID --label-id LABEL_UUID --jsonlabel list 返回 { roomId, labels: [{ labelId, roomId, name, color }] };接口没有分页参数。
创建标签要求名称 trim 后为 1–32 个 Unicode 字符,颜色为 #RRGGBB(建议加引号),大小写均可,写入时统一大写。
label create 可用 --label-id UUID 指定身份,省略则生成 UUID;它没有 --operation-id 参数或幂等重放保证。
创建失败会输出标签 ID,先用 label list 核实是否已创建。同名或已有 ID 冲突按服务端错误处理,不静默复用或覆盖。
task create 的 --label-id 可重复,但各 ID 必须是不同的有效 UUID;标签随 Task 创建请求的 labelIds 一次提交,
不采用先创建 Task 再逐个打标签的流程。目标标签须已存在于同一房间,最终权限与存在性由服务端验证。
省略参数沿用无初始标签行为。task edit 不接受此参数;已有 Task 使用 task label add/remove,每次仅操作一个标签。
Task 标签添加/移除沿用 --operation-id UUID,省略则生成;不自动重试写入,错误保留 Task、标签和操作 ID。
不确定结果先用 task show 确认,只使用相同操作 ID 和输入重试;更改输入或发起新操作使用新的操作 ID。
服务端重放回执返回 Task 的当前标签状态,可能已经包含之后的修改,CLI 不把它误判成原操作失败。
移除标签只解除与该 Task 的关联,不删除房间标签,也不触发 Agent。
本次不提供房间标签重命名、改色或删除命令。
Task 评论与回复
统一使用 task comment,省略 --reply-to 表示发顶层评论。默认只发表评论;只有显式指定
--agent-target-id 才会在保存评论后触发该 Agent,避免意外触发 Task 已指派的 Agent。
# 1. 顶层评论,不执行 Agent
tutti-cli task comment --room-id ROOM_ID --task-id TASK_UUID \
--content "补充一下验收标准" --json
# 2. 回复指定评论,不执行 Agent
tutti-cli task comment --room-id ROOM_ID --task-id TASK_UUID \
--reply-to COMMENT_UUID --content "这个方案可以" --json
# 3. 顶层评论,并触发指定 Agent
tutti-cli task comment --room-id ROOM_ID --task-id TASK_UUID \
--content "开始实现这个任务" --agent-target-id AGENT_ID --json
# 4. 在指定讨论串中回复,并触发 Agent 继续执行
tutti-cli task comment --room-id ROOM_ID --task-id TASK_UUID \
--reply-to COMMENT_UUID --content "按上面的建议继续修改" \
--agent-target-id AGENT_ID --json--content TEXT 与 --content-file PATH|- 互斥且必须提供一个;文件为 UTF-8,最大 1 MiB,
- 从 stdin 读取。支持 --operation-id UUID 进行显式幂等重试。
仅评论模式不接受 --model 或 --reasoning-effort;这两个参数必须与 Agent ID 一起使用。
服务端评论是两层结构。--reply-to 可以指定顶层评论或子回复:CLI 分页查询该 Task 的时间线,
找到指定评论及其根评论;新回复与 Agent 的回复都归入这个根评论的讨论串,不建立第三层嵌套。
回执的 replyTo 保留输入的评论 ID,parentCommentId 是实际所属根评论,commentId 是新发出的评论。
目标不存在、已删除、属于其他 Task、根评论不可用或时间线查询失败时不写入评论、不触发 Agent。
查询与写入之间的权限和删除竞态仍由服务端在写入时检查。
直接评论成功返回 status: "posted",退出码为 0;不会返回执行 ID。
触发 Agent 时返回下文的 accepted/pending/rejected 受理状态,Agent 回复留在同一讨论串。
已有 task run --instruction ... 保留,等价于必须指定 Agent 的评论执行入口,也支持 --reply-to。
选择 Agent 和模型
模型目录属于房间内的具体 Agent。先选 Agent,再查询它当前可用的模型:
tutti-cli agent list --room-id ROOM_ID --json
tutti-cli agent models --room-id ROOM_ID --agent-target-id AGENT_ID --json
# 将上一步 models[].value 原样传给 --model
tutti-cli task comment --room-id ROOM_ID --task-id TASK_UUID \
--content "按任务描述开始实现" --agent-target-id AGENT_ID \
--model MODEL_VALUE --reasoning-effort high --jsonagent models 返回 models、modelConfigurable、effectiveModel(如有)、effectiveSettings,
以及 reasoningConfigurable、reasoningEfforts 和按模型区分的 reasoningOptionsByModel。
使用模型的 value,不要使用展示用的 label;value 可能包含连接 ID 前缀,必须完整保留。
当 modelConfigurable=false 时不传 --model;推理强度使用该模型提供的选项,不能假定所有模型都支持 high。
目录提供空字符串推理选项时,可用 --reasoning-effort "" 显式选择该默认档位。
查询的有效设置代表本次目录查询的默认上下文,不保证等于已有 Task 会话的设置;执行时仍由服务端校验并应用设置。
查询使用与 Web 相同的实时能力协议,确认 Agent 属于该房间后向它查询模型;不会创建评论或触发 Task 执行。
Agent 不可用、目录响应非法或查询失败时返回错误;总等待时间最多 90 秒,Ctrl+C 可取消。
部分模型来源失败时仍返回可用模型,并标记 partial=true、列出 modelDiscoveryFailures,同时向 stderr 提示目录不完整。
空 models 是有效结果,不代表存在可选模型。命令的 JSON 输出不会混入提示文字。
在 Task 中触发 Agent
先查看这个 Room 中的 Agent,使用返回的 agentTargetId 精确选择执行目标:
tutti-cli agent list --room-id ROOM_ID --json
tutti-cli task run --room-id ROOM_ID --task-id TASK_UUID \
--agent-target-id AGENT_ID --instruction "修复任务中描述的问题,完成后汇报验证结果" --json
# 从 UTF-8 文件读取指令;用 - 可从 stdin 读取
tutti-cli task run --room-id ROOM_ID --task-id TASK_UUID \
--agent-target-id AGENT_ID --instruction-file ./instruction.md \
--model MODEL_ID --reasoning-effort high --operation-id OPERATION_UUID --jsonagent list 返回 { roomId, agents },列出名称、Provider、Computer 类型及执行可用性。
availability.status 为服务端的 available、checking 或 unavailable,有阻塞原因时显示 reason。
目录包含可见但不能立即执行的目标:ownership=requestable 需要授权,referenceOnly=true 是历史引用。
目录查询不启动执行,也不以 Computer 在线状态推断 Agent 可用。该目录接口没有分页参数。
task run 在已有 Task 中先发表一条当前用户的评论,再使用与 Web 相同的 :start-agent 接口触发 Agent。
必填 Room、Task、精确 Agent ID 和非空指令;--instruction 与 --instruction-file 互斥。
指令保留换行,文件须为 UTF-8 且不超过 1 MiB;只发送指令,不修改 Task 标题、描述或指派。
本次指令显示在 Task 评论中。顶层发言以新评论作为执行请求的 parentCommentId;指定 --reply-to 时使用已有根评论。
Agent 的最终回复由服务端发布到同一个评论串。
评论使用 COMMENT_ONLY 模式,禁止保存评论时自动触发已指派的 Agent;之后只对显式选中的 Agent 发起一次执行。
服务端继续管理会话创建或复用及执行,CLI 不创建脱离 Task 的独立会话。
--model 和 --reasoning-effort 可选,合法值由所选 Agent 的服务端能力校验;未指定时不覆盖服务端配置。
返回示例:
{
"roomId": "ROOM_ID",
"taskId": "TASK_UUID",
"agentTargetId": "AGENT_ID",
"operationId": "CLIENT_OPERATION_UUID",
"commentId": "CLIENT_OPERATION_UUID",
"admissionOperationId": "SERVER_ADMISSION_ID",
"status": "accepted",
"invocationId": "INVOCATION_ID"
}accepted:服务端已受理,并返回invocationId;退出码为 0,不表示执行已经完成。pending:请求已保存,执行受理仍待处理;退出码为 0,不会轮询或重新提交。后续在 Web Task 中查看执行进展。rejected:明确拒绝,退出码为 1;stdout 仍返回回执及可用的rejectionCode,stderr 显示错误。- 非法参数、HTTP 错误或回执身份不匹配:退出码为 1,不输出成功回执。Ctrl+C 取消返回 130;已提交的请求可能继续执行。
operationId 是本次 Task 写操作的幂等 ID;commentId 使用同一个 UUID,利用评论与 Task 写操作各自独立的幂等空间,使显式重试复用同一条评论。
admissionOperationId 是服务端根据活动生成的执行受理 ID,不能拿它代替 --operation-id。
未提供 --operation-id 时自动生成;请求失败也会在 stderr 保留该 ID。所有写入都不自动重试。
网络错误或取消后先用 task show 查询 Task,或在 Web 查看执行活动;不确定的写入只使用相同操作 ID 和相同输入重试,避免重复触发。
评论保存失败或回执身份不符时不触发 Agent;评论已保存但执行失败或被拒绝时保留评论,并输出可定位的 comment ID。
这两个请求不是一个原子事务;中途失败不会删除已保存的评论。同 ID、同输入重试会复用评论,再重放幂等执行请求。
明确拒绝的操作结果不可改写,解决原因后应使用新的操作 ID 提交,新操作也会创建新评论。
这两个命令复用 CLI 登录态和服务端权限检查,不读取本机 Computer 凭据,不为本地与云端 Agent 建立不同执行协议。
文件附件
tutti-cli task create --room-id ROOM_ID --title "构建日志" --file ./build.log --json
tutti-cli task edit --room-id ROOM_ID --task-id TASK_UUID \
--description "现场证据" --file ./screenshot.png --room-file /room/report.pdf --json
tutti-cli task comment --room-id ROOM_ID --task-id TASK_UUID \
--content-file ./reply.md --file ./fix-1.png --file ./fix-2.png --json--file 与 --room-file 都可重复、可混用,适用于 task create、task edit、task comment 和 task run。
创建可以只提供标题和附件;编辑附件时必须显式替换描述。评论与执行指令的附件挂在这条评论上,放在正文之后。
本地文件必须非空且为普通文件,拒绝符号链接;文件名不重复,合计不超过 500 MiB,附件总数不超过 256。
快照使用私有临时目录、分块读取并校验源文件未发生变化,命令结束时清理;SHA-256 与上传字节来自同一快照。
HTML、XHTML、SVG 沿用原 CLI 的主动内容限制;其余未知类型按 application/octet-stream 上传。
文本附件(.txt、.md、.csv)声明不带 charset 参数的 MIME,与服务端上传回执保持一致;文件按原始字节上传,不转码。
本地文件使用 room_task_attachment 私有上传,完成确认后才写 Task;上传目标不携带账号 Cookie,
不会打印签名 URL。--room-file 沿用 Room 成员可读共享,逻辑路径必须是规范的 /room/... 文件路径,
只适用于存在 Workspace 的 Room;不能建立公共分享。
附件处理失败不提交 Task 或评论,但此前已完成的私有上传或共享可能保留。重新上传会产生新 upload ID;
若最终 Task 写入结果不明,先查询结果,不能用同一 operation ID 重放不同的附件引用。
评论的 operation ID 就是评论 ID:结果不明时用同一 operation ID 和相同输入重试,评论未写入则正常发出;
已写入时服务端因附件引用不同返回 COMMENT_IDEMPOTENCY_CONFLICT,说明评论已存在,不再重发。
服务端须已具备 Task 私有附件能力(tutti-server #1020),没有客户端降级分支。
实现来源与协议
| 既有概念 / 实现 | 对外 CLI 实现 |
| -------------------------------------------------------------------- | ----------------------------------------------------- |
| gopackages/agent/cli/task_create.go 的创建参数 | src/main.ts 参数解析与 src/task.ts 创建行为 |
| gopackages/agent/cli/task_commands.go 的局部编辑 | src/task.ts,保留标题、描述、幂等和 updateMask 语义 |
| gopackages/agent/cli/task_files.go 的文件处理 | src/files.ts,Node 文件快照、上传与共享 |
| desktopd authbridge/protocol 与 tsh-web apps/auth 的登录 handoff | src/login.ts,复用 Web cli_bridge 协议 |
| Agent 执行上下文中的 Room | 用户显式输入 --room-id |
| desktopd Account session | CLI 自己持有、按 origin 隔离的用户会话 |
Task、Room、Project、operation ID 等在新入口仍是同一领域概念,因此保留名称。 参考参数、状态流和错误边界,以 TypeScript/Node 实现;不调用或复制 Go executable,不迁入 Agent execution authority。 没有渲染层改动,无需 UI System 依赖或样式。
严格保留的服务端协议:
| 用途 | 现有接口 / operation |
| -------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
| 登录 | /auth/login?state=...,bridge state v1、cli_bridge、规定的回调端口 |
| 兑换与校验账号 | Account POST /auth/v1/redeem_desktop_transfer_code、POST /user/v1/user_info |
| 退出 | Account POST /auth/v1/logout-web-session |
| 创建 | RoomTaskService_CreateRoomTask,POST /v2/rooms/{roomId}/tasks |
| 修改 | RoomTaskService_PatchRoomTask,PATCH /v2/rooms/{roomId}/tasks/{taskId} |
| 房间目录 | RoomService_ListRoomDirectory,GET /v2/rooms/directory?pageSize=...[&pageToken=...] |
| 查询 | RoomTaskService_GetRoomTask,GET /v2/rooms/{roomId}/tasks/{taskId} |
| 标签目录与创建 | GET/POST /v2/rooms/{roomId}/task-labels;创建字段 labelId/name/color |
| Task 标签 | POST /v2/rooms/{roomId}/tasks/{taskId}:attach-label 或 :detach-label,字段 labelId/clientOperationId |
| 房间成员 | RoomService_GetRoomShareState,GET /v2/rooms/{roomId}/share,仅输出成员信息 |
| Agent 目录 | ComputerService_GetAgentDirectoryProjection,GET /v2/agents/directory-projection?roomId=... |
| 模型目录 | POST /v2/rooms/{roomId}/agent-capability-queries(composer-options、section=core),GET /v2/rooms/{roomId}/agent-capability-queries/{queryId} |
| 回复定位 | RoomTaskService_ListRoomTaskTimeline,GET /v2/rooms/{roomId}/tasks/{taskId}/timeline?pageSize=100[&pageToken=...] |
| 执行指令评论 | RoomTaskService_CreateRoomTaskComment,POST /v2/rooms/{roomId}/tasks/{taskId}/comments,triggerMode=ROOM_TASK_COMMENT_TRIGGER_MODE_COMMENT_ONLY |
| Task 执行 | RoomTaskService_StartRoomTaskAgent,POST /v2/rooms/{roomId}/tasks/{taskId}:start-agent |
| 附件 | UploadService_CreateUpload、UploadService_CompleteUpload |
| Room 文件 | WorkspaceFilesystemService_CreateWorkspaceSharedPath,成员可见性与 X-Operation-Id |
Task 创建的 labelIds、标签 UUID/名称/颜色和添加/移除 operation 协议保持不变。
Task document、priority/status 枚举、clientOperationId、updateMask、上传字段及会话 Cookie 均沿用已有协议。
这些 server operations 的 repository consumer 已登记为 tutti,无需重复登记或修改服务端。
长期实现选择:独立 TS host 直接调用已有账号和 Task 契约。兼容方案是 TS 包装现有 Go CLI,但会依赖 Computer/Agent execution,无法满足普通用户独立安装登录的用途,因此没有采用。 能力边界为登录、房间与 Agent 目录查询、所选 Agent 的模型目录查询、房间成员查询、Task 创建、编辑、人员指派、取消指派、房间标签查询/创建、Task 标签添加/移除、附件、结果查询、评论发表与回复,以及 Task Agent 执行触发; 不包含 Agent 指派、评论编辑/删除、Task 删除、生命周期管理、执行等待或取消命令。
验证
pnpm --filter @tutti-os/tutti-cli typecheck
pnpm --filter @tutti-os/tutti-cli test
pnpm --filter @tutti-os/tutti-cli build
pnpm exec oxlint apps/tutti-cli --deny-warnings进程级接口测试运行真实 CLI、临时 HTTP 服务和隔离凭据目录,覆盖登录 handoff 校验、取消、失败保留会话、
凭据隔离、房间目录默认单页、原样游标续页、显式全部分页和本地/云端分类、筛选、空页仍保留游标、非法响应和分页失败不输出部分结果、精确创建/修改 JSON、描述清除、UTF-8 stdin、写入不重试、回执校验、附件上传顺序及失败时不写 Task 或评论。
Agent/模型与评论执行测试覆盖房间目标选择、实时模型查询与取消、模型连接值原样用于执行、部分发现失败、顶层/指定评论回复、仅评论不执行、评论失败不执行、执行受理状态与幂等重试。
此证据不能替代真实 Web 身份提供方与已部署服务的端到端验收;真实账号登录及目标 Room 创建/编辑应在发布前人工验收。
macOS 已运行;Linux/Windows 原生浏览器启动和 Windows ACL 仍需对应平台验证。
changed-aware 检查会按 apps/tutti-cli/package.json 自动选择 typecheck/test,push-ready 同时选择 build。
