@te-river/opencode-team-mode 1.6.2 → 1.7.1
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 +114 -118
- package/README.zh-CN.md +95 -100
- package/dist/agents.d.ts.map +1 -1
- package/dist/agents.js +89 -79
- package/dist/agents.js.map +1 -1
- package/dist/approval-gate.d.ts +8 -9
- package/dist/approval-gate.d.ts.map +1 -1
- package/dist/approval-gate.js +13 -14
- package/dist/approval-gate.js.map +1 -1
- package/dist/capabilities.d.ts +11 -37
- package/dist/capabilities.d.ts.map +1 -1
- package/dist/capabilities.js +11 -141
- package/dist/capabilities.js.map +1 -1
- package/dist/commands.js +1 -1
- package/dist/envprotect/hook.d.ts +12 -31
- package/dist/envprotect/hook.d.ts.map +1 -1
- package/dist/envprotect/hook.js +13 -91
- package/dist/envprotect/hook.js.map +1 -1
- package/dist/envprotect/patterns.d.ts +6 -4
- package/dist/envprotect/patterns.d.ts.map +1 -1
- package/dist/envprotect/patterns.js +6 -4
- package/dist/envprotect/patterns.js.map +1 -1
- package/dist/envprotect.d.ts +1 -1
- package/dist/envprotect.d.ts.map +1 -1
- package/dist/envprotect.js +1 -1
- package/dist/envprotect.js.map +1 -1
- package/dist/host/v2-browser-gate.d.ts +19 -1
- package/dist/host/v2-browser-gate.d.ts.map +1 -1
- package/dist/host/v2-browser-gate.js +47 -6
- package/dist/host/v2-browser-gate.js.map +1 -1
- package/dist/host/v2-capabilities.d.ts +16 -0
- package/dist/host/v2-capabilities.d.ts.map +1 -1
- package/dist/host/v2-capabilities.js +22 -1
- package/dist/host/v2-capabilities.js.map +1 -1
- package/dist/host/v2-default-agent.d.ts +37 -0
- package/dist/host/v2-default-agent.d.ts.map +1 -0
- package/dist/host/v2-default-agent.js +128 -0
- package/dist/host/v2-default-agent.js.map +1 -0
- package/dist/host/v2-events.d.ts +1 -0
- package/dist/host/v2-events.d.ts.map +1 -1
- package/dist/host/v2-events.js +14 -0
- package/dist/host/v2-events.js.map +1 -1
- package/dist/host/v2-guard.d.ts +178 -0
- package/dist/host/v2-guard.d.ts.map +1 -1
- package/dist/host/v2-guard.js +324 -8
- package/dist/host/v2-guard.js.map +1 -1
- package/dist/host/v2-offload.d.ts +1 -1
- package/dist/host/v2-offload.js +2 -2
- package/dist/host/v2-permissions.d.ts +45 -44
- package/dist/host/v2-permissions.d.ts.map +1 -1
- package/dist/host/v2-permissions.js +48 -49
- package/dist/host/v2-permissions.js.map +1 -1
- package/dist/host/v2-probe-chain.d.ts +93 -0
- package/dist/host/v2-probe-chain.d.ts.map +1 -0
- package/dist/host/v2-probe-chain.js +146 -0
- package/dist/host/v2-probe-chain.js.map +1 -0
- package/dist/host/v2-prune.d.ts +157 -0
- package/dist/host/v2-prune.d.ts.map +1 -0
- package/dist/host/v2-prune.js +507 -0
- package/dist/host/v2-prune.js.map +1 -0
- package/dist/host/v2-retry.d.ts +143 -0
- package/dist/host/v2-retry.d.ts.map +1 -0
- package/dist/host/v2-retry.js +315 -0
- package/dist/host/v2-retry.js.map +1 -0
- package/dist/host/v2-scope.d.ts.map +1 -1
- package/dist/host/v2-scope.js +14 -7
- package/dist/host/v2-scope.js.map +1 -1
- package/dist/host/v2-session-client.d.ts +24 -0
- package/dist/host/v2-session-client.d.ts.map +1 -1
- package/dist/host/v2-session-client.js +243 -3
- package/dist/host/v2-session-client.js.map +1 -1
- package/dist/host/v2-session.d.ts +72 -1
- package/dist/host/v2-session.d.ts.map +1 -1
- package/dist/host/v2-session.js +154 -10
- package/dist/host/v2-session.js.map +1 -1
- package/dist/host/v2-tool.d.ts +2 -3
- package/dist/host/v2-tool.d.ts.map +1 -1
- package/dist/host/v2-tool.js +2 -3
- package/dist/host/v2-tool.js.map +1 -1
- package/dist/host/v2-types.d.ts +30 -0
- package/dist/host/v2-types.d.ts.map +1 -1
- package/dist/host/v2-usage.d.ts +50 -0
- package/dist/host/v2-usage.d.ts.map +1 -0
- package/dist/host/v2-usage.js +82 -0
- package/dist/host/v2-usage.js.map +1 -0
- package/dist/host/v2.d.ts +14 -12
- package/dist/host/v2.d.ts.map +1 -1
- package/dist/host/v2.js +430 -73
- package/dist/host/v2.js.map +1 -1
- package/dist/identity.d.ts +27 -0
- package/dist/identity.d.ts.map +1 -0
- package/dist/identity.js +34 -0
- package/dist/identity.js.map +1 -0
- package/dist/index.d.ts +9 -24
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +17 -27
- package/dist/index.js.map +1 -1
- package/dist/prompts/lead.d.ts +1 -1
- package/dist/prompts/lead.d.ts.map +1 -1
- package/dist/prompts/lead.js +38 -25
- package/dist/prompts/lead.js.map +1 -1
- package/dist/prompts/shared.d.ts +2 -2
- package/dist/prompts/shared.d.ts.map +1 -1
- package/dist/prompts/shared.js +66 -56
- package/dist/prompts/shared.js.map +1 -1
- package/dist/prompts/specialists.d.ts +2 -2
- package/dist/prompts/specialists.d.ts.map +1 -1
- package/dist/prompts/specialists.js +53 -83
- package/dist/prompts/specialists.js.map +1 -1
- package/dist/tm/args-schema.d.ts +7 -32
- package/dist/tm/args-schema.d.ts.map +1 -1
- package/dist/tm/args-schema.js +12 -153
- package/dist/tm/args-schema.js.map +1 -1
- package/dist/tm/board.d.ts +10 -9
- package/dist/tm/board.d.ts.map +1 -1
- package/dist/tm/board.js +11 -10
- package/dist/tm/board.js.map +1 -1
- package/dist/tm/config-files.d.ts +81 -0
- package/dist/tm/config-files.d.ts.map +1 -0
- package/dist/tm/config-files.js +209 -0
- package/dist/tm/config-files.js.map +1 -0
- package/dist/tm/config-layers.d.ts +117 -0
- package/dist/tm/config-layers.d.ts.map +1 -0
- package/dist/tm/config-layers.js +317 -0
- package/dist/tm/config-layers.js.map +1 -0
- package/dist/tm/config.d.ts +10 -101
- package/dist/tm/config.d.ts.map +1 -1
- package/dist/tm/config.js +17 -71
- package/dist/tm/config.js.map +1 -1
- package/dist/tm/dispatch.d.ts +115 -21
- package/dist/tm/dispatch.d.ts.map +1 -1
- package/dist/tm/dispatch.js +424 -67
- package/dist/tm/dispatch.js.map +1 -1
- package/dist/tm/egress.d.ts +19 -7
- package/dist/tm/egress.d.ts.map +1 -1
- package/dist/tm/egress.js +21 -9
- package/dist/tm/egress.js.map +1 -1
- package/dist/tm/index.d.ts +23 -20
- package/dist/tm/index.d.ts.map +1 -1
- package/dist/tm/index.js +117 -116
- package/dist/tm/index.js.map +1 -1
- package/dist/tm/ledger.d.ts +9 -2
- package/dist/tm/ledger.d.ts.map +1 -1
- package/dist/tm/ledger.js +4 -1
- package/dist/tm/ledger.js.map +1 -1
- package/dist/tm/perm-ask.d.ts +3 -3
- package/dist/tm/perm-ask.d.ts.map +1 -1
- package/dist/tm/perm-ask.js +3 -3
- package/dist/tm/perm-ask.js.map +1 -1
- package/dist/tm/pipelines.d.ts +12 -11
- package/dist/tm/pipelines.d.ts.map +1 -1
- package/dist/tm/pipelines.js +40 -207
- package/dist/tm/pipelines.js.map +1 -1
- package/dist/tm/preview.d.ts +1 -1
- package/dist/tm/preview.js +3 -3
- package/dist/tm/preview.js.map +1 -1
- package/dist/tm/refs.d.ts +31 -4
- package/dist/tm/refs.d.ts.map +1 -1
- package/dist/tm/refs.js +48 -4
- package/dist/tm/refs.js.map +1 -1
- package/dist/tm/result.d.ts +1 -1
- package/dist/tm/result.d.ts.map +1 -1
- package/dist/tm/result.js +4 -3
- package/dist/tm/result.js.map +1 -1
- package/dist/tm/search.d.ts +2 -2
- package/dist/tm/search.js +7 -7
- package/dist/tm/search.js.map +1 -1
- package/dist/tm/serp-loop.d.ts +19 -7
- package/dist/tm/serp-loop.d.ts.map +1 -1
- package/dist/tm/serp-loop.js +24 -9
- package/dist/tm/serp-loop.js.map +1 -1
- package/dist/tm/stats.d.ts +12 -27
- package/dist/tm/stats.d.ts.map +1 -1
- package/dist/tm/stats.js +43 -45
- package/dist/tm/stats.js.map +1 -1
- package/dist/tm/store.d.ts +18 -5
- package/dist/tm/store.d.ts.map +1 -1
- package/dist/tm/store.js +39 -9
- package/dist/tm/store.js.map +1 -1
- package/dist/tm/tools.d.ts +7 -5
- package/dist/tm/tools.d.ts.map +1 -1
- package/dist/tm/tools.js +10 -41
- package/dist/tm/tools.js.map +1 -1
- package/dist/tm/webfetch.d.ts +1 -1
- package/dist/tm/webfetch.d.ts.map +1 -1
- package/dist/tm/webfetch.js +38 -23
- package/dist/tm/webfetch.js.map +1 -1
- package/dist/types.d.ts +40 -123
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +7 -21
- package/dist/types.js.map +1 -1
- package/docs/installation-v2.md +133 -33
- package/docs/installation.md +9 -0
- package/docs/research/browser-pane.md +5 -0
- package/docs/v2-port-notes.md +133 -0
- package/package.json +1 -1
- package/scripts/gen-v2-config.mjs +246 -94
- package/scripts/lib/fake-ctx.mjs +67 -0
package/README.md
CHANGED
|
@@ -19,10 +19,10 @@
|
|
|
19
19
|
> **The lazy path:** paste this to any coding agent and let it do the work:
|
|
20
20
|
>
|
|
21
21
|
> ```text
|
|
22
|
-
> Install the OpenCode plugin @te-river/opencode-team-mode.
|
|
23
|
-
> 1.18.x
|
|
22
|
+
> Install the OpenCode plugin @te-river/opencode-team-mode. This package supports **OpenCode 2.x
|
|
23
|
+
> only** — the 1.18.x personality was removed in the 1.7.0 line:
|
|
24
24
|
> 2.x → https://raw.githubusercontent.com/Te-River/Opencode-TeamMode/main/docs/installation-v2.md
|
|
25
|
-
> (
|
|
25
|
+
> (a 2.x plugin cannot create an agent, so the six roles and six `/team-*` commands come
|
|
26
26
|
> from a generator step in that guide — that step is not optional.) Then verify the install
|
|
27
27
|
> using the checks in that guide.
|
|
28
28
|
> ```
|
|
@@ -48,14 +48,14 @@ TeamMode's answer to each:
|
|
|
48
48
|
| Pain | TeamMode's answer |
|
|
49
49
|
|---|---|
|
|
50
50
|
| 🔥 **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. |
|
|
51
|
-
| 🐌 **Round-trip overhead** | `
|
|
51
|
+
| 🐌 **Round-trip overhead** | The host's own `execute` (Code Mode): the agent writes ONE program that makes N governed calls in a single turn. Zero LLM round-trips during the run. |
|
|
52
52
|
| 🕳️ **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. |
|
|
53
53
|
| 🌫️ **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. |
|
|
54
54
|
| 🧭 **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 renderer's supported set is measured rather than assumed, and the prompts carry the negative half of that measurement — footnotes, `==highlight==`, a bare `---` rule and `$…$` math reach you as literal text, so agents are told not to use them, while `mermaid` diagrams are offered because this host does draw them. |
|
|
55
55
|
| 🎯 **Goal drift** | The lead opens with `GOAL:` in your own words plus checkable `ACCEPTANCE:` criteria, and the run does not end while a criterion lacks evidence — the only legitimate stops are named (blocked on you, or provably unachievable). When a round settles with items still open on the host's todo list, `tm_join` says 目标未达成 and lists them, and the goal is carried through context compaction so a summarized transcript cannot redefine it. |
|
|
56
56
|
|
|
57
57
|
| 🗣️ **Replies in a language you never chose** | The governed tools answer in Chinese, and an agent left to its own devices mirrors that straight back at you. Every role now carries a reply-language rule: your own language wins, and a Chinese string is quoted verbatim only where it IS the evidence (a close verdict, a refusal line) — translating a verdict is how an unchecked claim starts looking checked. |
|
|
58
|
-
| ⏱️ **Rounds spent for the sake of looking careful** | 效率至上 is written into the lead and all five specialists: one wide call instead of three narrow ones, independent calls in the same round, ≥3 probes collapsed into one `
|
|
58
|
+
| ⏱️ **Rounds spent for the sake of looking careful** | 效率至上 is written into the lead and all five specialists: one wide call instead of three narrow ones, independent calls in the same round, ≥3 probes collapsed into one `execute` (Code Mode) program, no re-running a check to watch it pass again — with the boundary stated too: efficiency never buys its way out of the evidence rule, because an unverified "done" costs you the round *and* the bug. |
|
|
59
59
|
|
|
60
60
|
And the workflow discipline underneath: deterministic routing, a ≤30-line plan
|
|
61
61
|
you approve before ≥2 dispatches execute, structured `STATUS/CHANGES/FINDINGS/
|
|
@@ -75,7 +75,7 @@ projects**: on a two-file script the team simply has less to govern.
|
|
|
75
75
|
| 🏗️ **Architect** | System designer | Design docs, module structure, API contracts |
|
|
76
76
|
| 💻 **Implementer** | Code writer | Building features, writing production code |
|
|
77
77
|
| 🔍 **Reviewer** | Dimension-focused auditor | Single-dimension review by default; 3 in parallel only for high-risk changes |
|
|
78
|
-
| 🧪 **Tester** | Test engineer | Tests with real edge cases; static verification (build / typecheck / lint); governed UI verification via `
|
|
78
|
+
| 🧪 **Tester** | Test engineer | Tests with real edge cases; static verification (build / typecheck / lint); governed UI verification via the host's native `browser_*` tools |
|
|
79
79
|
| 🔎 **Researcher** | Knowledge finder | Local repo first, then the web — one of the two network roles (with the Lead) |
|
|
80
80
|
|
|
81
81
|
Out of the box, **Team is your default agent** — new chats open straight into the
|
|
@@ -90,10 +90,10 @@ orchestrator (opt-out in [Configuration](#-configuration)).
|
|
|
90
90
|
Copy this into any coding agent — it will edit your config, restart-remind you, and verify:
|
|
91
91
|
|
|
92
92
|
```text
|
|
93
|
-
Install the OpenCode plugin @te-river/opencode-team-mode.
|
|
94
|
-
1.18.x
|
|
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:
|
|
95
95
|
2.x → https://raw.githubusercontent.com/Te-River/Opencode-TeamMode/main/docs/installation-v2.md
|
|
96
|
-
(
|
|
96
|
+
(a 2.x plugin cannot create an agent, so the six roles and six `/team-*` commands come from
|
|
97
97
|
a generator step in that guide — that step is not optional.)
|
|
98
98
|
(If a URL is unreachable — common on mainland-China networks — retry with
|
|
99
99
|
the mirror prefix: https://ghproxy.net/ + the same path.)
|
|
@@ -273,45 +273,42 @@ through with `tm_fetch` when it genuinely needs the payload.
|
|
|
273
273
|
|
|
274
274
|
| Tool | What it does | Roles |
|
|
275
275
|
|---|---|---|
|
|
276
|
-
| `
|
|
276
|
+
| `tm_fetch` | Paged handle retrieval for offloaded results (JSON handles take a `fields` dot-path projection — a deliberately small jq subset like `items[].name`) | all six agents |
|
|
277
277
|
| `tm_memory` | Session + project + global memory store (Markdown + frontmatter): add / search / list / forget / compact | all six agents |
|
|
278
278
|
| `tm_board_write` | **The blackboard's write side**: places ONE new Markdown file at `<board-root>/<session-key>/<task-slug>/NN-<role>-<topic>[-rN].md` and chooses the name itself — a revision is a new round-suffixed file, never an overwrite, and the reply carries the path plus the byte count, never the content. It exists because the board used to need a file tool, and `architect` / `researcher` own none (no `write`, no `edit`, no `bash` even to stamp the session folder), so every oversized deliverable from those roles came back as `BLACKBOARD WRITE FAILED` plus the whole document pasted inline — the reply shape this team mandates was un-followable exactly where it mattered. Scope is enforced rather than asked: segments sanitized, target realpath-verified against the board root (a symlinked task dir is refused), the name always ends in `.md` so no `.env`/rc file can be produced, caps via `TM_BOARD_MAX_CHARS` + a per-session file limit | All six agents |
|
|
279
|
-
| `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 |
|
|
280
279
|
| `tm_search` | Multi-engine web search with extracted, deduplicated, RRF-fused hit lists | Lead + Researcher |
|
|
281
280
|
| `tm_webfetch` | Single governed GET of an allowlisted page (search pages auto-extracted). Manual redirects, re-checked per hop, and a refusal names the WHOLE chain (`跳转链: a → b(停在第 2 跳)`) — an allowlisted shortener that bounces off-site used to report only the off-site host, which read as "that site will not fetch" and sent the agent back to retry the entry URL it had just watched fail. A 429/503 carries its own `Retry-After` when the server sends delta-seconds (an HTTP-date is deliberately not laundered into a countdown), so "come back later" never looks like "no content here". The page GET also asks for Markdown first (`Accept: text/markdown,…`) — measured on `learn.microsoft.com`: 60,778 B of HTML becomes 11,449 B of Markdown, and every other host tried returns the same document either way, so the preference is free where it is ignored | Lead + Researcher |
|
|
282
|
-
| `tm_ledger` | **The lead's task ledger** (`add` / `doing` / `done` / `blocked` / `list`), stored in the host's own `ctx.storage` per session — the landing spot for the LEDGER rule
|
|
283
|
-
| `tm_join` | **Sub-agent collection** — there is no plugin-side dispatcher any more (`tm_dispatch` is removed: a child a plugin creates is a session the user can neither open nor stop from the interface). Delegation goes through the host's own `task` / `task { background: true }`, and `tm_join` is the read side: a status snapshot, a bounded `waitMs`, `cancel:true` to stop a runaway, and `claimNamedChild` so `tm_join { ids: ["ses_…"] }` pulls one child's WHOLE reply back through the offload pipeline (handle + ≤80-token preview) instead of kilotokens inline. It also rebuilds its registry from the host session tree after a restart, so leftover children are 接管 rather than lost. **On 2.x it also registers the children the host's own `subagent` tool created** (the ack's `metadata.sessionID` is the seam), so a background dispatch is never reported as "nothing to collect" while the user watches it run — and such a row says where its reply actually arrives (the host's injected message) and whether it settled by event or by inference. **Stopping one is a real call
|
|
284
|
-
| `
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
>
|
|
289
|
-
> `
|
|
290
|
-
> `glob` / `shell`, and `tm_ptc_run` by the host's `execute` (Code Mode) — the same
|
|
291
|
-
> job, one fewer tool for the model to choose between. The governance did NOT
|
|
281
|
+
| `tm_ledger` | **The lead's task ledger** (`add` / `doing` / `done` / `blocked` / `list`), stored in the host's own `ctx.storage` per session — the landing spot for the LEDGER rule, since OpenCode 2.x gives a plugin no `todowrite`. A repeated ask is ONE item, an id that matches two is refused with both printed, `blocked` carries the reason, and a write that did not reach storage is reported as a failure rather than as recorded | Lead only |
|
|
282
|
+
| `tm_join` | **Sub-agent collection** — there is no plugin-side dispatcher any more (`tm_dispatch` is removed: a child a plugin creates is a session the user can neither open nor stop from the interface). Delegation goes through the host's own `task` / `task { background: true }`, and `tm_join` is the read side: a status snapshot, a bounded `waitMs`, `cancel:true` to stop a runaway, and `claimNamedChild` so `tm_join { ids: ["ses_…"] }` pulls one child's WHOLE reply back through the offload pipeline (handle + ≤80-token preview) instead of kilotokens inline. It also rebuilds its registry from the host session tree after a restart, so leftover children are 接管 rather than lost. **On 2.x it also registers the children the host's own `subagent` tool created** (the ack's `metadata.sessionID` is the seam), so a background dispatch is never reported as "nothing to collect" while the user watches it run — and such a row says where its reply actually arrives (the host's injected message) and whether it settled by event or by inference. **Stopping one is a real call:** `cancel: true` goes to the host's `POST /api/session/{id}/interrupt` on 2.x (`tm_join { ids: ["ses_…"], cancel: true }` stops ONE named child; without `ids` it stops every still-running one). The host's own contract is `interrupted=true` for an active execution and `false` for the idle no-op, so the answer comes back as **five distinct verdicts** — 已由宿主中断 / 空闲未中断(it wasn't running: our row was stale, not failed)/ 未确认(the call worked, no boolean came back)/ 无中断缝 / 被宿主拒绝(with the host's reason) — counted in the reply and written to the trajectory (`stop_tried` / `stop_confirmed` / `stop_refused` / `stop_unknown`, readable via `tm_stats`). It will not summarise five different facts as one "已取消", and `resume` is never sent, because resuming pending steering is the opposite of cancelling. *(On `main` only — the published 1.6.1 still answers 宿主无 abort 接口 on 2.x.)* | Lead only |
|
|
283
|
+
| `tm_stats` | **The plugin reads its own trajectory back**: tokens kept out of the context window by offloading (net of the preview that arrived), seconds saved by dispatch overlap (serial cost minus the wall window the children actually used), governance counts (blocked subresources, clamped shell timeouts, cache hits, redactions) — plus the **host capability matrix** (`已验证/存在未用/待观察/缺失/需人眼` per host surface). Read-only over files this plugin wrote; run it first after an OpenCode upgrade. `{ recent: 20 }` appends a call-by-call recap — handle + payload path for every offloaded result, which is how you see what a governed tool actually returned (the host gives plugin tools no expandable card) | All agents |
|
|
284
|
+
|
|
285
|
+
> **The v1 (1.18.x) personality was removed in full for the 1.7.0 line.** The package
|
|
286
|
+
> exports `{id, setup}` and nothing else, so `tm_read` / `tm_grep` / `tm_bash` /
|
|
287
|
+
> `tm_ptc_run` / `tm_pty` are gone with it — the host's own `read` / `grep` / `glob` /
|
|
288
|
+
> `shell` and its `execute` (Code Mode) do that work now. The governance did NOT
|
|
292
289
|
> disappear with them: an oversized native result is offloaded through
|
|
293
290
|
> `tool.execute.after` (measured: `shell` 12,902 tokens arriving as a 78-token
|
|
294
291
|
> preview), and the address red line plus R6's per-command classification ride the
|
|
295
|
-
> host's `permission.evaluate`.
|
|
296
|
-
> `todowrite
|
|
292
|
+
> host's `permission.evaluate`. `tm_ledger` is the lead's list where the host gives a
|
|
293
|
+
> plugin no `todowrite`.
|
|
297
294
|
> **Nothing we change reaches outside Team.** Every v2 hook fires for every session on
|
|
298
295
|
> the host, so the tool-surface trim, the 0.2 temperature, the blackboard note, the offload
|
|
299
296
|
> of native results, the R6/address strictening and the forced-background dispatch all ask
|
|
300
297
|
> "is this one of our six roles?" first — `build`, `plan` and your own agents stay as a
|
|
301
298
|
> fresh install leaves them. Calls whose owner the host did not tell us are left alone too,
|
|
302
299
|
> and counted (`tm_stats` prints `作用域:我们 · 他人 · 未判定`).
|
|
303
|
-
> The installer writes the
|
|
304
|
-
> role prompts, which name `read` / `grep` / `shell` instead of the aliases.
|
|
305
|
-
> Everything above this note describes the v1 (1.18.x) surface, which still ships
|
|
306
|
-
> all thirteen.
|
|
300
|
+
> The installer writes the six
|
|
301
|
+
> role prompts, which name `read` / `grep` / `shell` instead of the removed aliases.
|
|
307
302
|
|
|
308
303
|
> **On OpenCode 2.x, interactive browsing is the host's, and it stays ours.** The
|
|
309
304
|
> desktop renders a browser in its side panel, and that panel attaches to the
|
|
310
305
|
> server's own browser service — a plugin cannot register a page of its own into it
|
|
311
306
|
> (forensics: `docs/research/browser-pane.md`). So the three roles with network
|
|
312
307
|
> grants (lead, researcher, tester) are pointed at the host's `browser_*` tools
|
|
313
|
-
>
|
|
314
|
-
> browser
|
|
308
|
+
> — the self-built `tm_browser` was removed in 1.7.0, so the host's catalog is the
|
|
309
|
+
> only browser. On a host with no native browser catalog (CLI, standalone) there is
|
|
310
|
+
> now no browser at all, and the agent reports that gap rather than simulating one.
|
|
311
|
+
> Handing browsing over did not mean handing
|
|
315
312
|
> governance over: `permission.evaluate` was observed NOT firing for `browser_*`, so
|
|
316
313
|
> our gate sits on `tool.execute.before` instead — it classifies the URL of every
|
|
317
314
|
> navigate/open, the path of every `browser_preview`, and every browser URL named
|
|
@@ -323,7 +320,7 @@ through with `tm_fetch` when it genuinely needs the payload.
|
|
|
323
320
|
> leaked past the refusal). One exception to the offload rule lives here: a
|
|
324
321
|
> `browser_snapshot` is an addressing table, not a document, so it is CAPPED (every
|
|
325
322
|
> `[ref=…]` line kept, static text dropped, budget `TM_NATIVE_SNAPSHOT_MAX_TOKENS`
|
|
326
|
-
> default 1 200
|
|
323
|
+
> default 1 200) rather than replaced by a
|
|
327
324
|
> handle. Measured on a 261-ref page: a head cut keeps 118 refs, this keeps 261 of
|
|
328
325
|
> them using 1 044 of 11 326 tokens.
|
|
329
326
|
|
|
@@ -332,8 +329,8 @@ through with `tm_fetch` when it genuinely needs the payload.
|
|
|
332
329
|
> It doubles as the fallback chain: when a tool errors (no browser on this
|
|
333
330
|
> host, blocked host), the agent says so and drops to the next rung — and
|
|
334
331
|
> rung ③ is where a missing capability gets reported, never fabricated.
|
|
335
|
-
> One stated exception: on the **web** channel `tm_search` / `tm_webfetch`
|
|
336
|
-
>
|
|
332
|
+
> One stated exception: on the **web** channel `tm_search` / `tm_webfetch` come
|
|
333
|
+
> first, because that path is the only one carrying the
|
|
337
334
|
> domain allowlist, the per-request confirmation dialog and the R6 red lines.
|
|
338
335
|
|
|
339
336
|
> **What the host UI cannot show you.** OpenCode renders a *plugin* tool call
|
|
@@ -391,8 +388,8 @@ using an HMAC-signed, run-scoped, expiring handle — and on a JSON handle it
|
|
|
391
388
|
can ask for a `fields` dot-path projection instead (a deliberately small jq
|
|
392
389
|
subset: `items[].name`, `[].stargazers_count`), so a big API dump narrows to
|
|
393
390
|
just the values needed without the raw body ever entering the window.
|
|
394
|
-
`
|
|
395
|
-
as structured errors instead of raw dumps.
|
|
391
|
+
The host's own `shell` runs read-only probes under its permission rules, and failures
|
|
392
|
+
come back as structured errors instead of raw dumps.
|
|
396
393
|
|
|
397
394
|
### Session + project + global memory (tm_memory)
|
|
398
395
|
|
|
@@ -452,49 +449,28 @@ Two more channels complete the surface:
|
|
|
452
449
|
- `tm_webfetch` — a known URL, one governed GET. Search-engine pages it
|
|
453
450
|
fetches are auto-extracted to hit lists too. JSON endpoints like
|
|
454
451
|
`registry.npmjs.org/<pkg>/latest` pass through untouched.
|
|
455
|
-
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
`
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
the session auto-degrades per instance to the zero-dep `cdp-legacy`
|
|
473
|
-
engine, which keeps the core verbs only; pin either via
|
|
474
|
-
`TM_BROWSER_ENGINE=playwright|cdp-legacy`). No browser download is ever
|
|
475
|
-
involved — Playwright launches YOUR installed browser by path, so
|
|
476
|
-
`npx playwright install` is not part of the user flow (the dependency
|
|
477
|
-
itself resolves at npm install/publish time). Isolated temp profile by
|
|
478
|
-
default: persistent logins only if you explicitly set
|
|
479
|
-
`TM_BROWSER_USER_DATA_DIR`. No cookies by design — the agent opens a blank
|
|
480
|
-
profile, which is also why a risk-scored site (Baidu) may hand it a
|
|
481
|
-
verification wall your own window walks past. Want it logged in: point the
|
|
482
|
-
variable at a DEDICATED directory and log in once by hand. The browser's **own** site-permission bubble
|
|
483
|
-
("…wants to access other apps and services on this device") is
|
|
484
|
-
auto-**denied** at launch: that modal is not our confirmation channel, it has
|
|
485
|
-
no timeout, and on a machine left running unattended it would hang the page
|
|
486
|
-
indefinitely. Nothing is ever auto-allowed — a page that truly needs such a
|
|
487
|
-
permission fails visibly at that feature instead of invisibly at a dialog. A
|
|
488
|
-
window an agent forgets is now **reported**, not merely reaped: `open` states
|
|
489
|
-
the close duty in its own reply (with the real idle seconds), the reply
|
|
490
|
-
contract requires the tool's close verdict in `EVIDENCE`, and `tm_join` tells
|
|
491
|
-
the lead `⚠ N 个浏览器还开着` when a sub-agent settled while still holding one
|
|
492
|
-
— so you are never the only one who notices.
|
|
452
|
+
- **Interactive browsing is the host's own `browser_*` tools** (`browser_tabs_open`
|
|
453
|
+
· `browser_navigate` · `browser_snapshot` · `browser_click` · `browser_evaluate`
|
|
454
|
+
…). The self-built `tm_browser` was **removed in 1.7.0** (a breaking change):
|
|
455
|
+
the desktop renders a browser in its side panel and that panel attaches to the
|
|
456
|
+
server's own browser service, so a plugin cannot register a page of its own
|
|
457
|
+
(forensics: `docs/research/browser-pane.md`). The host catalog is policed by
|
|
458
|
+
`src/host/v2-browser-gate.ts` at `execute.before` — the URL of every navigate/open,
|
|
459
|
+
the path of every `browser_preview`, and every browser URL named inside an
|
|
460
|
+
`execute` program are classified against the address red lines and the env-file
|
|
461
|
+
rule, and a refused call the host runs anyway has its page replaced with the same
|
|
462
|
+
refusal, so no out-of-policy content reaches the context, the store or the
|
|
463
|
+
trajectory. Calling convention differs from the deleted tool: `evaluate` takes
|
|
464
|
+
`{tabID, script}` where `script` is an **expression** (not a function source) and
|
|
465
|
+
the return value must be a scalar you `JSON.stringify` yourself; snapshot tokens
|
|
466
|
+
read `@e8 [link]`. **Known cost, accepted:** on a host with no native browser
|
|
467
|
+
catalog (CLI / standalone) there is now no browser at all — the agent reports that
|
|
468
|
+
gap rather than simulating one.
|
|
493
469
|
|
|
494
470
|
When a fetch still returns **403 after the real-Chrome headers**, the error
|
|
495
471
|
is a DIRECTIVE: the gate is JS-challenge / TLS-fingerprint based and only a
|
|
496
|
-
real browser passes — the agent is told to
|
|
497
|
-
|
|
472
|
+
real browser passes — the agent is told to open that URL with the host's
|
|
473
|
+
native `browser_navigate` (and read it with `browser_snapshot`). Search hit lists also
|
|
498
474
|
filter known noise: engine-internal wrappers (`so.com/link?`, `ai.so.com`)
|
|
499
475
|
and same-name-different-site domains (`maimai.cn` 脉脉 vs the maimai DX
|
|
500
476
|
game) never ride along — extend the hit blacklist with `TM_HIT_BLACKLIST`.
|
|
@@ -512,9 +488,9 @@ mzh.moegirl.org.cn, space.bilibili.com — is covered):
|
|
|
512
488
|
`TM_WEBFETCH_ALLOWED_DOMAINS` (`"*"` opens every host; a custom list REPLACES
|
|
513
489
|
the seed, so keep the engine hosts or `tm_search` loses its targets). Architect /
|
|
514
490
|
implementer / reviewer have NO network grant — web questions come back as a
|
|
515
|
-
reported gap, never simulated. The tester carries
|
|
516
|
-
governed UI verification of the project (local dev servers,
|
|
517
|
-
open web fetching stays with the two network roles.
|
|
491
|
+
reported gap, never simulated. The tester carries the host's native `browser_*`
|
|
492
|
+
tools ONLY, for governed UI verification of the project (local dev servers,
|
|
493
|
+
preview routes); open web fetching stays with the two network roles.
|
|
518
494
|
|
|
519
495
|
**Out-of-allowlist targets are a gate, not a wall.** When a fetch / search /
|
|
520
496
|
browser-open points at a host outside the allowlist, the tool hands the URL
|
|
@@ -527,6 +503,12 @@ remain hard-rejected with no dialog — R6 red lines are never consentable.
|
|
|
527
503
|
|
|
528
504
|
### Security: the R6 + R2 approval gate
|
|
529
505
|
|
|
506
|
+
**On OpenCode 2.x, R6 is armed by default.** A Team role's native `read` of a `.env` is denied
|
|
507
|
+
outright — no consent path — and a shell command that reads the environment goes to the host's own
|
|
508
|
+
permission prompt (a plugin cannot raise a dialog on 2.x, so the shell face is the host's `ask`
|
|
509
|
+
while the env-file face is a hard `deny`). Turn it off with the plugin option
|
|
510
|
+
`envProtect: false` or with `TM_ENV_PROTECT=off`.
|
|
511
|
+
|
|
530
512
|
**R6 environment protection.** With TeamMode active, the model cannot read
|
|
531
513
|
environment variables silently. Env reads (`printenv`, `env`, `Get-ChildItem
|
|
532
514
|
env:`, …) and env files (`.env`, shell rc) route through OpenCode's official
|
|
@@ -605,12 +587,18 @@ The plugin injects everything at startup — no agent files to copy.
|
|
|
605
587
|
|
|
606
588
|
```jsonc
|
|
607
589
|
{
|
|
608
|
-
"
|
|
609
|
-
|
|
590
|
+
"plugins": [
|
|
591
|
+
{ "package": "@te-river/opencode-team-mode@latest", "options": { "defaultAgent": false } }
|
|
610
592
|
]
|
|
611
593
|
}
|
|
612
594
|
```
|
|
613
595
|
|
|
596
|
+
Note the shape: on 2.x a plugin entry is either a string or an **object** with `package` and
|
|
597
|
+
`options`. The 1.x tuple form `["@te-river/opencode-team-mode@latest", { … }]` is rejected
|
|
598
|
+
(`path=$.plugins.1 kind=invalid`, measured 2026-10-06). Options do arrive — with the object
|
|
599
|
+
form the boot row reads `board_ttl_days=7` for `{ "ttlDays": 7 }`, so this is measured, not
|
|
600
|
+
documented-and-hoped.
|
|
601
|
+
|
|
614
602
|
Your own agents named `team` / `architect` / … always take precedence; the
|
|
615
603
|
plugin never clobbers user definitions. See [Customization](#-customization)
|
|
616
604
|
for overrides, extra agents and disabling roles.
|
|
@@ -620,11 +608,11 @@ for overrides, extra agents and disabling roles.
|
|
|
620
608
|
| Env var | Default | Purpose |
|
|
621
609
|
|---|---|---|
|
|
622
610
|
| `TM_ENV_PROTECT` | `strict` | R6 mode: `strict` / `standard` / `off` (off also disarms the approval timer) |
|
|
623
|
-
| `TM_R6_FINE_ASK` | classifier
|
|
611
|
+
| `TM_R6_FINE_ASK` | classifier | The command line is judged per-call by the host's `permission.evaluate` hook, so an ordinary `git status` asks nothing and an env dump still does. `off` falls back to asking about EVERY shell command — which is also what happens automatically on a host that does not expose the hook, and the boot note says which of the two caused it |
|
|
624
612
|
| `TM_V2_CODEMODE` | catalog (opt-in `direct`) — **v2 only** | How our tools reach the model. OpenCode 2.x decides tool visibility with `options.codemode`. We used to send `codemode:false` 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 ("They cannot be called directly…"), and the model's own callable list was the nine native tools. So the default now sends nothing, and `direct` is an experiment for a build that honours it. The observable truth is `tools_in_request` in the shutdown record (rendered by `tm_stats` as `请求内实际可见=…`), never the flag we sent |
|
|
625
613
|
| `TM_LEDGER_MAX_ITEMS` | 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 |
|
|
626
614
|
| `TM_V2_PROBE` | — (v2 only) | 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; without it the same name sets still ride the trajectory so `tm_stats` can show them |
|
|
627
|
-
| `TM_PRIVATE_SPACE` | `
|
|
615
|
+
| `TM_PRIVATE_SPACE` | `deny` | What private space (loopback, RFC1918, ULA, CGNAT, `.localhost`) does through our tools: `allow` opens the class, `deny` refuses it. 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 the refusal prints both operator exits instead (`allow`, or that one hostname in `TM_WEBFETCH_ALLOWED_DOMAINS`). Never conflated with the FORBIDDEN ranges: cloud-metadata / link-local / reserved are refused under every setting and every allowlist, with no consent path |
|
|
628
616
|
| `TM_V2_BROWSER_GATE` | on — **v2 only** | 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 the host's ungoverned browsing; the boot line and `tm_stats` say which world is running |
|
|
629
617
|
| `TM_NATIVE_REPORT_MAX_TOKENS` | 1600 — **v2 only** | The budget a report-shaped native result (anything carrying a Markdown table — `tm_stats` via Code Mode, a `tm_join` summary) keeps in context. Table lines and their headings win the space and the prose between them is what gets dropped, because a table with a hole in it is not a table; the full text still rides the handle |
|
|
630
618
|
| `TM_NATIVE_SNAPSHOT_MAX_TOKENS` | 1200 — **v2 only** | The budget a native `browser_snapshot` / `browser_find` 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 |
|
|
@@ -640,25 +628,27 @@ for overrides, extra agents and disabling roles.
|
|
|
640
628
|
| `TM_BLACKBOARD_TTL` | `7` | store retention (days) |
|
|
641
629
|
| `TM_BOARD_MAX_CHARS` | `200000` | tm_board_write: one board file's character cap — over it the write refuses (with a "split the topic" hint) instead of truncating a deliverable |
|
|
642
630
|
| `TM_BOARD_MAX_FILES` | `200` | tm_board_write: markdown files allowed per session folder; the TTL sweeper is the only reclaim path, so the refusal names it and the `ttlDays` option |
|
|
643
|
-
| `TM_BASH_READONLY_ALLOWED` | built-in table | tm_bash allowlist |
|
|
644
631
|
| `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) |
|
|
645
|
-
| `TM_WEBFETCH_ALLOWED_DOMAINS` |
|
|
646
|
-
| `
|
|
647
|
-
| `TM_BROWSER_HEADLESS` | `auto` | `1` headless (CI) / `0` headful / `auto` (headless only on display-less Linux) |
|
|
648
|
-
| `TM_BROWSER_ENGINE` | `playwright` | `playwright` (needs Node ≥ 20; any import failure auto-degrades) / `cdp-legacy` (zero-dep CDP pipe, core verbs only) |
|
|
649
|
-
| `TM_BROWSER_SNAPSHOT_MAX_TOKENS` | `1200` | hard cap on `take_snapshot` payloads |
|
|
650
|
-
| `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, and when the page has nothing addressable left the note says the blankness is ours and points at `allow_host` / this env var |
|
|
651
|
-
| `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 |
|
|
652
|
-
| `TM_BROWSER_ASK_EVAL` | `on` | `evaluate_script` runs arbitrary JS in YOUR browser — the one verb the domain allowlist can't cover (it limits where we navigate, not what a loaded page hands back). One official-dialog consent per browser session; no ask bridge ⇒ refused. `off` restores the old behaviour; the result redaction (JWT/bearer/cookie/api-key shapes) is NOT switchable |
|
|
653
|
-
| `TM_WEB_CACHE_TTL_SEC` | `300` | how long a governed fetch may re-serve the same URL (0 = off). One store shared by tm_webfetch / tm_search / the PTC bridge; entries are hash-named (a token-bearing query never hits disk) and are only read or written for hops the STATIC allowlist admitted — dialog consent stays per-request, and a re-served page says 缓存命中 |
|
|
654
|
-
| `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) |
|
|
632
|
+
| `TM_WEBFETCH_ALLOWED_DOMAINS` | `"*"` | tm_webfetch / tm_search allowlist (`"*"` opens all; empty = deny all; a custom list REPLACES the seed — keep the engine hosts). `"*"` does **not** cover private space: loopback / RFC1918 / CGNAT / `.localhost` still need the dialog on every use, 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 can open |
|
|
633
|
+
| `TM_WEB_CACHE_TTL_SEC` | `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 — dialog consent stays per-request, and a re-served page says 缓存命中 |
|
|
655
634
|
| `TM_SEARCH_WEIGHTS` | unset | per-engine fusion weight overrides, e.g. `bing=0.3,hn=0.25`; anything unset keeps the built-in table |
|
|
656
635
|
| `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) |
|
|
657
636
|
| `TM_SEARCH_MAX_HITS` | `10` | hits kept per engine leg and in the fused list |
|
|
658
637
|
| `TM_SEARCH_DISABLED_ENGINES` | unset | engines removed from the roster AND from every `auto` route (`sogou,baidu` style) |
|
|
659
638
|
| `TM_BASH_TIMEOUT_PROBE_MS` | `60000` | ceiling forced onto a `timeout` the model set for a read-only probe command (0 disables) |
|
|
639
|
+
| `TM_PRUNE` | `on` | `off` stops context pruning entirely (settled message bodies stay in the request) |
|
|
640
|
+
| `TM_PRUNE_AT_PERCENT` | `70` | prune once the request reaches this share of the model's window; derived from `limit.context`, never a fixed token count. **Clamped to 40–95** — a lower value is silently raised to 40, so a reproduction that sets 5 is really testing 40 |
|
|
641
|
+
| `TM_PRUNE_KEEP_TAIL_PERCENT` | `40` | share of the window kept verbatim at the tail; the newest message is always kept |
|
|
642
|
+
| `TM_RETRY` | `on` | `off` stops the retry governor (no backoff directive, no cooldown) |
|
|
643
|
+
| `TM_RETRY_BASE_MS` | `5000` | first backoff after a quota/rate-limit error; doubles per consecutive error |
|
|
644
|
+
| `TM_RETRY_MAX_MS` | `60000` | ceiling for that backoff |
|
|
645
|
+
| `TM_RETRY_JITTER` | `0.3` | ± fraction applied to the wait, so parallel sessions do not re-fire together |
|
|
646
|
+
| `TM_RETRY_BREAK_AFTER` | `5` | consecutive errors before the cooldown starts |
|
|
647
|
+
| `TM_RETRY_COOLDOWN_MS` | `60000` | how long new sub-agent dispatches are refused once the breaker trips |
|
|
648
|
+
| `TM_SPLIT_ADVICE` | `on` | `off` stops the split hint (the prompt discipline stays) |
|
|
649
|
+
| `TM_SPLIT_BRIEF_TOKENS` | `4000` | a dispatch brief above this size earns a split hint on the next request. **Clamped to ≥200** — a lower value is silently raised, so a reproduction that sets 50 is really testing 200 |
|
|
650
|
+
| `TM_SPLIT_MAX_CRITERIA` | `3` | the prompt's own rule: more acceptance criteria than this is a signal to split |
|
|
660
651
|
| `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 |
|
|
661
|
-
| `TM_PTY_MAX` | `4` | concurrent `tm_pty` terminal sessions this plugin may keep running at once |
|
|
662
652
|
| `TM_JOIN_MAX_WAIT_MS` | `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 |
|
|
663
653
|
| `TM_STORE_RECLAIM` | `on` | at boot, reclaim what an upgrade left behind: a per-workspace store shard idle past the TTL, and the pre-shard `blackboard/` + `trajectory/` under the temp-dir fallback (503 MB of expired runs on one real machine, with no sweeper pointed at them any more). 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 (the test runner sets it) |
|
|
664
654
|
| `TM_TASK_OFFLOAD` | `on` | keep the HOST's background sub-agent inside the context budget: when `task { background: true }` finishes, the host injects the child's full reply into your session; this replaces an oversized body with a preview + a `tm_fetch`-style pointer (`tm_join { ids: [...] }`), touching ONLY synthetic parts that carry the host's own `<task id=… state="completed">` envelope and exceed the text offload threshold. Nothing is copied to disk — the text stays where it was written (the child session). `off` restores the host's verbatim injection. **On OpenCode 2.x this half does not apply**: a v2 plugin is never handed the injected message before it is persisted, and we deliberately do NOT rewrite outgoing messages (a wrong guess about that layer's shape deletes evidence silently — the v1 `experimental.chat.messages.transform` path is left unimplemented on purpose). The compensation there is contractual, not a rewrite: the child's oversized deliverable goes to the blackboard file and its reply carries the path, so the lead reads the summary and pulls the whole thing with `tm_join` |
|
|
@@ -668,19 +658,12 @@ for overrides, extra agents and disabling roles.
|
|
|
668
658
|
| `TM_COMPACTION_AUTOCONTINUE` | `on` | `off` stops the host from silently resuming the turn after a compaction, so a human re-reads state first |
|
|
669
659
|
| `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 |
|
|
670
660
|
| `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 |
|
|
671
|
-
| `TM_BROWSER_USER_DATA_DIR` | — (isolated temp profile) | explicit persistent profile dir — the ONLY way logins survive between sessions, and honored by BOTH engines (playwright and cdp-legacy). Use a DEDICATED directory (e.g. `D:\tm-browser-profile`) and log in by hand the first time; pointing it at your browser's own data dir (`…\Microsoft\Edge\User Data`, `google-chrome`, Firefox `Profiles`) is refused before anything spawns, because that would have the agent browse as you while a force-kill reaper owns the process |
|
|
672
661
|
| `TM_MEMORY_GLOBAL_DIR` | `~/.opencode-team/memories/global/` | tm_memory GLOBAL tier store |
|
|
673
662
|
| `TM_MEMORY_SESSION_TTL_MIN` | `240` | session-tier entry TTL (lazy + boot sweep) |
|
|
674
663
|
| `TM_MEMORY_MAX_ENTRIES` | `200` | per-scope entry cap; over it `add` fails on purpose — run `compact` |
|
|
675
664
|
| `TM_MEMORY_STALE_DAYS` | `30` | age after which search hits are tagged `[stale Nd]` (`0` disables) |
|
|
676
665
|
| `TM_MEMORY_SESSION_PERSIST` | — (ephemeral) | `1` also writes session entries under `memories/sessions/<sid>/` |
|
|
677
666
|
| `TM_HIT_BLACKLIST` | `maimai.cn` | extra domains never listed as search hits (comma/semicolon separated; same-name-different-site noise like 脉脉) |
|
|
678
|
-
| `TM_PTC_MAX_PROGRAM_CHARS` | `4000` | PTC program source cap |
|
|
679
|
-
| `TM_PTC_MAX_CALLS` | `20` | PTC per-run bridge-call budget (1–200) |
|
|
680
|
-
| `TM_PTC_MAX_ERRORS` | `3` | PTC per-run error budget (1–50) |
|
|
681
|
-
| `TM_PTC_TIMEOUT_MS` | `60000` | PTC per-run wall-clock timeout (5s–10min) |
|
|
682
|
-
| `TM_PTC_ENGINE` | `auto` | `auto` (worker→inline degrade) / `worker` / `inline` |
|
|
683
|
-
| `TM_PTC_WEB_BRIDGE` | `on` | expose `tm.search` / `tm.webfetch` to PTC programs (`off` removes them from the bridge set) |
|
|
684
667
|
|
|
685
668
|
---
|
|
686
669
|
|
|
@@ -745,15 +728,27 @@ not the rule: a deliverable over ~50 lines goes to ONE named board file under
|
|
|
745
728
|
|
|
746
729
|
**Disable one:** `"researcher": { "disable": true }`.
|
|
747
730
|
|
|
748
|
-
**Board retention** via the
|
|
731
|
+
**Board retention** via the plugin entry's `options`:
|
|
732
|
+
|
|
733
|
+
```jsonc
|
|
734
|
+
{
|
|
735
|
+
"plugins": [
|
|
736
|
+
{ "package": "@te-river/opencode-team-mode@latest", "options": { "ttlDays": 7 } }
|
|
737
|
+
]
|
|
738
|
+
}
|
|
739
|
+
```
|
|
740
|
+
|
|
741
|
+
Valid range (0, 365], invalid values fall back to 5. Measured 2026-10-06: with this shape the
|
|
742
|
+
boot row reads `board_ttl_days=9` for `ttlDays: 9`. (The 1.x tuple form is rejected on 2.x:
|
|
743
|
+
`path=$.plugins.1 kind=invalid`.)
|
|
749
744
|
|
|
750
745
|
---
|
|
751
746
|
|
|
752
747
|
## ❓ FAQ
|
|
753
748
|
|
|
754
749
|
**Will this eat my tokens?**
|
|
755
|
-
The opposite is the point. Offload + 80-token previews +
|
|
756
|
-
exist because a five-agent pipeline naively bolted onto one context window
|
|
750
|
+
The opposite is the point. Offload + 80-token previews + the host's Code Mode
|
|
751
|
+
`execute` exist because a five-agent pipeline naively bolted onto one context window
|
|
757
752
|
*would* eat your tokens. The governance is the token-saver.
|
|
758
753
|
|
|
759
754
|
**Is the web access safe?**
|
|
@@ -779,8 +774,9 @@ here fails the test suite before it ever reaches you.
|
|
|
779
774
|
|
|
780
775
|
**Does it work in the CLI (TUI), or only Desktop?**
|
|
781
776
|
Both. Desktop adds the color-coded picker and panels; the governed tools and
|
|
782
|
-
the whole workflow are host-agnostic.
|
|
783
|
-
|
|
777
|
+
the whole workflow are host-agnostic. Interactive browsing is the host's own
|
|
778
|
+
`browser_*` catalog, so a host with no native browser (CLI / standalone) has
|
|
779
|
+
none — the agent reports that gap rather than simulating one.
|
|
784
780
|
|
|
785
781
|
**What happens if I don't answer a confirmation dialog?**
|
|
786
782
|
It auto-rejects after `TM_ASK_TIMEOUT_MIN` (default 1). The plugin never
|
|
@@ -790,7 +786,7 @@ self-approves — the only side it can take is yours or nobody's.
|
|
|
790
786
|
|
|
791
787
|
## 🗑️ Uninstall
|
|
792
788
|
|
|
793
|
-
1. Remove the entry from the `"
|
|
789
|
+
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.
|
|
794
790
|
2. Delete the cache dir (table in [Install](#-read-this-once-save-yourself-an-hour-later)) if you want the disk space back.
|
|
795
791
|
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.
|
|
796
792
|
|
|
@@ -803,28 +799,28 @@ No DLLs were harmed. Nothing was written to your working tree.
|
|
|
803
799
|
```
|
|
804
800
|
opencode-team-mode/
|
|
805
801
|
├── src/
|
|
806
|
-
│ ├── index.ts ← Plugin entry (
|
|
802
|
+
│ ├── index.ts ← Plugin entry ({id, setup} — v2 only)
|
|
807
803
|
│ ├── agents.ts ← Agent structure (modes, colors, temperatures, whitelist matrix)
|
|
808
804
|
│ ├── prompts/ ← Agent prompts (lead / specialists / shared) — pinned by tests
|
|
809
805
|
│ ├── commands.ts ← Slash command definitions
|
|
810
806
|
│ ├── blackboard.ts ← Shared blackboard + TTL sweeper
|
|
811
807
|
│ ├── envprotect.ts ← R6 facade (patterns / classifiers / gate predicates / hook)
|
|
812
|
-
│ ├──
|
|
808
|
+
│ ├── identity.ts ← Agent-name identity (case-insensitive lead match)
|
|
813
809
|
│ ├── tm/ ← Governed tools: pipelines / store / preview / guard / refs /
|
|
814
|
-
│ │ webfetch / search / memory / browser / shell-bridge /
|
|
815
|
-
│
|
|
816
|
-
|
|
810
|
+
│ │ webfetch / search / memory / browser / shell-bridge / board / ledger
|
|
811
|
+
│ ├── host/ ← v2 personality (setup / guard / offload / session / events / …)
|
|
812
|
+
│ └── types.ts ← Loader-contract types
|
|
813
|
+
├── docs/installation.md ← Historical 1.18.x guide (no longer supported)
|
|
817
814
|
├── docs/installation-v2.md ← The 2.x install guide (roles arrive as generated config files)
|
|
818
815
|
├── scripts/ ← One-line installers (bash / PowerShell)
|
|
819
816
|
├── pt07/ ← PT-07 baseline suite (seeded A/B token measurement)
|
|
820
817
|
└── README.*.md ← You are here (twice)
|
|
821
818
|
```
|
|
822
819
|
|
|
823
|
-
The loader calls `
|
|
824
|
-
|
|
825
|
-
`tool.execute.
|
|
826
|
-
|
|
827
|
-
win — the plugin never clobbers.
|
|
820
|
+
The loader calls `setup(ctx)` once: it registers the `tm_*` tools, installs the
|
|
821
|
+
R6/address guard on `permission.evaluate`, attaches the JIT offload on
|
|
822
|
+
`tool.execute.after`, and publishes the blackboard note. User-defined agents with
|
|
823
|
+
the same name always win — the plugin never clobbers.
|
|
828
824
|
|
|
829
825
|
---
|
|
830
826
|
|
|
@@ -846,8 +842,8 @@ are intentionally loose but not psychic).
|
|
|
846
842
|
## 🔗 Links
|
|
847
843
|
|
|
848
844
|
- [npm Package](https://www.npmjs.com/package/@te-river/opencode-team-mode) — `@te-river/opencode-team-mode`
|
|
849
|
-
- [Installation guide,
|
|
850
|
-
- [Installation guide,
|
|
845
|
+
- [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
|
|
846
|
+
- [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
|
|
851
847
|
- [OpenCode Desktop](https://opencode.ai) — Official website & download
|
|
852
848
|
- [OpenCode Docs](https://opencode.ai/docs) — Configuration & plugin documentation
|
|
853
849
|
- [OpenCode Plugin API](https://opencode.ai/docs/plugins) — Build your own plugins
|