@te-river/opencode-team-mode 1.6.0 → 1.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +80 -15
- package/README.zh-CN.md +67 -18
- package/dist/host/note.d.ts +7 -0
- package/dist/host/note.d.ts.map +1 -0
- package/dist/host/note.js +31 -0
- package/dist/host/note.js.map +1 -0
- package/dist/host/v1.d.ts +14 -0
- package/dist/host/v1.d.ts.map +1 -0
- package/dist/host/v1.js +340 -0
- package/dist/host/v1.js.map +1 -0
- package/dist/host/v2-browser-gate.d.ts +45 -0
- package/dist/host/v2-browser-gate.d.ts.map +1 -0
- package/dist/host/v2-browser-gate.js +319 -0
- package/dist/host/v2-browser-gate.js.map +1 -0
- package/dist/host/v2-capabilities.d.ts +78 -0
- package/dist/host/v2-capabilities.d.ts.map +1 -0
- package/dist/host/v2-capabilities.js +173 -0
- package/dist/host/v2-capabilities.js.map +1 -0
- package/dist/host/v2-client.d.ts +53 -0
- package/dist/host/v2-client.d.ts.map +1 -0
- package/dist/host/v2-client.js +193 -0
- package/dist/host/v2-client.js.map +1 -0
- package/dist/host/v2-events.d.ts +56 -0
- package/dist/host/v2-events.d.ts.map +1 -0
- package/dist/host/v2-events.js +158 -0
- package/dist/host/v2-events.js.map +1 -0
- package/dist/host/v2-guard.d.ts +101 -0
- package/dist/host/v2-guard.d.ts.map +1 -0
- package/dist/host/v2-guard.js +199 -0
- package/dist/host/v2-guard.js.map +1 -0
- package/dist/host/v2-offload.d.ts +148 -0
- package/dist/host/v2-offload.d.ts.map +1 -0
- package/dist/host/v2-offload.js +288 -0
- package/dist/host/v2-offload.js.map +1 -0
- package/dist/host/v2-permissions.d.ts +123 -0
- package/dist/host/v2-permissions.d.ts.map +1 -0
- package/dist/host/v2-permissions.js +190 -0
- package/dist/host/v2-permissions.js.map +1 -0
- package/dist/host/v2-probe.d.ts +85 -0
- package/dist/host/v2-probe.d.ts.map +1 -0
- package/dist/host/v2-probe.js +268 -0
- package/dist/host/v2-probe.js.map +1 -0
- package/dist/host/v2-scope.d.ts +51 -0
- package/dist/host/v2-scope.d.ts.map +1 -0
- package/dist/host/v2-scope.js +58 -0
- package/dist/host/v2-scope.js.map +1 -0
- package/dist/host/v2-session-client.d.ts +105 -0
- package/dist/host/v2-session-client.d.ts.map +1 -0
- package/dist/host/v2-session-client.js +390 -0
- package/dist/host/v2-session-client.js.map +1 -0
- package/dist/host/v2-session.d.ts +85 -0
- package/dist/host/v2-session.d.ts.map +1 -0
- package/dist/host/v2-session.js +171 -0
- package/dist/host/v2-session.js.map +1 -0
- package/dist/host/v2-subagent.d.ts +157 -0
- package/dist/host/v2-subagent.d.ts.map +1 -0
- package/dist/host/v2-subagent.js +286 -0
- package/dist/host/v2-subagent.js.map +1 -0
- package/dist/host/v2-tool.d.ts +67 -0
- package/dist/host/v2-tool.d.ts.map +1 -0
- package/dist/host/v2-tool.js +209 -0
- package/dist/host/v2-tool.js.map +1 -0
- package/dist/host/v2-transcript.d.ts +73 -0
- package/dist/host/v2-transcript.d.ts.map +1 -0
- package/dist/host/v2-transcript.js +151 -0
- package/dist/host/v2-transcript.js.map +1 -0
- package/dist/host/v2-types.d.ts +229 -0
- package/dist/host/v2-types.d.ts.map +1 -0
- package/dist/host/v2-types.js +17 -0
- package/dist/host/v2-types.js.map +1 -0
- package/dist/host/v2.d.ts +42 -0
- package/dist/host/v2.d.ts.map +1 -0
- package/dist/host/v2.js +853 -0
- package/dist/host/v2.js.map +1 -0
- package/dist/index.d.ts +23 -17
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +29 -366
- package/dist/index.js.map +1 -1
- package/dist/prompts/lead.d.ts +1 -1
- package/dist/prompts/lead.d.ts.map +1 -1
- package/dist/prompts/lead.js +456 -426
- package/dist/prompts/lead.js.map +1 -1
- package/dist/prompts/shared.d.ts +2 -2
- package/dist/prompts/shared.d.ts.map +1 -1
- package/dist/prompts/shared.js +38 -11
- package/dist/prompts/shared.js.map +1 -1
- package/dist/prompts/specialists.d.ts +2 -2
- package/dist/prompts/specialists.d.ts.map +1 -1
- package/dist/prompts/specialists.js +7 -6
- package/dist/prompts/specialists.js.map +1 -1
- package/dist/task-offload.d.ts +16 -0
- package/dist/task-offload.d.ts.map +1 -1
- package/dist/task-offload.js +51 -0
- package/dist/task-offload.js.map +1 -1
- package/dist/tm/browser.d.ts.map +1 -1
- package/dist/tm/browser.js +41 -10
- package/dist/tm/browser.js.map +1 -1
- package/dist/tm/dispatch.d.ts +86 -1
- package/dist/tm/dispatch.d.ts.map +1 -1
- package/dist/tm/dispatch.js +360 -39
- package/dist/tm/dispatch.js.map +1 -1
- package/dist/tm/dupe-guard.d.ts +16 -3
- package/dist/tm/dupe-guard.d.ts.map +1 -1
- package/dist/tm/dupe-guard.js +27 -7
- package/dist/tm/dupe-guard.js.map +1 -1
- package/dist/tm/index.d.ts +40 -1
- package/dist/tm/index.d.ts.map +1 -1
- package/dist/tm/index.js +40 -11
- package/dist/tm/index.js.map +1 -1
- package/dist/tm/ledger.d.ts +114 -0
- package/dist/tm/ledger.d.ts.map +1 -0
- package/dist/tm/ledger.js +308 -0
- package/dist/tm/ledger.js.map +1 -0
- package/dist/tm/perm-ask.d.ts +1 -3
- package/dist/tm/perm-ask.d.ts.map +1 -1
- package/dist/tm/perm-ask.js +10 -1
- package/dist/tm/perm-ask.js.map +1 -1
- package/dist/tm/preview.d.ts +55 -0
- package/dist/tm/preview.d.ts.map +1 -1
- package/dist/tm/preview.js +225 -0
- package/dist/tm/preview.js.map +1 -1
- package/dist/tm/search.d.ts.map +1 -1
- package/dist/tm/search.js +40 -10
- package/dist/tm/search.js.map +1 -1
- package/dist/tm/serp-loop.d.ts +45 -0
- package/dist/tm/serp-loop.d.ts.map +1 -0
- package/dist/tm/serp-loop.js +80 -0
- package/dist/tm/serp-loop.js.map +1 -0
- package/dist/tm/stats.d.ts +29 -0
- package/dist/tm/stats.d.ts.map +1 -1
- package/dist/tm/stats.js +278 -4
- package/dist/tm/stats.js.map +1 -1
- package/dist/tm/webfetch.d.ts +4 -0
- package/dist/tm/webfetch.d.ts.map +1 -1
- package/dist/tm/webfetch.js +75 -8
- package/dist/tm/webfetch.js.map +1 -1
- package/docs/installation-v2.md +445 -0
- package/docs/installation.md +12 -0
- package/docs/research/agent-data-exchange.md +117 -0
- package/docs/research/browser-pane.md +89 -0
- package/docs/research/host-http-api.md +132 -0
- package/docs/research/host-subagent-injection.md +187 -0
- package/docs/research/plugin-loader-contract.md +261 -0
- package/docs/research/v2-builtin-tools.md +447 -0
- package/docs/v2-port-notes.md +413 -0
- package/index.js +7 -0
- package/package.json +64 -61
- package/scripts/gen-v2-config.mjs +304 -0
- package/scripts/lib/config-surgery.cjs +239 -0
- package/scripts/lib/fake-ctx.mjs +169 -0
|
@@ -0,0 +1,53 @@
|
|
|
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
|
+
type ClientResult = {
|
|
19
|
+
data: unknown;
|
|
20
|
+
} | {
|
|
21
|
+
data: {
|
|
22
|
+
error: {
|
|
23
|
+
name: string;
|
|
24
|
+
data: {
|
|
25
|
+
message: string;
|
|
26
|
+
};
|
|
27
|
+
};
|
|
28
|
+
};
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* `client.file.read({ query: { path, directory } })` →
|
|
32
|
+
* `{ data: { type: "text", content } }` (the 1.18.x real-host shape).
|
|
33
|
+
*/
|
|
34
|
+
declare function read(req: unknown): Promise<ClientResult>;
|
|
35
|
+
/**
|
|
36
|
+
* `client.find.text({ query: { pattern, directory } })` →
|
|
37
|
+
* `{ data: FindMatch[] }`. `pattern` arrives as the raw model string, exactly
|
|
38
|
+
* as in v1; an invalid regex is the CALLER's problem to report, so it throws
|
|
39
|
+
* and pipelines.ts turns it into a structured error like every other host
|
|
40
|
+
* client failure.
|
|
41
|
+
*/
|
|
42
|
+
declare function findText(req: unknown): Promise<ClientResult>;
|
|
43
|
+
/** The two methods `createTmTools` actually reaches for on this object. */
|
|
44
|
+
export declare function createV2Client(): {
|
|
45
|
+
file: {
|
|
46
|
+
read: typeof read;
|
|
47
|
+
};
|
|
48
|
+
find: {
|
|
49
|
+
text: typeof findText;
|
|
50
|
+
};
|
|
51
|
+
};
|
|
52
|
+
export {};
|
|
53
|
+
//# sourceMappingURL=v2-client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"v2-client.d.ts","sourceRoot":"","sources":["../../src/host/v2-client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAgCH,KAAK,YAAY,GAAG;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE;QAAE,KAAK,EAAE;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,IAAI,EAAE;gBAAE,OAAO,EAAE,MAAM,CAAA;aAAE,CAAA;SAAE,CAAA;KAAE,CAAA;CAAE,CAAA;AAkBxG;;;GAGG;AACH,iBAAe,IAAI,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAAC,YAAY,CAAC,CAkBvD;AA6BD;;;;;;GAMG;AACH,iBAAe,QAAQ,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAAC,YAAY,CAAC,CAqE3D;AAED,2EAA2E;AAC3E,wBAAgB,cAAc,IAAI;IAAE,IAAI,EAAE;QAAE,IAAI,EAAE,OAAO,IAAI,CAAA;KAAE,CAAC;IAAC,IAAI,EAAE;QAAE,IAAI,EAAE,OAAO,QAAQ,CAAA;KAAE,CAAA;CAAE,CAEjG"}
|
|
@@ -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"}
|