@te-river/opencode-team-mode 1.8.3 → 1.8.4
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.
- package/README.md +499 -1021
- package/README.zh-CN.md +493 -920
- package/dist/host/v2-tail.d.ts +5 -1
- package/dist/host/v2-tail.d.ts.map +1 -1
- package/dist/host/v2-tail.js +9 -2
- package/dist/host/v2-tail.js.map +1 -1
- package/dist/tm/config-layers.js +1 -1
- package/dist/tm/config-layers.js.map +1 -1
- package/dist/tm/dispatch.d.ts +17 -0
- package/dist/tm/dispatch.d.ts.map +1 -1
- package/dist/tm/dispatch.js +52 -7
- package/dist/tm/dispatch.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,1021 +1,499 @@
|
|
|
1
|
-
# OpenCode TeamMode
|
|
2
|
-
|
|
3
|
-
**[English](./README.md)** | **[中文](./README.zh-CN.md)**
|
|
4
|
-
|
|
5
|
-
[](https://www.npmjs.com/package/@te-river/opencode-team-mode)
|
|
6
|
-
[](https://www.npmjs.com/package/@te-river/opencode-team-mode)
|
|
7
|
-
[](./LICENSE)
|
|
8
|
-
|
|
9
|
-
> 🤝 **Your OpenCode just hired a team.**
|
|
10
|
-
>
|
|
11
|
-
> Six specialized agents — a Lead, an Architect, an Implementer, a Reviewer, a Tester and a Researcher — with governed tools, structured handoffs, and a plan-first approval gate. One plugin, no files to copy.
|
|
12
|
-
|
|
13
|
-
> 💡 **Best for medium-to-large projects.** The governance layer (approval gate, context offload, tool allowlists) is an asset once a codebase has real surface area — and mostly overhead on tiny scripts and one-off questions. Use it where the work is.
|
|
14
|
-
|
|
15
|
-
---
|
|
16
|
-
|
|
17
|
-
## 📑 Contents
|
|
18
|
-
|
|
19
|
-
- [Why TeamMode](#-why-teammode)
|
|
20
|
-
- [The team](#-the-team)
|
|
21
|
-
- [Install](#-install)
|
|
22
|
-
- [Usage](#-usage)
|
|
23
|
-
- [JIT context and retry](#-jit-context-and-retry)
|
|
24
|
-
- [Governed tools & security](#-governed-tools--security)
|
|
25
|
-
- [Configuration](#️-configuration)
|
|
26
|
-
- [How the team works](#-how-the-team-works)
|
|
27
|
-
- [Customization](#-customization)
|
|
28
|
-
- [Troubleshooting](#-troubleshooting)
|
|
29
|
-
- [Uninstall](#️-uninstall)
|
|
30
|
-
|
|
31
|
-
---
|
|
32
|
-
|
|
33
|
-
## TL;DR — skip the docs
|
|
34
|
-
|
|
35
|
-
> **The lazy path:** paste this to any coding agent and let it do the work:
|
|
36
|
-
>
|
|
37
|
-
> ```text
|
|
38
|
-
> Install the OpenCode plugin @te-river/opencode-team-mode. This package supports **OpenCode 2.x
|
|
39
|
-
> only** — the 1.18.x personality was removed in the 1.7.0 line
|
|
40
|
-
>
|
|
41
|
-
> (a 2.x plugin cannot create an agent, so the six roles and six `/team-*` commands come
|
|
42
|
-
>
|
|
43
|
-
>
|
|
44
|
-
>
|
|
45
|
-
>
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
|
72
|
-
|
|
73
|
-
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
|
90
|
-
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
2.x
|
|
111
|
-
(
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
- **
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
session,
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
`background: true`
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
---
|
|
230
|
-
|
|
231
|
-
##
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
|
238
|
-
|
|
239
|
-
|
|
|
240
|
-
|
|
|
241
|
-
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
|
312
|
-
|
|
313
|
-
| `
|
|
314
|
-
| `
|
|
315
|
-
| `
|
|
316
|
-
| `
|
|
317
|
-
| `
|
|
318
|
-
| `
|
|
319
|
-
| `
|
|
320
|
-
| `
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
>
|
|
384
|
-
|
|
385
|
-
> the
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
`tm_search`
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
- **
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
server's own browser service, so a plugin cannot register a page of its own
|
|
501
|
-
(forensics: `docs/research/browser-pane.md`). The host catalog is policed by
|
|
502
|
-
`src/host/v2-browser-gate.ts` at `execute.before` — the URL of every navigate/open,
|
|
503
|
-
the path of every `browser_preview`, and every browser URL named inside an
|
|
504
|
-
`execute` program are classified against the address red lines and the env-file
|
|
505
|
-
rule, and a refused call the host runs anyway has its page replaced with the same
|
|
506
|
-
refusal, so no out-of-policy content reaches the context, the store or the
|
|
507
|
-
trajectory. Calling convention differs from the deleted tool: `evaluate` takes
|
|
508
|
-
`{tabID, script}` where `script` is an **expression** (not function source) and
|
|
509
|
-
the return value must be a scalar you `JSON.stringify` yourself; snapshot tokens
|
|
510
|
-
read `@e8 [link]`. **Known cost, accepted:** on a host with no native browser
|
|
511
|
-
catalog (CLI / standalone) there is now no browser at all — the agent reports that
|
|
512
|
-
gap rather than simulating one.
|
|
513
|
-
|
|
514
|
-
When a fetch still returns **403 after the real-Chrome headers**, the error
|
|
515
|
-
is a DIRECTIVE: the gate is JS-challenge / TLS-fingerprint based and only a
|
|
516
|
-
real browser passes — the agent is told to open that URL with the host's
|
|
517
|
-
native `browser_navigate` (and read it with `browser_snapshot`). Search hit lists also
|
|
518
|
-
filter known noise: engine-internal wrappers (`so.com/link?`, `ai.so.com`)
|
|
519
|
-
and same-name-different-site domains (`maimai.cn` 脉脉 vs the maimai DX
|
|
520
|
-
game) never ride along — extend the hit blacklist with `hitBlacklist`.
|
|
521
|
-
|
|
522
|
-
Seeded allowlist (all three web tools; 22 hosts — baidu/moegirl/bilibili are
|
|
523
|
-
PARENT domains, so every sibling subdomain — baike.baidu.com,
|
|
524
|
-
mzh.moegirl.org.cn, space.bilibili.com — is covered):
|
|
525
|
-
`baidu.com`, `bdimg.com` (Baidu's own script + asset CDN — not a baidu
|
|
526
|
-
subdomain, and a page whose bundle we block is a blank page we would then
|
|
527
|
-
report as "no content"), `moegirl.org.cn`, `bilibili.com`, `www.sogou.com`,
|
|
528
|
-
`www.so.com`, `cn.bing.com`, `www.bing.com`, `zhihu.com`, `juejin.cn`,
|
|
529
|
-
`csdn.net`, `cnblogs.com`, `gitee.com`, `github.com`, `api.github.com`,
|
|
530
|
-
`raw.githubusercontent.com`, `gist.githubusercontent.com`, `ghproxy.net`
|
|
531
|
-
(mainland mirror for github raw), `stackoverflow.com`, `npmjs.org`, `pypi.org`,
|
|
532
|
-
`learn.microsoft.com` — plus the two JSON search-engine hosts
|
|
533
|
-
(`api.stackexchange.com`, `hn.algolia.com`) while the default seed is in play,
|
|
534
|
-
so 24 effective. Extend via `webfetchAllowedDomains` (`"*"` opens every host; a
|
|
535
|
-
custom list REPLACES the seed, so keep the engine hosts or `tm_search` loses its
|
|
536
|
-
targets). Architect / implementer / reviewer have NO network grant — web
|
|
537
|
-
questions come back as a reported gap, never simulated. The tester carries the
|
|
538
|
-
host's native `browser_*` tools ONLY, for governed UI verification of the project
|
|
539
|
-
(local dev servers, preview routes); open web fetching stays with the two
|
|
540
|
-
network roles.
|
|
541
|
-
|
|
542
|
-
**Out-of-allowlist targets are a gate, not a wall.** When a fetch / search /
|
|
543
|
-
browser-open points at a host outside the allowlist, the governed call **fails
|
|
544
|
-
closed with a refusal naming the exits** — on 2.x a plugin cannot raise the
|
|
545
|
-
host's dialog, so there is no window to wait for and a gate with no door is not
|
|
546
|
-
a gate. Your doors are `privateSpace: "allow"` in `team-mode.jsonc`, or that one
|
|
547
|
-
hostname in `webfetchAllowedDomains`. Below that gate sit the classes no config
|
|
548
|
-
opens: cloud-metadata / link-local / multicast / reserved / benchmarking ranges
|
|
549
|
-
are refused outright with no consent path, including their IPv4-mapped and
|
|
550
|
-
DNS64 spellings (changing the notation is not a bypass), and env-file URLs and
|
|
551
|
-
non-http(s) schemes are hard-rejected.
|
|
552
|
-
|
|
553
|
-
### Security: R6 env protection + R2 dangerous operations
|
|
554
|
-
|
|
555
|
-
**R6 is armed by default, and it is a `strict` default.** `envProtect` accepts
|
|
556
|
-
`strict` / `standard` / `off` and resolves to `strict` by default. With TeamMode
|
|
557
|
-
active:
|
|
558
|
-
|
|
559
|
-
- **Env-FILE reads are a hard `deny`** — a Team role's native `read` /
|
|
560
|
-
`edit` / `glob` / `grep` of a `.env` is refused with no consent path. The
|
|
561
|
-
host's `grep` reports the *pattern* as its permission resource rather than the
|
|
562
|
-
path, so the same rule also runs on the whole tool input (`grep SECRET .env`
|
|
563
|
-
cannot slip through). Templates (`*.env.example` / `.sample` / `.template` /
|
|
564
|
-
`.dist`) stay readable.
|
|
565
|
-
- **Env-variable reads in a shell command go to the host's permission prompt**
|
|
566
|
-
(`ask`). The per-command classifier decides, so an ordinary `git status` asks
|
|
567
|
-
nothing and an env dump still does; env reads no wildcard can express
|
|
568
|
-
(embedded `$VAR` / `${VAR}` / `$env:` inside another command, command
|
|
569
|
-
substitution) are **never** asked — they are refused. The audit log records
|
|
570
|
-
only tool name + pattern category + verdict — never command text, paths,
|
|
571
|
-
variable names or values.
|
|
572
|
-
|
|
573
|
-
**R2 dangerous operations (the same face).** Delete, git publish, network
|
|
574
|
-
fetch, package install/publish, process/system, privilege changes — none are
|
|
575
|
-
silently allowed. The normal verification stack (`npm test`, `tsc`,
|
|
576
|
-
`git status`) is NOT gated, so day-to-day work runs uninterrupted.
|
|
577
|
-
|
|
578
|
-
**The plugin never self-allows.** Every hook it registers can only make a rule
|
|
579
|
-
*stricter*; a permission it is asked about is replied `deny` or left alone, never
|
|
580
|
-
`allow`.
|
|
581
|
-
|
|
582
|
-
> ⚠️ **When you approve the host's own dialog, pick "once" — not "always".**
|
|
583
|
-
> Verified on the live host, "always" records a far broader rule than the command
|
|
584
|
-
> you saw: approving `Get-ChildItem env:PATH` with "always" stores `Get-ChildItem
|
|
585
|
-
> *`, so every later `Get-ChildItem` runs with no dialog at all. The web channel
|
|
586
|
-
> has the same trap and it is easier to fall into: one "always" on a host lets
|
|
587
|
-
> **every agent session in that project** open it. The reply says which path let
|
|
588
|
-
> a page in (static allowlist / the dialog you just answered / a saved rule that
|
|
589
|
-
> answered in milliseconds), because an agent that cannot tell them apart reports
|
|
590
|
-
> "no dialog" as "that site is allowed".
|
|
591
|
-
|
|
592
|
-
> Deferral is per-session: the env face only applies in sessions running
|
|
593
|
-
> TeamMode's generated roles. In any other session the plugin leaves the host's
|
|
594
|
-
> behaviour exactly as it found it.
|
|
595
|
-
|
|
596
|
-
### Repo hygiene
|
|
597
|
-
|
|
598
|
-
The offload/trajectory/memory stores live under `<repo>/.git/opencode-team/`
|
|
599
|
-
(outside a git repo: the OS temp dir, sharded per workspace as
|
|
600
|
-
`opencode-team/w-<hash>/`, so one project never reads another's payloads) —
|
|
601
|
-
**never your working tree**. Every
|
|
602
|
-
agent is instructed to delete scratch files before reporting done and to keep
|
|
603
|
-
throwaway work in the OS temp dir. A TTL sweeper reclaims old task dirs at
|
|
604
|
-
startup + hourly; the Team Lead never deletes boards itself, so you can audit
|
|
605
|
-
any run.
|
|
606
|
-
|
|
607
|
-
---
|
|
608
|
-
|
|
609
|
-
## ⚙️ Configuration
|
|
610
|
-
|
|
611
|
-
**Configuration is JSON, and only JSON.** `team-mode.jsonc` is the single
|
|
612
|
-
configuration source. There is no environment layer, and **no `TM_*`
|
|
613
|
-
configuration environment variable exists any more** — the ones you may still
|
|
614
|
-
find in old configs are simply unknown keys, reported at boot and never applied.
|
|
615
|
-
(Three `TM_*` names survive as internal/test switches, listed at the end of
|
|
616
|
-
this section.)
|
|
617
|
-
|
|
618
|
-
### Where the file lives
|
|
619
|
-
|
|
620
|
-
| Layer | Path | Precedence |
|
|
621
|
-
|---|---|---|
|
|
622
|
-
| **global** | `~/.config/opencode/team-mode.jsonc` (honours the host's `OPENCODE_CONFIG_DIR`) | lowest — applies to every project |
|
|
623
|
-
| **project** | `<dir>/team-mode.jsonc`, for every directory from the workspace **up to the filesystem root** | higher — direct files merge farthest→closest |
|
|
624
|
-
| **project** | `<dir>/.opencode/team-mode.jsonc`, same walk | **highest** — every `.opencode/` file overrides every direct file |
|
|
625
|
-
|
|
626
|
-
That walk and precedence are the host's own config-layering rule, copied rather
|
|
627
|
-
than invented. JSONC is supported: `//` and `/* */` comments are masked at the
|
|
628
|
-
byte level, so a commented-out key cannot read as a live one. A layer that fails
|
|
629
|
-
to parse is skipped **whole** (half a config is worse than none) while a single
|
|
630
|
-
bad key loses only itself; unknown keys are warned about and never applied.
|
|
631
|
-
`tm_stats` renders the result: per-key source, skipped layers, unknown keys, and
|
|
632
|
-
the auto-create state.
|
|
633
|
-
|
|
634
|
-
### It writes itself
|
|
635
|
-
|
|
636
|
-
When the global file is **absent**, the plugin creates it at boot — atomically,
|
|
637
|
-
idempotently, and **never overwriting an existing file**. What it writes is an
|
|
638
|
-
**inert** template: all **64** registry keys, each as a *comment* under a
|
|
639
|
-
one-line description, so the file parses to `{}` and sets nothing until you
|
|
640
|
-
uncomment what you want.
|
|
641
|
-
|
|
642
|
-
```jsonc
|
|
643
|
-
// 全局配置。取消注释即可生效;默认全部注释 = 什么都不设置。
|
|
644
|
-
{
|
|
645
|
-
// "offloadThreshold": 2000, // 卸载阈值(估算 token;等于阈值也卸载)。
|
|
646
|
-
// "envProtect": "strict", // R6 环境防护模式(strict/standard/off)。(红线:仅全局)
|
|
647
|
-
// …共 64 个键,每个都带一行说明
|
|
648
|
-
}
|
|
649
|
-
```
|
|
650
|
-
|
|
651
|
-
Turn it off with the plugin option `autoCreate: false`, or the internal switch
|
|
652
|
-
`TM_CONFIG_AUTOCREATE=off`.
|
|
653
|
-
|
|
654
|
-
### Red-line keys
|
|
655
|
-
|
|
656
|
-
Five keys are **honored only in the global file**: `envProtect`, `r6FineAsk`,
|
|
657
|
-
`privateSpace`, `webfetchAllowedDomains`, `bashReadonlyAllowed`. A project file
|
|
658
|
-
may set them, but the value is **ignored and reported** — a committed project
|
|
659
|
-
config must not be able to switch off a guard for everyone who clones the repo.
|
|
660
|
-
The rule is derived from the registry itself (`redLine: true` on the spec), so a
|
|
661
|
-
new red-line key is red-line by declaration.
|
|
662
|
-
|
|
663
|
-
### Plugin options
|
|
664
|
-
|
|
665
|
-
Options live on the plugin entry, not in `team-mode.jsonc`:
|
|
666
|
-
|
|
667
|
-
```jsonc
|
|
668
|
-
{
|
|
669
|
-
"plugins": [
|
|
670
|
-
{ "package": "@te-river/opencode-team-mode@latest",
|
|
671
|
-
"options": { "defaultAgent": true, "autoCreate": true, "ttlDays": 7, "envProtect": true } }
|
|
672
|
-
]
|
|
673
|
-
}
|
|
674
|
-
```
|
|
675
|
-
|
|
676
|
-
| Option | Default | Meaning |
|
|
677
|
-
|---|---|---|
|
|
678
|
-
| `defaultAgent` | `true` | Promote Team to the default agent slot. Opt out with `false`. |
|
|
679
|
-
| `autoCreate` | `true` | Write the inert global `team-mode.jsonc` at boot when absent. |
|
|
680
|
-
| `ttlDays` (alias `blackboardTtlDays`) | `5` | Blackboard retention, valid range (0, 365]. |
|
|
681
|
-
| `envProtect` | `true` | Plugin-wide kill switch; `false` resolves the R6 mode to `off`. |
|
|
682
|
-
|
|
683
|
-
On 2.x a plugin entry is either a **string** or an **object** with `package` and
|
|
684
|
-
`options`. The 1.x tuple form `["@te-river/…", { … }]` is rejected
|
|
685
|
-
(`path=$.plugins.1 kind=invalid`, measured 2026-10-06). Options do arrive — with
|
|
686
|
-
the object form the boot row reads `board_ttl_days=9` for `{ "ttlDays": 9 }`, so
|
|
687
|
-
this is measured, not documented-and-hoped.
|
|
688
|
-
|
|
689
|
-
### Model choice
|
|
690
|
-
|
|
691
|
-
Every judgment — triage, decomposition, dispatch briefs, synthesis, review
|
|
692
|
-
verdicts — flows through the Team Lead. A weak model in that seat degrades the
|
|
693
|
-
whole pipeline no matter how strong the specialists are. Pin your best reasoning
|
|
694
|
-
model to `team`:
|
|
695
|
-
|
|
696
|
-
```jsonc
|
|
697
|
-
{
|
|
698
|
-
"agent": {
|
|
699
|
-
"team": { "model": "anthropic/claude-opus-4-5" }, // Lead earns your best model
|
|
700
|
-
"implementer": { "model": "anthropic/claude-sonnet-4-6" } // specialists tolerate cheaper
|
|
701
|
-
}
|
|
702
|
-
}
|
|
703
|
-
```
|
|
704
|
-
|
|
705
|
-
Your own agents named `team` / `architect` / … always take precedence; the
|
|
706
|
-
plugin never clobbers user definitions. See [Customization](#-customization)
|
|
707
|
-
for overrides, extra agents and disabling roles.
|
|
708
|
-
|
|
709
|
-
### Key reference (all 64)
|
|
710
|
-
|
|
711
|
-
<details>
|
|
712
|
-
<summary>All 64 keys — click to expand</summary>
|
|
713
|
-
|
|
714
|
-
**Offload, previews and the store**
|
|
715
|
-
|
|
716
|
-
| Key | Type | Default | Meaning |
|
|
717
|
-
|---|---|---|---|
|
|
718
|
-
| `offloadThreshold` | number | `2000` | Global offload boundary (estimated tokens, CJK-aware), used when the content class is unknown. A result AT the threshold offloads. |
|
|
719
|
-
| `offloadThresholdText` | number | `4000` | Boundary for prose (text / log / markdown). Inherits the global value unless set. |
|
|
720
|
-
| `offloadThresholdData` | number | `2000` | Boundary for structured payloads (json / csv / code / binary). Same inheritance. |
|
|
721
|
-
| `previewLines` | number | `20` | Raw lines a preview builder may scan. |
|
|
722
|
-
| `previewMaxTokens` | number | `80` | Hard preview cap. |
|
|
723
|
-
| `fetchMaxLines` | number | `2000` | `tm_fetch` per-segment line cap. |
|
|
724
|
-
| `blackboardDir` | string | auto | Run-payload store dir. Empty = `<repo>/.git/opencode-team/blackboard`. Explicit = absolute or project-relative. |
|
|
725
|
-
| `trajectoryDir` | string | auto | Trajectory store dir. Empty = `<repo>/.git/opencode-team/trajectory`. |
|
|
726
|
-
| `blackboardTtlDays` | number | `7` | Handle TTL and the physical sweep period for expired run dirs. |
|
|
727
|
-
| `storeReclaim` | `on`/`off` | `on` | At boot, reclaim what an upgrade left behind: a per-workspace shard idle past the TTL, and the pre-shard `blackboard/` + `trajectory/` at the temp-dir fallback. **Only TTL-expired entries are ever removed** — a fresh run dir survives, because a session started before the upgrade may still be writing there. `off` leaves the disk exactly as found. |
|
|
728
|
-
| `taskOffload` | `on`/`off` | `on` | Keep the host's background sub-agent inside the context budget: when it finishes, the injected full reply is replaced by a preview + a `tm_join` pointer. Touches only synthetic parts carrying the host's own `<task id=… state="completed">` / `<subagent … state="completed">` envelope and over the text threshold — a typed message is never touched even if it contains a perfect envelope, and a `state="error"` child is never rewritten. **Nothing is copied to disk**: the text stays where the child session already wrote it. `off` restores the host's verbatim injection. |
|
|
729
|
-
| `subagentConcurrency` | number | `8` | Ceiling on **running + in-flight** sub-agent dispatches per caller. In-flight = a dispatch the gate admitted but the host has not registered yet, so a **burst in one message is capped too** — the old registered-only count could not do that (24 parallel dispatches all passed it, then later ones were refused). `0` disables. A slot is released when the child registers, when the call ends without one, or after a 60 s TTL, so a lost call cannot leak it. |
|
|
730
|
-
| `boardMaxChars` | number | `200000` | `tm_board_write` per-file character cap — over it the write **refuses** (with a "split the topic" hint) rather than truncating a deliverable. |
|
|
731
|
-
| `boardMaxFiles` | number | `200` | Markdown files allowed per session folder. The TTL sweeper is the only reclaim path, so the refusal names it. |
|
|
732
|
-
| `joinMaxWaitMs` | number | `60000` | Ceiling on `tm_join { waitMs }`. Was 300 000, and a lead parked in it twice in a row (19 min of nothing) while its children worked — waiting is not parallelism, so the default now says "check, then work". A second consecutive wait after nothing settled is cut to 10 s and answered with what to do instead. |
|
|
733
|
-
| `ledgerMaxItems` | number | `200` | Ceiling on `tm_ledger` items per session. The host's `ctx.storage` has no TTL and no quota (measured), so the list refuses to grow past this instead of quietly dropping the oldest asks — and a refusal is something the lead can act on, while a silent truncation is a claim nobody can re-check. |
|
|
734
|
-
|
|
735
|
-
**Memory**
|
|
736
|
-
|
|
737
|
-
| Key | Type | Default | Meaning |
|
|
738
|
-
|---|---|---|---|
|
|
739
|
-
| `memoryGlobalDir` | string | auto | GLOBAL tier store. Empty = `~/.opencode-team/memories/global`. |
|
|
740
|
-
| `memorySessionTtlMin` | number | `240` | Session-tier entry TTL (lazy + boot sweep). |
|
|
741
|
-
| `memoryMaxEntries` | number | `200` | Per-scope entry cap; over it `add` fails on purpose — run `compact`. |
|
|
742
|
-
| `memoryStaleDays` | number | `30` | Age after which search hits are tagged `[stale Nd]` (`0` disables). |
|
|
743
|
-
| `memorySessionPersist` | string | `""` | `""` = in-process only; `"1"` also writes session entries under `memories/sessions/<sid>/`. |
|
|
744
|
-
|
|
745
|
-
**Web and search**
|
|
746
|
-
|
|
747
|
-
| Key | Type | Default | Meaning |
|
|
748
|
-
|---|---|---|---|
|
|
749
|
-
| `webfetchAllowedDomains` | string[] | 22-host seed | **red line.** Outbound allowlist (`"*"` = any host; `[]` = deny all). A custom list **replaces** the seed — keep the engine hosts. `"*"` does **not** cover private space: loopback / RFC1918 / CGNAT / `.localhost` still need `privateSpace: "allow"`, and non-routable ranges (169.254.0.0/16 metadata, 0.0.0.0/8, multicast, reserved, plus the IPv4-mapped and DNS64 spellings of the same target) are a hard red line no setting opens. |
|
|
750
|
-
| `searchDefaultEngine` | string | `auto` | Engine when no `engine` arg is given (`auto` = classify + parallel fan-out + RRF fusion; any table name also pins a manual default). |
|
|
751
|
-
| `searchMaxHits` | number | `10` | Hits kept per engine leg and in the fused list. |
|
|
752
|
-
| `searchWeights` | record | `{}` | Per-engine fusion weight overrides, e.g. `{"bing": 0.3}`; anything unset keeps the built-in table. |
|
|
753
|
-
| `searchDisabledEngines` | string[] | `[]` | Engines removed from the roster AND from every `auto` route. |
|
|
754
|
-
| `searchRelevanceFloor` | number | `0.35` | Weight fraction kept by a hit sharing no query token with its title/snippet/host (demotes junk without deleting an engine). |
|
|
755
|
-
| `hitBlacklist` | string[] | `[maimai.cn]` | Extra domains never listed as search hits (same-name-different-site noise like 脉脉). Merged with the built-in entry. |
|
|
756
|
-
| `webCacheTtlSec` | number | `300` | How long a governed fetch may re-serve the same URL (`0` = off). One store shared by `tm_webfetch` / `tm_search`; entries are hash-named (a token-bearing query never hits disk) and are only read or written for hops the STATIC allowlist admitted — so a hit can neither resurrect a removed host nor substitute for consent. A re-served page says 缓存命中. |
|
|
757
|
-
|
|
758
|
-
**Rounds, dispatch and context**
|
|
759
|
-
|
|
760
|
-
| Key | Type | Default | Meaning |
|
|
761
|
-
|---|---|---|---|
|
|
762
|
-
| `prune` | `on`/`off` | `on` | Context pruning: replace a SETTLED message body with a self-explaining pointer (an offload handle, a child id, a note). Evidence is never destroyed — the reply skeleton, GOAL/ACCEPTANCE, provenance, system messages, a still-running child and an uncollected child id are protected whole, and the untouched messages stay byte-exact. `off` disables. |
|
|
763
|
-
| `pruneAtPercent` | number | `70` | Prune once the request reaches this share of the model's window, derived from `limit.context` — never a hard-coded token count. **Clamped to 40–90.** |
|
|
764
|
-
| `pruneKeepTailPercent` | number | `40` | Share of the window kept verbatim at the tail; the newest message is always kept. |
|
|
765
|
-
| `pruneOffload` | `on`/`off` | `on` | Offload a message body to the run store BEFORE its text is replaced, so the stub carries a full `tm_fetch { ref, access_token, expire_at }` handle — otherwise "prune" is permanent deletion of the body. A body an earlier request already stored reuses that handle (one payload, not one per request); a failed offload falls back to the echo note and is counted (`prune_offload_failed`), never thrown. `off` = today's behaviour: pointer only, the body is not kept. |
|
|
766
|
-
| `pruneProtectPatterns` | regex list (comma separated) | `""` | Protection regexes for pruning: a message whose text matches ANY entry is never pruned, and the hits are counted (`prune_protect_matched`). An invalid entry is ignored (it never disables the ones next to it and never throws); an empty string = off, which is byte-identical to the behaviour without this key. Patterns containing a comma cannot be expressed — split on `,` is the parser. |
|
|
767
|
-
| `stateCard` | `on`/`off` | `on` | 任务状态卡 — ONE ≤300-token system entry on the LEAD's request: ledger counts plus open items (oldest trimmed first), the running children that belong to THIS session (an empty session id renders no children row at all), a shortened board root, and last round's offloaded-message count. Unchanged bytes write nothing (the card is compared byte-for-byte; the `hash:` line is a visible marker). `off` does not register the layer. |
|
|
768
|
-
| `dynamicTail` | `on`/`off` | `on` | Volatile context (state card / window entry / retry directive / split advice) is merged into ONE message at the END of the request (first line `[Team 动态上下文]`, each section headed by `### <name>`, fixed order state → window → retry → advice), so `system` carries only stable content and the provider's prefix cache can hit. A message array reused across requests has its old tail stripped before the rebuild (nothing accumulates); an empty sink writes not a single byte. `off` = the old behaviour: each producer injects its own `system` entry, byte-identical. |
|
|
769
|
-
| `splitAdvice` | `on`/`off` | `on` | Inject a split suggestion when a dispatch brief carries too many acceptance criteria. `off` stops the hint (the prompt discipline stays). |
|
|
770
|
-
| `splitBriefTokens` | number | `4000` | The brief token threshold that triggers it. **Clamped to ≥200.** |
|
|
771
|
-
| `splitMaxCriteria` | number | `3` | More acceptance criteria than this is a signal to split. |
|
|
772
|
-
| `retry` | `on`/`off` | `on` | Retry governance on BOTH request-failure surfaces. **(1) The provider-error governor — a PER-REQUEST retry chain:** each session's own failing request earns `retryMaxRetries` (5) retries waiting **5s/10s/20s/40s/60s** (base doubling to `retryMaxMs`, `retryJitter` default `0`), cumulative WITHIN that request — never across a session's lifetime. Chains are isolated per session (a child's failures never advance the lead's ladder), directives are delivered only to the session that earned them, and a chain ends on `session.idle`, `retryChainGapMs` of quiet, or budget exhaustion (a later error starts a fresh chain). **Sub-agent vs main agent:** a child whose budget is spent is told to hand the work back, and the LEAD receives a notice to run that step itself; the main agent is told to stop and report with options. Injected text names the retry index. A **hard quota** (the BILLING family only: `insufficient balance` / 余额不足 / 欠费 / `insufficient_quota` / billing texts) names NO wait — stop and raise-or-reset — and trips the breaker on the SECOND consecutive error; **alibaba's `Allocated quota exceeded … error-code#token-limit` is a TRANSIENT per-minute throttle (user correction 2026-10-21) and rides the ladder and the re-drive**, while a structured `retryAfterMs` is honoured as the wait floor. **(2) The HTTP layer:** a failed request inside `tm_webfetch` / every `tm_search` engine leg (network throw, per-request timeout, HTTP 429 or 5xx) is re-asked up to 3 times on the same 5s/10s/20s rungs, and the reply labels it (`第N次重试后成功` / `重试3次…仍失败`). A policy refusal (off-allowlist, red line, env-file URL, 403/418, other 4xx) is never re-asked. **The host's own retries win:** `session.retry.scheduled` — the desktop's 「尝试 N - 在 X 秒内重试」 countdown — stands our ladder directive down while the host is retrying, and our guidance speaks only when it does not (hard quota / spent budget). **③ The plugin re-drives dead sessions itself:** when a rung's wait is over and nobody continued, the layer admits a synthetic continuation via `ctx.session.synthetic` (verified callable on 2.1.2: "admit synthetic session input and schedule execution") — one per rung within `retryMaxRetries`, never for a session the scope has not seen, cancelled the moment the host retries or the session assembles a request on its own; hard quota re-drives only at a provider-stated retry time. Honest boundary: a plugin cannot intercept the model's own retry — but it can re-drive execution. |
|
|
773
|
-
| `retryBaseMs` | number | `5000` | First rung of the ladder: retry #1 waits 5s, then doubling. |
|
|
774
|
-
| `retryMaxMs` | number | `60000` | Ceiling for that backoff (rungs 4/5 rest on 40s/60s with the defaults). |
|
|
775
|
-
| `retryMaxRetries` | number | `5` | How many retries ONE request earns before its chain stops (5 → 5s/10s/20s/40s/60s). |
|
|
776
|
-
| `retryChainGapMs` | number | `600000` | A session quiet for this long starts a NEW retry chain (counts reset), ms. |
|
|
777
|
-
| `retryJitter` | number | `0` | ± fraction applied to the wait. Default `0` = NO jitter, so the 5s/10s/20s ladder is exact; set a fraction (≤0.95) if parallel sessions should not re-fire in lockstep. |
|
|
778
|
-
| `retryBreakAfter` | number | `5` | Consecutive errors before the breaker trips. |
|
|
779
|
-
| `retryCooldownMs` | number | `60000` | How long new sub-agent dispatches are refused once it has. |
|
|
780
|
-
|
|
781
|
-
**Host-surface governance (2.x)**
|
|
782
|
-
|
|
783
|
-
| Key | Type | Default | Meaning |
|
|
784
|
-
|---|---|---|---|
|
|
785
|
-
| `nativeOffload` | `on`/`off` | `on` | Govern the HOST's own `read`/`grep`/`glob`/`shell` results through the same pipeline. `off` restores the host behavior; the boot line distinguishes "you turned it off" from "this host has no execute.after". |
|
|
786
|
-
| `nativeSnapshotMaxTokens` | number | `1200` | Budget a native `browser_snapshot` keeps in context. Addressing lines win it before static text does; past `budget × 4` the reply states how many ref lines did not fit, and kept + dropped always equals the total. |
|
|
787
|
-
| `nativeReportMaxTokens` | number | `1600` | Budget a report-shaped native result (anything carrying a Markdown table) keeps. Table lines and their headings win the space; the full text still rides the handle. |
|
|
788
|
-
| `probeChain` | `on`/`off` | `on` | After a run of native `read`/`grep`/`glob`/`shell` passes, append ONE line naming `execute` (Code Mode) and what it saves. A hint, never a gate: the original body is preserved verbatim, nothing is rejected, and the append is idempotent. |
|
|
789
|
-
| `probeChainAfter` | number | `3` | How many consecutive native calls before the hint starts (`0` = off). |
|
|
790
|
-
| `v2BrowserGate` | `on`/`off` | `on` | The gate over the host's `browser_*` catalog (URLs and preview paths at `execute.before`, plus the browser URLs named inside an `execute` program). `off` restores ungoverned browsing; the boot line and `tm_stats` say which world is running. |
|
|
791
|
-
| `v2CodeMode` | string | `""` | `direct` sends `options.codemode=false`; empty = catalog mode. We used to send `direct` by default and claim direct delivery — **a live 2.0.16 desktop session disproved it**: with the flag sent, every `tm_*` still arrived inside the host's Code Mode catalog, so the default now sends nothing. The observable truth is `tools_in_request` in `tm_stats` (请求内实际可见=…), never the flag we sent. |
|
|
792
|
-
| `bashTimeoutProbeMs` | number | `60000` | Ceiling forced onto a `timeout` the model set for a read-only probe command. Applies ONLY to a command the read-only allowlist already accepts, and never invents a timeout the model omitted (`0` disables). |
|
|
793
|
-
| `bashTimeoutMaxMs` | number | `0` | Optional global ceiling for every other shell command. `0` = off, so a real build keeps the timeout it asked for. |
|
|
794
|
-
| `envProtect` | `strict`/`standard`/`off` | `strict` | **red line.** R6 mode. `off` also disarms the shell escalation. Any unrecognised value resolves to `strict`. |
|
|
795
|
-
| `r6FineAsk` | string | classifier | **red line.** `off` falls back to asking about EVERY shell command — which is also what happens automatically on a host that does not expose the per-action evaluation hook, and the boot note says which of the two caused it. |
|
|
796
|
-
| `privateSpace` | `allow`/`deny`/`ask` | `deny` | **red line.** Private space (loopback, RFC1918, ULA, CGNAT, `.localhost`) through our tools. The default is `deny` because a 2.x plugin cannot raise a dialog — telling the agent to wait for a window that will never open is not a gate with a procedure, so `ask` is the honest spelling of "not answerable" and an unrecognised value falls back to it. Never conflated with the FORBIDDEN ranges. |
|
|
797
|
-
| `bashReadonlyAllowed` | string[] | built-in | **red line.** The P3 read-only command allowlist. `tasklist` / `ps` / `findstr` are seeded in, because `已确认关闭` is only checkable if the agent can ask the OS for the pid — a user-facing verification with no read path is the same defect as a lie. |
|
|
798
|
-
|
|
799
|
-
**Legacy keys** — kept listed and parseable so an old config still loads, with
|
|
800
|
-
**no v2 reader**, and reported as such:
|
|
801
|
-
|
|
802
|
-
| Key | Default | Status |
|
|
803
|
-
|---|---|---|
|
|
804
|
-
| `askTimeoutFloorMin` | `1` | **Orphaned.** Its only consumer was the v1 approval gate, which was deleted (a 2.x plugin cannot raise a dialog, so there was nothing to time out on). Say so rather than leaving a knob that does nothing. |
|
|
805
|
-
| `compactionContext` | `on` | v1-only |
|
|
806
|
-
| `shellEnv` | `""` | v1-only |
|
|
807
|
-
| `ptcWebBridge` | `on` | v1-only |
|
|
808
|
-
|
|
809
|
-
</details>
|
|
810
|
-
|
|
811
|
-
> **Compaction timing belongs to the host, not to this plugin.** An earlier
|
|
812
|
-
> release triggered its own summarization at 75% of the window; that was removed
|
|
813
|
-
> by user decision — a plugin that summarizes a live conversation by itself
|
|
814
|
-
> takes a context-losing action you did not ask for. What remains is additive and
|
|
815
|
-
> runs only when the HOST compacts: the must-survive list (reply skeleton,
|
|
816
|
-
> offload handles, open sub-agent session ids, provenance, board paths) rides the
|
|
817
|
-
> host's own `session.hook("compaction")`, and the host's summarizer prompt is
|
|
818
|
-
> never replaced.
|
|
819
|
-
|
|
820
|
-
### The three surviving `TM_*` names
|
|
821
|
-
|
|
822
|
-
These are **internal / test switches**, not configuration — they are read from
|
|
823
|
-
the environment because a test or a diagnostic needs them without a config file:
|
|
824
|
-
|
|
825
|
-
| Name | Effect |
|
|
826
|
-
|---|---|
|
|
827
|
-
| `TM_STORE_RECLAIM` | Overrides the `storeReclaim` key (`off` = leave the disk exactly as found). The test runner sets it, because suites boot real runtimes in temp dirs. |
|
|
828
|
-
| `TM_V2_PROBE` | Path to a JSONL file where the surface probe records the host's real tool ids, permission action names and argument key **names**. Names and counts only — never a command line, path, URL or env value. It is how "does the host actually have X?" gets answered from the running build instead of from a doc. |
|
|
829
|
-
| `TM_CONFIG_AUTOCREATE` | `off` suppresses the inert global-file auto-create. |
|
|
830
|
-
|
|
831
|
-
Any **other** `TM_*` name in your environment is inert. If you have been setting
|
|
832
|
-
one, move it into `team-mode.jsonc` under the matching key above.
|
|
833
|
-
|
|
834
|
-
---
|
|
835
|
-
|
|
836
|
-
## 🏗️ How the team works
|
|
837
|
-
|
|
838
|
-
Sub-agents can't message each other live (platform limitation), so TeamMode
|
|
839
|
-
coordinates them through a **structured reply skeleton** — every specialist
|
|
840
|
-
reply is `STATUS: / CHANGES: / FINDINGS: / EVIDENCE: / HANDOFF:`, ≤50 lines,
|
|
841
|
-
relayed verbatim by the Lead into the next dispatch. Files are the exception,
|
|
842
|
-
not the rule: a deliverable over ~50 lines goes to ONE named board file under
|
|
843
|
-
`<repo>/.git/opencode-team/` (round-suffixed, working tree untouched).
|
|
844
|
-
|
|
845
|
-
- **Routing table:** question → direct answer; docs-only → implementer;
|
|
846
|
-
product change → implementer → tester → reviewer; multi-module →
|
|
847
|
-
architect → implementer → tester → reviewer(s); unknown tech → researcher
|
|
848
|
-
first. Fixed minimums — a product change routed below 3 dispatches is a
|
|
849
|
-
routing bug.
|
|
850
|
-
- **One dispatch, one verifiable deliverable.** A dispatch is a unit of
|
|
851
|
-
VERIFIABLE work, not a bucket for everything related. If the lead cannot say
|
|
852
|
-
how a child's result will be verified *on its own*, it is not a dispatch yet —
|
|
853
|
-
it is a wish. Cutting one deliverable into pieces that only make sense
|
|
854
|
-
together is chopping, not splitting, and it costs you more rounds than it
|
|
855
|
-
saves.
|
|
856
|
-
- **The architect is conditional.** Dispatch it only when the design is
|
|
857
|
-
genuinely unknown. With the root cause already verified (file:line evidence),
|
|
858
|
-
skip it and dispatch the implementer with the exact fix spec; if the fix moves
|
|
859
|
-
a contract or the strategy is undecided, keep the architect. The ceremony
|
|
860
|
-
serves unknowns, not multi-file diffs.
|
|
861
|
-
- **Tester and reviewer may run in the SAME round** — verification and review
|
|
862
|
-
are independent of each other. Serialise them only when a fix invalidates
|
|
863
|
-
both. Same for multiple implementers (each carrying its exact file ownership
|
|
864
|
-
and verbatim data contracts) and testers on disjoint packages.
|
|
865
|
-
- **Approval gate (count-based):** ≥2 dispatches → plan (≤30 lines) → **your
|
|
866
|
-
approval** → execute. Blocking questions are batched and asked immediately.
|
|
867
|
-
Splitting one ask into sub-2-dispatch pieces to dodge the gate is a protocol
|
|
868
|
-
violation, not a trick.
|
|
869
|
-
- **Only the Team Lead dispatches.** Specialists no longer hold the host's
|
|
870
|
-
`task`/`subagent` capability, so no sub-agent spawns a sub-agent.
|
|
871
|
-
- **Adaptive review:** one reviewer by default; three parallel dimensions
|
|
872
|
-
only for high-risk profiles (auth/security, cross-module contracts, public APIs).
|
|
873
|
-
- **Static verification:** build / typecheck / lint / tests. Improvised
|
|
874
|
-
browser automation is banned; unverified UI work ends with
|
|
875
|
-
`UI NOT VERIFIED: <what to check>`.
|
|
876
|
-
- **Evidence standard:** "done / fixed / passed" claims need verifiable
|
|
877
|
-
evidence — output, logs, diffs.
|
|
878
|
-
- **Your language, not the tool's.** The governed tools answer in Chinese; the
|
|
879
|
-
reply-language rule keeps your own language in the prose and quotes a Chinese
|
|
880
|
-
string verbatim only where it IS the evidence.
|
|
881
|
-
|
|
882
|
-
---
|
|
883
|
-
|
|
884
|
-
## 🔧 Customization
|
|
885
|
-
|
|
886
|
-
**Override an agent** — same name in your config wins:
|
|
887
|
-
|
|
888
|
-
```jsonc
|
|
889
|
-
{
|
|
890
|
-
"agent": {
|
|
891
|
-
"reviewer": {
|
|
892
|
-
"model": "anthropic/claude-sonnet-4-6",
|
|
893
|
-
"prompt": "You are an extremely strict reviewer. Reject anything with a lint warning."
|
|
894
|
-
}
|
|
895
|
-
}
|
|
896
|
-
}
|
|
897
|
-
```
|
|
898
|
-
|
|
899
|
-
**Add your own agents** alongside the team:
|
|
900
|
-
|
|
901
|
-
```jsonc
|
|
902
|
-
{
|
|
903
|
-
"agent": {
|
|
904
|
-
"devops": {
|
|
905
|
-
"mode": "subagent",
|
|
906
|
-
"description": "Handles CI/CD, Docker, and deployment tasks.",
|
|
907
|
-
"prompt": "You are the DevOps engineer..."
|
|
908
|
-
}
|
|
909
|
-
}
|
|
910
|
-
}
|
|
911
|
-
```
|
|
912
|
-
|
|
913
|
-
**Disable one:** `"researcher": { "disable": true }`.
|
|
914
|
-
|
|
915
|
-
Board retention, auto-create, the default slot and the R6 kill switch are
|
|
916
|
-
plugin-entry `options` — see
|
|
917
|
-
[Configuration](#️-configuration).
|
|
918
|
-
|
|
919
|
-
---
|
|
920
|
-
|
|
921
|
-
## ❓ Troubleshooting
|
|
922
|
-
|
|
923
|
-
**Will this eat my tokens?**
|
|
924
|
-
The opposite is the point. Offload + 80-token previews + the host's Code Mode
|
|
925
|
-
`execute` exist because a five-agent pipeline naively bolted onto one context window
|
|
926
|
-
*would* eat your tokens. The governance is the token-saver.
|
|
927
|
-
|
|
928
|
-
**Is the web access safe?**
|
|
929
|
-
It's the most guarded surface in the plugin: two full web roles plus a
|
|
930
|
-
browser-only tester grant, a domain allowlist whose off-list calls fail closed,
|
|
931
|
-
redirects re-checked per hop with the whole chain reported, env-file URL
|
|
932
|
-
refusal, address red lines no setting opens, and every payload rides the same
|
|
933
|
-
offload governance. No allowlisted page can bounce the fetch off-site.
|
|
934
|
-
|
|
935
|
-
**Why doesn't the plugin auto-update?**
|
|
936
|
-
OpenCode caches plugins by spec string and never re-resolves `@latest`
|
|
937
|
-
(upstream limitation, not ours). **Re-run the installer — that IS the
|
|
938
|
-
update** (it purges the cache and re-resolves npm copies); or delete the
|
|
939
|
-
cache dir by hand. If the boot probe's `entrypoint=` line names a path outside
|
|
940
|
-
`~/.cache/opencode/npm` or `~/.config/opencode/node_modules`, a `~/package.json`
|
|
941
|
-
is pinning a different copy — that is the diagnostic, not a guess. Recipe above
|
|
942
|
-
and in [docs/installation-v2.md](./docs/installation-v2.md).
|
|
943
|
-
|
|
944
|
-
**A knob I set in an env var does nothing.**
|
|
945
|
-
Configuration is JSON now. Any `TM_*` variable other than the three internal
|
|
946
|
-
switches is inert; move the value into `team-mode.jsonc` under the registry key.
|
|
947
|
-
Run `tm_stats` and read the layered-config section: it names the per-key source,
|
|
948
|
-
so a value you expected to be honored shows up with a source that is not the file
|
|
949
|
-
you edited.
|
|
950
|
-
|
|
951
|
-
**A red-line key I set in the project file is ignored.**
|
|
952
|
-
By design. The five red-line keys are honored only in the **global** file, and
|
|
953
|
-
`tm_stats` reports the ignored attempt by name rather than dropping it silently.
|
|
954
|
-
|
|
955
|
-
**My `AGENT NOT FOUND: "Team"`.**
|
|
956
|
-
The file name IS the agent id, and Windows/APFS keep the existing spelling when
|
|
957
|
-
a write opens that file through another one — so a pre-1.7.2 `team.md` survives
|
|
958
|
-
every install and the host registers the lead as lowercase. Use `--agent team`.
|
|
959
|
-
The installer's reclaim pass renames a case-only entry it has proven is the same
|
|
960
|
-
file; a manual install may need the same rename.
|
|
961
|
-
|
|
962
|
-
**Can agents run tools in parallel?**
|
|
963
|
-
Yes — and they're *engineered* for it: parallel `tm_search` / `tm_webfetch` /
|
|
964
|
-
`tm_fetch` calls get distinct step ids and isolated payloads. A regression
|
|
965
|
-
here fails the test suite before it ever reaches you.
|
|
966
|
-
|
|
967
|
-
**Does it work in the CLI (TUI), or only Desktop?**
|
|
968
|
-
Both. Desktop adds the color-coded picker and panels; the governed tools and
|
|
969
|
-
the whole workflow are host-agnostic. Interactive browsing is the host's own
|
|
970
|
-
`browser_*` catalog, so a host with no native browser (CLI / standalone) has
|
|
971
|
-
none — the agent reports that gap rather than simulating one.
|
|
972
|
-
|
|
973
|
-
**What happens if I don't answer the host's permission prompt?**
|
|
974
|
-
The host decides that, not this plugin. The plugin can only refuse: it never
|
|
975
|
-
self-allows, and every guard it installs can only make a rule stricter. A
|
|
976
|
-
session it does not recognize as one of its own is left exactly as the host
|
|
977
|
-
behaved.
|
|
978
|
-
|
|
979
|
-
---
|
|
980
|
-
|
|
981
|
-
## 🗑️ Uninstall
|
|
982
|
-
|
|
983
|
-
1. Remove the entry from the `"plugins"` array in your config file; if you installed on 2.x, delete the generated `~/.config/opencode/agents/*.md` and `commands/team-*.md` too, and any `default_agent: "team"` you no longer want.
|
|
984
|
-
2. Delete the cache dir (table in [Install](#-read-this-once-save-yourself-an-hour-later)) if you want the disk space back.
|
|
985
|
-
3. Restart OpenCode. The agents, commands and tools are gone; the stores under `<repo>/.git/opencode-team/` (and `~/.opencode-team/` for global memories) are plain files you can delete whenever. The global `team-mode.jsonc` is yours to keep or delete — the plugin will simply write a fresh inert one if you remove it.
|
|
986
|
-
|
|
987
|
-
No DLLs were harmed. Nothing was written to your working tree.
|
|
988
|
-
|
|
989
|
-
---
|
|
990
|
-
|
|
991
|
-
## 🏛️ Architecture (for the curious)
|
|
992
|
-
|
|
993
|
-
The package exports `{id, setup}` and nothing else. The loader calls `setup(ctx)` once: it registers the `tm_*` tools, resolves the two config layers, arms the R6 / address guard on `permission.evaluate`, attaches JIT governance on `tool.execute.after`, and publishes the blackboard note — and user-defined agents with the same name always win. One rule keeps the config layer honest: **`CONFIG_KEYS` is the registry and the single source of truth**, so a key absent from it is reported as unknown and never applied, and the red-line set is derived from that registry rather than hand-listed.
|
|
994
|
-
|
|
995
|
-
The module map, the per-layer seams and every measured host fact are in [AGENTS.md](./AGENTS.md); the dated evidence behind each v2 claim is in [docs/v2-port-notes.md](./docs/v2-port-notes.md). `pt07/` holds the PT-07 baseline suite (seeded A/B token measurement); `scripts/` the one-line installers.
|
|
996
|
-
|
|
997
|
-
---
|
|
998
|
-
|
|
999
|
-
## 🤝 Contributing
|
|
1000
|
-
|
|
1001
|
-
Issues and PRs welcome. Especially wanted: localization of agent prompts,
|
|
1002
|
-
more agent roles, more command templates, and real-world reports of the
|
|
1003
|
-
search engines' behavior (they rearrange their markup; the extractor filters
|
|
1004
|
-
are intentionally loose but not psychic).
|
|
1005
|
-
|
|
1006
|
-
---
|
|
1007
|
-
|
|
1008
|
-
## 📄 License
|
|
1009
|
-
|
|
1010
|
-
[Apache License 2.0](./LICENSE)
|
|
1011
|
-
|
|
1012
|
-
---
|
|
1013
|
-
|
|
1014
|
-
## 🔗 Links
|
|
1015
|
-
|
|
1016
|
-
- [npm Package](https://www.npmjs.com/package/@te-river/opencode-team-mode) — `@te-river/opencode-team-mode`
|
|
1017
|
-
- [Installation guide, **OpenCode 2.x**](./docs/installation-v2.md) — the supported install: a 2.x plugin cannot create an agent, so the six roles and six commands are generated into the config directory
|
|
1018
|
-
- [Installation guide, 1.18.x](./docs/installation.md) — **historical only**: the 1.18.x personality was removed in the 1.7.0 line, so this page is kept for reference and is not a working install path
|
|
1019
|
-
- [OpenCode Desktop](https://opencode.ai) — Official website & download
|
|
1020
|
-
- [OpenCode Docs](https://opencode.ai/docs) — Configuration & plugin documentation
|
|
1021
|
-
- [OpenCode Plugin API](https://opencode.ai/docs/plugins) — Build your own plugins
|
|
1
|
+
# OpenCode TeamMode
|
|
2
|
+
|
|
3
|
+
**[English](./README.md)** | **[中文](./README.zh-CN.md)**
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/@te-river/opencode-team-mode)
|
|
6
|
+
[](https://www.npmjs.com/package/@te-river/opencode-team-mode)
|
|
7
|
+
[](./LICENSE)
|
|
8
|
+
|
|
9
|
+
> 🤝 **Your OpenCode just hired a team.**
|
|
10
|
+
>
|
|
11
|
+
> Six specialized agents — a Lead, an Architect, an Implementer, a Reviewer, a Tester and a Researcher — with governed tools, structured handoffs, and a plan-first approval gate. One plugin, no files to copy.
|
|
12
|
+
|
|
13
|
+
> 💡 **Best for medium-to-large projects.** The governance layer (approval gate, context offload, tool allowlists) is an asset once a codebase has real surface area — and mostly overhead on tiny scripts and one-off questions. Use it where the work is.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## 📑 Contents
|
|
18
|
+
|
|
19
|
+
- [Why TeamMode](#-why-teammode)
|
|
20
|
+
- [The team](#-the-team)
|
|
21
|
+
- [Install](#-install)
|
|
22
|
+
- [Usage](#-usage)
|
|
23
|
+
- [JIT context and retry](#-jit-context-and-retry)
|
|
24
|
+
- [Governed tools & security](#-governed-tools--security)
|
|
25
|
+
- [Configuration](#️-configuration)
|
|
26
|
+
- [How the team works](#-how-the-team-works)
|
|
27
|
+
- [Customization](#-customization)
|
|
28
|
+
- [Troubleshooting](#-troubleshooting)
|
|
29
|
+
- [Uninstall](#️-uninstall)
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## TL;DR — skip the docs
|
|
34
|
+
|
|
35
|
+
> **The lazy path:** paste this to any coding agent and let it do the work:
|
|
36
|
+
>
|
|
37
|
+
> ```text
|
|
38
|
+
> Install the OpenCode plugin @te-river/opencode-team-mode. This package supports **OpenCode 2.x
|
|
39
|
+
> only** — the 1.18.x personality was removed in the 1.7.0 line. Follow
|
|
40
|
+
> https://raw.githubusercontent.com/Te-River/Opencode-TeamMode/main/docs/installation-v2.md
|
|
41
|
+
> (a 2.x plugin cannot create an agent, so the six roles and six `/team-*` commands come from
|
|
42
|
+
> a generator step in that guide — that step is not optional.) Then run its verification checks.
|
|
43
|
+
> ```
|
|
44
|
+
>
|
|
45
|
+
> **The one command to remember afterwards:** `/team-run <task>` — the Team Lead plans it, shows you the plan, waits for your approval, then runs the whole team.
|
|
46
|
+
|
|
47
|
+
**[Why](#-why-teammode) · [The team](#-the-team) · [Install](#-install) · [Usage](#-usage) · [Tools & security](#-governed-tools--security) · [Configuration](#️-configuration) · [How it works](#-how-the-team-works) · [Troubleshooting](#-troubleshooting) · [Uninstall](#-uninstall)**
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## 🤔 Why TeamMode?
|
|
52
|
+
|
|
53
|
+
A single agent doing everything ends the way you have seen: a context window stuffed with 5000-line file dumps, twenty `shell` round-trips for what one script could do, sub-agents that silently read `.env`, and web "research" that is the model's imagination. TeamMode answers each one with a mechanism, not a promise:
|
|
54
|
+
|
|
55
|
+
| Pain | What is actually enforced |
|
|
56
|
+
|---|---|
|
|
57
|
+
| 🔥 **Context flooding** | Every result over its content-class boundary (prose 4000 / data 2000 tokens, CJK-aware) is offloaded to a local run store and replaced by an 80-token preview plus an HMAC handle. The agent pages through what it needs. |
|
|
58
|
+
| 🐌 **Round-trip overhead** | The host's own `execute` (Code Mode): one program, N governed calls, zero LLM round-trips. |
|
|
59
|
+
| 🕳️ **Silent side effects** | R6: a Team role's `read` of a `.env` is **denied** with no consent path; an env-reading shell command goes to the host's own prompt. The plugin never approves on its own — it can only reject. |
|
|
60
|
+
| 🌫️ **Hallucinated research** | Web access is a two-role grant over allowlisted, governed tools. A fact that could not be fetched is reported as a gap. |
|
|
61
|
+
| 🎯 **Goal drift** | The lead opens with `GOAL:` in your words plus checkable `ACCEPTANCE:` criteria; `tm_join` reports 目标未达成 while the ledger still has open items, and the goal survives compaction. |
|
|
62
|
+
| 🗣️ **Replies in a language you never chose** | The tools answer in Chinese; every role carries a reply-language rule — your language in the prose, a Chinese string quoted verbatim only where it IS the evidence. |
|
|
63
|
+
| ⏱️ **Rounds spent looking careful** | 效率至上: one wide call over three narrow ones, independent calls in one round, ≥3 probes folded into one `execute` program — and efficiency never buys its way out of the evidence rule. |
|
|
64
|
+
|
|
65
|
+
Under the table sits the workflow discipline: deterministic routing, a ≤30-line plan you approve before ≥2 dispatches, structured `STATUS/CHANGES/FINDINGS/EVIDENCE/HANDOFF` replies, and static verification (build / typecheck / tests) instead of vibes. That discipline is also why the recommendation is **medium-to-large projects** — a two-file script leaves the team little to govern.
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## 👥 The team
|
|
70
|
+
|
|
71
|
+
| Agent | Role | When to use |
|
|
72
|
+
|---|---|---|
|
|
73
|
+
| 🎯 **Team Lead** (`@team`) | Orchestrator | Complex tasks that need planning + multi-step execution |
|
|
74
|
+
| 🏗️ **Architect** | System designer | Design docs, module structure, API contracts — **dispatched only when the design is genuinely unknown** |
|
|
75
|
+
| 💻 **Implementer** | Code writer | Building features, writing production code |
|
|
76
|
+
| 🔍 **Reviewer** | Dimension-focused auditor | Single-dimension review by default; 3 in parallel only for high-risk changes |
|
|
77
|
+
| 🧪 **Tester** | Test engineer | Real edge-case tests; static verification; governed UI verification via the host's native `browser_*` tools |
|
|
78
|
+
| 🔎 **Researcher** | Knowledge finder | Local repo first, then the web — one of the two network roles (with the Lead) |
|
|
79
|
+
|
|
80
|
+
Out of the box, **Team is your default agent** — new chats open straight into the orchestrator (opt out in [Configuration](#️-configuration)).
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
## 📦 Install
|
|
85
|
+
|
|
86
|
+
| Way | One command | Use it when |
|
|
87
|
+
|---|---|---|
|
|
88
|
+
| **Let your agent do it** (recommended) | paste the block below into any coding agent | you want the config edited, the restart handled and the install verified |
|
|
89
|
+
| **One-line script** | `curl -fsSL https://ghproxy.net/https://raw.githubusercontent.com/Te-River/Opencode-TeamMode/main/scripts/install.sh \| bash` (macOS / Linux)<br>`irm https://ghproxy.net/https://raw.githubusercontent.com/Te-River/Opencode-TeamMode/main/scripts/install.ps1 \| iex` (Windows) | you trust the script and want one step |
|
|
90
|
+
| **Manual** | add `"@te-river/opencode-team-mode@latest"` to the **`plugins`** array in `opencode.jsonc` | you want to control the config yourself |
|
|
91
|
+
|
|
92
|
+
```text
|
|
93
|
+
Install the OpenCode plugin @te-river/opencode-team-mode. This package supports **OpenCode 2.x
|
|
94
|
+
only** — the 1.18.x personality was removed in the 1.7.0 line. Follow
|
|
95
|
+
https://raw.githubusercontent.com/Te-River/Opencode-TeamMode/main/docs/installation-v2.md
|
|
96
|
+
(a 2.x plugin cannot create an agent, so the six roles and six `/team-*` commands come from
|
|
97
|
+
a generator step in that guide — that step is not optional.)
|
|
98
|
+
If a URL is unreachable — common on mainland-China networks — retry with the mirror prefix
|
|
99
|
+
https://ghproxy.net/ + the same path. If `npm` is slow or blocked, run `npm config set registry https://registry.npmmirror.com`, or add
|
|
100
|
+
`--registry https://registry.npmmirror.com` to the one command that needs it.
|
|
101
|
+
Then verify the install using the checks in that guide.
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
> **The key is `plugins` (plural).** A 2.x host never reads the singular `plugin` key, and it installs the package itself from the entry at startup. Entries across config files are **applied lowest→highest rather than replacing one another**, so keep exactly one Team entry — writing ours into both `opencode.json` and `opencode.jsonc` loads the plugin twice. The 2.x flow is [docs/installation-v2.md](./docs/installation-v2.md); the host's own loader code is [docs/research/plugin-loader-contract.md](./docs/research/plugin-loader-contract.md).
|
|
105
|
+
|
|
106
|
+
### ⚠️ Read this once, save yourself an hour later
|
|
107
|
+
|
|
108
|
+
- **Restart to activate.** After touching the config, restart with `opencode service restart` (or quit Desktop from the tray, not just the window). A restart is safe mid-session: the conversation survives, and only an in-flight tool call is aborted and has to be re-issued.
|
|
109
|
+
- **Updates: re-run the installer — that IS the update.** It is idempotent (re-patches the config, purges the stale cache, re-resolves npm copies), because OpenCode caches plugins by spec string and never re-resolves `@latest` (upstream limitation). The manual purge commands and the `~/package.json` shadowing trap are in [Troubleshooting](#-troubleshooting).
|
|
110
|
+
- **The manual path needs the generator:** a 2.x plugin cannot create an agent, so `node <pkg>/scripts/gen-v2-config.mjs` writes the six roles and six `/team-*` commands into the config directory, and `default_agent: "team"` is written LAST — then restart. That step is not optional.
|
|
111
|
+
- **Prerequisites:** [OpenCode](https://opencode.ai) (Desktop or CLI) and Node ≥ 18.
|
|
112
|
+
|
|
113
|
+
### 🖥️ What the installer changes on your machine
|
|
114
|
+
|
|
115
|
+
All of it is outside this repo; the installer prints what it changed and reads the value back:
|
|
116
|
+
|
|
117
|
+
- **Config entry** → `~/.config/opencode/opencode.jsonc` (created if missing; an existing `opencode.json` is migrated into the `.jsonc`, original left untouched) — until you delete the line.
|
|
118
|
+
- **Plugin cache purge** → `~/.cache/opencode/packages/*opencode-team-mode*`, including the nested copy that actually runs — re-downloaded on next start.
|
|
119
|
+
- **User env var** `OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS=true` → Windows `setx` writes it into your user profile (survives reboot, visible to every program you start); macOS `launchctl setenv` / Linux `systemctl --user set-environment` last this login session. It exists because `task { background: true }` is the only sub-agent the interface can **show** you (a card linking to the child you can stop), and the host reads that flag from **its own process** — a plugin cannot set flags there. Without it nothing breaks; `task` simply blocks the lead.
|
|
120
|
+
- **Undo / skip:** `[Environment]::SetEnvironmentVariable("OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS", $null, "User")` (Windows) · `launchctl unsetenv OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS` (macOS) · `systemctl --user unset-environment OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS` (Linux) · `.\install.ps1 -NoBackgroundSubagents` or `TEAMMODE_SKIP_BACKGROUND_SUBAGENTS=1 bash install.sh` at install time.
|
|
121
|
+
|
|
122
|
+
Then restart, open the agent picker, and look for **team, architect, implementer, reviewer, tester, researcher**. That's it — the team is hired.
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## 📖 Usage
|
|
127
|
+
|
|
128
|
+
### Slash commands
|
|
129
|
+
|
|
130
|
+
| Command | Agent | Description |
|
|
131
|
+
|---|---|---|
|
|
132
|
+
| `/team-plan <task>` | Architect | Implementation plan with architecture, file manifest, task breakdown |
|
|
133
|
+
| `/team-implement <task>` | Implementer | Production code for a feature or task |
|
|
134
|
+
| `/team-review [scope]` | Reviewer | Audit for bugs, security issues, quality problems |
|
|
135
|
+
| `/team-test [scope]` | Tester | Comprehensive tests with edge-case coverage |
|
|
136
|
+
| `/team-research <topic>` | Researcher | Local repo first; web via the governed tools |
|
|
137
|
+
| `/team-run <task>` | Team Lead | **The full workflow** — plan → approval → orchestration → verification |
|
|
138
|
+
|
|
139
|
+
Or `@`-mention agents directly: `@team`, `@architect`, `@implementer`, `@reviewer`, `@tester`, `@researcher`.
|
|
140
|
+
|
|
141
|
+
### What a run feels like
|
|
142
|
+
|
|
143
|
+
Team is the default agent — here a product change that earns three dispatches, so the approval gate:
|
|
144
|
+
|
|
145
|
+
```text
|
|
146
|
+
you Add token-bucket rate limiting to our Express API — 100 req/min/user, 429 over the
|
|
147
|
+
limit. Don't touch anything under src/legacy/.
|
|
148
|
+
team PLAN goal · scope · pipeline (implementer → tester → reviewer) · assumptions ·
|
|
149
|
+
boundaries — approve to proceed?
|
|
150
|
+
you approved, go
|
|
151
|
+
team ▸ implementer done · tester 14/14 · reviewer approve — src/legacy/ untouched
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
## 🧠 JIT context and retry
|
|
157
|
+
|
|
158
|
+
*New in this release:* layered request-side context keeps a long session inside the window without deleting evidence, plus a retry ladder that says what it cannot do.
|
|
159
|
+
|
|
160
|
+
- **State card (lead only).** ≤300 tokens of ledger doing/blocked/todo items, running sub-agents and the board root, injected once per request. Hash-gated: an unchanged state re-emits the same bytes, so the prefix stays cacheable (`stateCard: "off"` removes the layer).
|
|
161
|
+
- **Lossless pruning.** Past 70% of the window (`pruneAtPercent`) a settled body becomes a one-line pointer — but only after it was offloaded and deduplicated against the run store, so the stub carries a real `tm_fetch` handle (`pruneOffload`). Reply skeleton, GOAL/ACCEPTANCE, provenance, system messages and the board note are never cut; `pruneProtectPatterns` lets your regexes outrank the budget.
|
|
162
|
+
- **Window entry.** A separate `[Team 窗口占用]` line reports `<bucket>% · model <id>`, bucketed to the nearest 5% so one bucket stays byte-identical. An unreadable window means the line is absent — never "0%".
|
|
163
|
+
- **Dynamic tail.** State card, window entry, retry directive and split advice merge into ONE message at the request's end, so `system` stops changing and prefix caching survives; `dynamicTail: "off"` restores the previous system injection.
|
|
164
|
+
- **Cache metering.** Each request that reports usage writes one `v2-cache` row (read / write / input / rate); `tm_stats` renders the **缓存命中** section with its denominator. No row means usage was never read — not a 0% hit.
|
|
165
|
+
- **Retry.** A throttle is classified by name and text, then answered by a per-request ladder — 5s / 10s / 20s / 40s / 60s, five rungs, never shared across requests; the host's own countdown wins when it has one. A sub-agent that spends its budget hands the work back; the main agent stops, reports and offers options. A hard quota is the billing family only and names no wait. When nothing continues by itself, one `session.synthetic` re-drive is admitted per rung.
|
|
166
|
+
- **A user stop is a scope change.** Land in-flight work first, skip verification, then hand over in four sections: 已落盘 / 未开始 / 验证 / 提交与发布.
|
|
167
|
+
|
|
168
|
+
---
|
|
169
|
+
|
|
170
|
+
## 🧰 Governed tools & security
|
|
171
|
+
|
|
172
|
+
Every tool the team calls runs under ONE governance pipeline. A result above its content-class boundary (`offloadThresholdText` = 4000 for prose, `offloadThresholdData` = 2000 for json/csv/code/binary, unknown → the global `offloadThreshold`) is written to a local run store and replaced by a content-aware preview (≤80 tokens) plus an HMAC handle that `tm_fetch` pages back on demand. The same pipeline governs the **host's own** `read` / `grep` / `glob` / `shell` through `tool.execute.after`. Under it sit **R6** (env protection: an env-FILE read is a hard deny with no consent path; an env read inside a shell command is asked) and **R2** (delete / publish / install / kill / privilege). Every hook can only make a rule stricter — the plugin never self-allows — and nothing it changes reaches a session that is not one of the six roles.
|
|
173
|
+
|
|
174
|
+
| Tool | In one line | Roles |
|
|
175
|
+
|---|---|---|
|
|
176
|
+
| `tm_fetch` | Paged retrieval of an offloaded result; JSON handles take a `fields` dot-path projection (`items[].name`). | all six |
|
|
177
|
+
| `tm_memory` | Session / project / global memory (Markdown + frontmatter): add / search / list / forget / compact. | all six |
|
|
178
|
+
| `tm_board_write` | The board's write side: ONE new `NN-<role>-<topic>[-rN].md` under the board root, named by the tool, never overwritten, path + byte count returned. | all six |
|
|
179
|
+
| `tm_search` | Multi-engine search: extracted, deduplicated, RRF-fused hit lists. | Lead + Researcher |
|
|
180
|
+
| `tm_webfetch` | One governed GET of an allowlisted page, redirects re-checked per hop. | Lead + Researcher |
|
|
181
|
+
| `tm_ledger` | The lead's task ledger in the host's `ctx.storage` (2.x gives a plugin no `todowrite`). | Lead only |
|
|
182
|
+
| `tm_join` | Sub-agent collection: status, bounded `waitMs`, `cancel:true`, a named child's whole reply through the offload pipeline. | Lead only |
|
|
183
|
+
| `tm_stats` | Reads this plugin's own trajectory back: tokens kept out, dispatch overlap, capability matrix, layered-config sources. | all |
|
|
184
|
+
|
|
185
|
+
<details>
|
|
186
|
+
<summary><b>All the detail — every tool, every guard, every path</b></summary>
|
|
187
|
+
|
|
188
|
+
#### Tools in full
|
|
189
|
+
|
|
190
|
+
- **`tm_fetch`** — pages a handle (HMAC-signed, run-scoped, expiring). A JSON handle also takes `fields` (a deliberately small jq subset: `items[].name`, `[].stargazers_count`), so a big API dump narrows to the values needed without the raw body entering the window.
|
|
191
|
+
- **`tm_memory`** — three tiers: **session** (this conversation only, in-process, TTL `memorySessionTtlMin` = 240 min, invisible elsewhere; `memorySessionPersist: "1"` mirrors it under `memories/sessions/<sid>/`), **project** (default; `<repo>/.git/opencode-team/memories/…` — build commands, environment quirks, architecture decisions for THIS checkout) and **global** (`~/.opencode-team/memories/global/`, override `memoryGlobalDir` — conventions that follow you across projects). Search walks all tiers with **session > project > global** precedence, +2 weight on project entries, and a same-title higher-layer twin shadows the lower one. A near-duplicate add (same tier + category, or title+keywords Jaccard ≥ 0.6) **folds into** the existing entry (content wins, keywords union, old slug into `supersedes:`, answer says 已合并). `memoryMaxEntries` = 200 per scope: over it `add` fails on purpose — run `compact` (dry-run by default; `apply:true` performs it after copying every original to a timestamped `.compact-backup` tree). Entries past `memoryStaleDays` (30) surface tagged `[stale Nd]`. 4000 chars per memory.
|
|
192
|
+
- **`tm_board_write`** — the blackboard layout `<board-root>/<session-key>/<task-slug>/NN-<role>-<topic>[-rN].md`. It exists because architect / researcher own no file tool, so their oversized deliverables used to end as `BLACKBOARD WRITE FAILED` plus the whole document inline. Segments sanitized, target realpath-verified against the board root (a symlinked task dir is refused), always `.md` so no `.env` can be produced, capped by `boardMaxChars` (200000) and `boardMaxFiles` (200 per session).
|
|
193
|
+
- **`tm_search`** — one call, one query, clean results; the engine URL is built for you and collapsed into a numbered title+URL list. Engines: `auto` (default — classifies the query, fans the matching engines out **in parallel**, dedupes by host+path and fuses with weighted RRF; ranking scaled by real query-token overlap with floor `searchRelevanceFloor` = 0.35; every route has ≥2 legs), `bing` (cn.bing.com, the only live CN HTML SERP; multi-word CJK phrase-protected), `bing-int` (`&ensearch=1`, deliberately outside every `auto` route — two layouts of one index would double bing's vote), `stackoverflow` (keyless API, 300/day/IP; `auto` tracks the quota and swaps in `bing` once spent), `hn` (Algolia), `bilibili`, `moegirl` (MediaWiki API), `npm` (registry JSON), `github` (repo search; `org:` / `user:` / `stars:` / `language:` qualifiers fold into the query). All CN-reachable without API keys and all on the seeded allowlist; `sogou` / `so` / `baidu` were removed after the 2026-09-14 benchmark (anti-bot shells or 100% empty), and an empty result names the alternative engines.
|
|
194
|
+
- **`tm_webfetch`** — a known URL, one governed GET. Search pages are auto-extracted to hit lists; JSON endpoints pass through untouched. Redirects are followed manually and re-checked per hop, and a refusal names the WHOLE chain (`跳转链: a → b(停在第 2 跳)`) — an allowlisted shortener that bounces off-site must not read as "that site will not fetch". A 429/503 carries its own `Retry-After` when the server sends delta-seconds (an HTTP-date is never laundered into a countdown). The page GET asks for Markdown first (`Accept: text/markdown,…`): measured on `learn.microsoft.com`, 60,778 B of HTML becomes 11,449 B of Markdown, and other hosts return the same document either way. A 403/418 after real-Chrome headers is a DIRECTIVE: the gate is a JS challenge / TLS fingerprint, so open it with the host's `browser_navigate`.
|
|
195
|
+
- **`tm_ledger`** — `add` / `doing` / `done` / `blocked` / `list` in the host's `ctx.storage` per session. A repeated ask is ONE item, an ambiguous id is refused with both candidates printed, `blocked` carries its note, and a write that did not reach storage is reported as a failure rather than as recorded.
|
|
196
|
+
- **`tm_join`** — there is deliberately no plugin-side dispatcher (`tm_dispatch` is removed): a child a plugin creates is a session the user can neither open from a card nor stop from the interface. Delegation goes through the host's own `task` / `task { background: true }`, and `tm_join` is the read side: status snapshot, bounded `waitMs`, `cancel: true` (`tm_join { ids: ["ses_…"], cancel: true }` stops ONE named child; without `ids` it stops every still-running one), and `claimNamedChild` so an explicit id pulls that child's whole reply back through the offload pipeline. On 2.x it also registers children the host's own `subagent` tool created (the ack's `metadata.sessionID`), so a background dispatch is never reported as "nothing to collect", and the row says where the reply arrives and how it settled. Stopping one goes to `ctx.session.interrupt`, with **five distinct verdicts** — 已由宿主中断 / 空闲未中断(it was not running: our row was stale, not failed)/ 未确认(the call worked, no boolean came back)/ 无中断缝 / 被宿主拒绝 — counted as `stop_tried` / `stop_confirmed` / `stop_refused` / `stop_unknown`; `resume` is never sent.
|
|
197
|
+
- **`tm_stats`** — tokens kept out of the window (net of the preview that arrived), seconds saved by dispatch overlap, governance counts (blocked subresources, clamped timeouts, cache hits, redactions), the capability matrix, and the layered-config section (per-key source, red-line keys a file tried to set and lost, unknown keys, skipped layers). Read-only over files this plugin wrote. `tm_stats { recent: 20 }` appends a call-by-call recap with handle + payload path — the answer to "what did that tool actually return?", since the host renders a plugin tool call as a one-line card with no expandable body.
|
|
198
|
+
|
|
199
|
+
#### Scope, and what the 1.7.0 cut changed
|
|
200
|
+
|
|
201
|
+
The v1 (1.18.x) personality was removed in full: the package exports `{id, setup}` and nothing else, so `tm_read` / `tm_grep` / `tm_bash` / `tm_ptc_run` / `tm_pty` are gone — the host's own `read` / `grep` / `glob` / `shell` and its `execute` (Code Mode) do that work. The governance did not disappear with them: an oversized native result is offloaded through `tool.execute.after` (measured: `shell` 12,902 tokens arriving as a 78-token preview), and the address red line plus R6's per-command classification ride `permission.evaluate`. Every v2 hook fires for every session on the host, so each layer asks "is this one of our six roles?" first — `build`, `plan` and your own agents stay as a fresh install leaves them, and calls whose owner was not identified are left alone and counted (`tm_stats` prints 作用域:我们 · 他人 · 未判定).
|
|
202
|
+
|
|
203
|
+
#### Interactive browsing, and the web channel
|
|
204
|
+
|
|
205
|
+
Interactive browsing is the host's own `browser_*` catalog (`browser_tabs_open`, `browser_navigate`, `browser_snapshot`, `browser_click`, `browser_evaluate` …). The desktop renders that panel from the server's own browser service, so a plugin cannot register a page of its own; the self-built `tm_browser` was removed in 1.7.0. `permission.evaluate` was observed NOT firing for `browser_*`, so the gate sits on `tool.execute.before`: the URL of every navigate/open, the path of every `browser_preview`, and every browser URL named inside an `execute` program are classified, refused ones are blocked, and if the host runs a refused call anyway its page is replaced with the same refusal — the reply never claims a block we did not perform. A snapshot is an addressing table, so it is CAPPED rather than offloaded: every `[ref=…]` line kept, static text dropped, budget `nativeSnapshotMaxTokens` (1200) — measured on a 261-ref page, 261 refs kept in 1,044 of 11,326 tokens. Calling convention: `evaluate` takes `{tabID, script}` where `script` is an **expression** (not function source) and the return value must be a scalar you stringify; snapshot tokens read `@e8 [link]`. On a host with no native browser catalog (CLI / standalone) there is no browser at all, and the agent reports that gap rather than simulating one.
|
|
206
|
+
|
|
207
|
+
**The allowlist** (all web tools) seeds 22 hosts: `baidu.com`, `bdimg.com` (Baidu's own script + asset CDN), `moegirl.org.cn`, `bilibili.com`, `www.sogou.com`, `www.so.com`, `cn.bing.com`, `www.bing.com`, `zhihu.com`, `juejin.cn`, `csdn.net`, `cnblogs.com`, `gitee.com`, `github.com`, `api.github.com`, `raw.githubusercontent.com`, `gist.githubusercontent.com`, `ghproxy.net`, `stackoverflow.com`, `npmjs.org`, `pypi.org`, `learn.microsoft.com` — plus `api.stackexchange.com` and `hn.algolia.com` while the default seed is in play (24 effective). Parent domains cover their subdomains (`baike.baidu.com`, `mzh.moegirl.org.cn`, `space.bilibili.com`), and `bdimg.com` must be seeded explicitly because a page whose script CDN we block is a blank page we would then report as "no content". Extend via `webfetchAllowedDomains`; a custom list REPLACES the seed, so keep the engine hosts or `tm_search` loses its targets. Architect / implementer / reviewer have NO network grant; the tester carries the host's `browser_*` tools ONLY, for UI verification of the project; open web stays with the two network roles. Search hit lists filter engine wrappers (`so.com/link?`, `ai.so.com`) and same-name-different-site noise (`maimai.cn`), extensible via `hitBlacklist`. Governed fetches cache per URL for `webCacheTtlSec` (300 s), hash-named (a token-bearing query never hits disk), and only for hops the STATIC allowlist admitted — a hit can neither resurrect a removed host nor substitute for consent.
|
|
208
|
+
|
|
209
|
+
**Out-of-allowlist is a gate, not a wall.** The call fails closed with a refusal naming the exits — `privateSpace: "allow"` in `team-mode.jsonc`, or that one hostname in `webfetchAllowedDomains` — because a 2.x plugin cannot raise the host's dialog, and a gate with no door is not a gate. Below that sit classes no config opens: cloud-metadata / link-local / multicast / reserved / benchmarking ranges, including their IPv4-mapped and DNS64 spellings (changing the notation is not a bypass), plus env-file URLs and non-http(s) schemes, are hard-rejected. **Tool priority ladder, every task:** ① your own MCP/plugin tools → ② TeamMode governed tools → ③ the model's own reasoning; a missing capability is reported at rung ③, never fabricated (web is the one exception — governed `tm_search` / `tm_webfetch` come first there, because that path carries the allowlist and the red lines).
|
|
210
|
+
|
|
211
|
+
#### R6 env protection + R2 dangerous operations
|
|
212
|
+
|
|
213
|
+
R6 is armed by default, in `strict` mode (`envProtect` accepts `strict` / `standard` / `off`, and any unrecognised value resolves to `strict`).
|
|
214
|
+
|
|
215
|
+
- **Env-FILE reads are a hard deny** — a Team role's native `read` / `edit` / `glob` / `grep` of a `.env` is refused with no consent path. The host's `grep` reports the *pattern* as its permission resource, so the same rule also runs on the whole tool input (`grep SECRET .env` cannot slip through). Templates (`*.env.example` / `.sample` / `.template` / `.dist`) stay readable.
|
|
216
|
+
- **Env-variable reads in a shell command go to the host's prompt** (`ask`): the per-command classifier decides, so `git status` asks nothing and an env dump still does. Env reads no wildcard can express (embedded `$VAR` / `${VAR}` / `$env:` inside another command, command substitution) are **refused**, never asked. The audit log records only tool name + pattern category + verdict — never command text, paths, variable names or values.
|
|
217
|
+
- **Deferral is per-session:** the env face applies only in sessions running TeamMode's generated roles; anywhere else the plugin leaves the host's behaviour exactly as it found it.
|
|
218
|
+
|
|
219
|
+
**R2** covers delete, git publish, network fetch, package install/publish, process/system and privilege changes — none silently allowed, while the normal verification stack (`npm test`, `tsc`, `git status`) is not gated. **The plugin never self-allows:** every hook can only make a rule stricter; a permission it is asked about is replied `deny` or left alone.
|
|
220
|
+
|
|
221
|
+
> ⚠️ **When you approve the host's own dialog, pick "once" — not "always".** Verified live: "always" records a far broader rule than the command you saw (approving `Get-ChildItem env:PATH` stores `Get-ChildItem *`), so every later `Get-ChildItem` runs with no dialog. The web channel has the same trap: one "always" on a host lets **every agent session in that project** open it. The reply says which path let a page in (static allowlist / the dialog you just answered / a saved rule that answered in milliseconds), because an agent that cannot tell them apart reports "no dialog" as "that site is allowed".
|
|
222
|
+
|
|
223
|
+
#### Repo hygiene
|
|
224
|
+
|
|
225
|
+
The offload / trajectory / memory stores live under `<repo>/.git/opencode-team/` — outside a git repo, the OS temp dir, sharded per workspace as `opencode-team/w-<hash>/` so one project never reads another's payloads. **Never your working tree.** Every agent is told to delete scratch files before reporting done and to keep throwaway work in the OS temp dir. A TTL sweeper reclaims old task dirs at startup and hourly; the Team Lead never deletes boards itself, so you can audit any run.
|
|
226
|
+
|
|
227
|
+
</details>
|
|
228
|
+
|
|
229
|
+
---
|
|
230
|
+
|
|
231
|
+
## ⚙️ Configuration
|
|
232
|
+
|
|
233
|
+
**Configuration is JSON, and only JSON.** `team-mode.jsonc` is the single source; there is no environment layer, and **no `TM_*` configuration environment variable exists any more** — old ones are unknown keys, reported at boot and never applied. (Three `TM_*` names survive as internal/test switches, at the end of this section.)
|
|
234
|
+
|
|
235
|
+
### Where the file lives
|
|
236
|
+
|
|
237
|
+
| Layer | Path | Precedence |
|
|
238
|
+
|---|---|---|
|
|
239
|
+
| **global** | `~/.config/opencode/team-mode.jsonc` (honours `OPENCODE_CONFIG_DIR`) | lowest — every project |
|
|
240
|
+
| **project** | `<dir>/team-mode.jsonc`, walking from the workspace **up to the filesystem root** | higher — direct files merge farthest→closest |
|
|
241
|
+
| **project** | `<dir>/.opencode/team-mode.jsonc`, same walk | **highest** — every `.opencode/` file overrides every direct file |
|
|
242
|
+
|
|
243
|
+
That walk and precedence are the host's own config-layering rule, copied rather than invented. JSONC comments are masked at the byte level, so a commented-out key cannot read as live. A layer that fails to parse is skipped **whole**; a single bad key loses only itself; unknown keys are warned about and never applied. `tm_stats` renders the result: per-key source, skipped layers, unknown keys, auto-create state.
|
|
244
|
+
|
|
245
|
+
### It writes itself
|
|
246
|
+
|
|
247
|
+
When the global file is **absent**, the plugin creates it at boot — atomically, idempotently, **never overwriting an existing file**. What it writes is **inert**: all **64** registry keys, each as a *comment* under a one-line description, so the file parses to `{}` and sets nothing until you uncomment what you want.
|
|
248
|
+
|
|
249
|
+
```jsonc
|
|
250
|
+
// 全局配置。取消注释即可生效;默认全部注释 = 什么都不设置。
|
|
251
|
+
{
|
|
252
|
+
// "offloadThreshold": 2000, // 卸载阈值(估算 token;等于阈值也卸载)。
|
|
253
|
+
// "envProtect": "strict", // R6 环境防护模式(strict/standard/off)。(红线:仅全局)
|
|
254
|
+
// …共 64 个键,每个都带一行说明
|
|
255
|
+
}
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
Turn it off with the plugin option `autoCreate: false`, or the internal switch `TM_CONFIG_AUTOCREATE=off`.
|
|
259
|
+
|
|
260
|
+
### Red-line keys
|
|
261
|
+
|
|
262
|
+
Five keys are **honored only in the global file**: `envProtect`, `r6FineAsk`, `privateSpace`, `webfetchAllowedDomains`, `bashReadonlyAllowed`. A project file may set them, but the value is **ignored and reported** — a committed project config must not switch off a guard for everyone who clones the repo. The set is derived from the registry (`redLine: true`), so a new red-line key is red-line by declaration.
|
|
263
|
+
|
|
264
|
+
### Plugin options
|
|
265
|
+
|
|
266
|
+
Options live on the plugin entry, not in `team-mode.jsonc`:
|
|
267
|
+
|
|
268
|
+
```jsonc
|
|
269
|
+
{
|
|
270
|
+
"plugins": [
|
|
271
|
+
{ "package": "@te-river/opencode-team-mode@latest",
|
|
272
|
+
"options": { "defaultAgent": true, "autoCreate": true, "ttlDays": 7, "envProtect": true } }
|
|
273
|
+
]
|
|
274
|
+
}
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
| Option | Default | Meaning |
|
|
278
|
+
|---|---|---|
|
|
279
|
+
| `defaultAgent` | `true` | Promote Team to the default agent slot. |
|
|
280
|
+
| `autoCreate` | `true` | Write the inert global `team-mode.jsonc` at boot when absent. |
|
|
281
|
+
| `ttlDays` (alias `blackboardTtlDays`) | `5` | Blackboard retention, valid range (0, 365]. |
|
|
282
|
+
| `envProtect` | `true` | Plugin-wide kill switch; `false` resolves the R6 mode to `off`. |
|
|
283
|
+
|
|
284
|
+
On 2.x an entry is either a **string** or an **object** with `package` and `options`; the 1.x tuple form is rejected (`path=$.plugins.1 kind=invalid`, measured 2026-10-06). Options do arrive: with the object form, `{ "ttlDays": 9 }` reads back as `board_ttl_days=9` in the boot row.
|
|
285
|
+
|
|
286
|
+
### Model choice
|
|
287
|
+
|
|
288
|
+
Every judgment — triage, decomposition, dispatch briefs, synthesis, review verdicts — flows through the Team Lead, so a weak model there degrades the whole pipeline. Pin your best reasoning model to `team`:
|
|
289
|
+
|
|
290
|
+
```jsonc
|
|
291
|
+
{
|
|
292
|
+
"agent": {
|
|
293
|
+
"team": { "model": "anthropic/claude-opus-4-5" }, // Lead earns your best model
|
|
294
|
+
"implementer": { "model": "anthropic/claude-sonnet-4-6" } // specialists tolerate cheaper
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
Your own agents named `team` / `architect` / … always take precedence; the plugin never clobbers user definitions. See [Customization](#-customization).
|
|
300
|
+
|
|
301
|
+
### Key reference (all 64)
|
|
302
|
+
|
|
303
|
+
<details>
|
|
304
|
+
<summary>All 64 keys — click to expand</summary>
|
|
305
|
+
|
|
306
|
+
| Key | Type | Default | Meaning |
|
|
307
|
+
|---|---|---|---|
|
|
308
|
+
| **Offload, previews and the store** | | | |
|
|
309
|
+
| `offloadThreshold` | number | `2000` | Global offload boundary (estimated tokens, CJK-aware) when the content class is unknown. A result AT the threshold offloads. |
|
|
310
|
+
| `offloadThresholdText` | number | `4000` | Boundary for prose (text / log / markdown). Inherits the global value unless set. |
|
|
311
|
+
| `offloadThresholdData` | number | `2000` | Boundary for structured payloads (json / csv / code / binary). Same inheritance. |
|
|
312
|
+
| `previewLines` | number | `20` | Raw lines a preview builder may scan. |
|
|
313
|
+
| `previewMaxTokens` | number | `80` | Hard preview cap. |
|
|
314
|
+
| `fetchMaxLines` | number | `2000` | `tm_fetch` per-segment line cap. |
|
|
315
|
+
| `blackboardDir` | string | auto | Run-payload store; empty = `<repo>/.git/opencode-team/blackboard`; explicit = absolute or project-relative. |
|
|
316
|
+
| `trajectoryDir` | string | auto | Trajectory store; empty = `<repo>/.git/opencode-team/trajectory`. |
|
|
317
|
+
| `blackboardTtlDays` | number | `7` | Handle TTL and the physical sweep period for expired run dirs. |
|
|
318
|
+
| `storeReclaim` | `on`/`off` | `on` | At boot, reclaim what an upgrade left behind (a shard idle past the TTL, the pre-shard dirs at the temp fallback). **Only TTL-expired entries**; `off` leaves the disk exactly as found. |
|
|
319
|
+
| `taskOffload` | `on`/`off` | `on` | Replace the host's injected background-sub-agent reply with a preview + `tm_join` pointer. Only synthetic parts carrying the host's own envelope and over the threshold; a `state="error"` child is never rewritten; nothing copied to disk; `off` restores the verbatim injection. |
|
|
320
|
+
| `subagentConcurrency` | number | `8` | Ceiling on **running + in-flight** dispatches per caller, so a burst in one message is capped too; `0` disables. |
|
|
321
|
+
| `boardMaxChars` | number | `200000` | `tm_board_write` per-file cap — over it the write refuses (with a split-the-topic hint) rather than truncating a deliverable. |
|
|
322
|
+
| `boardMaxFiles` | number | `200` | Markdown files per session folder; the TTL sweeper is the only reclaim path, so the refusal names it. |
|
|
323
|
+
| `joinMaxWaitMs` | number | `60000` | Ceiling on `tm_join { waitMs }`; a second consecutive wait that settled nothing is cut to 10 s with a "stop waiting" directive. |
|
|
324
|
+
| `ledgerMaxItems` | number | `200` | `tm_ledger` items per session; past it the list refuses rather than quietly dropping the oldest asks. |
|
|
325
|
+
| **Memory** | | | |
|
|
326
|
+
| `memoryGlobalDir` | string | auto | GLOBAL tier store; empty = `~/.opencode-team/memories/global`. |
|
|
327
|
+
| `memorySessionTtlMin` | number | `240` | Session-tier entry TTL (lazy + boot sweep). |
|
|
328
|
+
| `memoryMaxEntries` | number | `200` | Per-scope entry cap; over it `add` fails on purpose — run `compact`. |
|
|
329
|
+
| `memoryStaleDays` | number | `30` | Age after which search hits are tagged `[stale Nd]` (`0` disables). |
|
|
330
|
+
| `memorySessionPersist` | string | `""` | `""` = in-process only; `"1"` also writes session entries under `memories/sessions/<sid>/`. |
|
|
331
|
+
| **Web and search** | | | |
|
|
332
|
+
| `webfetchAllowedDomains` | string[] | 22-host seed | **red line.** Outbound allowlist (`"*"` = any host; `[]` = deny all). A custom list **replaces** the seed — keep the engine hosts. `"*"` does **not** cover private space: loopback / RFC1918 / CGNAT / `.localhost` still need `privateSpace: "allow"`, and non-routable ranges are a hard red line no setting opens. |
|
|
333
|
+
| `searchDefaultEngine` | string | `auto` | Engine when no `engine` arg is given (`auto` = classify + parallel fan-out + RRF fusion). |
|
|
334
|
+
| `searchMaxHits` | number | `10` | Hits kept per engine leg and in the fused list. |
|
|
335
|
+
| `searchWeights` | record | `{}` | Per-engine fusion weight overrides, e.g. `{"bing": 0.3}`. |
|
|
336
|
+
| `searchDisabledEngines` | string[] | `[]` | Engines removed from the roster AND from every `auto` route. |
|
|
337
|
+
| `searchRelevanceFloor` | number | `0.35` | Weight kept by a hit sharing no query token (demotes junk without deleting an engine). |
|
|
338
|
+
| `hitBlacklist` | string[] | `[maimai.cn]` | Extra domains never listed as hits; merged with the built-in entry. |
|
|
339
|
+
| `webCacheTtlSec` | number | `300` | How long a governed fetch may re-serve the same URL (`0` = off). One store shared by `tm_webfetch` / `tm_search`; entries hash-named and only read/written for hops the STATIC allowlist admitted; a re-served page says 缓存命中. |
|
|
340
|
+
| **Rounds, dispatch and context** | | | |
|
|
341
|
+
| `prune` | `on`/`off` | `on` | Replace a SETTLED message body with a self-explaining pointer. Evidence is never destroyed: reply skeleton, GOAL/ACCEPTANCE, provenance, system messages, a running child and an uncollected child id are protected whole. |
|
|
342
|
+
| `pruneAtPercent` | number | `70` | Prune at this share of `limit.context` — never a hard-coded token count. **Clamped 40–90.** |
|
|
343
|
+
| `pruneKeepTailPercent` | number | `40` | Share of the window kept verbatim at the tail; the newest message is always kept. |
|
|
344
|
+
| `pruneOffload` | `on`/`off` | `on` | Offload a message body BEFORE its text is replaced, so the stub carries a full `tm_fetch { ref, access_token, expire_at }`; an already-stored body reuses its handle, and a failed offload falls back to the echo note and is counted (`prune_offload_failed`). |
|
|
345
|
+
| `pruneProtectPatterns` | regex list (comma separated) | `""` | A message whose text matches ANY entry is never pruned; hits are counted (`prune_protect_matched`); an invalid entry is ignored; empty = off. |
|
|
346
|
+
| `stateCard` | `on`/`off` | `on` | 任务状态卡 — ONE ≤300-token lead-only system entry: ledger counts + open items, this session's running children, a shortened board root, last round's offloaded count. Unchanged bytes write nothing. |
|
|
347
|
+
| `dynamicTail` | `on`/`off` | `off` | Volatile context (state card / window entry / retry directive / split advice) merges into ONE message at the END of the request (`[Team 动态上下文]`, `### <name>` sections), so `system` stays prefix-cacheable; a reused array has its old tail stripped; empty sink writes nothing. **Defaults to `off`**: host 2.1.x persists the appended message as a real user message and replays it as the last user input (measured 2026-10-22); the feature stays, opt in only after confirming the host behaviour is safe. `off` = per-producer system entries. |
|
|
348
|
+
| `splitAdvice` | `on`/`off` | `on` | Suggest splitting a brief that carries too many acceptance criteria. |
|
|
349
|
+
| `splitBriefTokens` | number | `4000` | Brief token threshold for that hint. **Clamped ≥200.** |
|
|
350
|
+
| `splitMaxCriteria` | number | `3` | More acceptance criteria than this is a split signal. |
|
|
351
|
+
| `retry` | `on`/`off` | `on` | Retry governance on both surfaces. **(1) Per-request provider ladder:** each failing request earns `retryMaxRetries` retries at 5s/10s/20s/40s/60s, isolated per session, ending on `session.idle`, `retryChainGapMs` of quiet or budget exhaustion; a child that spends its budget hands work back and the lead is told to run that step; a **hard quota** (billing family only: `insufficient balance` / `insufficient_quota` / 余额不足 / 欠费) names NO wait and trips the breaker on the second consecutive error, while alibaba's per-minute token-limit throttle rides the ladder; a structured `retryAfterMs` sets the wait floor. **(2) HTTP layer:** a failed `tm_webfetch` / `tm_search` leg (network throw, timeout, 429/5xx) is re-asked up to 3 times on the same rungs and labelled (`第N次重试后成功` / `重试3次…仍失败`); policy refusals are never re-asked. **The host's own retries win** (`session.retry.scheduled`), and ③ `ctx.session.synthetic` re-drives a dead session once per rung. |
|
|
352
|
+
| `retryBaseMs` | number | `5000` | First rung: retry #1 waits 5 s, then doubles. |
|
|
353
|
+
| `retryMaxMs` | number | `60000` | Backoff ceiling (rungs 4/5 rest at 40 s/60 s). |
|
|
354
|
+
| `retryMaxRetries` | number | `5` | How many retries ONE request earns (5 → 5s/10s/20s/40s/60s). |
|
|
355
|
+
| `retryChainGapMs` | number | `600000` | A session quiet this long starts a NEW chain. |
|
|
356
|
+
| `retryJitter` | number | `0` | ± fraction on the wait; `0` = NO jitter, so the ladder is exact. |
|
|
357
|
+
| `retryBreakAfter` | number | `5` | Consecutive errors before the dispatch breaker trips. |
|
|
358
|
+
| `retryCooldownMs` | number | `60000` | How long new sub-agent dispatches are refused once it has. |
|
|
359
|
+
| **Host-surface governance** | | | |
|
|
360
|
+
| `nativeOffload` | `on`/`off` | `on` | Govern the HOST's `read`/`grep`/`glob`/`shell` through the same pipeline; `off` restores host behavior, and the boot line distinguishes "you turned it off" from "this host has no execute.after". |
|
|
361
|
+
| `nativeSnapshotMaxTokens` | number | `1200` | Budget a native `browser_snapshot` keeps; addressing lines win over static text; past `budget × 4` the reply states how many ref lines did not fit. |
|
|
362
|
+
| `nativeReportMaxTokens` | number | `1600` | Budget a report-shaped native result keeps: table lines and headings win the space; the full text still rides the handle. |
|
|
363
|
+
| `probeChain` | `on`/`off` | `on` | After a run of native `read`/`grep`/`glob`/`shell`, append ONE line naming `execute` and what it saves — a hint, never a gate; body preserved verbatim; idempotent. |
|
|
364
|
+
| `probeChainAfter` | number | `3` | Consecutive native calls before the hint starts (`0` = off). |
|
|
365
|
+
| `v2BrowserGate` | `on`/`off` | `on` | Gate over the host's `browser_*` catalog (navigate/open URLs, preview paths, browser URLs inside an `execute` program). `off` restores ungoverned browsing and the boot line says which world runs. |
|
|
366
|
+
| `v2CodeMode` | string | `""` | `direct` sends `options.codemode=false`; empty = catalog mode. A live 2.0.16 session disproved the flag's effect, so the truth is `tools_in_request` in `tm_stats`, never the flag sent. |
|
|
367
|
+
| `bashTimeoutProbeMs` | number | `60000` | Ceiling forced onto a model-set timeout for a command the read-only allowlist already accepts; never invents a timeout; `0` disables. |
|
|
368
|
+
| `bashTimeoutMaxMs` | number | `0` | Optional global ceiling for every other shell command; `0` = off, so a real build keeps its timeout. |
|
|
369
|
+
| `envProtect` | `strict`/`standard`/`off` | `strict` | **red line.** R6 mode; `off` also disarms the shell escalation; any unrecognised value resolves to `strict`. |
|
|
370
|
+
| `r6FineAsk` | string | classifier | **red line.** `off` asks about EVERY shell command — also what happens on a host without the per-action evaluation hook; the boot note says which of the two caused it. |
|
|
371
|
+
| `privateSpace` | `allow`/`deny`/`ask` | `deny` | **red line.** Loopback / RFC1918 / ULA / CGNAT / `.localhost` through our tools. `deny` by default because a 2.x plugin cannot raise a dialog. Never conflated with the FORBIDDEN ranges. |
|
|
372
|
+
| `bashReadonlyAllowed` | string[] | built-in | **red line.** P3 read-only allowlist; `tasklist` / `ps` / `findstr` are seeded because `已确认关闭` is only checkable if the agent can ask the OS for the pid. |
|
|
373
|
+
|
|
374
|
+
**Legacy keys** — listed and parseable so an old config still loads, with **no v2 reader**, and reported as such:
|
|
375
|
+
|
|
376
|
+
| Key | Default | Status |
|
|
377
|
+
|---|---|---|
|
|
378
|
+
| `askTimeoutFloorMin` | `1` | **Orphaned.** Its only consumer was the v1 approval gate, which was deleted. |
|
|
379
|
+
| `compactionContext` | `on` | v1-only |
|
|
380
|
+
| `shellEnv` | `""` | v1-only |
|
|
381
|
+
| `ptcWebBridge` | `on` | v1-only |
|
|
382
|
+
|
|
383
|
+
</details>
|
|
384
|
+
|
|
385
|
+
> **Compaction timing belongs to the host, not to this plugin.** An earlier release triggered its own summarization at 75% of the window; that was removed by user decision — a plugin that summarizes a live conversation by itself takes a context-losing action you did not ask for. What remains runs only when the HOST compacts: the must-survive list (reply skeleton, offload handles, open sub-agent ids, provenance, board paths) rides `session.hook("compaction")`, and the host's summarizer prompt is never replaced.
|
|
386
|
+
|
|
387
|
+
### The three surviving `TM_*` names
|
|
388
|
+
|
|
389
|
+
These are **internal / test switches**, not configuration — read from the environment so a test or diagnostic needs no config file:
|
|
390
|
+
|
|
391
|
+
| Name | Effect |
|
|
392
|
+
|---|---|
|
|
393
|
+
| `TM_STORE_RECLAIM` | Overrides the `storeReclaim` key (`off` = leave the disk exactly as found). The test runner sets it. |
|
|
394
|
+
| `TM_V2_PROBE` | Path to a JSONL file where the surface probe records the host's real tool ids, permission action names and argument key **names** — names and counts only, never a command line, path, URL or env value. |
|
|
395
|
+
| `TM_CONFIG_AUTOCREATE` | `off` suppresses the inert global-file auto-create. |
|
|
396
|
+
|
|
397
|
+
Any **other** `TM_*` name in your environment is inert. If you have been setting one, move it into `team-mode.jsonc` under the matching key above.
|
|
398
|
+
|
|
399
|
+
---
|
|
400
|
+
|
|
401
|
+
## 🏗️ How the team works
|
|
402
|
+
|
|
403
|
+
Sub-agents cannot message each other live, so coordination is a **structured reply skeleton** — every specialist reply is `STATUS: / CHANGES: / FINDINGS: / EVIDENCE: / HANDOFF:`, ≤50 lines, relayed verbatim into the next dispatch. A deliverable over ~50 lines goes to ONE board file under `<repo>/.git/opencode-team/` (round-suffixed; working tree untouched). The package exports `{id, setup}`: `setup(ctx)` registers the `tm_*` tools, resolves the two config layers, arms the R6 / address guard, attaches JIT governance on `tool.execute.after` and publishes the blackboard note. `CONFIG_KEYS` is the single source of truth, so a key absent from it is reported and never applied.
|
|
404
|
+
|
|
405
|
+
- **Routing table:** question → direct answer; docs-only → implementer; product change → implementer → tester → reviewer; multi-module → architect → implementer → tester → reviewer(s); unknown tech → researcher first. Fixed minimums — a product change routed below 3 dispatches is a routing bug.
|
|
406
|
+
- **One dispatch, one verifiable deliverable** — if the lead cannot say how a child's result will be verified *on its own*, it is a wish, not a dispatch.
|
|
407
|
+
- **The architect is conditional:** only when the design is genuinely unknown. With a verified root cause (file:line evidence), skip it and spec the fix.
|
|
408
|
+
- **Tester and reviewer may run in the SAME round**; likewise implementers with disjoint file ownership and testers on disjoint packages.
|
|
409
|
+
- **Approval gate (count-based):** ≥2 dispatches → plan (≤30 lines) → **your approval** → execute. Blocking questions are batched and asked immediately.
|
|
410
|
+
- **Only the Team Lead dispatches** — specialists hold no `task`/`subagent` grant, so no sub-agent spawns a sub-agent.
|
|
411
|
+
- **Adaptive review:** one reviewer by default; three parallel dimensions only for auth/security, cross-module contracts or public APIs.
|
|
412
|
+
- **Static verification:** build / typecheck / lint / tests; improvised UI work ends with `UI NOT VERIFIED: <what to check>`, and every done / fixed / passed claim needs output, logs or diffs.
|
|
413
|
+
|
|
414
|
+
Module map, per-layer seams and every measured host fact: [AGENTS.md](./AGENTS.md). Dated evidence behind each v2 claim: [docs/v2-port-notes.md](./docs/v2-port-notes.md). `pt07/` holds the PT-07 baseline suite; `scripts/` the one-line installers.
|
|
415
|
+
|
|
416
|
+
## 🔧 Customization
|
|
417
|
+
|
|
418
|
+
**Override an agent** — the same name in your config wins:
|
|
419
|
+
|
|
420
|
+
```jsonc
|
|
421
|
+
{
|
|
422
|
+
"agent": {
|
|
423
|
+
"reviewer": {
|
|
424
|
+
"model": "anthropic/claude-sonnet-4-6",
|
|
425
|
+
"prompt": "You are an extremely strict reviewer. Reject anything with a lint warning."
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
**Add your own agents** alongside the team:
|
|
432
|
+
|
|
433
|
+
```jsonc
|
|
434
|
+
{
|
|
435
|
+
"agent": {
|
|
436
|
+
"devops": {
|
|
437
|
+
"mode": "subagent",
|
|
438
|
+
"description": "Handles CI/CD, Docker, and deployment tasks.",
|
|
439
|
+
"prompt": "You are the DevOps engineer..."
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
**Disable one:** `"researcher": { "disable": true }`. Board retention, auto-create, the default slot and the R6 kill switch are plugin-entry `options` — see [Configuration](#️-configuration).
|
|
446
|
+
|
|
447
|
+
## ❓ Troubleshooting
|
|
448
|
+
|
|
449
|
+
**Will this eat my tokens?** The opposite is the point. Offload + 80-token previews + the host's Code Mode `execute` exist because a five-agent pipeline naively bolted onto one context window *would* eat your tokens. The governance is the token-saver.
|
|
450
|
+
|
|
451
|
+
**Is the web access safe?** It is the most guarded surface in the plugin: two full web roles plus a browser-only tester grant, an allowlist whose off-list calls fail closed, redirects re-checked per hop with the whole chain reported, env-file URL refusal, address red lines no setting opens, and every payload under the same offload governance.
|
|
452
|
+
|
|
453
|
+
**Why doesn't the plugin auto-update?** OpenCode caches plugins by spec string and never re-resolves `@latest` (upstream limitation). **Re-run the installer — that IS the update**; or delete the cache by hand:
|
|
454
|
+
|
|
455
|
+
| OS | Purge command — recursive, covers BOTH cache layouts |
|
|
456
|
+
|---|---|
|
|
457
|
+
| macOS / Linux | `find ~/.cache/opencode/packages -type d -name '*opencode-team-mode*' -prune -exec rm -rf {} +` |
|
|
458
|
+
| Windows (PowerShell) | `foreach ($r in "$HOME\.cache\opencode\packages", "$env:LOCALAPPDATA\opencode\cache\packages") { if (Test-Path $r) { Get-ChildItem $r -Directory -Recurse -Filter '*opencode-team-mode*' -EA SilentlyContinue | Sort-Object { $_.FullName.Length } | Remove-Item -Recurse -Force -EA SilentlyContinue } }` |
|
|
459
|
+
|
|
460
|
+
A top-level-only delete silently misses the scoped `@te-river/` copy: OpenCode loads from `~/.cache/opencode/packages/`, **not** `~/.config/opencode/node_modules`. And **a published version can be shadowed by your HOME** — the host resolves a package spec through the nearest `node_modules` walk from HOME, so a stray `~/package.json` with a `devDependencies` pin loads *that* copy. The diagnostic is the boot probe's `entrypoint=` line, not a guess.
|
|
461
|
+
|
|
462
|
+
**A knob I set in an env var does nothing.** Configuration is JSON now; any `TM_*` variable other than the three internal switches is inert. Run `tm_stats` and read the layered-config section — it names the per-key source, so a value you expected shows a source that is not the file you edited.
|
|
463
|
+
|
|
464
|
+
**A red-line key I set in the project file is ignored.** By design: the five red-line keys are honored only in the **global** file, and `tm_stats` reports the ignored attempt by name.
|
|
465
|
+
|
|
466
|
+
**My `AGENT NOT FOUND: "Team"`.** The file name IS the agent id, and Windows/APFS keep the existing spelling when a write opens the file through another one — so a pre-1.7.2 `team.md` survives every install and the host registers the lead as lowercase. Use `--agent team`; the installer's reclaim pass renames a case-only entry it has proven is the same file.
|
|
467
|
+
|
|
468
|
+
**Can agents run tools in parallel?** Yes, and they are engineered for it: parallel `tm_search` / `tm_webfetch` / `tm_fetch` calls get distinct step ids and isolated payloads, and a regression fails the suite before it reaches you.
|
|
469
|
+
|
|
470
|
+
**Does it work in the CLI (TUI), or only Desktop?** Both. Desktop adds the color-coded picker and panels; the governed tools and the workflow are host-agnostic. Interactive browsing is the host's own `browser_*` catalog, so CLI / standalone has none — reported as a gap, never simulated.
|
|
471
|
+
|
|
472
|
+
**What happens if I don't answer the host's permission prompt?** The host decides that, not this plugin. The plugin can only refuse: it never self-allows, every guard can only make a rule stricter, and a session it does not recognize as one of its own is left exactly as the host behaved.
|
|
473
|
+
|
|
474
|
+
---
|
|
475
|
+
|
|
476
|
+
## 🗑️ Uninstall
|
|
477
|
+
|
|
478
|
+
1. Remove the entry from the `"plugins"` array in your config file; on 2.x also delete the generated `~/.config/opencode/agents/*.md` and `commands/team-*.md`, and any `default_agent: "team"` you no longer want.
|
|
479
|
+
2. Delete the cache dir (purge commands in [Troubleshooting](#-troubleshooting)) if you want the disk space back.
|
|
480
|
+
3. Restart OpenCode. The agents, commands and tools are gone; the stores under `<repo>/.git/opencode-team/` (and `~/.opencode-team/` for global memories) are plain files you can delete whenever. The global `team-mode.jsonc` is yours to keep or delete — the plugin writes a fresh inert one if you remove it.
|
|
481
|
+
|
|
482
|
+
No DLLs were harmed; nothing was written to your working tree.
|
|
483
|
+
|
|
484
|
+
## 🤝 Contributing
|
|
485
|
+
|
|
486
|
+
Issues and PRs welcome. Especially wanted: localization of agent prompts, more agent roles, more command templates, and real-world reports of the search engines' behavior (they rearrange their markup; the extractor filters are intentionally loose but not psychic).
|
|
487
|
+
|
|
488
|
+
## 📄 License
|
|
489
|
+
|
|
490
|
+
[Apache License 2.0](./LICENSE)
|
|
491
|
+
|
|
492
|
+
## 🔗 Links
|
|
493
|
+
|
|
494
|
+
- [npm Package](https://www.npmjs.com/package/@te-river/opencode-team-mode) — `@te-river/opencode-team-mode`
|
|
495
|
+
- [Installation guide, **OpenCode 2.x**](./docs/installation-v2.md) — the supported install: a 2.x plugin cannot create an agent, so the six roles and six commands are generated into the config directory
|
|
496
|
+
- [Installation guide, 1.18.x](./docs/installation.md) — **historical only**: the 1.18.x personality was removed in the 1.7.0 line
|
|
497
|
+
- [OpenCode Desktop](https://opencode.ai) — Official website & download
|
|
498
|
+
- [OpenCode Docs](https://opencode.ai/docs) — Configuration & plugin documentation
|
|
499
|
+
- [OpenCode Plugin API](https://opencode.ai/docs/plugins) — Build your own plugins
|