@beeos-ai/device-agent 0.4.3 → 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.
Files changed (180) hide show
  1. package/.pi/extensions/android-tools.ts +146 -15
  2. package/dist/adapters/agent-gateway-client.d.ts +37 -0
  3. package/dist/adapters/agent-gateway-client.js +45 -0
  4. package/dist/adapters/agent-gateway-client.js.map +1 -1
  5. package/dist/adapters/agentcore-pi.d.ts +22 -0
  6. package/dist/adapters/agentcore-pi.js +48 -0
  7. package/dist/adapters/agentcore-pi.js.map +1 -1
  8. package/dist/adapters/device-android-adb.d.ts +47 -0
  9. package/dist/adapters/device-android-adb.js +50 -0
  10. package/dist/adapters/device-android-adb.js.map +1 -1
  11. package/dist/adapters/device-desktop.d.ts +12 -0
  12. package/dist/adapters/device-desktop.js +31 -0
  13. package/dist/adapters/device-desktop.js.map +1 -1
  14. package/dist/agent-runtime/adapters/base.d.ts +20 -0
  15. package/dist/agent-runtime/adapters/base.js.map +1 -1
  16. package/dist/agent-runtime/agent-runner.d.ts +4 -2
  17. package/dist/agent-runtime/agent-runner.js.map +1 -1
  18. package/dist/agent-runtime/core.d.ts +60 -0
  19. package/dist/agent-runtime/core.js +190 -9
  20. package/dist/agent-runtime/core.js.map +1 -1
  21. package/dist/agent-runtime/feedback.d.ts +56 -0
  22. package/dist/agent-runtime/feedback.js +54 -0
  23. package/dist/agent-runtime/feedback.js.map +1 -0
  24. package/dist/agent-runtime/index.d.ts +1 -0
  25. package/dist/agent-runtime/index.js +1 -0
  26. package/dist/agent-runtime/index.js.map +1 -1
  27. package/dist/agent-runtime/plan-executor.js +24 -0
  28. package/dist/agent-runtime/plan-executor.js.map +1 -1
  29. package/dist/agent-runtime/planner/base.d.ts +10 -0
  30. package/dist/agent-runtime/planner/base.js.map +1 -1
  31. package/dist/agent-runtime/planner/llm-planner.js +1 -0
  32. package/dist/agent-runtime/planner/llm-planner.js.map +1 -1
  33. package/dist/agent-runtime/planner/prompts.d.ts +3 -0
  34. package/dist/agent-runtime/planner/prompts.js +12 -0
  35. package/dist/agent-runtime/planner/prompts.js.map +1 -1
  36. package/dist/agent-runtime/reflection.d.ts +25 -6
  37. package/dist/agent-runtime/reflection.js +64 -22
  38. package/dist/agent-runtime/reflection.js.map +1 -1
  39. package/dist/agent-runtime/reflectors/policy.d.ts +10 -4
  40. package/dist/agent-runtime/reflectors/policy.js +19 -6
  41. package/dist/agent-runtime/reflectors/policy.js.map +1 -1
  42. package/dist/agent-runtime/shortcuts/fs-store.d.ts +41 -0
  43. package/dist/agent-runtime/shortcuts/fs-store.js +122 -0
  44. package/dist/agent-runtime/shortcuts/fs-store.js.map +1 -0
  45. package/dist/agent-runtime/shortcuts/in-memory-store.d.ts +20 -0
  46. package/dist/agent-runtime/shortcuts/in-memory-store.js +64 -0
  47. package/dist/agent-runtime/shortcuts/in-memory-store.js.map +1 -0
  48. package/dist/agent-runtime/shortcuts/index.d.ts +10 -0
  49. package/dist/agent-runtime/shortcuts/index.js +10 -0
  50. package/dist/agent-runtime/shortcuts/index.js.map +1 -0
  51. package/dist/agent-runtime/shortcuts/types.d.ts +69 -0
  52. package/dist/agent-runtime/shortcuts/types.js +17 -0
  53. package/dist/agent-runtime/shortcuts/types.js.map +1 -0
  54. package/dist/app/agent-host.d.ts +83 -0
  55. package/dist/app/agent-host.js +345 -32
  56. package/dist/app/agent-host.js.map +1 -1
  57. package/dist/app/session-manager.d.ts +33 -4
  58. package/dist/app/session-manager.js +27 -5
  59. package/dist/app/session-manager.js.map +1 -1
  60. package/dist/bootstrap.js +187 -3
  61. package/dist/bootstrap.js.map +1 -1
  62. package/dist/config/loader.js +22 -0
  63. package/dist/config/loader.js.map +1 -1
  64. package/dist/config/schema.d.ts +18 -0
  65. package/dist/config/schema.js +118 -0
  66. package/dist/config/schema.js.map +1 -1
  67. package/dist/inbound/acp/handlers/session.js.map +1 -1
  68. package/dist/mcp-client/client.d.ts +6 -0
  69. package/dist/mcp-client/client.js +11 -0
  70. package/dist/mcp-client/client.js.map +1 -1
  71. package/dist/mcp-client/runtime.d.ts +13 -0
  72. package/dist/orchestrator-system-prompt.d.ts +26 -0
  73. package/dist/orchestrator-system-prompt.js +118 -2
  74. package/dist/orchestrator-system-prompt.js.map +1 -1
  75. package/dist/pi-context.d.ts +50 -0
  76. package/dist/pi-context.js.map +1 -1
  77. package/dist/ports/device.d.ts +18 -0
  78. package/dist/resources/augmenter.d.ts +48 -0
  79. package/dist/resources/augmenter.js +126 -0
  80. package/dist/resources/augmenter.js.map +1 -0
  81. package/dist/resources/cache.d.ts +57 -0
  82. package/dist/resources/cache.js +107 -0
  83. package/dist/resources/cache.js.map +1 -0
  84. package/dist/resources/index.d.ts +17 -0
  85. package/dist/resources/index.js +15 -0
  86. package/dist/resources/index.js.map +1 -0
  87. package/dist/resources/lifecycle/disk-pressure.d.ts +75 -0
  88. package/dist/resources/lifecycle/disk-pressure.js +148 -0
  89. package/dist/resources/lifecycle/disk-pressure.js.map +1 -0
  90. package/dist/resources/lifecycle/index.d.ts +7 -0
  91. package/dist/resources/lifecycle/index.js +7 -0
  92. package/dist/resources/lifecycle/index.js.map +1 -0
  93. package/dist/resources/lifecycle/manager.d.ts +38 -0
  94. package/dist/resources/lifecycle/manager.js +205 -0
  95. package/dist/resources/lifecycle/manager.js.map +1 -0
  96. package/dist/resources/lifecycle/sweeper.d.ts +36 -0
  97. package/dist/resources/lifecycle/sweeper.js +179 -0
  98. package/dist/resources/lifecycle/sweeper.js.map +1 -0
  99. package/dist/resources/lifecycle/types.d.ts +180 -0
  100. package/dist/resources/lifecycle/types.js +46 -0
  101. package/dist/resources/lifecycle/types.js.map +1 -0
  102. package/dist/resources/parsers.d.ts +28 -0
  103. package/dist/resources/parsers.js +88 -0
  104. package/dist/resources/parsers.js.map +1 -0
  105. package/dist/resources/registry.d.ts +47 -0
  106. package/dist/resources/registry.js +107 -0
  107. package/dist/resources/registry.js.map +1 -0
  108. package/dist/resources/resolvers/beeos-file-resolver.d.ts +62 -0
  109. package/dist/resources/resolvers/beeos-file-resolver.js +109 -0
  110. package/dist/resources/resolvers/beeos-file-resolver.js.map +1 -0
  111. package/dist/resources/resolvers/errors.d.ts +11 -0
  112. package/dist/resources/resolvers/errors.js +23 -0
  113. package/dist/resources/resolvers/errors.js.map +1 -0
  114. package/dist/resources/resolvers/mime-table.d.ts +41 -0
  115. package/dist/resources/resolvers/mime-table.js +92 -0
  116. package/dist/resources/resolvers/mime-table.js.map +1 -0
  117. package/dist/resources/resolvers/url-resolver.d.ts +85 -0
  118. package/dist/resources/resolvers/url-resolver.js +495 -0
  119. package/dist/resources/resolvers/url-resolver.js.map +1 -0
  120. package/dist/resources/shortcut-facade.d.ts +51 -0
  121. package/dist/resources/shortcut-facade.js +72 -0
  122. package/dist/resources/shortcut-facade.js.map +1 -0
  123. package/dist/resources/telemetry.d.ts +52 -0
  124. package/dist/resources/telemetry.js +59 -0
  125. package/dist/resources/telemetry.js.map +1 -0
  126. package/dist/resources/types.d.ts +249 -0
  127. package/dist/resources/types.js +29 -0
  128. package/dist/resources/types.js.map +1 -0
  129. package/dist/shortcuts/builtins/download-to-device.d.ts +83 -0
  130. package/dist/shortcuts/builtins/download-to-device.js +125 -0
  131. package/dist/shortcuts/builtins/download-to-device.js.map +1 -0
  132. package/dist/shortcuts/builtins/find-app-package.d.ts +79 -0
  133. package/dist/shortcuts/builtins/find-app-package.js +168 -0
  134. package/dist/shortcuts/builtins/find-app-package.js.map +1 -0
  135. package/dist/shortcuts/builtins/index.d.ts +32 -0
  136. package/dist/shortcuts/builtins/index.js +37 -0
  137. package/dist/shortcuts/builtins/index.js.map +1 -0
  138. package/dist/shortcuts/builtins/open-deeplink.d.ts +68 -0
  139. package/dist/shortcuts/builtins/open-deeplink.js +142 -0
  140. package/dist/shortcuts/builtins/open-deeplink.js.map +1 -0
  141. package/dist/shortcuts/builtins/set-clipboard.d.ts +65 -0
  142. package/dist/shortcuts/builtins/set-clipboard.js +112 -0
  143. package/dist/shortcuts/builtins/set-clipboard.js.map +1 -0
  144. package/dist/shortcuts/index.d.ts +11 -0
  145. package/dist/shortcuts/index.js +11 -0
  146. package/dist/shortcuts/index.js.map +1 -0
  147. package/dist/shortcuts/registry.d.ts +56 -0
  148. package/dist/shortcuts/registry.js +70 -0
  149. package/dist/shortcuts/registry.js.map +1 -0
  150. package/dist/shortcuts/router.d.ts +91 -0
  151. package/dist/shortcuts/router.js +158 -0
  152. package/dist/shortcuts/router.js.map +1 -0
  153. package/dist/shortcuts/types.d.ts +219 -0
  154. package/dist/shortcuts/types.js +46 -0
  155. package/dist/shortcuts/types.js.map +1 -0
  156. package/package.json +3 -3
  157. package/dist/agent-runtime/skills/sqlite-store.d.ts +0 -60
  158. package/dist/agent-runtime/skills/sqlite-store.js +0 -234
  159. package/dist/agent-runtime/skills/sqlite-store.js.map +0 -1
  160. package/dist/test-lan-autoglm.d.ts +0 -7
  161. package/dist/test-lan-autoglm.js +0 -46
  162. package/dist/test-lan-autoglm.js.map +0 -1
  163. package/dist/test-mem-breakdown.d.ts +0 -1
  164. package/dist/test-mem-breakdown.js +0 -103
  165. package/dist/test-mem-breakdown.js.map +0 -1
  166. package/dist/test-mem.d.ts +0 -1
  167. package/dist/test-mem.js +0 -60
  168. package/dist/test-mem.js.map +0 -1
  169. package/dist/test-mobile-use.d.ts +0 -1
  170. package/dist/test-mobile-use.js +0 -18
  171. package/dist/test-mobile-use.js.map +0 -1
  172. package/dist/test-screenshot-loop.d.ts +0 -1
  173. package/dist/test-screenshot-loop.js +0 -43
  174. package/dist/test-screenshot-loop.js.map +0 -1
  175. package/dist/test-screenshot.d.ts +0 -1
  176. package/dist/test-screenshot.js +0 -10
  177. package/dist/test-screenshot.js.map +0 -1
  178. package/dist/test-ui-xml-dump.d.ts +0 -15
  179. package/dist/test-ui-xml-dump.js +0 -49
  180. package/dist/test-ui-xml-dump.js.map +0 -1
