@te-river/opencode-team-mode 1.7.7 → 1.7.9
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 +1010 -1010
- package/README.zh-CN.md +907 -907
- package/dist/host/v2-browser-gate.js +2 -2
- package/dist/host/v2-offload.js +6 -6
- package/dist/host/v2-retry.d.ts +24 -11
- package/dist/host/v2-retry.d.ts.map +1 -1
- package/dist/host/v2-retry.js +81 -15
- package/dist/host/v2-retry.js.map +1 -1
- package/dist/prompts/specialists.js +286 -286
- package/dist/task-offload.js +1 -1
- package/dist/tm/config-layers.d.ts +5 -2
- package/dist/tm/config-layers.d.ts.map +1 -1
- package/dist/tm/config-layers.js +9 -6
- package/dist/tm/config-layers.js.map +1 -1
- package/dist/tm/config.d.ts.map +1 -1
- package/dist/tm/config.js +7 -1
- package/dist/tm/config.js.map +1 -1
- package/dist/tm/ledger.js +5 -5
- package/dist/tm/memory.js +9 -9
- package/dist/tm/search.d.ts +3 -0
- package/dist/tm/search.d.ts.map +1 -1
- package/dist/tm/search.js +67 -15
- package/dist/tm/search.js.map +1 -1
- package/dist/tm/tools.js +3 -3
- package/dist/tm/webfetch.d.ts +94 -5
- package/dist/tm/webfetch.d.ts.map +1 -1
- package/dist/tm/webfetch.js +285 -64
- package/dist/tm/webfetch.js.map +1 -1
- package/docs/installation-v2.md +11 -6
- package/docs/installation.md +505 -505
- package/docs/research/agent-data-exchange.md +117 -117
- package/docs/research/browser-pane.md +94 -94
- package/docs/research/host-http-api.md +132 -132
- package/docs/research/host-subagent-injection.md +187 -187
- package/docs/research/plugin-loader-contract.md +261 -261
- package/docs/research/v2-builtin-tools.md +447 -447
- package/docs/v2-port-notes.md +134 -78
- package/index.js +7 -7
- package/package.json +64 -64
- package/scripts/gen-v2-config.mjs +1 -1
- package/scripts/lib/config-surgery.cjs +239 -239
|
@@ -1,132 +1,132 @@
|
|
|
1
|
-
# OpenCode 2.0.18 — the host's own HTTP API, and what it changes for a plugin
|
|
2
|
-
|
|
3
|
-
**Status: mapped, not called.** Every route below was read out of the installed host's
|
|
4
|
-
embedded JS (the SDK client factory: `method:"GET", path:"/api/…"` entries, both literal and
|
|
5
|
-
template-literal), cross-checked against the official v2 API page for the plugin endpoints. **No
|
|
6
|
-
request has been made to any of them** — so "exists in the client" is not the same claim as
|
|
7
|
-
"works from a plugin process", and nothing here is a verified capability.
|
|
8
|
-
|
|
9
|
-
**Why it matters.** The v2 plugin context is thin: no SDK client, no `session.children`, no
|
|
10
|
-
message domain, no dialog. Several conclusions this repo recorded as host limitations are
|
|
11
|
-
actually *ctx* limitations — the server behind the ctx has a much larger surface. This page
|
|
12
|
-
separates the two so a future change does not re-accept a limitation that is really a missing
|
|
13
|
-
transport.
|
|
14
|
-
|
|
15
|
-
## 1. Discovery and authentication
|
|
16
|
-
|
|
17
|
-
`[B]`+`[D]` `GET /api/info` returns "the server identity, connection URLs, paths, and
|
|
18
|
-
readiness status". The CLI reaches a server through a helper that yields `{url, password}`, and
|
|
19
|
-
the transport is HTTP Basic with a fixed username:
|
|
20
|
-
|
|
21
|
-
```js
|
|
22
|
-
{ type: "basic", username: "opencode", password: e.password }
|
|
23
|
-
await fetch(new URL("/api/info", e.url), { headers: X(i), signal: AbortSignal.timeout(r) })
|
|
24
|
-
```
|
|
25
|
-
|
|
26
|
-
`[B]` The password is **not** per-process random-only: `<global-config>/service.json` holds a
|
|
27
|
-
single `password` string (43 chars on this machine). The port is dynamic (`serve --stdio
|
|
28
|
-
--port 0`), which is why `/api/info` exists.
|
|
29
|
-
|
|
30
|
-
`[U]` Whether a plugin can obtain `{url, password}` from its own context instead of reading
|
|
31
|
-
that file is the open question, and it is the whole cost of every item below: **reading the
|
|
32
|
-
user's credential file to call back into the host is a new trust boundary**, and a plugin that
|
|
33
|
-
does it silently would be doing exactly what goal #6 forbids. Nothing in this repo does it
|
|
34
|
-
today, and `src/host/v2-session-client.ts` says so where the temptation is documented.
|
|
35
|
-
|
|
36
|
-
## 2. The routes that touch our recorded conclusions
|
|
37
|
-
|
|
38
|
-
| route family (`[B]`) | what it offers | which claim it reopens |
|
|
39
|
-
|---|---|---|
|
|
40
|
-
| `GET /api/plugin`, `POST /api/plugin/check`, `POST /api/plugin/update` | the enabled server plugins **with live status**, plus update checking | the install-verification story: this is a cleaner arrival check than grepping `msg="loading plugin"` (which is what `docs/installation-v2.md` currently tells an agent to do) |
|
|
41
|
-
| `GET /api/session/{id}/message`, `GET /api/session/{id}/message/{messageID}`, `GET /api/session/{id}/context` | read a session's messages — including a CHILD session | `tm_join`'s "the body arrives by injection, we cannot read it" limit. The in-process `ctx.session.context` is tried first (see §4); the API is the fallback with a price |
|
|
42
|
-
| `GET /api/permission/request`, `POST /api/session/{id}/permission`, `GET /api/permission/saved`, `DELETE /api/permission/saved/{id}` | the permission queue, answering it, and the saved-rule store | **"a plugin cannot raise a dialog on v2"** — that was measured against the ctx (`options.permission` on our own tool produced no evaluation). The server clearly has a dialog surface; whether a plugin may drive it is unmeasured, and driving it *for* the user would be self-allowing, which this project never does |
|
|
43
|
-
| `GET /api/pty`, `POST /api/pty`, `GET /api/pty/{id}/output?`, `GET /api/experimental/persistent-pty/{id}`, `.../snapshot`, `.../connect-token`, `POST /api/experimental/persistent-pty/{id}/connect-token` | terminal sessions with **snapshot/read**, i.e. exactly the capture capability `tm_pty` lacked | task #21 ("v2 has no `client.pty` → retire `tm_pty`") was decided on the ctx surface. The host has a persistent-pty API. Retirement still looks right (a plugin spawning terminals the user cannot see is its own problem), but the reason is now "no in-process seam + trust boundary", not "the host has no such thing" |
|
|
44
|
-
| `GET /api/fs/read/{path}`, `GET /api/fs/list`, `GET /api/fs/find`, `POST /api/experimental/fs/write` | file access through the host's own permission layer | reinforces the 1.7.x decision to retire `tm_read`/`tm_grep`/`tm_bash` in favour of the native ladder — the native path is where the host's `external_directory` dialog lives |
|
|
45
|
-
| `POST /api/websearch`, `GET /api/websearch/provider` | server-side search | all four native providers need a key/`/connect`, so `tm_search`'s zero-key front stays (unchanged conclusion, now with the reason visible) |
|
|
46
|
-
| `POST /api/session/{id}/wait`, `POST /api/experimental/session/{id}/wait`, `GET /api/experimental/session/stats`, `GET /api/experimental/session/{id}/export`, `POST /api/session/{id}/synthetic`, `.../interrupt`, `.../compact`, `.../fork` | waiting on and injecting into sessions | the shape of what a *session-owning* API looks like — useful for judging whether `tm_join`'s bounded wait is fighting the host or duplicating it |
|
|
47
|
-
| `GET /api/event` | the SSE feed | `ctx.event.subscribe()` is this stream, in-process. No reason to prefer HTTP |
|
|
48
|
-
|
|
49
|
-
## 3. The question the user actually asked: is there a todo list?
|
|
50
|
-
|
|
51
|
-
**No.** `[B]` Across **136 distinct `METHOD /path` pairs** in the scanned window (55 literal +
|
|
52
|
-
81 template-literal paths, deduplicated — a lower bound, since the scan covered 2 MB of the
|
|
53
|
-
embedded JS rather than the whole client factory) there is no `todo`, no `plan`, no `task`
|
|
54
|
-
resource; the only `message` routes are `GET /api/session/{id}/message` and
|
|
55
|
-
`GET /api/session/{id}/message/{messageID}`. `[D]` The official API page lists none either. The
|
|
56
|
-
v1 REST endpoint `GET /session/{id}/todo` that `tm_join`'s goal tripwire was written against
|
|
57
|
-
does not exist on 2.x, which is the same fact as "the host has no `todowrite` tool" seen from
|
|
58
|
-
the other side. So `tm_ledger` is not a workaround we settled for: it is the only ledger there
|
|
59
|
-
is. What *is* available for the same purpose is `GET /api/session/{id}/context` (in-process)
|
|
60
|
-
and `/message` (HTTP) — reading what the model was told, rather than a list the model
|
|
61
|
-
maintains.
|
|
62
|
-
|
|
63
|
-
## 4. What was verified instead, in-process
|
|
64
|
-
|
|
65
|
-
`[B]` The host's own call sites give the plugin-facing argument shape, and that fixed a bridge
|
|
66
|
-
that had never worked:
|
|
67
|
-
|
|
68
|
-
```
|
|
69
|
-
te = (schema, fn) => (input) => decode(schema, input ?? {}) → fn(decoded)
|
|
70
|
-
await e.session.get({ sessionID: n })
|
|
71
|
-
await e.message.list({ sessionID: n, limit: 200, cursor: r })
|
|
72
|
-
```
|
|
73
|
-
|
|
74
|
-
`ctx.session.get` therefore takes a **single flat object `{sessionID}`** — not v1's
|
|
75
|
-
`{path:{id}}`, and not an array. `src/host/v2-session-client.ts` had both wrong, which is why
|
|
76
|
-
every lookup failed silently for a release cycle.
|
|
77
|
-
|
|
78
|
-
`[L]` **The full ctx domain set, measured 2026-09-26 with zero model tokens** (a plugin that
|
|
79
|
-
only inspects its own context, booted under `--standalone --model nope/nope`; every method
|
|
80
|
-
called with `{}` so the host's own decode error names the required key):
|
|
81
|
-
|
|
82
|
-
| domain | what the host gives a plugin |
|
|
83
|
-
|---|---|
|
|
84
|
-
| `session` | `hook create get switchAgent switchModel prompt generate command synthetic interrupt update move wait context` |
|
|
85
|
-
| `permission` | `hook list get reply` — all of `list/get/reply` decode as `{sessionID}` |
|
|
86
|
-
| `experimental` | `terminal.read` (decodes as `{sessionID}`; a terminal-id spelling has not been found) |
|
|
87
|
-
| `storage` | `get set remove scan` |
|
|
88
|
-
| `agent` / `model` / `command` / `mcp` / `worktree` | `list/get/transform/reload` families |
|
|
89
|
-
| `event` | `subscribe` (an async iterable, already used by `v2-events.ts`) |
|
|
90
|
-
| `rpc` | a single function of arity 1 — its calling convention has NOT been read out |
|
|
91
|
-
|
|
92
|
-
`[L]` **`ctx.session.context({sessionID})` answers with an ARRAY of flat items**, measured
|
|
93
|
-
against a real background child from a live round:
|
|
94
|
-
|
|
95
|
-
```
|
|
96
|
-
array(5) < { id: string, time: { created: number }, text: string, type: string } // type "user" …
|
|
97
|
-
```
|
|
98
|
-
|
|
99
|
-
That is NOT v1's `[{info:{role},parts:[…]}]`, and it is the reason a child's report was lost for
|
|
100
|
-
a release: `v2-session-client.ts` already called `context`, then handed the array to
|
|
101
|
-
`lastAssistantMessage`, which tolerates an unknown shape by returning no text. The seam
|
|
102
|
-
ANSWERED and the body was discarded, while `tm_join` told the lead 正文不经本工具 — our decode
|
|
103
|
-
miss reported as the host's limit. The flat shape is now normalised at the seam
|
|
104
|
-
(`normaliseContextMessages`), and no `time.completed` is invented there, because that field *is*
|
|
105
|
-
the settle verdict.
|
|
106
|
-
|
|
107
|
-
## 5. Deliberate non-goals
|
|
108
|
-
|
|
109
|
-
`[U]` No HTTP call is made by this plugin today. The reasons are recorded rather than implied:
|
|
110
|
-
the credential read is a trust boundary the user has not been asked about; a plugin answering
|
|
111
|
-
`/api/permission/request` would be self-allowing; and a plugin that can `POST
|
|
112
|
-
/api/experimental/fs/write` bypasses the very `session.hook("context")` trim that keeps a
|
|
113
|
-
denied role from being offered a tool. If any of these is ever pursued, it goes through the
|
|
114
|
-
same gate as everything else here: Team-scoped, counted, and visible in `tm_stats`.
|
|
115
|
-
|
|
116
|
-
## 6. The #32 decision, and what closed it
|
|
117
|
-
|
|
118
|
-
The user authorised the HTTP API on one condition — 保证不会被泄露. That condition is the reason
|
|
119
|
-
§4's probe happened first, and the probe made the condition moot:
|
|
120
|
-
|
|
121
|
-
- **子会话正文**: `ctx.session.context` in process (§4). No credential exists to leak.
|
|
122
|
-
- **权限队列**: `ctx.permission.list({sessionID})` answers in process — verified as a seam,
|
|
123
|
-
NOT yet used. Reading it to tell the user "N dialogs are waiting" is legitimate; calling
|
|
124
|
-
`reply` is not, because that is self-allowing, and `reply` stays uncalled.
|
|
125
|
-
- **pty 捕获**: `ctx.experimental.terminal.read({sessionID})` exists but its second argument
|
|
126
|
-
has not been found, so `tm_pty`'s retirement decision (§21) is unchanged for now — the
|
|
127
|
-
reason is updated from "the host has no such thing" to "the plugin seam is unread".
|
|
128
|
-
|
|
129
|
-
What the scan still settles on its own: there is **no todo/plan/task resource anywhere**, so
|
|
130
|
-
`tm_ledger` is not a compromise. And `~/.config/opencode/service.json` has never been opened by
|
|
131
|
-
this plugin, which `test-v2-adapter.mjs` group 12 asserts against the shipped source.
|
|
132
|
-
|
|
1
|
+
# OpenCode 2.0.18 — the host's own HTTP API, and what it changes for a plugin
|
|
2
|
+
|
|
3
|
+
**Status: mapped, not called.** Every route below was read out of the installed host's
|
|
4
|
+
embedded JS (the SDK client factory: `method:"GET", path:"/api/…"` entries, both literal and
|
|
5
|
+
template-literal), cross-checked against the official v2 API page for the plugin endpoints. **No
|
|
6
|
+
request has been made to any of them** — so "exists in the client" is not the same claim as
|
|
7
|
+
"works from a plugin process", and nothing here is a verified capability.
|
|
8
|
+
|
|
9
|
+
**Why it matters.** The v2 plugin context is thin: no SDK client, no `session.children`, no
|
|
10
|
+
message domain, no dialog. Several conclusions this repo recorded as host limitations are
|
|
11
|
+
actually *ctx* limitations — the server behind the ctx has a much larger surface. This page
|
|
12
|
+
separates the two so a future change does not re-accept a limitation that is really a missing
|
|
13
|
+
transport.
|
|
14
|
+
|
|
15
|
+
## 1. Discovery and authentication
|
|
16
|
+
|
|
17
|
+
`[B]`+`[D]` `GET /api/info` returns "the server identity, connection URLs, paths, and
|
|
18
|
+
readiness status". The CLI reaches a server through a helper that yields `{url, password}`, and
|
|
19
|
+
the transport is HTTP Basic with a fixed username:
|
|
20
|
+
|
|
21
|
+
```js
|
|
22
|
+
{ type: "basic", username: "opencode", password: e.password }
|
|
23
|
+
await fetch(new URL("/api/info", e.url), { headers: X(i), signal: AbortSignal.timeout(r) })
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
`[B]` The password is **not** per-process random-only: `<global-config>/service.json` holds a
|
|
27
|
+
single `password` string (43 chars on this machine). The port is dynamic (`serve --stdio
|
|
28
|
+
--port 0`), which is why `/api/info` exists.
|
|
29
|
+
|
|
30
|
+
`[U]` Whether a plugin can obtain `{url, password}` from its own context instead of reading
|
|
31
|
+
that file is the open question, and it is the whole cost of every item below: **reading the
|
|
32
|
+
user's credential file to call back into the host is a new trust boundary**, and a plugin that
|
|
33
|
+
does it silently would be doing exactly what goal #6 forbids. Nothing in this repo does it
|
|
34
|
+
today, and `src/host/v2-session-client.ts` says so where the temptation is documented.
|
|
35
|
+
|
|
36
|
+
## 2. The routes that touch our recorded conclusions
|
|
37
|
+
|
|
38
|
+
| route family (`[B]`) | what it offers | which claim it reopens |
|
|
39
|
+
|---|---|---|
|
|
40
|
+
| `GET /api/plugin`, `POST /api/plugin/check`, `POST /api/plugin/update` | the enabled server plugins **with live status**, plus update checking | the install-verification story: this is a cleaner arrival check than grepping `msg="loading plugin"` (which is what `docs/installation-v2.md` currently tells an agent to do) |
|
|
41
|
+
| `GET /api/session/{id}/message`, `GET /api/session/{id}/message/{messageID}`, `GET /api/session/{id}/context` | read a session's messages — including a CHILD session | `tm_join`'s "the body arrives by injection, we cannot read it" limit. The in-process `ctx.session.context` is tried first (see §4); the API is the fallback with a price |
|
|
42
|
+
| `GET /api/permission/request`, `POST /api/session/{id}/permission`, `GET /api/permission/saved`, `DELETE /api/permission/saved/{id}` | the permission queue, answering it, and the saved-rule store | **"a plugin cannot raise a dialog on v2"** — that was measured against the ctx (`options.permission` on our own tool produced no evaluation). The server clearly has a dialog surface; whether a plugin may drive it is unmeasured, and driving it *for* the user would be self-allowing, which this project never does |
|
|
43
|
+
| `GET /api/pty`, `POST /api/pty`, `GET /api/pty/{id}/output?`, `GET /api/experimental/persistent-pty/{id}`, `.../snapshot`, `.../connect-token`, `POST /api/experimental/persistent-pty/{id}/connect-token` | terminal sessions with **snapshot/read**, i.e. exactly the capture capability `tm_pty` lacked | task #21 ("v2 has no `client.pty` → retire `tm_pty`") was decided on the ctx surface. The host has a persistent-pty API. Retirement still looks right (a plugin spawning terminals the user cannot see is its own problem), but the reason is now "no in-process seam + trust boundary", not "the host has no such thing" |
|
|
44
|
+
| `GET /api/fs/read/{path}`, `GET /api/fs/list`, `GET /api/fs/find`, `POST /api/experimental/fs/write` | file access through the host's own permission layer | reinforces the 1.7.x decision to retire `tm_read`/`tm_grep`/`tm_bash` in favour of the native ladder — the native path is where the host's `external_directory` dialog lives |
|
|
45
|
+
| `POST /api/websearch`, `GET /api/websearch/provider` | server-side search | all four native providers need a key/`/connect`, so `tm_search`'s zero-key front stays (unchanged conclusion, now with the reason visible) |
|
|
46
|
+
| `POST /api/session/{id}/wait`, `POST /api/experimental/session/{id}/wait`, `GET /api/experimental/session/stats`, `GET /api/experimental/session/{id}/export`, `POST /api/session/{id}/synthetic`, `.../interrupt`, `.../compact`, `.../fork` | waiting on and injecting into sessions | the shape of what a *session-owning* API looks like — useful for judging whether `tm_join`'s bounded wait is fighting the host or duplicating it |
|
|
47
|
+
| `GET /api/event` | the SSE feed | `ctx.event.subscribe()` is this stream, in-process. No reason to prefer HTTP |
|
|
48
|
+
|
|
49
|
+
## 3. The question the user actually asked: is there a todo list?
|
|
50
|
+
|
|
51
|
+
**No.** `[B]` Across **136 distinct `METHOD /path` pairs** in the scanned window (55 literal +
|
|
52
|
+
81 template-literal paths, deduplicated — a lower bound, since the scan covered 2 MB of the
|
|
53
|
+
embedded JS rather than the whole client factory) there is no `todo`, no `plan`, no `task`
|
|
54
|
+
resource; the only `message` routes are `GET /api/session/{id}/message` and
|
|
55
|
+
`GET /api/session/{id}/message/{messageID}`. `[D]` The official API page lists none either. The
|
|
56
|
+
v1 REST endpoint `GET /session/{id}/todo` that `tm_join`'s goal tripwire was written against
|
|
57
|
+
does not exist on 2.x, which is the same fact as "the host has no `todowrite` tool" seen from
|
|
58
|
+
the other side. So `tm_ledger` is not a workaround we settled for: it is the only ledger there
|
|
59
|
+
is. What *is* available for the same purpose is `GET /api/session/{id}/context` (in-process)
|
|
60
|
+
and `/message` (HTTP) — reading what the model was told, rather than a list the model
|
|
61
|
+
maintains.
|
|
62
|
+
|
|
63
|
+
## 4. What was verified instead, in-process
|
|
64
|
+
|
|
65
|
+
`[B]` The host's own call sites give the plugin-facing argument shape, and that fixed a bridge
|
|
66
|
+
that had never worked:
|
|
67
|
+
|
|
68
|
+
```
|
|
69
|
+
te = (schema, fn) => (input) => decode(schema, input ?? {}) → fn(decoded)
|
|
70
|
+
await e.session.get({ sessionID: n })
|
|
71
|
+
await e.message.list({ sessionID: n, limit: 200, cursor: r })
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
`ctx.session.get` therefore takes a **single flat object `{sessionID}`** — not v1's
|
|
75
|
+
`{path:{id}}`, and not an array. `src/host/v2-session-client.ts` had both wrong, which is why
|
|
76
|
+
every lookup failed silently for a release cycle.
|
|
77
|
+
|
|
78
|
+
`[L]` **The full ctx domain set, measured 2026-09-26 with zero model tokens** (a plugin that
|
|
79
|
+
only inspects its own context, booted under `--standalone --model nope/nope`; every method
|
|
80
|
+
called with `{}` so the host's own decode error names the required key):
|
|
81
|
+
|
|
82
|
+
| domain | what the host gives a plugin |
|
|
83
|
+
|---|---|
|
|
84
|
+
| `session` | `hook create get switchAgent switchModel prompt generate command synthetic interrupt update move wait context` |
|
|
85
|
+
| `permission` | `hook list get reply` — all of `list/get/reply` decode as `{sessionID}` |
|
|
86
|
+
| `experimental` | `terminal.read` (decodes as `{sessionID}`; a terminal-id spelling has not been found) |
|
|
87
|
+
| `storage` | `get set remove scan` |
|
|
88
|
+
| `agent` / `model` / `command` / `mcp` / `worktree` | `list/get/transform/reload` families |
|
|
89
|
+
| `event` | `subscribe` (an async iterable, already used by `v2-events.ts`) |
|
|
90
|
+
| `rpc` | a single function of arity 1 — its calling convention has NOT been read out |
|
|
91
|
+
|
|
92
|
+
`[L]` **`ctx.session.context({sessionID})` answers with an ARRAY of flat items**, measured
|
|
93
|
+
against a real background child from a live round:
|
|
94
|
+
|
|
95
|
+
```
|
|
96
|
+
array(5) < { id: string, time: { created: number }, text: string, type: string } // type "user" …
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
That is NOT v1's `[{info:{role},parts:[…]}]`, and it is the reason a child's report was lost for
|
|
100
|
+
a release: `v2-session-client.ts` already called `context`, then handed the array to
|
|
101
|
+
`lastAssistantMessage`, which tolerates an unknown shape by returning no text. The seam
|
|
102
|
+
ANSWERED and the body was discarded, while `tm_join` told the lead 正文不经本工具 — our decode
|
|
103
|
+
miss reported as the host's limit. The flat shape is now normalised at the seam
|
|
104
|
+
(`normaliseContextMessages`), and no `time.completed` is invented there, because that field *is*
|
|
105
|
+
the settle verdict.
|
|
106
|
+
|
|
107
|
+
## 5. Deliberate non-goals
|
|
108
|
+
|
|
109
|
+
`[U]` No HTTP call is made by this plugin today. The reasons are recorded rather than implied:
|
|
110
|
+
the credential read is a trust boundary the user has not been asked about; a plugin answering
|
|
111
|
+
`/api/permission/request` would be self-allowing; and a plugin that can `POST
|
|
112
|
+
/api/experimental/fs/write` bypasses the very `session.hook("context")` trim that keeps a
|
|
113
|
+
denied role from being offered a tool. If any of these is ever pursued, it goes through the
|
|
114
|
+
same gate as everything else here: Team-scoped, counted, and visible in `tm_stats`.
|
|
115
|
+
|
|
116
|
+
## 6. The #32 decision, and what closed it
|
|
117
|
+
|
|
118
|
+
The user authorised the HTTP API on one condition — 保证不会被泄露. That condition is the reason
|
|
119
|
+
§4's probe happened first, and the probe made the condition moot:
|
|
120
|
+
|
|
121
|
+
- **子会话正文**: `ctx.session.context` in process (§4). No credential exists to leak.
|
|
122
|
+
- **权限队列**: `ctx.permission.list({sessionID})` answers in process — verified as a seam,
|
|
123
|
+
NOT yet used. Reading it to tell the user "N dialogs are waiting" is legitimate; calling
|
|
124
|
+
`reply` is not, because that is self-allowing, and `reply` stays uncalled.
|
|
125
|
+
- **pty 捕获**: `ctx.experimental.terminal.read({sessionID})` exists but its second argument
|
|
126
|
+
has not been found, so `tm_pty`'s retirement decision (§21) is unchanged for now — the
|
|
127
|
+
reason is updated from "the host has no such thing" to "the plugin seam is unread".
|
|
128
|
+
|
|
129
|
+
What the scan still settles on its own: there is **no todo/plan/task resource anywhere**, so
|
|
130
|
+
`tm_ledger` is not a compromise. And `~/.config/opencode/service.json` has never been opened by
|
|
131
|
+
this plugin, which `test-v2-adapter.mjs` group 12 asserts against the shipped source.
|
|
132
|
+
|