@te-river/opencode-team-mode 1.7.1 → 1.7.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -287,6 +287,7 @@ a hard `deny` with no consent path.
287
287
  | Roles are not in the picker | Step 2 never ran, or ran against a different `--dir`. `ls ~/.config/opencode/agents` |
288
288
  | The default silently became `build` | `default_agent` names an agent that does not exist — re-run Step 2, then Step 3 |
289
289
  | A hand-written `agents/Team.md` was not updated | By design: the generator refuses files it did not create. Move yours aside, or pass `--force` |
290
+ | The picker shows the lead as `team`, or Team is not the default | The host takes an agent's ID from the FILE NAME. A machine that ever carried the pre-1.7 `team.md` registers the lead as `team`, while the installer writes `default_agent: "Team"` — and the documented behaviour for a default naming an agent it cannot find is a silent fall back to `build`. **Fixed in 1.7.2**: the generator renames a case-only entry it has proven is the same file to the canonical spelling. On 1.7.1 or older a re-run does NOT fix it (a write through `Team.md` keeps the old spelling on Windows) — rename by hand in two steps: `team.md` → `Team.md` |
290
291
  | `/team-plan` etc. run but not as the specialist | 2.x documents `mode: subagent` as "runs only in a child session"; if a command selecting a specialist does not behave, the fix is `mode: "all"` in the role definition (report it — this is the one item in this flow still unverified on a live host) |
291
292
  | `tm_webfetch` on an odd site says it refused **without asking anyone** | Expected on 2.x: a plugin cannot raise a dialog. Use a source that works, or let the host's own permission rule allow it; the address red line (metadata / private) has no consent path at all, on either generation |
292
293
  | `task`/`subagent` seems to block the lead | On 2.x the plugin forces `background: true`; if you also set the old `OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS`, unset it — it is a v1 flag and only confuses the picture |
@@ -522,6 +523,7 @@ env-FILE 面是硬 `deny`、没有同意路径。
522
523
  | 选择器里没有那六个角色 | 第 2 步没跑,或者 `--dir` 指到了别处。`ls ~/.config/opencode/agents` |
523
524
  | 默认 agent 悄悄变成 `build` | `default_agent` 指向了一个不存在的角色——重跑第 2 步,再做第 3 步 |
524
525
  | 手写的 `agents/Team.md` 没被更新 | 这是设计:生成器拒绝覆盖不是它生成的文件。把你的文件挪开,或显式加 `--force` |
526
+ | 选择器里领队显示成 `team`,或者 Team 不是默认 | 宿主按**文件名**取 agent id。机器上只要曾经有过 1.7 之前的 `team.md`,领队就以 `team` 注册,而安装器写的是 `default_agent: "Team"`——按文档,默认值指向一个找不到的 agent 时会**静默回退 build**。**1.7.2 已修**:生成器会把"已确认是同一个文件、仅大小写不同"的条目标成规范拼写。1.7.1 及更早版本重跑**修不好**(Windows 上从 `Team.md` 写入会保留旧拼写),需要手工两步改名:`team.md` → `Team.md` |
525
527
  | `/team-plan` 能跑但不是以那个专家身份跑 | 2.x 把 `mode: subagent` 文档化为"只在子会话里运行"。如果选定专家的命令行为不对,改法是角色定义里用 `mode: "all"`(请回报——这是本流程里唯一还没在活体宿主上验证过的一项) |
526
528
  | `tm_webfetch` 说它"没问任何人就直接拒绝" | v2 的预期行为:插件弹不出对话框。换一个不需要这次访问的源,或让宿主自己的权限规则放行;地址红线(元数据 / 私网)在两代宿主上都没有授权路径 |
527
529
  | `subagent` 好像把领队挡住了 | v2 上插件会强制 `background: true`;如果你顺手设了老的 `OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS`,请取消它——那是 v1 的开关,在这里只会误导判断 |
@@ -544,3 +544,34 @@ CALLED" note — a future layer would otherwise have to re-probe a seam the 2.0.
544
544
  settled. Context Pruning (`TM_PRUNE`, default 70%) is a different mechanism: it rewrites settled
545
545
  message BODIES into pointers inside an outgoing request and never asks the host to summarize
546
546
  anything. `[R]`