@@ -0,0 +1,107 @@
1
+ /**
2
+ * Resource Resolver Registry — owns the parser/resolver dispatch and
3
+ * the `resolveAll(task)` orchestration entry point.
4
+ *
5
+ * Lifecycle:
6
+ * 1. Bootstrap constructs one registry per AgentHost.
7
+ * 2. AgentHost.submitTask calls `parseAll(prompt)` to extract refs.
8
+ * 3. For each ref, registry picks the first matching `ResourceResolver`
9
+ * and delegates resolution. Parallel where possible.
10
+ * 4. Failures are isolated: one bad URL must not poison the rest of
11
+ * the task. The host falls back to the original prompt only when
12
+ * every resolution failed (or zero refs were found).
13
+ *
14
+ * The registry stays explicit about its dependencies (`ResolverContext`)
15
+ * so unit tests can wire it up without bootstrap.
16
+ */
17
+ export class ResourceResolverRegistry {
18
+ parsers;
19
+ resolvers;
20
+ constructor(opts) {
21
+ this.parsers = [...opts.parsers];
22
+ this.resolvers = [...opts.resolvers];
23
+ }
24
+ /** Run every registered parser; deduplicate by JSON-canonical key. */
25
+ parseAll(task) {
26
+ if (!task)
27
+ return [];
28
+ const seen = new Set();
29
+ const out = [];
30
+ for (const p of this.parsers) {
31
+ let refs;
32
+ try {
33
+ refs = p.parse(task);
34
+ }
35
+ catch {
36
+ // A misbehaving parser must NOT crash the whole task.
37
+ continue;
38
+ }
39
+ for (const ref of refs) {
40
+ const key = stableKey(ref);
41
+ if (seen.has(key))
42
+ continue;
43
+ seen.add(key);
44
+ out.push(ref);
45
+ }
46
+ }
47
+ return out;
48
+ }
49
+ /**
50
+ * Resolve every ref present in the task. Returns successfully
51
+ * resolved assets; failures are surfaced via the `errors` array but
52
+ * never thrown — the AgentHost is responsible for falling back to
53
+ * the original prompt when no asset survived.
54
+ */
55
+ async resolveAll(task, ctx) {
56
+ const refs = this.parseAll(task);
57
+ if (refs.length === 0) {
58
+ return { assets: [], errors: [], skipped: [] };
59
+ }
60
+ const work = refs.map(async (ref) => {
61
+ const resolver = this.pick(ref);
62
+ if (!resolver) {
63
+ return { kind: "skip", ref };
64
+ }
65
+ try {
66
+ const asset = await resolver.resolve(ref, ctx);
67
+ return { kind: "ok", asset };
68
+ }
69
+ catch (e) {
70
+ return { kind: "err", ref, error: e };
71
+ }
72
+ });
73
+ const results = await Promise.all(work);
74
+ const out = { assets: [], errors: [], skipped: [] };
75
+ for (const r of results) {
76
+ if (r.kind === "ok")
77
+ out.assets.push(r.asset);
78
+ else if (r.kind === "err")
79
+ out.errors.push({ ref: r.ref, error: r.error });
80
+ else
81
+ out.skipped.push(r.ref);
82
+ }
83
+ return out;
84
+ }
85
+ pick(ref) {
86
+ for (const r of this.resolvers) {
87
+ if (r.match(ref))
88
+ return r;
89
+ }
90
+ return undefined;
91
+ }
92
+ }
93
+ function stableKey(ref) {
94
+ switch (ref.kind) {
95
+ case "url":
96
+ return `url:${ref.url}`;
97
+ case "file":
98
+ return `file:${ref.path}`;
99
+ case "text":
100
+ return `text:${ref.text}`;
101
+ case "coordinate":
102
+ return `coord:${ref.lat},${ref.lon}`;
103
+ case "beeos-file":
104
+ return `beeos-file:${ref.fileId}`;
105
+ }
106
+ }
107
+ //# sourceMappingURL=registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/resources/registry.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,62 @@
1
+ /**
2
+ * BeeOS File Resolver — turn a `beeos-file://<fileId>` ref into a
3
+ * device-local asset.
4
+ *
5
+ * The web UI emits an ACP `resource_link` block with
6
+ * `uri: "beeos-file://<fileId>"` for every file the user uploads via
7
+ * the chat input. The fileId addresses an opaque blob in the platform's
8
+ * file storage (S3 / MinIO). To get the bytes onto the device this
9
+ * resolver:
10
+ *
11
+ * 1. Calls the Agent Gateway's `GET /api/v1/agent/files/{id}/resolve`
12
+ * (Ed25519-authenticated) which returns a short-lived presigned
13
+ * download URL plus mime/name/size metadata.
14
+ * 2. Constructs a synthetic `kind: "url"` ref pointing at the
15
+ * presigned URL and delegates to the supplied `UrlResolver`. The
16
+ * url resolver does the heavy lifting (streaming download, MIME
17
+ * whitelist, content-length cap, push via MCP fileWrite, Android
18
+ * MediaScanner broadcast).
19
+ * 3. Rewrites the returned `ResolvedAsset` so the public fields refer
20
+ * to the original `beeos-file://<fileId>` form. Most importantly,
21
+ * the SigV4-signed presigned URL is NEVER copied into `sourceUrl`
22
+ * or any logged field — it would leak into LLM context (via the
23
+ * augmenter) and structured logs.
24
+ *
25
+ * The presigned URL points at the platform's object store. In production
26
+ * that's a public S3/MinIO host; in local dev it's `localhost:9000`.
27
+ * SSRF defenses (`denyPrivateIp`) MUST therefore be turned off for the
28
+ * delegated fetch — the resolve endpoint is already an authenticated,
29
+ * server-vended URL, so the loopback target is by-design and not a
30
+ * vector. MIME / size / cancellation defenses still apply.
31
+ */
32
+ import type { ResolvedAsset, ResolverContext, ResourceRef, ResourceResolver } from "../types.js";
33
+ import { type UrlResolver } from "./url-resolver.js";
34
+ import { type AgentGatewayIdentity, type ResolvedAgentFile } from "../../adapters/agent-gateway-client.js";
35
+ export interface BeeosFileResolverOptions {
36
+ /** Agent Gateway base URL, e.g. `http://localhost:8083` or `https://agent-gw.beeos.ai`. */
37
+ agentGatewayUrl: string;
38
+ /** Ed25519 identity used to sign the resolve request. */
39
+ identity: AgentGatewayIdentity;
40
+ /** UrlResolver instance to delegate the actual download/push pipeline to. */
41
+ urlResolver: UrlResolver;
42
+ /** Test seam — Agent Gateway resolve endpoint timeout. Default 10s. */
43
+ resolveTimeoutMs?: number;
44
+ /** Test seam — replaces `resolveAgentFile` for unit tests. */
45
+ resolveImpl?: (args: {
46
+ agentGatewayUrl: string;
47
+ identity: AgentGatewayIdentity;
48
+ fileId: string;
49
+ timeoutMs?: number;
50
+ }) => Promise<ResolvedAgentFile>;
51
+ }
52
+ export declare class BeeosFileResolver implements ResourceResolver {
53
+ readonly name = "beeos-file";
54
+ private readonly agentGatewayUrl;
55
+ private readonly identity;
56
+ private readonly urlResolver;
57
+ private readonly resolveTimeoutMs;
58
+ private readonly resolveImpl;
59
+ constructor(opts: BeeosFileResolverOptions);
60
+ match(ref: ResourceRef): boolean;
61
+ resolve(ref: ResourceRef, ctx: ResolverContext): Promise<ResolvedAsset>;
62
+ }
@@ -0,0 +1,109 @@
1
+ /**
2
+ * BeeOS File Resolver — turn a `beeos-file://<fileId>` ref into a
3
+ * device-local asset.
4
+ *
5
+ * The web UI emits an ACP `resource_link` block with
6
+ * `uri: "beeos-file://<fileId>"` for every file the user uploads via
7
+ * the chat input. The fileId addresses an opaque blob in the platform's
8
+ * file storage (S3 / MinIO). To get the bytes onto the device this
9
+ * resolver:
10
+ *
11
+ * 1. Calls the Agent Gateway's `GET /api/v1/agent/files/{id}/resolve`
12
+ * (Ed25519-authenticated) which returns a short-lived presigned
13
+ * download URL plus mime/name/size metadata.
14
+ * 2. Constructs a synthetic `kind: "url"` ref pointing at the
15
+ * presigned URL and delegates to the supplied `UrlResolver`. The
16
+ * url resolver does the heavy lifting (streaming download, MIME
17
+ * whitelist, content-length cap, push via MCP fileWrite, Android
18
+ * MediaScanner broadcast).
19
+ * 3. Rewrites the returned `ResolvedAsset` so the public fields refer
20
+ * to the original `beeos-file://<fileId>` form. Most importantly,
21
+ * the SigV4-signed presigned URL is NEVER copied into `sourceUrl`
22
+ * or any logged field — it would leak into LLM context (via the
23
+ * augmenter) and structured logs.
24
+ *
25
+ * The presigned URL points at the platform's object store. In production
26
+ * that's a public S3/MinIO host; in local dev it's `localhost:9000`.
27
+ * SSRF defenses (`denyPrivateIp`) MUST therefore be turned off for the
28
+ * delegated fetch — the resolve endpoint is already an authenticated,
29
+ * server-vended URL, so the loopback target is by-design and not a
30
+ * vector. MIME / size / cancellation defenses still apply.
31
+ */
32
+ import { ResolverError } from "./url-resolver.js";
33
+ import { resolveAgentFile, } from "../../adapters/agent-gateway-client.js";
34
+ export class BeeosFileResolver {
35
+ name = "beeos-file";
36
+ agentGatewayUrl;
37
+ identity;
38
+ urlResolver;
39
+ resolveTimeoutMs;
40
+ resolveImpl;
41
+ constructor(opts) {
42
+ this.agentGatewayUrl = opts.agentGatewayUrl;
43
+ this.identity = opts.identity;
44
+ this.urlResolver = opts.urlResolver;
45
+ this.resolveTimeoutMs = opts.resolveTimeoutMs ?? 10_000;
46
+ this.resolveImpl = opts.resolveImpl ?? resolveAgentFile;
47
+ }
48
+ match(ref) {
49
+ return ref.kind === "beeos-file";
50
+ }
51
+ async resolve(ref, ctx) {
52
+ if (ref.kind !== "beeos-file") {
53
+ throw new ResolverError("fetch_error", `BeeosFileResolver: unsupported ref kind ${ref.kind}`);
54
+ }
55
+ if (ctx.abortSignal.aborted) {
56
+ throw new ResolverError("aborted", "resolve aborted before start");
57
+ }
58
+ let presigned;
59
+ try {
60
+ presigned = await this.resolveImpl({
61
+ agentGatewayUrl: this.agentGatewayUrl,
62
+ identity: this.identity,
63
+ fileId: ref.fileId,
64
+ timeoutMs: this.resolveTimeoutMs,
65
+ });
66
+ }
67
+ catch (e) {
68
+ throw new ResolverError("fetch_error", `agent-gateway resolve failed for ${ref.fileId}: ${e.message}`, { fileId: ref.fileId });
69
+ }
70
+ if (!presigned.downloadUrl) {
71
+ throw new ResolverError("fetch_error", `agent-gateway returned empty downloadUrl for ${ref.fileId}`, { fileId: ref.fileId });
72
+ }
73
+ // The presigned URL points at the platform's object store
74
+ // (S3/MinIO). It's a server-vended URL we already trust because
75
+ // the resolve endpoint is Ed25519-authenticated; loopback in dev
76
+ // is by-design, so we turn off `denyPrivateIp` for the delegated
77
+ // fetch. MIME / size / cancellation defenses are unchanged.
78
+ //
79
+ // We also rewrite progress events from the inner UrlResolver so
80
+ // they carry the OUTER `beeos-file` ref shape. UrlResolver emits
81
+ // `phase: start/update/end` events with `ref: { kind: "url",
82
+ // url: <presigned> }` — those events flow into the AgentHost's
83
+ // `task:tool` stream and ultimately into the chat-UI tool card,
84
+ // which would leak the SigV4 token. Rewriting the ref keeps the
85
+ // progress UX (downloaded/total bytes, success/failure) intact
86
+ // while showing only the opaque `beeos-file://<fileId>` identity.
87
+ const outerEmit = ctx.emitProgress;
88
+ const innerCtx = {
89
+ ...ctx,
90
+ denyPrivateIp: false,
91
+ emitProgress: outerEmit
92
+ ? (ev) => outerEmit({ ...ev, ref })
93
+ : undefined,
94
+ };
95
+ const innerRef = { kind: "url", url: presigned.downloadUrl };
96
+ const inner = await this.urlResolver.resolve(innerRef, innerCtx);
97
+ // Strip the SigV4 token — the presigned URL must NEVER reach
98
+ // augmenter / logs / LLM context. Replace `ref` and `sourceUrl`
99
+ // with the opaque `beeos-file://<fileId>` form so downstream
100
+ // consumers can still correlate (telemetry, augmenter rendering)
101
+ // without exposing the presigned URL.
102
+ return {
103
+ ...inner,
104
+ ref,
105
+ sourceUrl: `beeos-file://${ref.fileId}`,
106
+ };
107
+ }
108
+ }
109
+ //# sourceMappingURL=beeos-file-resolver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"beeos-file-resolver.js","sourceRoot":"","sources":["../../../src/resources/resolvers/beeos-file-resolver.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Resolver error vocabulary — kept as a closed enum so Prometheus
3
+ * counters never leak high-cardinality strings, and so the AgentHost
4
+ * can dispatch on a finite set of structured failure modes.
5
+ *
6
+ * The labels here align 1:1 with `ResolveOutcomeLabel` in `types.ts`;
7
+ * this module is the source of truth for the *error* subset (and `success`
8
+ * lives at the call site as a sentinel — never thrown).
9
+ */
10
+ export type ResolverErrorCode = "fetch_error" | "push_error" | "scanner_failed" | "disallowed_scheme" | "oversized" | "timeout" | "private_ip_blocked" | "dns_rebind_blocked" | "disallowed_mime" | "disk_pressure" | "aborted";
11
+ export declare const AGENT_HOST_ERROR_LABELS: Record<ResolverErrorCode, string>;
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Resolver error vocabulary — kept as a closed enum so Prometheus
3
+ * counters never leak high-cardinality strings, and so the AgentHost
4
+ * can dispatch on a finite set of structured failure modes.
5
+ *
6
+ * The labels here align 1:1 with `ResolveOutcomeLabel` in `types.ts`;
7
+ * this module is the source of truth for the *error* subset (and `success`
8
+ * lives at the call site as a sentinel — never thrown).
9
+ */
10
+ export const AGENT_HOST_ERROR_LABELS = {
11
+ fetch_error: "fetch_error",
12
+ push_error: "push_error",
13
+ scanner_failed: "scanner_failed",
14
+ disallowed_scheme: "disallowed_scheme",
15
+ oversized: "oversized",
16
+ timeout: "timeout",
17
+ private_ip_blocked: "private_ip_blocked",
18
+ dns_rebind_blocked: "dns_rebind_blocked",
19
+ disallowed_mime: "disallowed_mime",
20
+ disk_pressure: "disk_pressure",
21
+ aborted: "aborted",
22
+ };
23
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../../../src/resources/resolvers/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAeH,MAAM,CAAC,MAAM,uBAAuB,GAAsC;IACxE,WAAW,EAAE,aAAa;IAC1B,UAAU,EAAE,YAAY;IACxB,cAAc,EAAE,gBAAgB;IAChC,iBAAiB,EAAE,mBAAmB;IACtC,SAAS,EAAE,WAAW;IACtB,OAAO,EAAE,SAAS;IAClB,kBAAkB,EAAE,oBAAoB;IACxC,kBAAkB,EAAE,oBAAoB;IACxC,eAAe,EAAE,iBAAiB;IAClC,aAAa,EAAE,eAAe;IAC9B,OAAO,EAAE,SAAS;CACnB,CAAC"}
@@ -0,0 +1,41 @@
1
+ /**
2
+ * MIME → file-extension whitelist.
3
+ *
4
+ * SECURITY: this module is the SOLE source of file extensions used by
5
+ * the URL resolver. Extensions are NEVER derived from URL paths —
6
+ * doing so opens a path-traversal / extension-injection vector when
7
+ * the upstream serves a `.html` URL with `Content-Type: image/jpeg`.
8
+ *
9
+ * Two surfaces:
10
+ * - `ALLOWED_MIMES` — exact MIME strings we accept.
11
+ * - `ALLOWED_MIME_PREFIXES` — type/subtype prefixes (`image/`,
12
+ * `video/`, `audio/`) for which we accept any subtype on the
13
+ * condition that the resulting `mapMimeToExt` returns a real ext.
14
+ *
15
+ * A MIME is "allowed" iff:
16
+ * 1. It appears in `ALLOWED_MIMES` OR matches a prefix in
17
+ * `ALLOWED_MIME_PREFIXES`, AND
18
+ * 2. `mapMimeToExt` returns a non-null extension.
19
+ *
20
+ * Add new entries by extending BOTH `ALLOWED_MIMES` (or appending to
21
+ * `ALLOWED_MIME_PREFIXES`) AND `MIME_TO_EXT`. Adding only one side is
22
+ * a bug — the resolver will accept the MIME but reject the resource
23
+ * for not having a whitelisted extension, leading to confusing errors.
24
+ */
25
+ /**
26
+ * Strict whitelist — every entry maps to exactly one extension. Adding
27
+ * a new entry requires explicit security review (no dynamic
28
+ * extensions, no falling back to URL-path extension parsing).
29
+ */
30
+ export declare const MIME_TO_EXT: Record<string, string>;
31
+ /** Exact-match MIMEs (a strict subset of `MIME_TO_EXT` keys). */
32
+ export declare const ALLOWED_MIMES: ReadonlySet<string>;
33
+ /**
34
+ * Prefix-match MIMEs. The resolver still rejects the request if the
35
+ * `mapMimeToExt` lookup returns null, so adding a prefix here is safe.
36
+ *
37
+ * `application/*` is intentionally NOT a prefix — we want strict allow-
38
+ * listing for application subtypes (executables, installers, archives).
39
+ */
40
+ export declare const ALLOWED_MIME_PREFIXES: readonly string[];
41
+ export declare function mapMimeToExt(mime: string): string | null;
@@ -0,0 +1,92 @@
1
+ /**
2
+ * MIME → file-extension whitelist.
3
+ *
4
+ * SECURITY: this module is the SOLE source of file extensions used by
5
+ * the URL resolver. Extensions are NEVER derived from URL paths —
6
+ * doing so opens a path-traversal / extension-injection vector when
7
+ * the upstream serves a `.html` URL with `Content-Type: image/jpeg`.
8
+ *
9
+ * Two surfaces:
10
+ * - `ALLOWED_MIMES` — exact MIME strings we accept.
11
+ * - `ALLOWED_MIME_PREFIXES` — type/subtype prefixes (`image/`,
12
+ * `video/`, `audio/`) for which we accept any subtype on the
13
+ * condition that the resulting `mapMimeToExt` returns a real ext.
14
+ *
15
+ * A MIME is "allowed" iff:
16
+ * 1. It appears in `ALLOWED_MIMES` OR matches a prefix in
17
+ * `ALLOWED_MIME_PREFIXES`, AND
18
+ * 2. `mapMimeToExt` returns a non-null extension.
19
+ *
20
+ * Add new entries by extending BOTH `ALLOWED_MIMES` (or appending to
21
+ * `ALLOWED_MIME_PREFIXES`) AND `MIME_TO_EXT`. Adding only one side is
22
+ * a bug — the resolver will accept the MIME but reject the resource
23
+ * for not having a whitelisted extension, leading to confusing errors.
24
+ */
25
+ /**
26
+ * Strict whitelist — every entry maps to exactly one extension. Adding
27
+ * a new entry requires explicit security review (no dynamic
28
+ * extensions, no falling back to URL-path extension parsing).
29
+ */
30
+ export const MIME_TO_EXT = {
31
+ // images
32
+ "image/jpeg": "jpg",
33
+ "image/jpg": "jpg",
34
+ "image/png": "png",
35
+ "image/gif": "gif",
36
+ "image/webp": "webp",
37
+ "image/heic": "heic",
38
+ "image/heif": "heif",
39
+ "image/avif": "avif",
40
+ "image/svg+xml": "svg",
41
+ "image/bmp": "bmp",
42
+ // video
43
+ "video/mp4": "mp4",
44
+ "video/quicktime": "mov",
45
+ "video/webm": "webm",
46
+ "video/3gpp": "3gp",
47
+ "video/x-matroska": "mkv",
48
+ // audio
49
+ "audio/mpeg": "mp3",
50
+ "audio/mp4": "m4a",
51
+ "audio/aac": "aac",
52
+ "audio/wav": "wav",
53
+ "audio/x-wav": "wav",
54
+ "audio/ogg": "ogg",
55
+ "audio/webm": "weba",
56
+ "audio/flac": "flac",
57
+ // documents
58
+ "application/pdf": "pdf",
59
+ "application/zip": "zip",
60
+ "application/vnd.android.package-archive": "apk",
61
+ "application/msword": "doc",
62
+ "application/vnd.openxmlformats-officedocument.wordprocessingml.document": "docx",
63
+ "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": "xlsx",
64
+ "application/vnd.openxmlformats-officedocument.presentationml.presentation": "pptx",
65
+ "application/vnd.ms-excel": "xls",
66
+ "application/vnd.ms-powerpoint": "ppt",
67
+ // text
68
+ "text/plain": "txt",
69
+ "text/csv": "csv",
70
+ };
71
+ /** Exact-match MIMEs (a strict subset of `MIME_TO_EXT` keys). */
72
+ export const ALLOWED_MIMES = new Set(Object.keys(MIME_TO_EXT));
73
+ /**
74
+ * Prefix-match MIMEs. The resolver still rejects the request if the
75
+ * `mapMimeToExt` lookup returns null, so adding a prefix here is safe.
76
+ *
77
+ * `application/*` is intentionally NOT a prefix — we want strict allow-
78
+ * listing for application subtypes (executables, installers, archives).
79
+ */
80
+ export const ALLOWED_MIME_PREFIXES = [
81
+ "image/",
82
+ "video/",
83
+ "audio/",
84
+ ];
85
+ export function mapMimeToExt(mime) {
86
+ const lower = mime.toLowerCase();
87
+ if (Object.prototype.hasOwnProperty.call(MIME_TO_EXT, lower)) {
88
+ return MIME_TO_EXT[lower];
89
+ }
90
+ return null;
91
+ }
92
+ //# sourceMappingURL=mime-table.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mime-table.js","sourceRoot":"","sources":["../../../src/resources/resolvers/mime-table.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH;;;;GAIG;AACH,MAAM,CAAC,MAAM,WAAW,GAA2B;IACjD,SAAS;IACT,YAAY,EAAE,KAAK;IACnB,WAAW,EAAE,KAAK;IAClB,WAAW,EAAE,KAAK;IAClB,WAAW,EAAE,KAAK;IAClB,YAAY,EAAE,MAAM;IACpB,YAAY,EAAE,MAAM;IACpB,YAAY,EAAE,MAAM;IACpB,YAAY,EAAE,MAAM;IACpB,eAAe,EAAE,KAAK;IACtB,WAAW,EAAE,KAAK;IAElB,QAAQ;IACR,WAAW,EAAE,KAAK;IAClB,iBAAiB,EAAE,KAAK;IACxB,YAAY,EAAE,MAAM;IACpB,YAAY,EAAE,KAAK;IACnB,kBAAkB,EAAE,KAAK;IAEzB,QAAQ;IACR,YAAY,EAAE,KAAK;IACnB,WAAW,EAAE,KAAK;IAClB,WAAW,EAAE,KAAK;IAClB,WAAW,EAAE,KAAK;IAClB,aAAa,EAAE,KAAK;IACpB,WAAW,EAAE,KAAK;IAClB,YAAY,EAAE,MAAM;IACpB,YAAY,EAAE,MAAM;IAEpB,YAAY;IACZ,iBAAiB,EAAE,KAAK;IACxB,iBAAiB,EAAE,KAAK;IACxB,yCAAyC,EAAE,KAAK;IAChD,oBAAoB,EAAE,KAAK;IAC3B,yEAAyE,EAAE,MAAM;IACjF,mEAAmE,EAAE,MAAM;IAC3E,2EAA2E,EAAE,MAAM;IACnF,0BAA0B,EAAE,KAAK;IACjC,+BAA+B,EAAE,KAAK;IAEtC,OAAO;IACP,YAAY,EAAE,KAAK;IACnB,UAAU,EAAE,KAAK;CAClB,CAAC;AAEF,iEAAiE;AACjE,MAAM,CAAC,MAAM,aAAa,GAAwB,IAAI,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC;AAEpF;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAsB;IACtD,QAAQ;IACR,QAAQ;IACR,QAAQ;CACT,CAAC;AAEF,MAAM,UAAU,YAAY,CAAC,IAAY;IACvC,MAAM,KAAK,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC;IACjC,IAAI,MAAM,CAAC,SAAS,CAAC,cAAc,CAAC,IAAI,CAAC,WAAW,EAAE,KAAK,CAAC,EAAE,CAAC;QAC7D,OAAO,WAAW,CAAC,KAAK,CAAC,CAAC;IAC5B,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}
@@ -0,0 +1,85 @@
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 { type LookupAddress } from "node:dns";
33
+ import { type ResolverErrorCode } from "./errors.js";
34
+ import type { ResolvedAsset, ResolverContext, ResourceRef, ResourceResolver } from "../types.js";
35
+ export declare class ResolverError extends Error {
36
+ readonly code: ResolverErrorCode;
37
+ readonly meta?: Record<string, unknown> | undefined;
38
+ constructor(code: ResolverErrorCode, message: string, meta?: Record<string, unknown> | undefined);
39
+ /** Promtheus-friendly outcome label (subset of error codes). */
40
+ get outcomeLabel(): string;
41
+ }
42
+ /**
43
+ * Optional injection hooks for tests. Production passes the real
44
+ * `globalThis.fetch` and `dns.lookup`.
45
+ */
46
+ export interface UrlResolverOptions {
47
+ fetchImpl?: typeof fetch;
48
+ dnsLookup?: (hostname: string) => Promise<LookupAddress[]>;
49
+ /**
50
+ * When set, the platform OS hint is forced to this value, bypassing
51
+ * `ctx.device.getInfo().os` lookup. Tests use this to drive the
52
+ * "Android-only media-scan" branch deterministically.
53
+ */
54
+ forceDeviceType?: string;
55
+ }
56
+ export declare class UrlResolver implements ResourceResolver {
57
+ readonly name = "url";
58
+ private readonly fetchImpl;
59
+ private readonly dnsLookup;
60
+ private readonly forceDeviceType?;
61
+ constructor(opts?: UrlResolverOptions);
62
+ match(ref: ResourceRef): boolean;
63
+ resolve(ref: ResourceRef, ctx: ResolverContext): Promise<ResolvedAsset>;
64
+ private doResolve;
65
+ private detectPlatform;
66
+ }
67
+ /**
68
+ * RFC 1918 / RFC 4193 / loopback / link-local / multicast / etc check.
69
+ * We deliberately keep this exhaustive rather than rely on a third-party
70
+ * package — the SSRF surface is small and security-critical.
71
+ *
72
+ * IPv4 loopback — 127.0.0.0/8
73
+ * IPv4 RFC1918 private — 10/8, 172.16/12, 192.168/16
74
+ * IPv4 link-local — 169.254/16
75
+ * IPv4 CGNAT — 100.64/10
76
+ * IPv4 broadcast / multicast — 224/4, 255.255.255.255
77
+ * IPv4 0.0.0.0/8 — "this network"
78
+ * IPv6 loopback — ::1
79
+ * IPv6 link-local — fe80::/10
80
+ * IPv6 ULA — fc00::/7
81
+ * IPv6 multicast — ff00::/8
82
+ * IPv6 unspecified — ::
83
+ * IPv4-mapped IPv6 — ::ffff:0:0/96 (mapped to v4 above)
84
+ */
85
+ export declare function isPrivateOrLoopback(addr: string, family: number): boolean;