Skip to content

feat(local-agent): enroll workspaces individually and give each agent its own access credential - #1495

Merged
jeff-r2026 merged 10 commits into
Tencent:mainfrom
JasonRUAN:feat/local-agent-per-agent-token
Oct 29, 2026
Merged

jeff-r2026 merged 10 commits into
Tencent:mainfrom
JasonRUAN:feat/local-agent-per-agent-token

Conversation

@JasonRUAN

@JasonRUAN JasonRUAN commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Two changes to the HTTP local agent.

  1. Enrollment is per workspace, not per machine (54219565). Previously, running the install prompt once on a device enrolled every workspace on it: each one got a project-binding prompt and was reported and synced. Now a workspace is enrolled only after the install prompt has run in it (__pending__). A workspace where it never ran is left alone: no binding prompt, no report, no sync.

  2. One access credential per agent (91fbc05d). The backend binds an sk- credential to the first agent instance that reports with it, and an instance is one agent type on one machine, so agents sharing one token evict each other. HTTP team sources now carry agentTokens:

    • init --http and source add-http gain --agent <tool> to declare which agent the credential belongs to; hooks are injected only into that tool.
    • Updating one agent's credential on the same endpoint no longer needs --force, and other agents' credentials are untouched.
    • A credential still in effect on the backend is kept and a newly issued one is not activated yet, so already distributed model configs do not break when the token changes.
    • The legacy shared token migrates compatibly: the only account using that agent takes it over; user-level hk- tokens stay shared by all agents.

Type of Change

  • Bug fix (non-breaking change that fixes an issue)
  • New feature (non-breaking change that adds functionality)
  • Breaking change (fix or feature causing existing behavior to change)
  • Documentation only
  • Refactor / internal cleanup

Test Plan

  • npx tsc --noEmit passes
  • npm run lint passes
  • npx vitest run passes
  • Added/updated tests for the change

Real-CLI verification record

image

1. Install prompt, scenario A, workspace W1 (this repo): init --http → pull → hooks check → bind-project → hook-dispatch session-start

$ teamai init --http <ENDPOINT> --token sk-8992f*** --agent codebuddy --force
✔ API key saved to /root/.teamai/apikey
✔ Updated teamai hooks in /root/.codebuddy/settings.json
✔ HTTP local agent initialized at /root/.teamai/local-agent/config.json
ℹ Run `teamai bind-project` once in each workspace it should sync; it leaves any other workspace alone.
✔ teamai initialized (HTTP read-only)!
$ teamai pull                     # exit 0, "Team repo: HTTP (report/sync delivery)"
[codebuddy] hooks OK (settings.json)
$ teamai bind-project
ℹ Workspace enrolled for local-agent sync: /root/Work/TeamAI/teamai-cli-per-agent-token
ℹ 可用项目:  (5 projects, id=29/30/31/34/35)
ℹ 未绑定项目。
$ printf '{"cwd":"%s"}' "$PWD" | teamai hook-dispatch session-start --tool codebuddy
{"hookSpecificOutput":{..."[teamai] 当前工作区尚未绑定项目 ... 可用项目: ... (id=29) ..."}}
✅ 队列空(上报成功)

~/.teamai/local-agent/config.json after the run: agentTokens: { codebuddy: sk-8992f*** }, no shared token, W1 recorded as __pending__ with ideType: codebuddy.

2. Bind a project

$ teamai bind-project --project-id 29
✔ 已将工作区绑定到项目:jasonruan测试项目 [id=29]

3. Install prompt again in W1 (idempotent, same token): exit 0, binding to project 29 kept, agentTokens unchanged. Then report and sync for the bound workspace, from ~/.teamai/debug.log:

[codebuddy] ← 200 OK POST <ENDPOINT>/api/local-agent/report   response: {"instance_id":"local-codebuddy-c83f23","project_synced":2,"rules_synced":1,"user_level_synced":9,...}
[codebuddy] sync returned 1 command(s): install_skill#23323
[codebuddy] ← 200 OK POST <ENDPOINT>/api/local-agent/commands/ack

Queue empty, no FAILED in debug.log.

4. Per-workspace enrollment, unenrolled workspace W2 (clawpro-ws2, fresh git init)

$ printf '{"cwd":"%s"}' "$W2" | teamai hook-dispatch session-start --tool codebuddy
{"hookSpecificOutput":{..."[teamai] This workspace is not enrolled, so teamai reports and syncs nothing here: .../clawpro-ws2\nRun `teamai bind-project` once in it to enroll ..."}}
debug.log: [local-agent] workspace not enrolled, skipping: .../clawpro-ws2

No report and no sync was sent, and W2 is not in workspaceBindings. Running the install prompt in W2 then printed Workspace enrolled for local-agent sync: .../clawpro-ws2, and W2 became __pending__ (ideType: codebuddy). The queue was empty. W1's binding and the codebuddy token were untouched.

Related Issues

Notes for Reviewers

HTTP team sources now carry agentTokens, so each Agent uses its own `sk-`
access credential to report and sync models with the backend instead of
sharing a single token.

- `init --http` and `source add-http` gain an `--agent <tool>` option to
  declare which Agent the credential belongs to (e.g. codebuddy, claude);
  hooks are injected only into that tool
- Updating one Agent's credential on the same endpoint no longer requires
  `--force`; the credentials of other Agents are left untouched
- Agent credentials already in effect on the backend are kept while they are
  still usable, and newly issued credentials are not activated yet, so that
  already distributed model configs do not break when the token changes
