@humain/terminal 0.1.1 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (193) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/bundle/chunks/{anthropic-messages-XULXUBIG.js → anthropic-messages-QFCEKGI3.js} +1 -1
  3. package/dist/bundle/chunks/{azure-openai-responses-JEG2XOYC.js → azure-openai-responses-G4ZMHV37.js} +1 -1
  4. package/dist/bundle/chunks/chunk-7T6XCLJF.js +22 -0
  5. package/dist/bundle/chunks/chunk-D6HHRSB5.js +103 -0
  6. package/dist/bundle/chunks/chunk-EY4DWHQU.js +2 -0
  7. package/dist/bundle/chunks/{chunk-LYAMSPR2.js → chunk-I47ZWR55.js} +483 -506
  8. package/dist/bundle/chunks/{chunk-JUPSJTFQ.js → chunk-K4JAZYQH.js} +1 -1
  9. package/dist/bundle/chunks/{chunk-HPPQHK3J.js → chunk-KKIB2TVR.js} +1 -1
  10. package/dist/bundle/chunks/chunk-LI2YVPWP.js +2 -0
  11. package/dist/bundle/chunks/{chunk-BZQ53VGE.js → chunk-XSR434CO.js} +1 -1
  12. package/dist/bundle/chunks/compat-QCV26HNS.js +2 -0
  13. package/dist/bundle/chunks/dist-PRPIUY3U.js +2 -0
  14. package/dist/bundle/chunks/esm-E7UVEYTO.js +4 -0
  15. package/dist/bundle/chunks/{extract-TRW5FBY6.js → extract-CTIFQZDJ.js} +15 -14
  16. package/dist/bundle/chunks/{google-generative-ai-OX6VSCA6.js → google-generative-ai-RSUU4MWO.js} +1 -1
  17. package/dist/bundle/chunks/{google-vertex-GW76GSS2.js → google-vertex-CKQPOITO.js} +1 -1
  18. package/dist/bundle/chunks/host-WP3MVKS2.js +2 -0
  19. package/dist/bundle/chunks/{node-KOIP55YZ.js → node-NF43OPII.js} +1 -1
  20. package/dist/bundle/chunks/{node-XY3CBY3C.js → node-UMFM7CDZ.js} +1 -1
  21. package/dist/bundle/chunks/{openai-codex-responses-NPLWP6ZC.js → openai-codex-responses-S34V5ZSD.js} +1 -1
  22. package/dist/bundle/chunks/{openai-completions-OT2GTJKC.js → openai-completions-T2IGVUEI.js} +1 -1
  23. package/dist/bundle/chunks/{openai-responses-5A6CR7GI.js → openai-responses-GYZODCT3.js} +1 -1
  24. package/dist/bundle/chunks/readability-LAGF34IB.js +3 -0
  25. package/dist/bundle/chunks/turndown.cjs-HRMZVRUL.js +2 -0
  26. package/dist/bundle/cli.js +1 -1
  27. package/dist/bundle/index.js +1 -1
  28. package/dist/bundle/rpc-entry.js +1 -1
  29. package/dist/core/tools/file-mutation-queue.d.ts.map +1 -1
  30. package/dist/core/tools/file-mutation-queue.js +21 -8
  31. package/dist/core/tools/file-mutation-queue.js.map +1 -1
  32. package/dist/core/tools/subagent-env.d.ts +18 -0
  33. package/dist/core/tools/subagent-env.d.ts.map +1 -0
  34. package/dist/core/tools/subagent-env.js +43 -0
  35. package/dist/core/tools/subagent-env.js.map +1 -0
  36. package/dist/core/tools/subagent-scheduler.d.ts +31 -0
  37. package/dist/core/tools/subagent-scheduler.d.ts.map +1 -0
  38. package/dist/core/tools/subagent-scheduler.js +203 -0
  39. package/dist/core/tools/subagent-scheduler.js.map +1 -0
  40. package/dist/core/tools/subagent.d.ts +83 -3
  41. package/dist/core/tools/subagent.d.ts.map +1 -1
  42. package/dist/core/tools/subagent.js +214 -76
  43. package/dist/core/tools/subagent.js.map +1 -1
  44. package/dist/humain/mcp-adapter/vendor/CHANGELOG.md +78 -0
  45. package/dist/humain/mcp-adapter/vendor/README.md +125 -19
  46. package/dist/humain/mcp-adapter/vendor/agent-plugin-loader.ts +69 -30
  47. package/dist/humain/mcp-adapter/vendor/agent-plugin-provenance.ts +36 -0
  48. package/dist/humain/mcp-adapter/vendor/claude-plugin-loader.ts +365 -0
  49. package/dist/humain/mcp-adapter/vendor/commands.ts +13 -15
  50. package/dist/humain/mcp-adapter/vendor/config.ts +198 -30
  51. package/dist/humain/mcp-adapter/vendor/consent-manager.ts +40 -9
  52. package/dist/humain/mcp-adapter/vendor/direct-tool-surface.ts +250 -0
  53. package/dist/humain/mcp-adapter/vendor/direct-tools.ts +9 -228
  54. package/dist/humain/mcp-adapter/vendor/dist/agent-plugin-loader.js +74 -32
  55. package/dist/humain/mcp-adapter/vendor/dist/agent-plugin-loader.js.map +1 -1
  56. package/dist/humain/mcp-adapter/vendor/dist/agent-plugin-provenance.d.ts +1 -0
  57. package/dist/humain/mcp-adapter/vendor/dist/agent-plugin-provenance.js +31 -0
  58. package/dist/humain/mcp-adapter/vendor/dist/agent-plugin-provenance.js.map +1 -0
  59. package/dist/humain/mcp-adapter/vendor/dist/claude-plugin-loader.d.ts +9 -0
  60. package/dist/humain/mcp-adapter/vendor/dist/claude-plugin-loader.js +362 -0
  61. package/dist/humain/mcp-adapter/vendor/dist/claude-plugin-loader.js.map +1 -0
  62. package/dist/humain/mcp-adapter/vendor/dist/config.d.ts +3 -1
  63. package/dist/humain/mcp-adapter/vendor/dist/config.js +189 -27
  64. package/dist/humain/mcp-adapter/vendor/dist/config.js.map +1 -1
  65. package/dist/humain/mcp-adapter/vendor/dist/metadata-cache.js +7 -16
  66. package/dist/humain/mcp-adapter/vendor/dist/metadata-cache.js.map +1 -1
  67. package/dist/humain/mcp-adapter/vendor/dist/package-mcp-loader.js +5 -17
  68. package/dist/humain/mcp-adapter/vendor/dist/package-mcp-loader.js.map +1 -1
  69. package/dist/humain/mcp-adapter/vendor/dist/types.d.ts +30 -4
  70. package/dist/humain/mcp-adapter/vendor/dist/types.js +26 -0
  71. package/dist/humain/mcp-adapter/vendor/dist/types.js.map +1 -1
  72. package/dist/humain/mcp-adapter/vendor/dist/utils.d.ts +6 -0
  73. package/dist/humain/mcp-adapter/vendor/dist/utils.js +65 -6
  74. package/dist/humain/mcp-adapter/vendor/dist/utils.js.map +1 -1
  75. package/dist/humain/mcp-adapter/vendor/host-html-template.ts +20 -19
  76. package/dist/humain/mcp-adapter/vendor/http-ca.ts +84 -0
  77. package/dist/humain/mcp-adapter/vendor/index.bundle.mjs +37102 -15103
  78. package/dist/humain/mcp-adapter/vendor/index.ts +828 -164
  79. package/dist/humain/mcp-adapter/vendor/init.ts +48 -10
  80. package/dist/humain/mcp-adapter/vendor/lazy-loader.ts +7 -0
  81. package/dist/humain/mcp-adapter/vendor/mcp-auth-fetch.ts +113 -0
  82. package/dist/humain/mcp-adapter/vendor/mcp-auth-flow.ts +256 -77
  83. package/dist/humain/mcp-adapter/vendor/mcp-auth.ts +316 -54
  84. package/dist/humain/mcp-adapter/vendor/mcp-code.ts +45 -18
  85. package/dist/humain/mcp-adapter/vendor/mcp-install.ts +60 -0
  86. package/dist/humain/mcp-adapter/vendor/mcp-oauth-provider.ts +70 -6
  87. package/dist/humain/mcp-adapter/vendor/mcp-output-guard.ts +53 -42
  88. package/dist/humain/mcp-adapter/vendor/mcp-panel-theme.ts +105 -0
  89. package/dist/humain/mcp-adapter/vendor/mcp-panel.ts +366 -331
  90. package/dist/humain/mcp-adapter/vendor/mcp-references.ts +9 -14
  91. package/dist/humain/mcp-adapter/vendor/mcp-script-worker.mjs +3 -1
  92. package/dist/humain/mcp-adapter/vendor/mcp-setup-panel.ts +383 -341
  93. package/dist/humain/mcp-adapter/vendor/mcp-status.ts +5 -0
  94. package/dist/humain/mcp-adapter/vendor/metadata-cache.ts +7 -16
  95. package/dist/humain/mcp-adapter/vendor/namespace-tools.ts +61 -3
  96. package/dist/humain/mcp-adapter/vendor/oauth.ts +6 -2
  97. package/dist/humain/mcp-adapter/vendor/package-mcp-loader.ts +5 -17
  98. package/dist/humain/mcp-adapter/vendor/package.json +13 -3
  99. package/dist/humain/mcp-adapter/vendor/prompts.ts +35 -3
  100. package/dist/humain/mcp-adapter/vendor/proxy-modes.ts +108 -7
  101. package/dist/humain/mcp-adapter/vendor/runtime-owner.ts +16 -1
  102. package/dist/humain/mcp-adapter/vendor/sampling-handler.ts +9 -32
  103. package/dist/humain/mcp-adapter/vendor/sandbox-proxy-template.ts +20 -93
  104. package/dist/humain/mcp-adapter/vendor/server-manager.ts +227 -40
  105. package/dist/humain/mcp-adapter/vendor/session-approvals.ts +187 -0
  106. package/dist/humain/mcp-adapter/vendor/skills/mcp-scripting/SKILL.md +9 -1
  107. package/dist/humain/mcp-adapter/vendor/state.ts +10 -1
  108. package/dist/humain/mcp-adapter/vendor/tool-approval.ts +10 -22
  109. package/dist/humain/mcp-adapter/vendor/tool-metadata.ts +14 -0
  110. package/dist/humain/mcp-adapter/vendor/types.ts +57 -4
  111. package/dist/humain/mcp-adapter/vendor/ui-server.ts +36 -28
  112. package/dist/humain/mcp-adapter/vendor/utils.ts +64 -6
  113. package/dist/humain/web-access/vendor/abortable.d.ts +3 -0
  114. package/dist/humain/web-access/vendor/abortable.d.ts.map +1 -0
  115. package/dist/humain/web-access/vendor/abortable.js +22 -0
  116. package/dist/humain/web-access/vendor/abortable.js.map +1 -0
  117. package/dist/humain/web-access/vendor/crawl4ai.d.ts +9 -0
  118. package/dist/humain/web-access/vendor/crawl4ai.d.ts.map +1 -0
  119. package/dist/humain/web-access/vendor/crawl4ai.js +187 -0
  120. package/dist/humain/web-access/vendor/crawl4ai.js.map +1 -0
  121. package/dist/humain/web-access/vendor/credential-source.d.ts.map +1 -1
  122. package/dist/humain/web-access/vendor/credential-source.js +1 -0
  123. package/dist/humain/web-access/vendor/credential-source.js.map +1 -1
  124. package/dist/humain/web-access/vendor/curator-page.d.ts.map +1 -1
  125. package/dist/humain/web-access/vendor/curator-page.js +20 -2
  126. package/dist/humain/web-access/vendor/curator-page.js.map +1 -1
  127. package/dist/humain/web-access/vendor/curator-run.d.ts +11 -0
  128. package/dist/humain/web-access/vendor/curator-run.d.ts.map +1 -0
  129. package/dist/humain/web-access/vendor/curator-run.js +38 -0
  130. package/dist/humain/web-access/vendor/curator-run.js.map +1 -0
  131. package/dist/humain/web-access/vendor/curator-server.d.ts +1 -0
  132. package/dist/humain/web-access/vendor/curator-server.d.ts.map +1 -1
  133. package/dist/humain/web-access/vendor/curator-server.js +2 -0
  134. package/dist/humain/web-access/vendor/curator-server.js.map +1 -1
  135. package/dist/humain/web-access/vendor/duckduckgo.d.ts.map +1 -1
  136. package/dist/humain/web-access/vendor/duckduckgo.js +1 -1
  137. package/dist/humain/web-access/vendor/duckduckgo.js.map +1 -1
  138. package/dist/humain/web-access/vendor/extract.d.ts +1 -1
  139. package/dist/humain/web-access/vendor/extract.d.ts.map +1 -1
  140. package/dist/humain/web-access/vendor/extract.js +58 -11
  141. package/dist/humain/web-access/vendor/extract.js.map +1 -1
  142. package/dist/humain/web-access/vendor/fetch-params.js +1 -1
  143. package/dist/humain/web-access/vendor/fetch-params.js.map +1 -1
  144. package/dist/humain/web-access/vendor/gemini-search.d.ts +2 -2
  145. package/dist/humain/web-access/vendor/gemini-search.d.ts.map +1 -1
  146. package/dist/humain/web-access/vendor/gemini-search.js +12 -5
  147. package/dist/humain/web-access/vendor/gemini-search.js.map +1 -1
  148. package/dist/humain/web-access/vendor/index.d.ts.map +1 -1
  149. package/dist/humain/web-access/vendor/index.js +23 -19
  150. package/dist/humain/web-access/vendor/index.js.map +1 -1
  151. package/dist/humain/web-access/vendor/openai-search.d.ts.map +1 -1
  152. package/dist/humain/web-access/vendor/openai-search.js +40 -14
  153. package/dist/humain/web-access/vendor/openai-search.js.map +1 -1
  154. package/dist/humain/web-access/vendor/page-query.d.ts.map +1 -1
  155. package/dist/humain/web-access/vendor/page-query.js +6 -2
  156. package/dist/humain/web-access/vendor/page-query.js.map +1 -1
  157. package/dist/humain/web-access/vendor/query-rewrite.d.ts.map +1 -1
  158. package/dist/humain/web-access/vendor/query-rewrite.js +6 -2
  159. package/dist/humain/web-access/vendor/query-rewrite.js.map +1 -1
  160. package/dist/humain/web-access/vendor/serpapi.d.ts +4 -0
  161. package/dist/humain/web-access/vendor/serpapi.d.ts.map +1 -0
  162. package/dist/humain/web-access/vendor/serpapi.js +215 -0
  163. package/dist/humain/web-access/vendor/serpapi.js.map +1 -0
  164. package/dist/humain/web-access/vendor/ssrf-protection.d.ts.map +1 -1
  165. package/dist/humain/web-access/vendor/ssrf-protection.js +5 -1
  166. package/dist/humain/web-access/vendor/ssrf-protection.js.map +1 -1
  167. package/dist/humain/web-access/vendor/storage.d.ts.map +1 -1
  168. package/dist/humain/web-access/vendor/storage.js +9 -0
  169. package/dist/humain/web-access/vendor/storage.js.map +1 -1
  170. package/dist/humain/web-access/vendor/summary-review.d.ts +1 -1
  171. package/dist/humain/web-access/vendor/summary-review.d.ts.map +1 -1
  172. package/dist/humain/web-access/vendor/summary-review.js +35 -24
  173. package/dist/humain/web-access/vendor/summary-review.js.map +1 -1
  174. package/dist/humain/web-access/vendor/utils.d.ts.map +1 -1
  175. package/dist/humain/web-access/vendor/utils.js +5 -3
  176. package/dist/humain/web-access/vendor/utils.js.map +1 -1
  177. package/dist/modes/json-event.d.ts +8 -0
  178. package/dist/modes/json-event.d.ts.map +1 -1
  179. package/dist/modes/json-event.js +17 -0
  180. package/dist/modes/json-event.js.map +1 -1
  181. package/dist/modes/print-mode.d.ts.map +1 -1
  182. package/dist/modes/print-mode.js +3 -2
  183. package/dist/modes/print-mode.js.map +1 -1
  184. package/docs/environment-variables.md +8 -0
  185. package/docs/json.md +16 -0
  186. package/npm-shrinkwrap.json +6 -6
  187. package/package.json +2 -2
  188. package/src/humain/mcp-adapter/UPSTREAM.md +56 -7
  189. package/src/humain/mcp-adapter/upstream.json +6 -4
  190. package/src/humain/mcp-adapter/vendor/CHANGELOG.md +78 -0
  191. package/src/humain/web-access/UPSTREAM.md +18 -8
  192. package/src/humain/web-access/upstream.json +6 -5
  193. package/dist/bundle/chunks/chunk-2LC3S3VV.js +0 -105
