@te-river/opencode-team-mode 1.6.2 → 1.7.0
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 +117 -118
- package/README.zh-CN.md +98 -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 +33 -0
- package/dist/host/v2-capabilities.d.ts.map +1 -1
- package/dist/host/v2-capabilities.js +50 -1
- package/dist/host/v2-capabilities.js.map +1 -1
- package/dist/host/v2-compaction.d.ts +129 -0
- package/dist/host/v2-compaction.d.ts.map +1 -0
- package/dist/host/v2-compaction.js +346 -0
- package/dist/host/v2-compaction.js.map +1 -0
- 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 +2 -0
- package/dist/host/v2-events.d.ts.map +1 -1
- package/dist/host/v2-events.js +27 -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 +25 -0
- package/dist/host/v2-types.d.ts.map +1 -1
- package/dist/host/v2.d.ts +14 -12
- package/dist/host/v2.d.ts.map +1 -1
- package/dist/host/v2.js +500 -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 +320 -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 +109 -33
- package/docs/installation.md +9 -0
- package/docs/research/browser-pane.md +5 -0
- package/docs/v2-port-notes.md +87 -0
- package/package.json +1 -1
- package/scripts/gen-v2-config.mjs +246 -94
- package/scripts/lib/fake-ctx.mjs +75 -0
package/dist/types.js
CHANGED
|
@@ -1,26 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Shared type definitions for the v2-only package.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* hooks.push(await plugin.server(input, load.options)); // ← only server()
|
|
11
|
-
* return;
|
|
12
|
-
* }
|
|
13
|
-
* // legacy: bare function default export also works
|
|
14
|
-
* }
|
|
15
|
-
*
|
|
16
|
-
* Facts encoded here:
|
|
17
|
-
* - `setup` is NEVER called on this loader version (v1.1-v1.3 died here).
|
|
18
|
-
* - default export MUST be an object with a `server(input, options)`
|
|
19
|
-
* function; `id` on the object controls the Desktop plugin display name.
|
|
20
|
-
* - server() returns v1 Hooks; the `config(cfg)` hook receives the merged
|
|
21
|
-
* opencode config and is the working mechanism to inject agents/commands.
|
|
22
|
-
* - Agent/command entries inside cfg use the v1 config shape:
|
|
23
|
-
* prompt (string) + permission (object keyed by tool).
|
|
4
|
+
* This file used to carry the OpenCode 1.18.x loader contract (`server()`,
|
|
5
|
+
* `Hooks`, `OpenCodePlugin`, the `tool.execute.before` payloads). v1 support
|
|
6
|
+
* was removed in 1.7.0, so those types are gone; what remains is the internal
|
|
7
|
+
* contract the tm_* tools and the v2 adapters share — the tool result /
|
|
8
|
+
* definition shapes, the host event shape, and the agent/command config
|
|
9
|
+
* shapes the v2 config generator reads.
|
|
24
10
|
*/
|
|
25
11
|
export {};
|
|
26
12
|
//# sourceMappingURL=types.js.map
|
package/dist/types.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG"}
|
package/docs/installation-v2.md
CHANGED
|
@@ -48,15 +48,15 @@ implied:
|
|
|
48
48
|
|---|---|---|
|
|
49
49
|
| The six roles | injected by the plugin at boot | `~/.config/opencode/agents/*.md`, written by the generator below — a plugin cannot add an agent |
|
|
50
50
|
| The six `/team-*` commands | injected by the plugin | `~/.config/opencode/commands/*.md`, same generator |
|
|
51
|
-
| Default agent | filled only if the user left it alone | **Team is the default on every boot** (the API has no getter), plus `default_agent: "
|
|
52
|
-
| Non-blocking commands | `tm_pty
|
|
53
|
-
| File access | `tm_read` / `tm_grep` / `tm_bash` | the host's own `read` / `grep` / `glob` / `shell` — **governed anyway**: oversized results are offloaded through `tool.execute.after`, and out-of-project paths go through the host's own `external_directory` permission (a dialog, where v1 had a hard refusal) |
|
|
54
|
-
| Batch calls | `tm_ptc_run` | the host's own `execute` (Code Mode) |
|
|
55
|
-
| Interactive browsing | `tm_browser` (our own playwright/CDP browser) | **the host's own `browser_*` tools
|
|
51
|
+
| Default agent | filled only if the user left it alone | **Team is the default on every boot** (the API has no getter), plus `default_agent: "Team"` in config, which is the checkable one. Opt out with `{ "package": "@te-river/opencode-team-mode@latest", "options": { "defaultAgent": false } }` in `plugins` — the object form, not the 1.x tuple (measured 2026-10-06: a tuple is rejected with `path=$.plugins.1 kind=invalid`, the object form is delivered) |
|
|
52
|
+
| Non-blocking commands | `tm_pty` — **removed with v1 support** | **not registered at all** — the v2 plugin context has no pty domain, so the tool could only ever report its own missing seam. Run a slow step as its own `shell` call (one per call, each with its own `timeout`) and tee the output to a log you can read back |
|
|
53
|
+
| File access | `tm_read` / `tm_grep` / `tm_bash` — **removed with v1 support** | the host's own `read` / `grep` / `glob` / `shell` — **governed anyway**: oversized results are offloaded through `tool.execute.after`, and out-of-project paths go through the host's own `external_directory` permission (a dialog, where v1 had a hard refusal) |
|
|
54
|
+
| Batch calls | `tm_ptc_run` — **removed with v1 support** | the host's own `execute` (Code Mode) |
|
|
55
|
+
| Interactive browsing | `tm_browser` (our own playwright/CDP browser) — **removed in 1.7.0** | **the host's own `browser_*` tools are the only browser** — they are the one the desktop renders in its side panel (`docs/research/browser-pane.md`). The host catalog is policed by `src/host/v2-browser-gate.ts` (URL / address red line / env-file path at `execute.before`, with a leak-detected fallback that replaces the page with the refusal). Snapshots are capped, not offloaded, so every `[ref=…]` the next click needs stays in context. **Known cost:** on a host with no native browser catalog (CLI / standalone) there is now no browser at all — the agent reports that gap rather than simulating one. Calling convention: `evaluate` takes `{tabID, script}` where `script` is an expression and the return value must be stringified; snapshot tokens read `@e8 [link]` |
|
|
56
56
|
| Sub-agent settlement | the host `event` hook | `ctx.event.subscribe()` — zero-dependency async iterable, filtered by event-type name (`src/host/v2-events.ts`). Without it `tm_join` cannot tell a settled child from a running one; `tm_stats` reports what the feed forwarded and which type names went unrecognised (names only, never payloads) |
|
|
57
57
|
| The task ledger | the host's `todowrite` | **`tm_ledger`**, stored in the host's `ctx.storage` (v2 has no `todowrite`) |
|
|
58
58
|
| Asking the user | the host's official per-request dialog | **a plugin cannot open a dialog on v2.** Governed calls that would have asked instead **fail closed** with a refusal that says why. The dialogs you do see are raised by the host itself (permission rules, out-of-project access) |
|
|
59
|
-
| Web access | a 22-host allowlist + the confirm dialog | **the domain gate is off** (`TM_WEBFETCH_ALLOWED_DOMAINS` defaults to `"*"`): a 2.x plugin cannot raise a dialog, so "approve to proceed" was an instruction to wait for a window that never opens. Address classes are still policed — cloud-metadata / link-local / reserved ranges are refused under every setting, and private space (
|
|
59
|
+
| Web access | a 22-host allowlist + the confirm dialog | **the domain gate is off** (`TM_WEBFETCH_ALLOWED_DOMAINS` defaults to `"*"`): a 2.x plugin cannot raise a dialog, so "approve to proceed" was an instruction to wait for a window that never opens. Address classes are still policed — cloud-metadata / link-local / reserved ranges are refused under every setting, and private space (RFC1918, CGNAT, ULA, IPv6 link-local, `.localhost`) is refused too, but the refusal now names both exits instead of promising a click: `TM_PRIVATE_SPACE=allow` (opens the class) or that one hostname in `TM_WEBFETCH_ALLOWED_DOMAINS` (opens exactly it). **Loopback (`127/8`, `::1`, `localhost`) is served by default** — it reaches only a service the user started on their own machine, and a gate whose only exit is a dialog the plugin cannot raise is a wall, not a gate |
|
|
60
60
|
| Sub-agents | needs `OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS=true` | native; the plugin forces `background: true` on every `subagent` call, so **do not set that env var for 2.x** (it is a v1 workaround and changes nothing here) |
|
|
61
61
|
| Effect on your other modes | the plugin's hooks are global | **none.** Every hook checks the session's owner first, so `build`, `plan` and any agent you installed yourself stay exactly as a fresh OpenCode leaves them — no tool is deleted from their requests, no temperature is set, nothing is offloaded, no permission is tightened, no dispatch is converted to background |
|
|
62
62
|
|
|
@@ -119,7 +119,7 @@ returning cleanly is not evidence the plugin loaded.
|
|
|
119
119
|
the next hand edit may not.
|
|
120
120
|
3. Generate the roles and commands (Step 2). A plugin cannot create an agent on 2.x, so
|
|
121
121
|
without these twelve files Team does not exist however well the plugin loaded.
|
|
122
|
-
4. Set `default_agent: "
|
|
122
|
+
4. Set `default_agent: "Team"` **last** (Step 3) — a default naming agents that are not on
|
|
123
123
|
disk makes the host fall back to `build` silently.
|
|
124
124
|
5. Verify, in this order, and report what you actually saw:
|
|
125
125
|
- `opencode-cli … run --standalone --print-logs` (or restart the desktop and read the
|
|
@@ -131,13 +131,13 @@ returning cleanly is not evidence the plugin loaded.
|
|
|
131
131
|
`agents_editor_unseen` with `agents_resolved_in_request` non-empty, because the host really
|
|
132
132
|
resolved those ids in a request — from 本进程无法区分, since `ctx.agent.transform` is a
|
|
133
133
|
snapshot from BEFORE the config directory merges. "Step 3 has not run" shows up at the host
|
|
134
|
-
(`--agent
|
|
134
|
+
(`--agent Team` does not resolve), never as a row in this table.
|
|
135
135
|
- If the log shows nothing at all about our id, the entry is under a key the host does
|
|
136
136
|
not read (`plugin`, singular) or the directory has no root `index.js`. Both failures
|
|
137
137
|
are silent by design; the absence of an error is not a success signal.
|
|
138
138
|
6. Say which of the two spellings you left installed, and whether the package came from
|
|
139
139
|
npm or from a local path — the Extensions panel cannot tell them apart (a loader file
|
|
140
|
-
named `team-mode.js` and a directory whose plugin id is
|
|
140
|
+
named `team-mode.js` and a directory whose plugin id is `@te-river/opencode-team-mode` print identically).
|
|
141
141
|
|
|
142
142
|
### Step 2 — Generate the six roles and the six commands
|
|
143
143
|
|
|
@@ -157,7 +157,7 @@ node …/scripts/gen-v2-config.mjs --dir ~/.config/opencode
|
|
|
157
157
|
|
|
158
158
|
Expected output: `共 12 个文件(角色 6 + 命令 6)`. It is idempotent — re-running writes
|
|
159
159
|
nothing and prints `已是最新`. A file it did not generate (no marker) is **refused**, not
|
|
160
|
-
overwritten, so your own `agents/
|
|
160
|
+
overwritten, so your own `agents/Team.md` survives; `--force` overrides that.
|
|
161
161
|
|
|
162
162
|
On Windows the installed path is
|
|
163
163
|
`%USERPROFILE%\.config\opencode\node_modules\@te-river\opencode-team-mode\scripts\gen-v2-config.mjs`.
|
|
@@ -174,7 +174,7 @@ The plugin also calls `default("team")` on every boot, but a 2.x plugin cannot r
|
|
|
174
174
|
value back, so the config key is the one you can verify:
|
|
175
175
|
|
|
176
176
|
```jsonc
|
|
177
|
-
{ "default_agent": "
|
|
177
|
+
{ "default_agent": "Team" }
|
|
178
178
|
```
|
|
179
179
|
|
|
180
180
|
**Order matters:** set it only after Step 2 wrote the role files. A default naming a
|
|
@@ -191,7 +191,7 @@ opencode reload # or just restart the desktop app
|
|
|
191
191
|
|
|
192
192
|
Four checks, in this order:
|
|
193
193
|
|
|
194
|
-
1. `opencode agents` (or the picker) lists `
|
|
194
|
+
1. `opencode agents` (or the picker) lists `Team`, `architect`, `implementer`,
|
|
195
195
|
`reviewer`, `tester`, `researcher`.
|
|
196
196
|
2. A new session starts as Team. Ask: `tm_stats` — the reply has a
|
|
197
197
|
**启动与人格** section showing the v2 boot line: which tools were registered, whether
|
|
@@ -215,13 +215,55 @@ opencode reload
|
|
|
215
215
|
The second command is not optional after an upgrade: the role files carry the prompts, and
|
|
216
216
|
a new release may have changed them. Re-running is cheap — unchanged files are skipped.
|
|
217
217
|
|
|
218
|
+
**Prefer the installer for an upgrade: `scripts/install.ps1` / `install.sh`, re-run.** It is the
|
|
219
|
+
only path that carries migrations. The installer recognises our own entry whatever its spelling
|
|
220
|
+
(`@te-river/opencode-team-mode`, a pinned version, or a `vendor/team-mode` path — one regex,
|
|
221
|
+
`scripts/lib/config-surgery.cjs`), so it **replaces** it instead of appending a second one; two
|
|
222
|
+
entries load the plugin twice, and the host's dedupe matches only an identical string. It then
|
|
223
|
+
rewrites `default_agent` and **reads the value back from disk**. That matters because
|
|
224
|
+
`default_agent` naming a role that does not exist is not an error: the host falls back to
|
|
225
|
+
`build` silently, and the session simply stops being Team. If you upgrade by hand
|
|
226
|
+
(`plugin update` + the generator) and a release ever renames a role, re-run the installer
|
|
227
|
+
before concluding the install is broken.
|
|
228
|
+
|
|
229
|
+
**Before a release, run the real-host acceptance script:** `node scripts/acceptance.mjs` prints a
|
|
230
|
+
pass/fail table for the checks that found the last two defects (plugin load, the 2.x option shape,
|
|
231
|
+
agent visibility, the layered config, R6 armed by default, `grep`/`glob` not bypassing the
|
|
232
|
+
env-file line). It is zero-token by default; `--turns` adds the checks that need a model.
|
|
233
|
+
|
|
234
|
+
### Layered configuration (`team-mode.jsonc`)
|
|
235
|
+
|
|
236
|
+
Settings can live in a file instead of the environment. Two layers, mirroring the host's own config:
|
|
237
|
+
|
|
238
|
+
| layer | file |
|
|
239
|
+
|---|---|
|
|
240
|
+
| global | `~/.config/opencode/team-mode.jsonc` |
|
|
241
|
+
| project | `<project>/team-mode.jsonc`, or `<project>/.opencode/team-mode.jsonc` (the `.opencode` one wins) |
|
|
242
|
+
|
|
243
|
+
Precedence is `env < global < project`, and **a file beats an environment variable** — a value you
|
|
244
|
+
put in a file wins over the matching `TM_*`. To get the environment back for one run, set
|
|
245
|
+
`TM_CONFIG_ENV_ONLY=1`: it is read before any file, and a file cannot switch it off.
|
|
246
|
+
|
|
247
|
+
Keys are the `TM_*` names in camelCase (`offloadThreshold`, `searchDefaultEngine`, …). Six keys are
|
|
248
|
+
**red lines a file may not change** — `envProtect`, `r6FineAsk`, `privateSpace`,
|
|
249
|
+
`webfetchAllowedDomains`, `browserAskEval`, `bashReadonlyAllowed`; a file value for one of them is
|
|
250
|
+
ignored and reported. A file that fails to parse is skipped **whole** (half a config is worse than
|
|
251
|
+
none), while a single bad key loses only itself. Unknown keys warn at boot and are never applied.
|
|
252
|
+
`tm_stats` shows which layer each key came from.
|
|
253
|
+
|
|
254
|
+
**R6 (env protection) is armed by default.** A Team role's native `read` of a `.env` is denied
|
|
255
|
+
outright, and a shell command that reads the environment goes to the host's own permission prompt.
|
|
256
|
+
Turn it off with the plugin option `envProtect: false` or with `TM_ENV_PROTECT=off`; on 2.x the
|
|
257
|
+
shell face is an `ask` (the host's dialog — a plugin cannot raise one) while the env-FILE face is
|
|
258
|
+
a hard `deny` with no consent path.
|
|
259
|
+
|
|
218
260
|
### Uninstalling
|
|
219
261
|
|
|
220
262
|
1. Remove the plugin entry (`opencode plugin remove @te-river/opencode-team-mode`).
|
|
221
|
-
2. Delete the generated files: `~/.config/opencode/agents/{
|
|
263
|
+
2. Delete the generated files: `~/.config/opencode/agents/{Team,architect,implementer,reviewer,tester,researcher}.md`
|
|
222
264
|
and `~/.config/opencode/commands/team-*.md`. Only files carrying our marker were written
|
|
223
265
|
by the generator — anything else you wrote yourself is yours.
|
|
224
|
-
3. Remove `"default_agent": "
|
|
266
|
+
3. Remove `"default_agent": "Team"` if you set it.
|
|
225
267
|
4. The plugin's own data lives under `<repo>/.git/opencode-team/` (git workspaces) or the
|
|
226
268
|
OS temp dir; deleting it costs nothing — the TTL sweeper does the same over time.
|
|
227
269
|
|
|
@@ -231,15 +273,16 @@ a new release may have changed them. Re-running is cheap — unchanged files are
|
|
|
231
273
|
|---|---|
|
|
232
274
|
| Roles are not in the picker | Step 2 never ran, or ran against a different `--dir`. `ls ~/.config/opencode/agents` |
|
|
233
275
|
| The default silently became `build` | `default_agent` names an agent that does not exist — re-run Step 2, then Step 3 |
|
|
234
|
-
| A hand-written `agents/
|
|
276
|
+
| A hand-written `agents/Team.md` was not updated | By design: the generator refuses files it did not create. Move yours aside, or pass `--force` |
|
|
235
277
|
| `/team-plan` etc. run but not as the specialist | 2.x documents `mode: subagent` as "runs only in a child session"; if a command selecting a specialist does not behave, the fix is `mode: "all"` in the role definition (report it — this is the one item in this flow still unverified on a live host) |
|
|
236
278
|
| `tm_webfetch` on an odd site says it refused **without asking anyone** | Expected on 2.x: a plugin cannot raise a dialog. Use a source that works, or let the host's own permission rule allow it; the address red line (metadata / private) has no consent path at all, on either generation |
|
|
237
279
|
| `task`/`subagent` seems to block the lead | On 2.x the plugin forces `background: true`; if you also set the old `OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS`, unset it — it is a v1 flag and only confuses the picture |
|
|
238
280
|
| A tool's output arrives un-offloaded | `TM_NATIVE_OFFLOAD=off` (or a missing `tool.execute.after` seam on an older 2.x build) — `tm_stats` names which |
|
|
281
|
+
| Context compacts earlier than the host would on its own | By design (task #39): Team admits a compaction once the usage crosses **75% of the model's window**, through `ctx.session.compact` — the host's `compaction` block has no percentage key and a 2.x plugin has no `config` domain, so the threshold had to live in the package. `TM_COMPACT_AT_PERCENT=90` moves it, `TM_COMPACT_TRIGGER=off` hands the timing back to the host entirely. `tm_stats` prints `compact_checked / fired / confirmed / below / deduped / no_usage / no_limit`, and the `ctx.session.compact` capability row is `ok` only once the host has accepted an admission |
|
|
239
282
|
| The plugin behaves oddly — two dialogs, doubled hooks, tools registered twice | It is loaded twice. `opencode.json` and `opencode.jsonc` are merged, and the host's dedupe matches only an identical string, so the same package under two spellings (or in both files) is two personalities. Count the Team entries across BOTH files; `msg="loading plugin"` appearing twice for one id is the proof |
|
|
240
283
|
| You wrote the entry and the host says nothing at all about it | Two silent causes, both by design: the key is singular `plugin` (2.x reads `plugins`), or a directory entry without a root `index.js`. The loader resolves a directory as `<dir>/index` only and drops it with no message when that fails — absence of an error is not a success signal here |
|
|
241
284
|
| A background sub-agent's reply is huge | The child is registered, collectable AND readable: `tm_join` lists it (marked 宿主 subagent 派发), reads its report back through `ctx.session.context`, and credits the seam it came from (`正文来源=ctx.session.context`) — so the reply arrives through the same JIT pipeline as everything else (offload + ≤80-token preview + `tm_fetch` handle when it is oversized). What v2 still never gives a plugin is the injected message BEFORE the host persists it, so we can read a report but cannot rewrite the injection — which is why a >~50-line DELIVERABLE still belongs on the blackboard file with its path in the reply |
|
|
242
|
-
| You want
|
|
285
|
+
| You want a plugin's own browser visible in the side panel | Not possible — the panel attaches to the server's own browser service (`docs/research/browser-pane.md`), and the self-built `tm_browser` was removed in 1.7.0. What IS reachable is a still frame: hand a screenshot to the host's `browser_preview { path }`, which renders a server-local png/html/md/pdf into that panel. Not yet confirmed in a real GUI session |
|
|
243
286
|
| You need to know what the host actually exposes | `TM_V2_PROBE=<path>.jsonl` records tool and action **names and counts only** — never a command, path, URL or value — and `tm_stats` renders the capability matrix |
|
|
244
287
|
|
|
245
288
|
---
|
|
@@ -259,16 +302,16 @@ a new release may have changed them. Re-running is cheap — unchanged files are
|
|
|
259
302
|
|---|---|---|
|
|
260
303
|
| 六个角色 | 插件启动时注入 | `~/.config/opencode/agents/*.md`,由下面的生成器写出——插件没有"造 agent"的接口 |
|
|
261
304
|
| 六条 `/team-*` 命令 | 插件注入 | 同一个生成器写 `commands/*.md` |
|
|
262
|
-
| 默认 agent | 只在用户没动过时补位 | **每次启动都把 Team 设成默认**(v2 没有读取接口),再加配置里的 `default_agent: "
|
|
263
|
-
| 读文件 / 搜代码 / 跑命令 | `tm_read` / `tm_grep` / `tm_bash
|
|
264
|
-
| 批量调用 | `tm_ptc_run
|
|
305
|
+
| 默认 agent | 只在用户没动过时补位 | **每次启动都把 Team 设成默认**(v2 没有读取接口),再加配置里的 `default_agent: "Team"`(这一条才是你核对得动的)。退出方式:`plugins` 里写 `{ "package": "@te-river/opencode-team-mode@latest", "options": { "defaultAgent": false } }`——必须是对象形状,不是 1.x 的元组(2026-10-06 实测:元组被拒 `path=$.plugins.1 kind=invalid`,对象形状能送达) |
|
|
306
|
+
| 读文件 / 搜代码 / 跑命令 | `tm_read` / `tm_grep` / `tm_bash`——**已随 v1 支持一起移除** | 宿主的 `read` / `grep` / `glob` / `shell`,**治理照旧**:超大结果照样在 `tool.execute.after` 被卸载成预览 + 句柄,跨出项目的路径走宿主自己的 `external_directory` 权限(v1 是硬拒,v2 是弹窗) |
|
|
307
|
+
| 批量调用 | `tm_ptc_run`——**已随 v1 支持一起移除** | 宿主自己的 `execute`(Code Mode) |
|
|
265
308
|
| 入口文件(v2 主机的硬要求) | — | 宿主把插件**目录**解析成 `<目录>/index.js`;只写 `package.json#exports` 的目录会被**静默忽略**(连错误都没有)。本包根目录的 `index.js` 就是为这条存在的——`plugins: ["./vendor/team-mode"]` 这类官方写法能加载,靠的是它 |
|
|
266
|
-
| 交互式浏览 | `tm_browser`(我们自己起的 playwright/CDP
|
|
309
|
+
| 交互式浏览 | `tm_browser`(我们自己起的 playwright/CDP 浏览器)——**已在 1.7.0 移除** | **宿主自己的 `browser_*` 是唯一的浏览器**——只有它是桌面端侧边栏里那个浏览器(`docs/research/browser-pane.md`)。原生目录由 `src/host/v2-browser-gate.ts` 管(`execute.before` 上判 URL / 地址红线 / 环境文件路径,并且有"漏过去就把页面换成拒绝语"的兜底)。快照是**截断**而不是卸载,所以下一次点击要的 `[ref=…]` 都留在上下文里。**已知代价:** 没有原生浏览器目录的宿主(CLI / standalone)从此没有浏览器可用——agent 如实报告这个缺口,绝不模拟。调用约定:`evaluate` 的参数是 `{tabID, script}`,`script` 是表达式,返回值要自己 stringify;快照记号形如 `@e8 [link]` |
|
|
267
310
|
| 子代理结算检测 | 宿主的 `event` 钩子 | `ctx.event.subscribe()`——零依赖的 async iterable,按事件类型名过滤(`src/host/v2-events.ts`)。没有它 `tm_join` 分不清「已结算」和「仍在跑」;`tm_stats` 会给出转发了多少、哪些类型名没认出来(只记名字,绝不记负载) |
|
|
268
311
|
| 任务清单 | 宿主 `todowrite` | **`tm_ledger`**,存在宿主的 `ctx.storage` 里(v2 不给插件 `todowrite`) |
|
|
269
312
|
| 征求用户同意 | 宿主官方逐次弹窗 | **插件在 v2 弹不出对话框。** 原本该问的受治理调用一律**直接拒绝**,并说明是"没人可问"而不是"问了被拒"。你看到的弹窗都来自宿主自己(权限规则、越出项目目录) |
|
|
270
|
-
| 联网 | 22 个域名白名单 + 确认弹窗 | **域名门禁关掉**(`TM_WEBFETCH_ALLOWED_DOMAINS` 默认 `"*"`):2.x 插件弹不出确认框,"批准后放行"等于让 agent 去等一个永远不会出现的窗口。地址类别照管——元数据 / 链路本地 /
|
|
271
|
-
| 非阻塞命令 | `tm_pty
|
|
313
|
+
| 联网 | 22 个域名白名单 + 确认弹窗 | **域名门禁关掉**(`TM_WEBFETCH_ALLOWED_DOMAINS` 默认 `"*"`):2.x 插件弹不出确认框,"批准后放行"等于让 agent 去等一个永远不会出现的窗口。地址类别照管——元数据 / 链路本地 / 保留网段在任何设置下都拒;私网(RFC1918、CGNAT、ULA、IPv6 链路本地、`.localhost`)也拒,但拒绝语现在给出两条出口而不是承诺点击:`TM_PRIVATE_SPACE=allow`(放开整段),或把那一台主机名写进 `TM_WEBFETCH_ALLOWED_DOMAINS`(只放开它自己)。**回环(`127/8`、`::1`、`localhost`)默认放行**——它只打到用户自己机器上的服务,而一道唯一出口是"插件弹不出来的确认框"的门禁,是墙不是门 |
|
|
314
|
+
| 非阻塞命令 | `tm_pty`——**已随 v1 支持一起移除** | **完全不注册**——v2 插件上下文没有 pty 域,这个工具只能报自己缺缝。慢步骤请一条一个 `shell` 调用(各自带 `timeout`),并把输出 tee 到日志好回读 |
|
|
272
315
|
| 子代理 | 需要 `OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS=true` | 原生能力;插件对每次 `subagent` 强制 `background: true`,所以 **2.x 不要去设那个环境变量**(那是 v1 的补丁,在这里什么也不改变,只会让人误判) |
|
|
273
316
|
| 对你其它模式的影响 | 插件钩子是全局的 | **没有影响。** 每个钩子都先看会话归属,所以 `build`、`plan` 和你自己装的 agent 都保持刚装好 OpenCode 时的样子——不会从它们的请求里删工具、不会设温度、不会卸载结果、不会收紧权限、也不会把它们的派发改成后台 |
|
|
274
317
|
|
|
@@ -324,18 +367,18 @@ Node 20+ 还会额外启用 Playwright 浏览器引擎)。
|
|
|
324
367
|
下一个手工编辑的人未必容忍。
|
|
325
368
|
3. 生成角色与命令(第 2 步)。2.x 上插件不能创建 agent,所以这十二个文件不在,Team 就不存在,
|
|
326
369
|
插件加载得再好也没用。
|
|
327
|
-
4. 最后一步才写 `default_agent: "
|
|
370
|
+
4. 最后一步才写 `default_agent: "Team"`(第 3 步)——默认值指向不存在的 agent 会让宿主
|
|
328
371
|
静默退回 `build`。
|
|
329
372
|
5. 验证,按顺序,并且把你真正看到的说出来:
|
|
330
373
|
- `opencode-cli … run --standalone --print-logs`(或重启桌面端后读日志)→
|
|
331
374
|
**恰好一条** `msg="loading plugin" id=<我们的条目> entrypoint=file:///…/index.js`,
|
|
332
375
|
并且**没有**指名我们的 `failed to load plugin`。
|
|
333
376
|
- 新建一个 Team 会话 → `tm_stats` 的 `启动与人格`:`tools_registered: 9`,以及 `作用域`
|
|
334
|
-
那一行。角色这一项看的是可见性行,不再是缺名字段(boot 行的 `agents_missing` 已经去掉):它区分"已装而看不见"——`agents_editor_unseen` 有值,且 `agents_resolved_in_request` 也有值,说明宿主在真实请求里解析过这些 id——和"本进程无法区分",因为 `ctx.agent.transform` 拿到的是配置目录合并**之前**的角色集。"第 3 步没做"要到宿主那边才看得见(`--agent
|
|
377
|
+
那一行。角色这一项看的是可见性行,不再是缺名字段(boot 行的 `agents_missing` 已经去掉):它区分"已装而看不见"——`agents_editor_unseen` 有值,且 `agents_resolved_in_request` 也有值,说明宿主在真实请求里解析过这些 id——和"本进程无法区分",因为 `ctx.agent.transform` 拿到的是配置目录合并**之前**的角色集。"第 3 步没做"要到宿主那边才看得见(`--agent Team` 解析不到),不会在这张表里变成一行结论。
|
|
335
378
|
- 日志里关于我们的 id 一个字都没有 → 条目写在了宿主不读的键下(单数 `plugin`),或者那个
|
|
336
379
|
目录没有根 `index.js`。这两种失败都是设计上静默的,所以"没报错"不是成功信号。
|
|
337
380
|
6. 明确说清你留下的是哪种写法、包来自 npm 还是本地路径 —— 扩展面板分不出来:一个叫
|
|
338
|
-
`team-mode.js` 的 loader 文件,和一个插件 id 恰好是
|
|
381
|
+
`team-mode.js` 的 loader 文件,和一个插件 id 恰好是 `@te-river/opencode-team-mode` 的目录条目,显示出来一模一样。
|
|
339
382
|
|
|
340
383
|
### 第 2 步 — 生成六个角色和六条命令
|
|
341
384
|
|
|
@@ -353,7 +396,7 @@ node …/scripts/gen-v2-config.mjs --dir ~/.config/opencode
|
|
|
353
396
|
|
|
354
397
|
期望输出 `共 12 个文件(角色 6 + 命令 6)`。它是幂等的:再跑一次什么都不写,只报
|
|
355
398
|
`已是最新`。不是它生成的文件(没有标记)会被**拒绝覆盖**,所以你自己手写的
|
|
356
|
-
`agents/
|
|
399
|
+
`agents/Team.md` 安全;要强行覆盖才用 `--force`。
|
|
357
400
|
|
|
358
401
|
Windows 上的路径是
|
|
359
402
|
`%USERPROFILE%\.config\opencode\node_modules\@te-river\opencode-team-mode\scripts\gen-v2-config.mjs`。
|
|
@@ -368,7 +411,7 @@ Windows 上的路径是
|
|
|
368
411
|
插件每次启动也会调 `default("team")`,但 v2 插件读不回这个值,所以能核对的是配置键:
|
|
369
412
|
|
|
370
413
|
```jsonc
|
|
371
|
-
{ "default_agent": "
|
|
414
|
+
{ "default_agent": "Team" }
|
|
372
415
|
```
|
|
373
416
|
|
|
374
417
|
**顺序不能颠倒**:必须在第 2 步已经把角色文件写出去之后再设。默认值指向一个不存在的 agent
|
|
@@ -383,7 +426,7 @@ opencode reload # 或者直接重启桌面端
|
|
|
383
426
|
|
|
384
427
|
按顺序做四项核对:
|
|
385
428
|
|
|
386
|
-
1. `opencode agents`(或选择器里)能看到 `
|
|
429
|
+
1. `opencode agents`(或选择器里)能看到 `Team`、`architect`、`implementer`、`reviewer`、
|
|
387
430
|
`tester`、`researcher`。
|
|
388
431
|
2. 新会话默认是 Team。在里面跑 `tm_stats` —— 回复里应有**启动与人格**一段,展示 v2 的启动
|
|
389
432
|
快照:注册了哪些工具、`tool.execute.after` / `permission.evaluate` / `session.context`
|
|
@@ -406,13 +449,45 @@ opencode reload
|
|
|
406
449
|
第二条命令在升级之后不是可选项:角色文件里装的是提示词,新版本可能改过。重跑很便宜——没变
|
|
407
450
|
的文件会被跳过。
|
|
408
451
|
|
|
452
|
+
**升级请优先走安装器:重跑 `scripts/install.ps1` / `install.sh`。** 它是唯一带迁移的路径。安装器
|
|
453
|
+
认得出"我们自己的那一条"无论它写成什么样子(`@te-river/opencode-team-mode`、钉死的版本号、或
|
|
454
|
+
`vendor/team-mode` 路径——同一个正则,在 `scripts/lib/config-surgery.cjs` 里),所以它是**替换**而不是
|
|
455
|
+
再追加一条;追加成两条会让插件被装载两次,而宿主的去重只认完全相同的字符串。之后它会重写
|
|
456
|
+
`default_agent` 并**从磁盘回读**。这条要紧是因为:`default_agent` 指向一个不存在的角色**不算错误**——
|
|
457
|
+
宿主会静默退回 `build`,会话就此不再是 Team。如果你手工升级(`plugin update` + 生成器),而某个版本
|
|
458
|
+
改过角色名,请先重跑安装器,再下结论说装坏了。
|
|
459
|
+
|
|
460
|
+
**发版前跑一次真机验收脚本:** `node scripts/acceptance.mjs` 会打印一张通过/不通过表,覆盖"抓出最近两个缺陷"的那些检查
|
|
461
|
+
(插件装载、2.x 选项形状、角色可见性、分层配置、R6 默认 armed、`grep`/`glob` 不绕 env-FILE)。默认零 token;`--turns` 加上需要真回合的检查。
|
|
462
|
+
|
|
463
|
+
### 分层配置(`team-mode.jsonc`)
|
|
464
|
+
|
|
465
|
+
设置可以写在文件里,而不是环境变量里。两层,与宿主自己的配置同构:
|
|
466
|
+
|
|
467
|
+
| 层 | 文件 |
|
|
468
|
+
|---|---|
|
|
469
|
+
| 全局 | `~/.config/opencode/team-mode.jsonc` |
|
|
470
|
+
| 项目 | `<project>/team-mode.jsonc`,或 `<project>/.opencode/team-mode.jsonc`(后者胜) |
|
|
471
|
+
|
|
472
|
+
优先级 `env < 全局 < 项目`,并且**文件赢环境变量**——写进文件的值会盖过同名的 `TM_*`。想让某一次运行回到纯环境变量,设
|
|
473
|
+
`TM_CONFIG_ENV_ONLY=1`:它在任何文件被读取之前求值,文件关不掉它。
|
|
474
|
+
|
|
475
|
+
键名就是 `TM_*` 的 camelCase 写法(`offloadThreshold`、`searchDefaultEngine` …)。有六个键是**文件不许改写的红线**——
|
|
476
|
+
`envProtect`、`r6FineAsk`、`privateSpace`、`webfetchAllowedDomains`、`browserAskEval`、`bashReadonlyAllowed`;文件里写了也会被忽略并报告。
|
|
477
|
+
解析失败的那一层**整层跳过**(半份配置比没有配置更危险),单个坏键只丢它自己。未知键在启动时告警、**不应用**。
|
|
478
|
+
`tm_stats` 会显示每个键最终来自哪一层。
|
|
479
|
+
|
|
480
|
+
**R6(环境变量保护)默认开启。** Team 角色用原生 `read` 读 `.env` 会被直接拒;读环境变量的 shell 命令会走宿主自己的权限提示。
|
|
481
|
+
关闭方式:插件选项 `envProtect: false`,或 `TM_ENV_PROTECT=off`。2.x 上 shell 面是 `ask`(宿主的对话框——插件弹不出来),
|
|
482
|
+
env-FILE 面是硬 `deny`、没有同意路径。
|
|
483
|
+
|
|
409
484
|
### 卸载
|
|
410
485
|
|
|
411
486
|
1. 去掉插件条目(`opencode plugin remove @te-river/opencode-team-mode`)。
|
|
412
|
-
2. 删掉生成的文件:`~/.config/opencode/agents/{
|
|
487
|
+
2. 删掉生成的文件:`~/.config/opencode/agents/{Team,architect,implementer,reviewer,tester,researcher}.md`
|
|
413
488
|
和 `~/.config/opencode/commands/team-*.md`。只有带我们标记的文件是生成器写的,你自己写的
|
|
414
489
|
东西它从不碰。
|
|
415
|
-
3. 如果设过 `"default_agent": "
|
|
490
|
+
3. 如果设过 `"default_agent": "Team"`,一并删掉。
|
|
416
491
|
4. 插件自己的数据在 `<repo>/.git/opencode-team/`(git 工作区)或系统临时目录下;删掉没有代价
|
|
417
492
|
——TTL 清理器本来就会定期做同样的事。
|
|
418
493
|
|
|
@@ -422,16 +497,17 @@ opencode reload
|
|
|
422
497
|
|---|---|
|
|
423
498
|
| 选择器里没有那六个角色 | 第 2 步没跑,或者 `--dir` 指到了别处。`ls ~/.config/opencode/agents` |
|
|
424
499
|
| 默认 agent 悄悄变成 `build` | `default_agent` 指向了一个不存在的角色——重跑第 2 步,再做第 3 步 |
|
|
425
|
-
| 手写的 `agents/
|
|
500
|
+
| 手写的 `agents/Team.md` 没被更新 | 这是设计:生成器拒绝覆盖不是它生成的文件。把你的文件挪开,或显式加 `--force` |
|
|
426
501
|
| `/team-plan` 能跑但不是以那个专家身份跑 | 2.x 把 `mode: subagent` 文档化为"只在子会话里运行"。如果选定专家的命令行为不对,改法是角色定义里用 `mode: "all"`(请回报——这是本流程里唯一还没在活体宿主上验证过的一项) |
|
|
427
502
|
| `tm_webfetch` 说它"没问任何人就直接拒绝" | v2 的预期行为:插件弹不出对话框。换一个不需要这次访问的源,或让宿主自己的权限规则放行;地址红线(元数据 / 私网)在两代宿主上都没有授权路径 |
|
|
428
503
|
| `subagent` 好像把领队挡住了 | v2 上插件会强制 `background: true`;如果你顺手设了老的 `OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS`,请取消它——那是 v1 的开关,在这里只会误导判断 |
|
|
429
504
|
| 某个工具的输出没被卸载 | `TM_NATIVE_OFFLOAD=off`(或者那个 2.x 构建没有 `tool.execute.after` 缝)——`tm_stats` 会告诉你是哪一种 |
|
|
505
|
+
| 上下文比宿主自己更早压缩 | 这是设计(任务 #39):用量越过**模型窗口的 75%**,Team 就用 `ctx.session.compact` 提交一次压缩——宿主的 `compaction` 配置里没有百分比键,2.x 插件也没有 config 域,所以这个阈值只能长在包里。`TM_COMPACT_AT_PERCENT=90` 可以调,`TM_COMPACT_TRIGGER=off` 把时机整个交回宿主。`tm_stats` 会打印 `compact_checked / fired / confirmed / below / deduped / no_usage / no_limit`,而 `ctx.session.compact` 那行能力矩阵只有在宿主真的接受过一次之后才会写 `ok` |
|
|
430
506
|
| 插件行为怪:两次弹窗、钩子重复、工具像注册了两遍 | 它被加载了两次。`opencode.json` 与 `opencode.jsonc` 是合并读取的,而宿主去重只认完全相同的字符串,所以同一个包换两种写法(或者同时躺在两个文件里)就是两套人格。把两个文件里指向 Team 的条目一起数一遍;`msg="loading plugin"` 对同一个 id 出现两条就是证据 |
|
|
431
507
|
| 写了条目,但宿主日志里关于它一个字都没有 | 两个"设计上静默"的原因:键名写成了单数 `plugin`(2.x 读 `plugins`),或者目录条目缺根 `index.js`。宿主解析目录只试 `<dir>/index`,试不出来就直接丢掉、不留任何消息 —— 在这里"没报错"不是成功信号 |
|
|
432
508
|
| 后台子代理的回复太长 | 子会话现在会被登记、可被收集(`tm_join` 会列出它,行上标着「宿主 subagent 派发」),但它的**正文**仍然是宿主的注入消息,v2 不会在落盘前把这条消息交给插件;我们也不改写发出的消息(猜错那一层的形状等于静默删证据)。所以超限交付走黑板文件:正文进文件、回复里带路径,领队读那个文件 |
|
|
433
509
|
| `tm_join` 说"没有匹配的派发",可孩子明明跑完了 | 活体在 2.0.16 上复现过:领队用的是**宿主的** `subagent{background:true}`,插件不创建子会话所以登记里没有它,而收养/按 id 认领原本走 `client.session.children` / `client.session.get`——v2 的插件上下文里根本没有 client。所以回复是通过宿主自己的完成注入到达领队的,不是通过我们的收集通道。这条待补的桥是 `ctx.session.get` 做父子校验(事件流已经能给出候选 sessionID) |
|
|
434
|
-
|
|
|
510
|
+
| 想在侧边栏看到插件自己的浏览器页面 | 做不到(面板挂的是服务端自己的浏览器服务;见 `docs/research/browser-pane.md`,自建 `tm_browser` 已在 1.7.0 移除)。能看到的是**静帧**:让 agent 把截图交给宿主的 `browser_preview { path }`,它会把服务端本地文件(png/html/md/pdf/mermaid)渲染进面板——这条尚未在你的 GUI 里验证过 |
|
|
435
511
|
| 想知道宿主到底给了什么 | `TM_V2_PROBE=<路径>.jsonl` 只记工具名与权限动作名**以及计数**,绝不记命令行、路径、URL 或环境变量值;`tm_stats` 会把能力矩阵渲染出来 |
|
|
436
512
|
|
|
437
513
|
### v2 专属的开关
|
|
@@ -442,4 +518,4 @@ opencode reload
|
|
|
442
518
|
| `TM_NATIVE_OFFLOAD` | on | 用 `tool.execute.after` 治理宿主原生工具的结果;off 就退回宿主原样 |
|
|
443
519
|
| `TM_R6_FINE_ASK` | on(v2) | 由按命令分类器决定哪条 shell 要问;`off` 回到"每条 shell 都问",宿主没有 `permission.hook` 时也自动回到这一档 |
|
|
444
520
|
| `TM_V2_PROBE` | 未设 | 把宿主真实工具面/动作名记成 JSONL(只有名字和计数) |
|
|
445
|
-
| `"team-mode": { "defaultAgent": false }` | 不设置 = Team 永远默认 |
|
|
521
|
+
| `{ "package": "@te-river/opencode-team-mode@latest", "options": { "defaultAgent": false } }` | 不设置 = Team 永远默认 | 退出默认位抢占(2.x 必须用对象形状;1.x 的元组会被宿主拒:`path=$.plugins.1 kind=invalid`) |
|
package/docs/installation.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# TeamMode Installation Guide / 安装指南
|
|
2
2
|
|
|
3
|
+
> **⚠️ HISTORICAL — 1.18.x IS NO LONGER SUPPORTED.** As of the 1.7.0 line this package
|
|
4
|
+
> targets OpenCode 2.x only: the `server` (1.18.x) personality was removed, so the plugin
|
|
5
|
+
> exports `{id, setup}` and nothing else. This page is kept for reference only and is **not
|
|
6
|
+
> a working install path** — install from **[installation-v2.md](./installation-v2.md)**.
|
|
7
|
+
>
|
|
8
|
+
> **⚠️ 历史文档——1.18.x 已不再支持。** 自 1.7.0 线起本包只面向 OpenCode 2.x:`server`
|
|
9
|
+
> (1.18.x)人格已移除,插件只导出 `{id, setup}`。本页仅作历史记录,**不是可用的安装
|
|
10
|
+
> 路径**——请走 **[installation-v2.md](./installation-v2.md)**。
|
|
11
|
+
|
|
3
12
|
> **This page is the OpenCode 1.18.x path.** On OpenCode 2.x a plugin cannot create an
|
|
4
13
|
> agent, so the six roles and the six `/team-*` commands have to be generated into
|
|
5
14
|
> `~/.config/opencode/agents/` and `commands/` — that flow is its own page:
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
# 能不能把 tm_browser 显示进宿主侧边栏?(BrowserPane 取证)
|
|
2
2
|
|
|
3
|
+
> **历史记录(2026-10-06 补注):自建浏览器 `tm_browser` 已于 1.7.0 移除**(提交 `d668817`,
|
|
4
|
+
> 破坏性变更)。本页记录的是移除前的取证,保留是因为它解释了"为什么浏览器只能交给宿主原生
|
|
5
|
+
> `browser_*`"——结论(插件无法把自己的页面注册进侧边栏)至今成立,且正是移除自建浏览器的依据。
|
|
6
|
+
> 现在浏览器只剩宿主原生 `browser_*`,由 `src/host/v2-browser-gate.ts` 在 `execute.before` 上治理。
|
|
7
|
+
|
|
3
8
|
日期 2026-09-25。取证对象是用户机器上的安装体,只读、未修改宿主:
|
|
4
9
|
|
|
5
10
|
| 证据 | 位置 |
|
package/docs/v2-port-notes.md
CHANGED
|
@@ -411,3 +411,90 @@ Adjacent and still unread: `Session.Message.Assistant.snapshot {start,end,files}
|
|
|
411
411
|
`Text.state?: ProviderState` are in the contract and are not consumed here — nothing needs
|
|
412
412
|
them yet, and since the probe records key names only, the next reader must re-measure rather than
|
|
413
413
|
trust this paragraph.
|
|
414
|
+
|
|
415
|
+
### 2026-10-06 — the 1.7.0 cut: four measurements that shaped it `[L][R][D]`
|
|
416
|
+
|
|
417
|
+
The v1 personality was deleted outright for the 1.7.0 line (`src/host/v1.ts`, `src/tm/ptc/`,
|
|
418
|
+
`src/tm/ptc.ts`, `src/tm/pty.ts`, `src/tool-coerce.ts`, `src/approval-gate.ts`,
|
|
419
|
+
`src/host-hooks.ts`, `src/tm/bash-timeout.ts`), and four live/measured facts decided how the
|
|
420
|
+
survivors were written. Each is recorded here so the next reader does not re-derive it.
|
|
421
|
+
|
|
422
|
+
- **`permission.evaluate` DOES fire for the file tools, not just `shell`.** A live session's
|
|
423
|
+
guard tally read `guard_actions: read=152 grep=86 edit=246 external_directory=38` — so the
|
|
424
|
+
R6 env-file path face (`pathGuard`, #44) can deny `read` / `write` / `edit` / `glob` /
|
|
425
|
+
`grep` on an env-file path, and the earlier assumption that only the shell command line
|
|
426
|
+
reached this hook was wrong. `[L]`
|
|
427
|
+
- **Context usage lives ONLY in the `session.usage.updated` event.** `session.hook("context")`
|
|
428
|
+
hands over the assembled messages, and those messages carry no token counts: a second turn
|
|
429
|
+
that DID contain an assistant message still logged `source=no_usage messages=3`. The
|
|
430
|
+
denominator is `ctx.model.list()`'s `limit.context`; the numerator is the event. This is
|
|
431
|
+
why `v2-compaction.ts` taps the event feed rather than reading the request payload. `[L]`
|
|
432
|
+
- **win32 `statSync` is case-insensitive on the identity, `realpathSync` is not on the
|
|
433
|
+
spelling.** For the same file, `statSync` returns the SAME `dev`+`ino` for both casings,
|
|
434
|
+
while `realpathSync` echoes back the casing it was handed. So the generator's
|
|
435
|
+
"did we generate this file" check uses `statSync`'s `dev`+`ino` (a rename leftover like the
|
|
436
|
+
old `agents/team.md` is recognised), and a file it cannot identify is **left alone** rather
|
|
437
|
+
than deleted. `[R]`
|
|
438
|
+
- **A plugin cannot name a config-file role.** `ctx.agent.list()` shows only the 7 built-ins,
|
|
439
|
+
and `agent.get({agentID:"team"})` answers `Agent not found: team` — the config directory's
|
|
440
|
+
roles are not visible to the plugin. Since the desktop picker renders `Agent.Info.name` and
|
|
441
|
+
a config role's name is its id verbatim (`Config.Agent` has no `name` key), the only lever
|
|
442
|
+
was the id: the lead role is `Team`. Identity comparisons go through `src/identity.ts`
|
|
443
|
+
(case-insensitive), so `team` / `Team` / `TEAM` are all the lead and an existing install or
|
|
444
|
+
in-flight session is not broken. `agents_default` in the trajectory keeps the lower-case
|
|
445
|
+
value — that field is a measurement, not a label. `[L][R]`
|
|
446
|
+
- **The self-built browser is gone; the host's own is the only one** (user decision
|
|
447
|
+
2026-10-06, commit `d668817`, a breaking change, −6 337 lines). `src/tm/browser.ts`
|
|
448
|
+
(playwright/cdp engines, profile isolation, pid ledger, `browsers.jsonl` orphan
|
|
449
|
+
reaper, subresource gate, `allow_host`, the per-caller lease table),
|
|
450
|
+
`src/tm/serp-loop.ts` and `test-browser.mjs` were deleted, along with every
|
|
451
|
+
`TM_BROWSER_*` knob and `tm_join`'s browser-lease tripwire (a lease warning with no
|
|
452
|
+
leases left can never fire). What stays is the **enforcement for the native path**:
|
|
453
|
+
`src/host/v2-browser-gate.ts` polices the host's `browser_*` catalog at
|
|
454
|
+
`execute.before` (URL and address red lines, env-file paths, a leak-detected
|
|
455
|
+
fallback that replaces the page with the refusal). The test suite count drops
|
|
456
|
+
9 → 8; `test-v2-adapter` still pins the native snapshot's addressability and the
|
|
457
|
+
gate's teeth. Known cost, accepted: on CLI / standalone there is now no browser at
|
|
458
|
+
all, and the prompts require the agent to report that gap rather than simulate it.
|
|
459
|
+
`[L]`
|
|
460
|
+
- **Native browser calling convention, which differs from the deleted tool.**
|
|
461
|
+
`browser_evaluate` takes `{tabID, script}` where `script` is an **expression** (not
|
|
462
|
+
a function source) and the return value must be a scalar you `JSON.stringify`
|
|
463
|
+
yourself; snapshot addressable tokens read `@e8 [link]` (not the deleted tool's
|
|
464
|
+
`[uid=eN]`). `[L]`
|
|
465
|
+
- **Plugin options on 2.x are the `{package, options}` object shape.** The 1.x tuple
|
|
466
|
+
`["spec", {…}]` is rejected with `path=$.plugins.1 kind=invalid`; the object form is
|
|
467
|
+
delivered. Measured 2026-10-06. `[L]`
|
|
468
|
+
- **The host's config has NO session layer.** `opencode.ai/v2/docs/config` is
|
|
469
|
+
authoritative: global `~/.config/opencode/opencode.json(c)`, project
|
|
470
|
+
`<dir>/opencode.json(c)` or `<dir>/.opencode/opencode.json(c)`, merged
|
|
471
|
+
farthest→closest with every `.opencode/` dir overriding every direct config — and
|
|
472
|
+
no per-session layer. So a layered `team-mode` config can only be global + project.
|
|
473
|
+
`[L]`
|
|
474
|
+
|
|
475
|
+
- **The env-FILE red line leaked on `grep` until #58** (measured on 2.0.24). `pathGuard` reads
|
|
476
|
+
`permission.evaluate`'s `resources`, and the host's `grep` reports the **pattern**, not the
|
|
477
|
+
search path, as its resource (the tools page says so verbatim). So `read .env` was denied while
|
|
478
|
+
`grep SECRET .env` returned `SECRET_TOKEN=abc123`. The check now also runs on
|
|
479
|
+
`tool.hook("execute.before")`, the only seam that sees the whole input, over the path-bearing
|
|
480
|
+
fields per tool — and the field names were read out of the host binary rather than guessed
|
|
481
|
+
(`read`/`write`/`edit` → `filePath`; `glob` → `pattern`; `grep` → `path`/`include`).
|
|
482
|
+
`grep`'s own pattern is deliberately NOT judged: it is a regex, and `isEnvFilePath` would
|
|
483
|
+
misfire on a search for the text `.env`. Residual boundary, stated rather than papered over: a
|
|
484
|
+
`grep` that passes only a pattern and no `path`/`include` cannot be judged at this seam.
|
|
485
|
+
`[L]`
|
|
486
|
+
|
|
487
|
+
- **`tools_in_request` was unobservable in a CLI run** (measured on 2.0.24): it was written only
|
|
488
|
+
on `v2-shutdown`, and a CLI process never reaches dispose. It now rides the throttled
|
|
489
|
+
`v2-surface` row too, which is the row a CLI run actually writes. `[L]`
|
|
490
|
+
|
|
491
|
+
- **The context-hook part inventory on 2.0.24** (measured 2026-10-06, three real turns with a
|
|
492
|
+
probe plugin on `session.hook("context")`, plus a read-only scan of the host binary's
|
|
493
|
+
`LLM.Content` union). Message shape is `{id, role, metadata, content:[…]}`; the part types
|
|
494
|
+
that actually appeared, with their key names: `text` → `type,text,providerMetadata`;
|
|
495
|
+
`reasoning` → `type,text,providerMetadata`; `tool-call` → `type,id,name,input,
|
|
496
|
+
providerExecuted,providerMetadata`; `tool-result` → `type,id,name,namespace,result,
|
|
497
|
+
providerExecuted,cache,metadata,providerMetadata`; `media` → `type,media,filename,metadata`.
|
|
498
|
+
The binary's union also names `compaction` and `effort`, which were never observed in a turn.
|
|
499
|
+
This is why the prune layer's first version was inert: it accepted only `type:"text"`, and the
|
|
500
|
+
bulk of a real request is `tool-result`/`tool-call`. `[L]`
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@te-river/opencode-team-mode",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.7.0",
|
|
4
4
|
"description": "Team collaboration mode plugin for OpenCode Desktop — injects specialized agents (architect, reviewer, implementer, tester, researcher) and team workflow commands into your OpenCode workspace.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|