@te-river/opencode-team-mode 1.5.0 → 1.5.2
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 +157 -53
- package/README.zh-CN.md +137 -53
- package/dist/agents.d.ts.map +1 -1
- package/dist/agents.js +128 -26
- package/dist/agents.js.map +1 -1
- package/dist/approval-gate.d.ts +147 -0
- package/dist/approval-gate.d.ts.map +1 -0
- package/dist/approval-gate.js +471 -0
- package/dist/approval-gate.js.map +1 -0
- package/dist/commands.js +3 -3
- package/dist/commands.js.map +1 -1
- package/dist/envprotect.d.ts +234 -0
- package/dist/envprotect.d.ts.map +1 -0
- package/dist/envprotect.js +820 -0
- package/dist/envprotect.js.map +1 -0
- package/dist/index.d.ts +5 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +112 -1
- package/dist/index.js.map +1 -1
- package/dist/tm/config.d.ts +105 -0
- package/dist/tm/config.d.ts.map +1 -0
- package/dist/tm/config.js +118 -0
- package/dist/tm/config.js.map +1 -0
- package/dist/tm/guard.d.ts +56 -0
- package/dist/tm/guard.d.ts.map +1 -0
- package/dist/tm/guard.js +249 -0
- package/dist/tm/guard.js.map +1 -0
- package/dist/tm/index.d.ts +41 -0
- package/dist/tm/index.d.ts.map +1 -0
- package/dist/tm/index.js +93 -0
- package/dist/tm/index.js.map +1 -0
- package/dist/tm/preview.d.ts +46 -0
- package/dist/tm/preview.d.ts.map +1 -0
- package/dist/tm/preview.js +354 -0
- package/dist/tm/preview.js.map +1 -0
- package/dist/tm/ptc.d.ts +279 -0
- package/dist/tm/ptc.d.ts.map +1 -0
- package/dist/tm/ptc.js +839 -0
- package/dist/tm/ptc.js.map +1 -0
- package/dist/tm/refs.d.ts +37 -0
- package/dist/tm/refs.d.ts.map +1 -0
- package/dist/tm/refs.js +58 -0
- package/dist/tm/refs.js.map +1 -0
- package/dist/tm/store.d.ts +95 -0
- package/dist/tm/store.d.ts.map +1 -0
- package/dist/tm/store.js +222 -0
- package/dist/tm/store.js.map +1 -0
- package/dist/tm/tools.d.ts +84 -0
- package/dist/tm/tools.d.ts.map +1 -0
- package/dist/tm/tools.js +710 -0
- package/dist/tm/tools.js.map +1 -0
- package/dist/types.d.ts +155 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +3 -2
package/dist/agents.js
CHANGED
|
@@ -23,6 +23,75 @@
|
|
|
23
23
|
* - All agents run at temperature 0.2 for format discipline.
|
|
24
24
|
*/
|
|
25
25
|
/* ------------------------------------------------------------------ */
|
|
26
|
+
/* Tool whitelist builder (Phase 2 / T2.1, G2 ruling "方案甲") */
|
|
27
|
+
/* ------------------------------------------------------------------ */
|
|
28
|
+
/**
|
|
29
|
+
* Builds the permission block that IS the agent's tool whitelist.
|
|
30
|
+
*
|
|
31
|
+
* Verified live (T0.4③): a "deny" permission removes the built-in tool
|
|
32
|
+
* from the model's tool surface entirely — zero bypass, zero hallucinated
|
|
33
|
+
* calls. So a whitelist = deny every excluded built-in + allow the kept
|
|
34
|
+
* set, exactly the shape the D6 probe validated
|
|
35
|
+
* (%TEMP%/opencode/tm-probe/workspace/opencode.json).
|
|
36
|
+
*
|
|
37
|
+
* G2 rulings baked in:
|
|
38
|
+
* - glob/list never enter the whitelist — file enumeration goes through
|
|
39
|
+
* tm_bash (ls / dir / Get-ChildItem);
|
|
40
|
+
* - webfetch/websearch excluded network-wide (P5 network zero);
|
|
41
|
+
* - "tm_*" covers exactly the four governed tools (tm_read / tm_grep /
|
|
42
|
+
* tm_bash / tm_fetch); the R6 env-protection hook aliases onto them
|
|
43
|
+
* unchanged, so narrowing the surface does not weaken the anti-backdoor
|
|
44
|
+
* chain.
|
|
45
|
+
*
|
|
46
|
+
* T2.1 review revision (Critical fix): the built-in bash RETURNS for the
|
|
47
|
+
* execution roles (team / implementer / reviewer / tester). G2 only ruled
|
|
48
|
+
* that glob/list enumeration goes through tm_bash — it never ruled away
|
|
49
|
+
* command execution, and tm_bash is a read-only allowlist that cannot run
|
|
50
|
+
* npm test / tsc / --help probes. R6 keeps governing bash's env surface
|
|
51
|
+
* through the envprotect hook, independently of this matrix.
|
|
52
|
+
*/
|
|
53
|
+
const NEVER_ALLOWED = [
|
|
54
|
+
"read",
|
|
55
|
+
"grep",
|
|
56
|
+
"glob",
|
|
57
|
+
"list",
|
|
58
|
+
"apply_patch",
|
|
59
|
+
"webfetch",
|
|
60
|
+
"websearch",
|
|
61
|
+
"todowrite",
|
|
62
|
+
"lsp",
|
|
63
|
+
"skill",
|
|
64
|
+
"question",
|
|
65
|
+
];
|
|
66
|
+
/** Built-ins granted per agent; every one NOT granted is denied. */
|
|
67
|
+
const PER_AGENT_TOOLS = ["edit", "write", "task", "bash"];
|
|
68
|
+
/** The governed tools, named explicitly next to the "tm_*" wildcard
|
|
69
|
+
* (belt-and-braces: the explicit allows survive even if a host ever
|
|
70
|
+
* stops expanding the wildcard). */
|
|
71
|
+
const TM_TOOLS = ["tm_read", "tm_grep", "tm_bash", "tm_fetch"];
|
|
72
|
+
/** M3: tm_ptc_run — explicit per-agent grant (five specialists = allow,
|
|
73
|
+
* team = deny to override the tm_* wildcard). Design §3: PTC is a
|
|
74
|
+
* governed, read-only bridge; the team lead orchestrates, it does not
|
|
75
|
+
* run batch programs itself. */
|
|
76
|
+
const PTC_TOOL = "tm_ptc_run";
|
|
77
|
+
const whitelist = (...granted) => {
|
|
78
|
+
const permission = {};
|
|
79
|
+
for (const tool of NEVER_ALLOWED)
|
|
80
|
+
permission[tool] = "deny";
|
|
81
|
+
for (const tool of PER_AGENT_TOOLS) {
|
|
82
|
+
permission[tool] = granted.includes(tool) ? "allow" : "deny";
|
|
83
|
+
}
|
|
84
|
+
for (const tool of TM_TOOLS)
|
|
85
|
+
permission[tool] = "allow";
|
|
86
|
+
permission["tm_*"] = "allow";
|
|
87
|
+
return permission;
|
|
88
|
+
};
|
|
89
|
+
/** Apply the M3 tm_ptc_run grant to an agent's permission block. The five
|
|
90
|
+
* specialists get `allow`; the team lead gets `deny` (overrides the wildcard). */
|
|
91
|
+
function applyPtcPermission(permission, isTeamLead) {
|
|
92
|
+
permission[PTC_TOOL] = isTeamLead ? "deny" : "allow";
|
|
93
|
+
}
|
|
94
|
+
/* ------------------------------------------------------------------ */
|
|
26
95
|
/* Reply contract + hybrid blackboard (appended to every specialist) */
|
|
27
96
|
/* ------------------------------------------------------------------ */
|
|
28
97
|
const REPLY_CONTRACT = `
|
|
@@ -74,8 +143,9 @@ process management every run.
|
|
|
74
143
|
## Triage — classify before acting (Step 0, always)
|
|
75
144
|
- Question ≠ work order. When the user asks, analyzes, or consults
|
|
76
145
|
("why does X fail?", "how would we do Y?"), ANSWER it — read code if
|
|
77
|
-
useful, change nothing. If answering needs
|
|
78
|
-
\`researcher\`; don't
|
|
146
|
+
useful, change nothing. If answering needs a deeper dig through the
|
|
147
|
+
codebase than your context affords, dispatch \`researcher\`; don't
|
|
148
|
+
grind through it yourself.
|
|
79
149
|
- Spotted an obvious defect while answering? Propose the fix and WAIT for
|
|
80
150
|
the go-ahead — never fix-on-the-sly.
|
|
81
151
|
- Explicit action request ("fix X", "add Y", "refactor Z") → route via the
|
|
@@ -96,7 +166,7 @@ files — NOT docs, comments, formatting, NOT *.test.* files).
|
|
|
96
166
|
| Docs / comments / formatting only | implementer (or direct edit, see below) |
|
|
97
167
|
| Product behavior change (bug fix, small feature) | implementer → tester → reviewer |
|
|
98
168
|
| Multi-module / cross-interface feature | architect → implementer → tester → reviewer(s) |
|
|
99
|
-
|
|
|
169
|
+
| Unfamiliar tech / dependency in play | researcher first (local-repo evidence: call sites, installed/vendored packages, shipped docs), then the fitting row above |
|
|
100
170
|
|
|
101
171
|
- FIXED MINIMUM PIPELINES: the reviewer may be skipped ONLY for
|
|
102
172
|
non-product artifacts, with a one-line reason. A product change routed
|
|
@@ -123,8 +193,9 @@ Count the dispatches your routing row prescribes:
|
|
|
123
193
|
ONLY when it is genuinely absent. These docs define the conventions
|
|
124
194
|
the whole team must follow; distill the binding ones for your
|
|
125
195
|
dispatches. Then read the relevant source yourself; dispatch
|
|
126
|
-
\`researcher\` ONLY for genuinely
|
|
127
|
-
|
|
196
|
+
\`researcher\` ONLY for genuinely unfamiliar tech — its findings come
|
|
197
|
+
from the local repo (code, docs, installed packages), never the web.
|
|
198
|
+
Done means you can state which files change, in what order, and the risks.
|
|
128
199
|
- Plan (≤30 lines): Goal / Root cause or scope (file:line evidence) /
|
|
129
200
|
Change list (file → what) / Pipeline (routing row + agents) /
|
|
130
201
|
Assumptions & risks / Open questions.
|
|
@@ -244,8 +315,8 @@ Findings that drive architecture or API usage must be verified before
|
|
|
244
315
|
adoption:
|
|
245
316
|
- The researcher tags each finding High / Medium / Low confidence.
|
|
246
317
|
- Low/medium-confidence claims that affect the design get a second check
|
|
247
|
-
(re-ask the researcher for
|
|
248
|
-
actual codebase).
|
|
318
|
+
(re-ask the researcher for a second local source, or sanity-check against
|
|
319
|
+
the actual codebase).
|
|
249
320
|
- Never let an unverified claim silently become an implementation
|
|
250
321
|
decision; list remaining assumptions explicitly in the final report.
|
|
251
322
|
|
|
@@ -272,12 +343,10 @@ If a target file does not exist, offer to create it; skip both when the
|
|
|
272
343
|
user opted out.
|
|
273
344
|
`,
|
|
274
345
|
color: "#E879F9", // purple
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
task: "allow",
|
|
280
|
-
},
|
|
346
|
+
// Whitelist (7 tools): tm_* x4 + task dispatch + edit (<=10-line
|
|
347
|
+
// non-product edits, see "When you may edit directly") + write (board
|
|
348
|
+
// files) + bash (discovery-gate probes: --help, installed versions).
|
|
349
|
+
permission: whitelist("task", "edit", "write", "bash"),
|
|
281
350
|
temperature: 0.2,
|
|
282
351
|
};
|
|
283
352
|
/* ------------------------------------------------------------------ */
|
|
@@ -320,9 +389,12 @@ When the team lead sends back a design flaw found in review or testing:
|
|
|
320
389
|
of guessing about the codebase.
|
|
321
390
|
`,
|
|
322
391
|
color: "#38BDF8", // sky blue
|
|
323
|
-
//
|
|
324
|
-
//
|
|
325
|
-
|
|
392
|
+
// Whitelist (5 tools): tm_* x4 + task dispatch. Fully read-only by
|
|
393
|
+
// design (T2.1): output lives in the reply; if a board artifact is ever
|
|
394
|
+
// dispatched, the BLACKBOARD WRITE FAILED fallback hands the content to
|
|
395
|
+
// the lead inline (see Blackboard rules). No bash — unchanged from the
|
|
396
|
+
// pre-T2.1 architect bash:deny posture.
|
|
397
|
+
permission: whitelist("task"),
|
|
326
398
|
temperature: 0.2,
|
|
327
399
|
};
|
|
328
400
|
/* ------------------------------------------------------------------ */
|
|
@@ -359,7 +431,9 @@ to you by the team lead.
|
|
|
359
431
|
check, the previously failing test).
|
|
360
432
|
`,
|
|
361
433
|
color: "#4ADE80", // green
|
|
362
|
-
|
|
434
|
+
// Whitelist (7 tools): tm_* x4 + edit/write (code implementation) + bash
|
|
435
|
+
// (narrowest verification for the fix: build / typecheck / failing test).
|
|
436
|
+
permission: whitelist("edit", "write", "bash"),
|
|
363
437
|
temperature: 0.2,
|
|
364
438
|
};
|
|
365
439
|
/* ------------------------------------------------------------------ */
|
|
@@ -417,9 +491,12 @@ the previously flagged scope plus regressions introduced by the fixes;
|
|
|
417
491
|
confirm each prior finding item by item (fixed / not fixed / partial).
|
|
418
492
|
`,
|
|
419
493
|
color: "#FB923C", // orange
|
|
420
|
-
//
|
|
421
|
-
// (
|
|
422
|
-
|
|
494
|
+
// Whitelist (6 tools): tm_* x4 + task dispatch + bash. No edit/write
|
|
495
|
+
// (T2.1): findings travel in the reply skeleton; a dispatched board
|
|
496
|
+
// artifact rides the BLACKBOARD WRITE FAILED inline fallback (see
|
|
497
|
+
// Blackboard rules). bash backs the evidence standard — the reviewer
|
|
498
|
+
// must be able to run npm test / tsc itself.
|
|
499
|
+
permission: whitelist("task", "bash"),
|
|
423
500
|
temperature: 0.2,
|
|
424
501
|
};
|
|
425
502
|
/* ------------------------------------------------------------------ */
|
|
@@ -483,7 +560,9 @@ worse than admitting the gap.
|
|
|
483
560
|
refactor instead of contorting the test.
|
|
484
561
|
`,
|
|
485
562
|
color: "#F472B6", // pink
|
|
486
|
-
|
|
563
|
+
// Whitelist (7 tools): tm_* x4 + edit/write (test files) + bash (the
|
|
564
|
+
// whole verification stack: build / typecheck / lint / test runs).
|
|
565
|
+
permission: whitelist("edit", "write", "bash"),
|
|
487
566
|
temperature: 0.2,
|
|
488
567
|
};
|
|
489
568
|
/* ------------------------------------------------------------------ */
|
|
@@ -491,10 +570,11 @@ worse than admitting the gap.
|
|
|
491
570
|
/* ------------------------------------------------------------------ */
|
|
492
571
|
const researcher = {
|
|
493
572
|
mode: "subagent",
|
|
494
|
-
description: "Researcher — investigates
|
|
495
|
-
"documentation; every finding
|
|
496
|
-
"
|
|
497
|
-
"must inform a
|
|
573
|
+
description: "Researcher — investigates the local repository: code, configs, " +
|
|
574
|
+
"installed/vendored packages, and shipped documentation; every finding " +
|
|
575
|
+
"carries a source (file:line) and confidence tag so the team can decide " +
|
|
576
|
+
"what needs verification. Use for information that must inform a " +
|
|
577
|
+
"technical decision. Web research is out of scope (no network tools).",
|
|
498
578
|
prompt: `You are the **Researcher** on a multi-agent coding team.
|
|
499
579
|
|
|
500
580
|
## Role
|
|
@@ -534,7 +614,13 @@ by the team — flag prominently if that is the case.
|
|
|
534
614
|
of re-deriving.
|
|
535
615
|
`,
|
|
536
616
|
color: "#A78BFA", // violet
|
|
537
|
-
|
|
617
|
+
// Whitelist (4 tools): tm_* x4 ONLY — intentionally below the 5-7 band
|
|
618
|
+
// (T2.1): pure local research. webfetch/websearch are excluded
|
|
619
|
+
// network-wide (P5), which also removes the dangling websearch:allow
|
|
620
|
+
// found in T0.3; file enumeration goes through tm_bash (G2 方案甲).
|
|
621
|
+
// No bash / no execution rights is intentional trimming — local
|
|
622
|
+
// research reads, it does not run.
|
|
623
|
+
permission: whitelist(),
|
|
538
624
|
temperature: 0.2,
|
|
539
625
|
};
|
|
540
626
|
/* Shared rules appended to every specialist prompt (after the contract). */
|
|
@@ -545,6 +631,14 @@ Every "done / fixed / passed" claim in your reply must carry its
|
|
|
545
631
|
evidence: command output, log lines, or a diff. No narrative-only
|
|
546
632
|
completions.
|
|
547
633
|
|
|
634
|
+
## Tool surface (do not retry removed tools)
|
|
635
|
+
All file reads / searches / enumeration go through tm_read / tm_grep / tm_bash.
|
|
636
|
+
The built-in read/grep/glob/list tools are removed from the tool surface —
|
|
637
|
+
retrying them only wastes a turn. Built-in bash exists only where granted
|
|
638
|
+
(team / implementer / reviewer / tester run commands: build / test / git);
|
|
639
|
+
architect and researcher have no bash at all — one-off read-only commands
|
|
640
|
+
go through tm_bash or are reported as a gap.
|
|
641
|
+
|
|
548
642
|
## Project conventions
|
|
549
643
|
If the project README (or AGENTS.md) is quoted in your dispatch, treat
|
|
550
644
|
its conventions as binding — they outrank your defaults. Do not re-open
|
|
@@ -555,6 +649,14 @@ belongs to the work.`;
|
|
|
555
649
|
for (const a of [architect, implementer, reviewer, tester, researcher]) {
|
|
556
650
|
a.prompt = (a.prompt ?? "") + REPLY_CONTRACT + SHARED_RULES;
|
|
557
651
|
}
|
|
652
|
+
/* M3: tm_ptc_run permission — five specialists get allow, team gets deny
|
|
653
|
+
* (overrides the tm_* wildcard). Applied once at module init. */
|
|
654
|
+
if (teamLead.permission)
|
|
655
|
+
applyPtcPermission(teamLead.permission, true);
|
|
656
|
+
for (const a of [architect, implementer, reviewer, tester, researcher]) {
|
|
657
|
+
if (a.permission)
|
|
658
|
+
applyPtcPermission(a.permission, false);
|
|
659
|
+
}
|
|
558
660
|
/* ------------------------------------------------------------------ */
|
|
559
661
|
/* Export all agents keyed by name */
|
|
560
662
|
/* ------------------------------------------------------------------ */
|
package/dist/agents.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agents.js","sourceRoot":"","sources":["../src/agents.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAIH,wEAAwE;AACxE,yEAAyE;AACzE,wEAAwE;AACxE,MAAM,cAAc,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;+DA0BwC,CAAA;AAE/D,wEAAwE;AACxE,wEAAwE;AACxE,wEAAwE;AACxE,MAAM,QAAQ,GAAgB;IAC5B,IAAI,EAAE,SAAS;IACf,WAAW,EACT,4DAA4D;QAC5D,qEAAqE;QACrE,qEAAqE;QACrE,oEAAoE;QACpE,6DAA6D;QAC7D,4BAA4B;IAC9B,MAAM,EAAE
|
|
1
|
+
{"version":3,"file":"agents.js","sourceRoot":"","sources":["../src/agents.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,147 @@
|
|
|
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 (index.ts 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 v1
|
|
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 on 1.18.x): the plugin's `input.client` is the v1
|
|
57
|
+
* `OpencodeClient`, which has NO `permission` namespace and NO list/create
|
|
58
|
+
* endpoint; the reply path is the top-level
|
|
59
|
+
* `postSessionIdPermissionsPermissionId({ path: { id: sessionID,
|
|
60
|
+
* permissionID }, body: { response } })`. `client.permission.reply` /
|
|
61
|
+
* `client.permission.list` are still tried first so a richer host build is
|
|
62
|
+
* supported, but on v1 they are simply absent and the poll fallback is a
|
|
63
|
+
* no-op (events are the sole pending signal there).
|
|
64
|
+
*/
|
|
65
|
+
import type { PermissionEvent } from "./types.js";
|
|
66
|
+
/** Default timeout, in minutes, before an unanswered dialog is auto-rejected. */
|
|
67
|
+
export declare const DEFAULT_ASK_TIMEOUT_MIN = 10;
|
|
68
|
+
/**
|
|
69
|
+
* Hard floor (minutes) for the auto-reject timeout. Measured live on the
|
|
70
|
+
* OpenCode Desktop: a `permission.replied` (human answered "once") reaches
|
|
71
|
+
* the plugin through the host event bus ~120s LATE — a 1-minute timer
|
|
72
|
+
* therefore fires BEFORE the cancel arrives and auto-rejects the just-
|
|
73
|
+
* approved request on a dead id (4xx → permanent degraded, the D4
|
|
74
|
+
* double-reject race). 3 min = observed ~120s lag + margin; in-range
|
|
75
|
+
* values below the floor clamp UP to it (a longer timeout is always the
|
|
76
|
+
* safe direction — the dialog just stays open a bit longer).
|
|
77
|
+
*/
|
|
78
|
+
export declare const MIN_ASK_TIMEOUT_MIN = 3;
|
|
79
|
+
/** The verdict vocabulary recorded in the audit trail (privacy: no command
|
|
80
|
+
* text, path, variable name or value is ever logged). */
|
|
81
|
+
export type ApprovalVerdict = "ask" | "allowed-once" | "allowed-always" | "rejected" | "timeout-rejected" | "degraded";
|
|
82
|
+
/**
|
|
83
|
+
* Resolve `TM_ASK_TIMEOUT_MIN`. Unset / blank / non-numeric / <1 / >1440
|
|
84
|
+
* (24h) fall back to the 10-minute default — a mistyped value can never
|
|
85
|
+
* disable the timeout or make it absurdly long. Valid values below the
|
|
86
|
+
* 3-minute bus-lag floor clamp UP to it (1→3, 2→3; see
|
|
87
|
+
* `MIN_ASK_TIMEOUT_MIN` for the measured ~120s replied-event lag).
|
|
88
|
+
*/
|
|
89
|
+
export declare function resolveAskTimeoutMs(env?: Record<string, string | undefined>): number;
|
|
90
|
+
type TimerHandle = unknown;
|
|
91
|
+
export interface GateTimers {
|
|
92
|
+
setTimeoutFn: (cb: () => void, ms: number) => TimerHandle;
|
|
93
|
+
clearTimeoutFn: (handle: TimerHandle) => void;
|
|
94
|
+
}
|
|
95
|
+
export interface ApprovalGateDeps {
|
|
96
|
+
client: unknown;
|
|
97
|
+
timeoutMs: number;
|
|
98
|
+
/** Injectable timers (default: real global timers) for deterministic tests. */
|
|
99
|
+
timers?: GateTimers;
|
|
100
|
+
/** Injectable clock (default: Date.now) for deterministic window tests. */
|
|
101
|
+
now?: () => number;
|
|
102
|
+
}
|
|
103
|
+
export interface ApprovalGate {
|
|
104
|
+
/** Handle one host event (permission.asked/updated / permission.replied). */
|
|
105
|
+
handleEvent(event: PermissionEvent): void;
|
|
106
|
+
/** Start the poll fallback (no-op on v1 where no list endpoint exists). */
|
|
107
|
+
start(): void;
|
|
108
|
+
/** Stop the poll + every pending timer. */
|
|
109
|
+
dispose(): void;
|
|
110
|
+
/** Global gate health: armed, not degraded, and the client can reply. */
|
|
111
|
+
isArmed(): boolean;
|
|
112
|
+
/**
|
|
113
|
+
* The R6 hook's deferral query: TRUE only when this exact session is
|
|
114
|
+
* registered as carrying our injected ask set AND the gate is healthy.
|
|
115
|
+
* Replaces the old global `isArmed()` deferral (which let stock build/plan
|
|
116
|
+
* sessions pass env reads silently with no dialog in front of them).
|
|
117
|
+
*/
|
|
118
|
+
canDefer(sessionID?: string): boolean;
|
|
119
|
+
/** True once a session registered (exec-role prompt seen on
|
|
120
|
+
* message.updated / chat.message by index.ts, or an R6-env-classified
|
|
121
|
+
* asked event). Introspection for tests/diagnostics. */
|
|
122
|
+
hasLiveAsk(sessionID?: string): boolean;
|
|
123
|
+
/** Mark a session as carrying the injected ask set (index.ts calls this
|
|
124
|
+
* when a user prompt routes to an agent we injected the escalated bash
|
|
125
|
+
* ask object into — proven live: `message.updated` UserMessage carries
|
|
126
|
+
* { sessionID, role:"user", agent } and fires BEFORE the session's first
|
|
127
|
+
* tool call, while permission.asked fires AFTER tool.execute.before). */
|
|
128
|
+
registerExecSession(sessionID?: string): void;
|
|
129
|
+
/** True after the user picked "always" on an env-related ask in this session
|
|
130
|
+
* — subsequent env reads pass silently (the approval event itself is audited). */
|
|
131
|
+
isEnvApproved(sessionID?: string): boolean;
|
|
132
|
+
/** Number of pending requests currently being timed (tests / introspection). */
|
|
133
|
+
pendingSize(): number;
|
|
134
|
+
}
|
|
135
|
+
/** True when the client exposes a permission-reply path at all. index.ts
|
|
136
|
+
* arms the gate ONLY for such a client — fail-closed otherwise (no armed
|
|
137
|
+
* auto-reject ⇒ the R6 hook keeps hard-throwing every env read). */
|
|
138
|
+
export declare function hasPermissionReplyCapability(client: unknown): boolean;
|
|
139
|
+
/**
|
|
140
|
+
* Create the approval gate. It is intentionally pure about timing +
|
|
141
|
+
* replying + session registration; the caller (index.ts) decides whether to
|
|
142
|
+
* arm it (mode != off AND the client is reply-capable), feeds events in, and
|
|
143
|
+
* wires `canDefer(sessionID)` into the R6 hook.
|
|
144
|
+
*/
|
|
145
|
+
export declare function createApprovalGate(deps: ApprovalGateDeps): ApprovalGate;
|
|
146
|
+
export {};
|
|
147
|
+
//# sourceMappingURL=approval-gate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"approval-gate.d.ts","sourceRoot":"","sources":["../src/approval-gate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+DG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAGjD,iFAAiF;AACjF,eAAO,MAAM,uBAAuB,KAAK,CAAA;AAEzC;;;;;;;;;GASG;AACH,eAAO,MAAM,mBAAmB,IAAI,CAAA;AAiBpC;0DAC0D;AAC1D,MAAM,MAAM,eAAe,GACvB,KAAK,GACL,cAAc,GACd,gBAAgB,GAChB,UAAU,GACV,kBAAkB,GAClB,UAAU,CAAA;AAEd;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAe,GAAG,MAAM,CAQjG;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;CACnB;AAED,MAAM,WAAW,YAAY;IAC3B,6EAA6E;IAC7E,WAAW,CAAC,KAAK,EAAE,eAAe,GAAG,IAAI,CAAA;IACzC,2EAA2E;IAC3E,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;uFACmF;IACnF,aAAa,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAA;IAC1C,gFAAgF;IAChF,WAAW,IAAI,MAAM,CAAA;CACtB;AA6GD;;qEAEqE;AACrE,wBAAgB,4BAA4B,CAAC,MAAM,EAAE,OAAO,GAAG,OAAO,CAErE;AAgBD;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,gBAAgB,GAAG,YAAY,CA6OvE"}
|