- Migrate the legacy shared token in a compatible way: the only account using
  that Agent takes over the credential; user-level `hk-` tokens are still
  shared by all Agents
- Add unit tests for per-agent tokens, and update the command help text and
  the English and Chinese docs
@github-actions

Copy link
Copy Markdown
  • [P1 blocking] Missing real-CLI verification record — PR description. This changes runtime behavior, but the Test Plan records only type checking, lint, and Vitest. The first Code Review Rules item explicitly requires an end-to-end / real-CLI verification record. Document one representative successful run; adding E2E tests alone is not that record.

  • [P1 blocking] A valid user token cannot replace a revoked agent token — src/local-agent.ts:4799. After CodeBuddy’s stored sk- token is revoked, reinitializing the same endpoint with --agent codebuddy --token hk-valid --force preserves that revoked entry in agentTokens. It overrides the newly saved shared token, so CodeBuddy’s report/sync requests continue failing authentication. Replace the targeted agent’s credential when explicitly switching it to a user token.

  • [P2 non-blocking] Project binding selects an expired credential even when another works — src/local-agent.ts:1075. With no shared token, agent-less requests always select the first stored agent token. If that token is revoked but another agent’s token remains valid, bind-project cannot list projects or complete binding. Retry another configured credential on an authentication rejection.

  • [P2 non-blocking] HTTP onboarding omits the newly required enrollment step — skill-data/setup/references/join-member.md:110 and docs/guide/member-guide.md:33. Both workflows still stop at init and verification, without running bind-project. Following them in a fresh workspace now leaves report/sync disabled, with no binding prompt to recover. Explain enrollment in the setup skill and affected bilingual guides.

Only the specified diff was read; no PR code was run, built, or installed.

@SaulMoro

Copy link
Copy Markdown
Collaborator

This was generated by AI during triage.

@JasonRUAN Thanks for the clear write-up. Notes for the maintainer and for you. We ran the built CLI against a loopback HTTP server that mimics the local-agent endpoints (report, sync, get-config, bind), with machines set up by main's build and then upgraded to this branch. The real backend's token rules and the install prompt are outside the repo, so we couldn't check them.

  • [P1 blocking] No real-CLI verification record. The Test Plan lists tsc, lint and vitest only. The repository's rule for PRs that change runtime behavior asks for one representative run of the built CLI.
  • [P1 blocking] A member who follows the docs gets no sync in any project. teamai init --http <url> --token <sk> --agent claude in a project, as docs/guide/member-guide.md and the join-member setup skill describe. Afterwards a Claude session in that project, and in any other workspace, sends no request. Only a session started from $HOME reports and syncs. On main the same steps sync everywhere. The binding prompt only appears for CodeBuddy/WorkBuddy, and the gate logs only at debug level, so nothing tells the user why. The way in is teamai bind-project, which no doc or skill mentions. Either have init --http in a workspace enroll that workspace, or document bind-project in member-guide (en and zh-CN) and join-member and print a one-time hint from the gate.
  • [P1 blocking] The legacy-token migration takes the shared token away from other agents. Set up on main with init --http --token sk-shared --agent claude,codebuddy --scope user, a bound CodeBuddy workspace, and Claude sessions from $HOME, all reporting with sk-shared. Then this branch runs init --http --token sk-cb --agent codebuddy --scope user --force. The config becomes {"agentTokens":{"codebuddy":"sk-shared"}} with no token, while enabledAgents is still claude,codebuddy and Claude's hooks remain. The next Claude session sends no token, gets 401, and prints sync FAILED: unauthorized. legacyTokenAgent (src/local-agent.ts:4769-4774) decides who uses the token from workspaceBindings, and on main only buddy tools ever get a binding, so Claude, Codex and Cursor never count. The smallest fix is to copy instead of move: drop sharedToken = undefined; at :4809, so agents with no entry of their own keep the shared token. That also keeps a downgraded build working.
  • [P2 non-blocking, important before merge]
    • Switching an agent to a valid hk- token keeps its revoked sk- entry in use. With hk-, perAgent is false, so agentTokens carries over (:4799) and tokenForAgent prefers the agent's own entry. CodeBuddy keeps failing with unauthorized after the documented re-init with a working key. This is the CI review's second finding, reproduced.
    • bind-project always uses the first stored agent token (Object.values(agentTokens)[0], :1073). If that one is revoked it fails with unauthorized, even when another token works, and the workspace stays __pending__. This is the CI review's third finding, reproduced.
    • There's no CHANGELOG entry, and no line under [Unreleased] Breaking Changes, though the body ticks "Breaking change". Upgraders need two facts. Workspaces where bind-project never ran stop reporting and syncing, with no message. And after init --http --agent, an older teamai sends no token for that agent and gets 401. We checked that on both main and the PR's merge-base.
  • [P2 non-blocking] A mistyped --agent (init --http --agent nosuch, or source add-http --agent nosuch) stores the token under agentTokens.nosuch, installs no hook, and exits 0. source add-http doesn't even warn.
  • [P3 nit]
    • When the existing token is kept, ~/.teamai/apikey is still overwritten with the new, unused one.
    • "No --force needed" doesn't hold in user scope: init --http … --scope user on an existing install aborts, and still exits 0.
    • A still-working token can't be replaced on purpose, since --force also goes through chooseAgentToken.
    • Non-null assertions (agent!, existing!, token!) in initLocalAgentHttp (:4805-4811, :4826) go away with one narrowing check.
    • Commit 54219565 has a Chinese, non-conventional subject. A squash under the PR title avoids it.

