@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,261 @@
|
|
|
1
|
+
# OpenCode 2.0.16 — how the host actually loads a plugin from `config.plugins`
|
|
2
|
+
|
|
3
|
+
**Scope:** read-only forensics. The strings below were cut out of
|
|
4
|
+
`resources/opencode-cli.exe` with a windowed byte scan; the live confirmations were
|
|
5
|
+
run against a sandboxed `OPENCODE_CONFIG_DIR` + temp store roots. Nothing in the host
|
|
6
|
+
was modified, and no path, URL, command line or env value of the user's appears here.
|
|
7
|
+
|
|
8
|
+
**Why this page exists.** The official v2 plugin docs document a `"plugins"` key
|
|
9
|
+
(a list of package names), and for a while our answer to "can the installer just write
|
|
10
|
+
that key?" was "no, it does not load". That answer was **wrong**, and the wrong answer
|
|
11
|
+
was the reason the installers carried a cache-purge/re-resolve story that v2 does not
|
|
12
|
+
need. This page is the corrected contract, with the host's own code as the evidence.
|
|
13
|
+
|
|
14
|
+
## 0. Where each claim comes from
|
|
15
|
+
|
|
16
|
+
| Tag | Source | Strength |
|
|
17
|
+
|---|---|---|
|
|
18
|
+
| `[B]` | Verbatim from the embedded Effect/JS in `resources/opencode-cli.exe` (`opencode-cli.version` reads `2.0.16`) | strongest offline |
|
|
19
|
+
| `[L]` | Our own live boot probe against a sandboxed config dir (`--standalone`, `--print-logs`, `v2-boot` trajectory line) | strongest overall |
|
|
20
|
+
| `[R]` | Our repo (`index.js`, `package.json#files`) | ours, verified |
|
|
21
|
+
| `[D]` | Official v2 docs | corroborating |
|
|
22
|
+
|
|
23
|
+
## 1. The entry: `config.plugins` is read, mapped, then resolved
|
|
24
|
+
|
|
25
|
+
The code blocks below are the host's own minified text **reformatted for reading**:
|
|
26
|
+
identifiers, strings and control flow are untouched, whitespace is not. The unmodified
|
|
27
|
+
bytes are in the extract window quoted per line.
|
|
28
|
+
|
|
29
|
+
`[B]` `ConfigPluginSource.scan` (minified `BN`) takes `l.info.plugins ?? []` from every
|
|
30
|
+
parsed config **document** and maps each entry through the operation builder `DN`:
|
|
31
|
+
|
|
32
|
+
```js
|
|
33
|
+
function DN(e){
|
|
34
|
+
if (typeof e !== "string") return {type:"add", target:e.package, options:e.options ?? {}}
|
|
35
|
+
if (!e.startsWith("-")) return {type:"add", target:e, options:{}}
|
|
36
|
+
if (e.length === 1) throw Error("Plugin remove operation requires a target")
|
|
37
|
+
return {type:"remove", target:e.slice(1)}
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
So an entry is either a bare string, an object `{package, options}`, or a
|
|
42
|
+
**`-<target>` string that removes** a previously-declared plugin. Then the target is
|
|
43
|
+
resolved relative to the file that declared it:
|
|
44
|
+
|
|
45
|
+
```js
|
|
46
|
+
let g = l.path ? ln.dirname(l.path) : a.directory
|
|
47
|
+
let v = c.target.startsWith("file://") ? dm(c.target)
|
|
48
|
+
: (c.target.startsWith("./") || c.target.startsWith("../"))
|
|
49
|
+
? ln.resolve(g, c.target)
|
|
50
|
+
: c.target
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
`[B]` Three branches follow, and they are the whole answer to the installer question:
|
|
54
|
+
|
|
55
|
+
1. **Not absolute** (a plain npm/git specifier) → handed to the package installer
|
|
56
|
+
(`PluginModule.load` calls `Bun.add(target)` unless `options.install === false`),
|
|
57
|
+
i.e. `"@te-river/opencode-team-mode@latest"` works exactly as documented.
|
|
58
|
+
2. **Absolute and a FILE** → `Dt("configured plugin path must be a directory", {target})`
|
|
59
|
+
(a warning) and the entry is dropped (`Ie()`).
|
|
60
|
+
3. **Absolute and a DIRECTORY**, or a relative path that resolved to one → the entrypoint
|
|
61
|
+
is resolved for the directory, and **if that resolution yields no server the entry
|
|
62
|
+
returns `[]` — no log, no warning, no error**:
|
|
63
|
+
|
|
64
|
+
```js
|
|
65
|
+
let c = yield*e.isDir(l.target)
|
|
66
|
+
let g = c ? yield*y(()=>U0({directory: l.target})) : {server: UN(l.target).href}
|
|
67
|
+
if (!g.server) return [] // <-- silent
|
|
68
|
+
if (c) { let k = resolve(l.target), f = resolve(dm(g.server))
|
|
69
|
+
if (!contains(k, f)) return [] } // entrypoint must live inside the dir
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
`[L]` This is the branch that ate our afternoon: `plugins: ["C:/…/team-mode"]` pointing at
|
|
73
|
+
a directory that declared only `package.json#exports` produced **one line of output —
|
|
74
|
+
nothing** — and a perfectly healthy-looking host with no Team tools.
|
|
75
|
+
|
|
76
|
+
## 2. What a directory must contain to be loadable
|
|
77
|
+
|
|
78
|
+
`[B]` `PluginModule.load` (minified `SB`) resolves the entrypoint and only then logs:
|
|
79
|
+
|
|
80
|
+
```js
|
|
81
|
+
// PluginModule.load (SB): `r` = target is absolute, `i.install === false` skips the
|
|
82
|
+
// package installer, `U0` is the entrypoint resolver, `rL` is the host's log call.
|
|
83
|
+
let p = r ? void 0 : (i?.install === false ? await resolve(e.target) : await add(e.target))
|
|
84
|
+
let l = r && (await stat(e.target)).isFile()
|
|
85
|
+
? {server: fileURL(e.target).href}
|
|
86
|
+
: await U0(p ?? {directory: e.target})
|
|
87
|
+
let c = l.server
|
|
88
|
+
if (!r && i?.install === false && !c) return {pending: true}
|
|
89
|
+
if (!c) return new LoadError({message: `Plugin entrypoint not found: ${e.target}`})
|
|
90
|
+
yield* log({msg: "loading plugin", id: e.target, entrypoint: c})
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
and the module must then validate as a definition:
|
|
94
|
+
|
|
95
|
+
```
|
|
96
|
+
Plugin must export a default definition with an id and an effect or setup function.
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
`[L]` For a **directory** target, the entrypoint that `U0` returns on 2.0.16 is
|
|
100
|
+
`<dir>/index.js`. Measured with `--print-logs`:
|
|
101
|
+
|
|
102
|
+
```
|
|
103
|
+
msg="loading plugin" id=…\vendor\team-mode entrypoint=file:///…/vendor/team-mode/index.js
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
A directory holding only `package.json` (`exports` → `./dist/index.js`, `main` likewise)
|
|
107
|
+
is **not** resolved to `dist/index.js` — it falls into the `if (!g.server) return []`
|
|
108
|
+
branch above and is skipped with no message at all. That is why the package now ships a
|
|
109
|
+
root [`index.js`](../../index.js) `[R]` re-exporting `./dist/index.js`, and why it is in
|
|
110
|
+
`package.json#files`: it is part of the host contract, not a convenience shim.
|
|
111
|
+
|
|
112
|
+
## 3. Consequences for the installer
|
|
113
|
+
|
|
114
|
+
- `"plugins"` **is** the right key to write, and the documented npm-specifier form works
|
|
115
|
+
without a cache purge: the host's own `opencode plugin add` installs and writes that
|
|
116
|
+
field (`[D]`+`[L]`). v1's purge machinery has nothing to do on v2.
|
|
117
|
+
- A **local** install (this repo, or a copy on disk) must point at a **directory that
|
|
118
|
+
contains `index.js`**, and the value must be relative (`"./vendor/team-mode"`, resolved
|
|
119
|
+
against the config file's own directory) or an absolute directory. An absolute path to
|
|
120
|
+
`index.js` itself is refused as "must be a directory".
|
|
121
|
+
- Whatever path style is used, the only proof of success is the host's own
|
|
122
|
+
`msg="loading plugin"` line or our `v2-boot` trajectory row — never the config write
|
|
123
|
+
returning 0. The silent `return []` has no diagnostic to fail on.
|
|
124
|
+
- Dedup matters: the same plugin reachable both as an npm cache copy and as a local
|
|
125
|
+
directory is loaded twice, and the second registration is the one that wins the hooks.
|
|
126
|
+
`PluginModule.watch` keeps a per-path set, so two *different* paths are two watchers.
|
|
127
|
+
|
|
128
|
+
## 5. The entrypoint resolver, in full (`U0`)
|
|
129
|
+
|
|
130
|
+
`[B]` Everything above funnels into one small function, and it is the reason a local
|
|
131
|
+
install has exactly one legal shape. Reformatted for reading; identifiers untouched:
|
|
132
|
+
|
|
133
|
+
```js
|
|
134
|
+
function U0(r){
|
|
135
|
+
let n = (t) => {
|
|
136
|
+
for (let o of t) {
|
|
137
|
+
let i = r.name ? [r.name, o].filter(Boolean).join("/") : s.resolve(r.directory, o || "index")
|
|
138
|
+
try { return R0(i, r.directory) } // resolve as a module specifier
|
|
139
|
+
catch (e) { if (!["ENOENT","ENOTDIR","MODULE_NOT_FOUND","ERR_MODULE_NOT_FOUND",
|
|
140
|
+
"ERR_PACKAGE_PATH_NOT_EXPORTED","ERR_UNSUPPORTED_DIR_IMPORT"]
|
|
141
|
+
.includes(String(e.code))) throw e }
|
|
142
|
+
}
|
|
143
|
+
return // nothing resolved → undefined
|
|
144
|
+
}
|
|
145
|
+
return { server: n(["server",""]), tui: n(["tui"]), rpc: n(["rpc"]) }
|
|
146
|
+
}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Read it as a decision table:
|
|
150
|
+
|
|
151
|
+
| target | what `U0` is called with | what resolves |
|
|
152
|
+
|---|---|---|
|
|
153
|
+
| an installed package | `{name:"@te-river/opencode-team-mode", directory:<cache>}` | the specifier `@te-river/opencode-team-mode/server`, then the package root — i.e. `package.json#exports`, so `dist/index.js` arrives |
|
|
154
|
+
| a directory path | `{directory:<path>}` (no `name`) | `resolve(dir, "index")` → **`<dir>/index.js`** and nothing else |
|
|
155
|
+
|
|
156
|
+
So the asymmetry is not a documentation gap, it is the code: a package may declare its
|
|
157
|
+
entrypoint, a directory may not. A directory that resolves nothing returns
|
|
158
|
+
`{server: undefined}`, and `ConfigPluginSource.scan` then drops the entry with
|
|
159
|
+
`return []` — the silent skip in §1. `[L]` Measured both ways on 2.0.18: with a root
|
|
160
|
+
`index.js` the host logs `entrypoint=file:///…/vendor/team-mode/index.js`; without it,
|
|
161
|
+
the boot is clean and the plugin is simply absent.
|
|
162
|
+
|
|
163
|
+
`features.tui` / `features.rpc` in the load result come from the same call, which is why
|
|
164
|
+
a TUI-only plugin is recorded somewhere else — see §6.
|
|
165
|
+
|
|
166
|
+
## 6. What `opencode plugin add` actually does
|
|
167
|
+
|
|
168
|
+
`[B]` The subcommand (`cli.plugin.add`) is four steps, and each one closes an option the
|
|
169
|
+
installer might otherwise reach for:
|
|
170
|
+
|
|
171
|
+
```js
|
|
172
|
+
if (!(yield* ne(()=>pP(e.package))))
|
|
173
|
+
return h(Error("Plugin target must be an npm registry package or Git package specifier"))
|
|
174
|
+
let r = yield* (yield* ga).add(e.package) // install
|
|
175
|
+
let l = U0(r), o = k(l.server, l.tui) // configurationTarget: "server" | "tui"
|
|
176
|
+
if (!o) return h(Error(`Plugin package has no server or TUI entrypoint: ${e.package}`))
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
- **A local directory is refused**, by name. Vendoring a tree is therefore a config edit,
|
|
180
|
+
never a `plugin add`.
|
|
181
|
+
- A **server** plugin is written by `writePluginConfig` into the global config the Config
|
|
182
|
+
service names (`i.config`). `[L]` Measured with a redirected `HOME`: it created and
|
|
183
|
+
edited `~/.config/opencode/opencode.json` — not the `.jsonc` — and printed the file it
|
|
184
|
+
touched.
|
|
185
|
+
- Its dedupe is `d(t, n)`: `r === n || (typeof r === "object" && r.package === n)`, i.e.
|
|
186
|
+
**the identical string only**. Two spellings of one plugin are two entries.
|
|
187
|
+
- It parses with `allowTrailingComma`, edits the AST (`Ru(doc, ["plugins"], [...t, n])`,
|
|
188
|
+
tab size 2) and swaps the file through `<file>.tmp` + rename, mode `0o600`. So a
|
|
189
|
+
trailing comma in a user's array is legal to the host — and the reason our own writer
|
|
190
|
+
must not leave one dangling is that the NEXT hand edit may not be a lenient parser.
|
|
191
|
+
- A **TUI-only** plugin goes to a different service (`Il`) with its own `plugins` array.
|
|
192
|
+
Our package has a `server` entrypoint, so it always takes the first branch.
|
|
193
|
+
|
|
194
|
+
## 7. Why two entries really do load twice
|
|
195
|
+
|
|
196
|
+
`[B]` `PluginSupervisor.resolve` iterates the operations and, before loading, asks only
|
|
197
|
+
whether the target matches an already-known plugin **id**:
|
|
198
|
+
|
|
199
|
+
```js
|
|
200
|
+
let l = (N,O) => N === "*" || (N.endsWith(".*") ? O.startsWith(N.slice(0,-1)) : N === O)
|
|
201
|
+
let O = S().filter((ee) => l(N.target, ee.id))
|
|
202
|
+
if (O.length > 0 || N.target === "*" || …) { O.forEach(ee => g.add(ee.id)); continue }
|
|
203
|
+
let Q = yield* e.load(N, { install: s }) // otherwise: load it
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
`N.target` is the config string (`@te-river/opencode-team-mode@latest`, or a path) and
|
|
207
|
+
`ee.id` is the plugin's own exported id (`team-mode`). They are never equal, so that
|
|
208
|
+
guard is a no-op for package entries, and a second operation for the same package
|
|
209
|
+
reaches `e.load` again. `[L]` Confirmed: the same spec present in `opencode.json` and
|
|
210
|
+
`opencode.jsonc` produced two `msg="loading plugin"` lines for one id, with no warning —
|
|
211
|
+
two personalities, the same tools and hooks bound twice.
|
|
212
|
+
|
|
213
|
+
A load failure is loud, unlike the directory skip: `failed to load plugin` carries
|
|
214
|
+
`target`, a `ref`, and the cause, and `PluginModule.LoadError` is what `@opencode-aide`
|
|
215
|
+
1.6.0 produces on a 2.x host —
|
|
216
|
+
|
|
217
|
+
```
|
|
218
|
+
Plugin must export a default definition with an id and an effect or setup function.
|
|
219
|
+
(cause: SchemaError(Missing key at ["default"]["effect"] / ["default"]["setup"]))
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
because the accepted shape is a union, `bB = U([ {id, effect:fn}, {id, setup:fn} ])`, and
|
|
223
|
+
a v1-only `{id, server}` default satisfies neither. That is the whole reason this package
|
|
224
|
+
ships one barrel exporting both personalities.
|
|
225
|
+
|
|
226
|
+
## 8. What the Extensions panel's row name tells you (and what it does not)
|
|
227
|
+
|
|
228
|
+
`[L]` Observed on the user's desktop, before and after a config change: a plugin loaded
|
|
229
|
+
from `~/.config/opencode/plugins/team-mode.js` (the directory-scan path the docs
|
|
230
|
+
describe) is listed as `team-mode`, which is that FILE's basename, while an npm entry is
|
|
231
|
+
listed verbatim as `@te-river/opencode-team-mode@latest`. A `plugins` entry pointing at a
|
|
232
|
+
local directory also listed itself as `team-mode` — the loaded plugin's own id.
|
|
233
|
+
|
|
234
|
+
The mechanism is inferred, the consequence is not: **the row name cannot tell you which
|
|
235
|
+
spelling is installed**, because a file named `team-mode.js` and a package whose id is
|
|
236
|
+
`team-mode` print identically. Two rows that look like one plugin may be two loads, and
|
|
237
|
+
one row may be either a loader file or a directory entry. The only channel that
|
|
238
|
+
distinguishes them is `msg="loading plugin" id=<target> entrypoint=<url>`, which names
|
|
239
|
+
the config string and the resolved file, and our own `v2-boot` trajectory line.
|
|
240
|
+
|
|
241
|
+
**How the installers use this:** `scripts/lib/config-surgery.cjs` keeps exactly one Team
|
|
242
|
+
entry across `opencode.jsonc` and `opencode.json`, matching our own entries by
|
|
243
|
+
`/opencode[-_]?team[-_]?mode|vendor[/\\]team-mode/i` — case- and hyphen-tolerant, because
|
|
244
|
+
a working tree is `D:/Github/Opencode-TeamMode` and an entry it fails to recognise is one
|
|
245
|
+
it would add beside the existing one.
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
## 9. What this page does NOT establish
|
|
249
|
+
|
|
250
|
+
`[U]` Whether an object entry's `options` carries anything beyond `install:false` — the
|
|
251
|
+
scanned code reads only that field, which is not a claim that nothing else exists.
|
|
252
|
+
`[U]` Whether the `<dir>/index` resolution survives a future host: it is read off 2.0.16
|
|
253
|
+
and 2.0.18, and the shipped `package.json#exports` stays as the belt while the root
|
|
254
|
+
`index.js` is the part this contract requires. `[U]` Which file the Config service names
|
|
255
|
+
as `i.config` when both `opencode.json` and `opencode.jsonc` exist — measured only in the
|
|
256
|
+
case where the `.jsonc` was absent, so the installers do not depend on it: they write the
|
|
257
|
+
`.jsonc` and reclaim their own entry from the `.json`. `[U]` Whether the host prunes the
|
|
258
|
+
timestamped `~/.cache/opencode/npm/<pkg>@latest/<ts>/` directories: one existed after
|
|
259
|
+
several installs on this machine, which is an observation, not a garbage-collection
|
|
260
|
+
guarantee.
|
|
261
|
+
|