@te-river/opencode-team-mode 1.5.15 → 1.6.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 +853 -690
- package/README.zh-CN.md +761 -631
- package/dist/agents.d.ts +5 -3
- package/dist/agents.d.ts.map +1 -1
- package/dist/agents.js +19 -8
- package/dist/agents.js.map +1 -1
- package/dist/capabilities.d.ts +1 -1
- package/dist/capabilities.js +2 -2
- package/dist/capabilities.js.map +1 -1
- package/dist/host/note.d.ts +7 -0
- package/dist/host/note.d.ts.map +1 -0
- package/dist/host/note.js +28 -0
- package/dist/host/note.js.map +1 -0
- package/dist/host/v1.d.ts +14 -0
- package/dist/host/v1.d.ts.map +1 -0
- package/dist/host/v1.js +340 -0
- package/dist/host/v1.js.map +1 -0
- package/dist/host/v2-browser-gate.d.ts +45 -0
- package/dist/host/v2-browser-gate.d.ts.map +1 -0
- package/dist/host/v2-browser-gate.js +319 -0
- package/dist/host/v2-browser-gate.js.map +1 -0
- package/dist/host/v2-capabilities.d.ts +68 -0
- package/dist/host/v2-capabilities.d.ts.map +1 -0
- package/dist/host/v2-capabilities.js +146 -0
- package/dist/host/v2-capabilities.js.map +1 -0
- package/dist/host/v2-client.d.ts +53 -0
- package/dist/host/v2-client.d.ts.map +1 -0
- package/dist/host/v2-client.js +193 -0
- package/dist/host/v2-client.js.map +1 -0
- package/dist/host/v2-events.d.ts +56 -0
- package/dist/host/v2-events.d.ts.map +1 -0
- package/dist/host/v2-events.js +158 -0
- package/dist/host/v2-events.js.map +1 -0
- package/dist/host/v2-guard.d.ts +101 -0
- package/dist/host/v2-guard.d.ts.map +1 -0
- package/dist/host/v2-guard.js +199 -0
- package/dist/host/v2-guard.js.map +1 -0
- package/dist/host/v2-offload.d.ts +148 -0
- package/dist/host/v2-offload.d.ts.map +1 -0
- package/dist/host/v2-offload.js +288 -0
- package/dist/host/v2-offload.js.map +1 -0
- package/dist/host/v2-permissions.d.ts +123 -0
- package/dist/host/v2-permissions.d.ts.map +1 -0
- package/dist/host/v2-permissions.js +190 -0
- package/dist/host/v2-permissions.js.map +1 -0
- package/dist/host/v2-probe.d.ts +85 -0
- package/dist/host/v2-probe.d.ts.map +1 -0
- package/dist/host/v2-probe.js +268 -0
- package/dist/host/v2-probe.js.map +1 -0
- package/dist/host/v2-scope.d.ts +51 -0
- package/dist/host/v2-scope.d.ts.map +1 -0
- package/dist/host/v2-scope.js +58 -0
- package/dist/host/v2-scope.js.map +1 -0
- package/dist/host/v2-session-client.d.ts +74 -0
- package/dist/host/v2-session-client.d.ts.map +1 -0
- package/dist/host/v2-session-client.js +171 -0
- package/dist/host/v2-session-client.js.map +1 -0
- package/dist/host/v2-session.d.ts +85 -0
- package/dist/host/v2-session.d.ts.map +1 -0
- package/dist/host/v2-session.js +171 -0
- package/dist/host/v2-session.js.map +1 -0
- package/dist/host/v2-subagent.d.ts +157 -0
- package/dist/host/v2-subagent.d.ts.map +1 -0
- package/dist/host/v2-subagent.js +286 -0
- package/dist/host/v2-subagent.js.map +1 -0
- package/dist/host/v2-tool.d.ts +67 -0
- package/dist/host/v2-tool.d.ts.map +1 -0
- package/dist/host/v2-tool.js +209 -0
- package/dist/host/v2-tool.js.map +1 -0
- package/dist/host/v2-transcript.d.ts +73 -0
- package/dist/host/v2-transcript.d.ts.map +1 -0
- package/dist/host/v2-transcript.js +151 -0
- package/dist/host/v2-transcript.js.map +1 -0
- package/dist/host/v2-types.d.ts +229 -0
- package/dist/host/v2-types.d.ts.map +1 -0
- package/dist/host/v2-types.js +17 -0
- package/dist/host/v2-types.js.map +1 -0
- package/dist/host/v2.d.ts +42 -0
- package/dist/host/v2.d.ts.map +1 -0
- package/dist/host/v2.js +706 -0
- package/dist/host/v2.js.map +1 -0
- package/dist/host-hooks.d.ts +13 -2
- package/dist/host-hooks.d.ts.map +1 -1
- package/dist/host-hooks.js +18 -5
- package/dist/host-hooks.js.map +1 -1
- package/dist/index.d.ts +23 -17
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +29 -326
- 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 +452 -359
- 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 +89 -17
- 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 +44 -10
- package/dist/prompts/specialists.js.map +1 -1
- package/dist/task-offload.d.ts +83 -0
- package/dist/task-offload.d.ts.map +1 -0
- package/dist/task-offload.js +174 -0
- package/dist/task-offload.js.map +1 -0
- package/dist/tm/args-schema.d.ts +4 -0
- package/dist/tm/args-schema.d.ts.map +1 -1
- package/dist/tm/args-schema.js +36 -3
- package/dist/tm/args-schema.js.map +1 -1
- package/dist/tm/board.d.ts +113 -0
- package/dist/tm/board.d.ts.map +1 -0
- package/dist/tm/board.js +275 -0
- package/dist/tm/board.js.map +1 -0
- package/dist/tm/browser.d.ts +325 -6
- package/dist/tm/browser.d.ts.map +1 -1
- package/dist/tm/browser.js +1360 -135
- package/dist/tm/browser.js.map +1 -1
- package/dist/tm/config.d.ts +36 -25
- package/dist/tm/config.d.ts.map +1 -1
- package/dist/tm/config.js +29 -7
- package/dist/tm/config.js.map +1 -1
- package/dist/tm/dispatch.d.ts +133 -53
- package/dist/tm/dispatch.d.ts.map +1 -1
- package/dist/tm/dispatch.js +481 -279
- package/dist/tm/dispatch.js.map +1 -1
- package/dist/tm/dupe-guard.d.ts +16 -3
- package/dist/tm/dupe-guard.d.ts.map +1 -1
- package/dist/tm/dupe-guard.js +27 -7
- package/dist/tm/dupe-guard.js.map +1 -1
- package/dist/tm/egress.d.ts +44 -0
- package/dist/tm/egress.d.ts.map +1 -0
- package/dist/tm/egress.js +191 -0
- package/dist/tm/egress.js.map +1 -0
- package/dist/tm/guard.js +1 -1
- package/dist/tm/guard.js.map +1 -1
- package/dist/tm/index.d.ts +67 -2
- package/dist/tm/index.d.ts.map +1 -1
- package/dist/tm/index.js +228 -20
- package/dist/tm/index.js.map +1 -1
- package/dist/tm/ledger.d.ts +114 -0
- package/dist/tm/ledger.d.ts.map +1 -0
- package/dist/tm/ledger.js +308 -0
- package/dist/tm/ledger.js.map +1 -0
- package/dist/tm/perm-ask.d.ts +45 -5
- package/dist/tm/perm-ask.d.ts.map +1 -1
- package/dist/tm/perm-ask.js +107 -7
- package/dist/tm/perm-ask.js.map +1 -1
- package/dist/tm/pipelines.d.ts.map +1 -1
- package/dist/tm/pipelines.js +24 -2
- package/dist/tm/pipelines.js.map +1 -1
- package/dist/tm/preview.d.ts +55 -0
- package/dist/tm/preview.d.ts.map +1 -1
- package/dist/tm/preview.js +225 -0
- package/dist/tm/preview.js.map +1 -1
- package/dist/tm/ptc/tool.d.ts.map +1 -1
- package/dist/tm/ptc/tool.js +2 -4
- package/dist/tm/ptc/tool.js.map +1 -1
- package/dist/tm/pty.d.ts.map +1 -1
- package/dist/tm/pty.js +6 -3
- package/dist/tm/pty.js.map +1 -1
- package/dist/tm/search.d.ts +5 -0
- package/dist/tm/search.d.ts.map +1 -1
- package/dist/tm/search.js +40 -8
- package/dist/tm/search.js.map +1 -1
- package/dist/tm/serp-loop.d.ts +45 -0
- package/dist/tm/serp-loop.d.ts.map +1 -0
- package/dist/tm/serp-loop.js +80 -0
- package/dist/tm/serp-loop.js.map +1 -0
- package/dist/tm/stats.d.ts +26 -0
- package/dist/tm/stats.d.ts.map +1 -1
- package/dist/tm/stats.js +194 -4
- package/dist/tm/stats.js.map +1 -1
- package/dist/tm/webfetch.d.ts +56 -0
- package/dist/tm/webfetch.d.ts.map +1 -1
- package/dist/tm/webfetch.js +187 -19
- package/dist/tm/webfetch.js.map +1 -1
- package/dist/tool-coerce.d.ts +24 -0
- package/dist/tool-coerce.d.ts.map +1 -0
- package/dist/tool-coerce.js +63 -0
- package/dist/tool-coerce.js.map +1 -0
- package/dist/types.d.ts +7 -1
- package/dist/types.d.ts.map +1 -1
- package/docs/installation-v2.md +436 -0
- package/docs/installation.md +81 -2
- package/docs/research/agent-data-exchange.md +117 -0
- package/docs/research/browser-pane.md +89 -0
- package/docs/research/host-http-api.md +132 -0
- package/docs/research/host-subagent-injection.md +187 -0
- package/docs/research/plugin-loader-contract.md +261 -0
- package/docs/research/v2-builtin-tools.md +447 -0
- package/docs/v2-port-notes.md +315 -0
- package/index.js +7 -0
- package/package.json +4 -1
- package/scripts/gen-v2-config.mjs +304 -0
- package/scripts/lib/config-surgery.cjs +239 -0
- package/scripts/lib/fake-ctx.mjs +146 -0
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
# The v2 port, in full — evidence, history and the reasoning behind each rule
|
|
2
|
+
|
|
3
|
+
AGENTS.md keeps the **rules** and points here for the **evidence and the story**: what was
|
|
4
|
+
measured, on which host build, which claim was later falsified and by what, and why a
|
|
5
|
+
decision was taken the way it was. This split exists because AGENTS.md is loaded into every
|
|
6
|
+
session — prose there is paid for per request — while this file costs nothing until someone
|
|
7
|
+
reads it, which is the same rule the prompts follow (see "subtraction philosophy" in
|
|
8
|
+
AGENTS.md).
|
|
9
|
+
|
|
10
|
+
Nothing was rewritten when it moved here: the section below is byte-exact from AGENTS.md as
|
|
11
|
+
of 2026-09-26. New findings append to the section that owns them, with a date and the
|
|
12
|
+
evidence tag (`[B]` binary read, `[L]` live host, `[R]` our own measured code, `[D]` docs).
|
|
13
|
+
Related: `docs/research/plugin-loader-contract.md` (how the host loads a plugin),
|
|
14
|
+
`docs/research/host-subagent-injection.md` (the background child's three moments),
|
|
15
|
+
`docs/research/v2-builtin-tools.md`, `docs/research/browser-pane.md`,
|
|
16
|
+
`docs/research/agent-data-exchange.md`, and `docs/installation-v2.md` for the install flow.
|
|
17
|
+
|
|
18
|
+
## The v2 delta (2026-09-25, in force)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
The goals above were written against OpenCode 1.18.x. These are the places where
|
|
22
|
+
the 2.x port changes the business claim rather than the implementation — read
|
|
23
|
+
this before treating any sentence above as platform-neutral.
|
|
24
|
+
|
|
25
|
+
- **Everything the plugin changes is Team-scoped (user requirement, 2026-09-25).**
|
|
26
|
+
Every v2 hook fires for EVERY session on the host, so `src/host/v2-scope.ts` is the
|
|
27
|
+
one answer each layer asks before it writes: the per-role tool trim, `temperature`
|
|
28
|
+
0.2, the board note, the compaction survival list, the native-result offload, the
|
|
29
|
+
R6/egress permission strictening, and the forced `background: true` all no-op for
|
|
30
|
+
`build`, `plan`, or any agent that is not one of our six — those modes must look
|
|
31
|
+
exactly like a freshly installed OpenCode. A session also becomes "ours" when one of
|
|
32
|
+
our own tools serves it (`bindV2Tool`'s `onCall` → `learn`), because the host does not
|
|
33
|
+
promise `agent` on every event. The third verdict, `unknown`, is deliberately NOT
|
|
34
|
+
treated as ours: isolation outranks coverage, so an unresolved call is left completely
|
|
35
|
+
alone — and it is COUNTED (`scope_unknown`) and printed by `tm_stats` with the sentence
|
|
36
|
+
"没资格治理", because a governance layer that silently stopped applying after a host
|
|
37
|
+
upgrade is the overstated claim this product exists to refuse. The consequence is said
|
|
38
|
+
out loud in the boot notes rather than hidden: outside Team the red lines we inject
|
|
39
|
+
(metadata/private addresses, R6 classification) do NOT apply either — that floor is the
|
|
40
|
+
host's or the user's own config's job. (v1 cannot be scoped this way: its `config` hook
|
|
41
|
+
and bash escalation sit in the FROZEN personality.)
|
|
42
|
+
- **Goal 1's injection is gone on v2.** `AgentEditor` has no `add`, and
|
|
43
|
+
`ctx.command.transform.add` was measured NOT to reach the UI, so the six roles
|
|
44
|
+
and the six `/team-*` commands are config files —
|
|
45
|
+
`~/.config/opencode/agents/*.md` and `commands/*.md`, written by the installer
|
|
46
|
+
and projected from `dist/` by `scripts/gen-v2-config.mjs` — never hand-copied,
|
|
47
|
+
or the prompts drift. The
|
|
48
|
+
plugin still owns the permission matrix (it merges its triples onto whatever
|
|
49
|
+
the config declares) and still ATTEMPTS the Team-default promotion on every boot
|
|
50
|
+
-- but `editor.default("team")` is measurably a no-op against a live host (see
|
|
51
|
+
the v2-default-agent gap below), so the requirement is satisfied by the
|
|
52
|
+
INSTALLER writing `default_agent: "team"`, not by this call.
|
|
53
|
+
- **Goal 5's "ask the user" does not exist on v2.** A plugin cannot raise the
|
|
54
|
+
host's official dialog (live-probed: declaring `options.permission` on our own
|
|
55
|
+
tool triggered no evaluation and no prompt). Governed calls therefore **fail
|
|
56
|
+
closed** with a v2-worded refusal instead of asking, and the only dialog left
|
|
57
|
+
is one the host raises on its own (`effect:"ask"` from a permission rule or
|
|
58
|
+
`permission.hook("evaluate")`). Anything that reads the v1 ask path as a model
|
|
59
|
+
for v2 is wrong.
|
|
60
|
+
- **Goal 4 is now the hard floor, and it has to cover the host's own tools.**
|
|
61
|
+
`tool.hook("execute.after")` takes a mutable `result` (docs example reassigns
|
|
62
|
+
it; a live probe confirmed the write lands), so offload + ≤80-token preview +
|
|
63
|
+
HMAC handle must extend to native `read`/`grep`/`shell`/`webfetch`. A promise
|
|
64
|
+
that only holds while the model happens to pick `tm_*` is not a promise.
|
|
65
|
+
**Both halves are now in:** `src/host/v2-session.ts` removes every tool a role
|
|
66
|
+
is denied from the assembled request (and the whole `browser_*` catalog where
|
|
67
|
+
`tm_browser` is denied), restores the 0.2 temperature, carries the resolved
|
|
68
|
+
board root to the lead, and starts the blackboard sweeper v2 silently lacked;
|
|
69
|
+
`src/host/v2-offload.ts` governs the NATIVE results through
|
|
70
|
+
`tool.hook("execute.after")` — measured on a live host: native `shell` 12 902
|
|
71
|
+
tokens arriving as a 78-token preview, `execute` 15 000 as 58.
|
|
72
|
+
**The governed name list is now every tool a Team role can call** (user
|
|
73
|
+
requirement #2: coverage may not depend on which tool the model picked):
|
|
74
|
+
`read grep glob shell bash webfetch websearch execute edit write patch question
|
|
75
|
+
subagent` plus the whole `browser_*` namespace matched BY PREFIX, because the host
|
|
76
|
+
has 45 of them and a 45-name list goes stale on the 46th without notice. Safety did
|
|
77
|
+
not move with the names — the decision to rewrite is per payload (an unrecognised
|
|
78
|
+
shape is left byte-exact), which is what the closed name list was really protecting.
|
|
79
|
+
And coverage is now a NUMBER: the report separates `ours` (every call resolved to a
|
|
80
|
+
Team session), `seen` (on the governed surface), `unmatched` (ours, but a tool we do
|
|
81
|
+
not touch) and `offloaded`; `tm_stats` prints them, so "most calls are JIT-governed"
|
|
82
|
+
can be checked and can also admit what it missed.
|
|
83
|
+
- **Done (1.7.0 line): `tm_read` / `tm_grep` / `tm_bash` retired on v2 only.**
|
|
84
|
+
The ordering AGENTS.md demanded held: governance moved down FIRST
|
|
85
|
+
(`permission.hook("evaluate")` for the address + R6 red lines,
|
|
86
|
+
`execute.after` for the offload), and only then did the aliases go. Where the
|
|
87
|
+
third piece of v1's P2 went: the host has its own `external_directory`
|
|
88
|
+
permission action, observed live answering `effect:"ask"` with a real
|
|
89
|
+
`permission.asked` behind it — so the native ladder is scoped by a dialog
|
|
90
|
+
rather than by our hard throw, which is stricter, not looser. The retirement
|
|
91
|
+
has TWO halves and both are pinned by `test-v2-adapter` group 3: the trio is
|
|
92
|
+
not registered and gets no permission triples (`V1_ONLY_TOOLS`, now also the
|
|
93
|
+
`mergeTriples` reclaim set, so a stale `tm_read` rule from an earlier boot is
|
|
94
|
+
collected), AND v1's matrix denies native `read`/grep/glob for every role —
|
|
95
|
+
projecting those denies would have left a v2 role with NO file access at all,
|
|
96
|
+
which is why `V2_LADDER_ACTIONS` exempts them in both the triple translation
|
|
97
|
+
and `toolsToRemove()`. v1 keeps all three because it cannot rewrite a tool
|
|
98
|
+
result at all. The two personalities now ship different tool surfaces, and
|
|
99
|
+
the prompt fork is real: `V2_TEXT` in `gen-v2-config.mjs` rewrites every
|
|
100
|
+
sentence that named the trio or called the shell `bash` (a key that stops
|
|
101
|
+
matching throws the build), which is why the v2 config files say `read` /
|
|
102
|
+
`grep` / `shell`.
|
|
103
|
+
- **The bloat number, and what it is NOT.** Summing the thirteen `tm_*`
|
|
104
|
+
definitions through our own `estimateTokens` gives **9 528 tokens**
|
|
105
|
+
(descriptions 6 639 + schemas 2 889) — that is the size of OUR definition set,
|
|
106
|
+
measured offline against `dist/`. **It is not a per-request cost, and a live
|
|
107
|
+
2.0.16 session says so:** the `team` agent's assembled request carried
|
|
108
|
+
**6 tools** (`edit`/`question`/`shell`/`subagent`/`write`/`execute`) and
|
|
109
|
+
**zero `tm_*`**, while the model's 61 calls in that session went to
|
|
110
|
+
`shell`(42)/`execute`(14)/`edit`(4)/`write`(1). So either plugin tools are
|
|
111
|
+
delivered only through the Code Mode catalog (the way `browser_*` is) or the v2
|
|
112
|
+
**RESOLVED (2026-09-25, read out of the 2.0.16 binary): the catalog explanation is
|
|
113
|
+
the right one, and it is our own doing.** Visibility is decided by
|
|
114
|
+
`options.codemode` — a tool whose value is not `false` is offered ONLY inside the
|
|
115
|
+
Code Mode catalog, where the host keeps `≤120 chars` of the description's FIRST
|
|
116
|
+
line under a ~2 000-token budget. `bindV2Tool` never sent `options`, so every
|
|
117
|
+
`tm_*` we register was catalog-only from the start: the tools WERE live (callable
|
|
118
|
+
from inside `execute`), and almost none of the governance text written for them
|
|
119
|
+
ever reached the model. `TM_V2_CODEMODE=direct` now sends the flag, and the cost is
|
|
120
|
+
MEASURED per role through our own `estimateTokens` (definitions + schemas, after
|
|
121
|
+
the request-layer trim): architect / implementer / reviewer **2 660** tokens,
|
|
122
|
+
tester **5 448**, researcher **7 113**, team **8 515** — i.e. direct delivery is
|
|
123
|
+
affordable for the three build-class roles and expensive for the two network roles
|
|
124
|
+
plus the lead, where `tm_browser` alone contributes 2 788. **DECIDED (user,
|
|
125
|
+
2026-09-25): slim first, then default to direct.** `tm_browser`'s description went
|
|
126
|
+
from a narrative of every lesson to what the model cannot recover from a result —
|
|
127
|
+
729 tokens (its parameter table is the other 1 337, and those descriptors are
|
|
128
|
+
per-parameter truth, not prose), and with `tm_pty` retired too the measured cost is
|
|
129
|
+
now **2 660 for architect/implementer/reviewer, 4 726 tester, 6 391 researcher,
|
|
130
|
+
7 366 lead**, which is what `direct` was supposed to buy per request. **FALSIFIED
|
|
131
|
+
2026-09-26 by the user's own desktop session**: with `options.codemode:false` sent for
|
|
132
|
+
all ten tools, 2.0.16 still delivered every `tm_*` inside the host's Code Mode catalog
|
|
133
|
+
("They cannot be called directly…"), and the model's own callable list was the nine
|
|
134
|
+
native tools. So the flag changes what WE send and nothing else, the default now sends
|
|
135
|
+
nothing (`TM_V2_CODEMODE=direct` is an opt-in experiment for a build that honours it),
|
|
136
|
+
and the outcome is an OBSERVATION — `tools_in_request`, derived from whether a `tm_*`
|
|
137
|
+
name ever appears in an assembled request's own tool map — printed by `tm_stats` beside
|
|
138
|
+
the sent flag. The token figures above stand as measurements of OUR definition set;
|
|
139
|
+
what turned out to be fiction was the claim that the model receives them.
|
|
140
|
+
The history did not disappear — it stayed in AGENTS.md and CHANGELOG.md
|
|
141
|
+
where it costs nothing per request, which is the rule this repo applies to prompts
|
|
142
|
+
generally (see the subtraction philosophy).
|
|
143
|
+
What the live session DID confirm: the per-role trim works (the native
|
|
144
|
+
read/grep/glob/list/webfetch/websearch/skill/patch the matrix denies were
|
|
145
|
+
gone from the request).
|
|
146
|
+
- **Goal 4 gains a third outcome besides "in context" and "offloaded": CAPPED.** A
|
|
147
|
+
live session caught the generic offload eating a report: a `tm_stats` answer called
|
|
148
|
+
through Code Mode arrived as the `execute` result at 2 917 tokens and came back as an
|
|
149
|
+
80-token preview, so the lead spent the round paging a table back in and the user got
|
|
150
|
+
prose about numbers. Two payloads are structurally unreadable once previewed — a
|
|
151
|
+
browser snapshot (its `[ref=…]` lines ARE the next click's arguments) and a report
|
|
152
|
+
(a table with a row missing is not a smaller table, it is a broken one) — so
|
|
153
|
+
`capKeepingAddressing()` and `capKeepingTables()` keep the structural lines and drop
|
|
154
|
+
the prose instead, up to `budget × 4`, and say how much did not fit. `reportCapped` /
|
|
155
|
+
`capped` / `offloaded` are three separate counters in `tm_stats`, because they answer
|
|
156
|
+
three different questions about what entered the context.
|
|
157
|
+
- **The two personalities now ship different tool surfaces, and the prompt fork
|
|
158
|
+
has a home.** `V1_ONLY_TOOLS` (in `v2-permissions.ts`) is the single source for
|
|
159
|
+
what v2 does not register; `v2.ts` skips those when registering and
|
|
160
|
+
`gen-v2-config.mjs` drops their permission triples, because an `allow` for an
|
|
161
|
+
action the host never heard of claims a capability that does not exist. The
|
|
162
|
+
sentences that name them are rewritten by the `V2_TEXT` table in the same
|
|
163
|
+
generator -- exact strings, and **it throws if a key stops matching**, so
|
|
164
|
+
editing a prompt without updating the table fails the build instead of
|
|
165
|
+
silently shipping a v2 model a rule about a tool it cannot call. The lead's delegation
|
|
166
|
+
section is the SECOND instance of that bug class and is forked the same way: on v2
|
|
167
|
+
the host's tool is `subagent` (measured -- `execute.before {tool:"subagent"}` and
|
|
168
|
+
`permission.evaluate {action:"subagent"}`), background needs no operator flag
|
|
169
|
+
because the plugin forces it, so both the "pick a synchronous `task`" rule and the
|
|
170
|
+
`OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS=true` sentence are v1 text that would
|
|
171
|
+
have a v2 lead checking a switch that does not exist. This is the
|
|
172
|
+
mechanism the `tm_read`/`tm_grep`/`tm_bash` retirement (done, 1.7.0 line) and
|
|
173
|
+
the v2-only `tm_ledger` (also done — the generator throws on a key that stops
|
|
174
|
+
matching, which is what keeps a v2 prompt from naming a tool v2 does not have).
|
|
175
|
+
- **`tm_browser` is probe-gated, not decided.** The host ships 45 `browser_*`
|
|
176
|
+
tools and a `browser` deny rule with `resource:"*"` removes the whole catalog,
|
|
177
|
+
so the shape worth testing is "thin door over the host's own panel" via
|
|
178
|
+
`ctx.rpc("experimental.browser")` — whether a plugin can drive that
|
|
179
|
+
user-visible panel has never been verified live. If it cannot, playwright-core
|
|
180
|
+
stays and the tax is real. What is already known as lost either way: no
|
|
181
|
+
subresource policy on the native side, no headless ("screenshots require a
|
|
182
|
+
focused visible tab"), and no per-target consent.
|
|
183
|
+
- **`tm_search` survives because native `websearch` needs a key.** All four
|
|
184
|
+
providers (Exa / Firecrawl / Parallel / Tavily) require an API key or
|
|
185
|
+
`/connect`, while the governed front's whole premise is zero-key and
|
|
186
|
+
CN-reachable. **The hole this section recorded is now closed:** native
|
|
187
|
+
`webfetch` had no egress red line, so a Build-class agent could fetch
|
|
188
|
+
`169.254.169.254` past `checkWebUrl` -- `src/host/v2-guard.ts` now asks the
|
|
189
|
+
address question at `permission.hook("evaluate")` regardless of R6 (metadata /
|
|
190
|
+
link-local / reserved = deny with no consent path, IPv4-mapped and DNS64
|
|
191
|
+
carriers unwrapped first, private = the host's own ask, env-file URL = deny),
|
|
192
|
+
and it only ever gets STRICTER than whatever the host or a user rule already
|
|
193
|
+
chose. The same hook carries R6's per-command classification, and it is now IN
|
|
194
|
+
CHARGE: a live `--standalone` run recorded `{action:"shell", resourceCount:1}`
|
|
195
|
+
reaching `permission.evaluate` for a real `git status --short`, which is the
|
|
196
|
+
proof the coarse config-level `shell -> ask` was being kept for. So the blanket
|
|
197
|
+
escalation is the FALLBACK, reached either by `TM_R6_FINE_ASK=off` or by the host
|
|
198
|
+
not exposing `permission.hook` at all — and the boot note names WHICH of the two
|
|
199
|
+
applies, because two causes sharing one message is how a fallback gets mistaken
|
|
200
|
+
for a setting. The hook keeps counting what it sees (`shellMatched`) so the
|
|
201
|
+
classifier's own hit rate stays measurable rather than assumed.
|
|
202
|
+
- **What the Team agent is actually OFFERED on v2 (measured, not documented).**
|
|
203
|
+
Six tools in the request: `edit execute question shell subagent write`. That list
|
|
204
|
+
is the ground truth for three otherwise-guessed things. ① `read`, `grep`, `glob`,
|
|
205
|
+
`webfetch`, `websearch`, `skill`, `patch` are absent **because our own matrix
|
|
206
|
+
denies them and the request layer deletes denied tools** — the host has those
|
|
207
|
+
actions (it names them in `permission.evaluate` and in our triples), so retiring
|
|
208
|
+
`tm_read`/`tm_grep` is a permission flip plus governance, not a search for a
|
|
209
|
+
missing tool; and inside `execute` (Code Mode) the native names genuinely do not
|
|
210
|
+
exist — `tools.read`/`tools.shell`/`tools.webfetch` return `Unknown tool` while
|
|
211
|
+
`typeof` reports `"function"` for all three, so a probe that trusts `typeof` gets
|
|
212
|
+
a false positive. ② `todowrite` is gone, and the LEDGER rule now has a home anyway:
|
|
213
|
+
`tm_ledger` keeps the lead's list in the host's own `ctx.storage`, and
|
|
214
|
+
`tm_join`'s goal tripwire reads it (an empty list answers `ledger_empty`, never a
|
|
215
|
+
silent pass).
|
|
216
|
+
Measured facts about that domain (live 2.0.16 probe, `docs/research/agent-data-exchange.md`): it is shared across sessions, agents AND projects — the only namespace is the plugin id — which is why the session id is part of the key and a call without one is refused; and it has no TTL and no quota, which is why `LEDGER_MAX_ITEMS` (default 200, `TM_LEDGER_MAX_ITEMS`) REFUSES rather than truncating — silently dropping the oldest asks would be the same overstated claim wearing a new uniform. ③ `execute.after` sees a native `shell` result with keys
|
|
217
|
+
`content / metadata / output`, which is the precondition for putting JIT offload
|
|
218
|
+
over the native tools (#5) — without that observation the retirement of
|
|
219
|
+
`tm_bash` would have been an assumption.
|
|
220
|
+
- **Goal 5 is REPLACED on v2: no domain gate at all, address red line only.** The
|
|
221
|
+
user's instruction (2026-09-25) is that nothing on the network may be blocked
|
|
222
|
+
except sensitive and internal addresses. On v1 the 22-host seed list was livable
|
|
223
|
+
because the plugin could raise the host's official per-request dialog; on v2 it
|
|
224
|
+
cannot raise one, so an allowlist became a set of pages nobody can approve -- a
|
|
225
|
+
gate with no door. v2 therefore resolves its tool config from a COPY of the env
|
|
226
|
+
with `TM_WEBFETCH_ALLOWED_DOMAINS` defaulting to `"*"` (an explicit user value
|
|
227
|
+
always wins; `createTmTools` gained an `env` option precisely so v1 keeps its
|
|
228
|
+
shipped default in the same process). What still holds, hardest: `checkWebUrl`'s
|
|
229
|
+
address policy -- link-local/metadata/reserved ranges are a deny no config can
|
|
230
|
+
open, and private space (loopback, RFC1918, CGNAT, `.localhost`) stays refused
|
|
231
|
+
through our tools, which have no dialog to ask with. Verified against the built
|
|
232
|
+
runtime: `https://example.com` (in no seed) returns the page, while
|
|
233
|
+
`169.254.169.254`, `192.168.1.1` and `localhost:3000` are refused before any
|
|
234
|
+
request. The refusal wording is pinned too: a v2 refusal may not promise "只能逐次
|
|
235
|
+
经用户批准" without also saying the host cannot open that dialog.
|
|
236
|
+
**What this does NOT buy: per-target visibility inside the native browser.** The
|
|
237
|
+
Team's direct surface is six tools (`edit execute question shell subagent write`),
|
|
238
|
+
so `tools.browser.*` output reaches context ONLY as the aggregate return of one
|
|
239
|
+
Code Mode program -- which is why `execute` is on the governed-tool list:
|
|
240
|
+
measured live, a 15,000-token program return arrived as `offloaded:true /
|
|
241
|
+
preview_tokens:58` + a `tm_fetch` handle. Governing the native browser means
|
|
242
|
+
governing that door.
|
|
243
|
+
- **Goal 6 gains one more instance.** `inputSchemaFor` now reports `source`,
|
|
244
|
+
because a table *derived by regex from a descriptor string* and a table
|
|
245
|
+
*translated by zod* are both `exact` but are not the same claim — the boot log
|
|
246
|
+
names which tools got which.
|
|
247
|
+
- **The "all agents 0.2" invariant cannot live in agent config on v2**
|
|
248
|
+
(`temperature` is a documented legacy field and the runner "preserves these
|
|
249
|
+
values but does not yet send them with model requests"); it must go through
|
|
250
|
+
`session.hook("context").options`. Background subagents, by contrast, are
|
|
251
|
+
native on v2 (`subagent {background:true}`, default nesting depth one = T3's
|
|
252
|
+
"only the lead dispatches" for free), so the
|
|
253
|
+
`OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS` env hack in the installers is
|
|
254
|
+
v1-only and must fork rather than delete.
|
|
255
|
+
- **The installer's v2 job is TWO things, and the plugin half of it is one config
|
|
256
|
+
key.** Reverse-engineered from the host binary and confirmed on live 2.0.16/2.0.18
|
|
257
|
+
(`docs/research/plugin-loader-contract.md` is the evidence page): the key is
|
|
258
|
+
**`plugins` (plural)** — a 2.x host never reads the 1.18.x singular `plugin` key, which
|
|
259
|
+
is why an install that wrote there looked complete and did nothing; an entry is
|
|
260
|
+
**installed by the host itself at startup** (`Bun.add` inside `PluginModule.load`, then
|
|
261
|
+
`msg="loading plugin" id=… entrypoint=file:///…/.cache/opencode/npm/<pkg>@latest/<ts>/
|
|
262
|
+
node_modules/<pkg>/dist/index.js`), so `opencode plugin add` is a convenience that
|
|
263
|
+
writes the same key and v1's cache-purge / npm-re-resolve machinery is obsolete; a
|
|
264
|
+
**directory** target resolves only as `<dir>/index` (hence the root `index.js` this
|
|
265
|
+
package ships) and an unresolvable one is dropped with **no message at all**, so the
|
|
266
|
+
only proof of an install is that log line or our own `v2-boot` row; and
|
|
267
|
+
`plugin add` **refuses a path** ("Plugin target must be an npm registry package or Git
|
|
268
|
+
package specifier"), which is why local mode is a config edit. The second measured fact
|
|
269
|
+
is the one that bites: `opencode.json` and `opencode.jsonc` are **both parsed and
|
|
270
|
+
merged** while the host's dedupe matches only an identical string, so the same plugin
|
|
271
|
+
under two spellings or in both files **loads twice** (two personalities, the same hooks
|
|
272
|
+
bound twice) — which is exactly what the old 1.18.x "migrate `.json` forward to `.jsonc`"
|
|
273
|
+
rule produced, so that migration is gone and the installers now write one entry into
|
|
274
|
+
the `.jsonc` (user requirement: keep the file previous installs used) and RECLAIM their
|
|
275
|
+
own entry from the legacy `.json`. Both front-ends call one
|
|
276
|
+
`scripts/lib/config-surgery.cjs` so they cannot drift, and it matches Team entries
|
|
277
|
+
case- and hyphen-insensitively because a working tree is `Opencode-TeamMode`. What
|
|
278
|
+
nothing else can do remains: write `agents/*.md`, `commands/*.md`, and `default_agent`
|
|
279
|
+
(which must come last — a default naming a missing agent makes the host fall back to
|
|
280
|
+
`build` silently).
|
|
281
|
+
- **The host's own background children are collectable (`babfcb8`).** On v2 every
|
|
282
|
+
dispatch IS the host's `subagent` tool, and `tm_join` had no record of them, so it
|
|
283
|
+
answered 没有待收集的派发 about work the user could watch on screen and a bounded wait
|
|
284
|
+
could never end. `src/host/v2-subagent.ts` pairs the two seams the user's own exported
|
|
285
|
+
session shows (`docs/research/host-subagent-injection.md`): `execute.before` carries
|
|
286
|
+
`{agent, background, description}` and `execute.after` carries
|
|
287
|
+
`result.metadata.sessionID` + `status:"running"`, with the ack sentence as a second
|
|
288
|
+
source since no field shape survives an upgrade unchanged. Two honesty rules come with
|
|
289
|
+
it: the child's BODY is the host's injected message, which v2 never hands a plugin
|
|
290
|
+
before persisting, so a host child with nothing readable says where the text actually
|
|
291
|
+
arrives instead of printing a reply we never read; and settle provenance is printed —
|
|
292
|
+
the child's own `session.idle` is `event`, while "its parent went idle so the host has
|
|
293
|
+
collected it" is labelled 推定 (the timestamps prove the ordering, once). A synchronous
|
|
294
|
+
child has no id and registers nothing, which is the correct answer, not a failure.
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
### 2026-09-26 (later the same day) — that BODY sentence was our decode miss `[L]`
|
|
298
|
+
|
|
299
|
+
The paragraph above says the child's body is something v2 never hands a plugin. It is true of
|
|
300
|
+
the INJECTION (there is still no v2 equivalent of `chat.message`, so the message cannot be
|
|
301
|
+
rewritten before it persists) and false of the BODY, which is readable: `ctx.session.context
|
|
302
|
+
({sessionID})` answers with an array of flat `{id, time:{created}, text, type}` items, measured
|
|
303
|
+
against a real background child (`array(5)`, first item `type:"user"`). The bridge had been
|
|
304
|
+
calling that seam all along and handing the array to `lastAssistantMessage`, which returns no
|
|
305
|
+
text for a shape it does not recognise — so the call SUCCEEDED, the report was discarded, and the
|
|
306
|
+
user-visible sentence blamed the host. Flat items are normalised at the seam now
|
|
307
|
+
(`normaliseContextMessages`, no invented `time.completed`), the reply credits
|
|
308
|
+
`正文来源=ctx.session.context`, and the trajectory writes `child_body` / `child_body_missing`
|
|
309
|
+
with the seam name only.
|
|
310
|
+
|
|
311
|
+
Two adjacent findings from the same zero-token probe (`--standalone --model nope/nope`, a plugin
|
|
312
|
+
that only inspects its own ctx): the plugin ctx carries `permission.list/get/reply` and
|
|
313
|
+
`experimental.terminal.read`, all decoding `{sessionID}`; and `ctx.rpc` is a function of arity 1
|
|
314
|
+
whose convention has NOT been read. `permission.reply` remains uncalled by design — answering a
|
|
315
|
+
dialog for the user is self-allowing. Recorded in `docs/research/host-http-api.md` §4/§6.
|
package/index.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// The host resolves a plugin DIRECTORY as `<dir>/index.js` (measured on 2.0.16:
|
|
2
|
+
// `msg="loading plugin" … entrypoint=file:///…/team-mode/index.js`, while a directory that
|
|
3
|
+
// only declares package.json#exports is skipped with no error at all). This shim is
|
|
4
|
+
// therefore part of the contract, not a convenience: without it neither
|
|
5
|
+
// `plugins: ["…/opencode-team-mode"]` in a config nor a node_modules install under that
|
|
6
|
+
// key can load the plugin. The dual-personality shape itself lives in dist/index.js.
|
|
7
|
+
export { default } from "./dist/index.js"
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@te-river/opencode-team-mode",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.1",
|
|
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",
|
|
@@ -13,6 +13,9 @@
|
|
|
13
13
|
},
|
|
14
14
|
"files": [
|
|
15
15
|
"dist",
|
|
16
|
+
"index.js",
|
|
17
|
+
"scripts/lib",
|
|
18
|
+
"scripts/gen-v2-config.mjs",
|
|
16
19
|
"README.md",
|
|
17
20
|
"README.zh-CN.md",
|
|
18
21
|
"LICENSE",
|