@@ -52,7 +52,22 @@ export function combineAbortSignals(...signals: Array<AbortSignal | undefined>):
52
52
  const active = signals.filter((signal): signal is AbortSignal => signal !== undefined);
53
53
  if (active.length === 0) return undefined;
54
54
  if (active.length === 1) return active[0];
55
- return AbortSignal.any(active);
55
+ if (typeof AbortSignal.any === "function") return AbortSignal.any(active);
56
+
57
+ // AbortSignal.any was added after Node 20.0.0, our minimum supported version.
58
+ const controller = new AbortController();
59
+ const alreadyAborted = active.find(signal => signal.aborted);
60
+ if (alreadyAborted) {
61
+ controller.abort(alreadyAborted.reason);
62
+ return controller.signal;
63
+ }
64
+
65
+ const onAbort = (event: Event) => {
66
+ for (const signal of active) signal.removeEventListener("abort", onAbort);
67
+ controller.abort((event.target as AbortSignal).reason);
68
+ };
69
+ for (const signal of active) signal.addEventListener("abort", onAbort, { once: true });
70
+ return controller.signal;
56
71
  }
57
72
 
58
73
  /** Fence session-bound UI calls after the owning extension runtime stops. */
@@ -1,5 +1,4 @@
1
- import { complete } from "@earendil-works/pi-ai/compat";
2
- import type { Api, AssistantMessage, Message, Model, ProviderHeaders, TextContent } from "@earendil-works/pi-ai";
1
+ import type { Api, AssistantMessage, Message, Model, TextContent } from "@earendil-works/pi-ai";
3
2
  import { truncateAtWord } from "./utils.ts";