We agree with all four findings in the CI review and reproduced the three that involve running code. We rate its onboarding finding as blocking, for the reason above. No other open PR touches this code, and current main fixes none of it.

@SaulMoro
SaulMoro requested a review from jeff-r2026 October 28, 2026 04:27
@hsuchifeng

Copy link
Copy Markdown
Collaborator

分层代码评审 · 第 1 轮 · commit 91fbc05

分层代码评审:2 个需要处理(Important 2 / Security 0),Nit 3,原有问题 0

PR:#1495 · feat(local-agent): enroll workspaces individually and give each agent its own access credential · 作者 JasonRUAN
评审的提交:91fbc05dbd31a361770b949abd4634c52bf81535

结论

需要先修改(2 条 Important 未解决)。CI 全绿。

风险档位

  • 档位:high,需要 2 人批准。
  • 原因:改了 sk-/hk- 访问凭据的存放、选用和迁移(鉴权/凭据路径,任何档位都至少 1 人);作者自己标了 Breaking change,会影响已有安装;作者权限查询返回 403,按不可信作者再加 1 人,到上限 2 人。
  • 敏感路径:src/local-agent.ts(凭据选用 tokenForAgent、旧共享 token 迁移、tokenStillWorks)、src/source.ts(source add-http / init --http 写入 agentTokens)。

发现(按严重程度)

  1. 【Important / P1 阻断,已确认,3 票】src/local-agent.ts:4418 升级后旧工作区不再同步,已下发的资源停在旧版本
    • 什么情况下出错:main 上只有 CodeBuddy/WorkBuddy 会建工作区绑定,Claude、Codex 等用过的工作区没有绑定条目。升级后新加的"未登记就跳过"检查在上报和同步之前直接返回:这些工作区里的 skills/rules/claudemd 既不更新也不清理,后端下发的卸载、模型配置命令也到不了,只打一条 debug 日志。另外 localAgentInstructionText(2799 行,hook-handlers.ts:1155 调用)没有同样的检查,会继续注入缓存里的旧 claudemd。新成员按文档 init --http --agent claude 后开会话也不同步(验证 Agent 去掉 e2e 里补加的 bind-project --skip 后,git-exclude-uninstall-915.test.ts:572 失败)。
    • 怎么修:升级时把已有下发记录(delivered 状态里出现过的工作区)视为已登记做迁移,或在未登记时至少清理/停止注入旧资源并给出可见提示;localAgentInstructionText 加同样的登记检查;文档和 add-http 输出写明需要先跑 teamai bind-project。
  2. 【Important / P1 阻断,已确认,2 票】src/local-agent.ts:4799 不带单个 --agent 重新配置时,各 agent 旧 token 仍然优先
    • 什么情况下出错:不带 --agent 执行 source add-http <同一 url> --token sk-new,或用 --agent a,b,只替换共享 token,agentTokens 原样保留;tokenForAgent 优先取 agent 自己那条,已作废的 sk-old 继续使用,请求 401,命令却显示成功。(PR 上只提过单个 agent 加 hk- 的情况。)
    • 怎么修:对共享 token 或多 agent 的更新,同步覆盖/清掉对应 agent 的旧条目(至少对已判定失效的那些),或明确报错要求逐个 --agent 更新。
  3. 【Nit / P3 可选,可能成立,取决于后端规则】src/local-agent.ts:4810 推断不出旧共享 token 属于哪个 agent 时,新 token 不经 tokenStillWorks 检查直接启用
    • 什么情况下出错:按 PR 自己的说法,新 sk- 被启用会让旧 token 和已下发的模型配置失效。
    • 怎么修:这一分支同样走"旧的仍有效就保留、新的暂不启用"的检查。
  4. 【Nit / P3 可选,已确认】src/source.ts:659 输出与文档没说要先 bind-project
    • 什么情况下出错:输出写"下次会话会上报和同步",实际上没跑过 bind-project 的工作区什么都不会发;本次改过的 docs/guide/distribution.md(中英文)也没提这一步。
    • 怎么修:改输出文案,并在中英文 distribution / member-guide 和 skill-data/setup/references/join-member.md 里补上 teamai bind-project。
  5. 【Nit / P3 可选,已确认】三处带 token 的请求没有测试覆盖
    • 什么情况下出错:ack 回执(local-agent.ts:4369、:4381)、经 TEAMAI_PLUGIN_TOOL 的插件同步请求(:1262)、绑定提示拉项目列表(:1454、:1527)。验证 Agent 把这三处的 agent token 去掉后测试照样全过(mock 后端不发命令)。
    • 怎么修:mock 后端下发一条命令并断言 ack、插件同步、/projects/mine 请求带的是该 agent 的 token。

已去重(PR 上已有人提过,本报告不重复计入):缺真实 CLI 运行记录;旧共享 token 迁移时被移走、其他 agent 拿不到(4807 行,迁移测试把该行为固定下来);换成 hk- 后旧 sk- 还在用;bind-project 固定用第一条 token;加 --force 也换不掉还能用的 token;--agent 写错不报错。
已剔除(推测):tokenStillWorks 依赖接口文档未写明的后端行为。

