@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,268 @@
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
3
+ import { parseHostEnvelope } from "../task-offload.js";
4
+ const URLish = (s) => {
5
+ if (typeof s !== "string")
6
+ return false;
7
+ return /^https?:\/\//i.test(s.trim()) || /^\[[0-9a-f:.]+\]$/i.test(s.trim());
8
+ };
9
+ const keysOf = (v) => {
10
+ if (!v || typeof v !== "object" || Array.isArray(v))
11
+ return [];
12
+ return Object.keys(v).sort();
13
+ };
14
+ /** Any ctx domain whose `hook` is absent is reported rather than crashing boot. */
15
+ function hookFn(ctx, domain) {
16
+ const d = ctx?.[domain];
17
+ const h = d?.hook;
18
+ return typeof h === "function" ? h : null;
19
+ }
20
+ export async function applyV2Probe(ctx, opts = {}) {
21
+ const env = opts.env ?? process.env;
22
+ const file = typeof env.TM_V2_PROBE === "string" && env.TM_V2_PROBE.trim() ? env.TM_V2_PROBE.trim() : null;
23
+ const maxLines = Number.isFinite(Number(opts.maxLines)) && Number(opts.maxLines) > 0 ? Number(opts.maxLines) : 400;
24
+ const flushMs = Number.isFinite(Number(opts.flushMs)) && Number(opts.flushMs) >= 0 ? Number(opts.flushMs) : 20_000;
25
+ const report = {
26
+ agentsSeen: [],
27
+ toolNames: [],
28
+ executed: [],
29
+ executedAfter: [],
30
+ actions: [],
31
+ urlResources: 0,
32
+ evaluations: 0,
33
+ messages: 0,
34
+ taskEnvelopes: 0,
35
+ maxEnvelopeChars: 0,
36
+ messageShapes: [],
37
+ envelopeForms: new Set(),
38
+ hooksMissing: [],
39
+ ctxDomains: [],
40
+ probeTarget: file ? path.basename(file) : "",
41
+ lines: 0,
42
+ };
43
+ report.ctxDomains = Object.keys((ctx ?? {})).sort();
44
+ const seenToolNames = new Set();
45
+ const seenExecuted = new Set();
46
+ const seenExecutedAfter = new Set();
47
+ const seenActions = new Set();
48
+ const seenAgents = new Set();
49
+ const seenBeforeShapes = new Set();
50
+ const messageShapes = [];
51
+ const envelopeForms = report.envelopeForms;
52
+ const write = (rec) => {
53
+ if (!file || report.lines >= maxLines)
54
+ return;
55
+ report.lines += 1;
56
+ try {
57
+ fs.appendFileSync(file, `${JSON.stringify({ at: new Date().toISOString(), ...rec })}\n`);
58
+ }
59
+ catch {
60
+ /* a probe that cannot write stays silent; it must never break a tool call */
61
+ }
62
+ };
63
+ let lastFlush = 0;
64
+ let lastSummary = "";
65
+ // Declared BEFORE any hook is registered: the host can fire a context hook while
66
+ // this function is still awaiting its own registrations, and a callback landing
67
+ // in the temporal dead zone would throw inside somebody's model request.
68
+ const flush = (force = false) => {
69
+ if (!opts.onSummary)
70
+ return;
71
+ const now = Date.now();
72
+ if (!force && now - lastFlush < flushMs)
73
+ return;
74
+ const summary = probeSummary(report);
75
+ const flat = JSON.stringify(summary);
76
+ // An idle session re-runs the context hook on every request; writing an
77
+ // identical line each time would be noise that buries the ones that differ.
78
+ if (!force && flat === lastSummary)
79
+ return;
80
+ lastFlush = now;
81
+ lastSummary = flat;
82
+ try {
83
+ opts.onSummary(summary);
84
+ }
85
+ catch {
86
+ /* observability is an extra, never a reason to fail a request */
87
+ }
88
+ };
89
+ const pending = [];
90
+ const registrations = [];
91
+ const arm = (domain, point, cb) => {
92
+ const hook = hookFn(ctx, domain);
93
+ if (!hook) {
94
+ report.hooksMissing.push(`${domain}.${point}`);
95
+ return;
96
+ }
97
+ // The host's convention is `ctx.session.hook("context", …)` — the POINT name,
98
+ // not "session.context". Passing the dotted form would register a hook under a
99
+ // name nothing fires, and the probe would report an empty surface as if the host
100
+ // had none. The label keeps the domain so a missing seam reads unambiguously.
101
+ // The body is wrapped so a defect HERE can never surface as a failed tool call
102
+ // or a broken model request: an observer that can break the thing it observes is
103
+ // not an observer. (This was not theoretical — a callback referencing a `const`
104
+ // declared further down the module hit its temporal dead zone while the host was
105
+ // mid-hook, which is exactly how the shape recorder reported nothing.)
106
+ const guarded = (event) => {
107
+ try {
108
+ cb(event);
109
+ }
110
+ catch (err) {
111
+ const tag = `${domain}.${point}`;
112
+ if (!report.hooksMissing.includes(`${tag}:callback-threw`))
113
+ report.hooksMissing.push(`${tag}:callback-threw`);
114
+ void err;
115
+ }
116
+ };
117
+ try {
118
+ pending.push(Promise.resolve(hook(point, guarded)));
119
+ }
120
+ catch (err) {
121
+ report.hooksMissing.push(`${domain}.${point}:${String(err?.message ?? err).slice(0, 60)}`);
122
+ }
123
+ };
124
+ arm("session", "context", (raw) => {
125
+ const event = raw;
126
+ const names = Object.keys(event?.tools ?? {});
127
+ const msgs = Array.isArray(event.messages) ? event.messages : [];
128
+ report.messages = Math.max(report.messages, msgs.length);
129
+ // The KEY NAMES of what a message actually is — recorded because guessing the
130
+ // shape is what made the v1 envelope matcher quietly dead: the probe reported
131
+ // "0 envelopes" and the honest reading was "I do not know where this lands",
132
+ // not "nothing lands". Names only, never content.
133
+ if (msgs.length && !messageShapes.length) {
134
+ for (const m of msgs.slice(0, 3)) {
135
+ const keys = keysOf(m);
136
+ const nested = {};
137
+ for (const k of keys) {
138
+ const v = m[k];
139
+ if (v && typeof v === "object")
140
+ nested[k] = Array.isArray(v) ? [`array(${v.length})`] : keysOf(v).slice(0, 8);
141
+ }
142
+ messageShapes.push({ keys, nested });
143
+ }
144
+ report.messageShapes = messageShapes;
145
+ write({ where: "message-shape", shapes: messageShapes });
146
+ }
147
+ // Shape only: a boolean "this looks like the host's envelope" and a character
148
+ // count. Nothing of the body is kept, hashed or written.
149
+ for (const m of msgs) {
150
+ // Read the TEXT of each part. JSON.stringify was wrong twice over: escaping
151
+ // the quotes means an envelope inside a part never matches its own parser (so
152
+ // this counter reported 0 on a host that was emitting them), and it would have
153
+ // serialized fields nobody should have in a probe file.
154
+ const texts = [];
155
+ if (typeof m === "string")
156
+ texts.push(m);
157
+ else {
158
+ const parts = m?.parts;
159
+ if (Array.isArray(parts)) {
160
+ for (const pt of parts) {
161
+ if (typeof pt?.text === "string")
162
+ texts.push(pt.text);
163
+ }
164
+ }
165
+ else if (typeof m?.text === "string")
166
+ texts.push(m.text);
167
+ }
168
+ // Asked of the same parser the offload uses — a probe that greps for the v1
169
+ // string while the host emits another reports "0 envelopes" forever, and a
170
+ // zero like that is indistinguishable from "nothing was big enough".
171
+ for (const text of texts) {
172
+ const env = parseHostEnvelope(text);
173
+ if (!env)
174
+ continue;
175
+ report.taskEnvelopes += 1;
176
+ report.maxEnvelopeChars = Math.max(report.maxEnvelopeChars, text.length);
177
+ report.envelopeForms.add(env.form);
178
+ }
179
+ }
180
+ for (const n of names)
181
+ seenToolNames.add(n);
182
+ const agent = String(event?.agent ?? "?");
183
+ if (!seenAgents.has(agent)) {
184
+ seenAgents.add(agent);
185
+ report.agentsSeen = [...seenAgents].sort();
186
+ write({ where: "context", agent, toolCount: names.length, tools: names.slice().sort() });
187
+ }
188
+ report.toolNames = [...seenToolNames].sort();
189
+ flush(false);
190
+ });
191
+ arm("tool", "execute.before", (raw) => {
192
+ const event = raw;
193
+ const id = String(event?.tool ?? "?");
194
+ if (!seenExecuted.has(id)) {
195
+ seenExecuted.add(id);
196
+ report.executed = [...seenExecuted].sort();
197
+ write({ where: "execute.before", tool: id, inputKeys: keysOf(event?.input) });
198
+ return;
199
+ }
200
+ // A tool already on record still tells us something new when its argument
201
+ // table differs — the native browser namespace is the open question here, and
202
+ // one line per distinct argument shape is what answers it.
203
+ const shape = keysOf(event?.input).join(",");
204
+ if (id.includes("browser") && !seenBeforeShapes.has(`${id}(${shape})`)) {
205
+ seenBeforeShapes.add(`${id}(${shape})`);
206
+ write({ where: "execute.before.input", tool: id, inputKeys: keysOf(event?.input) });
207
+ }
208
+ });
209
+ arm("tool", "execute.after", (raw) => {
210
+ const event = raw;
211
+ const id = String(event?.tool ?? "?");
212
+ if (!seenExecutedAfter.has(id)) {
213
+ seenExecutedAfter.add(id);
214
+ report.executedAfter = [...seenExecutedAfter].sort();
215
+ write({ where: "execute.after", tool: id, resultKeys: keysOf(event?.result) });
216
+ }
217
+ });
218
+ arm("permission", "evaluate", (raw) => {
219
+ const event = raw;
220
+ report.evaluations += 1;
221
+ const action = String(event?.action ?? "?");
222
+ const resources = Array.isArray(event?.resources) ? event.resources : [];
223
+ const hasUrl = resources.some(URLish);
224
+ if (hasUrl)
225
+ report.urlResources += 1;
226
+ if (seenActions.has(action))
227
+ return;
228
+ seenActions.add(action);
229
+ report.actions = [...seenActions].sort();
230
+ write({ where: "permission.evaluate", action, resourceCount: resources.length, hasUrl });
231
+ });
232
+ // A registration that rejects is a host fact worth recording — silently dropping
233
+ // it would leave the probe reporting "no browser tools" when the real reason is
234
+ // that the hook never attached.
235
+ const settled = await Promise.allSettled(pending);
236
+ settled.forEach((r, i) => {
237
+ if (r.status === "fulfilled" && r.value && typeof r.value.dispose === "function")
238
+ registrations.push(r.value);
239
+ else if (r.status === "rejected")
240
+ report.hooksMissing.push(`#${i}:${String(r.reason?.message ?? r.reason).slice(0, 60)}`);
241
+ });
242
+ flush(true);
243
+ return { enabled: Boolean(file), report, registrations, flush };
244
+ }
245
+ /** Compact form for the trajectory — names are safe, values never are. */
246
+ export function probeSummary(report) {
247
+ const browserTools = report.toolNames.filter((n) => n.toLowerCase().includes("browser"));
248
+ return {
249
+ probe_agents: report.agentsSeen.join(" "),
250
+ probe_tool_count: report.toolNames.length,
251
+ probe_browser_tools: browserTools.join(" "),
252
+ probe_executed: report.executed.join(" "),
253
+ probe_executed_after: report.executedAfter.join(" "),
254
+ probe_actions: report.actions.join(" "),
255
+ probe_evaluations: report.evaluations,
256
+ probe_messages: report.messages,
257
+ probe_task_envelopes: report.taskEnvelopes,
258
+ probe_max_envelope_chars: report.maxEnvelopeChars,
259
+ probe_message_shapes: JSON.stringify(report.messageShapes).slice(0, 500),
260
+ probe_envelope_forms: [...report.envelopeForms].join(" "),
261
+ probe_url_resources: report.urlResources,
262
+ probe_hooks_missing: report.hooksMissing.join(" "),
263
+ probe_ctx_domains: report.ctxDomains.join(" "),
264
+ probe_lines: report.lines,
265
+ probe_target: report.probeTarget,
266
+ };
267
+ }
268
+ //# sourceMappingURL=v2-probe.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"v2-probe.js","sourceRoot":"","sources":["../../src/host/v2-probe.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,SAAS,CAAA;AAC7B,OAAO,KAAK,IAAI,MAAM,WAAW,CAAA;AAEjC,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAA;AAqFtD,MAAM,MAAM,GAAG,CAAC,CAAU,EAAW,EAAE;IACrC,IAAI,OAAO,CAAC,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAA;IACvC,OAAO,eAAe,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,IAAI,oBAAoB,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAA;AAC9E,CAAC,CAAA;AAED,MAAM,MAAM,GAAG,CAAC,CAAU,EAAY,EAAE;IACtC,IAAI,CAAC,CAAC,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC;QAAE,OAAO,EAAE,CAAA;IAC9D,OAAO,MAAM,CAAC,IAAI,CAAC,CAA4B,CAAC,CAAC,IAAI,EAAE,CAAA;AACzD,CAAC,CAAA;AAED,mFAAmF;AACnF,SAAS,MAAM,CAAC,GAAY,EAAE,MAAc;IAC1C,MAAM,CAAC,GAAI,GAA2C,EAAE,CAAC,MAAM,CAAwC,CAAA;IACvG,MAAM,CAAC,GAAG,CAAC,EAAE,IAAI,CAAA;IACjB,OAAO,OAAO,CAAC,KAAK,UAAU,CAAC,CAAC,CAAE,CAAyD,CAAC,CAAC,CAAC,IAAI,CAAA;AACpG,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,GAAY,EAAE,OAAqB,EAAE;IACtE,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,CAAA;IACnC,MAAM,IAAI,GAAG,OAAO,GAAG,CAAC,WAAW,KAAK,QAAQ,IAAI,GAAG,CAAC,WAAW,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,WAAW,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,CAAA;IAC1G,MAAM,QAAQ,GAAG,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,GAAG,CAAA;IAClH,MAAM,OAAO,GAAG,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,MAAM,CAAA;IAElH,MAAM,MAAM,GAAkB;QAC5B,UAAU,EAAE,EAAE;QACd,SAAS,EAAE,EAAE;QACb,QAAQ,EAAE,EAAE;QACZ,aAAa,EAAE,EAAE;QACjB,OAAO,EAAE,EAAE;QACX,YAAY,EAAE,CAAC;QACf,WAAW,EAAE,CAAC;QACd,QAAQ,EAAE,CAAC;QACX,aAAa,EAAE,CAAC;QAChB,gBAAgB,EAAE,CAAC;QACnB,aAAa,EAAE,EAAE;QACjB,aAAa,EAAE,IAAI,GAAG,EAAU;QAChC,YAAY,EAAE,EAAE;QAChB,UAAU,EAAE,EAAE;QACd,WAAW,EAAE,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE;QAC5C,KAAK,EAAE,CAAC;KACT,CAAA;IAED,MAAM,CAAC,UAAU,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,GAAG,IAAI,EAAE,CAA4B,CAAC,CAAC,IAAI,EAAE,CAAA;IAE9E,MAAM,aAAa,GAAG,IAAI,GAAG,EAAU,CAAA;IACvC,MAAM,YAAY,GAAG,IAAI,GAAG,EAAU,CAAA;IACtC,MAAM,iBAAiB,GAAG,IAAI,GAAG,EAAU,CAAA;IAC3C,MAAM,WAAW,GAAG,IAAI,GAAG,EAAU,CAAA;IACrC,MAAM,UAAU,GAAG,IAAI,GAAG,EAAU,CAAA;IACpC,MAAM,gBAAgB,GAAG,IAAI,GAAG,EAAU,CAAA;IAC1C,MAAM,aAAa,GAAmC,EAAE,CAAA;IACxD,MAAM,aAAa,GAAG,MAAM,CAAC,aAAa,CAAA;IAE1C,MAAM,KAAK,GAAG,CAAC,GAA4B,EAAE,EAAE;QAC7C,IAAI,CAAC,IAAI,IAAI,MAAM,CAAC,KAAK,IAAI,QAAQ;YAAE,OAAM;QAC7C,MAAM,CAAC,KAAK,IAAI,CAAC,CAAA;QACjB,IAAI,CAAC;YACH,EAAE,CAAC,cAAc,CAAC,IAAI,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,EAAE,EAAE,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,EAAE,GAAG,GAAG,EAAE,CAAC,IAAI,CAAC,CAAA;QAC1F,CAAC;QAAC,MAAM,CAAC;YACP,6EAA6E;QAC/E,CAAC;IACH,CAAC,CAAA;IAED,IAAI,SAAS,GAAG,CAAC,CAAA;IACjB,IAAI,WAAW,GAAG,EAAE,CAAA;IACpB,iFAAiF;IACjF,gFAAgF;IAChF,yEAAyE;IACzE,MAAM,KAAK,GAAG,CAAC,KAAK,GAAG,KAAK,EAAE,EAAE;QAC9B,IAAI,CAAC,IAAI,CAAC,SAAS;YAAE,OAAM;QAC3B,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAA;QACtB,IAAI,CAAC,KAAK,IAAI,GAAG,GAAG,SAAS,GAAG,OAAO;YAAE,OAAM;QAC/C,MAAM,OAAO,GAAG,YAAY,CAAC,MAAM,CAAC,CAAA;QACpC,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAA;QACpC,wEAAwE;QACxE,4EAA4E;QAC5E,IAAI,CAAC,KAAK,IAAI,IAAI,KAAK,WAAW;YAAE,OAAM;QAC1C,SAAS,GAAG,GAAG,CAAA;QACf,WAAW,GAAG,IAAI,CAAA;QAClB,IAAI,CAAC;YACH,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAA;QACzB,CAAC;QAAC,MAAM,CAAC;YACP,iEAAiE;QACnE,CAAC;IACH,CAAC,CAAA;IAED,MAAM,OAAO,GAAmC,EAAE,CAAA;IAClD,MAAM,aAAa,GAAqB,EAAE,CAAA;IAC1C,MAAM,GAAG,GAAG,CAAC,MAAc,EAAE,KAAa,EAAE,EAA4B,EAAE,EAAE;QAC1E,MAAM,IAAI,GAAG,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,CAAA;QAChC,IAAI,CAAC,IAAI,EAAE,CAAC;YACV,MAAM,CAAC,YAAY,CAAC,IAAI,CAAC,GAAG,MAAM,IAAI,KAAK,EAAE,CAAC,CAAA;YAC9C,OAAM;QACR,CAAC;QACD,8EAA8E;QAC9E,gFAAgF;QAChF,iFAAiF;QACjF,+EAA+E;QAC/E,+EAA+E;QAC/E,iFAAiF;QACjF,iFAAiF;QACjF,iFAAiF;QACjF,uEAAuE;QACvE,MAAM,OAAO,GAAG,CAAC,KAAc,EAAE,EAAE;YACjC,IAAI,CAAC;gBACH,EAAE,CAAC,KAAK,CAAC,CAAA;YACX,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,MAAM,GAAG,GAAG,GAAG,MAAM,IAAI,KAAK,EAAE,CAAA;gBAChC,IAAI,CAAC,MAAM,CAAC,YAAY,CAAC,QAAQ,CAAC,GAAG,GAAG,iBAAiB,CAAC;oBAAE,MAAM,CAAC,YAAY,CAAC,IAAI,CAAC,GAAG,GAAG,iBAAiB,CAAC,CAAA;gBAC7G,KAAK,GAAG,CAAA;YACV,CAAC;QACH,CAAC,CAAA;QACD,IAAI,CAAC;YACH,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,EAAE,OAAO,CAA8B,CAAC,CAAC,CAAA;QAClF,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,CAAC,YAAY,CAAC,IAAI,CAAC,GAAG,MAAM,IAAI,KAAK,IAAI,MAAM,CAAE,GAAa,EAAE,OAAO,IAAI,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC,CAAA;QACvG,CAAC;IACH,CAAC,CAAA;IAED,GAAG,CAAC,SAAS,EAAE,SAAS,EAAE,CAAC,GAAG,EAAE,EAAE;QAChC,MAAM,KAAK,GAAG,GAA0D,CAAA;QACxE,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,EAAE,KAAK,IAAI,EAAE,CAAC,CAAA;QAC7C,MAAM,IAAI,GAAG,KAAK,CAAC,OAAO,CAAE,KAAgC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAE,KAAiC,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAA;QACzH,MAAM,CAAC,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,QAAQ,EAAE,IAAI,CAAC,MAAM,CAAC,CAAA;QACxD,8EAA8E;QAC9E,8EAA8E;QAC9E,6EAA6E;QAC7E,mDAAmD;QACnD,IAAI,IAAI,CAAC,MAAM,IAAI,CAAC,aAAa,CAAC,MAAM,EAAE,CAAC;YACzC,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC;gBACjC,MAAM,IAAI,GAAG,MAAM,CAAC,CAAC,CAAC,CAAA;gBACtB,MAAM,MAAM,GAA6B,EAAE,CAAA;gBAC3C,KAAK,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC;oBACrB,MAAM,CAAC,GAAI,CAA6B,CAAC,CAAC,CAAC,CAAA;oBAC3C,IAAI,CAAC,IAAI,OAAO,CAAC,KAAK,QAAQ;wBAAE,MAAM,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAA;gBAC/G,CAAC;gBACD,aAAa,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAA;YACtC,CAAC;YACD,MAAM,CAAC,aAAa,GAAG,aAAa,CAAA;YACpC,KAAK,CAAC,EAAE,KAAK,EAAE,eAAe,EAAE,MAAM,EAAE,aAAa,EAAE,CAAC,CAAA;QAC1D,CAAC;QACD,8EAA8E;QAC9E,0DAA0D;QAC1D,KAAK,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC;YACrB,6EAA6E;YAC7E,8EAA8E;YAC9E,+EAA+E;YAC/E,wDAAwD;YACxD,MAAM,KAAK,GAAa,EAAE,CAAA;YAC1B,IAAI,OAAO,CAAC,KAAK,QAAQ;gBAAE,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;iBACnC,CAAC;gBACJ,MAAM,KAAK,GAAI,CAAyB,EAAE,KAAK,CAAA;gBAC/C,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;oBACzB,KAAK,MAAM,EAAE,IAAI,KAAK,EAAE,CAAC;wBACvB,IAAI,OAAQ,EAAyB,EAAE,IAAI,KAAK,QAAQ;4BAAE,KAAK,CAAC,IAAI,CAAE,EAAuB,CAAC,IAAI,CAAC,CAAA;oBACrG,CAAC;gBACH,CAAC;qBAAM,IAAI,OAAQ,CAAwB,EAAE,IAAI,KAAK,QAAQ;oBAAE,KAAK,CAAC,IAAI,CAAE,CAAsB,CAAC,IAAI,CAAC,CAAA;YAC1G,CAAC;YACD,4EAA4E;YAC5E,2EAA2E;YAC3E,qEAAqE;YACrE,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;gBACzB,MAAM,GAAG,GAAG,iBAAiB,CAAC,IAAI,CAAC,CAAA;gBACnC,IAAI,CAAC,GAAG;oBAAE,SAAQ;gBAClB,MAAM,CAAC,aAAa,IAAI,CAAC,CAAA;gBACzB,MAAM,CAAC,gBAAgB,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,gBAAgB,EAAE,IAAI,CAAC,MAAM,CAAC,CAAA;gBACxE,MAAM,CAAC,aAAa,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,CAAA;YACpC,CAAC;QACH,CAAC;QACD,KAAK,MAAM,CAAC,IAAI,KAAK;YAAE,aAAa,CAAC,GAAG,CAAC,CAAC,CAAC,CAAA;QAC3C,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,EAAE,KAAK,IAAI,GAAG,CAAC,CAAA;QACzC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC;YAC3B,UAAU,CAAC,GAAG,CAAC,KAAK,CAAC,CAAA;YACrB,MAAM,CAAC,UAAU,GAAG,CAAC,GAAG,UAAU,CAAC,CAAC,IAAI,EAAE,CAAA;YAC1C,KAAK,CAAC,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,CAAC,MAAM,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,CAAA;QAC1F,CAAC;QACD,MAAM,CAAC,SAAS,GAAG,CAAC,GAAG,aAAa,CAAC,CAAC,IAAI,EAAE,CAAA;QAC5C,KAAK,CAAC,KAAK,CAAC,CAAA;IACd,CAAC,CAAC,CAAA;IAEF,GAAG,CAAC,MAAM,EAAE,gBAAgB,EAAE,CAAC,GAAG,EAAE,EAAE;QACpC,MAAM,KAAK,GAAG,GAAyC,CAAA;QACvD,MAAM,EAAE,GAAG,MAAM,CAAC,KAAK,EAAE,IAAI,IAAI,GAAG,CAAC,CAAA;QACrC,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC;YAC1B,YAAY,CAAC,GAAG,CAAC,EAAE,CAAC,CAAA;YACpB,MAAM,CAAC,QAAQ,GAAG,CAAC,GAAG,YAAY,CAAC,CAAC,IAAI,EAAE,CAAA;YAC1C,KAAK,CAAC,EAAE,KAAK,EAAE,gBAAgB,EAAE,IAAI,EAAE,EAAE,EAAE,SAAS,EAAE,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,EAAE,CAAC,CAAA;YAC7E,OAAM;QACR,CAAC;QACD,0EAA0E;QAC1E,8EAA8E;QAC9E,2DAA2D;QAC3D,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;QAC5C,IAAI,EAAE,CAAC,QAAQ,CAAC,SAAS,CAAC,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,KAAK,GAAG,CAAC,EAAE,CAAC;YACvE,gBAAgB,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,KAAK,GAAG,CAAC,CAAA;YACvC,KAAK,CAAC,EAAE,KAAK,EAAE,sBAAsB,EAAE,IAAI,EAAE,EAAE,EAAE,SAAS,EAAE,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,EAAE,CAAC,CAAA;QACrF,CAAC;IACH,CAAC,CAAC,CAAA;IAEF,GAAG,CAAC,MAAM,EAAE,eAAe,EAAE,CAAC,GAAG,EAAE,EAAE;QACnC,MAAM,KAAK,GAAG,GAA0C,CAAA;QACxD,MAAM,EAAE,GAAG,MAAM,CAAC,KAAK,EAAE,IAAI,IAAI,GAAG,CAAC,CAAA;QACrC,IAAI,CAAC,iBAAiB,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC;YAC/B,iBAAiB,CAAC,GAAG,CAAC,EAAE,CAAC,CAAA;YACzB,MAAM,CAAC,aAAa,GAAG,CAAC,GAAG,iBAAiB,CAAC,CAAC,IAAI,EAAE,CAAA;YACpD,KAAK,CAAC,EAAE,KAAK,EAAE,eAAe,EAAE,IAAI,EAAE,EAAE,EAAE,UAAU,EAAE,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,EAAE,CAAC,CAAA;QAChF,CAAC;IACH,CAAC,CAAC,CAAA;IAEF,GAAG,CAAC,YAAY,EAAE,UAAU,EAAE,CAAC,GAAG,EAAE,EAAE;QACpC,MAAM,KAAK,GAAG,GAA0D,CAAA;QACxE,MAAM,CAAC,WAAW,IAAI,CAAC,CAAA;QACvB,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,EAAE,MAAM,IAAI,GAAG,CAAC,CAAA;QAC3C,MAAM,SAAS,GAAG,KAAK,CAAC,OAAO,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,CAAA;QACxE,MAAM,MAAM,GAAG,SAAS,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;QACrC,IAAI,MAAM;YAAE,MAAM,CAAC,YAAY,IAAI,CAAC,CAAA;QACpC,IAAI,WAAW,CAAC,GAAG,CAAC,MAAM,CAAC;YAAE,OAAM;QACnC,WAAW,CAAC,GAAG,CAAC,MAAM,CAAC,CAAA;QACvB,MAAM,CAAC,OAAO,GAAG,CAAC,GAAG,WAAW,CAAC,CAAC,IAAI,EAAE,CAAA;QACxC,KAAK,CAAC,EAAE,KAAK,EAAE,qBAAqB,EAAE,MAAM,EAAE,aAAa,EAAE,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAA;IAC1F,CAAC,CAAC,CAAA;IAEF,iFAAiF;IACjF,gFAAgF;IAChF,gCAAgC;IAChC,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,UAAU,CAAC,OAAO,CAAC,CAAA;IACjD,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;QACvB,IAAI,CAAC,CAAC,MAAM,KAAK,WAAW,IAAI,CAAC,CAAC,KAAK,IAAI,OAAO,CAAC,CAAC,KAAK,CAAC,OAAO,KAAK,UAAU;YAAE,aAAa,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,CAAA;aACxG,IAAI,CAAC,CAAC,MAAM,KAAK,UAAU;YAAE,MAAM,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,MAAM,CAAE,CAAC,CAAC,MAAgB,EAAE,OAAO,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC,CAAA;IACtI,CAAC,CAAC,CAAA;IACF,KAAK,CAAC,IAAI,CAAC,CAAA;IAEX,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,aAAa,EAAE,KAAK,EAAE,CAAA;AACjE,CAAC;AAED,0EAA0E;AAC1E,MAAM,UAAU,YAAY,CAAC,MAAqB;IAChD,MAAM,YAAY,GAAG,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAA;IACxF,OAAO;QACL,YAAY,EAAE,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,GAAG,CAAC;QACzC,gBAAgB,EAAE,MAAM,CAAC,SAAS,CAAC,MAAM;QACzC,mBAAmB,EAAE,YAAY,CAAC,IAAI,CAAC,GAAG,CAAC;QAC3C,cAAc,EAAE,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC;QACzC,oBAAoB,EAAE,MAAM,CAAC,aAAa,CAAC,IAAI,CAAC,GAAG,CAAC;QACpD,aAAa,EAAE,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC;QACvC,iBAAiB,EAAE,MAAM,CAAC,WAAW;QACrC,cAAc,EAAE,MAAM,CAAC,QAAQ;QAC/B,oBAAoB,EAAE,MAAM,CAAC,aAAa;QAC1C,wBAAwB,EAAE,MAAM,CAAC,gBAAgB;QACjD,oBAAoB,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC;QACxE,oBAAoB,EAAE,CAAC,GAAG,MAAM,CAAC,aAAa,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC;QACzD,mBAAmB,EAAE,MAAM,CAAC,YAAY;QACxC,mBAAmB,EAAE,MAAM,CAAC,YAAY,CAAC,IAAI,CAAC,GAAG,CAAC;QAClD,iBAAiB,EAAE,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,GAAG,CAAC;QAC9C,WAAW,EAAE,MAAM,CAAC,KAAK;QACzB,YAAY,EAAE,MAAM,CAAC,WAAW;KACjC,CAAA;AACH,CAAC"}
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Team-scope isolation for the v2 personality (#22, user requirement 2026-09-25).
3
+ *
4
+ * "Everything the plugin changes must stay inside Team mode; the other modes
5
+ * (build, plan, anything the user installed) must look exactly like a freshly
6
+ * installed OpenCode." Every v2 hook we register is GLOBAL — `execute.after`,
7
+ * `permission.evaluate`, `session.context` fire for every session on the host, not
8
+ * only for ours. Without one shared answer to "is this ours?", the plugin would
9
+ * · delete tools from a build agent's request,
10
+ * · set `temperature` on somebody else's model call,
11
+ * · offload a stranger's tool result into OUR run store,
12
+ * · force `background: true` on a dispatch the user made on purpose,
13
+ * · and tighten a permission the user's own config already decided.
14
+ *
15
+ * The answer is deliberately three-valued, and the third value is the one that
16
+ * needs explaining. `unknown` means the host did not tell us which agent this is
17
+ * (the field is optional in the shape we were handed). We then treat the call as
18
+ * NOT ours — requirement first, coverage second — but we COUNT it, because
19
+ * "governance is live" and "governance silently stopped applying because a field
20
+ * went missing in a host upgrade" are two different facts, and the second one has
21
+ * to be readable from `tm_stats` rather than inferred from a bad feeling.
22
+ *
23
+ * A session can also become known from the inside: every call to one of OUR tools
24
+ * carries a session id and an agent in its own context, so `learn()` records that
25
+ * pair and a later `execute.after` for the same session resolves even if the host
26
+ * left `agent` off that event.
27
+ */
28
+ export type ScopeVerdict = "ours" | "foreign" | "unknown";
29
+ export interface V2ScopeReport {
30
+ /** calls that belong to a Team role — the ones we are allowed to touch */
31
+ ours: number;
32
+ /** calls we identified as somebody else's and left completely alone */
33
+ foreign: number;
34
+ /** calls where the host gave us no agent at all — skipped, and the number that
35
+ * tells whether the governance layers are actually live */
36
+ unknown: number;
37
+ }
38
+ export interface TeamScope {
39
+ readonly names: ReadonlySet<string>;
40
+ isOurs(agent: unknown): boolean;
41
+ decide(event: {
42
+ agent?: unknown;
43
+ sessionID?: unknown;
44
+ } | null | undefined): ScopeVerdict;
45
+ learn(agent: unknown, sessionID: unknown): void;
46
+ readonly report: V2ScopeReport;
47
+ /** counted at each call site, so a layer that never fires cannot look healthy */
48
+ count(verdict: ScopeVerdict): ScopeVerdict;
49
+ }
50
+ export declare function createTeamScope(names: Iterable<string>): TeamScope;
51
+ //# sourceMappingURL=v2-scope.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"v2-scope.d.ts","sourceRoot":"","sources":["../../src/host/v2-scope.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,SAAS,GAAG,SAAS,CAAA;AAEzD,MAAM,WAAW,aAAa;IAC5B,0EAA0E;IAC1E,IAAI,EAAE,MAAM,CAAA;IACZ,uEAAuE;IACvE,OAAO,EAAE,MAAM,CAAA;IACf;gEAC4D;IAC5D,OAAO,EAAE,MAAM,CAAA;CAChB;AAED,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,KAAK,EAAE,WAAW,CAAC,MAAM,CAAC,CAAA;IACnC,MAAM,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAAA;IAC/B,MAAM,CAAC,KAAK,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAC;QAAC,SAAS,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,IAAI,GAAG,SAAS,GAAG,YAAY,CAAA;IACxF,KAAK,CAAC,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,OAAO,GAAG,IAAI,CAAA;IAC/C,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAA;IAC9B,iFAAiF;IACjF,KAAK,CAAC,OAAO,EAAE,YAAY,GAAG,YAAY,CAAA;CAC3C;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,GAAG,SAAS,CAyBlE"}
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Team-scope isolation for the v2 personality (#22, user requirement 2026-09-25).
3
+ *
4
+ * "Everything the plugin changes must stay inside Team mode; the other modes
5
+ * (build, plan, anything the user installed) must look exactly like a freshly
6
+ * installed OpenCode." Every v2 hook we register is GLOBAL — `execute.after`,
7
+ * `permission.evaluate`, `session.context` fire for every session on the host, not
8
+ * only for ours. Without one shared answer to "is this ours?", the plugin would
9
+ * · delete tools from a build agent's request,
10
+ * · set `temperature` on somebody else's model call,
11
+ * · offload a stranger's tool result into OUR run store,
12
+ * · force `background: true` on a dispatch the user made on purpose,
13
+ * · and tighten a permission the user's own config already decided.
14
+ *
15
+ * The answer is deliberately three-valued, and the third value is the one that
16
+ * needs explaining. `unknown` means the host did not tell us which agent this is
17
+ * (the field is optional in the shape we were handed). We then treat the call as
18
+ * NOT ours — requirement first, coverage second — but we COUNT it, because
19
+ * "governance is live" and "governance silently stopped applying because a field
20
+ * went missing in a host upgrade" are two different facts, and the second one has
21
+ * to be readable from `tm_stats` rather than inferred from a bad feeling.
22
+ *
23
+ * A session can also become known from the inside: every call to one of OUR tools
24
+ * carries a session id and an agent in its own context, so `learn()` records that
25
+ * pair and a later `execute.after` for the same session resolves even if the host
26
+ * left `agent` off that event.
27
+ */
28
+ export function createTeamScope(names) {
29
+ const set = new Set([...names].map(String));
30
+ const bySession = new Map();
31
+ const report = { ours: 0, foreign: 0, unknown: 0 };
32
+ const scope = {
33
+ names: set,
34
+ isOurs: (agent) => typeof agent === "string" && set.has(agent),
35
+ learn(agent, sessionID) {
36
+ if (typeof sessionID !== "string" || !sessionID)
37
+ return;
38
+ if (typeof agent === "string" && set.has(agent))
39
+ bySession.set(sessionID, agent);
40
+ },
41
+ decide(event) {
42
+ const agent = event?.agent;
43
+ if (typeof agent === "string" && agent)
44
+ return set.has(agent) ? "ours" : "foreign";
45
+ const sid = event?.sessionID;
46
+ if (typeof sid === "string" && bySession.has(sid))
47
+ return "ours";
48
+ return "unknown";
49
+ },
50
+ count(verdict) {
51
+ report[verdict]++;
52
+ return verdict;
53
+ },
54
+ report,
55
+ };
56
+ return scope;
57
+ }
58
+ //# sourceMappingURL=v2-scope.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"v2-scope.js","sourceRoot":"","sources":["../../src/host/v2-scope.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAwBH,MAAM,UAAU,eAAe,CAAC,KAAuB;IACrD,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAA;IAC3C,MAAM,SAAS,GAAG,IAAI,GAAG,EAAkB,CAAA;IAC3C,MAAM,MAAM,GAAkB,EAAE,IAAI,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,CAAA;IACjE,MAAM,KAAK,GAAc;QACvB,KAAK,EAAE,GAAG;QACV,MAAM,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,OAAO,KAAK,KAAK,QAAQ,IAAI,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC;QAC9D,KAAK,CAAC,KAAK,EAAE,SAAS;YACpB,IAAI,OAAO,SAAS,KAAK,QAAQ,IAAI,CAAC,SAAS;gBAAE,OAAM;YACvD,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC;gBAAE,SAAS,CAAC,GAAG,CAAC,SAAS,EAAE,KAAK,CAAC,CAAA;QAClF,CAAC;QACD,MAAM,CAAC,KAAK;YACV,MAAM,KAAK,GAAG,KAAK,EAAE,KAAK,CAAA;YAC1B,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK;gBAAE,OAAO,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAA;YAClF,MAAM,GAAG,GAAG,KAAK,EAAE,SAAS,CAAA;YAC5B,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,SAAS,CAAC,GAAG,CAAC,GAAG,CAAC;gBAAE,OAAO,MAAM,CAAA;YAChE,OAAO,SAAS,CAAA;QAClB,CAAC;QACD,KAAK,CAAC,OAAO;YACX,MAAM,CAAC,OAAO,CAAC,EAAE,CAAA;YACjB,OAAO,OAAO,CAAA;QAChB,CAAC;QACD,MAAM;KACP,CAAA;IACD,OAAO,KAAK,CAAA;AACd,CAAC"}
@@ -0,0 +1,74 @@
1
+ /**
2
+ * The v2 session read seam, shaped like the v1 client the collect path expects (#8).
3
+ *
4
+ * `tm_join` collects a child in two ways: it looks the child up in its own registry
5
+ * (children THIS plugin process created — and since `babfcb8` also the host's `subagent`
6
+ * children), and otherwise it asks the host. That ask was written against v1's SDK client
7
+ * (`client.session.get({path:{id}})`), and on a live host it never once resolved.
8
+ *
9
+ * Why it failed, read out of 2.0.18 rather than guessed (both quotes are from the embedded
10
+ * JS): the plugin-facing wrapper is a schema decode plus a single-object call —
11
+ *
12
+ * te = (schema, fn) => (input) => decode(schema, input ?? {}) → fn(decoded)
13
+ * get: te(N["session.get"], a.session.get)
14
+ *
15
+ * and the host's own call sites are flat:
16
+ *
17
+ * await e.session.get({ sessionID: n })
18
+ * await e.message.list({ sessionID: n, limit: 200, cursor: r })
19
+ *
20
+ * So our adapter had TWO bugs: the key (`path.id` / `id` instead of `sessionID`) and the
21
+ * arity (every shape was passed as a one-element ARRAY, which no `te`-wrapped method
22
+ * accepts). A decode failure looks exactly like "the host has no such method", which is
23
+ * how this stayed wrong for a whole release cycle.
24
+ *
25
+ * What this surface does NOT give, stated rather than discovered later:
26
+ * · no `children` method, so adoption from the host's session tree is still impossible on
27
+ * v2 — only an explicitly named id can be claimed, which is what the lead's prompt says.
28
+ * · no `message`/`messages` domain on the plugin ctx (the host's internal `message.list`
29
+ * is the SDK client, not this surface). `context` is the only candidate that could
30
+ * carry a child's reply, so it is tried, and its outcome — including the NAMES of the
31
+ * keys it returned — is recorded. Key names are safe; a message body is not written to
32
+ * the trajectory by this file.
33
+ * · the HTTP API does expose `GET /api/session/{id}/message` (136 distinct routes were
34
+ * enumerated from a scanned window of the binary, and none of them is a todo list —
35
+ * which settles the `tm_ledger` question: there is no host todo resource to read), but
36
+ * reaching it needs the basic-auth password from
37
+ * `~/.config/opencode/service.json`. A plugin reading the user's credential file to
38
+ * call back into the host is a new trust boundary, so it is NOT done here; it is
39
+ * recorded as the option with its cost attached. See
40
+ * `docs/research/host-http-api.md`.
41
+ */
42
+ export interface V2SessionReaderReport {
43
+ /** calls made through this adapter */
44
+ attempted: number;
45
+ /** calls that produced an object with a parent id */
46
+ resolved: number;
47
+ /** which argument shape the host answered — `sessionID` is the measured one */
48
+ usedShape?: string;
49
+ /** last error text, unwrapped from the host's own error envelope */
50
+ lastError?: string;
51
+ /** shapes tried and failed on the last call */
52
+ failedShapes: string[];
53
+ /** `ctx.session.context` was reached at all */
54
+ contextTried: number;
55
+ contextOk: number;
56
+ contextError?: string;
57
+ /** the KEY NAMES the context call returned (names only, never values) */
58
+ contextKeys: string[];
59
+ }
60
+ export interface V2SessionReader {
61
+ /** a `{session:{get,messages}}` object safe to hand to code written for the v1 client */
62
+ client: unknown;
63
+ report: V2SessionReaderReport;
64
+ }
65
+ /** Turn the host's flat context items into the `{info,parts}` shape the collect path
66
+ * already understands. Only the ROLE is inferred here, and only from the item's own `type`
67
+ * field (measured values: "user" for the brief, something else for the answer); a
68
+ * `time.completed` is NEVER invented, because that field is the settle verdict and a
69
+ * fabricated one would let tm_join claim a child finished at a time nobody observed.
70
+ * Anything already shaped like v1 passes through untouched, so a host that changes back
71
+ * does not need this code to change. */
72
+ export declare function normaliseContextMessages(list: unknown): unknown;
73
+ export declare function createV2SessionReader(ctx: unknown): V2SessionReader;
74
+ //# sourceMappingURL=v2-session-client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"v2-session-client.d.ts","sourceRoot":"","sources":["../../src/host/v2-session-client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AAEH,MAAM,WAAW,qBAAqB;IACpC,sCAAsC;IACtC,SAAS,EAAE,MAAM,CAAA;IACjB,qDAAqD;IACrD,QAAQ,EAAE,MAAM,CAAA;IAChB,+EAA+E;IAC/E,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,oEAAoE;IACpE,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,+CAA+C;IAC/C,YAAY,EAAE,MAAM,EAAE,CAAA;IACtB,+CAA+C;IAC/C,YAAY,EAAE,MAAM,CAAA;IACpB,SAAS,EAAE,MAAM,CAAA;IACjB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,yEAAyE;IACzE,WAAW,EAAE,MAAM,EAAE,CAAA;CACtB;AAED,MAAM,WAAW,eAAe;IAC9B,yFAAyF;IACzF,MAAM,EAAE,OAAO,CAAA;IACf,MAAM,EAAE,qBAAqB,CAAA;CAC9B;AA+BD;;;;;;yCAMyC;AACzC,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAgB/D;AAED,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,OAAO,GAAG,eAAe,CAwEnE"}
@@ -0,0 +1,171 @@
1
+ /**
2
+ * The v2 session read seam, shaped like the v1 client the collect path expects (#8).
3
+ *
4
+ * `tm_join` collects a child in two ways: it looks the child up in its own registry
5
+ * (children THIS plugin process created — and since `babfcb8` also the host's `subagent`
6
+ * children), and otherwise it asks the host. That ask was written against v1's SDK client
7
+ * (`client.session.get({path:{id}})`), and on a live host it never once resolved.
8
+ *
9
+ * Why it failed, read out of 2.0.18 rather than guessed (both quotes are from the embedded
10
+ * JS): the plugin-facing wrapper is a schema decode plus a single-object call —
11
+ *
12
+ * te = (schema, fn) => (input) => decode(schema, input ?? {}) → fn(decoded)
13
+ * get: te(N["session.get"], a.session.get)
14
+ *
15
+ * and the host's own call sites are flat:
16
+ *
17
+ * await e.session.get({ sessionID: n })
18
+ * await e.message.list({ sessionID: n, limit: 200, cursor: r })
19
+ *
20
+ * So our adapter had TWO bugs: the key (`path.id` / `id` instead of `sessionID`) and the
21
+ * arity (every shape was passed as a one-element ARRAY, which no `te`-wrapped method
22
+ * accepts). A decode failure looks exactly like "the host has no such method", which is
23
+ * how this stayed wrong for a whole release cycle.
24
+ *
25
+ * What this surface does NOT give, stated rather than discovered later:
26
+ * · no `children` method, so adoption from the host's session tree is still impossible on
27
+ * v2 — only an explicitly named id can be claimed, which is what the lead's prompt says.
28
+ * · no `message`/`messages` domain on the plugin ctx (the host's internal `message.list`
29
+ * is the SDK client, not this surface). `context` is the only candidate that could
30
+ * carry a child's reply, so it is tried, and its outcome — including the NAMES of the
31
+ * keys it returned — is recorded. Key names are safe; a message body is not written to
32
+ * the trajectory by this file.
33
+ * · the HTTP API does expose `GET /api/session/{id}/message` (136 distinct routes were
34
+ * enumerated from a scanned window of the binary, and none of them is a todo list —
35
+ * which settles the `tm_ledger` question: there is no host todo resource to read), but
36
+ * reaching it needs the basic-auth password from
37
+ * `~/.config/opencode/service.json`. A plugin reading the user's credential file to
38
+ * call back into the host is a new trust boundary, so it is NOT done here; it is
39
+ * recorded as the option with its cost attached. See
40
+ * `docs/research/host-http-api.md`.
41
+ */
42
+ /** Measured first, then the v1 spellings kept as fallbacks for an older 2.x build. Each
43
+ * builder returns the ARGUMENT OBJECT — `te`-wrapped methods take one object, not an
44
+ * array, and that alone is why every previous shape failed. */
45
+ const SHAPES = [
46
+ ["sessionID", (id) => ({ sessionID: id })],
47
+ ["path", (id) => ({ path: { id } })],
48
+ ["flat", (id) => ({ id })],
49
+ ];
50
+ function idOf(opts) {
51
+ const o = (opts ?? {});
52
+ return String(o.path?.id ?? o.query?.id ?? o.sessionID ?? o.id ?? "");
53
+ }
54
+ function unwrap(value) {
55
+ if (!value || typeof value !== "object")
56
+ return undefined;
57
+ const rec = value;
58
+ // `{data}` and `{result}` are both envelopes on this surface; reading a field off the
59
+ // wrapper yields undefined, which then reads as "not my child" and refuses a child that is.
60
+ for (const cand of [rec, rec.data, rec.result, rec.session]) {
61
+ if (cand && typeof cand === "object" && (("parentID" in cand) || ("parent_id" in cand) || ("id" in cand))) {
62
+ return cand;
63
+ }
64
+ }
65
+ return rec;
66
+ }
67
+ /** Turn the host's flat context items into the `{info,parts}` shape the collect path
68
+ * already understands. Only the ROLE is inferred here, and only from the item's own `type`
69
+ * field (measured values: "user" for the brief, something else for the answer); a
70
+ * `time.completed` is NEVER invented, because that field is the settle verdict and a
71
+ * fabricated one would let tm_join claim a child finished at a time nobody observed.
72
+ * Anything already shaped like v1 passes through untouched, so a host that changes back
73
+ * does not need this code to change. */
74
+ export function normaliseContextMessages(list) {
75
+ if (!Array.isArray(list))
76
+ return list;
77
+ return list.map((item) => {
78
+ if (!item || typeof item !== "object")
79
+ return item;
80
+ const rec = item;
81
+ if (rec.info || Array.isArray(rec.parts) || Array.isArray(rec.content))
82
+ return item;
83
+ const text = rec.text;
84
+ if (typeof text !== "string" || !text.trim())
85
+ return item;
86
+ const type = String(rec.type ?? "");
87
+ const role = /^user$/i.test(type) ? "user" : "assistant";
88
+ const created = rec.time?.created;
89
+ return {
90
+ info: { role, ...(typeof created === "number" ? { time: { created } } : {}) },
91
+ parts: [{ type: "text", text }],
92
+ };
93
+ });
94
+ }
95
+ export function createV2SessionReader(ctx) {
96
+ const report = {
97
+ attempted: 0, resolved: 0, failedShapes: [], contextTried: 0, contextOk: 0, contextKeys: [],
98
+ };
99
+ const domain = ctx?.session;
100
+ const call = async (method, arg) => {
101
+ const fn = domain?.[method];
102
+ if (typeof fn !== "function")
103
+ return undefined;
104
+ // Property access on the domain, never a captured reference: a detached `get` loses
105
+ // its receiver and the host's client throws (the dispatch.ts §10 rule).
106
+ return await fn.call(domain, arg);
107
+ };
108
+ const client = {
109
+ session: {
110
+ get: async (opts) => {
111
+ const id = idOf(opts);
112
+ report.attempted++;
113
+ if (!id || !id.startsWith("ses"))
114
+ return { data: undefined };
115
+ const tried = [];
116
+ for (const [shape, build] of SHAPES) {
117
+ try {
118
+ const raw = await call("get", build(id));
119
+ const rec = unwrap(raw);
120
+ if (rec) {
121
+ report.resolved++;
122
+ report.usedShape = shape;
123
+ report.failedShapes = [];
124
+ const parent = rec.parentID ?? rec.parent_id;
125
+ return { data: { id, parentID: typeof parent === "string" ? parent : undefined, raw: rec } };
126
+ }
127
+ tried.push(shape);
128
+ }
129
+ catch (err) {
130
+ tried.push(shape);
131
+ report.lastError = String(err?.message ?? err).slice(0, 160);
132
+ }
133
+ }
134
+ report.failedShapes = tried;
135
+ return { data: undefined };
136
+ },
137
+ /** The reply path: `ctx.session.context({sessionID})` is the in-process seam that
138
+ * answers for a child's messages — measured on 2.0.18, where it returns an ARRAY of
139
+ * `{id, time:{created}, text, type}`. That is NOT v1's `[{info:{role},parts:[…]}]`,
140
+ * and this is the reason `tm_join` kept saying it could not read a reply that was
141
+ * sitting right there: `lastAssistantMessage` tolerates an unknown shape by returning
142
+ * no text, so the call succeeded and the body was silently dropped. The flat shape is
143
+ * therefore NORMALISED here, at the seam that knows it, so the collect path stays
144
+ * single-shaped — and the result says WHICH seam answered, because a reply that
145
+ * credits "session.messages" (a v1 endpoint this host does not have) is a claim about
146
+ * the host that nobody measured. */
147
+ messages: async (opts) => {
148
+ const id = idOf(opts);
149
+ if (!id || typeof domain?.context !== "function")
150
+ return { data: undefined };
151
+ report.contextTried++;
152
+ try {
153
+ const raw = await call("context", { sessionID: id });
154
+ const rec = unwrap(raw);
155
+ if (!rec)
156
+ return { data: undefined };
157
+ report.contextOk++;
158
+ report.contextKeys = Array.isArray(rec) ? ["array"] : Object.keys(rec).slice(0, 24);
159
+ const list = Array.isArray(rec) ? rec : (rec.messages ?? rec.data ?? rec);
160
+ return { data: normaliseContextMessages(list), via: "ctx.session.context" };
161
+ }
162
+ catch (err) {
163
+ report.contextError = String(err?.message ?? err).slice(0, 160);
164
+ return { data: undefined };
165
+ }
166
+ },
167
+ },
168
+ };
169
+ return { client, report };
170
+ }
171
+ //# sourceMappingURL=v2-session-client.js.map