@te-river/opencode-team-mode 1.5.5 → 1.5.6

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.
Files changed (158) hide show
  1. package/README.md +732 -634
  2. package/README.zh-CN.md +655 -576
  3. package/dist/agents.d.ts +6 -1
  4. package/dist/agents.d.ts.map +1 -1
  5. package/dist/agents.js +103 -531
  6. package/dist/agents.js.map +1 -1
  7. package/dist/approval-gate.d.ts +16 -0
  8. package/dist/approval-gate.d.ts.map +1 -1
  9. package/dist/approval-gate.js +8 -1
  10. package/dist/approval-gate.js.map +1 -1
  11. package/dist/blackboard.d.ts.map +1 -1
  12. package/dist/blackboard.js +3 -2
  13. package/dist/blackboard.js.map +1 -1
  14. package/dist/commands.d.ts.map +1 -1
  15. package/dist/commands.js +7 -6
  16. package/dist/commands.js.map +1 -1
  17. package/dist/envprotect/bash-classify.d.ts +42 -0
  18. package/dist/envprotect/bash-classify.d.ts.map +1 -0
  19. package/dist/envprotect/bash-classify.js +221 -0
  20. package/dist/envprotect/bash-classify.js.map +1 -0
  21. package/dist/envprotect/gate-predicates.d.ts +57 -0
  22. package/dist/envprotect/gate-predicates.d.ts.map +1 -0
  23. package/dist/envprotect/gate-predicates.js +174 -0
  24. package/dist/envprotect/gate-predicates.js.map +1 -0
  25. package/dist/envprotect/hook.d.ts +45 -0
  26. package/dist/envprotect/hook.d.ts.map +1 -0
  27. package/dist/envprotect/hook.js +158 -0
  28. package/dist/envprotect/hook.js.map +1 -0
  29. package/dist/envprotect/path-classify.d.ts +16 -0
  30. package/dist/envprotect/path-classify.d.ts.map +1 -0
  31. package/dist/envprotect/path-classify.js +72 -0
  32. package/dist/envprotect/path-classify.js.map +1 -0
  33. package/dist/envprotect/patterns.d.ts +77 -0
  34. package/dist/envprotect/patterns.d.ts.map +1 -0
  35. package/dist/envprotect/patterns.js +182 -0
  36. package/dist/envprotect/patterns.js.map +1 -0
  37. package/dist/envprotect.d.ts +19 -217
  38. package/dist/envprotect.d.ts.map +1 -1
  39. package/dist/envprotect.js +18 -803
  40. package/dist/envprotect.js.map +1 -1
  41. package/dist/fs-safe.d.ts +16 -0
  42. package/dist/fs-safe.d.ts.map +1 -0
  43. package/dist/fs-safe.js +56 -0
  44. package/dist/fs-safe.js.map +1 -0
  45. package/dist/index.d.ts.map +1 -1
  46. package/dist/index.js +22 -6
  47. package/dist/index.js.map +1 -1
  48. package/dist/prompts/lead.d.ts +7 -0
  49. package/dist/prompts/lead.d.ts.map +1 -0
  50. package/dist/prompts/lead.js +230 -0
  51. package/dist/prompts/lead.js.map +1 -0
  52. package/dist/prompts/shared.d.ts +8 -0
  53. package/dist/prompts/shared.d.ts.map +1 -0
  54. package/dist/prompts/shared.js +118 -0
  55. package/dist/prompts/shared.js.map +1 -0
  56. package/dist/prompts/specialists.d.ts +12 -0
  57. package/dist/prompts/specialists.d.ts.map +1 -0
  58. package/dist/prompts/specialists.js +218 -0
  59. package/dist/prompts/specialists.js.map +1 -0
  60. package/dist/shell-text.d.ts +14 -0
  61. package/dist/shell-text.d.ts.map +1 -0
  62. package/dist/shell-text.js +16 -0
  63. package/dist/shell-text.js.map +1 -0
  64. package/dist/tm/args-schema.d.ts +36 -0
  65. package/dist/tm/args-schema.d.ts.map +1 -0
  66. package/dist/tm/args-schema.js +191 -0
  67. package/dist/tm/args-schema.js.map +1 -0
  68. package/dist/tm/browser.d.ts +52 -0
  69. package/dist/tm/browser.d.ts.map +1 -0
  70. package/dist/tm/browser.js +361 -0
  71. package/dist/tm/browser.js.map +1 -0
  72. package/dist/tm/client-unwrap.d.ts +38 -0
  73. package/dist/tm/client-unwrap.d.ts.map +1 -0
  74. package/dist/tm/client-unwrap.js +113 -0
  75. package/dist/tm/client-unwrap.js.map +1 -0
  76. package/dist/tm/config.d.ts +46 -18
  77. package/dist/tm/config.d.ts.map +1 -1
  78. package/dist/tm/config.js +60 -17
  79. package/dist/tm/config.js.map +1 -1
  80. package/dist/tm/guard.d.ts.map +1 -1
  81. package/dist/tm/guard.js +4 -7
  82. package/dist/tm/guard.js.map +1 -1
  83. package/dist/tm/index.d.ts +15 -5
  84. package/dist/tm/index.d.ts.map +1 -1
  85. package/dist/tm/index.js +95 -19
  86. package/dist/tm/index.js.map +1 -1
  87. package/dist/tm/memory.d.ts +82 -0
  88. package/dist/tm/memory.d.ts.map +1 -0
  89. package/dist/tm/memory.js +350 -0
  90. package/dist/tm/memory.js.map +1 -0
  91. package/dist/tm/pipelines.d.ts +58 -0
  92. package/dist/tm/pipelines.d.ts.map +1 -0
  93. package/dist/tm/pipelines.js +301 -0
  94. package/dist/tm/pipelines.js.map +1 -0
  95. package/dist/tm/preview.d.ts +6 -1
  96. package/dist/tm/preview.d.ts.map +1 -1
  97. package/dist/tm/preview.js +31 -8
  98. package/dist/tm/preview.js.map +1 -1
  99. package/dist/tm/ptc/budgets.d.ts +38 -0
  100. package/dist/tm/ptc/budgets.d.ts.map +1 -0
  101. package/dist/tm/ptc/budgets.js +59 -0
  102. package/dist/tm/ptc/budgets.js.map +1 -0
  103. package/dist/tm/ptc/contract.d.ts +131 -0
  104. package/dist/tm/ptc/contract.d.ts.map +1 -0
  105. package/dist/tm/ptc/contract.js +47 -0
  106. package/dist/tm/ptc/contract.js.map +1 -0
  107. package/dist/tm/ptc/driver.d.ts +37 -0
  108. package/dist/tm/ptc/driver.d.ts.map +1 -0
  109. package/dist/tm/ptc/driver.js +181 -0
  110. package/dist/tm/ptc/driver.js.map +1 -0
  111. package/dist/tm/ptc/engines.d.ts +32 -0
  112. package/dist/tm/ptc/engines.d.ts.map +1 -0
  113. package/dist/tm/ptc/engines.js +201 -0
  114. package/dist/tm/ptc/engines.js.map +1 -0
  115. package/dist/tm/ptc/gate.d.ts +28 -0
  116. package/dist/tm/ptc/gate.d.ts.map +1 -0
  117. package/dist/tm/ptc/gate.js +127 -0
  118. package/dist/tm/ptc/gate.js.map +1 -0
  119. package/dist/tm/ptc/index.d.ts +19 -0
  120. package/dist/tm/ptc/index.d.ts.map +1 -0
  121. package/dist/tm/ptc/index.js +15 -0
  122. package/dist/tm/ptc/index.js.map +1 -0
  123. package/dist/tm/ptc/pscan.d.ts +11 -0
  124. package/dist/tm/ptc/pscan.d.ts.map +1 -0
  125. package/dist/tm/ptc/pscan.js +27 -0
  126. package/dist/tm/ptc/pscan.js.map +1 -0
  127. package/dist/tm/ptc/summary.d.ts +17 -0
  128. package/dist/tm/ptc/summary.d.ts.map +1 -0
  129. package/dist/tm/ptc/summary.js +107 -0
  130. package/dist/tm/ptc/summary.js.map +1 -0
  131. package/dist/tm/ptc/tool.d.ts +35 -0
  132. package/dist/tm/ptc/tool.d.ts.map +1 -0
  133. package/dist/tm/ptc/tool.js +135 -0
  134. package/dist/tm/ptc/tool.js.map +1 -0
  135. package/dist/tm/ptc.d.ts +45 -26
  136. package/dist/tm/ptc.d.ts.map +1 -1
  137. package/dist/tm/ptc.js +71 -45
  138. package/dist/tm/ptc.js.map +1 -1
  139. package/dist/tm/result.d.ts +34 -0
  140. package/dist/tm/result.d.ts.map +1 -0
  141. package/dist/tm/result.js +87 -0
  142. package/dist/tm/result.js.map +1 -0
  143. package/dist/tm/shell-bridge.d.ts +22 -0
  144. package/dist/tm/shell-bridge.d.ts.map +1 -0
  145. package/dist/tm/shell-bridge.js +70 -0
  146. package/dist/tm/shell-bridge.js.map +1 -0
  147. package/dist/tm/store.d.ts.map +1 -1
  148. package/dist/tm/store.js +2 -1
  149. package/dist/tm/store.js.map +1 -1
  150. package/dist/tm/tools.d.ts +13 -71
  151. package/dist/tm/tools.d.ts.map +1 -1
  152. package/dist/tm/tools.js +21 -655
  153. package/dist/tm/tools.js.map +1 -1
  154. package/dist/tm/webfetch.d.ts +94 -0
  155. package/dist/tm/webfetch.d.ts.map +1 -0
  156. package/dist/tm/webfetch.js +225 -0
  157. package/dist/tm/webfetch.js.map +1 -0
  158. package/package.json +2 -2