验证

  • 在 worktree layered-review/wt-1495(commit 91fbc05)上:npx tsc --noEmit、npm run lint、npm run build 通过。
  • npx vitest run 跑 8 个相关测试文件:333 通过、1 失败;失败的是 root 下必然失败的权限测试,父提交 5c065cd 上同样失败,属环境问题。容器缺 unzip,用 Python 替身并把 TMPDIR 移出 noexec 的 /tmp 后,另外 24 个环境性失败消失。
  • E2E 跑 6 个改动文件:34 通过、2 失败,同为 root 权限测试,环境问题。
  • 反向测试:去掉 e2e 里补加的 bind-project --skip,git-exclude-uninstall-915.test.ts:572 失败(发现 1);去掉三处 agent token 测试仍通过(发现 5),另 7 处变异均被测试捕获。
  • CI:全部通过(GitHub provider remote fixture 那项 skipping)。

PR 说明缺的部分

  • 测试方法:只有 tsc / lint / vitest,缺按仓库 Code Review Rules 要求的一次真实 CLI 端到端记录。
  • 风险:标了 Breaking change,但没写升级影响(旧工作区不再同步、需要 bind-project、旧共享 token 迁移规则)。
  • 怎么撤回:没有回滚方案(回退版本后 agentTokens / 绑定条目如何处理);也没有 CHANGELOG 条目;"Notes for Reviewers" 为空。

同一批一起审的其他 PR

  • 无,本批只有这一个 PR。

提醒

  • 每条发现都要么修掉,要么写明理由后关闭。
  • 评审只给意见,是否合并由人决定。
  • 人的批准只对上面这个提交(91fbc05dbd31a361770b949abd4634c52bf81535)有效;之后有新提交要重新评审、重新批准。

- Rework access-token ownership so an HTTP token is bound to the single
  agent it belongs to; refuse when several agents are named without a clear
  owner, and copy (not move) a legacy shared token so agents with no entry
  of their own keep a fallback credential. Drop a shared token that the
  backend now rejects, so those agents stop reporting success while stuck
  at 401.

- Treat a workspace an earlier release delivered to as already enrolled, so
  upgrading keeps what it holds in sync instead of going silent.

- Make binding-hint markers time-bounded and path-safe: the dedup marker
  used to embed path separators (writing into a nonexistent dir) or, on
  hosts with a stable process id, outlive the chat that wrote it and silence
  every later session. Hints are now consumed only after they are actually
  emitted.

- Tell the user when a workspace is not enrolled (once per session, every
  host) instead of failing silently, and list available projects in
  non-interactive runs so setup scripts don't believe the account is empty.

- Surface a local-agent init failure in `init` (exit 1 + warning) rather
  than hiding it as debug, and align the bind-project / enrollment wording
  across docs and skill data.
@JasonRUAN
JasonRUAN force-pushed the feat/local-agent-per-agent-token branch from f041a2e to c06e9cc Compare October 28, 2026 12:21
@github-actions

Copy link
Copy Markdown
  • [P1 blocking] Missing real-CLI verification record — PR description, Test Plan. The described enrollment and credential changes affect runtime behavior, but the supplied Test Plan lists only type checking, lint, and Vitest. This violates the first checked-out Code Review Rules item, which requires an end-to-end / real-CLI verification record. Document one representative successful CLI run. The earlier testing finding remains unresolved.

Review incomplete: The specified head commit f041a2e9880cd12cf377011fff020a5f22372697 is unavailable locally, and network access failed. I could not read the requested diff or determine whether the three earlier code/documentation findings are resolved; I am not re-raising them without evidence.

No PR code was run, built, or installed. Please provide the exact diff or make the head commit available locally.

@github-actions

Copy link
Copy Markdown
  • [P1 blocking] Missing real-CLI verification record — PR description, Test Plan. Still unresolved: this changes runtime behavior, but the description records only type checking, lint, and Vitest. The first checked-out Code Review Rules item explicitly requires an end-to-end / real-CLI verification record. Document one representative successful CLI run; adding E2E tests is not a verification record.

  • [P1 blocking] A user token still cannot replace a revoked agent token — src/local-agent.ts:4925. With a revoked CodeBuddy token in agentTokens, reinitializing the same endpoint with --agent codebuddy --token hk-valid --force preserves that entry and saves hk-valid only as the shared token. CodeBuddy still selects its revoked credential and fails report/sync authentication. Clear or replace the targeted agent’s override when explicitly switching to a user token. The earlier finding remains unresolved.

  • [P2 non-blocking] Project binding still selects a revoked credential when another works — src/local-agent.ts:1075. With no shared token and { codebuddy: 'sk-revoked', claude: 'sk-valid' }, agent-less project requests select only the first credential. bind-project therefore fails to list projects or complete binding despite having a valid credential available. Retry another configured credential after an authentication rejection. The earlier finding remains unresolved.

  • [P2 non-blocking] New non-interactive output violates the English-output requirement — src/local-agent.ts:1330. Non-interactive bind-project now prints 可用项目:, and the added test explicitly requires that Chinese heading. The checked-out AGENTS.md requires English CLI output and English assertions. Use an English heading and update the assertion.

Resolved: The earlier onboarding-documentation finding is fixed: the setup reference and both member guides now explain workspace enrollment with bind-project, including --skip.

Only the specified diff and trusted base review criteria were read. No PR code was run, built, or installed.

@SaulMoro

Copy link
Copy Markdown
Collaborator

This was generated by AI during triage.

