@te-river/opencode-team-mode 1.5.11 → 1.5.13

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.
Files changed (112) hide show
  1. package/README.md +154 -57
  2. package/README.zh-CN.md +126 -47
  3. package/dist/agents.d.ts +5 -1
  4. package/dist/agents.d.ts.map +1 -1
  5. package/dist/agents.js +37 -11
  6. package/dist/agents.js.map +1 -1
  7. package/dist/approval-gate.d.ts +54 -16
  8. package/dist/approval-gate.d.ts.map +1 -1
  9. package/dist/approval-gate.js +163 -42
  10. package/dist/approval-gate.js.map +1 -1
  11. package/dist/host-hooks.d.ts +115 -0
  12. package/dist/host-hooks.d.ts.map +1 -0
  13. package/dist/host-hooks.js +270 -0
  14. package/dist/host-hooks.js.map +1 -0
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +68 -4
  17. package/dist/index.js.map +1 -1
  18. package/dist/prompts/lead.d.ts +1 -1
  19. package/dist/prompts/lead.d.ts.map +1 -1
  20. package/dist/prompts/lead.js +88 -6
  21. package/dist/prompts/lead.js.map +1 -1
  22. package/dist/prompts/shared.d.ts +1 -1
  23. package/dist/prompts/shared.d.ts.map +1 -1
  24. package/dist/prompts/shared.js +203 -119
  25. package/dist/prompts/shared.js.map +1 -1
  26. package/dist/prompts/specialists.d.ts +2 -2
  27. package/dist/prompts/specialists.d.ts.map +1 -1
  28. package/dist/prompts/specialists.js +73 -23
  29. package/dist/prompts/specialists.js.map +1 -1
  30. package/dist/tm/args-schema.d.ts +31 -1
  31. package/dist/tm/args-schema.d.ts.map +1 -1
  32. package/dist/tm/args-schema.js +155 -39
  33. package/dist/tm/args-schema.js.map +1 -1
  34. package/dist/tm/bash-timeout.d.ts +72 -0
  35. package/dist/tm/bash-timeout.d.ts.map +1 -0
  36. package/dist/tm/bash-timeout.js +100 -0
  37. package/dist/tm/bash-timeout.js.map +1 -0
  38. package/dist/tm/browser.d.ts +250 -20
  39. package/dist/tm/browser.d.ts.map +1 -1
  40. package/dist/tm/browser.js +1200 -143
  41. package/dist/tm/browser.js.map +1 -1
  42. package/dist/tm/config.d.ts +129 -0
  43. package/dist/tm/config.d.ts.map +1 -1
  44. package/dist/tm/config.js +117 -1
  45. package/dist/tm/config.js.map +1 -1
  46. package/dist/tm/dispatch.d.ts +95 -0
  47. package/dist/tm/dispatch.d.ts.map +1 -0
  48. package/dist/tm/dispatch.js +369 -0
  49. package/dist/tm/dispatch.js.map +1 -0
  50. package/dist/tm/index.d.ts +32 -7
  51. package/dist/tm/index.d.ts.map +1 -1
  52. package/dist/tm/index.js +74 -5
  53. package/dist/tm/index.js.map +1 -1
  54. package/dist/tm/memory.d.ts +88 -10
  55. package/dist/tm/memory.d.ts.map +1 -1
  56. package/dist/tm/memory.js +881 -99
  57. package/dist/tm/memory.js.map +1 -1
  58. package/dist/tm/pipelines.d.ts +26 -3
  59. package/dist/tm/pipelines.d.ts.map +1 -1
  60. package/dist/tm/pipelines.js +167 -11
  61. package/dist/tm/pipelines.js.map +1 -1
  62. package/dist/tm/ptc/budgets.d.ts +22 -1
  63. package/dist/tm/ptc/budgets.d.ts.map +1 -1
  64. package/dist/tm/ptc/budgets.js +87 -8
  65. package/dist/tm/ptc/budgets.js.map +1 -1
  66. package/dist/tm/ptc/contract.d.ts +49 -9
  67. package/dist/tm/ptc/contract.d.ts.map +1 -1
  68. package/dist/tm/ptc/contract.js +41 -3
  69. package/dist/tm/ptc/contract.js.map +1 -1
  70. package/dist/tm/ptc/driver.d.ts +5 -2
  71. package/dist/tm/ptc/driver.d.ts.map +1 -1
  72. package/dist/tm/ptc/driver.js +47 -15
  73. package/dist/tm/ptc/driver.js.map +1 -1
  74. package/dist/tm/ptc/engines.d.ts +15 -4
  75. package/dist/tm/ptc/engines.d.ts.map +1 -1
  76. package/dist/tm/ptc/engines.js +281 -28
  77. package/dist/tm/ptc/engines.js.map +1 -1
  78. package/dist/tm/ptc/index.d.ts +4 -4
  79. package/dist/tm/ptc/index.d.ts.map +1 -1
  80. package/dist/tm/ptc/index.js +2 -2
  81. package/dist/tm/ptc/index.js.map +1 -1
  82. package/dist/tm/ptc/pscan.d.ts +20 -1
  83. package/dist/tm/ptc/pscan.d.ts.map +1 -1
  84. package/dist/tm/ptc/pscan.js +328 -14
  85. package/dist/tm/ptc/pscan.js.map +1 -1
  86. package/dist/tm/ptc/summary.d.ts.map +1 -1
  87. package/dist/tm/ptc/summary.js +23 -1
  88. package/dist/tm/ptc/summary.js.map +1 -1
  89. package/dist/tm/ptc/tool.d.ts +34 -3
  90. package/dist/tm/ptc/tool.d.ts.map +1 -1
  91. package/dist/tm/ptc/tool.js +117 -20
  92. package/dist/tm/ptc/tool.js.map +1 -1
  93. package/dist/tm/pty.d.ts +70 -0
  94. package/dist/tm/pty.d.ts.map +1 -0
  95. package/dist/tm/pty.js +242 -0
  96. package/dist/tm/pty.js.map +1 -0
  97. package/dist/tm/search.d.ts +128 -15
  98. package/dist/tm/search.d.ts.map +1 -1
  99. package/dist/tm/search.js +556 -47
  100. package/dist/tm/search.js.map +1 -1
  101. package/dist/tm/shell-bridge.d.ts +14 -1
  102. package/dist/tm/shell-bridge.d.ts.map +1 -1
  103. package/dist/tm/shell-bridge.js +57 -10
  104. package/dist/tm/shell-bridge.js.map +1 -1
  105. package/dist/tm/webfetch.d.ts +29 -7
  106. package/dist/tm/webfetch.d.ts.map +1 -1
  107. package/dist/tm/webfetch.js +117 -15
  108. package/dist/tm/webfetch.js.map +1 -1
  109. package/dist/types.d.ts +16 -0
  110. package/dist/types.d.ts.map +1 -1
  111. package/docs/installation.md +216 -17
  112. package/package.json +5 -2