@@ -0,0 +1,174 @@
1
+ /**
2
+ * Unified approval gate — deferral predicates. The byte-exact vs loose glob
3
+ * split: LOOSE (case-insensitive, trimmed) identifies dialogs for the gate;
4
+ * BYTE-EXACT governs deferral, so a command is only passed through when the
5
+ * host dialog is GUARANTEED to fire. Split out of the former monolithic
6
+ * envprotect.ts; behavior unchanged.
7
+ */
8
+ import { R2_DANGER_BASH_ASK_PATTERNS, R6_ENV_BASH_ASK_PATTERNS } from "./patterns.js";
9
+ import { commandToken, isEnvDumpStatement } from "./bash-classify.js";
10
+ import { splitShellSegments } from "../shell-text.js";
11
+ // local alias for the shared segmentation (call sites below)
12
+ const splitSegments = splitShellSegments;
13
+ // ---------- approval-gate deferral predicates ----------
14
+ /** Compile `*` into `.*`, escape every other regex metacharacter, anchor. */
15
+ function globToRegex(pattern, flags) {
16
+ let out = "";
17
+ for (const ch of pattern) {
18
+ if (ch === "*")
19
+ out += ".*";
20
+ else if (".+?^${}()|[]\\".includes(ch))
21
+ out += "\\" + ch;
22
+ else
23
+ out += ch;
24
+ }
25
+ return new RegExp("^" + out + "$", flags);
26
+ }
27
+ /**
28
+ * Two spellings of the same glob grammar:
29
+ * - LOOSE (case-insensitive, trimmed) — dialog IDENTIFICATION only
30
+ * (categorizePermission): over-matching here can merely arm a timer for a
31
+ * dialog the host already opened, never let a command run;
32
+ * - EXACT (byte-for-byte, no trim) — the DEFERRAL side: a command is only
33
+ * passed through to the dialog when it reproduces the injected config
34
+ * pattern head exactly, so ANY host matcher at least as greedy as ours
35
+ * must pop the dialog. Case/trim leniency on this side would defer forms
36
+ * like `GET-CONTENT ENV:PATH` that a byte-exact host grammar never pops
37
+ * — a SILENT env read with no popup. If the host turns out to be
38
+ * case-insensitive, exact costs nothing but a few extra hard throws
39
+ * (fail-closed direction).
40
+ */
41
+ const GLOB_CACHE = new Map();
42
+ function glob(pattern, flags) {
43
+ const key = flags + pattern;
44
+ let re = GLOB_CACHE.get(key);
45
+ if (!re) {
46
+ re = globToRegex(pattern, flags);
47
+ GLOB_CACHE.set(key, re);
48
+ }
49
+ return re;
50
+ }
51
+ /** True when `text` matches ANY of the given ask globs (LOOSE, host-style). */
52
+ export function commandMatchesAnyAskPattern(text, patterns) {
53
+ const t = String(text ?? "").trim();
54
+ if (!t)
55
+ return false;
56
+ for (const p of patterns)
57
+ if (glob(p, "i").test(t))
58
+ return true;
59
+ return false;
60
+ }
61
+ /** Byte-exact deferral-side match (see the GLOB_CACHE note above). */
62
+ function commandMatchesAnyAskPatternExact(text, patterns) {
63
+ if (!text)
64
+ return false;
65
+ for (const p of patterns)
66
+ if (glob(p, "").test(text))
67
+ return true;
68
+ return false;
69
+ }
70
+ /**
71
+ * True when a built-in-bash command is an environment read in a form the
72
+ * host is configured to ASK about (and therefore the dialog governs it).
73
+ * The hook then lets it proceed WITHOUT throwing, so a human approval is
74
+ * meaningful. Kept deliberately narrower than classifyBashCommand: every
75
+ * form it accepts is a head-anchored single statement that matches one of
76
+ * R6_ENV_BASH_ASK BYTE-EXACTLY (so the dialog WILL fire under any host
77
+ * grammar at least as greedy as ours — no case/trim leniency, see
78
+ * commandMatchesAnyAskPatternExact), and every hazard that breaks that
79
+ * alignment (compound statements, substitutions, `${…}`, `$ALLCAPS`,
80
+ * assignment prefixes, `time`/subshell/escaped heads, user extra-deny, path
81
+ * heads like `/usr/bin/env`, `cmd /c …`, and the PowerShell env-drive forms
82
+ * with intervening flags or quotes that the globs miss) returns false → the
83
+ * classifier keeps hard-throwing. tm_* channels are hard-blocked separately
84
+ * (the dialog never opens for them).
85
+ */
86
+ export function isAskGatedEnvCommand(command, mode, extra = []) {
87
+ const text = String(command ?? "");
88
+ if (!text.trim())
89
+ return false;
90
+ for (const r of extra)
91
+ if (r.test(text))
92
+ return false;
93
+ // command substitution runs its own line — the outer pattern never matches
94
+ if (/\$\(|`|<\(|>\(/.test(text))
95
+ return false;
96
+ // ${VAR} brace expansion is wildcard-inexpressible
97
+ if (/\$\{[^}]*\}/.test(text))
98
+ return false;
99
+ const segs = splitSegments(text).map((s) => s.trim()).filter(Boolean);
100
+ if (segs.length !== 1)
101
+ return false;
102
+ const seg = segs[0];
103
+ // byte-exact head: a VAR=value prefix, stray leading whitespace or any
104
+ // case deviation from the injected pattern fails here and stays hard
105
+ if (!commandMatchesAnyAskPatternExact(text, R6_ENV_BASH_ASK_PATTERNS))
106
+ return false;
107
+ const { token, rest } = commandToken(seg);
108
+ // classic dump head: printenv / env / set / declare|typeset|export|local -p
109
+ if (isEnvDumpStatement(token, rest))
110
+ return true;
111
+ // PowerShell env: drive read with no flags between the cmdlet and the
112
+ // drive, spellings byte-identical to the config globs (see the doc above)
113
+ if (/^(?:Get-ChildItem|gci|dir|ls|Get-Item|gi|Get-Content|gc|cat|type)\s+env:/.test(seg)) {
114
+ return true;
115
+ }
116
+ // leading `$env:` drive form (matches the `$env:*` config glob)
117
+ if (text.startsWith("$env:"))
118
+ return true;
119
+ return false;
120
+ }
121
+ /**
122
+ * Coarse category for a host permission-event payload, used ONLY to gate the
123
+ * timer, to register the deferral session, and to tag the audit (never the
124
+ * pattern text itself). Returns null for anything that is not one of OUR
125
+ * injected bash asks, so the gate leaves other tools' dialogs (edit,
126
+ * webfetch, host-native rules) alone.
127
+ *
128
+ * Real 1.18.29 `permission.asked` props carry NO `type` field: the tool name
129
+ * rides in `permission` and the concrete command segments in `patterns[]`
130
+ * (plus `metadata.command` with the raw line). Inference order, defensive
131
+ * across builds:
132
+ * 1. explicit tool field (`type` || `permission`) — non-bash is a hard no;
133
+ * 2. absent tool field → bash evidence = `metadata.command` present, or a
134
+ * candidate that is COMMAND-SHAPED (contains whitespace: our command
135
+ * globs all need an embedded space except bare keys, and bare-key
136
+ * patterns like a file named `set` cannot prove bash without the
137
+ * command metadata beside them).
138
+ * Matching here is the LOOSE glob (case-insensitive): over-matching can only
139
+ * arm a timer for a dialog the host already opened, never run a command —
140
+ * the deferral side keeps the byte-exact matcher (isAskGatedEnvCommand).
141
+ */
142
+ export function categorizePermission(props) {
143
+ if (!props)
144
+ return null;
145
+ const type = String(props.type ?? props.permission ?? "").trim().toLowerCase();
146
+ if (type && type !== "bash")
147
+ return null;
148
+ const raw = props.patterns ?? props.pattern;
149
+ const list = raw == null ? [] : Array.isArray(raw) ? raw.map(String) : [String(raw)];
150
+ const metaCmd = typeof props.metadata?.command === "string" ? props.metadata.command : "";
151
+ const title = typeof props.title === "string" ? props.title : "";
152
+ const candidates = [...list];
153
+ if (metaCmd)
154
+ candidates.push(metaCmd);
155
+ if (title)
156
+ candidates.push(title);
157
+ if (!candidates.length)
158
+ return null;
159
+ if (!type) {
160
+ // no tool field at all (live 1.18.29 asked): bash evidence = the bash
161
+ // tool's own metadata.command, or a command-shaped candidate (contains
162
+ // whitespace — every multi-word ask glob needs one; single bare words
163
+ // like a path named `set` prove nothing without metadata beside them)
164
+ if (!metaCmd && !candidates.some((c) => /\s/.test(c)))
165
+ return null;
166
+ }
167
+ const hits = (patterns) => candidates.some((p) => patterns.includes(p) || commandMatchesAnyAskPattern(p, patterns));
168
+ if (hits(R6_ENV_BASH_ASK_PATTERNS))
169
+ return "env";
170
+ if (hits(R2_DANGER_BASH_ASK_PATTERNS))
171
+ return "danger";
172
+ return null;
173
+ }
174
+ //# sourceMappingURL=gate-predicates.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gate-predicates.js","sourceRoot":"","sources":["../../src/envprotect/gate-predicates.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,45 @@
1
+ /**
2
+ * R6 — the `tool.execute.before` hook: file-tool path classification,
3
+ * tool-call level routing (tm_* aliasing = anti-backdoor), and the hook
4
+ * factory with the privacy-red-line audit. Split out of the former
5
+ * monolithic envprotect.ts; behavior unchanged.
6
+ */
7
+ import { type EnvProtectMode } from "./patterns.js";
8
+ /**
9
+ * Classify the path-class arguments of a file tool. Returns the pattern
10
+ * category when the call must be blocked, null when it may pass.
11
+ */
12
+ export declare function classifyPathFields(args: Record<string, unknown> | undefined, mode: EnvProtectMode, extra?: RegExp[]): string | null;
13
+ /**
14
+ * Top routing: tool name + tool args -> category to block, or null to pass.
15
+ * `off` always passes (the hook stays installed but is a no-op).
16
+ */
17
+ export declare function inspectToolCall(tool: string, args: Record<string, unknown> | undefined, mode: EnvProtectMode, extra?: RegExp[]): string | null;
18
+ /**
19
+ * Build the `tool.execute.before` hook. The hook is installed in every
20
+ * mode; with mode "off" it returns immediately and everything passes.
21
+ * When a block triggers, the audit entry is written first, then the fixed
22
+ * structured error is thrown so opencode surfaces it to the model as the
23
+ * failed tool result.
24
+ *
25
+ * `options.deferToApproval` (the unified approval gate) — called with the
26
+ * tool call's `input.sessionID`; when it reports `true`, a built-in-bash
27
+ * environment read in an EXACT ask-glob form is passed through WITHOUT
28
+ * throwing, because the official confirmation dialog is the live gate for
29
+ * it (approve → runs, reject or timeout → the host blocks it). The signal
30
+ * is SESSION-SCOPED on purpose: the gate registers only sessions carrying
31
+ * our injected bash ask set (exec-role chat.message, or an R6-env-classified
32
+ * `permission.asked`), so a stock build/plan session — where no dialog would
33
+ * ever open for `printenv` — keeps the hard throw instead of slipping the
34
+ * read through a global arm flag. Reaching the deferral point at all
35
+ * additionally requires the command to match the config globs byte-exactly,
36
+ * so the dialog is GUARANTEED to fire wherever deferral happens. A failed
37
+ * auto-reject flips the gate back to `false` everywhere, restoring the hard
38
+ * throw (fail-closed). tm_* channels are never deferred (no dialog opens
39
+ * for them).
40
+ */
41
+ export declare function createEnvProtectHook(client: unknown, mode: EnvProtectMode, extra?: RegExp[], options?: {
42
+ deferToApproval?: (sessionID?: string) => boolean;
43
+ envApproved?: (sessionID?: string) => boolean;
44
+ }): (input: unknown, output: unknown) => Promise<void>;
45
+ //# sourceMappingURL=hook.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hook.d.ts","sourceRoot":"","sources":["../../src/envprotect/hook.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAKL,KAAK,cAAc,EACpB,MAAM,eAAe,CAAA;AAetB;;;GAGG;AACH,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,EACzC,IAAI,EAAE,cAAc,EACpB,KAAK,GAAE,MAAM,EAAO,GACnB,MAAM,GAAG,IAAI,CAWf;AAoBD;;;GAGG;AACH,wBAAgB,eAAe,CAC7B,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,EACzC,IAAI,EAAE,cAAc,EACpB,KAAK,GAAE,MAAM,EAAO,GACnB,MAAM,GAAG,IAAI,CAYf;AA0CD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,OAAO,EACf,IAAI,EAAE,cAAc,EACpB,KAAK,GAAE,MAAM,EAAO,EACpB,OAAO,GAAE;IAAE,eAAe,CAAC,EAAE,CAAC,SAAS,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC;IAAC,WAAW,CAAC,EAAE,CAAC,SAAS,CAAC,EAAE,MAAM,KAAK,OAAO,CAAA;CAAO,GACjH,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,CAgCpD"}
@@ -0,0 +1,158 @@
1
+ /**
2
+ * R6 — the `tool.execute.before` hook: file-tool path classification,
3
+ * tool-call level routing (tm_* aliasing = anti-backdoor), and the hook
4
+ * factory with the privacy-red-line audit. Split out of the former
5
+ * monolithic envprotect.ts; behavior unchanged.
6
+ */
7
+ import { CATEGORY_ENV_FILE_PATH, CATEGORY_EXTRA_DENY, ENV_PROTECT_SERVICE, envProtectError, } from "./patterns.js";
8
+ import { classifyBashCommand } from "./bash-classify.js";
9
+ import { isEnvFilePath } from "./path-classify.js";
10
+ import { isAskGatedEnvCommand } from "./gate-predicates.js";
11
+ // ---------- file-tool path classification ----------
12
+ /**
13
+ * Path-class argument keys scanned defensively across read / grep / glob /
14
+ * list (read uses `filePath`; the others historically use `path`, and
15
+ * `pattern` / `include` select files for glob / grep). Keys outside this
16
+ * set (descriptions, notes) are never scanned.
17
+ */
18
+ const PATH_LIKE_KEYS = /^(filepath|path|file|dir|directory|pattern|include)$/i;
19
+ /**
20
+ * Classify the path-class arguments of a file tool. Returns the pattern
21
+ * category when the call must be blocked, null when it may pass.
22
+ */
23
+ export function classifyPathFields(args, mode, extra = []) {
24
+ if (!args || typeof args !== "object")
25
+ return null;
26
+ for (const [key, value] of Object.entries(args)) {
27
+ if (typeof value !== "string" || !value)
28
+ continue;
29
+ if (!PATH_LIKE_KEYS.test(key))
30
+ continue;
31
+ for (const rule of extra) {
32
+ if (rule.test(value))
33
+ return CATEGORY_EXTRA_DENY;
34
+ }
35
+ if (isEnvFilePath(value))
36
+ return CATEGORY_ENV_FILE_PATH;
37
+ }
38
+ return null;
39
+ }
40
+ // ---------- tool-call level routing ----------
41
+ /** File tools whose path-class arguments are scanned for env files. */
42
+ const PATH_SCAN_TOOLS = new Set(["read", "grep", "glob", "list"]);
43
+ /**
44
+ * TeamMode's own tm_* tools alias onto the built-in surface so the SAME
45
+ * interception applies to them (anti-backdoor: tm_read/tm_grep/tm_bash must
46
+ * never become an R6 bypass by virtue of a different tool name). The tools
47
+ * ALSO re-check with the same matchers inside their own pipelines — this
48
+ * hook-level alias is the outer defense layer.
49
+ */
50
+ const TM_TOOL_ALIASES = {
51
+ tm_read: "read",
52
+ tm_grep: "grep",
53
+ tm_bash: "bash",
54
+ };
55
+ /**
56
+ * Top routing: tool name + tool args -> category to block, or null to pass.
57
+ * `off` always passes (the hook stays installed but is a no-op).
58
+ */
59
+ export function inspectToolCall(tool, args, mode, extra = []) {
60
+ if (mode === "off")
61
+ return null;
62
+ const rawName = String(tool ?? "").trim().toLowerCase();
63
+ const name = TM_TOOL_ALIASES[rawName] ?? rawName;
64
+ if (name === "bash") {
65
+ const command = args?.command;
66
+ return classifyBashCommand(String(command ?? ""), mode, extra);
67
+ }
68
+ if (PATH_SCAN_TOOLS.has(name)) {
69
+ return classifyPathFields(args, mode, extra);
70
+ }
71
+ return null;
72
+ }
73
+ /**
74
+ * Best-effort audit of one interception. Records ONLY the tool name and
75
+ * the pattern category (privacy red line: never command text, paths,
76
+ * variable names or values); the message embeds the service id itself so
77
+ * file-backed log sinks that drop the `service` field stay greppable. A
78
+ * failing audit endpoint must never turn a block into a pass-through, so
79
+ * every failure is swallowed after the call attempt.
80
+ */
81
+ async function auditInterception(client, tool, category) {
82
+ try {
83
+ // Call the method THROUGH the app object. Destructuring `app.log`
84
+ // first would drop the SDK's `this` binding, so every real call died
85
+ // inside the SDK and was silently swallowed here — zero audit entries.
86
+ const app = client?.app;
87
+ if (!app || typeof app.log !== "function")
88
+ return;
89
+ await app.log({
90
+ body: {
91
+ level: "warn",
92
+ service: ENV_PROTECT_SERVICE,
93
+ message: `${ENV_PROTECT_SERVICE} :: ${tool} :: ${category}`,
94
+ },
95
+ });
96
+ }
97
+ catch {
98
+ /* audit is best-effort; the block below still takes effect */
99
+ }
100
+ }
101
+ /**
102
+ * Build the `tool.execute.before` hook. The hook is installed in every
103
+ * mode; with mode "off" it returns immediately and everything passes.
104
+ * When a block triggers, the audit entry is written first, then the fixed
105
+ * structured error is thrown so opencode surfaces it to the model as the
106
+ * failed tool result.
107
+ *
108
+ * `options.deferToApproval` (the unified approval gate) — called with the
109
+ * tool call's `input.sessionID`; when it reports `true`, a built-in-bash
110
+ * environment read in an EXACT ask-glob form is passed through WITHOUT
111
+ * throwing, because the official confirmation dialog is the live gate for
112
+ * it (approve → runs, reject or timeout → the host blocks it). The signal
113
+ * is SESSION-SCOPED on purpose: the gate registers only sessions carrying
114
+ * our injected bash ask set (exec-role chat.message, or an R6-env-classified
115
+ * `permission.asked`), so a stock build/plan session — where no dialog would
116
+ * ever open for `printenv` — keeps the hard throw instead of slipping the
117
+ * read through a global arm flag. Reaching the deferral point at all
118
+ * additionally requires the command to match the config globs byte-exactly,
119
+ * so the dialog is GUARANTEED to fire wherever deferral happens. A failed
120
+ * auto-reject flips the gate back to `false` everywhere, restoring the hard
121
+ * throw (fail-closed). tm_* channels are never deferred (no dialog opens
122
+ * for them).
123
+ */
124
+ export function createEnvProtectHook(client, mode, extra = [], options = {}) {
125
+ return async (input, output) => {
126
+ if (mode === "off")
127
+ return;
128
+ const tool = String(input?.tool ?? "");
129
+ const rawSid = input?.sessionID;
130
+ const sessionID = typeof rawSid === "string" && rawSid.trim() !== "" ? rawSid.trim() : undefined;
131
+ const args = output?.args;
132
+ const category = inspectToolCall(tool, args, mode, extra);
133
+ if (!category)
134
+ return;
135
+ // Session-wide env approval ("always" on first env ask) — pass silently;
136
+ // the "always" event itself was already audited by the approval gate.
137
+ // The blanket NEVER covers env-FILE reads (CATEGORY_ENV_FILE_PATH):
138
+ // files on disk (.env, shell rc family) never open a dialog of their
139
+ // own (they are not in the ask-pattern set), so no "always" verdict can
140
+ // have consented to them — they keep hard-throwing even in an
141
+ // env-approved session.
142
+ if (sessionID &&
143
+ options.envApproved?.(sessionID) &&
144
+ category !== CATEGORY_ENV_FILE_PATH) {
145
+ return;
146
+ }
147
+ // Defer ONLY the built-in bash tool (never its tm_bash alias), ONLY in a
148
+ // registered session, for the exact forms the config escalates to `ask`.
149
+ if (tool.trim().toLowerCase() === "bash" && sessionID && options.deferToApproval?.(sessionID)) {
150
+ const command = args?.command;
151
+ if (isAskGatedEnvCommand(String(command ?? ""), mode, extra))
152
+ return;
153
+ }
154
+ await auditInterception(client, tool, category);
155
+ throw envProtectError(category);
156
+ };
157
+ }
158
+ //# sourceMappingURL=hook.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hook.js","sourceRoot":"","sources":["../../src/envprotect/hook.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * R6 — env-file path matching (.env family, shell rc files) for both path
3
+ * arguments and bash tokens. Split out of the former monolithic
4
+ * envprotect.ts; behavior unchanged.
5
+ */
6
+ /**
7
+ * True when the value points at an env file. Matches on the BASENAME with
8
+ * both separators handled; URL query/fragment tails are cut first so
9
+ * `https://x/.env?raw=1` matches the same basename as a plain path, and
10
+ * asterisks are stripped so glob patterns like "*.env" or a recursive deep
11
+ * form of ".env.local" are caught by the same matcher as real paths.
12
+ * (Note: glob spellings are written without backticks here — a double-star
13
+ * + slash inside JSDoc would close the comment block early.)
14
+ */
15
+ export declare function isEnvFilePath(raw: string): boolean;
16
+ //# sourceMappingURL=path-classify.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"path-classify.d.ts","sourceRoot":"","sources":["../../src/envprotect/path-classify.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAyBH;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAiClD"}
@@ -0,0 +1,72 @@
1
+ /**
2
+ * R6 — env-file path matching (.env family, shell rc files) for both path
3
+ * arguments and bash tokens. Split out of the former monolithic
4
+ * envprotect.ts; behavior unchanged.
5
+ */
6
+ // ---------- env-file path matching ----------
7
+ /** Shell/rc basenames whose content is effectively an env dump. */
8
+ const ENV_FILE_BASENAMES = new Set([
9
+ ".bashrc",
10
+ ".bash_profile",
11
+ ".profile",
12
+ ".zshrc",
13
+ ".zprofile",
14
+ ".zshenv",
15
+ ]);
16
+ /** Checked-in template suffixes — documentation copies, not real secrets. */
17
+ const ENV_TEMPLATE_SUFFIX = /\.(?:example|sample|template|dist)$/;
18
+ /**
19
+ * Code identifiers that merely end in ".env" and name an object, not a
20
+ * file (`process.env`, `import.meta.env`, ...). The escaped form is
21
+ * listed too because a grep pattern like `process\.env` survives
22
+ * tokenization with its backslash.
23
+ */
24
+ const ENV_IDENTIFIER = /^(?:process|deno|bun|os|import\.meta)\.env$/i;
25
+ /**
26
+ * True when the value points at an env file. Matches on the BASENAME with
27
+ * both separators handled; URL query/fragment tails are cut first so
28
+ * `https://x/.env?raw=1` matches the same basename as a plain path, and
29
+ * asterisks are stripped so glob patterns like "*.env" or a recursive deep
30
+ * form of ".env.local" are caught by the same matcher as real paths.
31
+ * (Note: glob spellings are written without backticks here — a double-star
32
+ * + slash inside JSDoc would close the comment block early.)
33
+ */
34
+ export function isEnvFilePath(raw) {
35
+ const value = String(raw ?? "")
36
+ // one percent-decode pass FIRST, so URL-encoded names like
37
+ // `https://x/%2Eenv` land on the same basename as the plain path
38
+ // (single pass only: `%252E` decodes to `%2E` and stays encoded)
39
+ .replace(/%([0-9A-Fa-f]{2})/g, (_, hex) => String.fromCharCode(parseInt(hex, 16)))
40
+ .replace(/[?#].*$/, "")
41
+ .replace(/\*/g, "")
42
+ .trim();
43
+ if (!value)
44
+ return false;
45
+ // A code identifier like `process.env` is not a file path, however it is
46
+ // segmented. The unescaped form matters because a grep pattern spelled
47
+ // `process\.env` would otherwise split at the backslash into path +
48
+ // basename `.env` and slip past the identifier check below.
49
+ if (ENV_IDENTIFIER.test(value) || ENV_IDENTIFIER.test(value.replace(/\\/g, ""))) {
50
+ return false;
51
+ }
52
+ const segments = value.split(/[\\/]/);
53
+ const base = (segments[segments.length - 1] || "").toLowerCase();
54
+ if (!base)
55
+ return false;
56
+ if (base === ".env")
57
+ return true;
58
+ if (base.startsWith(".env.")) {
59
+ // `.env.example` and friends are safe checked-in documentation
60
+ if (ENV_TEMPLATE_SUFFIX.test(base))
61
+ return false;
62
+ return true;
63
+ }
64
+ if (base.endsWith(".env")) {
65
+ if (ENV_IDENTIFIER.test(base) || ENV_IDENTIFIER.test(base.replace(/\\/g, ""))) {
66
+ return false;
67
+ }
68
+ return true;
69
+ }
70
+ return ENV_FILE_BASENAMES.has(base);
71
+ }
72
+ //# sourceMappingURL=path-classify.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"path-classify.js","sourceRoot":"","sources":["../../src/envprotect/path-classify.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,+CAA+C;AAE/C,mEAAmE;AACnE,MAAM,kBAAkB,GAAG,IAAI,GAAG,CAAC;IACjC,SAAS;IACT,eAAe;IACf,UAAU;IACV,QAAQ;IACR,WAAW;IACX,SAAS;CACV,CAAC,CAAA;AAEF,6EAA6E;AAC7E,MAAM,mBAAmB,GAAG,qCAAqC,CAAA;AAEjE;;;;;GAKG;AACH,MAAM,cAAc,GAAG,8CAA8C,CAAA;AAErE;;;;;;;;GAQG;AACH,MAAM,UAAU,aAAa,CAAC,GAAW;IACvC,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,IAAI,EAAE,CAAC;QAC7B,2DAA2D;QAC3D,iEAAiE;QACjE,iEAAiE;SAChE,OAAO,CAAC,oBAAoB,EAAE,CAAC,CAAC,EAAE,GAAG,EAAE,EAAE,CAAC,MAAM,CAAC,YAAY,CAAC,QAAQ,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,CAAC;SACjF,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC;SACtB,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC;SAClB,IAAI,EAAE,CAAA;IACT,IAAI,CAAC,KAAK;QAAE,OAAO,KAAK,CAAA;IACxB,yEAAyE;IACzE,wEAAwE;IACxE,oEAAoE;IACpE,4DAA4D;IAC5D,IAAI,cAAc,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,cAAc,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC;QAChF,OAAO,KAAK,CAAA;IACd,CAAC;IACD,MAAM,QAAQ,GAAG,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,CAAA;IACrC,MAAM,IAAI,GAAG,CAAC,QAAQ,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,WAAW,EAAE,CAAA;IAChE,IAAI,CAAC,IAAI;QAAE,OAAO,KAAK,CAAA;IACvB,IAAI,IAAI,KAAK,MAAM;QAAE,OAAO,IAAI,CAAA;IAChC,IAAI,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;QAC7B,+DAA+D;QAC/D,IAAI,mBAAmB,CAAC,IAAI,CAAC,IAAI,CAAC;YAAE,OAAO,KAAK,CAAA;QAChD,OAAO,IAAI,CAAA;IACb,CAAC;IACD,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;QAC1B,IAAI,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC;YAC9E,OAAO,KAAK,CAAA;QACd,CAAC;QACD,OAAO,IAAI,CAAA;IACb,CAAC;IACD,OAAO,kBAAkB,CAAC,GAAG,CAAC,IAAI,CAAC,CAAA;AACrC,CAAC"}
@@ -0,0 +1,77 @@
1
+ /**
2
+ * R6/R2 — environment-protection constants, the host-native `ask` pattern
3
+ * sets (Layer 1 of the unified approval gate), and the mode/config parsing.
4
+ * Split out of the former monolithic envprotect.ts; behavior unchanged.
5
+ *
6
+ * These are the shapes a wildcard CAN express (head-anchored globs). The
7
+ * plugin NEVER self-allows; an unanswered dialog is auto-REJECTED by the
8
+ * approval timer (see approval-gate.ts).
9
+ */
10
+ /** Audit log service identifier (also the query key for "what was blocked"). */
11
+ export declare const ENV_PROTECT_SERVICE = "team-mode-env-protect";
12
+ /**
13
+ * Fixed structured message returned to the model on every block. The
14
+ * category is appended as a machine-readable suffix; tests pin both.
15
+ */
16
+ export declare const ENV_PROTECT_MESSAGE = "TeamMode R6 env protection: \u73AF\u5883\u53D8\u91CF\u8BFB\u53D6\u5DF2\u88AB\u62E6\u622A\u3002\u9700\u8981\u53D8\u91CF\u503C\u8BF7\u5411 HUMAN \u7533\u8BF7\u3002";
17
+ /** Pattern categories surfaced in the block message and the audit log. */
18
+ export declare const CATEGORY_BASH_ENV_COMMAND = "bash-env-command";
19
+ export declare const CATEGORY_BASH_ENV_EXPANSION = "bash-env-expansion";
20
+ export declare const CATEGORY_ENV_FILE_PATH = "env-file-path";
21
+ export declare const CATEGORY_EXTRA_DENY = "extra-deny";
22
+ /**
23
+ * Two faces are routed to the OpenCode official confirmation dialog by
24
+ * declaring them `ask` in each agent's bash permission object. The host
25
+ * pops a dialog for any command matching one of these patterns and SUSPENDS
26
+ * it: a human reply ("once"/"always") runs the command, a "reject" or NO
27
+ * REPLY is handled by the approval timer (Layer 2, see approval-gate.ts)
28
+ * which auto-REJECTS after TM_ASK_TIMEOUT_MIN. The plugin NEVER self-allows.
29
+ *
30
+ * These are the shapes a wildcard CAN express (head-anchored globs, matching
31
+ * the probe-verified grammar: `printenv*`, `rm *`, `Get-ChildItem env:*`).
32
+ * Wildcards CANNOT express (embedded `${VAR}` / `$ALLCAPS` / `$env:` inside
33
+ * another command, command substitution `$(printenv)`, subshell/escaped/
34
+ * `time`-prefixed heads, `VAR=… env` assignment prefixes, split `declare -xp`,
35
+ * env-file paths at arbitrary positions) stay the classifier's HARD THROW —
36
+ * that is the `isAskGatedEnvCommand` boundary, kept in lockstep with the
37
+ * classifier so a form we stop throwing on is exactly a form the dialog shows.
38
+ */
39
+ /** R6 env face — the wildcard-expressible environment-variable reads. */
40
+ export declare const R6_ENV_BASH_ASK: Readonly<Record<string, "ask">>;
41
+ /** R2 danger face — destructive / publishing / network / install / process /
42
+ * privilege commands. Independent of TM_ENV_PROTECT (R2 is its own red
43
+ * line); the built-in bash tool stays `allow` for everything else, so the
44
+ * normal verification stack (npm test, tsc, git status/diff) never pops. */
45
+ export declare const R2_DANGER_BASH_ASK: Readonly<Record<string, "ask">>;
46
+ /** Ordered key lists (frozen) — single source for the matcher + the gate. */
47
+ export declare const R6_ENV_BASH_ASK_PATTERNS: readonly string[];
48
+ export declare const R2_DANGER_BASH_ASK_PATTERNS: readonly string[];
49
+ /**
50
+ * The bash permission object injected into the four execution-role agents
51
+ * (team / implementer / reviewer / tester). Default stays `allow`
52
+ * (`"*": "allow"`, preserving the T2.1 matrix semantics — bash is granted,
53
+ * not removed); only the listed shapes escalate to the host dialog. The R6
54
+ * env face is dropped when `mode === "off"` (the R6 kill switch) AND when
55
+ * `envFace` is false — index.ts passes false while the approval gate cannot
56
+ * arm: the R6 hook then hard-throws every env read BEFORE its dialog could
57
+ * ever satisfy it, so injecting the env asks would only create dead popups
58
+ * a human can answer yet never make runnable. The R2 face is always
59
+ * present (its dialog is the whole gate — no code-level throw is involved).
60
+ */
61
+ export declare function bashAskPatterns(mode: EnvProtectMode, envFace?: boolean): Record<string, string>;
62
+ export type EnvProtectMode = "strict" | "standard" | "off";
63
+ /**
64
+ * Resolve the protection mode. Only exact "standard" / "off" switch those
65
+ * modes on; everything else (unset, typos, wrong case) fails CLOSED into
66
+ * strict — a mistyped opt-out must never disable the red line.
67
+ */
68
+ export declare function resolveEnvProtectMode(raw: unknown): EnvProtectMode;
69
+ /**
70
+ * Parse `TM_ENV_PROTECT_EXTRA_DENY` (semicolon-separated regex bodies).
71
+ * Invalid regexes are skipped rather than crashing plugin startup; the
72
+ * remaining rules still apply. Empty/absent input yields no extra rules.
73
+ */
74
+ export declare function parseExtraDeny(raw: unknown): RegExp[];
75
+ /** The block error handed back to the model (fixed text + category tag). */
76
+ export declare function envProtectError(category: string): Error;
77
+ //# sourceMappingURL=patterns.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"patterns.d.ts","sourceRoot":"","sources":["../../src/envprotect/patterns.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,gFAAgF;AAChF,eAAO,MAAM,mBAAmB,0BAA0B,CAAA;AAE1D;;;GAGG;AACH,eAAO,MAAM,mBAAmB,sKAC4B,CAAA;AAE5D,0EAA0E;AAC1E,eAAO,MAAM,yBAAyB,qBAAqB,CAAA;AAC3D,eAAO,MAAM,2BAA2B,uBAAuB,CAAA;AAC/D,eAAO,MAAM,sBAAsB,kBAAkB,CAAA;AACrD,eAAO,MAAM,mBAAmB,eAAe,CAAA;AAK/C;;;;;;;;;;;;;;;;GAgBG;AAEH,yEAAyE;AACzE,eAAO,MAAM,eAAe,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAyB1D,CAAA;AAEF;;;6EAG6E;AAC7E,eAAO,MAAM,kBAAkB,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CA6C7D,CAAA;AAEF,6EAA6E;AAC7E,eAAO,MAAM,wBAAwB,EAAE,SAAS,MAAM,EAErD,CAAA;AACD,eAAO,MAAM,2BAA2B,EAAE,SAAS,MAAM,EAExD,CAAA;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,cAAc,EAAE,OAAO,UAAO,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAO5F;AAED,MAAM,MAAM,cAAc,GAAG,QAAQ,GAAG,UAAU,GAAG,KAAK,CAAA;AAE1D;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,OAAO,GAAG,cAAc,CAIlE;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,EAAE,CAarD;AAED,4EAA4E;AAC5E,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,KAAK,CAEvD"}