@JasonRUAN Thanks for the update. With the built CLI at c06e9cc7:

  • A member is now told to run teamai bind-project, through the docs, the init hint and a once-per-session "not enrolled" notice.
  • The legacy shared token is now copied, not moved, so Claude keeps working and a downgrade to 0.28.0 still syncs.
  • hsuchifeng's cached-claudemd point is fixed.

These still block it:

  • [P1 blocking] No real-CLI record in the body. The Test Plan still lists only tsc, lint and Vitest. The CI review asks for the same.

  • [P1 blocking] Lint & Test fails on all six jobs. CI lints the merge with main, where ci(lint): enable oxlint suspicious and perf categories #1414 turned on no-shadow. It finds 7 warnings in the PR's test edits:

    • import fs from 'node:fs' in local-agent-mcp.test.ts shadows five const fs = await import('../utils/fs.js');
    • a local execFileSync import in local-agent.test.ts shadows the top-level one;
    • a fetchMock parameter named init shadows init.
  • [P1 blocking] E2E shard 3/3 fails 14 cases in the PR's own local-agent-mcp-local-915 and local-agent-teardown-915. The new one-agent rule turns down --agent claude,workbuddy with a non-hk- token. On a fresh machine, init --agent claude,codebuddy with an sk- token exits 1, where 0.28.0 accepts it and both tools sync. If that refusal is intended, the e2e files and the docs need to change with it, and the CHANGELOG needs to say so.

  • [P1 blocking] A fresh init --http --scope user exits 1 with "initialized without the local agent", though the agent works. We hit it with an hk- token and no --agent. Init writes config.yaml and apikey first. Loading the local-agent config then creates config.json from them, so the setup sees an existing install and throws "already initialized". Main logged that error at debug level and carried on. This PR turns it into exit 1, with a wrong warning.

  • [P1 blocking] A token given without --agent goes to the one agent that already holds a token, and is then dropped. Claude holds sk-claude. init --http --token sk-cb --force, with no --agent as in the member guide, prints "Keeping the claude access token already on this machine; the new one is not used". The config keeps only Claude's token, and CodeBuddy's next session gets 401. At 91fbc05d this worked. The held.length === 1 ? held[0] inference at src/local-agent.ts:4906-4908 causes it. Refusing, as for two held tokens, or keeping the token as the shared one avoids it.

  • Still open from our last comment:

    • an hk- token doesn't replace a revoked agent sk- token (src/local-agent.ts:4925);
    • bind-project uses the first stored token even when it is revoked (:1075);
    • there is no CHANGELOG Breaking line.

    The CI review reproduces the first two as well. Here is a Breaking line that matches what we saw with 0.28.0 and this build:

    HTTP local agent: a workspace is synced only once it is enrolled. Run teamai bind-project (or --skip) once in each workspace. Workspaces an earlier release already delivered project resources to stay enrolled; any other workspace now gets a one-time notice instead of a sync. init --http --agent <tool> stores that agent's token separately, and an older teamai cannot read a per-agent token: an agent that has only its own token gets 401 after a downgrade.

  • [P2 non-blocking, but it fits this PR] Non-interactive bind-project now prints 可用项目:, and a new test asserts it. CLI output and test assertions stay in English (AGENTS.md).

@jeff-r2026 This is a breaking change to how the HTTP local agent enrolls workspaces, so the call is yours.

… initialized" refusal

When `teamai init --http` enrolls an access token without an explicit `--agent`, the token's ownership was only a guess. The previous logic could drop that token when the guessed agent kept its still-working credential, and could refuse a brand-new install because loading the local-agent config backfills a `config.json` from the key the init itself just wrote — mistaking its own footprint for a pre-existing setup.

This change:
- Treats a token whose agent is inferred (no `--agent`) as a spare: if the guessed agent keeps the credential it already has, the incoming token is preserved in the shared slot so agents with no token of their own can still report with it.
- Only counts a `config.json` that existed before the call as "already initialized", so a fresh `init --http --scope user` with a token and no `--agent` no longer fails with the overwrite refusal.
- Removes the special `hk-` user-token sharing path; a user-level token now follows the same one-agent-at-a-time enrollment as every other token.

The `--token` help text and CHANGELOG document the one-agent-at-a-time behavior, and E2E coverage is added for a fresh user-scope init plus extended across the local-agent and init-http scenarios.
@github-actions

Copy link
Copy Markdown
  • [P1 blocking] Missing real-CLI verification record — PR description, Test Plan. Still unresolved: this changes runtime behavior, but the description records only type checking, lint, and Vitest. The first trusted Code Review Rules item requires an end-to-end / real-CLI verification record. Document one representative successful run; adding E2E tests is not a record of executing them.

  • [P1 blocking] User-level tokens are incorrectly restricted to one agent — src/local-agent.ts:4932. perAgent and the multiple-agent rejection apply to every token, not just sk- credentials. Consequently, init --http <endpoint> --token hk-valid --agent claude,codex --force rejects a valid shared user token. With a single --agent, it instead stores that user token only in agentTokens, leaving other agents without the shared credential. Keep hk- tokens shared and exempt them from the one-agent restriction.

  • [P2 non-blocking] Project binding still selects a revoked credential when another works — src/local-agent.ts:1119. With no shared token and { codebuddy: 'sk-revoked', claude: 'sk-valid' }, agent-less project requests select only the first credential. bind-project therefore cannot list projects or complete binding despite having a valid credential available. Retry another configured credential after an authentication rejection. The earlier finding remains unresolved.

  • [P2 non-blocking] Non-interactive output still violates the English-output requirement — src/local-agent.ts:1375. The new heading is 可用项目:, and the added test explicitly asserts it. The trusted base requires English CLI output and English assertions. Translate the heading and update the assertion. The earlier finding remains unresolved.