package/README.md CHANGED
@@ -44,10 +44,11 @@ TeamMode's answer to each:
44
44
 
45
45
  | Pain | TeamMode's answer |
46
46
  |---|---|
47
- | 🔥 **Context flooding** | Every governed tool output over 2000 tokens is offloaded to a local run store and replaced by an 80-token preview + an HMAC handle. The agent pages through what it needs — the window never drowns. |
47
+ | 🔥 **Context flooding** | Every governed tool output over its content-class offload threshold (prose 4000 / data 2000 tokens, CJK-aware) is offloaded to a local run store and replaced by an 80-token preview + an HMAC handle. The agent pages through what it needs — the window never drowns. |
48
48
  | 🐌 **Round-trip overhead** | `tm_ptc_run`: the agent writes ONE program that makes N governed calls in a single turn. Zero LLM round-trips during the run. |
49
- | 🕳️ **Silent side effects** | R6/R2 approval gate: env-var reads and dangerous ops route through OpenCode's official confirmation dialog, auto-rejected after 10 unanswered minutes. The plugin never approves on its own — it only ever rejects. |
49
+ | 🕳️ **Silent side effects** | R6/R2 approval gate: env-var reads and dangerous ops route through OpenCode's official confirmation dialog, auto-rejected after 1 unanswered minute (default). The plugin never approves on its own — it only ever rejects. |
50
50
  | 🌫️ **Hallucinated research** | Web access is a two-role grant with an allowlisted, governed tool chain. A fact that couldn't be fetched is reported as a gap — never fabricated. |
51
+ | 🧭 **Walls of text** | Replies are steered into the shape the host renders fastest: a markdown table for per-file / per-case / per-finding results, fenced code for diffs and configs, a browser screenshot attached as an inline image only when you ask for one. The host does not draw mermaid, so no agent pretends it does. |
51
52
 
52
53
  And the workflow discipline underneath: deterministic routing, a ≤30-line plan
53
54
  you approve before ≥2 dispatches execute, structured `STATUS/CHANGES/FINDINGS/
@@ -127,10 +128,12 @@ OpenCode installs the plugin on next startup.
127
128
  - **Restart to activate.** After touching `opencode.json`, fully quit and restart OpenCode (Desktop: quit from tray, not just the window).
128
129
  - **Plugin updates: re-run the installer.** It is idempotent — a re-run re-patches the config (no-op when present), purges the stale plugin cache, and re-resolves any npm-installed copy. This exists because OpenCode caches plugins by spec string and does NOT re-resolve `@latest` when a new version publishes (upstream limitation). Manual recipe, if you prefer:
129
130
 
130
- | OS | Cache location |
131
+ | OS | Purge command — recursive, covers BOTH cache layouts |
131
132
  |---|---|
132
- | macOS / Linux | `rm -rf ~/.cache/opencode/packages/@te_river+opencode-team-mode@latest` |
133
- | Windows | `Remove-Item -Recurse -Force "$env:LOCALAPPDATA\opencode\cache\packages\@te_river+opencode-team-mode@latest"` |
133
+ | macOS / Linux | `find ~/.cache/opencode/packages -type d -name '*opencode-team-mode*' -prune -exec rm -rf {} +` |
134
+ | 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 } }` |
135
+
136
+ ⚠️ OpenCode loads the plugin from `~/.cache/opencode/packages/` — possibly nested inside the scoped `@te-river/` directory — **not** from `~/.config/opencode/node_modules`, which is why the delete must recurse (a top-level-only `rm -rf` silently misses the scoped copy).
134
137
 
