@te-river/opencode-team-mode 1.6.0 → 1.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (150) hide show
  1. package/README.md +80 -15
  2. package/README.zh-CN.md +67 -18
  3. package/dist/host/note.d.ts +7 -0
  4. package/dist/host/note.d.ts.map +1 -0
  5. package/dist/host/note.js +28 -0
  6. package/dist/host/note.js.map +1 -0
  7. package/dist/host/v1.d.ts +14 -0
  8. package/dist/host/v1.d.ts.map +1 -0
  9. package/dist/host/v1.js +340 -0
  10. package/dist/host/v1.js.map +1 -0
  11. package/dist/host/v2-browser-gate.d.ts +45 -0
  12. package/dist/host/v2-browser-gate.d.ts.map +1 -0
  13. package/dist/host/v2-browser-gate.js +319 -0
  14. package/dist/host/v2-browser-gate.js.map +1 -0
  15. package/dist/host/v2-capabilities.d.ts +68 -0
  16. package/dist/host/v2-capabilities.d.ts.map +1 -0
  17. package/dist/host/v2-capabilities.js +146 -0
  18. package/dist/host/v2-capabilities.js.map +1 -0
  19. package/dist/host/v2-client.d.ts +53 -0
  20. package/dist/host/v2-client.d.ts.map +1 -0
  21. package/dist/host/v2-client.js +193 -0
  22. package/dist/host/v2-client.js.map +1 -0
  23. package/dist/host/v2-events.d.ts +56 -0
  24. package/dist/host/v2-events.d.ts.map +1 -0
  25. package/dist/host/v2-events.js +158 -0
  26. package/dist/host/v2-events.js.map +1 -0
  27. package/dist/host/v2-guard.d.ts +101 -0
  28. package/dist/host/v2-guard.d.ts.map +1 -0
  29. package/dist/host/v2-guard.js +199 -0
  30. package/dist/host/v2-guard.js.map +1 -0
  31. package/dist/host/v2-offload.d.ts +148 -0
  32. package/dist/host/v2-offload.d.ts.map +1 -0
  33. package/dist/host/v2-offload.js +288 -0
  34. package/dist/host/v2-offload.js.map +1 -0
  35. package/dist/host/v2-permissions.d.ts +123 -0
  36. package/dist/host/v2-permissions.d.ts.map +1 -0
  37. package/dist/host/v2-permissions.js +190 -0
  38. package/dist/host/v2-permissions.js.map +1 -0
  39. package/dist/host/v2-probe.d.ts +85 -0
  40. package/dist/host/v2-probe.d.ts.map +1 -0
  41. package/dist/host/v2-probe.js +268 -0
  42. package/dist/host/v2-probe.js.map +1 -0
  43. package/dist/host/v2-scope.d.ts +51 -0
  44. package/dist/host/v2-scope.d.ts.map +1 -0
  45. package/dist/host/v2-scope.js +58 -0
  46. package/dist/host/v2-scope.js.map +1 -0
  47. package/dist/host/v2-session-client.d.ts +74 -0
  48. package/dist/host/v2-session-client.d.ts.map +1 -0
  49. package/dist/host/v2-session-client.js +171 -0
  50. package/dist/host/v2-session-client.js.map +1 -0
  51. package/dist/host/v2-session.d.ts +85 -0
  52. package/dist/host/v2-session.d.ts.map +1 -0
  53. package/dist/host/v2-session.js +171 -0
  54. package/dist/host/v2-session.js.map +1 -0
  55. package/dist/host/v2-subagent.d.ts +157 -0
  56. package/dist/host/v2-subagent.d.ts.map +1 -0
  57. package/dist/host/v2-subagent.js +286 -0
  58. package/dist/host/v2-subagent.js.map +1 -0
  59. package/dist/host/v2-tool.d.ts +67 -0
  60. package/dist/host/v2-tool.d.ts.map +1 -0
  61. package/dist/host/v2-tool.js +209 -0
  62. package/dist/host/v2-tool.js.map +1 -0
  63. package/dist/host/v2-transcript.d.ts +73 -0
  64. package/dist/host/v2-transcript.d.ts.map +1 -0
  65. package/dist/host/v2-transcript.js +151 -0
  66. package/dist/host/v2-transcript.js.map +1 -0
  67. package/dist/host/v2-types.d.ts +229 -0
  68. package/dist/host/v2-types.d.ts.map +1 -0
  69. package/dist/host/v2-types.js +17 -0
  70. package/dist/host/v2-types.js.map +1 -0
  71. package/dist/host/v2.d.ts +42 -0
  72. package/dist/host/v2.d.ts.map +1 -0
  73. package/dist/host/v2.js +706 -0
  74. package/dist/host/v2.js.map +1 -0
  75. package/dist/index.d.ts +23 -17
  76. package/dist/index.d.ts.map +1 -1
  77. package/dist/index.js +29 -366
  78. package/dist/index.js.map +1 -1
  79. package/dist/prompts/lead.d.ts +1 -1
  80. package/dist/prompts/lead.d.ts.map +1 -1
  81. package/dist/prompts/lead.js +452 -426
  82. package/dist/prompts/lead.js.map +1 -1
  83. package/dist/prompts/shared.d.ts +2 -2
  84. package/dist/prompts/shared.d.ts.map +1 -1
  85. package/dist/prompts/shared.js +30 -10
  86. package/dist/prompts/shared.js.map +1 -1
  87. package/dist/prompts/specialists.d.ts +2 -2
  88. package/dist/prompts/specialists.d.ts.map +1 -1
  89. package/dist/prompts/specialists.js +7 -6
  90. package/dist/prompts/specialists.js.map +1 -1
  91. package/dist/task-offload.d.ts +16 -0
  92. package/dist/task-offload.d.ts.map +1 -1
  93. package/dist/task-offload.js +51 -0
  94. package/dist/task-offload.js.map +1 -1
  95. package/dist/tm/browser.d.ts.map +1 -1
  96. package/dist/tm/browser.js +41 -10
  97. package/dist/tm/browser.js.map +1 -1
  98. package/dist/tm/dispatch.d.ts +47 -0
  99. package/dist/tm/dispatch.d.ts.map +1 -1
  100. package/dist/tm/dispatch.js +230 -31
  101. package/dist/tm/dispatch.js.map +1 -1
  102. package/dist/tm/dupe-guard.d.ts +16 -3
  103. package/dist/tm/dupe-guard.d.ts.map +1 -1
  104. package/dist/tm/dupe-guard.js +27 -7
  105. package/dist/tm/dupe-guard.js.map +1 -1
  106. package/dist/tm/index.d.ts +40 -1
  107. package/dist/tm/index.d.ts.map +1 -1
  108. package/dist/tm/index.js +40 -11
  109. package/dist/tm/index.js.map +1 -1
  110. package/dist/tm/ledger.d.ts +114 -0
  111. package/dist/tm/ledger.d.ts.map +1 -0
  112. package/dist/tm/ledger.js +308 -0
  113. package/dist/tm/ledger.js.map +1 -0
  114. package/dist/tm/perm-ask.d.ts +1 -3
  115. package/dist/tm/perm-ask.d.ts.map +1 -1
  116. package/dist/tm/perm-ask.js +10 -1
  117. package/dist/tm/perm-ask.js.map +1 -1
  118. package/dist/tm/preview.d.ts +55 -0
  119. package/dist/tm/preview.d.ts.map +1 -1
  120. package/dist/tm/preview.js +225 -0
  121. package/dist/tm/preview.js.map +1 -1
  122. package/dist/tm/search.d.ts.map +1 -1
  123. package/dist/tm/search.js +40 -10
  124. package/dist/tm/search.js.map +1 -1
  125. package/dist/tm/serp-loop.d.ts +45 -0
  126. package/dist/tm/serp-loop.d.ts.map +1 -0
  127. package/dist/tm/serp-loop.js +80 -0
  128. package/dist/tm/serp-loop.js.map +1 -0
  129. package/dist/tm/stats.d.ts +10 -0
  130. package/dist/tm/stats.d.ts.map +1 -1
  131. package/dist/tm/stats.js +160 -0
  132. package/dist/tm/stats.js.map +1 -1
  133. package/dist/tm/webfetch.d.ts +4 -0
  134. package/dist/tm/webfetch.d.ts.map +1 -1
  135. package/dist/tm/webfetch.js +75 -8
  136. package/dist/tm/webfetch.js.map +1 -1
  137. package/docs/installation-v2.md +436 -0
  138. package/docs/installation.md +12 -0
  139. package/docs/research/agent-data-exchange.md +117 -0
  140. package/docs/research/browser-pane.md +89 -0
  141. package/docs/research/host-http-api.md +132 -0
  142. package/docs/research/host-subagent-injection.md +187 -0
  143. package/docs/research/plugin-loader-contract.md +261 -0
  144. package/docs/research/v2-builtin-tools.md +447 -0
  145. package/docs/v2-port-notes.md +315 -0
  146. package/index.js +7 -0
  147. package/package.json +64 -61
  148. package/scripts/gen-v2-config.mjs +304 -0
  149. package/scripts/lib/config-surgery.cjs +239 -0
  150. package/scripts/lib/fake-ctx.mjs +146 -0