4
3
  import { throwIfAborted } from "./abort.ts";
5
4
  import type { ExtensionUIContext, ModelRegistry } from "@earendil-works/pi-coding-agent";
@@ -13,7 +12,7 @@ import {
13
12
  } from "@modelcontextprotocol/client";
14
13
 
15
14
  export type SamplingUIContext = Pick<ExtensionUIContext, "confirm">;
16
- export type SamplingModelRegistry = Pick<ModelRegistry, "getAvailable" | "getApiKeyAndHeaders">;
15
+ export type SamplingModelRegistry = Pick<ModelRegistry, "getAvailable" | "complete">;
17
16
 
18
17
  export interface SamplingHandlerOptions {
19
18
  serverName: string;
@@ -57,7 +56,7 @@ export async function handleSamplingRequest(
57
56
  }
58
57
 
59
58
  const messages = params.messages.map(convertSamplingMessage);
60
- const { model, apiKey, headers } = await resolveSamplingModel(options, params.modelPreferences);
59
+ const model = resolveSamplingModel(options, params.modelPreferences);
61
60
  throwIfAborted(signal);
62
61
  await confirmSampling(
63
62
  options,
@@ -66,15 +65,13 @@ export async function handleSamplingRequest(
66
65
  );
67
66
  throwIfAborted(signal);
68
67
 
69
- const result = await complete(
68
+ const result = await options.modelRegistry.complete(
70
69
  model,
71
70
  {
72
71
  ...(params.systemPrompt !== undefined ? { systemPrompt: params.systemPrompt } : {}),
73
72
  messages,
74
73
  },
75
74
  {
76
- ...(apiKey !== undefined ? { apiKey } : {}),
77
- ...(headers !== undefined ? { headers } : {}),
78
75
  maxTokens: params.maxTokens,
79
76
  ...(params.temperature !== undefined ? { temperature: params.temperature } : {}),
80
77
  ...(params.metadata !== undefined ? { metadata: params.metadata as Record<string, unknown> } : {}),
@@ -124,14 +121,10 @@ function messageText(message: Message): string {
124
121
  }).join("\n");
125
122
  }
126
123
 
127
- async function resolveSamplingModel(
124
+ function resolveSamplingModel(
128
125
  options: SamplingHandlerOptions,
129
126
  modelPreferences: ModelPreferences | undefined,
130
- ): Promise<{
131
- model: Model<Api>;
132
- apiKey?: string;
133
- headers?: ProviderHeaders;
134
- }> {
127
+ ): Model<Api> {
135
128
  const candidates: Model<Api>[] = [];
136
129
  const availableModels = options.modelRegistry.getAvailable();
137
130
 
@@ -153,26 +146,10 @@ async function resolveSamplingModel(
153
146
  addSamplingCandidate(candidates, model);
154
147
  }
155
148
 
156
- const errors: string[] = [];
157
149
  const signal = options.getSignal();
158
- for (const model of candidates) {
159
- throwIfAborted(signal);
160
- const auth = await options.modelRegistry.getApiKeyAndHeaders(model);
161
- throwIfAborted(signal);
162
- if (auth.ok === false) {
163
- errors.push(`${model.provider}/${model.id}: ${auth.error}`);
164
- continue;
165
- }
166
- return {
167
- model,
168
- ...(auth.apiKey !== undefined ? { apiKey: auth.apiKey } : {}),
169
- ...(auth.headers !== undefined ? { headers: auth.headers } : {}),
170
- };
171
- }
172
-
173
- if (errors.length > 0) {
174
- throw new Error(`No configured auth for MCP sampling model. ${errors.join("; ")}`);
175
- }
150
+ throwIfAborted(signal);
151
+ const model = candidates[0];
152
+ if (model) return model;
176
153
  throw new Error("No Pi model is available for MCP sampling");
177
154
  }
178
155
 
@@ -7,17 +7,22 @@ export const SANDBOX_PROXY_SANDBOX =
7
7
  "allow-scripts allow-forms allow-modals allow-popups allow-downloads allow-same-origin";
8
8
  export const SANDBOX_INNER_SANDBOX = SANDBOX_PROXY_SANDBOX;
9
9
  export const SANDBOX_PROXY_PATH = "/sandbox";
10
+ export const SANDBOX_RESOURCE_PATH_PREFIX = "/resource/";
10
11
 
11
12
  export interface SandboxProxyTemplateInput {
12
13
  parentOrigin: string;
14
+ resourcePath: string;
15
+ allowAttribute: string;
13
16
  }
14
17
 
15
18
  /**
16
19
  * Build the static document used by the trusted, second-origin sandbox proxy.
17
- * The only per-session value is the exact origin of the parent host.
20
+ * The per-session values are the exact parent host origin and opaque resource path.
18
21
  */
19
22
  export function buildSandboxProxyHtml(input: SandboxProxyTemplateInput): string {
20
23
  const parentOrigin = safeInlineJSON(input.parentOrigin);
24
+ const resourcePath = safeInlineJSON(input.resourcePath);
25
+ const allowAttribute = escapeHtmlAttribute(input.allowAttribute);
21
26
 
22
27
  return `<!doctype html>
23
28
  <html lang="en">
@@ -31,17 +36,21 @@ export function buildSandboxProxyHtml(input: SandboxProxyTemplateInput): string
31
36
  </style>
32
37
  </head>
33
38
  <body>
34
- <iframe id="mcp-app" title="MCP App" sandbox="${SANDBOX_INNER_SANDBOX}" referrerpolicy="no-referrer"></iframe>
39
+ <iframe id="mcp-app" title="MCP App" sandbox="${SANDBOX_INNER_SANDBOX}" allow="${allowAttribute}" referrerpolicy="no-referrer"></iframe>
35
40
  <script>
36
41
  const EXPECTED_PARENT_ORIGIN = ${parentOrigin};
42
+ const RESOURCE_PATH = ${resourcePath};
37
43
  const SANDBOX_PROXY_READY_METHOD = "ui/notifications/sandbox-proxy-ready";
38
44
  const SANDBOX_RESOURCE_READY_METHOD = "ui/notifications/sandbox-resource-ready";
39
- const INNER_SANDBOX = ${safeInlineJSON(SANDBOX_INNER_SANDBOX)};
40
45
  const MAX_PENDING_MESSAGES = 64;
41
46
  const innerFrame = document.getElementById("mcp-app");
42
47
  const pendingToInner = [];
43
48
  let innerReady = false;
44
49
 
50
+ innerFrame.addEventListener("load", () => {
51
+ if (innerFrame.getAttribute("src") === RESOURCE_PATH) flushPendingMessages();
52
+ });
53
+
45
54
  const isObject = (value) => value !== null && typeof value === "object";
46
55
  const isMessageFromParent = (event) =>
47
56
  event.source === window.parent && event.origin === EXPECTED_PARENT_ORIGIN;
@@ -68,99 +77,13 @@ export function buildSandboxProxyHtml(input: SandboxProxyTemplateInput): string
68
77
  }
69
78
  };
70
79
 
71
- const sanitizeDomains = (domains) => {
72
- if (!Array.isArray(domains)) return [];
73
- return [...new Set(domains.filter((domain) =>
74
- typeof domain === "string" &&
75
- domain.length > 0 &&
76
- /^[\\x21-\\x7E]+$/.test(domain) &&
77
- !/[;'\"]/.test(domain),
78
- ))];
79
- };
80
-
81
- const toDirective = (name, trustedSources, domains) =>
82
- name + " " + [...new Set([...trustedSources, ...domains])].join(" ");
83
-
84
- const buildResourceCsp = (csp) => {
85
- const resourceDomains = sanitizeDomains(csp?.resourceDomains);
86
- const connectDomains = sanitizeDomains(csp?.connectDomains);
87
- const frameDomains = sanitizeDomains(csp?.frameDomains);
88
- const baseUriDomains = sanitizeDomains(csp?.baseUriDomains);
89
- return [
90
- "default-src 'none'",
91
- "sandbox " + INNER_SANDBOX,
92
- toDirective("script-src", ["'self'", "'unsafe-inline'"], resourceDomains),
93
- toDirective("style-src", ["'self'", "'unsafe-inline'"], resourceDomains),
94
- toDirective("font-src", ["'self'"], resourceDomains),
95
- toDirective("img-src", ["'self'", "data:"], resourceDomains),
96
- toDirective("media-src", ["'self'", "data:"], resourceDomains),
97
- connectDomains.length > 0 ? "connect-src " + connectDomains.join(" ") : "connect-src 'none'",
98
- frameDomains.length > 0 ? "frame-src " + frameDomains.join(" ") : "frame-src 'none'",
99
- "worker-src 'none'",
100
- "object-src 'none'",
101
- baseUriDomains.length > 0 ? "base-uri " + baseUriDomains.join(" ") : "base-uri 'self'",
102
- ].join("; ");
103
- };
104
-
105
- const escapeAttribute = (value) => value
106
- .replace(/&/g, "&amp;")
107
- .replace(/\"/g, "&quot;")
108
- .replace(/</g, "&lt;")
109
- .replace(/>/g, "&gt;");
110
-
111
- const injectCsp = (html, csp) => {
112
- const meta = '<meta http-equiv="Content-Security-Policy" content="' + escapeAttribute(csp) + '">';
113
- const head = html.match(/<head(?:\\s[^>]*)?>/i);
114
- if (head && head.index !== undefined) {
115
- const end = head.index + head[0].length;
116
- return html.slice(0, end) + meta + html.slice(end);
117
- }
118
- return meta + html;
119
- };
120
-
121
- const safeSandbox = (requested) => {
122
- const requestedTokens = typeof requested === "string" ? requested.split(/\\s+/) : [];
123
- const allowedTokens = new Set(INNER_SANDBOX.split(" "));
124
- const tokens = requestedTokens.filter((token) => allowedTokens.has(token));
125
- // Provider HTML needs both script execution and a real proxy-origin
126
- // storage context. Never accept popup escape or top-navigation tokens.
127
- return [...new Set([
128
- "allow-scripts",
129
- "allow-same-origin",
130
- ...tokens,
131
- ])].filter((token) => allowedTokens.has(token)).join(" ");
132
- };
133
-
134
- const buildAllowAttribute = (permissions) => {
135
- if (!isObject(permissions) || Array.isArray(permissions)) return "";
136
- const allowed = [];
137
- if (permissions.camera) allowed.push("camera");
138
- if (permissions.microphone) allowed.push("microphone");
139
- if (permissions.geolocation) allowed.push("geolocation");
140
- if (permissions.clipboardWrite) allowed.push("clipboard-write");
141
- return allowed.join("; ");
142
- };
143
-
144
- const loadResource = (params) => {
145
- if (!isObject(params) || typeof params.html !== "string" || !innerFrame.contentDocument) return;
146
- innerFrame.setAttribute("sandbox", safeSandbox(params.sandbox));
147
- const allow = buildAllowAttribute(params.permissions);
148
- if (allow) innerFrame.setAttribute("allow", allow);
149
- else innerFrame.removeAttribute("allow");
150
- innerReady = false;
151
- const document = innerFrame.contentDocument;
152
- document.open();
153
- document.write(injectCsp(params.html, buildResourceCsp(params.csp)));
154
- document.close();
155
- flushPendingMessages();
156
- };
157
-
158
80
  window.addEventListener("message", (event) => {
159
81
  if (isMessageFromParent(event)) {
160
82
  const data = event.data;
161
83
  if (!isObject(data)) return;
162
84
  if (data.method === SANDBOX_RESOURCE_READY_METHOD) {
163
- loadResource(data.params);
85
+ innerReady = false;
86
+ innerFrame.setAttribute("src", RESOURCE_PATH);
164
87
  return;
165
88
  }
166
89
  if (data.method === SANDBOX_PROXY_READY_METHOD) return;
@@ -186,10 +109,14 @@ export function buildSandboxProxyHtml(input: SandboxProxyTemplateInput): string
186
109
  </html>`;
187
110
  }
188
111
 
112
+ function escapeHtmlAttribute(value: string): string {
113
+ return value.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
114
+ }
115
+
189
116
  /**
190
117
  * CSP for the proxy document itself. It contains only the inline relay script
191
- * and a nested about:blank frame; provider policy is applied separately after
192
- * the host sends the resource-ready notification.
118
+ * and a same-origin nested frame. Provider policy is applied on the resource
119
+ * navigation response, not inherited from this relay document.
193
120
  */
194
121
  export function buildSandboxProxyCsp(): string {
195
122
  return [