135
138
  If you also npm-installed the plugin into `~/.config/opencode`, its package-lock pins the version — run `npm install @te-river/opencode-team-mode@latest` there too. Full recipe (including an agent-driven update prompt): [installation guide, Updating](./docs/installation.md).
136
139
  - **Prerequisites:** [OpenCode](https://opencode.ai) (Desktop or CLI) and Node ≥ 18.
@@ -201,19 +204,24 @@ as a structured skeleton — nothing landed in your repo, nothing was guessed.
201
204
  ## 🧰 Governed tools & security
202
205
 
203
206
  Every tool TeamMode adds runs under ONE governance pipeline: outputs above
204
- `TM_OFFLOAD_THRESHOLD` tokens never enter the context window — they are
205
- offloaded to a run store and replaced by a content-aware preview plus an
206
- HMAC-signed handle that the agent pages through with `tm_fetch` when it
207
- genuinely needs the payload.
207
+ the offload threshold never enter the context window — the boundary is
208
+ content-class aware (`TM_OFFLOAD_THRESHOLD_TEXT` = 4000 for prose:
209
+ text/log/markdown; `TM_OFFLOAD_THRESHOLD_DATA` = 2000 for json/csv/code/
210
+ binary; an unknown class falls back to the global `TM_OFFLOAD_THRESHOLD`) —
211
+ and oversized results are offloaded to a run store and replaced by a
212
+ content-aware preview plus an HMAC-signed handle that the agent pages
213
+ through with `tm_fetch` when it genuinely needs the payload.
208
214
 
209
215
  | Tool | What it does | Roles |
210
216
  |---|---|---|
211
- | `tm_read` / `tm_grep` / `tm_bash` / `tm_fetch` | Governed file read / regex search / read-only shell (allowlist) / paged handle retrieval | all six agents |
212
- | `tm_memory` | Project + global memory store (Markdown + frontmatter): add / search / list / forget | all six agents |
213
- | `tm_ptc_run` | Batch orchestration: one program, N governed calls, zero LLM round-trips | all six agents |
214
- | `tm_search` | Multi-engine web search with extracted, deduplicated hit lists | Lead + Researcher |
217
+ | `tm_read` / `tm_grep` / `tm_bash` / `tm_fetch` | Governed file read / regex search / read-only shell (allowlist) / paged handle retrieval (JSON handles take a `fields` dot-path projection — a deliberately small jq subset like `items[].name`) | all six agents |
218
+ | `tm_memory` | Session + project + global memory store (Markdown + frontmatter): add / search / list / forget / compact | all six agents |
219
+ | `tm_ptc_run` | Batch orchestration: one program, N governed calls, zero LLM round-trips; web roles also get `tm.search` / `tm.webfetch` inside the program | all six agents |
220
+ | `tm_search` | Multi-engine web search with extracted, deduplicated, RRF-fused hit lists | Lead + Researcher |
215
221
  | `tm_webfetch` | Single governed GET of an allowlisted page (search pages auto-extracted) | Lead + Researcher |
216
- | `tm_browser` | Interactive browser session (CDP, **your default browser**): open / navigate / read / screenshot / close | Lead + Researcher + Tester (UI verification) |
222
+ | `tm_dispatch` / `tm_join` | **Async sub-agent dispatch**: `tm_dispatch` starts a specialist in its own child session and returns the id immediately (the built-in `task` tool blocks you until it finishes), `tm_join` collects — status snapshot, bounded `waitMs`, `cancel:true` to abort a runaway child; collected replies ride the offload pipeline, so five fat reports arrive as handles + previews instead of multiplying your context. Lead-only, and a child never dispatches | Lead only |
223
+ | `tm_pty` | **Non-blocking command execution** on the host's own terminal sessions (`start`/`status`/`list`/`kill`) — independent builds and test suites overlap instead of queueing behind one 120 s bash call. Captures no output (the command tees its own log; read it with `tm_read`), and every start passes the R6 classifier, the R2 danger-face globs **and** the official confirmation dialog before a process exists | Lead only |
224
+ | `tm_browser` | Interactive browser session (**your default browser**): 16 Playwright verbs (snapshot-first `take_snapshot` → uid-addressed `click`/`fill`/`drag`/…) + 5 legacy compat verbs (open/navigate/read/screenshot/close); Playwright engine needs Node ≥ 20, below that (or on any import failure) it auto-degrades to the legacy CDP engine. It drives YOUR default browser channel (an Edge Beta default opens Edge Beta), stays headful unless the operator sets `TM_BROWSER_HEADLESS`, loads a page's own images/CSS/JS via the `same-site` subresource policy, and `take_screenshot { image:true }` attaches a JPEG so the model can actually see the screen | Lead + Researcher + Tester (UI verification) |
217
225
 
218
226
  > **Fixed tool priority ladder (every task): ① TeamMode governed tools
219
227
  > (`tm_*`) → ② user MCP/plugin tools → ③ the model's own reasoning.**
@@ -228,26 +236,48 @@ its own step id and its own payload, and nothing cross-contaminates
228
236
  ### Context governance: offload, handles, previews
229
237
 
230
238
  Large tool outputs are context cost's main driver — every step re-sends the
231
- whole window. So results above `TM_OFFLOAD_THRESHOLD` tokens are written to a
232
- local run store (`<repo>/.git/opencode-team/`, never your working tree) and
233
- replaced by a handle with a content-aware preview: JSON keys / CSV header +
234
- shape / log ERROR×N stats / code signatures / binary metadata, hard-capped at
235
- 80 tokens. When the agent actually needs the payload, it pages through with
236
- `tm_fetch` using an HMAC-signed, run-scoped, expiring handle. `tm_bash` only
237
- allows read-only commands (allowlist), and failures come back as structured
238
- errors instead of raw dumps.
239
-
240
- ### Project + global memory (tm_memory)
239
+ whole window. So results above the content-class offload boundary (prose →
240
+ `TM_OFFLOAD_THRESHOLD_TEXT`, structured data → `TM_OFFLOAD_THRESHOLD_DATA`,
241
+ unknown → global `TM_OFFLOAD_THRESHOLD`) are written to a local run store
242
+ (`<repo>/.git/opencode-team/`, never your working tree) and replaced by a
243
+ handle with a content-aware preview: JSON keys / CSV header + shape / log
244
+ ERROR×N stats / code signatures / binary metadata, hard-capped at 80 tokens.
245
+ When the agent actually needs the payload, it pages through with `tm_fetch`
246
+ using an HMAC-signed, run-scoped, expiring handle — and on a JSON handle it
247
+ can ask for a `fields` dot-path projection instead (a deliberately small jq
248
+ subset: `items[].name`, `[].stargazers_count`), so a big API dump narrows to
249
+ just the values needed without the raw body ever entering the window.
250
+ `tm_bash` only allows read-only commands (allowlist), and failures come back
251
+ as structured errors instead of raw dumps.
252
+
253
+ ### Session + project + global memory (tm_memory)
241
254
 
242
255
  Durable facts — build commands, environment quirks, architecture decisions,
243
- your conventions — live as human-editable Markdown with frontmatter:
244
-
245
- - **`project`** (default): `<repo>/.git/opencode-team/memories/…` — per checkout, git-adjacent.
246
- - **`global`**: `~/.opencode-team/memories/global/` (override `TM_MEMORY_GLOBAL_DIR`) — **follows you across ALL projects**.
247
-
248
- Actions: `add` / `search` (deterministic keyword scoring) / `list` / `forget`;
249
- 4000 chars per memory. Agents are prompted to search before assuming
250
- conventions and to save hard-won facts for the next conversation.
256
+ your conventions — live as human-editable Markdown with frontmatter, in
257
+ THREE tiers:
258
+
259
+ - **`session`**: this conversation's transients only — in-process, TTL-swept
260
+ (`TM_MEMORY_SESSION_TTL_MIN`, default 240 min), invisible to other
261
+ sessions; ephemeral unless `TM_MEMORY_SESSION_PERSIST=1` writes them under
262
+ `memories/sessions/<sid>/`.
263
+ - **`project`** (default): `<repo>/.git/opencode-team/memories/…` — per checkout, git-adjacent. Facts about THIS repo: build commands, environment quirks, architecture decisions.
264
+ - **`global`**: `~/.opencode-team/memories/global/` (override `TM_MEMORY_GLOBAL_DIR`) — **follows you across ALL projects**. User-level conventions: preferred package manager, commit style, tooling habits.
265
+
266
+ Actions: `add` / `search` (deterministic keyword scoring) / `list` / `forget` /
267
+ `compact`; 4000 chars per memory. `search` walks ALL tiers with **session >
268
+ project > global precedence** — project entries get a +2 near-tie weight and a
269
+ same-title higher-layer twin shadows the lower one (it never surfaces).
270
+ Near-duplicates never pile up: an `add` that hits an existing entry in the same
271
+ tier and category (dedup key, or title+keywords Jaccard ≥ 0.6) **folds into
272
+ it** — new content wins, keywords union, the old slug moves to `supersedes:`,
273
+ and the answer says 已合并 (that is normal; don't re-add under a variant title).
274
+ When a tier reaches `TM_MEMORY_MAX_ENTRIES` (200 per scope) the add fails on
275
+ purpose: run `compact` first — it dry-runs the merge plan by default, and
276
+ re-running with `apply:true` performs it after copying every original to a
277
+ timestamped `.compact-backup` tree (the rollback path). Entries older than
278
+ `TM_MEMORY_STALE_DAYS` (30) surface tagged `[stale Nd]` in search. Agents
279
+ are prompted to search before assuming conventions and to save hard-won
280
+ facts for the next conversation.
251
281
 
252
282
  ### 🌐 Web search that actually works (in China)
253
283
 
@@ -258,18 +288,22 @@ sees raw SERP chrome.
258
288
 
259
289
  | Engine | Notes |
260
290
  |---|---|
261
- | `bing` (default) | cn.bing.com; `bing-int` forces international results (`ensearch=1`) |
262
- | `sogou` / `so` (360) | CN-native engines, good for CJK content |
263
- | `baidu` | flakiest (anti-bot) but sometimes the only CN-specific index; failures name alternatives |
291
+ | `auto` (default) | Classifies the query, fans the matching engines out **in parallel** (every route has ≥2 legs), dedupes by host+path and fuses them with weighted RRF into a top-10 list tagged with each hit's source engine(s). Ranking is scaled by real query-token overlap (floor `TM_SEARCH_RELEVANCE_FLOOR`), so a trusted engine's off-topic junk no longer outranks another engine's best hit. Routing: errors / camelCase APIs → `stackoverflow`+`github`+`bing`; dev-ecosystem (releases, frameworks, open source) → `hn`+`github`+`npm`; Chinese → `bing`+`moegirl`+`stackoverflow`+`hn`; other → `bing`+`stackoverflow`+`hn`+`github`. Pin another default via `TM_SEARCH_DEFAULT_ENGINE` |
292
+ | `bing` | cn.bing.com — the only live CN HTML SERP; multi-word CJK queries get their phrase boundary protected (quoted) so markup shuffle can't split the result list |
293
+ | `stackoverflow` | api.stackexchange.com question search (no key, 300/day/IP) → numbered questions with composite snippets; `auto` tracks the quota and swaps in `bing` once spent |
294
+ | `hn` | Hacker News via Algolia API (no key) → story titles + snippets with direct article URLs |
264
295
  | `bilibili` | video search |
265
296
  | `moegirl` | MediaWiki search API — entry titles + snippets, structured |
266
297
  | `npm` | registry search → name@version + description, structured |
267
- | `github` | repo search API → stars + description, structured |
298
+ | `github` | repo search API → stars + description, structured; `org:` / `user:` / `stars:` / `language:` qualifiers fold into the query (e.g. `vector db stars:>500 language:rust`) |
268
299
 
269
- All nine engines are reachable from mainland China **without API keys**, and
270
- every one of them sits on the seeded domain allowlist. On an empty result
271
- (an anti-bot shell), the error names the alternative engines instead of
272
- leaving the agent stuck. Two more channels complete the surface:
300
+ All seven engines are reachable from mainland China **without API keys**, and
301
+ every one of them sits on the seeded domain allowlist. The previous CN HTML
302
+ SERPs (`sogou` / `so` / `baidu` / `bing-int`) were **removed** — a live
303
+ benchmark (2026-09-14) showed them serving anti-bot shells or 100% empty
304
+ results; they are not even manually selectable. On an empty result,
305
+ the error names the alternative engines instead of leaving the agent stuck.
306
+ Two more channels complete the surface:
273
307
 
274
308
  - `tm_webfetch` — a known URL, one governed GET. Search-engine pages it
275
309
  fetches are auto-extracted to hit lists too. JSON endpoints like
@@ -277,9 +311,28 @@ leaving the agent stuck. Two more channels complete the surface:
277
311
  - `tm_browser` — JS-rendered pages: **your DEFAULT browser** (Windows
278
312
  registry / Linux `xdg-settings`; Chromium-family only — Firefox falls back
279
313
  to the Edge/Chrome probe order because CDP is Chromium-proprietary;
280
- `TM_BROWSER_PATH` overrides), headful via CDP pipe, isolated temp profile,
281
- **domain allowlist enforced at the network layer** per request
282
- (`Fetch.requestPaused` → non-allowlisted hosts get `BlockedByClient`).
314
+ `TM_BROWSER_PATH` overrides), headful by default, isolated temp profile,
315
+ **domain allowlist enforced at the network layer** per request and per
316
+ redirect hop. The action surface is chrome-devtools-mcp aligned: **16
317
+ Playwright verbs** (`navigate_page` · `take_snapshot` · `click` · `fill` ·
318
+ `hover` · `drag` · `press_key` · `select_page` · `upload_file` · `wait_for`
319
+ · `evaluate_script` · `list_console_messages` · `list_network_requests` ·
320
+ `list_pages` · `take_screenshot` · `handle_dialog`) plus 5 legacy compat
321
+ verbs (`open` / `navigate` / `read` / `screenshot` / `close`).
322
+ Snapshot-first: `take_snapshot` returns the aria snapshot with injected
323
+ `[uid=eN]` tokens, follow-up actions address nodes by uid instead of
324
+ guessed locators; snapshots are hard-capped by
325
+ `TM_BROWSER_SNAPSHOT_MAX_TOKENS` (default 1200).
326
+ **Engine split:** the primary engine is `playwright-core` (an optional
327
+ dependency — needs **Node ≥ 20**; on older Node, or if the import fails,
328
+ the session auto-degrades per instance to the zero-dep `cdp-legacy`
329
+ engine, which keeps the core verbs only; pin either via
330
+ `TM_BROWSER_ENGINE=playwright|cdp-legacy`). No browser download is ever
331
+ involved — Playwright launches YOUR installed browser by path, so
332
+ `npx playwright install` is not part of the user flow (the dependency
333
+ itself resolves at npm install/publish time). Isolated temp profile by
334
+ default: persistent logins only if you explicitly set
335
+ `TM_BROWSER_USER_DATA_DIR`.
283
336
 
284
337
  When a fetch still returns **403 after the real-Chrome headers**, the error
285
338
  is a DIRECTIVE: the gate is JS-challenge / TLS-fingerprint based and only a
@@ -289,16 +342,18 @@ filter known noise: engine-internal wrappers (`so.com/link?`, `ai.so.com`)
289
342
  and same-name-different-site domains (`maimai.cn` 脉脉 vs the maimai DX
290
343
  game) never ride along — extend the hit blacklist with `TM_HIT_BLACKLIST`.
291
344
 
292
- Seeded allowlist (both tools; 21 hosts — baidu/moegirl/bilibili are PARENT
293
- domains, so every sibling subdomain — baike.baidu.com, mzh.moegirl.org.cn,
294
- space.bilibili.com — is covered):
345
+ Seeded allowlist (all three web tools; 23 hosts — baidu/moegirl/bilibili are
346
+ PARENT domains, so every sibling subdomain — baike.baidu.com,
347
+ mzh.moegirl.org.cn, space.bilibili.com — is covered):
295
348
  `baidu.com`, `moegirl.org.cn`, `bilibili.com`, `www.sogou.com`, `www.so.com`,
296
349
  `cn.bing.com`, `www.bing.com`, `zhihu.com`, `juejin.cn`, `csdn.net`,
297
350
  `cnblogs.com`, `gitee.com`, `github.com`, `api.github.com`,
298
351
  `raw.githubusercontent.com`, `gist.githubusercontent.com`, `ghproxy.net`
299
- (mainland mirror for github raw), `stackoverflow.com`, `npmjs.org`,
300
- `pypi.org`, `learn.microsoft.com` — extend via
301
- `TM_WEBFETCH_ALLOWED_DOMAINS` (`"*"` opens every host). Architect /
352
+ (mainland mirror for github raw), `stackoverflow.com`, `api.stackexchange.com`
353
+ + `hn.algolia.com` (the two JSON search engines), `npmjs.org`, `pypi.org`,
354
+ `learn.microsoft.com` — extend via
355
+ `TM_WEBFETCH_ALLOWED_DOMAINS` (`"*"` opens every host; a custom list REPLACES
356
+ the seed, so keep the engine hosts or `tm_search` loses its targets). Architect /
302
357
  implementer / reviewer have NO network grant — web questions come back as a
303
358
  reported gap, never simulated. The tester carries `tm_browser` ONLY, for
304
359
  governed UI verification of the project (local dev servers, preview routes);
@@ -307,7 +362,7 @@ open web fetching stays with the two network roles.
307
362
  **Out-of-allowlist targets are a gate, not a wall.** When a fetch / search /
308
363
  browser-open points at a host outside the allowlist, the tool hands the URL
309
364
  to OpenCode's **official confirmation dialog** — you decide, once per
310
- target (an unanswered dialog is auto-rejected on the usual 10-minute timer,
365
+ target (an unanswered dialog is auto-rejected on the usual 1-minute timer,
311
366
  and the plugin still never self-allows). Every dialog also fires a
312
367
  **system toast notification**, so you know something is waiting even when
313
368
  you're not staring at the screen. Env-file URLs and non-http(s) schemes
@@ -319,11 +374,21 @@ remain hard-rejected with no dialog — R6 red lines are never consentable.
319
374
  environment variables silently. Env reads (`printenv`, `env`, `Get-ChildItem
320
375
  env:`, …) and env files (`.env`, shell rc) route through OpenCode's official
321
376
  confirmation dialog; unanswered prompts are **auto-rejected after
322
- `TM_ASK_TIMEOUT_MIN` (default 10 min)**. Env reads no wildcard can express
377
+ `TM_ASK_TIMEOUT_MIN` (default 1 min)**. Env reads no wildcard can express
323
378
  (embedded `$VAR` / `${VAR}` / `$env:` inside another command, command
324
379
  substitution) and the `tm_*` wrapper channel stay a **hard block** — no dialog
325
380
  to slip through. The audit log records only tool name + pattern category +
326
- verdict — never command text, paths, variable names or values.
381
+ verdict — never command text, paths, variable names or values. Verdicts now
382
+ also explain a dialog that cannot be answered anymore: a reply that hits an
383
+ **already-closed** dialog (host 404 — the timer already auto-rejected) audits
384
+ as `already-closed` without degrading the gate, a plugin-side bad reply shape
385
+ audits as `rejected-shape-bug`, and a user reply that arrives *after* the
386
+ auto-reject is recorded as `late-<verdict>` for observability only (the
387
+ rejection stands — the plugin still never self-allows). The 1-minute auto-reject
388
+ floor is tunable via `TM_ASK_TIMEOUT_FLOOR_MIN`; the short default is safe
389
+ because a reply that races the timer audits as benign `already-closed` (host
390
+ 404 on a closed dialog — no gate degrade), and the observed ~120 s is the
391
+ host-to-plugin event-bus *delivery* lag, not click-resolution latency.
327
392
 
328
393
  **R2 dangerous operations (same dialog).** Delete, git publish, network
329
394
  fetch, package install/publish, process/system, privilege changes — none are
@@ -390,24 +455,52 @@ for overrides, extra agents and disabling roles.
390
455
  | Env var | Default | Purpose |
391
456
  |---|---|---|
392
457
  | `TM_ENV_PROTECT` | `strict` | R6 mode: `strict` / `standard` / `off` (off also disarms the approval timer) |
393
- | `TM_ASK_TIMEOUT_MIN` | `10` | minutes before an unanswered dialog is auto-rejected (hard floor 3 — the host's reply event reaches the plugin ~120 s late) |
458
+ | `TM_ASK_TIMEOUT_MIN` | `1` | minutes before an unanswered dialog is auto-rejected (floored at 1 min — safe: a racing reply audits as benign `already-closed`; the host's reply event reaching the plugin ~120 s late is event-bus delivery lag, not a click delay) |
459
+ | `TM_ASK_TIMEOUT_FLOOR_MIN` | `1` | minimum enforced for the ask timeout above |
394
460
  | `TM_ENV_PROTECT_EXTRA_DENY` | — | extra block patterns (regex; always hard block, never dialog-governed) |
395
- | `TM_OFFLOAD_THRESHOLD` | `2000` | offload threshold (tokens, CJK-aware estimate) |
461
+ | `TM_OFFLOAD_THRESHOLD` | `2000` | global offload fallback (tokens, CJK-aware estimate) — used when the content class is unknown |
462
+ | `TM_OFFLOAD_THRESHOLD_TEXT` | `4000` | offload boundary for prose (text / log / markdown) |
463
+ | `TM_OFFLOAD_THRESHOLD_DATA` | `2000` | offload boundary for structured payloads (json / csv / code / binary) |
396
464
  | `TM_PREVIEW_MAX_TOKENS` | `80` | preview hard cap |
397
465
  | `TM_FETCH_MAX_LINES` | `2000` | tm_fetch page cap |
398
466
  | `TM_BLACKBOARD_DIR` / `TM_TRAJECTORY_DIR` | `<repo>/.git/opencode-team/…` | offload store / trajectory ledger (tmpdir fallback; explicit = absolute or project-relative) |
399
467
  | `TM_BLACKBOARD_TTL` | `7` | store retention (days) |
400
468
  | `TM_BASH_READONLY_ALLOWED` | built-in table | tm_bash allowlist |
401
- | `TM_WEBFETCH_ALLOWED_DOMAINS` | the 21 seeded hosts | tm_webfetch / tm_search / tm_browser allowlist (`"*"` opens all; empty = deny all) |
469
+ | `TM_SEARCH_DEFAULT_ENGINE` | `auto` | tm_search engine when no `engine` arg is given (`auto` = classify + parallel fan-out + RRF fusion; any table name also pins a manual default) |
470
+ | `TM_WEBFETCH_ALLOWED_DOMAINS` | the 23 seeded hosts | tm_webfetch / tm_search / tm_browser allowlist (`"*"` opens all; empty = deny all; a custom list REPLACES the seed — keep the engine hosts) |
402
471
  | `TM_BROWSER_PATH` | auto-detect | tm_browser executable override (default: your DEFAULT browser when Chromium-family, else Edge/Chrome probes) |
403
472
  | `TM_BROWSER_HEADLESS` | `auto` | `1` headless (CI) / `0` headful / `auto` (headless only on display-less Linux) |
404
- | `TM_MEMORY_GLOBAL_DIR` | `~/.opencode-team/memories/global/` | tm_memory GLOBAL scope store |
473
+ | `TM_BROWSER_ENGINE` | `playwright` | `playwright` (needs Node ≥ 20; any import failure auto-degrades) / `cdp-legacy` (zero-dep CDP pipe, core verbs only) |
474
+ | `TM_BROWSER_SNAPSHOT_MAX_TOKENS` | `1200` | hard cap on `take_snapshot` payloads |
475
+ | `TM_BROWSER_SUBRESOURCE` | `same-site` | what a page may load after its navigation was allowed: `same-site` = images/media/fonts/stylesheets always, scripts/XHR only for a site this session actually opened; `passive` = only the passive types; `off` = the legacy every-request gate. Blocked requests surface as a "N 个子资源请求被拦截" note on the next snapshot |
476
+ | `TM_BROWSER_IDLE_MS` | `180000` | an untouched browser session closes itself after this many ms (0 disables) and tells the user — a window nobody owns is a user-facing bug |
477
+ | `TM_BROWSER_IMAGE_MAX_BYTES` | `400000` | ceiling on the JPEG `take_screenshot { image:true }` inlines into the model's context (above it the reply stays path-only and says why) |
478
+ | `TM_SEARCH_WEIGHTS` | unset | per-engine fusion weight overrides, e.g. `bing=0.3,hn=0.25`; anything unset keeps the built-in table |
479
+ | `TM_SEARCH_RELEVANCE_FLOOR` | `0.35` | weight fraction kept by a hit that shares no query token with its title/snippet/host (demotes junk without deleting an engine) |
480
+ | `TM_SEARCH_MAX_HITS` | `10` | hits kept per engine leg and in the fused list |
481
+ | `TM_SEARCH_DISABLED_ENGINES` | unset | engines removed from the roster AND from every `auto` route (`sogou,baidu` style) |
482
+ | `TM_BASH_TIMEOUT_PROBE_MS` | `60000` | ceiling forced onto a `timeout` the model set for a read-only probe command (0 disables) |
483
+ | `TM_BASH_TIMEOUT_MAX_MS` | `0` | optional global ceiling for every other bash command — off by default so a real build keeps the timeout it asked for |
484
+ | `TM_PTY_MAX` | `4` | concurrent `tm_pty` terminal sessions this plugin may keep running at once |
485
+ | `TM_TOOL_HINTS` | `on` | append TeamMode's call-site discipline to the built-in `bash` / `task` tool DESCRIPTIONS via `tool.definition` (append-only, idempotent — the host text is never replaced) |
486
+ | `TM_AGENT_TEMPERATURE` | `off` | `on` applies a per-role sampling table (architect 0.35 / researcher 0.3 / reviewer 0.1 / rest 0.2) via `chat.params`; or give it `reviewer=0.05;team=0.4`. Off = the documented "all agents at 0.2" invariant stands |
487
+ | `TM_COMPACTION_CONTEXT` | `on` | on the host's pre-compaction hook, add the must-survive list (reply skeleton, offload handles, dispatched child session ids, provenance, board paths). Additive — the host's own summarizer prompt is never replaced |
488
+ | `TM_COMPACTION_AUTOCONTINUE` | `on` | `off` stops the host from silently resuming the turn after a compaction, so a human re-reads state first |
489
+ | `TM_SHELL_NO_COLOR` | `on` | inject `NO_COLOR`/`TERM=dumb` into every child shell via `shell.env` (ANSI progress bars are pure context tax). Never overwrites a value the host already set |
490
+ | `TM_SHELL_ENV` | — | explicit `KEY=VALUE;KEY2=VALUE2` passthrough into child shells — deliberately allowlisted, so this hook can't become a side channel for the parent environment |
491
+ | `TM_BROWSER_USER_DATA_DIR` | — (isolated temp profile) | explicit persistent profile dir — the ONLY way logins survive between sessions |
492
+ | `TM_MEMORY_GLOBAL_DIR` | `~/.opencode-team/memories/global/` | tm_memory GLOBAL tier store |
493
+ | `TM_MEMORY_SESSION_TTL_MIN` | `240` | session-tier entry TTL (lazy + boot sweep) |
494
+ | `TM_MEMORY_MAX_ENTRIES` | `200` | per-scope entry cap; over it `add` fails on purpose — run `compact` |
495
+ | `TM_MEMORY_STALE_DAYS` | `30` | age after which search hits are tagged `[stale Nd]` (`0` disables) |
496
+ | `TM_MEMORY_SESSION_PERSIST` | — (ephemeral) | `1` also writes session entries under `memories/sessions/<sid>/` |
405
497
  | `TM_HIT_BLACKLIST` | `maimai.cn` | extra domains never listed as search hits (comma/semicolon separated; same-name-different-site noise like 脉脉) |
406
498
  | `TM_PTC_MAX_PROGRAM_CHARS` | `4000` | PTC program source cap |
407
499
  | `TM_PTC_MAX_CALLS` | `20` | PTC per-run bridge-call budget (1–200) |
408
500
  | `TM_PTC_MAX_ERRORS` | `3` | PTC per-run error budget (1–50) |
409
501
  | `TM_PTC_TIMEOUT_MS` | `60000` | PTC per-run wall-clock timeout (5s–10min) |
410
502
  | `TM_PTC_ENGINE` | `auto` | `auto` (worker→inline degrade) / `worker` / `inline` |
503
+ | `TM_PTC_WEB_BRIDGE` | `on` | expose `tm.search` / `tm.webfetch` to PTC programs (`off` removes them from the bridge set) |
411
504
 
412
505
  ---
413
506
 
@@ -427,6 +520,10 @@ not the rule: a deliverable over ~50 lines goes to ONE named board file under
427
520
  routing bug.
428
521
  - **Approval gate (count-based):** ≥2 dispatches → plan (≤30 lines) → **your
429
522
  approval** → execute. Blocking questions are batched and asked immediately.
523
+ - **Concurrent dispatch:** independent dispatches batch into the SAME round
524
+ (parallel implementers with per-file ownership + verbatim contracts,
525
+ 3-dimension reviews, split test suites); only the Team Lead dispatches —
526
+ specialists no longer hold `task`, so no sub-agent spawns a sub-agent.
430
527
  - **Adaptive review:** one reviewer by default; three parallel dimensions
431
528
  only for high-risk profiles (auth/security, cross-module contracts, public APIs).
432
529
  - **Static verification:** build / typecheck / lint / tests. Improvised
@@ -506,7 +603,7 @@ the whole workflow are host-agnostic. On display-less Linux, `tm_browser`
506
603
  runs headless automatically.
507
604
 
508
605
  **What happens if I don't answer a confirmation dialog?**
509
- It auto-rejects after `TM_ASK_TIMEOUT_MIN` (default 10). The plugin never
606
+ It auto-rejects after `TM_ASK_TIMEOUT_MIN` (default 1). The plugin never
510
607
  self-approves — the only side it can take is yours or nobody's.
511
608
 
512
609
  ---