@@ -0,0 +1,193 @@
1
+ /**
2
+ * The v2 client shim.
3
+ *
4
+ * v1 handed plugin tools an SDK client with `file.read` and `find.text`; the v2
5
+ * plugin context has no `file` domain at all (verified against the live
6
+ * `ctx` key list), and `tm_read`/`tm_grep` hard-fail without it
7
+ * ("宿主 client 不可用"). Rather than fork those two pipelines per host
8
+ * generation, this module satisfies the SAME two method contracts from Node fs,
9
+ * returning the exact payload shapes the real host emits so
10
+ * `unwrapClientResult`/`extractText` in `src/tm/client-unwrap.ts` keep working
11
+ * untouched.
12
+ *
13
+ * The scan is bounded on purpose. An unbounded recursive grep over a real
14
+ * workspace is the difference between a tool call and a hung turn, and the
15
+ * bound is REPORTED rather than silently truncating — a truncated answer that
16
+ * reads like a complete one is the defect this repo keeps getting paid for.
17
+ */
18
+ import fs from "node:fs";
19
+ import path from "node:path";
20
+ /** Directories no read-only search should ever walk into. */
21
+ const SKIP_DIRS = new Set([
22
+ ".git",
23
+ "node_modules",
24
+ "dist",
25
+ "build",
26
+ "out",
27
+ "target",
28
+ ".next",
29
+ ".nuxt",
30
+ ".venv",
31
+ "venv",
32
+ "__pycache__",
33
+ ".cache",
34
+ ".turbo",
35
+ ".gradle",
36
+ "coverage",
37
+ ".pytest_cache",
38
+ ]);
39
+ const MAX_VISITED_FILES = 20_000;
40
+ const MAX_MATCHED_FILES = 200;
41
+ const MAX_LINES_PER_FILE = 20;
42
+ const MAX_TOTAL_LINES = 500;
43
+ const MAX_SCAN_FILE_BYTES = 2 * 1024 * 1024;
44
+ const MAX_READ_BYTES = 8 * 1024 * 1024;
45
+ function clientError(name, message) {
46
+ return { data: { error: { name, data: { message } } } };
47
+ }
48
+ function looksBinary(buffer) {
49
+ const head = buffer.subarray(0, Math.min(buffer.length, 8_192));
50
+ return head.includes(0);
51
+ }
52
+ /**
53
+ * `client.file.read({ query: { path, directory } })` →
54
+ * `{ data: { type: "text", content } }` (the 1.18.x real-host shape).
55
+ */
56
+ async function read(req) {
57
+ const query = req?.query ?? {};
58
+ const target = typeof query.path === "string" ? query.path : "";
59
+ if (!target)
60
+ return clientError("ReadError", "缺少 path 参数");
61
+ try {
62
+ const st = fs.statSync(target);
63
+ if (st.isDirectory())
64
+ return clientError("ReadError", `路径是目录: ${target}`);
65
+ if (st.size > MAX_READ_BYTES) {
66
+ return clientError("ReadError", `文件过大(${st.size} 字节 > ${MAX_READ_BYTES}),本工具不整读:改用 tm_grep 定位行号,或 tm_bash 的 head/tail 取样`);
67
+ }
68
+ return { data: { type: "text", content: fs.readFileSync(target, "utf8") } };
69
+ }
70
+ catch (err) {
71
+ const code = err?.code ?? "ReadError";
72
+ return clientError(String(code), `${code}: ${target}`);
73
+ }
74
+ }
75
+ function walkFiles(root, out) {
76
+ let truncated = false;
77
+ const stack = [root];
78
+ while (stack.length) {
79
+ const dir = stack.pop();
80
+ let entries;
81
+ try {
82
+ entries = fs.readdirSync(dir, { withFileTypes: true });
83
+ }
84
+ catch {
85
+ continue; // unreadable directory: skip, it is not our workspace to force
86
+ }
87
+ for (const entry of entries) {
88
+ const full = path.join(dir, entry.name);
89
+ if (entry.isSymbolicLink())
90
+ continue; // never follow links: cycles and escapes
91
+ if (entry.isDirectory()) {
92
+ if (SKIP_DIRS.has(entry.name))
93
+ continue;
94
+ stack.push(full);
95
+ continue;
96
+ }
97
+ if (!entry.isFile())
98
+ continue;
99
+ if (out.length >= MAX_VISITED_FILES)
100
+ return { truncated: true };
101
+ out.push(full);
102
+ }
103
+ }
104
+ return { truncated };
105
+ }
106
+ /**
107
+ * `client.find.text({ query: { pattern, directory } })` →
108
+ * `{ data: FindMatch[] }`. `pattern` arrives as the raw model string, exactly
109
+ * as in v1; an invalid regex is the CALLER's problem to report, so it throws
110
+ * and pipelines.ts turns it into a structured error like every other host
111
+ * client failure.
112
+ */
113
+ async function findText(req) {
114
+ const query = req?.query ?? {};
115
+ const pattern = typeof query.pattern === "string" ? query.pattern : "";
116
+ if (!pattern)
117
+ return clientError("FindError", "缺少 pattern 参数");
118
+ const root = typeof query.directory === "string" && query.directory ? query.directory : process.cwd();
119
+ const re = new RegExp(pattern);
120
+ const files = [];
121
+ const walked = walkFiles(root, files);
122
+ const matches = [];
123
+ let totalLines = 0;
124
+ // Every bound that actually BIT has to be named in the answer: a grep that
125
+ // shows 20 of a file's 400 matching lines and stays silent reads as a
126
+ // complete answer, which is the exact defect this repo keeps paying for.
127
+ const trimmed = [];
128
+ let truncated = walked.truncated;
129
+ for (const file of files) {
130
+ if (matches.length >= MAX_MATCHED_FILES || totalLines >= MAX_TOTAL_LINES) {
131
+ truncated = true;
132
+ trimmed.push(`范围在 ${matches.length} 个文件 / ${totalLines} 行处停止(${files.length} 个文件待扫)`);
133
+ break;
134
+ }
135
+ let st;
136
+ try {
137
+ st = fs.statSync(file);
138
+ }
139
+ catch {
140
+ continue;
141
+ }
142
+ if (st.size > MAX_SCAN_FILE_BYTES)
143
+ continue;
144
+ let buffer;
145
+ try {
146
+ buffer = fs.readFileSync(file);
147
+ }
148
+ catch {
149
+ continue;
150
+ }
151
+ if (buffer.length === 0 || looksBinary(buffer))
152
+ continue;
153
+ const lines = buffer.toString("utf8").split(/\r?\n/);
154
+ const hits = [];
155
+ let firstLine = 0;
156
+ let matchedInFile = 0;
157
+ for (let i = 0; i < lines.length; i++) {
158
+ if (!re.test(lines[i]))
159
+ continue;
160
+ matchedInFile++;
161
+ if (!firstLine)
162
+ firstLine = i + 1;
163
+ if (hits.length < MAX_LINES_PER_FILE && totalLines + hits.length < MAX_TOTAL_LINES)
164
+ hits.push(lines[i]);
165
+ }
166
+ if (!matchedInFile)
167
+ continue;
168
+ if (hits.length < matchedInFile) {
169
+ truncated = true;
170
+ trimmed.push(`${path.basename(file)} 命中 ${matchedInFile} 行,只带前 ${hits.length} 行`);
171
+ }
172
+ matches.push({ path: file, lines: hits, line: firstLine });
173
+ totalLines += hits.length;
174
+ }
175
+ if (truncated) {
176
+ const detail = trimmed.length
177
+ ? trimmed.slice(0, 3).join(";") + (trimmed.length > 3 ? `;另有 ${trimmed.length - 3} 处` : "")
178
+ : "达到扫描上限";
179
+ matches.push({
180
+ path: "(扫描被截断)",
181
+ lines: [
182
+ `这是部分结果:${detail}(上限 每文件 ${MAX_LINES_PER_FILE} 行 / ${MAX_MATCHED_FILES} 个文件 / ${MAX_TOTAL_LINES} 行)。缩小 directory 或收紧 pattern 再来一次,别把这份当成全量`,
183
+ ],
184
+ line: 0,
185
+ });
186
+ }
187
+ return { data: matches };
188
+ }
189
+ /** The two methods `createTmTools` actually reaches for on this object. */
190
+ export function createV2Client() {
191
+ return { file: { read }, find: { text: findText } };
192
+ }
193
+ //# sourceMappingURL=v2-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"v2-client.js","sourceRoot":"","sources":["../../src/host/v2-client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,56 @@
1
+ /**
2
+ * The v2 event feed — the seam `tm_join`'s settle detection was missing (#8).
3
+ *
4
+ * v1 handed the plugin a host `event` hook and `src/host/v1.ts` pumped every
5
+ * `session.idle` / `session.error` / `session.status` into
6
+ * `tmRuntime.observeDispatchEvent`. The v2 personality never subscribed to
7
+ * anything, so on 2.x the child registry learned nothing: a child that finished
8
+ * two seconds after the dispatch stayed "running" for the whole `waitMs` budget,
9
+ * `tm_join` answered 仍在运行 about a session that had already settled, and the
10
+ * lead's second wait was cut short by a streak counter that was measuring our own
11
+ * deafness. That is goal #6's failure shape — a tool reporting a state it never
12
+ * observed — and it was a bug, not a limitation.
13
+ *
14
+ * `ctx.event.subscribe()` is an async iterable on the Promise-based plugin API:
15
+ * measured on a live 2.0.16 (docs/research/agent-data-exchange.md), with no Effect
16
+ * runtime and no new dependency. Two properties of it are load-bearing here:
17
+ *
18
+ * · the stream is SERVER-WIDE. Every session on the host appears in it, so the
19
+ * feed forwards only event types on the whitelist below and the dispatcher
20
+ * ignores any session id it has no record of — the plugin never reads a
21
+ * stranger's session state into our context or trajectory.
22
+ * · event NAMES changed wholesale between generations. So the whitelist is a
23
+ * living list with a counter behind it: an unseen type is counted and stays
24
+ * unseen, rather than being forwarded on a guess or swallowed silently. After
25
+ * the next host upgrade, `tm_stats` is where the renamed events show up.
26
+ *
27
+ * Nothing in here may break a session: every subscription and iteration step is
28
+ * wrapped, and a stream that throws ends the feed with a recorded reason instead
29
+ * of an exception in the host's event loop.
30
+ */
31
+ import type { HostEvent } from "../types.js";
32
+ import type { V2Registration } from "./v2-types.js";
33
+ /** The types the dispatcher understands, plus the v2 spellings we have observed.
34
+ * Anything else is counted, not forwarded. */
35
+ export declare const FEED_TYPES: readonly ["session.idle", "session.error", "session.status", "session.statused", "session.updated"];
36
+ export interface V2EventFeedReport {
37
+ /** subscribe() existed and returned something iterable */
38
+ active: boolean;
39
+ received: number;
40
+ forwarded: number;
41
+ /** type → count, for every type we did not recognise (names only, no payload) */
42
+ unknown: Record<string, number>;
43
+ /** what killed the loop, if anything — the difference between "no events" and
44
+ * "we stopped listening" */
45
+ stopped?: string;
46
+ }
47
+ export interface V2EventFeed {
48
+ readonly report: V2EventFeedReport;
49
+ registrations: V2Registration[];
50
+ stop: () => Promise<void>;
51
+ }
52
+ export declare function applyV2EventFeed(ctx: unknown, opts: {
53
+ onEvent: (event: HostEvent) => void;
54
+ types?: readonly string[];
55
+ }): Promise<V2EventFeed>;
56
+ //# sourceMappingURL=v2-events.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"v2-events.d.ts","sourceRoot":"","sources":["../../src/host/v2-events.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AAC5C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAA;AAEnD;+CAC+C;AAC/C,eAAO,MAAM,UAAU,qGAMb,CAAA;AAEV,MAAM,WAAW,iBAAiB;IAChC,0DAA0D;IAC1D,MAAM,EAAE,OAAO,CAAA;IACf,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,MAAM,CAAA;IACjB,iFAAiF;IACjF,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC/B;iCAC6B;IAC7B,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,MAAM,EAAE,iBAAiB,CAAA;IAClC,aAAa,EAAE,cAAc,EAAE,CAAA;IAC/B,IAAI,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CAC1B;AAeD,wBAAsB,gBAAgB,CACpC,GAAG,EAAE,OAAO,EACZ,IAAI,EAAE;IAAE,OAAO,EAAE,CAAC,KAAK,EAAE,SAAS,KAAK,IAAI,CAAC;IAAC,KAAK,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;CAAE,GACvE,OAAO,CAAC,WAAW,CAAC,CAsGtB"}
@@ -0,0 +1,158 @@
1
+ /**
2
+ * The v2 event feed — the seam `tm_join`'s settle detection was missing (#8).
3
+ *
4
+ * v1 handed the plugin a host `event` hook and `src/host/v1.ts` pumped every
5
+ * `session.idle` / `session.error` / `session.status` into
6
+ * `tmRuntime.observeDispatchEvent`. The v2 personality never subscribed to
7
+ * anything, so on 2.x the child registry learned nothing: a child that finished
8
+ * two seconds after the dispatch stayed "running" for the whole `waitMs` budget,
9
+ * `tm_join` answered 仍在运行 about a session that had already settled, and the
10
+ * lead's second wait was cut short by a streak counter that was measuring our own
11
+ * deafness. That is goal #6's failure shape — a tool reporting a state it never
12
+ * observed — and it was a bug, not a limitation.
13
+ *
14
+ * `ctx.event.subscribe()` is an async iterable on the Promise-based plugin API:
15
+ * measured on a live 2.0.16 (docs/research/agent-data-exchange.md), with no Effect
16
+ * runtime and no new dependency. Two properties of it are load-bearing here:
17
+ *
18
+ * · the stream is SERVER-WIDE. Every session on the host appears in it, so the
19
+ * feed forwards only event types on the whitelist below and the dispatcher
20
+ * ignores any session id it has no record of — the plugin never reads a
21
+ * stranger's session state into our context or trajectory.
22
+ * · event NAMES changed wholesale between generations. So the whitelist is a
23
+ * living list with a counter behind it: an unseen type is counted and stays
24
+ * unseen, rather than being forwarded on a guess or swallowed silently. After
25
+ * the next host upgrade, `tm_stats` is where the renamed events show up.
26
+ *
27
+ * Nothing in here may break a session: every subscription and iteration step is
28
+ * wrapped, and a stream that throws ends the feed with a recorded reason instead
29
+ * of an exception in the host's event loop.
30
+ */
31
+ /** The types the dispatcher understands, plus the v2 spellings we have observed.
32
+ * Anything else is counted, not forwarded. */
33
+ export const FEED_TYPES = [
34
+ "session.idle",
35
+ "session.error",
36
+ "session.status",
37
+ "session.statused",
38
+ "session.updated",
39
+ ];
40
+ /** Event shapes the host has used across generations; read defensively because
41
+ * none of them is a promised interface. */
42
+ function eventOf(raw) {
43
+ const e = raw;
44
+ if (!e || typeof e !== "object")
45
+ return {};
46
+ const type = e.type ?? e.kind ?? e.name;
47
+ const props = e.properties ?? e.payload ?? e.data;
48
+ return {
49
+ ...(typeof type === "string" ? { type } : {}),
50
+ ...(props && typeof props === "object" ? { properties: props } : {}),
51
+ };
52
+ }
53
+ export async function applyV2EventFeed(ctx, opts) {
54
+ const allowed = new Set(opts.types ?? FEED_TYPES);
55
+ const report = { active: false, received: 0, forwarded: 0, unknown: {} };
56
+ let closed = false;
57
+ let closeStream;
58
+ const feed = {
59
+ report,
60
+ registrations: [],
61
+ async stop() {
62
+ closed = true;
63
+ try {
64
+ await closeStream?.();
65
+ }
66
+ catch {
67
+ /* the stream is already gone; the feed is closing either way */
68
+ }
69
+ },
70
+ };
71
+ const event = ctx?.event;
72
+ const subscribe = event?.subscribe;
73
+ if (typeof subscribe !== "function") {
74
+ report.stopped = "no ctx.event.subscribe on this host";
75
+ return feed;
76
+ }
77
+ let stream;
78
+ try {
79
+ // Property access, never a captured reference: a detached `subscribe` loses
80
+ // its receiver and the host's client throws (the dispatch.ts §10 rule).
81
+ stream = await event.subscribe();
82
+ }
83
+ catch (err) {
84
+ report.stopped = `subscribe threw: ${String(err?.message ?? err).slice(0, 120)}`;
85
+ return feed;
86
+ }
87
+ const iterable = stream;
88
+ if (!iterable || (typeof iterable[Symbol.asyncIterator] !== "function" && typeof iterable.subscribe !== "function" && typeof iterable.on !== "function")) {
89
+ report.stopped = "subscribe returned something we cannot iterate";
90
+ return feed;
91
+ }
92
+ report.active = true;
93
+ const handle = (raw) => {
94
+ if (closed)
95
+ return;
96
+ const ev = eventOf(raw);
97
+ report.received++;
98
+ const type = String(ev.type ?? "");
99
+ if (!type || !allowed.has(type)) {
100
+ const key = type || "(no type)";
101
+ report.unknown[key] = (report.unknown[key] ?? 0) + 1;
102
+ return;
103
+ }
104
+ report.forwarded++;
105
+ try {
106
+ opts.onEvent(ev);
107
+ }
108
+ catch (err) {
109
+ report.stopped = `consumer threw: ${String(err?.message ?? err).slice(0, 120)}`;
110
+ closed = true;
111
+ }
112
+ };
113
+ if (typeof iterable.subscribe === "function") {
114
+ try {
115
+ ;
116
+ iterable.subscribe(handle);
117
+ }
118
+ catch (err) {
119
+ report.stopped = `stream.subscribe threw: ${String(err?.message ?? err).slice(0, 120)}`;
120
+ }
121
+ return feed;
122
+ }
123
+ if (typeof iterable.on === "function") {
124
+ for (const t of allowed) {
125
+ try {
126
+ ;
127
+ iterable.on(t, handle);
128
+ }
129
+ catch {
130
+ /* one type missing is not a reason to lose the rest */
131
+ }
132
+ }
133
+ return feed;
134
+ }
135
+ // async-iterable path: pull until the host closes the stream or we stop. The
136
+ // iterator is held rather than created inline by `for await` because teardown has
137
+ // to reach the SAME object — return() on a second, untouched iterator would
138
+ // "close" a subscription while the running one kept the host's stream alive.
139
+ const pull = iterable[Symbol.asyncIterator]?.();
140
+ if (pull)
141
+ closeStream = () => pull.return?.();
142
+ void (async () => {
143
+ try {
144
+ if (!pull)
145
+ return;
146
+ for await (const raw of { [Symbol.asyncIterator]: () => pull }) {
147
+ if (closed)
148
+ break;
149
+ handle(raw);
150
+ }
151
+ }
152
+ catch (err) {
153
+ report.stopped = `stream threw: ${String(err?.message ?? err).slice(0, 120)}`;
154
+ }
155
+ })();
156
+ return feed;
157
+ }
158
+ //# sourceMappingURL=v2-events.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"v2-events.js","sourceRoot":"","sources":["../../src/host/v2-events.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,101 @@
1
+ /**
2
+ * The v2 permission-guard layer (#7).
3
+ *
4
+ * v1 enforced two things in code that v2 can enforce one level lower — at the
5
+ * host's own `permission.hook("evaluate")`, whose `effect` is mutable and whose
6
+ * `resources` carry the concrete command line or URL (live-probed: `action`
7
+ * arrives as shell / read / edit / external_directory with the real payload).
8
+ *
9
+ * 1. THE EGRESS RED LINE ON THE HOST'S OWN WEB TOOL. v1 never had to think
10
+ * about this because native `webfetch` was denied in the whitelist; on v2 a
11
+ * Build-class agent has it, and it has no notion of `169.254.169.254` — the
12
+ * cloud metadata endpoint whose response is temporary credentials. The
13
+ * domain allowlist answers "is this host on the list"; only `checkWebUrl`
14
+ * asks the address question, and that question has to be asked of the native
15
+ * path too or the port becomes a way around it.
16
+ * 2. R6's per-command precision. v2's closest thing to v1's pattern-object
17
+ * escalation is currently "ask on every shell command", which is honest but
18
+ * coarse. The classifier can restore the per-command shape — but only if
19
+ * the host actually calls `evaluate` for shell, which has not been proven on
20
+ * a live 2.0.16 yet. So the strictening hook is installed either way and
21
+ * COUNTS what it sees, while the config-level coarse ask stays in force
22
+ * until `TM_R6_FINE_ASK=on` says otherwise. A guard that fails open because
23
+ * we trusted an unproven hook is the opposite of this product.
24
+ *
25
+ * The hook only ever makes a decision STRICTER (allow -> ask, anything -> deny on
26
+ * a red line). It never loosens what the host or the user's own rules chose.
27
+ */
28
+ import { type EnvProtectMode } from "../envprotect.js";
29
+ import type { TeamScope } from "./v2-scope.js";
30
+ import type { V2Context, V2Registration } from "./v2-types.js";
31
+ export interface GuardDecision {
32
+ effect: "allow" | "deny" | "ask";
33
+ message?: string;
34
+ /** the rule that answered, in words the agent reads back — never a bare code */
35
+ why: string;
36
+ }
37
+ /** The address question, asked of the host's native web path. */
38
+ export declare function webGuard(resources: readonly string[]): GuardDecision | null;
39
+ /** R6's env face and R2's danger face, per command line. */
40
+ export declare function shellGuard(command: string | undefined, mode: EnvProtectMode): GuardDecision | null;
41
+ export interface GuardReport {
42
+ seen: number;
43
+ byAction: Record<string, number>;
44
+ strictened: number;
45
+ denied: number;
46
+ /** what the classifier WOULD have asked for, counted even while the coarse
47
+ * config-level ask is still in force — this is the evidence that decides
48
+ * whether TM_R6_FINE_ASK can become the default. */
49
+ shellMatched: number;
50
+ /** evaluations skipped because the session belongs to a non-Team agent (#22) —
51
+ * the number that tells the user how much of this floor is not ours to hold */
52
+ foreignSkipped: number;
53
+ }
54
+ export interface BackgroundForceReport {
55
+ seen: number;
56
+ forced: number;
57
+ }
58
+ /**
59
+ * Every sub-agent dispatch runs in the background.
60
+ *
61
+ * The user's standing instruction (2026-09-25). It is not a preference tweak: a
62
+ * foreground `subagent` call blocks the lead for the whole child run, which is
63
+ * the one thing the throughput mandate cannot survive — and on v2 background is
64
+ * a first-class host capability (`subagent {background:true}` returns
65
+ * immediately and the parent is notified when the child finishes), so nothing
66
+ * has to be opted into at the environment level the way v1 needed
67
+ * `OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS`.
68
+ *
69
+ * `execute.before` takes a mutable `input` (docs say "inspect or REPLACE"; a
70
+ * live host confirmed the write lands), so this is the honest place to enforce
71
+ * it — a prompt rule thousands of tokens earlier is not the same thing. An
72
+ * input that is not an object is left alone rather than invented.
73
+ */
74
+ export declare function applyV2BackgroundForce(ctx: V2Context, opts?: {
75
+ scope?: TeamScope;
76
+ }): Promise<{
77
+ registrations: V2Registration[];
78
+ report: BackgroundForceReport;
79
+ }>;
80
+ export declare function applyV2PermissionGuards(ctx: V2Context, opts: {
81
+ envProtectMode: EnvProtectMode;
82
+ scope?: TeamScope;
83
+ }): Promise<{
84
+ registrations: V2Registration[];
85
+ report: GuardReport;
86
+ installed: boolean;
87
+ }>;
88
+ /** Does the config still need the coarse `shell -> ask` escalation? Only while
89
+ * the fine path is switched off; with the classifier in charge, escalating every
90
+ * command would mask it.
91
+ * Was the coarse default until a live host proved the seam exists: a
92
+ * `--standalone` run of the v2 personality recorded
93
+ * `{action:"shell", resourceCount:1, hasUrl:false}` reaching `evaluate` for a real
94
+ * `git status --short` (2026-09-25), which is the observation this function was
95
+ * waiting for. So the classifier is in charge by default and the escalation is
96
+ * the fallback again — `TM_R6_FINE_ASK=off` restores "every command asks", which is
97
+ * what a user should reach for if their host build turns out not to fire the hook.
98
+ * An absent hook still forces coarse: no seam, no per-command judgement.
99
+ */
100
+ export declare function needsCoarseShellAsk(env: NodeJS.ProcessEnv, guardsInstalled: boolean): boolean;
101
+ //# sourceMappingURL=v2-guard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"v2-guard.d.ts","sourceRoot":"","sources":["../../src/host/v2-guard.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,OAAO,EAKL,KAAK,cAAc,EACpB,MAAM,kBAAkB,CAAA;AAEzB,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,eAAe,CAAA;AAC9C,OAAO,KAAK,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,eAAe,CAAA;AAE9D,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,OAAO,GAAG,MAAM,GAAG,KAAK,CAAA;IAChC,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,gFAAgF;IAChF,GAAG,EAAE,MAAM,CAAA;CACZ;AAqBD,iEAAiE;AACjE,wBAAgB,QAAQ,CAAC,SAAS,EAAE,SAAS,MAAM,EAAE,GAAG,aAAa,GAAG,IAAI,CAyB3E;AAED,4DAA4D;AAC5D,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,EAAE,IAAI,EAAE,cAAc,GAAG,aAAa,GAAG,IAAI,CAUlG;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAChC,UAAU,EAAE,MAAM,CAAA;IAClB,MAAM,EAAE,MAAM,CAAA;IACd;;yDAEqD;IACrD,YAAY,EAAE,MAAM,CAAA;IACpB;oFACgF;IAChF,cAAc,EAAE,MAAM,CAAA;CACvB;AAED,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,MAAM,CAAA;CACf;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,sBAAsB,CAC1C,GAAG,EAAE,SAAS,EACd,IAAI,GAAE;IAAE,KAAK,CAAC,EAAE,SAAS,CAAA;CAAO,GAC/B,OAAO,CAAC;IAAE,aAAa,EAAE,cAAc,EAAE,CAAC;IAAC,MAAM,EAAE,qBAAqB,CAAA;CAAE,CAAC,CAoB7E;AAED,wBAAsB,uBAAuB,CAC3C,GAAG,EAAE,SAAS,EACd,IAAI,EAAE;IAAE,cAAc,EAAE,cAAc,CAAC;IAAC,KAAK,CAAC,EAAE,SAAS,CAAA;CAAE,GAC1D,OAAO,CAAC;IAAE,aAAa,EAAE,cAAc,EAAE,CAAC;IAAC,MAAM,EAAE,WAAW,CAAC;IAAC,SAAS,EAAE,OAAO,CAAA;CAAE,CAAC,CA0CvF;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,CAAC,UAAU,EAAE,eAAe,EAAE,OAAO,GAAG,OAAO,CAO7F"}