Resolved: The earlier revoked-agent-token replacement scenario now selects the incoming token when the stored credential receives 401/403. The onboarding-documentation finding is also fixed in the setup reference and both member guides.

Only the specified diff and trusted base criteria were read. No PR code was run, built, or installed.

@SaulMoro

Copy link
Copy Markdown
Collaborator

This was generated by AI during triage.

@JasonRUAN Thanks, this head closes most of what we raised at c06e9cc7. With the built CLI against c06e9cc7 and release 0.28.0:

  • Fixed.
    • Lint passes on the merge with main (f7e11fe4), and CI e2e shard 3/3 passes on that merge, including your e2e files. Refusing --agent a,b is now intended, documented, and the tests follow it.
    • A fresh init --http --scope user exits 0 again.
    • A token without --agent stays the shared token, so CodeBuddy reports with 200 where it got 401.
    • An hk- token now replaces a revoked agent token.

Still open:

  • [P1 blocking] No real-CLI record in the body. The body hasn't changed since our last comment. This is the first Code Review Rules item in AGENTS.md, and the CI review raises it again. One built-CLI run of the enrollment flow (init --http … --agent <tool>, bind-project, a report) is enough.
  • [P2 non-blocking, but it fits this PR] bind-project still uses the first stored token when it's revoked. With {"agentTokens":{"claude":"sk-…"(revoked),"codebuddy":"sk-…"}}, bind-project --project-id 7 exits 1 with ✖ unauthorized and leaves the project at __pending__. tokenForAgent (src/local-agent.ts:1117) is unchanged. The CI review reports the same.
  • [P2 non-blocking, but it fits this PR] Chinese output. The non-interactive heading 可用项目: and the test that asserts it are still there. AGENTS.md asks for English output and English assertions.
  • [P2 non-blocking, but it fits this PR] Code, docs and body disagree about hk- tokens. 51583f54 removes the hk- exemption, so init --http <url> --token hk-… --agent claude,codebuddy now exits 1 ("An access token works for one agent only…"), where 0.28.0 and c06e9cc7 exit 0. The body still says user-level hk- tokens stay shared. The member guide (docs/guide/member-guide.md:33) and the distribution guide (:304) say only sk- tokens are limited. The CI review calls this P1. We rate it lower, because main has no notion of hk- tokens and the CHANGELOG lists the refusal. Does the backend bind an hk- token to one agent? If yes, the docs, body and CHANGELOG should say every token. If no, the exemption should come back.
  • [P2 non-blocking, but it fits this PR] The CHANGELOG Breaking line covers only the one-token-per-agent rule. It should also say that a workspace syncs only after bind-project, and what a member sees after downgrading (the 401).
  • [P2 non-blocking] A refused multi-agent init leaves the state it refuses, with a false warning. On a fresh HOME, init --http <url> --token hk-… --agent claude,codebuddy --scope user exits 1. It warns that report/sync "keeps the credentials this machine already had", but the machine had none. Afterwards config.json holds the shared token, both tools have hooks, and after bind-project --skip both report with it. Refusing before anything is written would fix both.
  • [P3 nit] Two edge cases where the inferred token owner is wrong, and a test comment that cites "(#review)". Details are in our run notes and can be shared.

Locally, npm ci, build, tsc and lint pass on the head and on the merge, and your three unit files pass (319/319). At the time of this comment, CI Lint & Test on Ubuntu, e2e shards 1/3 and 3/3, and Windows had passed. macOS and shard 2/3 were still running.

@jeff-r2026 This one is yours to merge: the record, bind-project, the English heading and the hk- scope question are what's left from our side.

…eeps a user token (hk-) in the shared slot, so it is accepted with any number of --agent and stays available to every agent.
@github-actions

Copy link
Copy Markdown
  • [P1 blocking] Missing real-CLI verification record — PR description, Test Plan. Still unresolved: this changes runtime behavior, but the description records only type checking, lint, and Vitest. The first trusted Code Review Rules item requires an end-to-end / real-CLI verification record. Document one representative successful run; adding E2E tests is not an execution record.

  • [P1 blocking] A user token cannot replace a revoked agent token — src/local-agent.ts:4950. With agentTokens.codebuddy = 'sk-revoked', running init --http <same-endpoint> --agent codebuddy --token hk-valid --force preserves that override because perAgent is false for hk-. CodeBuddy therefore keeps sending the revoked token instead of the valid shared credential, breaking report/sync authentication. Clear the targeted agent’s override when explicitly switching it to a user token. This earlier scenario is broken again.

  • [P2 non-blocking] Project binding still chooses a revoked credential despite another working — src/local-agent.ts:1119. With no shared token and { codebuddy: 'sk-revoked', claude: 'sk-valid' }, agent-less project requests select only the first credential. bind-project cannot list projects or bind the workspace. Retry another configured credential after an authentication rejection.

  • [P2 non-blocking] Environment-provided tokens bypass per-agent updates — src/local-agent.ts:4919. With a revoked CodeBuddy override, TEAMAI_API_TOKEN=sk-new teamai init --http <same-endpoint> --agent codebuddy --force leaves that override unchanged: initHttp resolves the environment credential, but passes only options.token here. Pass the resolved API key so the documented environment-token path also updates the selected agent.

  • [P2 non-blocking] New non-interactive output still violates the English-output requirement — src/local-agent.ts:1375. The heading remains 可用项目:, and the added test explicitly asserts it. The trusted base requires English CLI output and English assertions. Translate the heading and update the assertion.

