@beeos-ai/device-agent 0.4.2 → 0.5.0
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/.pi/extensions/android-tools.ts +146 -15
- package/LICENSE +201 -0
- package/dist/adapters/agent-gateway-client.d.ts +37 -0
- package/dist/adapters/agent-gateway-client.js +45 -0
- package/dist/adapters/agent-gateway-client.js.map +1 -1
- package/dist/adapters/agentcore-pi.d.ts +22 -0
- package/dist/adapters/agentcore-pi.js +48 -0
- package/dist/adapters/agentcore-pi.js.map +1 -1
- package/dist/adapters/device-android-adb.d.ts +47 -0
- package/dist/adapters/device-android-adb.js +50 -0
- package/dist/adapters/device-android-adb.js.map +1 -1
- package/dist/adapters/device-desktop.d.ts +12 -0
- package/dist/adapters/device-desktop.js +31 -0
- package/dist/adapters/device-desktop.js.map +1 -1
- package/dist/agent-runtime/adapters/base.d.ts +20 -0
- package/dist/agent-runtime/adapters/base.js.map +1 -1
- package/dist/agent-runtime/agent-runner.d.ts +4 -2
- package/dist/agent-runtime/agent-runner.js.map +1 -1
- package/dist/agent-runtime/core.d.ts +60 -0
- package/dist/agent-runtime/core.js +190 -9
- package/dist/agent-runtime/core.js.map +1 -1
- package/dist/agent-runtime/feedback.d.ts +56 -0
- package/dist/agent-runtime/feedback.js +54 -0
- package/dist/agent-runtime/feedback.js.map +1 -0
- package/dist/agent-runtime/index.d.ts +1 -0
- package/dist/agent-runtime/index.js +1 -0
- package/dist/agent-runtime/index.js.map +1 -1
- package/dist/agent-runtime/plan-executor.js +24 -0
- package/dist/agent-runtime/plan-executor.js.map +1 -1
- package/dist/agent-runtime/planner/base.d.ts +10 -0
- package/dist/agent-runtime/planner/base.js.map +1 -1
- package/dist/agent-runtime/planner/llm-planner.js +1 -0
- package/dist/agent-runtime/planner/llm-planner.js.map +1 -1
- package/dist/agent-runtime/planner/prompts.d.ts +3 -0
- package/dist/agent-runtime/planner/prompts.js +12 -0
- package/dist/agent-runtime/planner/prompts.js.map +1 -1
- package/dist/agent-runtime/reflection.d.ts +25 -6
- package/dist/agent-runtime/reflection.js +64 -22
- package/dist/agent-runtime/reflection.js.map +1 -1
- package/dist/agent-runtime/reflectors/policy.d.ts +10 -4
- package/dist/agent-runtime/reflectors/policy.js +19 -6
- package/dist/agent-runtime/reflectors/policy.js.map +1 -1
- package/dist/agent-runtime/shortcuts/fs-store.d.ts +41 -0
- package/dist/agent-runtime/shortcuts/fs-store.js +122 -0
- package/dist/agent-runtime/shortcuts/fs-store.js.map +1 -0
- package/dist/agent-runtime/shortcuts/in-memory-store.d.ts +20 -0
- package/dist/agent-runtime/shortcuts/in-memory-store.js +64 -0
- package/dist/agent-runtime/shortcuts/in-memory-store.js.map +1 -0
- package/dist/agent-runtime/shortcuts/index.d.ts +10 -0
- package/dist/agent-runtime/shortcuts/index.js +10 -0
- package/dist/agent-runtime/shortcuts/index.js.map +1 -0
- package/dist/agent-runtime/shortcuts/types.d.ts +69 -0
- package/dist/agent-runtime/shortcuts/types.js +17 -0
- package/dist/agent-runtime/shortcuts/types.js.map +1 -0
- package/dist/app/agent-host.d.ts +83 -0
- package/dist/app/agent-host.js +345 -32
- package/dist/app/agent-host.js.map +1 -1
- package/dist/app/session-manager.d.ts +33 -4
- package/dist/app/session-manager.js +27 -5
- package/dist/app/session-manager.js.map +1 -1
- package/dist/bootstrap.js +187 -3
- package/dist/bootstrap.js.map +1 -1
- package/dist/config/loader.js +22 -0
- package/dist/config/loader.js.map +1 -1
- package/dist/config/schema.d.ts +18 -0
- package/dist/config/schema.js +118 -0
- package/dist/config/schema.js.map +1 -1
- package/dist/inbound/acp/handlers/session.js.map +1 -1
- package/dist/mcp-client/client.d.ts +6 -0
- package/dist/mcp-client/client.js +11 -0
- package/dist/mcp-client/client.js.map +1 -1
- package/dist/mcp-client/runtime.d.ts +13 -0
- package/dist/orchestrator-system-prompt.d.ts +26 -0
- package/dist/orchestrator-system-prompt.js +118 -2
- package/dist/orchestrator-system-prompt.js.map +1 -1
- package/dist/pi-context.d.ts +50 -0
- package/dist/pi-context.js.map +1 -1
- package/dist/ports/device.d.ts +18 -0
- package/dist/resources/augmenter.d.ts +48 -0
- package/dist/resources/augmenter.js +126 -0
- package/dist/resources/augmenter.js.map +1 -0
- package/dist/resources/cache.d.ts +57 -0
- package/dist/resources/cache.js +107 -0
- package/dist/resources/cache.js.map +1 -0
- package/dist/resources/index.d.ts +17 -0
- package/dist/resources/index.js +15 -0
- package/dist/resources/index.js.map +1 -0
- package/dist/resources/lifecycle/disk-pressure.d.ts +75 -0
- package/dist/resources/lifecycle/disk-pressure.js +148 -0
- package/dist/resources/lifecycle/disk-pressure.js.map +1 -0
- package/dist/resources/lifecycle/index.d.ts +7 -0
- package/dist/resources/lifecycle/index.js +7 -0
- package/dist/resources/lifecycle/index.js.map +1 -0
- package/dist/resources/lifecycle/manager.d.ts +38 -0
- package/dist/resources/lifecycle/manager.js +205 -0
- package/dist/resources/lifecycle/manager.js.map +1 -0
- package/dist/resources/lifecycle/sweeper.d.ts +36 -0
- package/dist/resources/lifecycle/sweeper.js +179 -0
- package/dist/resources/lifecycle/sweeper.js.map +1 -0
- package/dist/resources/lifecycle/types.d.ts +180 -0
- package/dist/resources/lifecycle/types.js +46 -0
- package/dist/resources/lifecycle/types.js.map +1 -0
- package/dist/resources/parsers.d.ts +28 -0
- package/dist/resources/parsers.js +88 -0
- package/dist/resources/parsers.js.map +1 -0
- package/dist/resources/registry.d.ts +47 -0
- package/dist/resources/registry.js +107 -0
- package/dist/resources/registry.js.map +1 -0
- package/dist/resources/resolvers/beeos-file-resolver.d.ts +62 -0
- package/dist/resources/resolvers/beeos-file-resolver.js +109 -0
- package/dist/resources/resolvers/beeos-file-resolver.js.map +1 -0
- package/dist/resources/resolvers/errors.d.ts +11 -0
- package/dist/resources/resolvers/errors.js +23 -0
- package/dist/resources/resolvers/errors.js.map +1 -0
- package/dist/resources/resolvers/mime-table.d.ts +41 -0
- package/dist/resources/resolvers/mime-table.js +92 -0
- package/dist/resources/resolvers/mime-table.js.map +1 -0
- package/dist/resources/resolvers/url-resolver.d.ts +85 -0
- package/dist/resources/resolvers/url-resolver.js +495 -0
- package/dist/resources/resolvers/url-resolver.js.map +1 -0
- package/dist/resources/shortcut-facade.d.ts +51 -0
- package/dist/resources/shortcut-facade.js +72 -0
- package/dist/resources/shortcut-facade.js.map +1 -0
- package/dist/resources/telemetry.d.ts +52 -0
- package/dist/resources/telemetry.js +59 -0
- package/dist/resources/telemetry.js.map +1 -0
- package/dist/resources/types.d.ts +249 -0
- package/dist/resources/types.js +29 -0
- package/dist/resources/types.js.map +1 -0
- package/dist/shortcuts/builtins/download-to-device.d.ts +83 -0
- package/dist/shortcuts/builtins/download-to-device.js +125 -0
- package/dist/shortcuts/builtins/download-to-device.js.map +1 -0
- package/dist/shortcuts/builtins/find-app-package.d.ts +79 -0
- package/dist/shortcuts/builtins/find-app-package.js +168 -0
- package/dist/shortcuts/builtins/find-app-package.js.map +1 -0
- package/dist/shortcuts/builtins/index.d.ts +32 -0
- package/dist/shortcuts/builtins/index.js +37 -0
- package/dist/shortcuts/builtins/index.js.map +1 -0
- package/dist/shortcuts/builtins/open-deeplink.d.ts +68 -0
- package/dist/shortcuts/builtins/open-deeplink.js +142 -0
- package/dist/shortcuts/builtins/open-deeplink.js.map +1 -0
- package/dist/shortcuts/builtins/set-clipboard.d.ts +65 -0
- package/dist/shortcuts/builtins/set-clipboard.js +112 -0
- package/dist/shortcuts/builtins/set-clipboard.js.map +1 -0
- package/dist/shortcuts/index.d.ts +11 -0
- package/dist/shortcuts/index.js +11 -0
- package/dist/shortcuts/index.js.map +1 -0
- package/dist/shortcuts/registry.d.ts +56 -0
- package/dist/shortcuts/registry.js +70 -0
- package/dist/shortcuts/registry.js.map +1 -0
- package/dist/shortcuts/router.d.ts +91 -0
- package/dist/shortcuts/router.js +158 -0
- package/dist/shortcuts/router.js.map +1 -0
- package/dist/shortcuts/types.d.ts +219 -0
- package/dist/shortcuts/types.js +46 -0
- package/dist/shortcuts/types.js.map +1 -0
- package/package.json +24 -25
- package/dist/agent-runtime/skills/sqlite-store.d.ts +0 -60
- package/dist/agent-runtime/skills/sqlite-store.js +0 -234
- package/dist/agent-runtime/skills/sqlite-store.js.map +0 -1
- package/dist/test-lan-autoglm.d.ts +0 -7
- package/dist/test-lan-autoglm.js +0 -46
- package/dist/test-lan-autoglm.js.map +0 -1
- package/dist/test-mem-breakdown.d.ts +0 -1
- package/dist/test-mem-breakdown.js +0 -103
- package/dist/test-mem-breakdown.js.map +0 -1
- package/dist/test-mem.d.ts +0 -1
- package/dist/test-mem.js +0 -60
- package/dist/test-mem.js.map +0 -1
- package/dist/test-mobile-use.d.ts +0 -1
- package/dist/test-mobile-use.js +0 -18
- package/dist/test-mobile-use.js.map +0 -1
- package/dist/test-screenshot-loop.d.ts +0 -1
- package/dist/test-screenshot-loop.js +0 -43
- package/dist/test-screenshot-loop.js.map +0 -1
- package/dist/test-screenshot.d.ts +0 -1
- package/dist/test-screenshot.js +0 -10
- package/dist/test-screenshot.js.map +0 -1
- package/dist/test-ui-xml-dump.d.ts +0 -15
- package/dist/test-ui-xml-dump.js +0 -49
- package/dist/test-ui-xml-dump.js.map +0 -1
|
@@ -0,0 +1,495 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* URL Resolver — fetch HTTP(S) URLs and push them onto the device.
|
|
3
|
+
*
|
|
4
|
+
* Phase 1 / Pillar 1 of the GUI-Shortcut Hybrid Plan.
|
|
5
|
+
*
|
|
6
|
+
* Pipeline:
|
|
7
|
+
*
|
|
8
|
+
* 1. validate scheme — `http` / `https` only (allowedSchemes).
|
|
9
|
+
* 2. SSRF check — DNS resolve the hostname; reject any private
|
|
10
|
+
* / loopback / link-local / multicast IP.
|
|
11
|
+
* 3. fetch (streaming) — chunk-by-chunk with running size cap and
|
|
12
|
+
* AbortSignal merge. Validates Content-Type
|
|
13
|
+
* against the platform-supported MIME table.
|
|
14
|
+
* 4. DNS-rebind check — after the body has been read, re-resolve the
|
|
15
|
+
* hostname and assert at least one returned
|
|
16
|
+
* IP overlaps with the IP we accepted in (2).
|
|
17
|
+
* 5. choose ext — strictly via Content-Type → ext mapping.
|
|
18
|
+
* Path-derived extensions are NEVER trusted.
|
|
19
|
+
* 6. choose target — `ctx.device.resolveWellKnownDir(mime)` picks
|
|
20
|
+
* the platform-appropriate directory.
|
|
21
|
+
* 7. push — `ctx.mcp.fileWrite(remotePath, bytes)`.
|
|
22
|
+
* 8. media-scan — Android only: `touch -d now <path> &&
|
|
23
|
+
* am broadcast -a MEDIA_SCANNER_SCAN_FILE`
|
|
24
|
+
* so Gallery / WeChat / Photos see it as
|
|
25
|
+
* fresh and orderable. Best-effort; failure
|
|
26
|
+
* does NOT fail the resolve.
|
|
27
|
+
*
|
|
28
|
+
* All four SSRF defenses operate on the same `ResolverContext` so the
|
|
29
|
+
* AgentHost can flip them off in tests / lab environments via the
|
|
30
|
+
* feature flag.
|
|
31
|
+
*/
|
|
32
|
+
import { Buffer } from "node:buffer";
|
|
33
|
+
import { promises as dns } from "node:dns";
|
|
34
|
+
import { createHash } from "node:crypto";
|
|
35
|
+
import { URL } from "node:url";
|
|
36
|
+
import { AGENT_HOST_ERROR_LABELS } from "./errors.js";
|
|
37
|
+
import { mapMimeToExt, ALLOWED_MIME_PREFIXES, ALLOWED_MIMES } from "./mime-table.js";
|
|
38
|
+
export class ResolverError extends Error {
|
|
39
|
+
code;
|
|
40
|
+
meta;
|
|
41
|
+
constructor(code, message, meta) {
|
|
42
|
+
super(message);
|
|
43
|
+
this.code = code;
|
|
44
|
+
this.meta = meta;
|
|
45
|
+
this.name = "ResolverError";
|
|
46
|
+
}
|
|
47
|
+
/** Promtheus-friendly outcome label (subset of error codes). */
|
|
48
|
+
get outcomeLabel() {
|
|
49
|
+
return AGENT_HOST_ERROR_LABELS[this.code];
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
const DEFAULT_FETCH_IMPL = (...args) => fetch(...args);
|
|
53
|
+
const DEFAULT_DNS_LOOKUP = async (hostname) => {
|
|
54
|
+
// Use `lookup` with `all: true` instead of `resolve4`+`resolve6` so we
|
|
55
|
+
// honor /etc/hosts and the OS resolver — same surface area Node's
|
|
56
|
+
// built-in fetch sees on a subsequent connect, which is essential for
|
|
57
|
+
// the DNS-rebind defence.
|
|
58
|
+
return dns.lookup(hostname, { all: true });
|
|
59
|
+
};
|
|
60
|
+
export class UrlResolver {
|
|
61
|
+
name = "url";
|
|
62
|
+
fetchImpl;
|
|
63
|
+
dnsLookup;
|
|
64
|
+
forceDeviceType;
|
|
65
|
+
constructor(opts = {}) {
|
|
66
|
+
this.fetchImpl = opts.fetchImpl ?? DEFAULT_FETCH_IMPL;
|
|
67
|
+
this.dnsLookup = opts.dnsLookup ?? DEFAULT_DNS_LOOKUP;
|
|
68
|
+
this.forceDeviceType = opts.forceDeviceType;
|
|
69
|
+
}
|
|
70
|
+
match(ref) {
|
|
71
|
+
return ref.kind === "url";
|
|
72
|
+
}
|
|
73
|
+
async resolve(ref, ctx) {
|
|
74
|
+
if (ref.kind !== "url") {
|
|
75
|
+
throw new ResolverError("fetch_error", `UrlResolver: unsupported ref kind ${ref.kind}`);
|
|
76
|
+
}
|
|
77
|
+
if (ctx.abortSignal.aborted) {
|
|
78
|
+
throw new ResolverError("aborted", "resolve aborted before start");
|
|
79
|
+
}
|
|
80
|
+
emit(ctx, { phase: "start", ref });
|
|
81
|
+
let asset;
|
|
82
|
+
try {
|
|
83
|
+
asset = await this.doResolve(ref, ctx);
|
|
84
|
+
emit(ctx, { phase: "end", ref, asset });
|
|
85
|
+
return asset;
|
|
86
|
+
}
|
|
87
|
+
catch (e) {
|
|
88
|
+
const err = e instanceof Error ? e : new Error(String(e));
|
|
89
|
+
emit(ctx, { phase: "end", ref, error: err });
|
|
90
|
+
throw err;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
/* ---------------------------------------------------------------- */
|
|
94
|
+
/* Internal pipeline */
|
|
95
|
+
/* ---------------------------------------------------------------- */
|
|
96
|
+
async doResolve(ref, ctx) {
|
|
97
|
+
/* 1. scheme check */
|
|
98
|
+
let parsed;
|
|
99
|
+
try {
|
|
100
|
+
parsed = new URL(ref.url);
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
throw new ResolverError("disallowed_scheme", `invalid URL: ${ref.url}`);
|
|
104
|
+
}
|
|
105
|
+
const scheme = parsed.protocol.replace(/:$/, "").toLowerCase();
|
|
106
|
+
if (!ctx.allowedSchemes.includes(scheme)) {
|
|
107
|
+
throw new ResolverError("disallowed_scheme", `scheme "${scheme}" not in allow-list`, { scheme });
|
|
108
|
+
}
|
|
109
|
+
/* 2. SSRF — pre-fetch IP allow-list */
|
|
110
|
+
if (ctx.denyPrivateIp) {
|
|
111
|
+
const initial = await this.dnsLookup(parsed.hostname);
|
|
112
|
+
if (initial.length === 0) {
|
|
113
|
+
throw new ResolverError("fetch_error", `DNS resolved no addresses for ${parsed.hostname}`);
|
|
114
|
+
}
|
|
115
|
+
for (const a of initial) {
|
|
116
|
+
if (isPrivateOrLoopback(a.address, a.family)) {
|
|
117
|
+
throw new ResolverError("private_ip_blocked", `private/loopback IP ${a.address} for ${parsed.hostname}`);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
/* 3. fetch (streaming, with running cap and timeout) */
|
|
122
|
+
const fetchController = new AbortController();
|
|
123
|
+
const onAbort = () => fetchController.abort();
|
|
124
|
+
if (ctx.abortSignal.aborted) {
|
|
125
|
+
throw new ResolverError("aborted", "aborted before fetch");
|
|
126
|
+
}
|
|
127
|
+
ctx.abortSignal.addEventListener("abort", onAbort, { once: true });
|
|
128
|
+
const timer = setTimeout(() => fetchController.abort(), ctx.timeoutMs);
|
|
129
|
+
let response;
|
|
130
|
+
try {
|
|
131
|
+
response = await this.fetchImpl(parsed.toString(), {
|
|
132
|
+
signal: fetchController.signal,
|
|
133
|
+
redirect: "follow",
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
catch (e) {
|
|
137
|
+
ctx.abortSignal.removeEventListener("abort", onAbort);
|
|
138
|
+
clearTimeout(timer);
|
|
139
|
+
if (ctx.abortSignal.aborted) {
|
|
140
|
+
throw new ResolverError("aborted", "aborted during fetch", { cause: errString(e) });
|
|
141
|
+
}
|
|
142
|
+
throw new ResolverError("fetch_error", `fetch failed: ${errString(e)}`);
|
|
143
|
+
}
|
|
144
|
+
if (!response.ok) {
|
|
145
|
+
ctx.abortSignal.removeEventListener("abort", onAbort);
|
|
146
|
+
clearTimeout(timer);
|
|
147
|
+
throw new ResolverError("fetch_error", `fetch returned ${response.status} ${response.statusText}`, { status: response.status });
|
|
148
|
+
}
|
|
149
|
+
/* validate Content-Type early so we don't burn bandwidth on disallowed types */
|
|
150
|
+
const rawCt = response.headers.get("content-type") ?? "";
|
|
151
|
+
const mime = normaliseMime(rawCt);
|
|
152
|
+
if (!isAllowedMime(mime)) {
|
|
153
|
+
ctx.abortSignal.removeEventListener("abort", onAbort);
|
|
154
|
+
clearTimeout(timer);
|
|
155
|
+
throw new ResolverError("disallowed_mime", `Content-Type "${mime}" is not in the allow-list`, { mime });
|
|
156
|
+
}
|
|
157
|
+
/* declared Content-Length sanity check (still need streaming check) */
|
|
158
|
+
const declaredLen = parseInt(response.headers.get("content-length") ?? "", 10);
|
|
159
|
+
if (Number.isFinite(declaredLen) && declaredLen > ctx.maxBytes) {
|
|
160
|
+
ctx.abortSignal.removeEventListener("abort", onAbort);
|
|
161
|
+
clearTimeout(timer);
|
|
162
|
+
throw new ResolverError("oversized", `declared Content-Length ${declaredLen} exceeds maxBytes ${ctx.maxBytes}`, { declaredLen, maxBytes: ctx.maxBytes });
|
|
163
|
+
}
|
|
164
|
+
/* stream body, abort on cap overrun */
|
|
165
|
+
let bytes;
|
|
166
|
+
try {
|
|
167
|
+
bytes = await readBodyWithCap(response, ctx.maxBytes, Number.isFinite(declaredLen) ? declaredLen : undefined, (downloaded, total) => {
|
|
168
|
+
emit(ctx, {
|
|
169
|
+
phase: "update",
|
|
170
|
+
ref,
|
|
171
|
+
downloadedBytes: downloaded,
|
|
172
|
+
...(total !== undefined ? { totalBytes: total } : {}),
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
catch (e) {
|
|
177
|
+
if (ctx.abortSignal.aborted) {
|
|
178
|
+
throw new ResolverError("aborted", "aborted during body read", { cause: errString(e) });
|
|
179
|
+
}
|
|
180
|
+
if (e instanceof ResolverError)
|
|
181
|
+
throw e;
|
|
182
|
+
throw new ResolverError("fetch_error", `body read failed: ${errString(e)}`);
|
|
183
|
+
}
|
|
184
|
+
finally {
|
|
185
|
+
ctx.abortSignal.removeEventListener("abort", onAbort);
|
|
186
|
+
clearTimeout(timer);
|
|
187
|
+
}
|
|
188
|
+
/* 4. DNS-rebind defence — re-resolve after the connection completed
|
|
189
|
+
* and require at least one IP overlap with the initial allow-list.
|
|
190
|
+
* Skipped when private-IP blocking is off (test/lab environments).
|
|
191
|
+
*/
|
|
192
|
+
if (ctx.denyPrivateIp) {
|
|
193
|
+
const initial = await this.dnsLookup(parsed.hostname).catch(() => []);
|
|
194
|
+
const post = await this.dnsLookup(parsed.hostname).catch(() => []);
|
|
195
|
+
const initialIps = new Set(initial.map((a) => a.address));
|
|
196
|
+
const overlap = post.some((a) => initialIps.has(a.address));
|
|
197
|
+
if (!overlap && post.length > 0) {
|
|
198
|
+
throw new ResolverError("dns_rebind_blocked", `DNS for ${parsed.hostname} changed mid-fetch (rebind defence)`, { initial: [...initialIps], post: post.map((a) => a.address) });
|
|
199
|
+
}
|
|
200
|
+
// Also: if any post-fetch IP is private/loopback we treat that as
|
|
201
|
+
// a successful rebind and reject.
|
|
202
|
+
for (const a of post) {
|
|
203
|
+
if (isPrivateOrLoopback(a.address, a.family)) {
|
|
204
|
+
throw new ResolverError("dns_rebind_blocked", `post-fetch DNS now resolves to private IP ${a.address}`, { address: a.address });
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
/* 5. choose ext via MIME → ext mapping (NEVER from URL path) */
|
|
209
|
+
const ext = mapMimeToExt(mime);
|
|
210
|
+
if (!ext) {
|
|
211
|
+
throw new ResolverError("disallowed_mime", `MIME "${mime}" has no whitelisted extension mapping`, { mime });
|
|
212
|
+
}
|
|
213
|
+
/* 6. resolve well-known dir */
|
|
214
|
+
let baseDir;
|
|
215
|
+
try {
|
|
216
|
+
baseDir = await ctx.device.resolveWellKnownDir(mime);
|
|
217
|
+
}
|
|
218
|
+
catch (e) {
|
|
219
|
+
throw new ResolverError("push_error", `device cannot resolve well-known dir for ${mime}: ${errString(e)}`, { mime });
|
|
220
|
+
}
|
|
221
|
+
const sha = createHash("sha256").update(bytes).digest("hex");
|
|
222
|
+
const sha8 = sha.slice(0, 8);
|
|
223
|
+
const filename = `beeos_${Math.floor(Date.now() / 1000)}_${sha8}.${ext}`;
|
|
224
|
+
const remotePath = joinRemote(baseDir, filename);
|
|
225
|
+
/* 7a. ensure the sandbox directory exists. The production adapter
|
|
226
|
+
* resolves to a sub-folder like `/sdcard/Pictures/beeos/` (so the
|
|
227
|
+
* lifecycle sweepers have a safe-to-wipe boundary); on a freshly
|
|
228
|
+
* provisioned device that subdir does NOT exist yet and `fileWrite`
|
|
229
|
+
* would fail with `ENOENT`. We do this best-effort via the same
|
|
230
|
+
* MCP `executeCommand` channel the media-scan branch uses; on the
|
|
231
|
+
* legacy in-proc bridge the call is a no-op and `fileWrite` either
|
|
232
|
+
* succeeds (the subdir was already there) or fails the next step
|
|
233
|
+
* with a structured error. */
|
|
234
|
+
if (typeof ctx.mcp.executeCommand === "function") {
|
|
235
|
+
try {
|
|
236
|
+
await ctx.mcp.executeCommand(`mkdir -p ${shellQuote(baseDir)}`);
|
|
237
|
+
}
|
|
238
|
+
catch (e) {
|
|
239
|
+
ctx.log.warn({ baseDir, err: errString(e) }, "url-resolver: mkdir -p sandbox dir failed (continuing)");
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
/* 7b. disk-pressure guard (Phase 1.5 / lifecycle L3). Optional —
|
|
243
|
+
* when no guard is wired the resolver behaves exactly as before. */
|
|
244
|
+
if (ctx.diskPressure) {
|
|
245
|
+
try {
|
|
246
|
+
await ctx.diskPressure.assertCapacity({
|
|
247
|
+
mcp: ctx.mcp,
|
|
248
|
+
sandboxDir: baseDir,
|
|
249
|
+
incomingBytes: bytes.length,
|
|
250
|
+
log: ctx.log,
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
catch (e) {
|
|
254
|
+
if (e instanceof ResolverError)
|
|
255
|
+
throw e;
|
|
256
|
+
throw new ResolverError("push_error", `disk-pressure guard failed: ${errString(e)}`, { baseDir });
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
/* 7c. push via MCP file_write */
|
|
260
|
+
if (typeof ctx.mcp.fileWrite !== "function") {
|
|
261
|
+
throw new ResolverError("push_error", "MCP runtime does not implement fileWrite (legacy in-proc bridge?)");
|
|
262
|
+
}
|
|
263
|
+
try {
|
|
264
|
+
await ctx.mcp.fileWrite(remotePath, bytes);
|
|
265
|
+
}
|
|
266
|
+
catch (e) {
|
|
267
|
+
throw new ResolverError("push_error", `mcp.fileWrite failed: ${errString(e)}`, { remotePath });
|
|
268
|
+
}
|
|
269
|
+
/* 8. Android-only media-scan + touch-now */
|
|
270
|
+
let scanned = false;
|
|
271
|
+
const platform = await this.detectPlatform(ctx);
|
|
272
|
+
if (platform === "android" && typeof ctx.mcp.executeCommand === "function") {
|
|
273
|
+
try {
|
|
274
|
+
// touch -d now updates mtime so Gallery / Photos / WeChat sort
|
|
275
|
+
// it as the most recent file (otherwise the file's mtime is
|
|
276
|
+
// whatever ADB wrote — potentially seconds-old, but on some
|
|
277
|
+
// ROMs the mtime is preserved from the upload's source).
|
|
278
|
+
await ctx.mcp.executeCommand(`touch -d now ${shellQuote(remotePath)}`);
|
|
279
|
+
await ctx.mcp.executeCommand(`am broadcast -a android.intent.action.MEDIA_SCANNER_SCAN_FILE -d file://${remotePath}`);
|
|
280
|
+
scanned = true;
|
|
281
|
+
}
|
|
282
|
+
catch (e) {
|
|
283
|
+
// Best-effort. Don't fail the resolve — agent can still
|
|
284
|
+
// reference the file by absolute path.
|
|
285
|
+
ctx.log.warn({ remotePath, err: errString(e) }, "url-resolver: media-scan trigger failed (non-fatal)");
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
return {
|
|
289
|
+
ref,
|
|
290
|
+
remotePath,
|
|
291
|
+
mime,
|
|
292
|
+
sizeBytes: bytes.length,
|
|
293
|
+
sha256: sha,
|
|
294
|
+
scannedToMediaStore: scanned,
|
|
295
|
+
sourceUrl: ref.url,
|
|
296
|
+
resolvedAt: Date.now(),
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
async detectPlatform(ctx) {
|
|
300
|
+
if (this.forceDeviceType)
|
|
301
|
+
return this.forceDeviceType.toLowerCase();
|
|
302
|
+
try {
|
|
303
|
+
const info = await ctx.device.getInfo();
|
|
304
|
+
return (info.os ?? info.type ?? "").toString().toLowerCase();
|
|
305
|
+
}
|
|
306
|
+
catch {
|
|
307
|
+
return "";
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
/* ------------------------------------------------------------------ */
|
|
312
|
+
/* Helpers */
|
|
313
|
+
/* ------------------------------------------------------------------ */
|
|
314
|
+
function emit(ctx, ev) {
|
|
315
|
+
if (!ctx.emitProgress)
|
|
316
|
+
return;
|
|
317
|
+
try {
|
|
318
|
+
ctx.emitProgress(ev);
|
|
319
|
+
}
|
|
320
|
+
catch {
|
|
321
|
+
/* swallow — telemetry must never break the pipeline */
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
function errString(e) {
|
|
325
|
+
if (e instanceof Error)
|
|
326
|
+
return e.message;
|
|
327
|
+
return String(e);
|
|
328
|
+
}
|
|
329
|
+
/**
|
|
330
|
+
* Read a `Response` body into a single Buffer, aborting once the
|
|
331
|
+
* accumulated size exceeds `maxBytes`. Reports progress through
|
|
332
|
+
* `onProgress` after each chunk so the AgentHost can render a download
|
|
333
|
+
* progress card.
|
|
334
|
+
*/
|
|
335
|
+
async function readBodyWithCap(res, maxBytes, declaredLen, onProgress) {
|
|
336
|
+
if (!res.body) {
|
|
337
|
+
const ab = await res.arrayBuffer();
|
|
338
|
+
if (ab.byteLength > maxBytes) {
|
|
339
|
+
throw new ResolverError("oversized", `body ${ab.byteLength} exceeds maxBytes ${maxBytes}`);
|
|
340
|
+
}
|
|
341
|
+
onProgress(ab.byteLength, declaredLen);
|
|
342
|
+
return Buffer.from(ab);
|
|
343
|
+
}
|
|
344
|
+
const reader = res.body.getReader();
|
|
345
|
+
const chunks = [];
|
|
346
|
+
let received = 0;
|
|
347
|
+
try {
|
|
348
|
+
while (true) {
|
|
349
|
+
const { value, done } = await reader.read();
|
|
350
|
+
if (done)
|
|
351
|
+
break;
|
|
352
|
+
if (value) {
|
|
353
|
+
const chunk = Buffer.from(value);
|
|
354
|
+
received += chunk.length;
|
|
355
|
+
if (received > maxBytes) {
|
|
356
|
+
throw new ResolverError("oversized", `body exceeded maxBytes ${maxBytes} during streaming`);
|
|
357
|
+
}
|
|
358
|
+
chunks.push(chunk);
|
|
359
|
+
onProgress(received, declaredLen);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
finally {
|
|
364
|
+
try {
|
|
365
|
+
reader.releaseLock();
|
|
366
|
+
}
|
|
367
|
+
catch {
|
|
368
|
+
/* noop */
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
return Buffer.concat(chunks);
|
|
372
|
+
}
|
|
373
|
+
function normaliseMime(raw) {
|
|
374
|
+
// strip ;charset=… and trim whitespace
|
|
375
|
+
const idx = raw.indexOf(";");
|
|
376
|
+
const body = idx >= 0 ? raw.slice(0, idx) : raw;
|
|
377
|
+
return body.trim().toLowerCase();
|
|
378
|
+
}
|
|
379
|
+
function isAllowedMime(mime) {
|
|
380
|
+
if (!mime)
|
|
381
|
+
return false;
|
|
382
|
+
if (ALLOWED_MIMES.has(mime))
|
|
383
|
+
return true;
|
|
384
|
+
for (const p of ALLOWED_MIME_PREFIXES) {
|
|
385
|
+
if (mime.startsWith(p))
|
|
386
|
+
return true;
|
|
387
|
+
}
|
|
388
|
+
return false;
|
|
389
|
+
}
|
|
390
|
+
/**
|
|
391
|
+
* RFC 1918 / RFC 4193 / loopback / link-local / multicast / etc check.
|
|
392
|
+
* We deliberately keep this exhaustive rather than rely on a third-party
|
|
393
|
+
* package — the SSRF surface is small and security-critical.
|
|
394
|
+
*
|
|
395
|
+
* IPv4 loopback — 127.0.0.0/8
|
|
396
|
+
* IPv4 RFC1918 private — 10/8, 172.16/12, 192.168/16
|
|
397
|
+
* IPv4 link-local — 169.254/16
|
|
398
|
+
* IPv4 CGNAT — 100.64/10
|
|
399
|
+
* IPv4 broadcast / multicast — 224/4, 255.255.255.255
|
|
400
|
+
* IPv4 0.0.0.0/8 — "this network"
|
|
401
|
+
* IPv6 loopback — ::1
|
|
402
|
+
* IPv6 link-local — fe80::/10
|
|
403
|
+
* IPv6 ULA — fc00::/7
|
|
404
|
+
* IPv6 multicast — ff00::/8
|
|
405
|
+
* IPv6 unspecified — ::
|
|
406
|
+
* IPv4-mapped IPv6 — ::ffff:0:0/96 (mapped to v4 above)
|
|
407
|
+
*/
|
|
408
|
+
export function isPrivateOrLoopback(addr, family) {
|
|
409
|
+
if (!addr)
|
|
410
|
+
return true;
|
|
411
|
+
const a = addr.toLowerCase();
|
|
412
|
+
if (family === 4 || /^\d+\.\d+\.\d+\.\d+$/.test(a)) {
|
|
413
|
+
return isPrivateIpv4(a);
|
|
414
|
+
}
|
|
415
|
+
// IPv4-mapped IPv6 — ::ffff:1.2.3.4 / ::ffff:0102:0304
|
|
416
|
+
if (a.startsWith("::ffff:")) {
|
|
417
|
+
const tail = a.slice("::ffff:".length);
|
|
418
|
+
if (/^\d+\.\d+\.\d+\.\d+$/.test(tail)) {
|
|
419
|
+
return isPrivateIpv4(tail);
|
|
420
|
+
}
|
|
421
|
+
// hex-form ::ffff:0102:0304 → 1.2.3.4
|
|
422
|
+
const mapped = hex32ToIpv4(tail);
|
|
423
|
+
if (mapped)
|
|
424
|
+
return isPrivateIpv4(mapped);
|
|
425
|
+
}
|
|
426
|
+
// IPv6 loopback / unspecified
|
|
427
|
+
if (a === "::1" || a === "::")
|
|
428
|
+
return true;
|
|
429
|
+
// IPv6 link-local fe80::/10
|
|
430
|
+
if (a.startsWith("fe8") || a.startsWith("fe9") || a.startsWith("fea") || a.startsWith("feb")) {
|
|
431
|
+
return true;
|
|
432
|
+
}
|
|
433
|
+
// IPv6 ULA fc00::/7 (covers fc.. and fd..)
|
|
434
|
+
if (a.startsWith("fc") || a.startsWith("fd"))
|
|
435
|
+
return true;
|
|
436
|
+
// IPv6 multicast ff00::/8
|
|
437
|
+
if (a.startsWith("ff"))
|
|
438
|
+
return true;
|
|
439
|
+
return false;
|
|
440
|
+
}
|
|
441
|
+
function isPrivateIpv4(a) {
|
|
442
|
+
const parts = a.split(".").map((s) => parseInt(s, 10));
|
|
443
|
+
if (parts.length !== 4 || parts.some((n) => !Number.isFinite(n)))
|
|
444
|
+
return true;
|
|
445
|
+
const [b1, b2] = parts;
|
|
446
|
+
// 0.0.0.0/8 — "this host on this network"
|
|
447
|
+
if (b1 === 0)
|
|
448
|
+
return true;
|
|
449
|
+
if (b1 === 127)
|
|
450
|
+
return true; // 127/8 loopback
|
|
451
|
+
if (b1 === 10)
|
|
452
|
+
return true; // 10/8 private
|
|
453
|
+
if (b1 === 172 && b2 >= 16 && b2 <= 31)
|
|
454
|
+
return true; // 172.16/12 private
|
|
455
|
+
if (b1 === 192 && b2 === 168)
|
|
456
|
+
return true; // 192.168/16 private
|
|
457
|
+
if (b1 === 169 && b2 === 254)
|
|
458
|
+
return true; // 169.254/16 link-local
|
|
459
|
+
if (b1 === 100 && b2 >= 64 && b2 <= 127)
|
|
460
|
+
return true; // 100.64/10 CGNAT
|
|
461
|
+
if (b1 >= 224 && b1 <= 239)
|
|
462
|
+
return true; // 224/4 multicast
|
|
463
|
+
if (b1 >= 240)
|
|
464
|
+
return true; // 240/4 reserved + 255.255.255.255 broadcast
|
|
465
|
+
return false;
|
|
466
|
+
}
|
|
467
|
+
function hex32ToIpv4(s) {
|
|
468
|
+
// expects e.g. "0102:0304" or "0102:0304" or "102:304"
|
|
469
|
+
const parts = s.split(":");
|
|
470
|
+
if (parts.length !== 2)
|
|
471
|
+
return null;
|
|
472
|
+
const high = parseInt(parts[0], 16);
|
|
473
|
+
const low = parseInt(parts[1], 16);
|
|
474
|
+
if (!Number.isFinite(high) || !Number.isFinite(low))
|
|
475
|
+
return null;
|
|
476
|
+
const b1 = (high >> 8) & 0xff;
|
|
477
|
+
const b2 = high & 0xff;
|
|
478
|
+
const b3 = (low >> 8) & 0xff;
|
|
479
|
+
const b4 = low & 0xff;
|
|
480
|
+
return `${b1}.${b2}.${b3}.${b4}`;
|
|
481
|
+
}
|
|
482
|
+
function joinRemote(dir, filename) {
|
|
483
|
+
const left = dir.endsWith("/") ? dir.slice(0, -1) : dir;
|
|
484
|
+
return `${left}/${filename}`;
|
|
485
|
+
}
|
|
486
|
+
/**
|
|
487
|
+
* Quote a path for safe inclusion in a single-quoted shell argument.
|
|
488
|
+
* Path is generated server-side (no user input), but defensive
|
|
489
|
+
* quoting keeps us safe in case future filename schemes ever include
|
|
490
|
+
* exotic characters.
|
|
491
|
+
*/
|
|
492
|
+
function shellQuote(s) {
|
|
493
|
+
return `'${s.replace(/'/g, "'\\''")}'`;
|
|
494
|
+
}
|
|
495
|
+
//# sourceMappingURL=url-resolver.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"url-resolver.js","sourceRoot":"","sources":["../../../src/resources/resolvers/url-resolver.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,QAAQ,IAAI,GAAG,EAAsB,MAAM,UAAU,CAAC;AAC/D,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AAE/B,OAAO,EAAE,uBAAuB,EAA0B,MAAM,aAAa,CAAC;AAC9E,OAAO,EAAE,YAAY,EAAE,qBAAqB,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AASrF,MAAM,OAAO,aAAc,SAAQ,KAAK;IAEpB;IAEA;IAHlB,YACkB,IAAuB,EACvC,OAAe,EACC,IAA8B;QAE9C,KAAK,CAAC,OAAO,CAAC,CAAC;QAJC,SAAI,GAAJ,IAAI,CAAmB;QAEvB,SAAI,GAAJ,IAAI,CAA0B;QAG9C,IAAI,CAAC,IAAI,GAAG,eAAe,CAAC;IAC9B,CAAC;IACD,gEAAgE;IAChE,IAAI,YAAY;QACd,OAAO,uBAAuB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC5C,CAAC;CACF;AAiBD,MAAM,kBAAkB,GAAG,CAAC,GAAG,IAA8B,EAAE,EAAE,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,CAAC;AACjF,MAAM,kBAAkB,GAAG,KAAK,EAAE,QAAgB,EAA4B,EAAE;IAC9E,uEAAuE;IACvE,kEAAkE;IAClE,sEAAsE;IACtE,0BAA0B;IAC1B,OAAO,GAAG,CAAC,MAAM,CAAC,QAAQ,EAAE,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC;AAC7C,CAAC,CAAC;AAEF,MAAM,OAAO,WAAW;IACb,IAAI,GAAG,KAAK,CAAC;IACL,SAAS,CAAe;IACxB,SAAS,CAAiD;IAC1D,eAAe,CAAU;IAE1C,YAAY,OAA2B,EAAE;QACvC,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,SAAS,IAAI,kBAAkB,CAAC;QACtD,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,SAAS,IAAI,kBAAkB,CAAC;QACtD,IAAI,CAAC,eAAe,GAAG,IAAI,CAAC,eAAe,CAAC;IAC9C,CAAC;IAED,KAAK,CAAC,GAAgB;QACpB,OAAO,GAAG,CAAC,IAAI,KAAK,KAAK,CAAC;IAC5B,CAAC;IAED,KAAK,CAAC,OAAO,CAAC,GAAgB,EAAE,GAAoB;QAClD,IAAI,GAAG,CAAC,IAAI,KAAK,KAAK,EAAE,CAAC;YACvB,MAAM,IAAI,aAAa,CACrB,aAAa,EACb,qCAAqC,GAAG,CAAC,IAAI,EAAE,CAChD,CAAC;QACJ,CAAC;QACD,IAAI,GAAG,CAAC,WAAW,CAAC,OAAO,EAAE,CAAC;YAC5B,MAAM,IAAI,aAAa,CAAC,SAAS,EAAE,8BAA8B,CAAC,CAAC;QACrE,CAAC;QAED,IAAI,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,CAAC;QACnC,IAAI,KAAgC,CAAC;QACrC,IAAI,CAAC;YACH,KAAK,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;YACvC,IAAI,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC,CAAC;YACxC,OAAO,KAAK,CAAC;QACf,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,MAAM,GAAG,GAAG,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;YAC1D,IAAI,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,CAAC;YAC7C,MAAM,GAAG,CAAC;QACZ,CAAC;IACH,CAAC;IAED,sEAAsE;IACtE,wEAAwE;IACxE,sEAAsE;IAE9D,KAAK,CAAC,SAAS,CACrB,GAAkC,EAClC,GAAoB;QAEpB,qBAAqB;QACrB,IAAI,MAAW,CAAC;QAChB,IAAI,CAAC;YACH,MAAM,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAC5B,CAAC;QAAC,MAAM,CAAC;YACP,MAAM,IAAI,aAAa,CAAC,mBAAmB,EAAE,gBAAgB,GAAG,CAAC,GAAG,EAAE,CAAC,CAAC;QAC1E,CAAC;QACD,MAAM,MAAM,GAAG,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC;QAC/D,IAAI,CAAC,GAAG,CAAC,cAAc,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;YACzC,MAAM,IAAI,aAAa,CACrB,mBAAmB,EACnB,WAAW,MAAM,qBAAqB,EACtC,EAAE,MAAM,EAAE,CACX,CAAC;QACJ,CAAC;QAED,uCAAuC;QACvC,IAAI,GAAG,CAAC,aAAa,EAAE,CAAC;YACtB,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;YACtD,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBACzB,MAAM,IAAI,aAAa,CAAC,aAAa,EAAE,iCAAiC,MAAM,CAAC,QAAQ,EAAE,CAAC,CAAC;YAC7F,CAAC;YACD,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;gBACxB,IAAI,mBAAmB,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC;oBAC7C,MAAM,IAAI,aAAa,CACrB,oBAAoB,EACpB,uBAAuB,CAAC,CAAC,OAAO,QAAQ,MAAM,CAAC,QAAQ,EAAE,CAC1D,CAAC;gBACJ,CAAC;YACH,CAAC;QACH,CAAC;QAED,wDAAwD;QACxD,MAAM,eAAe,GAAG,IAAI,eAAe,EAAE,CAAC;QAC9C,MAAM,OAAO,GAAG,GAAG,EAAE,CAAC,eAAe,CAAC,KAAK,EAAE,CAAC;QAC9C,IAAI,GAAG,CAAC,WAAW,CAAC,OAAO,EAAE,CAAC;YAC5B,MAAM,IAAI,aAAa,CAAC,SAAS,EAAE,sBAAsB,CAAC,CAAC;QAC7D,CAAC;QACD,GAAG,CAAC,WAAW,CAAC,gBAAgB,CAAC,OAAO,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;QACnE,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,eAAe,CAAC,KAAK,EAAE,EAAE,GAAG,CAAC,SAAS,CAAC,CAAC;QAEvE,IAAI,QAAkB,CAAC;QACvB,IAAI,CAAC;YACH,QAAQ,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,QAAQ,EAAE,EAAE;gBACjD,MAAM,EAAE,eAAe,CAAC,MAAM;gBAC9B,QAAQ,EAAE,QAAQ;aACnB,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,GAAG,CAAC,WAAW,CAAC,mBAAmB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;YACtD,YAAY,CAAC,KAAK,CAAC,CAAC;YACpB,IAAI,GAAG,CAAC,WAAW,CAAC,OAAO,EAAE,CAAC;gBAC5B,MAAM,IAAI,aAAa,CAAC,SAAS,EAAE,sBAAsB,EAAE,EAAE,KAAK,EAAE,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;YACtF,CAAC;YACD,MAAM,IAAI,aAAa,CAAC,aAAa,EAAE,iBAAiB,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QAC1E,CAAC;QAED,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;YACjB,GAAG,CAAC,WAAW,CAAC,mBAAmB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;YACtD,YAAY,CAAC,KAAK,CAAC,CAAC;YACpB,MAAM,IAAI,aAAa,CACrB,aAAa,EACb,kBAAkB,QAAQ,CAAC,MAAM,IAAI,QAAQ,CAAC,UAAU,EAAE,EAC1D,EAAE,MAAM,EAAE,QAAQ,CAAC,MAAM,EAAE,CAC5B,CAAC;QACJ,CAAC;QAED,gFAAgF;QAChF,MAAM,KAAK,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,IAAI,EAAE,CAAC;QACzD,MAAM,IAAI,GAAG,aAAa,CAAC,KAAK,CAAC,CAAC;QAClC,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,EAAE,CAAC;YACzB,GAAG,CAAC,WAAW,CAAC,mBAAmB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;YACtD,YAAY,CAAC,KAAK,CAAC,CAAC;YACpB,MAAM,IAAI,aAAa,CACrB,iBAAiB,EACjB,iBAAiB,IAAI,4BAA4B,EACjD,EAAE,IAAI,EAAE,CACT,CAAC;QACJ,CAAC;QAED,uEAAuE;QACvE,MAAM,WAAW,GAAG,QAAQ,CAAC,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC;QAC/E,IAAI,MAAM,CAAC,QAAQ,CAAC,WAAW,CAAC,IAAI,WAAW,GAAG,GAAG,CAAC,QAAQ,EAAE,CAAC;YAC/D,GAAG,CAAC,WAAW,CAAC,mBAAmB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;YACtD,YAAY,CAAC,KAAK,CAAC,CAAC;YACpB,MAAM,IAAI,aAAa,CACrB,WAAW,EACX,2BAA2B,WAAW,qBAAqB,GAAG,CAAC,QAAQ,EAAE,EACzE,EAAE,WAAW,EAAE,QAAQ,EAAE,GAAG,CAAC,QAAQ,EAAE,CACxC,CAAC;QACJ,CAAC;QAED,uCAAuC;QACvC,IAAI,KAAa,CAAC;QAClB,IAAI,CAAC;YACH,KAAK,GAAG,MAAM,eAAe,CAC3B,QAAQ,EACR,GAAG,CAAC,QAAQ,EACZ,MAAM,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,SAAS,EACtD,CAAC,UAAU,EAAE,KAAK,EAAE,EAAE;gBACpB,IAAI,CAAC,GAAG,EAAE;oBACR,KAAK,EAAE,QAAQ;oBACf,GAAG;oBACH,eAAe,EAAE,UAAU;oBAC3B,GAAG,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;iBACtD,CAAC,CAAC;YACL,CAAC,CACF,CAAC;QACJ,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,IAAI,GAAG,CAAC,WAAW,CAAC,OAAO,EAAE,CAAC;gBAC5B,MAAM,IAAI,aAAa,CAAC,SAAS,EAAE,0BAA0B,EAAE,EAAE,KAAK,EAAE,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;YAC1F,CAAC;YACD,IAAI,CAAC,YAAY,aAAa;gBAAE,MAAM,CAAC,CAAC;YACxC,MAAM,IAAI,aAAa,CAAC,aAAa,EAAE,qBAAqB,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QAC9E,CAAC;gBAAS,CAAC;YACT,GAAG,CAAC,WAAW,CAAC,mBAAmB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;YACtD,YAAY,CAAC,KAAK,CAAC,CAAC;QACtB,CAAC;QAED;;;WAGG;QACH,IAAI,GAAG,CAAC,aAAa,EAAE,CAAC;YACtB,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC;YACtE,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC;YACnE,MAAM,UAAU,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC;YAC1D,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC;YAC5D,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBAChC,MAAM,IAAI,aAAa,CACrB,oBAAoB,EACpB,WAAW,MAAM,CAAC,QAAQ,qCAAqC,EAC/D,EAAE,OAAO,EAAE,CAAC,GAAG,UAAU,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,CAC/D,CAAC;YACJ,CAAC;YACD,kEAAkE;YAClE,kCAAkC;YAClC,KAAK,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC;gBACrB,IAAI,mBAAmB,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC;oBAC7C,MAAM,IAAI,aAAa,CACrB,oBAAoB,EACpB,6CAA6C,CAAC,CAAC,OAAO,EAAE,EACxD,EAAE,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE,CACvB,CAAC;gBACJ,CAAC;YACH,CAAC;QACH,CAAC;QAED,gEAAgE;QAChE,MAAM,GAAG,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC;QAC/B,IAAI,CAAC,GAAG,EAAE,CAAC;YACT,MAAM,IAAI,aAAa,CACrB,iBAAiB,EACjB,SAAS,IAAI,wCAAwC,EACrD,EAAE,IAAI,EAAE,CACT,CAAC;QACJ,CAAC;QAED,+BAA+B;QAC/B,IAAI,OAAe,CAAC;QACpB,IAAI,CAAC;YACH,OAAO,GAAG,MAAM,GAAG,CAAC,MAAM,CAAC,mBAAmB,CAAC,IAAI,CAAC,CAAC;QACvD,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,MAAM,IAAI,aAAa,CACrB,YAAY,EACZ,4CAA4C,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,EACnE,EAAE,IAAI,EAAE,CACT,CAAC;QACJ,CAAC;QACD,MAAM,GAAG,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QAC7D,MAAM,IAAI,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;QAC7B,MAAM,QAAQ,GAAG,SAAS,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,IAAI,IAAI,IAAI,GAAG,EAAE,CAAC;QACzE,MAAM,UAAU,GAAG,UAAU,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;QAEjD;;;;;;;;sCAQ8B;QAC9B,IAAI,OAAO,GAAG,CAAC,GAAG,CAAC,cAAc,KAAK,UAAU,EAAE,CAAC;YACjD,IAAI,CAAC;gBACH,MAAM,GAAG,CAAC,GAAG,CAAC,cAAc,CAAC,YAAY,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;YAClE,CAAC;YAAC,OAAO,CAAC,EAAE,CAAC;gBACX,GAAG,CAAC,GAAG,CAAC,IAAI,CACV,EAAE,OAAO,EAAE,GAAG,EAAE,SAAS,CAAC,CAAC,CAAC,EAAE,EAC9B,wDAAwD,CACzD,CAAC;YACJ,CAAC;QACH,CAAC;QAED;4EACoE;QACpE,IAAI,GAAG,CAAC,YAAY,EAAE,CAAC;YACrB,IAAI,CAAC;gBACH,MAAM,GAAG,CAAC,YAAY,CAAC,cAAc,CAAC;oBACpC,GAAG,EAAE,GAAG,CAAC,GAAG;oBACZ,UAAU,EAAE,OAAO;oBACnB,aAAa,EAAE,KAAK,CAAC,MAAM;oBAC3B,GAAG,EAAE,GAAG,CAAC,GAAG;iBACb,CAAC,CAAC;YACL,CAAC;YAAC,OAAO,CAAC,EAAE,CAAC;gBACX,IAAI,CAAC,YAAY,aAAa;oBAAE,MAAM,CAAC,CAAC;gBACxC,MAAM,IAAI,aAAa,CACrB,YAAY,EACZ,+BAA+B,SAAS,CAAC,CAAC,CAAC,EAAE,EAC7C,EAAE,OAAO,EAAE,CACZ,CAAC;YACJ,CAAC;QACH,CAAC;QAED,iCAAiC;QACjC,IAAI,OAAO,GAAG,CAAC,GAAG,CAAC,SAAS,KAAK,UAAU,EAAE,CAAC;YAC5C,MAAM,IAAI,aAAa,CACrB,YAAY,EACZ,mEAAmE,CACpE,CAAC;QACJ,CAAC;QACD,IAAI,CAAC;YACH,MAAM,GAAG,CAAC,GAAG,CAAC,SAAS,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC;QAC7C,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,MAAM,IAAI,aAAa,CACrB,YAAY,EACZ,yBAAyB,SAAS,CAAC,CAAC,CAAC,EAAE,EACvC,EAAE,UAAU,EAAE,CACf,CAAC;QACJ,CAAC;QAED,4CAA4C;QAC5C,IAAI,OAAO,GAAG,KAAK,CAAC;QACpB,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,CAAC;QAChD,IAAI,QAAQ,KAAK,SAAS,IAAI,OAAO,GAAG,CAAC,GAAG,CAAC,cAAc,KAAK,UAAU,EAAE,CAAC;YAC3E,IAAI,CAAC;gBACH,+DAA+D;gBAC/D,4DAA4D;gBAC5D,4DAA4D;gBAC5D,yDAAyD;gBACzD,MAAM,GAAG,CAAC,GAAG,CAAC,cAAc,CAAC,gBAAgB,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC;gBACvE,MAAM,GAAG,CAAC,GAAG,CAAC,cAAc,CAC1B,2EAA2E,UAAU,EAAE,CACxF,CAAC;gBACF,OAAO,GAAG,IAAI,CAAC;YACjB,CAAC;YAAC,OAAO,CAAC,EAAE,CAAC;gBACX,wDAAwD;gBACxD,uCAAuC;gBACvC,GAAG,CAAC,GAAG,CAAC,IAAI,CACV,EAAE,UAAU,EAAE,GAAG,EAAE,SAAS,CAAC,CAAC,CAAC,EAAE,EACjC,qDAAqD,CACtD,CAAC;YACJ,CAAC;QACH,CAAC;QAED,OAAO;YACL,GAAG;YACH,UAAU;YACV,IAAI;YACJ,SAAS,EAAE,KAAK,CAAC,MAAM;YACvB,MAAM,EAAE,GAAG;YACX,mBAAmB,EAAE,OAAO;YAC5B,SAAS,EAAE,GAAG,CAAC,GAAG;YAClB,UAAU,EAAE,IAAI,CAAC,GAAG,EAAE;SACvB,CAAC;IACJ,CAAC;IAEO,KAAK,CAAC,cAAc,CAAC,GAAoB;QAC/C,IAAI,IAAI,CAAC,eAAe;YAAE,OAAO,IAAI,CAAC,eAAe,CAAC,WAAW,EAAE,CAAC;QACpE,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;YACxC,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,IAAI,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,QAAQ,EAAE,CAAC,WAAW,EAAE,CAAC;QAC/D,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,EAAE,CAAC;QACZ,CAAC;IACH,CAAC;CACF;AAED,wEAAwE;AACxE,yEAAyE;AACzE,wEAAwE;AAExE,SAAS,IAAI,CAAC,GAAoB,EAAE,EAAwB;IAC1D,IAAI,CAAC,GAAG,CAAC,YAAY;QAAE,OAAO;IAC9B,IAAI,CAAC;QACH,GAAG,CAAC,YAAY,CAAC,EAAE,CAAC,CAAC;IACvB,CAAC;IAAC,MAAM,CAAC;QACP,uDAAuD;IACzD,CAAC;AACH,CAAC;AAED,SAAS,SAAS,CAAC,CAAU;IAC3B,IAAI,CAAC,YAAY,KAAK;QAAE,OAAO,CAAC,CAAC,OAAO,CAAC;IACzC,OAAO,MAAM,CAAC,CAAC,CAAC,CAAC;AACnB,CAAC;AAED;;;;;GAKG;AACH,KAAK,UAAU,eAAe,CAC5B,GAAa,EACb,QAAgB,EAChB,WAA+B,EAC/B,UAA6E;IAE7E,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC;QACd,MAAM,EAAE,GAAG,MAAM,GAAG,CAAC,WAAW,EAAE,CAAC;QACnC,IAAI,EAAE,CAAC,UAAU,GAAG,QAAQ,EAAE,CAAC;YAC7B,MAAM,IAAI,aAAa,CACrB,WAAW,EACX,QAAQ,EAAE,CAAC,UAAU,qBAAqB,QAAQ,EAAE,CACrD,CAAC;QACJ,CAAC;QACD,UAAU,CAAC,EAAE,CAAC,UAAU,EAAE,WAAW,CAAC,CAAC;QACvC,OAAO,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACzB,CAAC;IACD,MAAM,MAAM,GAAG,GAAG,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC;IACpC,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,IAAI,QAAQ,GAAG,CAAC,CAAC;IACjB,IAAI,CAAC;QACH,OAAO,IAAI,EAAE,CAAC;YACZ,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,MAAM,MAAM,CAAC,IAAI,EAAE,CAAC;YAC5C,IAAI,IAAI;gBAAE,MAAM;YAChB,IAAI,KAAK,EAAE,CAAC;gBACV,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;gBACjC,QAAQ,IAAI,KAAK,CAAC,MAAM,CAAC;gBACzB,IAAI,QAAQ,GAAG,QAAQ,EAAE,CAAC;oBACxB,MAAM,IAAI,aAAa,CACrB,WAAW,EACX,0BAA0B,QAAQ,mBAAmB,CACtD,CAAC;gBACJ,CAAC;gBACD,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;gBACnB,UAAU,CAAC,QAAQ,EAAE,WAAW,CAAC,CAAC;YACpC,CAAC;QACH,CAAC;IACH,CAAC;YAAS,CAAC;QACT,IAAI,CAAC;YACH,MAAM,CAAC,WAAW,EAAE,CAAC;QACvB,CAAC;QAAC,MAAM,CAAC;YACP,UAAU;QACZ,CAAC;IACH,CAAC;IACD,OAAO,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;AAC/B,CAAC;AAED,SAAS,aAAa,CAAC,GAAW;IAChC,uCAAuC;IACvC,MAAM,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IAC7B,MAAM,IAAI,GAAG,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC;IAChD,OAAO,IAAI,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;AACnC,CAAC;AAED,SAAS,aAAa,CAAC,IAAY;IACjC,IAAI,CAAC,IAAI;QAAE,OAAO,KAAK,CAAC;IACxB,IAAI,aAAa,CAAC,GAAG,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IACzC,KAAK,MAAM,CAAC,IAAI,qBAAqB,EAAE,CAAC;QACtC,IAAI,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC;YAAE,OAAO,IAAI,CAAC;IACtC,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,mBAAmB,CAAC,IAAY,EAAE,MAAc;IAC9D,IAAI,CAAC,IAAI;QAAE,OAAO,IAAI,CAAC;IACvB,MAAM,CAAC,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC;IAE7B,IAAI,MAAM,KAAK,CAAC,IAAI,sBAAsB,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;QACnD,OAAO,aAAa,CAAC,CAAC,CAAC,CAAC;IAC1B,CAAC;IAED,uDAAuD;IACvD,IAAI,CAAC,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;QAC5B,MAAM,IAAI,GAAG,CAAC,CAAC,KAAK,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;QACvC,IAAI,sBAAsB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YACtC,OAAO,aAAa,CAAC,IAAI,CAAC,CAAC;QAC7B,CAAC;QACD,sCAAsC;QACtC,MAAM,MAAM,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC;QACjC,IAAI,MAAM;YAAE,OAAO,aAAa,CAAC,MAAM,CAAC,CAAC;IAC3C,CAAC;IAED,8BAA8B;IAC9B,IAAI,CAAC,KAAK,KAAK,IAAI,CAAC,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IAC3C,4BAA4B;IAC5B,IAAI,CAAC,CAAC,UAAU,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,UAAU,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,UAAU,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,UAAU,CAAC,KAAK,CAAC,EAAE,CAAC;QAC7F,OAAO,IAAI,CAAC;IACd,CAAC;IACD,2CAA2C;IAC3C,IAAI,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IAC1D,0BAA0B;IAC1B,IAAI,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IACpC,OAAO,KAAK,CAAC;AACf,CAAC;AAED,SAAS,aAAa,CAAC,CAAS;IAC9B,MAAM,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC;IACvD,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,IAAI,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;QAAE,OAAO,IAAI,CAAC;IAC9E,MAAM,CAAC,EAAE,EAAE,EAAE,CAAC,GAAG,KAAK,CAAC;IACvB,0CAA0C;IAC1C,IAAI,EAAE,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAC1B,IAAI,EAAE,KAAK,GAAG;QAAE,OAAO,IAAI,CAAC,CAAC,iBAAiB;IAC9C,IAAI,EAAE,KAAK,EAAE;QAAE,OAAO,IAAI,CAAC,CAAC,eAAe;IAC3C,IAAI,EAAE,KAAK,GAAG,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE;QAAE,OAAO,IAAI,CAAC,CAAC,oBAAoB;IACzE,IAAI,EAAE,KAAK,GAAG,IAAI,EAAE,KAAK,GAAG;QAAE,OAAO,IAAI,CAAC,CAAC,qBAAqB;IAChE,IAAI,EAAE,KAAK,GAAG,IAAI,EAAE,KAAK,GAAG;QAAE,OAAO,IAAI,CAAC,CAAC,wBAAwB;IACnE,IAAI,EAAE,KAAK,GAAG,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,GAAG;QAAE,OAAO,IAAI,CAAC,CAAC,kBAAkB;IACxE,IAAI,EAAE,IAAI,GAAG,IAAI,EAAE,IAAI,GAAG;QAAE,OAAO,IAAI,CAAC,CAAC,kBAAkB;IAC3D,IAAI,EAAE,IAAI,GAAG;QAAE,OAAO,IAAI,CAAC,CAAC,6CAA6C;IACzE,OAAO,KAAK,CAAC;AACf,CAAC;AAED,SAAS,WAAW,CAAC,CAAS;IAC5B,uDAAuD;IACvD,MAAM,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC3B,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IACpC,MAAM,IAAI,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IACpC,MAAM,GAAG,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IACnC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC;IACjE,MAAM,EAAE,GAAG,CAAC,IAAI,IAAI,CAAC,CAAC,GAAG,IAAI,CAAC;IAC9B,MAAM,EAAE,GAAG,IAAI,GAAG,IAAI,CAAC;IACvB,MAAM,EAAE,GAAG,CAAC,GAAG,IAAI,CAAC,CAAC,GAAG,IAAI,CAAC;IAC7B,MAAM,EAAE,GAAG,GAAG,GAAG,IAAI,CAAC;IACtB,OAAO,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,CAAC;AACnC,CAAC;AAED,SAAS,UAAU,CAAC,GAAW,EAAE,QAAgB;IAC/C,MAAM,IAAI,GAAG,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC;IACxD,OAAO,GAAG,IAAI,IAAI,QAAQ,EAAE,CAAC;AAC/B,CAAC;AAED;;;;;GAKG;AACH,SAAS,UAAU,CAAC,CAAS;IAC3B,OAAO,IAAI,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,CAAC;AACzC,CAAC"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `BootstrapShortcutResourceFacade` — a `ShortcutResourceFacade` impl
|
|
3
|
+
* that delegates into the same `ResourceResolverRegistry` + cache the
|
|
4
|
+
* `AgentHostImpl` uses, so the `download_to_device` shortcut and the
|
|
5
|
+
* AgentHost auto-trigger share **one** download per URL. This closes
|
|
6
|
+
* the wiring gap that previously caused
|
|
7
|
+
* `download_to_device` → `permission_denied` in the Pi extension path.
|
|
8
|
+
*
|
|
9
|
+
* Design notes:
|
|
10
|
+
*
|
|
11
|
+
* - Owns no state of its own — it's a thin adapter. Cache writes still
|
|
12
|
+
* go through the shared `ResourceResolutionCache` so a subsequent
|
|
13
|
+
* AgentHost auto-trigger sees the result.
|
|
14
|
+
* - Re-uses the registry's `resolveAll(taskText, ctx)` entry point
|
|
15
|
+
* because `UrlParser` already knows how to extract a URL from a
|
|
16
|
+
* string. We pass the URL itself as the synthetic task — no parser
|
|
17
|
+
* change required.
|
|
18
|
+
* - Throws `ResolverError` (not a generic `Error`) so the
|
|
19
|
+
* `download_to_device` shortcut can map the code to the
|
|
20
|
+
* `ShortcutErrorCode` vocabulary deterministically.
|
|
21
|
+
*/
|
|
22
|
+
import type { Logger } from "../logger/index.js";
|
|
23
|
+
import type { IDevice } from "../ports/device.js";
|
|
24
|
+
import type { IMcpRuntime } from "../mcp-client/runtime.js";
|
|
25
|
+
import type { AgentHostResourceConfig } from "../app/agent-host.js";
|
|
26
|
+
import type { ShortcutResourceFacade } from "../shortcuts/types.js";
|
|
27
|
+
import type { ResourceResolverRegistry } from "./registry.js";
|
|
28
|
+
import { ResourceResolutionCache } from "./cache.js";
|
|
29
|
+
import type { DiskPressureGuard } from "./types.js";
|
|
30
|
+
export interface BootstrapShortcutResourceFacadeOptions {
|
|
31
|
+
registry: ResourceResolverRegistry;
|
|
32
|
+
cache: ResourceResolutionCache;
|
|
33
|
+
device: IDevice;
|
|
34
|
+
mcpProvider: () => IMcpRuntime;
|
|
35
|
+
config: AgentHostResourceConfig;
|
|
36
|
+
diskPressure?: DiskPressureGuard;
|
|
37
|
+
log: Logger;
|
|
38
|
+
/** Optional shared abort signal — caller's host abort. Defaults to a fresh per-call controller. */
|
|
39
|
+
hostSignal?: AbortSignal;
|
|
40
|
+
}
|
|
41
|
+
export declare class BootstrapShortcutResourceFacade implements ShortcutResourceFacade {
|
|
42
|
+
private readonly opts;
|
|
43
|
+
constructor(opts: BootstrapShortcutResourceFacadeOptions);
|
|
44
|
+
resolveUrl(url: string): Promise<{
|
|
45
|
+
remotePath: string;
|
|
46
|
+
mime: string;
|
|
47
|
+
sizeBytes: number;
|
|
48
|
+
sha256: string;
|
|
49
|
+
sourceUrl: string;
|
|
50
|
+
}>;
|
|
51
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { ResourceResolutionCache } from "./cache.js";
|
|
2
|
+
import { ResolverError } from "./resolvers/url-resolver.js";
|
|
3
|
+
export class BootstrapShortcutResourceFacade {
|
|
4
|
+
opts;
|
|
5
|
+
constructor(opts) {
|
|
6
|
+
this.opts = opts;
|
|
7
|
+
}
|
|
8
|
+
async resolveUrl(url) {
|
|
9
|
+
if (!this.opts.config.enabled) {
|
|
10
|
+
throw new ResolverError("fetch_error", "resource resolution disabled (RESOURCE_RESOLUTION_ENABLED=false)");
|
|
11
|
+
}
|
|
12
|
+
const cacheKey = ResourceResolutionCache.keyForUrl(url);
|
|
13
|
+
const cached = this.opts.cache.get(cacheKey);
|
|
14
|
+
if (cached) {
|
|
15
|
+
// Validate the device-side file is still alive (operators
|
|
16
|
+
// occasionally clear well-known dirs between tasks). On a
|
|
17
|
+
// missing file we invalidate and fall through to a fresh
|
|
18
|
+
// resolve — same protocol the AgentHost uses.
|
|
19
|
+
const alive = await ResourceResolutionCache.validateAlive(cached, this.opts.mcpProvider()).catch(() => false);
|
|
20
|
+
if (alive)
|
|
21
|
+
return toFacadeOutput(cached);
|
|
22
|
+
this.opts.cache.invalidate(cacheKey);
|
|
23
|
+
}
|
|
24
|
+
const ctl = new AbortController();
|
|
25
|
+
const cleanup = [];
|
|
26
|
+
if (this.opts.hostSignal) {
|
|
27
|
+
if (this.opts.hostSignal.aborted)
|
|
28
|
+
ctl.abort();
|
|
29
|
+
else {
|
|
30
|
+
const onAbort = () => ctl.abort();
|
|
31
|
+
this.opts.hostSignal.addEventListener("abort", onAbort, { once: true });
|
|
32
|
+
cleanup.push(() => this.opts.hostSignal?.removeEventListener("abort", onAbort));
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
const timer = setTimeout(() => ctl.abort(), this.opts.config.timeoutMs);
|
|
36
|
+
cleanup.push(() => clearTimeout(timer));
|
|
37
|
+
try {
|
|
38
|
+
const out = await this.opts.registry.resolveAll(url, {
|
|
39
|
+
device: this.opts.device,
|
|
40
|
+
mcp: this.opts.mcpProvider(),
|
|
41
|
+
abortSignal: ctl.signal,
|
|
42
|
+
maxBytes: this.opts.config.maxBytes,
|
|
43
|
+
timeoutMs: this.opts.config.timeoutMs,
|
|
44
|
+
allowedSchemes: [...this.opts.config.allowedSchemes],
|
|
45
|
+
denyPrivateIp: this.opts.config.denyPrivateIp,
|
|
46
|
+
log: this.opts.log,
|
|
47
|
+
...(this.opts.diskPressure ? { diskPressure: this.opts.diskPressure } : {}),
|
|
48
|
+
});
|
|
49
|
+
if (out.assets[0]) {
|
|
50
|
+
this.opts.cache.set(cacheKey, out.assets[0]);
|
|
51
|
+
return toFacadeOutput(out.assets[0]);
|
|
52
|
+
}
|
|
53
|
+
if (out.errors[0])
|
|
54
|
+
throw out.errors[0].error;
|
|
55
|
+
throw new ResolverError("fetch_error", `no resolver matched url: ${url}`);
|
|
56
|
+
}
|
|
57
|
+
finally {
|
|
58
|
+
for (const fn of cleanup)
|
|
59
|
+
fn();
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
function toFacadeOutput(asset) {
|
|
64
|
+
return {
|
|
65
|
+
remotePath: asset.remotePath,
|
|
66
|
+
mime: asset.mime,
|
|
67
|
+
sizeBytes: asset.sizeBytes,
|
|
68
|
+
sha256: asset.sha256,
|
|
69
|
+
sourceUrl: asset.sourceUrl,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=shortcut-facade.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shortcut-facade.js","sourceRoot":"","sources":["../../src/resources/shortcut-facade.ts"],"names":[],"mappings":"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"}
|