@te-river/opencode-team-mode 1.7.2 → 1.7.3
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 +1009 -849
- package/README.zh-CN.md +906 -756
- package/dist/host/v2-browser-gate.d.ts +4 -1
- package/dist/host/v2-browser-gate.d.ts.map +1 -1
- package/dist/host/v2-browser-gate.js +1 -1
- package/dist/host/v2-browser-gate.js.map +1 -1
- package/dist/host/v2-capabilities.js +1 -1
- package/dist/host/v2-capabilities.js.map +1 -1
- package/dist/host/v2-guard.d.ts +3 -1
- package/dist/host/v2-guard.d.ts.map +1 -1
- package/dist/host/v2-guard.js +2 -2
- package/dist/host/v2-guard.js.map +1 -1
- package/dist/host/v2-offload.d.ts +12 -2
- package/dist/host/v2-offload.d.ts.map +1 -1
- package/dist/host/v2-offload.js +9 -9
- package/dist/host/v2-offload.js.map +1 -1
- package/dist/host/v2-probe-chain.d.ts +5 -1
- package/dist/host/v2-probe-chain.d.ts.map +1 -1
- package/dist/host/v2-probe-chain.js +4 -6
- package/dist/host/v2-probe-chain.js.map +1 -1
- package/dist/host/v2-probe.d.ts.map +1 -1
- package/dist/host/v2-probe.js +2 -1
- package/dist/host/v2-probe.js.map +1 -1
- package/dist/host/v2-prune.d.ts +5 -1
- package/dist/host/v2-prune.d.ts.map +1 -1
- package/dist/host/v2-prune.js +8 -14
- package/dist/host/v2-prune.js.map +1 -1
- package/dist/host/v2-retry.d.ts +9 -2
- package/dist/host/v2-retry.d.ts.map +1 -1
- package/dist/host/v2-retry.js +13 -15
- package/dist/host/v2-retry.js.map +1 -1
- package/dist/host/v2-session.d.ts +6 -2
- package/dist/host/v2-session.d.ts.map +1 -1
- package/dist/host/v2-session.js +7 -13
- package/dist/host/v2-session.js.map +1 -1
- package/dist/host/v2.d.ts +7 -2
- package/dist/host/v2.d.ts.map +1 -1
- package/dist/host/v2.js +38 -35
- package/dist/host/v2.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 +23 -7
- package/dist/prompts/lead.js.map +1 -1
- package/dist/prompts/specialists.d.ts +1 -1
- package/dist/prompts/specialists.d.ts.map +1 -1
- package/dist/prompts/specialists.js +1 -1
- package/dist/tm/args-schema.d.ts +1 -1
- package/dist/tm/args-schema.js +6 -6
- package/dist/tm/args-schema.js.map +1 -1
- package/dist/tm/board.js +2 -2
- package/dist/tm/board.js.map +1 -1
- package/dist/tm/config-files.d.ts +41 -17
- package/dist/tm/config-files.d.ts.map +1 -1
- package/dist/tm/config-files.js +68 -73
- package/dist/tm/config-files.js.map +1 -1
- package/dist/tm/config-layers.d.ts +65 -33
- package/dist/tm/config-layers.d.ts.map +1 -1
- package/dist/tm/config-layers.js +165 -81
- package/dist/tm/config-layers.js.map +1 -1
- package/dist/tm/config-template.d.ts +32 -0
- package/dist/tm/config-template.d.ts.map +1 -0
- package/dist/tm/config-template.js +74 -0
- package/dist/tm/config-template.js.map +1 -0
- package/dist/tm/config.d.ts +115 -140
- package/dist/tm/config.d.ts.map +1 -1
- package/dist/tm/config.js +100 -200
- package/dist/tm/config.js.map +1 -1
- package/dist/tm/index.d.ts +11 -3
- package/dist/tm/index.d.ts.map +1 -1
- package/dist/tm/index.js +46 -16
- package/dist/tm/index.js.map +1 -1
- package/dist/tm/ledger.d.ts +7 -3
- package/dist/tm/ledger.d.ts.map +1 -1
- package/dist/tm/ledger.js +5 -4
- package/dist/tm/ledger.js.map +1 -1
- package/dist/tm/memory.js +3 -3
- package/dist/tm/pipelines.js +1 -1
- package/dist/tm/search.js +6 -6
- package/dist/tm/search.js.map +1 -1
- package/dist/tm/stats.js +2 -2
- package/dist/tm/stats.js.map +1 -1
- package/dist/tm/tools.js +2 -2
- package/dist/tm/webfetch.d.ts +5 -2
- package/dist/tm/webfetch.d.ts.map +1 -1
- package/dist/tm/webfetch.js +24 -18
- package/dist/tm/webfetch.js.map +1 -1
- package/docs/installation-v2.md +59 -38
- package/docs/installation.md +4 -4
- package/package.json +1 -1
- package/dist/approval-gate.d.ts +0 -205
- package/dist/approval-gate.d.ts.map +0 -1
- package/dist/approval-gate.js +0 -627
- package/dist/approval-gate.js.map +0 -1
- package/dist/host/v1.d.ts +0 -14
- package/dist/host/v1.d.ts.map +0 -1
- package/dist/host/v1.js +0 -340
- package/dist/host/v1.js.map +0 -1
- package/dist/host/v2-client.d.ts +0 -53
- package/dist/host/v2-client.d.ts.map +0 -1
- package/dist/host/v2-client.js +0 -193
- package/dist/host/v2-client.js.map +0 -1
- package/dist/host/v2-transcript.d.ts +0 -73
- package/dist/host/v2-transcript.d.ts.map +0 -1
- package/dist/host/v2-transcript.js +0 -151
- package/dist/host/v2-transcript.js.map +0 -1
- package/dist/host-hooks.d.ts +0 -126
- package/dist/host-hooks.d.ts.map +0 -1
- package/dist/host-hooks.js +0 -284
- package/dist/host-hooks.js.map +0 -1
- package/dist/tm/bash-timeout.d.ts +0 -72
- package/dist/tm/bash-timeout.d.ts.map +0 -1
- package/dist/tm/bash-timeout.js +0 -100
- package/dist/tm/bash-timeout.js.map +0 -1
- package/dist/tm/browser.d.ts +0 -642
- package/dist/tm/browser.d.ts.map +0 -1
- package/dist/tm/browser.js +0 -2836
- package/dist/tm/browser.js.map +0 -1
- package/dist/tm/ptc/budgets.d.ts +0 -59
- package/dist/tm/ptc/budgets.d.ts.map +0 -1
- package/dist/tm/ptc/budgets.js +0 -138
- package/dist/tm/ptc/budgets.js.map +0 -1
- package/dist/tm/ptc/contract.d.ts +0 -171
- package/dist/tm/ptc/contract.d.ts.map +0 -1
- package/dist/tm/ptc/contract.js +0 -85
- package/dist/tm/ptc/contract.js.map +0 -1
- package/dist/tm/ptc/driver.d.ts +0 -40
- package/dist/tm/ptc/driver.d.ts.map +0 -1
- package/dist/tm/ptc/driver.js +0 -213
- package/dist/tm/ptc/driver.js.map +0 -1
- package/dist/tm/ptc/engines.d.ts +0 -43
- package/dist/tm/ptc/engines.d.ts.map +0 -1
- package/dist/tm/ptc/engines.js +0 -454
- package/dist/tm/ptc/engines.js.map +0 -1
- package/dist/tm/ptc/gate.d.ts +0 -28
- package/dist/tm/ptc/gate.d.ts.map +0 -1
- package/dist/tm/ptc/gate.js +0 -130
- package/dist/tm/ptc/gate.js.map +0 -1
- package/dist/tm/ptc/index.d.ts +0 -19
- package/dist/tm/ptc/index.d.ts.map +0 -1
- package/dist/tm/ptc/index.js +0 -15
- package/dist/tm/ptc/index.js.map +0 -1
- package/dist/tm/ptc/pscan.d.ts +0 -30
- package/dist/tm/ptc/pscan.d.ts.map +0 -1
- package/dist/tm/ptc/pscan.js +0 -341
- package/dist/tm/ptc/pscan.js.map +0 -1
- package/dist/tm/ptc/summary.d.ts +0 -17
- package/dist/tm/ptc/summary.d.ts.map +0 -1
- package/dist/tm/ptc/summary.js +0 -134
- package/dist/tm/ptc/summary.js.map +0 -1
- package/dist/tm/ptc/tool.d.ts +0 -66
- package/dist/tm/ptc/tool.d.ts.map +0 -1
- package/dist/tm/ptc/tool.js +0 -231
- package/dist/tm/ptc/tool.js.map +0 -1
- package/dist/tm/ptc.d.ts +0 -298
- package/dist/tm/ptc.d.ts.map +0 -1
- package/dist/tm/ptc.js +0 -865
- package/dist/tm/ptc.js.map +0 -1
- package/dist/tm/pty.d.ts +0 -70
- package/dist/tm/pty.d.ts.map +0 -1
- package/dist/tm/pty.js +0 -245
- package/dist/tm/pty.js.map +0 -1
- package/dist/tool-coerce.d.ts +0 -24
- package/dist/tool-coerce.d.ts.map +0 -1
- package/dist/tool-coerce.js +0 -63
- package/dist/tool-coerce.js.map +0 -1
package/docs/installation-v2.md
CHANGED
|
@@ -56,7 +56,7 @@ implied:
|
|
|
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** (`
|
|
59
|
+
| Web access | a 22-host allowlist + the confirm dialog | **the domain gate is off** (`webfetchAllowedDomains` defaults to `["*"]` on v2): 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: `"privateSpace": "allow"` in the global `team-mode.jsonc` (opens the class) or that one hostname in `webfetchAllowedDomains` (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
|
|
|
@@ -246,29 +246,40 @@ env-file line). It is zero-token by default; `--turns` adds the checks that need
|
|
|
246
246
|
|
|
247
247
|
### Layered configuration (`team-mode.jsonc`)
|
|
248
248
|
|
|
249
|
-
|
|
249
|
+
`team-mode.jsonc` is the **only** configuration source — every `TM_*` configuration environment
|
|
250
|
+
variable is gone. Two layers, mirroring the host's own config:
|
|
250
251
|
|
|
251
252
|
| layer | file |
|
|
252
253
|
|---|---|
|
|
253
|
-
| global | `~/.config/opencode/team-mode.jsonc` |
|
|
254
|
+
| global | `~/.config/opencode/team-mode.jsonc` (honours `OPENCODE_CONFIG_DIR`) |
|
|
254
255
|
| project | `<project>/team-mode.jsonc`, or `<project>/.opencode/team-mode.jsonc` (the `.opencode` one wins) |
|
|
255
256
|
|
|
256
|
-
Precedence is `
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
257
|
+
Precedence is `global < project`. **The plugin writes the global file for you at boot** when it is
|
|
258
|
+
absent: a fully-commented, INERT template — every key appears as a comment (`// "key": <default>,`)
|
|
259
|
+
under its one-line doc, so the file sets nothing until you uncomment a line. It is idempotent and
|
|
260
|
+
never overwrites an existing file. Turn it off with the plugin option `autoCreate: false` (or the
|
|
261
|
+
internal `TM_CONFIG_AUTOCREATE=off`); the boot row records `config_autocreate` as
|
|
262
|
+
`created` / `present` / `off` / `failed`.
|
|
263
|
+
|
|
264
|
+
Keys are camelCase (`offloadThreshold`, `searchDefaultEngine`, …) — the registry in
|
|
265
|
+
`src/tm/config-layers.ts` is the single source of truth (59 keys, each with its type, default and
|
|
266
|
+
doc). Five keys are **red lines a PROJECT file may not change** — `envProtect`, `r6FineAsk`,
|
|
267
|
+
`privateSpace`, `webfetchAllowedDomains`, `bashReadonlyAllowed`; they are settable only in the
|
|
268
|
+
global file, and a project value is ignored and reported. A file that fails to parse is skipped
|
|
269
|
+
**whole** (half a config is worse than none), while a single bad key loses only itself. Unknown keys
|
|
270
|
+
warn at boot and are never applied. `tm_stats` shows which layer each key came from, the red-line
|
|
271
|
+
keys a file tried to set and lost, the unknown keys, the skipped layers, and the auto-create state.
|
|
272
|
+
|
|
273
|
+
**R6 (env protection) is armed by default** (`envProtect` defaults to `"strict"`; the canonical
|
|
274
|
+
values are `strict` / `standard` / `off`). A Team role's native `read` of a `.env` is denied
|
|
268
275
|
outright, and a shell command that reads the environment goes to the host's own permission prompt.
|
|
269
|
-
Turn it off with the plugin option `envProtect: false` or with `
|
|
270
|
-
shell face is an `ask` (the host's dialog — a
|
|
271
|
-
a hard `deny` with no consent path.
|
|
276
|
+
Turn it off with the plugin option `envProtect: false` or with `"envProtect": "off"` in the
|
|
277
|
+
**global** file (it is a red-line key); on 2.x the shell face is an `ask` (the host's dialog — a
|
|
278
|
+
plugin cannot raise one) while the env-FILE face is a hard `deny` with no consent path.
|
|
279
|
+
|
|
280
|
+
The only `TM_*` environment variables left are internal/test switches, not configuration:
|
|
281
|
+
`TM_STORE_RECLAIM` (test override of the `storeReclaim` key), `TM_V2_PROBE` (name-level JSONL
|
|
282
|
+
dump) and `TM_CONFIG_AUTOCREATE` (the auto-create off switch).
|
|
272
283
|
|
|
273
284
|
### Uninstalling
|
|
274
285
|
|
|
@@ -291,8 +302,8 @@ a hard `deny` with no consent path.
|
|
|
291
302
|
| `/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) |
|
|
292
303
|
| `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 |
|
|
293
304
|
| `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 |
|
|
294
|
-
| A tool's output arrives un-offloaded | `
|
|
295
|
-
| Sessions used to compact at 75% on their own; they do not any more | That trigger was **removed in 1.7.1** (it shipped in 1.7.0). The host owns when a conversation is summarized again, exactly as if the plugin were not installed. The three knobs that drove it (`TM_COMPACT_TRIGGER`, `TM_COMPACT_AT_PERCENT`, `TM_COMPACT_MIN_MS`) are gone, and a `team-mode.jsonc` that still names them reports them as unknown keys at boot instead of applying them. What stays is additive and only runs when the HOST compacts:
|
|
305
|
+
| A tool's output arrives un-offloaded | `"nativeOffload": "off"` in the config file (or a missing `tool.execute.after` seam on an older 2.x build) — `tm_stats` names which |
|
|
306
|
+
| Sessions used to compact at 75% on their own; they do not any more | That trigger was **removed in 1.7.1** (it shipped in 1.7.0). The host owns when a conversation is summarized again, exactly as if the plugin were not installed. The three knobs that drove it (`TM_COMPACT_TRIGGER`, `TM_COMPACT_AT_PERCENT`, `TM_COMPACT_MIN_MS`) are gone, and a `team-mode.jsonc` that still names them reports them as unknown keys at boot instead of applying them. What stays is additive and only runs when the HOST compacts: the survival list is pushed on `session.hook("compaction")` (the `compactionContext` key is a v1-only parse-compat entry) |
|
|
296
307
|
| 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 |
|
|
297
308
|
| 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 |
|
|
298
309
|
| 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 |
|
|
@@ -324,7 +335,7 @@ a hard `deny` with no consent path.
|
|
|
324
335
|
| 子代理结算检测 | 宿主的 `event` 钩子 | `ctx.event.subscribe()`——零依赖的 async iterable,按事件类型名过滤(`src/host/v2-events.ts`)。没有它 `tm_join` 分不清「已结算」和「仍在跑」;`tm_stats` 会给出转发了多少、哪些类型名没认出来(只记名字,绝不记负载) |
|
|
325
336
|
| 任务清单 | 宿主 `todowrite` | **`tm_ledger`**,存在宿主的 `ctx.storage` 里(v2 不给插件 `todowrite`) |
|
|
326
337
|
| 征求用户同意 | 宿主官方逐次弹窗 | **插件在 v2 弹不出对话框。** 原本该问的受治理调用一律**直接拒绝**,并说明是"没人可问"而不是"问了被拒"。你看到的弹窗都来自宿主自己(权限规则、越出项目目录) |
|
|
327
|
-
| 联网 | 22 个域名白名单 + 确认弹窗 | **域名门禁关掉**(`
|
|
338
|
+
| 联网 | 22 个域名白名单 + 确认弹窗 | **域名门禁关掉**(`webfetchAllowedDomains` 在 v2 上默认 `["*"]`):2.x 插件弹不出确认框,"批准后放行"等于让 agent 去等一个永远不会出现的窗口。地址类别照管——元数据 / 链路本地 / 保留网段在任何设置下都拒;私网(RFC1918、CGNAT、ULA、IPv6 链路本地、`.localhost`)也拒,但拒绝语现在给出两条出口而不是承诺点击:在全局 `team-mode.jsonc` 里写 `"privateSpace": "allow"`(放开整段),或把那一台主机名写进 `webfetchAllowedDomains`(只放开它自己)。**回环(`127/8`、`::1`、`localhost`)默认放行**——它只打到用户自己机器上的服务,而一道唯一出口是"插件弹不出来的确认框"的门禁,是墙不是门 |
|
|
328
339
|
| 非阻塞命令 | `tm_pty`——**已随 v1 支持一起移除** | **完全不注册**——v2 插件上下文没有 pty 域,这个工具只能报自己缺缝。慢步骤请一条一个 `shell` 调用(各自带 `timeout`),并把输出 tee 到日志好回读 |
|
|
329
340
|
| 子代理 | 需要 `OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS=true` | 原生能力;插件对每次 `subagent` 强制 `background: true`,所以 **2.x 不要去设那个环境变量**(那是 v1 的补丁,在这里什么也不改变,只会让人误判) |
|
|
330
341
|
| 对你其它模式的影响 | 插件钩子是全局的 | **没有影响。** 每个钩子都先看会话归属,所以 `build`、`plan` 和你自己装的 agent 都保持刚装好 OpenCode 时的样子——不会从它们的请求里删工具、不会设温度、不会卸载结果、不会收紧权限、也不会把它们的派发改成后台 |
|
|
@@ -487,24 +498,30 @@ opencode reload
|
|
|
487
498
|
|
|
488
499
|
### 分层配置(`team-mode.jsonc`)
|
|
489
500
|
|
|
490
|
-
|
|
501
|
+
`team-mode.jsonc` 是**唯一**的配置源——所有 `TM_*` 配置环境变量都已移除。两层,与宿主自己的配置同构:
|
|
491
502
|
|
|
492
503
|
| 层 | 文件 |
|
|
493
504
|
|---|---|
|
|
494
|
-
| 全局 | `~/.config/opencode/team-mode.jsonc` |
|
|
505
|
+
| 全局 | `~/.config/opencode/team-mode.jsonc`(认 `OPENCODE_CONFIG_DIR`) |
|
|
495
506
|
| 项目 | `<project>/team-mode.jsonc`,或 `<project>/.opencode/team-mode.jsonc`(后者胜) |
|
|
496
507
|
|
|
497
|
-
优先级
|
|
498
|
-
|
|
508
|
+
优先级 `全局 < 项目`。**全局文件由插件在启动时自动生成**(文件不存在时):一份全注释的**惰性**模板——
|
|
509
|
+
每个键都以注释形式出现(`// "key": <默认值>,`),注释行就是它的一行说明,所以在你取消注释之前,这个文件什么都不设置。
|
|
510
|
+
幂等、原子写入,绝不覆盖已存在的文件。关闭方式:插件选项 `autoCreate: false`(或内部开关 `TM_CONFIG_AUTOCREATE=off`);
|
|
511
|
+
启动行会记录 `config_autocreate`(`created` / `present` / `off` / `failed`)。
|
|
499
512
|
|
|
500
|
-
|
|
501
|
-
|
|
513
|
+
键名是 camelCase(`offloadThreshold`、`searchDefaultEngine` …)——`src/tm/config-layers.ts` 里的注册表是唯一事实来源
|
|
514
|
+
(59 个键,每个都带类型、默认值与说明)。有五个键是**项目文件不许改写的红线**——`envProtect`、`r6FineAsk`、
|
|
515
|
+
`privateSpace`、`webfetchAllowedDomains`、`bashReadonlyAllowed`;它们只能在全局文件里设置,项目文件里写了会被忽略并报告。
|
|
502
516
|
解析失败的那一层**整层跳过**(半份配置比没有配置更危险),单个坏键只丢它自己。未知键在启动时告警、**不应用**。
|
|
503
|
-
`tm_stats`
|
|
517
|
+
`tm_stats` 会显示每个键最终来自哪一层、被忽略的红线键、未知键、被跳过的层,以及自动生成的状态。
|
|
504
518
|
|
|
505
|
-
**R6
|
|
506
|
-
|
|
507
|
-
env-FILE 面是硬 `deny`、没有同意路径。
|
|
519
|
+
**R6(环境变量保护)默认开启**(`envProtect` 默认 `"strict"`;规范取值 `strict` / `standard` / `off`)。Team 角色用原生 `read` 读 `.env` 会被直接拒;
|
|
520
|
+
读环境变量的 shell 命令会走宿主自己的权限提示。关闭方式:插件选项 `envProtect: false`,或在**全局**文件里写 `"envProtect": "off"`(它是红线键)。
|
|
521
|
+
2.x 上 shell 面是 `ask`(宿主的对话框——插件弹不出来),env-FILE 面是硬 `deny`、没有同意路径。
|
|
522
|
+
|
|
523
|
+
仅存的 `TM_*` 环境变量是内部/测试开关,不是配置:`TM_STORE_RECLAIM`(测试覆盖 `storeReclaim` 键)、
|
|
524
|
+
`TM_V2_PROBE`(只记名字的 JSONL 转储)、`TM_CONFIG_AUTOCREATE`(自动生成的关闭开关)。
|
|
508
525
|
|
|
509
526
|
### 卸载
|
|
510
527
|
|
|
@@ -527,8 +544,8 @@ env-FILE 面是硬 `deny`、没有同意路径。
|
|
|
527
544
|
| `/team-plan` 能跑但不是以那个专家身份跑 | 2.x 把 `mode: subagent` 文档化为"只在子会话里运行"。如果选定专家的命令行为不对,改法是角色定义里用 `mode: "all"`(请回报——这是本流程里唯一还没在活体宿主上验证过的一项) |
|
|
528
545
|
| `tm_webfetch` 说它"没问任何人就直接拒绝" | v2 的预期行为:插件弹不出对话框。换一个不需要这次访问的源,或让宿主自己的权限规则放行;地址红线(元数据 / 私网)在两代宿主上都没有授权路径 |
|
|
529
546
|
| `subagent` 好像把领队挡住了 | v2 上插件会强制 `background: true`;如果你顺手设了老的 `OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS`,请取消它——那是 v1 的开关,在这里只会误导判断 |
|
|
530
|
-
| 某个工具的输出没被卸载 | `
|
|
531
|
-
| 会话不再自己提前压缩了(1.7.0 曾是模型窗口的 75%) | 那个触发器在 **1.7.1 已整体移除**(它随 1.7.0 发布)。压缩时机重新完全归宿主,跟没装插件一样。驱动它的三个开关(`TM_COMPACT_TRIGGER`、`TM_COMPACT_AT_PERCENT`、`TM_COMPACT_MIN_MS`)已删除;`team-mode.jsonc` 里若还留着它们,启动时会报为未知键而不是照旧生效。保留下来的是"只在宿主自己压缩时"
|
|
547
|
+
| 某个工具的输出没被卸载 | 配置文件里写 `"nativeOffload": "off"`(或者那个 2.x 构建没有 `tool.execute.after` 缝)——`tm_stats` 会告诉你是哪一种 |
|
|
548
|
+
| 会话不再自己提前压缩了(1.7.0 曾是模型窗口的 75%) | 那个触发器在 **1.7.1 已整体移除**(它随 1.7.0 发布)。压缩时机重新完全归宿主,跟没装插件一样。驱动它的三个开关(`TM_COMPACT_TRIGGER`、`TM_COMPACT_AT_PERCENT`、`TM_COMPACT_MIN_MS`)已删除;`team-mode.jsonc` 里若还留着它们,启动时会报为未知键而不是照旧生效。保留下来的是"只在宿主自己压缩时"才追加的一项:必须存活清单在 `session.hook("compaction")` 上推送(`compactionContext` 键是 v1-only 的解析兼容项) |
|
|
532
549
|
| 插件行为怪:两次弹窗、钩子重复、工具像注册了两遍 | 它被加载了两次。`opencode.json` 与 `opencode.jsonc` 是合并读取的,而宿主去重只认完全相同的字符串,所以同一个包换两种写法(或者同时躺在两个文件里)就是两套人格。把两个文件里指向 Team 的条目一起数一遍;`msg="loading plugin"` 对同一个 id 出现两条就是证据 |
|
|
533
550
|
| 写了条目,但宿主日志里关于它一个字都没有 | 两个"设计上静默"的原因:键名写成了单数 `plugin`(2.x 读 `plugins`),或者目录条目缺根 `index.js`。宿主解析目录只试 `<dir>/index`,试不出来就直接丢掉、不留任何消息 —— 在这里"没报错"不是成功信号 |
|
|
534
551
|
| 后台子代理的回复太长 | 子会话现在会被登记、可被收集(`tm_join` 会列出它,行上标着「宿主 subagent 派发」),但它的**正文**仍然是宿主的注入消息,v2 不会在落盘前把这条消息交给插件;我们也不改写发出的消息(猜错那一层的形状等于静默删证据)。所以超限交付走黑板文件:正文进文件、回复里带路径,领队读那个文件 |
|
|
@@ -538,10 +555,14 @@ env-FILE 面是硬 `deny`、没有同意路径。
|
|
|
538
555
|
|
|
539
556
|
### v2 专属的开关
|
|
540
557
|
|
|
541
|
-
|
|
558
|
+
配置键写在 `team-mode.jsonc` 里(红线键只能在全局文件里设);环境变量只剩三个内部/测试开关。
|
|
559
|
+
|
|
560
|
+
| 键 / 变量 | 默认 | 作用 |
|
|
542
561
|
|---|---|---|
|
|
543
|
-
| `
|
|
544
|
-
| `
|
|
545
|
-
| `
|
|
546
|
-
| `TM_V2_PROBE
|
|
562
|
+
| `webfetchAllowedDomains`(配置) | `["*"]`(**仅 v2**) | 域名门禁默认关掉,只留地址红线;显式给了值就以它为准(v1 仍是 24 个种子域名) |
|
|
563
|
+
| `nativeOffload`(配置) | on | 用 `tool.execute.after` 治理宿主原生工具的结果;off 就退回宿主原样 |
|
|
564
|
+
| `r6FineAsk`(配置) | `""`(自动) | 由按命令分类器决定哪条 shell 要问;`off` 回到"每条 shell 都问",宿主没有 `permission.hook` 时也自动回到这一档 |
|
|
565
|
+
| `TM_V2_PROBE`(环境,内部) | 未设 | 把宿主真实工具面/动作名记成 JSONL(只有名字和计数) |
|
|
566
|
+
| `TM_STORE_RECLAIM`(环境,内部) | on | 测试覆盖 `storeReclaim` 键:off 时启动不回收存储分片 |
|
|
567
|
+
| `TM_CONFIG_AUTOCREATE`(环境,内部) | on | off 时不在启动时自动生成全局 `team-mode.jsonc` |
|
|
547
568
|
| `{ "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
|
@@ -97,7 +97,7 @@ Report which checks passed. If verification fails, see Troubleshooting.
|
|
|
97
97
|
OpenCode's own `task { background: true }` is the only sub-agent its interface
|
|
98
98
|
can **show**: the card links to the live child session, it does not block the
|
|
99
99
|
lead, and the host wakes the parent with the result. TeamMode then keeps that
|
|
100
|
-
result inside the token budget (`
|
|
100
|
+
result inside the token budget (`taskOffload`).
|
|
101
101
|
|
|
102
102
|
It rides an OpenCode **experimental flag** read from the host process
|
|
103
103
|
environment at startup, so a plugin cannot set it — the installer writes it for
|
|
@@ -273,7 +273,7 @@ never re-resolved on its own).
|
|
|
273
273
|
| Version is old despite `@latest` | Plugin cache never invalidates | See Updating — delete the cache dir (recursively; both layouts) |
|
|
274
274
|
| Still the old version after bumping the version or re-running the installer | The scoped cache `packages/@te-river/opencode-team-mode@latest` is nested below the top-level `packages/*opencode-team-mode*` glob, so the stale copy was never purged | Recursively delete that cache dir (`Get-ChildItem -Recurse -Filter '*opencode-team-mode*'` / `find -type d -name '*opencode-team-mode*' -prune`), then restart — the current installers already recurse |
|
|
275
275
|
| Node version error on startup | npm needs Node ≥ 18 | Upgrade Node |
|
|
276
|
-
| Env-var commands suddenly prompt for confirmation | That's the plugin's R6 protection working as designed | Approve once per operation, or set `
|
|
276
|
+
| Env-var commands suddenly prompt for confirmation | That's the plugin's R6 protection working as designed | Approve once per operation, or set `envProtect: "off"` in `team-mode.jsonc` (not recommended) |
|
|
277
277
|
| A user-defined agent named `team`/`architect`/… got shadowed | Plugin injects defaults; your own definitions always win | The plugin never clobbers user agents — check for typos in your agent names |
|
|
278
278
|
|
|
279
279
|
---
|
|
@@ -342,7 +342,7 @@ never re-resolved on its own).
|
|
|
342
342
|
|
|
343
343
|
OpenCode 自带的 `task { background: true }` 是唯一能在它界面上**看见**的子代理:
|
|
344
344
|
卡片直接链到那个子会话、不阻塞 lead、完成时宿主把父会话唤醒。TeamMode 再把
|
|
345
|
-
回来的结果压在 token 预算内(`
|
|
345
|
+
回来的结果压在 token 预算内(`taskOffload`)。
|
|
346
346
|
|
|
347
347
|
它依赖一个 OpenCode 的**实验性开关**,由宿主进程启动时读环境变量决定,插件无法
|
|
348
348
|
自己打开——所以安装脚本替你写:
|
|
@@ -501,5 +501,5 @@ https://raw.githubusercontent.com/Te-River/Opencode-TeamMode/main/docs/installat
|
|
|
501
501
|
| 用着 `@latest` 版本还是旧的 | 插件缓存永不失效 | 见"更新"——递归删除缓存目录(两种布局) |
|
|
502
502
|
| 改了版本/重跑安装器后仍是旧版 | 作用域缓存 `packages/@te-river/opencode-team-mode@latest` 嵌在顶层 `packages/*opencode-team-mode*` 通配符扫不到的下一层,旧副本根本没被清掉 | 递归删除该缓存目录(`Get-ChildItem -Recurse -Filter '*opencode-team-mode*'` / `find -type d -name '*opencode-team-mode*' -prune`)后重启——当前安装器已改为递归清理 |
|
|
503
503
|
| 启动报 Node 版本错误 | npm 需要 Node ≥ 18 | 升级 Node |
|
|
504
|
-
| 环境变量命令突然弹确认框 | 这是插件的 R6 保护在正常工作 | 每个操作选"一次",或设 `
|
|
504
|
+
| 环境变量命令突然弹确认框 | 这是插件的 R6 保护在正常工作 | 每个操作选"一次",或设 `envProtect: "off"`(不推荐) |
|
|
505
505
|
| 用户自定义的 `team`/`architect` 等 agent 被遮蔽 | 插件注入默认值;你的定义永远优先 | 插件从不覆盖用户 agent——检查你的 agent 名字有没有拼错 |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@te-river/opencode-team-mode",
|
|
3
|
-
"version": "1.7.
|
|
3
|
+
"version": "1.7.3",
|
|
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",
|
package/dist/approval-gate.d.ts
DELETED
|
@@ -1,205 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Unified approval gate — Layer 2 (timeout auto-reject) + Layer 3 (SDK
|
|
3
|
-
* fallback) of the R6 + R2 approval flow.
|
|
4
|
-
*
|
|
5
|
-
* The host only shows its official confirmation dialog for command patterns
|
|
6
|
-
* declared `ask` in an agent's permission config (Layer 1, envprotect.ts).
|
|
7
|
-
* When a dialog opens it SUSPENDS the tool call and waits for a human; on the
|
|
8
|
-
* OpenCode DESKTOP that human is present, but a request can also be left
|
|
9
|
-
* unanswered. This module owns ONE timer that watches the host permission
|
|
10
|
-
* events and closes abandoned requests on the safe side.
|
|
11
|
-
*
|
|
12
|
-
* REAL host contract (1.18.29, probed live via SSE + the plugin event hook —
|
|
13
|
-
* see gate-test report-p5):
|
|
14
|
-
* permission.asked → properties = { id, sessionID, permission: "bash",
|
|
15
|
-
* patterns: [<concrete command segment>, …], metadata:
|
|
16
|
-
* { command }, always: [<host's "always" generalization
|
|
17
|
-
* proposal>], tool: { messageID, callID } } — the props
|
|
18
|
-
* carry NO `type` field, and the tool name sits in
|
|
19
|
-
* `permission`. A props matching one of OUR injected
|
|
20
|
-
* bash ask sets means the dialog is ours: audit "ask",
|
|
21
|
-
* arm the timeout, and — for the R6 env face — mark
|
|
22
|
-
* the session deferrable (canDefer, see below).
|
|
23
|
-
* permission.replied → properties = { sessionID, requestID, reply } on the
|
|
24
|
-
* wire, but the plugin-side observer has also seen it
|
|
25
|
-
* with ONLY { sessionID }. The gate therefore cancels
|
|
26
|
-
* EVERY pending timer of that session on a reply: an
|
|
27
|
-
* already-approved command left on a live timer would
|
|
28
|
-
* get a second reject after the timeout, the SDK call
|
|
29
|
-
* would hit a dead id (4xx) and flip the gate
|
|
30
|
-
* permanently degraded.
|
|
31
|
-
*
|
|
32
|
-
* Older/typed spellings (`permission.updated`, `pattern`, `permissionID`,
|
|
33
|
-
* `response`) keep being accepted — the shipped SDK d.ts still names the
|
|
34
|
-
* events that way, and some builds omit the reply id entirely (a short
|
|
35
|
-
* ghost-suppression window per session then absorbs late/duplicate `asked`
|
|
36
|
-
* replays instead of re-arming a ghost timer).
|
|
37
|
-
*
|
|
38
|
-
* Hard rules (HUMAN-approved):
|
|
39
|
-
* - the plugin NEVER self-allows: `reply` is fixed to "reject". The only
|
|
40
|
-
* way a gated command runs is a human approving the dialog;
|
|
41
|
-
* - on `TM_ENV_PROTECT=off` the gate is not armed (the caller decides);
|
|
42
|
-
* - deferral is SESSION-SCOPED (canDefer): only sessions registered as
|
|
43
|
-
* carrying our injected ask set may have env reads passed through —
|
|
44
|
-
* registration comes from an exec-role user prompt (the live-proven
|
|
45
|
-
* `message.updated` UserMessage{role,agent,sessionID} signal, plus the
|
|
46
|
-
* documented `chat.message` hook) or from an R6-env-classified asked
|
|
47
|
-
* event. Stock build/plan sessions never register, so the global R6
|
|
48
|
-
* hook keeps hard-throwing there — closing the R6 bypass the global
|
|
49
|
-
* `isArmed()` deferral allowed;
|
|
50
|
-
* - if the SDK reply FAILS (network/host — including the SDK's
|
|
51
|
-
* `throwOnError:false` envelope resolving with `{ error }`), we cannot
|
|
52
|
-
* close the dialog, so the gate flips itself permanently "degraded" —
|
|
53
|
-
* the R6 hook stops deferring env reads and hard-throws them again
|
|
54
|
-
* (fail-closed), and the event is audited "degraded".
|
|
55
|
-
*
|
|
56
|
-
* SDK surface (probed live): the host client may carry NO `permission`
|
|
57
|
-
* namespace and NO list/create endpoint; the reply path is the top-level
|
|
58
|
-
* `postSessionIdPermissionsPermissionId({ path: { id: sessionID,
|
|
59
|
-
* permissionID }, body: { response } })`. `client.permission.reply` /
|
|
60
|
-
* `client.permission.list` are still tried first so a richer host build is
|
|
61
|
-
* supported, but when they are absent the poll fallback is a no-op (events
|
|
62
|
-
* are the sole pending signal).
|
|
63
|
-
*/
|
|
64
|
-
import type { PermissionEvent } from "./types.js";
|
|
65
|
-
/** Default timeout, in minutes, before an unanswered dialog is auto-rejected.
|
|
66
|
-
* 1 min (user directive) — an unanswered dialog no longer hangs for 10.
|
|
67
|
-
* A short timer is safe because a reject that lands on an already-closed
|
|
68
|
-
* request is benign; see `MIN_ASK_TIMEOUT_MIN` for the full rationale. */
|
|
69
|
-
export declare const DEFAULT_ASK_TIMEOUT_MIN = 1;
|
|
70
|
-
/**
|
|
71
|
-
* Hard floor (minutes) for the auto-reject timeout. The HISTORIC 3-min
|
|
72
|
-
* floor existed to dodge the measured ~120s `permission.replied` bus lag:
|
|
73
|
-
* a shorter timer would fire before the cancel arrives and auto-reject a
|
|
74
|
-
* just-approved request. That lag, though, is the HOST DELIVERING the
|
|
75
|
-
* replied event to the plugin over the bus — NOT the host resolving the
|
|
76
|
-
* user's click: an approval takes effect instantly host-side, so the
|
|
77
|
-
* plugin's late reject simply targets an already-closed request id → 404.
|
|
78
|
-
* T2's `classifyReplyFailure` maps that outcome to the benign
|
|
79
|
-
* `already-closed` verdict (it never flips the gate to `degraded`, and the
|
|
80
|
-
* approved command runs either way); `onReplied` records a reply landing
|
|
81
|
-
* after the auto-reject as `late-<verdict>` for observability. With that
|
|
82
|
-
* classification in place a short timeout no longer disables the gate, so
|
|
83
|
-
* the floor drops to 1 minute (user directive). Trade-off: an UNANSWERED
|
|
84
|
-
* dialog auto-rejects after 1 min — a user who stepped away re-triggers
|
|
85
|
-
* the command. In-range values below the floor still clamp UP to it.
|
|
86
|
-
*
|
|
87
|
-
* This is the FALLBACK default of the floor. The live floor comes from the
|
|
88
|
-
* P0 config knob `TM_ASK_TIMEOUT_FLOOR_MIN`
|
|
89
|
-
* (`resolveTmConfig().askTimeoutFloorMin`, itself defaulted to 1).
|
|
90
|
-
*/
|
|
91
|
-
export declare const MIN_ASK_TIMEOUT_MIN = 1;
|
|
92
|
-
/** The verdict vocabulary recorded in the audit trail (privacy: no command
|
|
93
|
-
* text, path, variable name or value is ever logged). `already-closed` /
|
|
94
|
-
* `rejected-shape-bug` split the old blanket `degraded` on a failed reply by
|
|
95
|
-
* `classifyReplyFailure`; a late reply past the auto-reject is recorded as a
|
|
96
|
-
* dynamic `late-<verdict>` string (observability only — never re-runs or
|
|
97
|
-
* revives the command). */
|
|
98
|
-
export type ApprovalVerdict = "ask" | "allowed-once" | "allowed-always" | "rejected" | "timeout-rejected" | "already-closed" | "rejected-shape-bug" | "degraded";
|
|
99
|
-
/**
|
|
100
|
-
* Resolve `TM_ASK_TIMEOUT_MIN`. Unset / blank / non-numeric / <1 / >1440
|
|
101
|
-
* (24h) fall back to the 1-minute default — a mistyped value can never
|
|
102
|
-
* disable the timeout or make it absurdly long. Valid values below the
|
|
103
|
-
* floor clamp UP to it — the floor is the P0 config knob
|
|
104
|
-
* `TM_ASK_TIMEOUT_FLOOR_MIN` (`resolveTmConfig().askTimeoutFloorMin`,
|
|
105
|
-
* default 1). The historic ~120s bus-lag reason for a 3-min floor is
|
|
106
|
-
* obsolete: a late reject on an already-closed id is benign
|
|
107
|
-
* `already-closed`, not a degraded flip (see `MIN_ASK_TIMEOUT_MIN`).
|
|
108
|
-
*/
|
|
109
|
-
export declare function resolveAskTimeoutMs(env?: Record<string, string | undefined>): number;
|
|
110
|
-
type TimerHandle = unknown;
|
|
111
|
-
export interface GateTimers {
|
|
112
|
-
setTimeoutFn: (cb: () => void, ms: number) => TimerHandle;
|
|
113
|
-
clearTimeoutFn: (handle: TimerHandle) => void;
|
|
114
|
-
}
|
|
115
|
-
export interface ApprovalGateDeps {
|
|
116
|
-
client: unknown;
|
|
117
|
-
timeoutMs: number;
|
|
118
|
-
/** Injectable timers (default: real global timers) for deterministic tests. */
|
|
119
|
-
timers?: GateTimers;
|
|
120
|
-
/** Injectable clock (default: Date.now) for deterministic window tests. */
|
|
121
|
-
now?: () => number;
|
|
122
|
-
/** Best-effort attention hook — fired on EVERY permission.asked (all
|
|
123
|
-
* dialogs: bash R2/R6 asks AND the tm_* ctx.ask web dialogs) so a user
|
|
124
|
-
* not staring at the screen learns a confirmation is waiting. The caller
|
|
125
|
-
* wires this to the host's `tui.showToast`. Never throws into the gate. */
|
|
126
|
-
notify?: (message: string) => void;
|
|
127
|
-
}
|
|
128
|
-
export interface ApprovalGate {
|
|
129
|
-
/** Handle one host event (permission.asked/updated / permission.replied). */
|
|
130
|
-
handleEvent(event: PermissionEvent): void;
|
|
131
|
-
/** Start the poll fallback (no-op when the host has no list endpoint). */
|
|
132
|
-
start(): void;
|
|
133
|
-
/** Stop the poll + every pending timer. */
|
|
134
|
-
dispose(): void;
|
|
135
|
-
/** Global gate health: armed, not degraded, and the client can reply. */
|
|
136
|
-
isArmed(): boolean;
|
|
137
|
-
/**
|
|
138
|
-
* The R6 hook's deferral query: TRUE only when this exact session is
|
|
139
|
-
* registered as carrying our injected ask set AND the gate is healthy.
|
|
140
|
-
* Replaces the old global `isArmed()` deferral (which let stock build/plan
|
|
141
|
-
* sessions pass env reads silently with no dialog in front of them).
|
|
142
|
-
*/
|
|
143
|
-
canDefer(sessionID?: string): boolean;
|
|
144
|
-
/** True once a session registered (exec-role prompt seen on
|
|
145
|
-
* message.updated / chat.message by index.ts, or an R6-env-classified
|
|
146
|
-
* asked event). Introspection for tests/diagnostics. */
|
|
147
|
-
hasLiveAsk(sessionID?: string): boolean;
|
|
148
|
-
/** Mark a session as carrying the injected ask set (index.ts calls this
|
|
149
|
-
* when a user prompt routes to an agent we injected the escalated bash
|
|
150
|
-
* ask object into — proven live: `message.updated` UserMessage carries
|
|
151
|
-
* { sessionID, role:"user", agent } and fires BEFORE the session's first
|
|
152
|
-
* tool call, while permission.asked fires AFTER tool.execute.before). */
|
|
153
|
-
registerExecSession(sessionID?: string): void;
|
|
154
|
-
/**
|
|
155
|
-
* Drop a session's exec-role registration (index.ts calls this when a user
|
|
156
|
-
* prompt routes to an agent that does NOT carry our injected ask set).
|
|
157
|
-
* The verified host passes {tool, sessionID, callID} with NO agent to
|
|
158
|
-
* tool.execute.before (desktop binary: `plugin.trigger("tool.execute.before",
|
|
159
|
-
* { tool, sessionID, callID }, { args })`), so the per-turn agent signal
|
|
160
|
-
* can only come from message.updated/chat.message. Without revocation a
|
|
161
|
-
* session that once ran a team prompt stayed deferrable forever — even
|
|
162
|
-
* after the user switched the picker to a stock agent whose dialogs would
|
|
163
|
-
* never fire, silently passing env reads (canDefer true + no dialog).
|
|
164
|
-
* A later env-classified permission.asked re-registers on real dialog
|
|
165
|
-
* evidence, and the next exec-role prompt re-registers again — both paths
|
|
166
|
-
* re-arm from fresh evidence, so revocation only ever closes a stale
|
|
167
|
-
* window (fail-closed direction).
|
|
168
|
-
*/
|
|
169
|
-
revokeExecSession(sessionID?: string): void;
|
|
170
|
-
/** True after the user picked "always" on an env-related ask in this session
|
|
171
|
-
* — subsequent env reads pass silently (the approval event itself is audited). */
|
|
172
|
-
isEnvApproved(sessionID?: string): boolean;
|
|
173
|
-
/** Number of pending requests currently being timed (tests / introspection). */
|
|
174
|
-
pendingSize(): number;
|
|
175
|
-
}
|
|
176
|
-
/** How a FAILED permission-reply call should be treated by the gate. */
|
|
177
|
-
export type ReplyFailureClass = "benign-closed" | "param-shape" | "transport";
|
|
178
|
-
/**
|
|
179
|
-
* Classify a reply-call failure so the gate no longer flips permanently
|
|
180
|
-
* `degraded` on a harmless close. `unwrapSdkEnvelope` has already hoisted the
|
|
181
|
-
* HTTP status onto the thrown envelope error; a genuine `Promise.reject`
|
|
182
|
-
* arrives as a plain Error with no status.
|
|
183
|
-
* - `benign-closed`: 404, or a NotFound/PermissionNotFound name/_tag — the
|
|
184
|
-
* dialog was ALREADY closed on the host (the D4 late-reply race): our
|
|
185
|
-
* reject was a no-op, so the popup path stays trustworthy.
|
|
186
|
-
* - `param-shape`: 400, or a BadRequest/InvalidRequest name/_tag — our
|
|
187
|
-
* reply body/params are wrong for this host: a real integration bug.
|
|
188
|
-
* - `transport`: everything else — a genuine Error, a 5xx, or an empty /
|
|
189
|
-
* status-less body — we could not reach or close the dialog.
|
|
190
|
-
* Reads only the error CLASS + status number (privacy: never message text).
|
|
191
|
-
*/
|
|
192
|
-
export declare function classifyReplyFailure(err: unknown): ReplyFailureClass;
|
|
193
|
-
/** True when the client exposes a permission-reply path at all. index.ts
|
|
194
|
-
* arms the gate ONLY for such a client — fail-closed otherwise (no armed
|
|
195
|
-
* auto-reject ⇒ the R6 hook keeps hard-throwing every env read). */
|
|
196
|
-
export declare function hasPermissionReplyCapability(client: unknown): boolean;
|
|
197
|
-
/**
|
|
198
|
-
* Create the approval gate. It is intentionally pure about timing +
|
|
199
|
-
* replying + session registration; the caller (index.ts) decides whether to
|
|
200
|
-
* arm it (mode != off AND the client is reply-capable), feeds events in, and
|
|
201
|
-
* wires `canDefer(sessionID)` into the R6 hook.
|
|
202
|
-
*/
|
|
203
|
-
export declare function createApprovalGate(deps: ApprovalGateDeps): ApprovalGate;
|
|
204
|
-
export {};
|
|
205
|
-
//# sourceMappingURL=approval-gate.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"approval-gate.d.ts","sourceRoot":"","sources":["../src/approval-gate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8DG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAIjD;;;2EAG2E;AAC3E,eAAO,MAAM,uBAAuB,IAAI,CAAA;AAExC;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,mBAAmB,IAAI,CAAA;AAkBpC;;;;;4BAK4B;AAC5B,MAAM,MAAM,eAAe,GACvB,KAAK,GACL,cAAc,GACd,gBAAgB,GAChB,UAAU,GACV,kBAAkB,GAClB,gBAAgB,GAChB,oBAAoB,GACpB,UAAU,CAAA;AAEd;;;;;;;;;GASG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAe,GAAG,MAAM,CAWjG;AAED,KAAK,WAAW,GAAG,OAAO,CAAA;AAE1B,MAAM,WAAW,UAAU;IACzB,YAAY,EAAE,CAAC,EAAE,EAAE,MAAM,IAAI,EAAE,EAAE,EAAE,MAAM,KAAK,WAAW,CAAA;IACzD,cAAc,EAAE,CAAC,MAAM,EAAE,WAAW,KAAK,IAAI,CAAA;CAC9C;AAED,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,OAAO,CAAA;IACf,SAAS,EAAE,MAAM,CAAA;IACjB,+EAA+E;IAC/E,MAAM,CAAC,EAAE,UAAU,CAAA;IACnB,2EAA2E;IAC3E,GAAG,CAAC,EAAE,MAAM,MAAM,CAAA;IAClB;;;iFAG6E;IAC7E,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAA;CACnC;AAED,MAAM,WAAW,YAAY;IAC3B,6EAA6E;IAC7E,WAAW,CAAC,KAAK,EAAE,eAAe,GAAG,IAAI,CAAA;IACzC,0EAA0E;IAC1E,KAAK,IAAI,IAAI,CAAA;IACb,2CAA2C;IAC3C,OAAO,IAAI,IAAI,CAAA;IACf,yEAAyE;IACzE,OAAO,IAAI,OAAO,CAAA;IAClB;;;;;OAKG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAA;IACrC;;8DAE0D;IAC1D,UAAU,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAA;IACvC;;;;8EAI0E;IAC1E,mBAAmB,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC7C;;;;;;;;;;;;;;OAcG;IACH,iBAAiB,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3C;uFACmF;IACnF,aAAa,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAA;IAC1C,gFAAgF;IAChF,WAAW,IAAI,MAAM,CAAA;CACtB;AAuED,wEAAwE;AACxE,MAAM,MAAM,iBAAiB,GAAG,eAAe,GAAG,aAAa,GAAG,WAAW,CAAA;AAE7E;;;;;;;;;;;;;GAaG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,OAAO,GAAG,iBAAiB,CAgBpE;AA8DD;;qEAEqE;AACrE,wBAAgB,4BAA4B,CAAC,MAAM,EAAE,OAAO,GAAG,OAAO,CAErE;AAyBD;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,gBAAgB,GAAG,YAAY,CAqUvE"}
|