Resolved: The earlier hk- multiple-agent restriction and onboarding-documentation findings are fixed.

Only the specified diff and trusted base criteria were read. No PR code was run, built, or installed.

@SaulMoro

Copy link
Copy Markdown
Collaborator

This was generated by AI during triage.

@JasonRUAN Thanks, a0a477df settles the hk- question: a user token stays shared with any number of --agent, and the CHANGELOG now says only sk- tokens are bound to one agent. That matches the guides.

It brings one earlier point back, which the CI review also reports:

  • [P2 non-blocking, but it fits this PR] A user token no longer replaces a revoked agent token. Take a machine with agentTokens.codebuddy = 'sk-…' (revoked) that runs init --http <same endpoint> --agent codebuddy --token hk-… --force. agentScoped is false, so agent is undefined and perAgent is false. agentTokens keeps CodeBuddy's revoked entry, and the hk- token goes to the shared slot. tokenForAgent prefers the agent's own entry, so CodeBuddy keeps reporting with the revoked token and gets 401. 51583f54 fixed this ("The codebuddy access token on this machine no longer works; using the new one"). Clearing the named agent's entry when it's given a user token would keep both fixes. We read this from the code at src/local-agent.ts:4925-4970 and didn't rerun it.

The CI review adds one point we haven't checked: TEAMAI_API_TOKEN=sk-… init --http … --agent codebuddy --force doesn't update CodeBuddy's entry, because only options.token reaches initLocalAgentHttp.

Everything else from our last comment still stands: the real-CLI record in the body ([P1 blocking]), bind-project with a revoked first token, the 可用项目: heading, the rest of the Breaking line, and the refused multi-agent init.

@jeff-r2026 For your merge call.

… (hk-), that agent's own token entry is now cleared, so it falls back to the shared credential instead of keeping to send a revoked one.
@github-actions

Copy link
Copy Markdown
  • [P1 blocking] Missing real-CLI verification record — PR description, Test Plan. Still unresolved: this changes runtime behavior, but the description lists only type checking, lint, and Vitest. The first trusted Code Review Rules item requires one representative end-to-end / real-CLI execution record; adding E2E tests is not that record.

  • [P1 blocking] Switching multiple agents to a user token preserves revoked overrides — src/local-agent.ts:4955. With revoked CodeBuddy and Claude entries, init --http <same-endpoint> --token hk-valid --agent codebuddy,claude --force accepts the shared token but deletes neither override because namedAgent exists only for a single selection. Both agents keep sending revoked credentials and fail report/sync authentication. Clear the overrides for every explicitly selected agent when switching to a user token.

  • [P2 non-blocking] Project binding still selects a revoked credential despite another working — src/local-agent.ts:1119. With no shared token and { codebuddy: 'sk-revoked', claude: 'sk-valid' }, agent-less project requests use only the first token, so bind-project fails despite an available valid credential. Retry another configured credential after an authentication rejection. The earlier finding remains unresolved.

  • [P2 non-blocking] Environment-provided tokens still bypass per-agent updates — src/local-agent.ts:4919. initHttp resolves TEAMAI_API_TOKEN, but passes only options.token to this function. Consequently, TEAMAI_API_TOKEN=sk-new teamai init --http <same-endpoint> --agent codebuddy --force leaves a revoked CodeBuddy override unchanged. Pass the resolved API key. The earlier finding remains unresolved.

  • [P2 non-blocking] No-force credential updates still hit the outer initialization guard — src/local-agent.ts:4937. This exemption is reached only after initHttp asks whether to overwrite an existing installation. In a non-interactive re-init without --force, that guard aborts before updating the selected agent’s credential. Apply the same-endpoint credential-update exemption to the outer guard.

  • [P2 non-blocking] New non-interactive output still violates the English-output requirement — src/local-agent.ts:1375. The heading remains 可用项目:, and the added test asserts it. The trusted base requires English CLI output and English assertions. Translate both. The earlier finding remains unresolved.

Resolved: The single-agent revoked-token replacement, hk- multiple-agent restriction, and onboarding-documentation findings are fixed.

Only the specified diff, including relevant context, and trusted base review criteria were read. No PR code was run, built, or installed.

@SaulMoro

Copy link
Copy Markdown
Collaborator

This was generated by AI during triage.

@JasonRUAN Thanks, 2c95ae03 fixes the single-agent case: --agent codebuddy --token hk-… now drops CodeBuddy's own entry, so it falls back to the shared user token.

The CI review is right that the same happens with several agents. namedAgent is set only when --agent names one tool. So init --http <same endpoint> --token hk-… --agent codebuddy,claude --force keeps both agents' revoked entries, and both keep getting 401. Since a user token is now accepted with any number of --agent, clearing the entry of every named agent closes it. We read this from the code (src/local-agent.ts:4955) and didn't rerun it.

The rest of our earlier comments still stands, starting with the real-CLI record in the body ([P1 blocking]).

@jeff-r2026 For your merge call.

…to a shared user token

When `init --http`/`add-http` re-enrolls a shared user token (`hk-`) for
several agents at once via `--agent`, each named agent now has its own
per-agent `sk-` entry removed so it reports with the shared token instead
of a stale, possibly revoked credential.