547
+
548
+ ### 2026-10-07 — the agent ID is the FILE NAME, and a case-only difference is not cosmetic `[L][R]`
549
+
550
+ **Measured on 2.0.24 / 2026-10-07 (this machine).** The global config directory held
551
+ `agents/team.md` — lowercase, left over from the pre-1.7 naming — while the generator had been
552
+ "writing" `agents/Team.md` on every install since. The host was asked directly:
553
+
554
+ - `opencode-cli run --standalone --agent Team --model nope/nope "x"` → `Agent not found: "Team"`
555
+ - `opencode-cli run --standalone --agent team --model nope/nope "x"` → resolves (fails only on
556
+ the fake model: `Model unavailable: nope/nope`)
557
+ - `opencode-cli api get /api/agent?directory=…` → `{"id":"team","name":"team",…}` for the lead
558
+
559
+ So the on-disk spelling IS the registered id, and the installer's own
560
+ `default_agent: "Team"` named an agent that did not exist. Per the agents doc the fallback for
561
+ an unresolvable default is `build` — silent, which is exactly the failure the installer's
562
+ "write `default_agent` LAST, and read it back" rule exists to prevent; the read-back cannot see
563
+ it, because the key IS on disk and the ID is what is wrong. `[L]`
564
+
565
+ **Why every install failed to fix it.** `writeFileSync(<dir>/Team.md)` on a case-insensitive,
566
+ case-PRESERVING filesystem opens the existing `team.md` and leaves that name alone — and the
567
+ reclaim pass deliberately refused to delete the case-only entry (deleting it would delete the
568
+ file just written: the #47/#48 guard). Nothing in the pipeline could ever change the spelling.
569
+ The fix is an explicit two-step rename (`team.md` → temp → `Team.md`; a case-only `rename` on
570
+ win32 is itself a no-op), performed ONLY on an entry the identity verdict has already PROVEN is
571
+ the same file as a target, with a restore path if the second step throws. `[R]`
572
+
573
+ **The lesson, stated in the general form.** "Is the tool loaded" and "is the file on disk" were
574
+ both yes here — the two checks the installer prints. What no check covered was *which id the
575
+ host derives from it*, and that is the difference between Team being the default and a silent
576
+ `build`. A verifiable claim has to name the artefact the host actually keys on (the FILE NAME),
577
+ not the one we wrote (the path we computed). `[R]`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@te-river/opencode-team-mode",
3
- "version": "1.7.1",
3
+ "version": "1.7.2",
4
4
  "description": "Team collaboration mode plugin for OpenCode Desktop — injects specialized agents (architect, reviewer, implementer, tester, researcher) and team workflow commands into your OpenCode workspace.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -29,7 +29,7 @@
29
29
  * Refuses to touch a file it did not generate unless --force is given.
30
30
  */
31
31
 
32
- import { mkdirSync, readFileSync, writeFileSync, existsSync, readdirSync, realpathSync, statSync } from "node:fs"
32
+ import { mkdirSync, readFileSync, writeFileSync, existsSync, readdirSync, realpathSync, renameSync, statSync } from "node:fs"
33
33
  import { join } from "node:path"
34
34
  import { homedir } from "node:os"
35
35
  import { pathToFileURL } from "node:url"
@@ -366,9 +366,45 @@ function reclaimStale(dir, keepNames, label) {
366
366
  }
367
367
  }
368
368
  if (sameAs) {
369
- // Same file as a target, stored under a different case. There is nothing
370
- // stale here, and deleting it would delete the file we just wrote.
371
- caseNotes.push(`保留 ${file} — 与目标 ${sameAs} 是同一个文件(本机不区分大小写),未删除`)
369
+ // Same file as a target, stored under a DIFFERENT case. Nothing here is
370
+ // stale and deleting it would delete the file we just wrote — but leaving
371
+ // the old spelling is a real defect, not a cosmetic one: the HOST TAKES AN
372
+ // AGENT'S ID FROM THE FILE NAME. A machine that still carries the
373
+ // pre-1.7 `team.md` registers the lead as `team`, so the installer's own
374
+ // `default_agent: "Team"` resolves to nothing and the host falls back to
375
+ // `build` — silently, because that fallback is documented as the normal
376
+ // move for a default that names an agent it cannot find. Measured on
377
+ // 2.0.24 / 2026-10-07 on this very machine: `--agent Team` answered
378
+ // `Agent not found: "Team"`, `--agent team` resolved, and
379
+ // `GET /api/agent` listed `{"id":"team","name":"team"}`.
380
+ //
381
+ // Windows and APFS keep the EXISTING spelling when a write opens that file
382
+ // through the other one, which is how the stale name survives every
383
+ // install, so the rename has to be explicit — and on win32 a case-only
384
+ // rename is itself a no-op, hence the temporary name in the middle.
385
+ if (printOnly) {
386
+ caseNotes.push(`保留 ${file} — 与目标 ${sameAs} 是同一个文件(--print,未修正大小写)`)
387
+ continue
388
+ }
389
+ const target = join(dir, sameAs)
390
+ const tmp = join(dir, `.${entry}.casefix-${process.pid}`)
391
+ try {
392
+ renameSync(file, tmp)
393
+ renameSync(tmp, target)
394
+ caseNotes.push(`已修正大小写 ${entry} → ${sameAs} — 与目标是同一个文件;宿主按文件名取 agent id,不改就一直挂在旧 id 上`)
395
+ } catch (err) {
396
+ // Never leave a half-rename behind: if the second step failed, put the
397
+ // file back under the name we found it (the stale spelling is survivable;
398
+ // a file stranded under the temporary name is not).
399
+ try {
400
+ if (existsSync(tmp) && !existsSync(target)) renameSync(tmp, target)
401
+ } catch {
402
+ /* it stays under the temporary name, and the note below says so */
403
+ }
404
+ caseNotes.push(
405
+ `大小写未修正 ${entry}(目标 ${sameAs}):${String((err && err.message) || err).replace(/\s+/g, " ").slice(0, 80)}`,
406
+ )
407
+ }
372
408
  continue
373
409
  }
374
410
  if (undecidable) {