Previously only one of the named agents was cleared — the rest kept their
old per-agent entries, so a revoked or rotated token outlived the switch
and those agents kept sending a dead credential. The fix drops the
per-agent entry of every agent listed in `--agent` (filterAgents) rather
than a single matched agent.

A test switches multiple agents to a shared user token and asserts the
unnamed agent keeps its own token, matching the updated CHANGELOG note.
@github-actions

Copy link
Copy Markdown
  • [P1 blocking] Missing real-CLI verification record — PR description, Test Plan. This changes runtime behavior, but the description records only type checking, lint, and Vitest. The first trusted Code Review Rules item requires one representative end-to-end / real-CLI execution record. Adding E2E tests is not a record of running them. Still unresolved.

  • [P2 non-blocking] Project binding still selects a revoked credential despite another working — src/local-agent.ts:1119. With no shared token and { codebuddy: 'sk-revoked', claude: 'sk-valid' }, agent-less project requests use only the first token. bind-project fails despite an available valid credential. Retry another configured credential after an authentication rejection.

  • [P2 non-blocking] Environment-provided tokens still bypass per-agent updates — src/init.ts:936. initHttp resolves the API key but passes only options.token. With a revoked CodeBuddy override, TEAMAI_API_TOKEN=sk-new teamai init --http <same-endpoint> --agent codebuddy --force preserves the revoked credential. Pass the resolved API key.

  • [P2 non-blocking] No-force credential updates still hit the outer initialization guard — src/local-agent.ts:4937. The same-endpoint exemption cannot bypass the earlier guard at src/init.ts:808. Non-interactive re-init of an existing installation without --force aborts before updating the selected agent’s credential. Apply the exemption to the outer guard too.

  • [P2 non-blocking] New non-interactive output still violates the English-output requirement — src/local-agent.ts:1375. The heading remains 可用项目:, and src/__tests__/local-agent.test.ts:441 asserts it. The trusted base requires English CLI output and English assertions. Translate both.

Resolved: Switching either one or multiple explicitly selected agents to an hk- token now clears their overrides. The earlier user-token multiple-agent restriction and onboarding-documentation findings are also fixed.

Only the specified diff, relevant context, supplied PR description, and trusted base criteria were read. No PR code was run, built, or installed.

@github-actions

Copy link
Copy Markdown
  • [P2 non-blocking] Project binding still selects a revoked credential despite another working — src/local-agent.ts:1123. With no shared token and { codebuddy: 'sk-revoked', claude: 'sk-valid' }, agent-less project requests use only the first token. bind-project fails despite an available valid credential. Retry another configured credential after an authentication rejection.

  • [P2 non-blocking] Environment-provided tokens still bypass per-agent updates — src/init.ts:943. initHttp resolves the API key but passes only options.token. Consequently, TEAMAI_API_TOKEN=sk-new teamai init --http <same-endpoint> --agent codebuddy --force preserves a revoked CodeBuddy override. Pass the resolved API key.

  • [P2 non-blocking] No-force credential updates still hit the outer initialization guard — src/init.ts:774. The same-endpoint exemption does not bypass the re-init guard at src/init.ts:816. In an existing user-scope installation, non-interactive init --http <same-endpoint> --token sk-new --agent codebuddy --scope user aborts before updating the credential. Apply the exemption to that guard too.

  • [P2 non-blocking] New non-interactive output still violates the English-output requirement — src/local-agent.ts:1379. The heading remains 可用项目:, and the added test asserts it. The trusted base requires English CLI output and English assertions. Translate both.

Resolved: The description now supplies sufficient representative real-CLI verification. Single- and multiple-agent switches to hk- clear the selected overrides; the earlier user-token multiple-agent restriction and onboarding-documentation findings are also fixed.

Only the specified diff, relevant context, supplied description, and trusted base criteria were read. No PR code was run, built, or installed.

@SaulMoro

Copy link
Copy Markdown
Collaborator

This was generated by AI during triage.

Update at 6b67bc75. The changes since our last note are the merge of main and the isAgentToken refactor.

  • Real-CLI record: the PR body now has one, so our earlier [P1 blocking] point is resolved. The CI review agrees.
  • The refactor: it's behavior-neutral. initLocalAgentHttp now comes from the import that initHttp already does at the top, and the only change is that isAgentToken trims the token before checking sk-. The branch also merges cleanly with current main (d33a911e), and CI is green.

Still open from our earlier comments, for the maintainer to weigh:

  • [P2 non-blocking] The non-interactive bind-project output and the hook message still print 可用项目:, and the new test asserts it. AGENTS.md requires English CLI output and English test assertions.
  • [P2 non-blocking] With no shared token, bind-project uses only the first per-agent token, so a revoked one fails even when another agent's token works.
  • [P2 non-blocking] TEAMAI_API_TOKEN=sk-… teamai init --http … --agent <tool> --force does not update that agent's own token, because initHttp passes only options.token.

The CI review's new point is that a same-endpoint token update without --force still stops at the re-init guard. From the code (src/init.ts, re-init guard after the scope summary), a non-interactive run answers no and aborts. That's the same as any re-init on main, so I'd rate it a [P3 nit] rather than a regression.

@jeff-r2026 for the call on the remaining items.

@jeff-r2026
jeff-r2026 merged commit e91cd5f into Tencent:main Oct 29, 2026
18 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants