@wenathlan/devthink 2.0.14 → 2.0.19

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 (444) hide show
  1. package/CHANGELOG.md +60 -0
  2. package/{dist/bridge.d.ts → bridge.d.ts} +1 -1
  3. package/bridge.d.ts.map +1 -0
  4. package/bridge.js +1877 -0
  5. package/bun.d.ts.map +1 -0
  6. package/bun.js +52329 -0
  7. package/caps/background.json +712 -0
  8. package/caps/cli.json +360 -0
  9. package/caps/library.json +3611 -0
  10. package/caps/mcp.json +126 -0
  11. package/caps/pagebridge.json +377 -0
  12. package/caps/popup.json +51 -0
  13. package/caps/sidepanel.json +522 -0
  14. package/checksums.txt +142 -0
  15. package/cli.d.ts.map +1 -0
  16. package/{dist/cli.js → cli.js} +2 -2
  17. package/companion.d.ts.map +1 -0
  18. package/companion.js +389 -0
  19. package/crossbrowser.d.ts.map +1 -0
  20. package/crossbrowser.js +1191 -0
  21. package/dashdone.d.ts.map +1 -0
  22. package/dashdone.js +368 -0
  23. package/deno.d.ts.map +1 -0
  24. package/deno.js +52325 -0
  25. package/devthink.umd.js +52346 -0
  26. package/fixtures/bun.ts +7 -0
  27. package/fixtures/cjs.js +6 -0
  28. package/fixtures/deno.ts +6 -0
  29. package/fixtures/example-org-pagestate.json +22 -0
  30. package/fixtures/importers/automa-workflow.json +18 -0
  31. package/fixtures/importers/selenium-side.json +20 -0
  32. package/fixtures/importers/tabular-plan.csv +7 -0
  33. package/fixtures/importers/uivision-macro.json +10 -0
  34. package/fixtures/importers/v1-plan.json +13 -0
  35. package/fixtures/mcp-client.mjs +14 -0
  36. package/fixtures/pages/checkout.html +192 -0
  37. package/fixtures/pages/dashboard.html +110 -0
  38. package/fixtures/pages/feed.html +113 -0
  39. package/fixtures/pages/product-grid.html +150 -0
  40. package/fixtures/plans/form-inventory-plan.json +11 -0
  41. package/fixtures/plans/release-notes-plan.json +11 -0
  42. package/fixtures/recipes/agentscompete.json +11 -0
  43. package/fixtures/recipes/agentsforms.json +11 -0
  44. package/fixtures/recipes/agentsmonitor.json +11 -0
  45. package/fixtures/recipes/agentsreview.json +29 -0
  46. package/fixtures/recipes/agentswarm.json +11 -0
  47. package/fixtures/recipes/fillcalendar.json +12 -0
  48. package/fixtures/recipes/fillcheckout.json +13 -0
  49. package/fixtures/recipes/filldependentdropdowns.json +12 -0
  50. package/fixtures/recipes/fillfromcsv.json +13 -0
  51. package/fixtures/recipes/fillloginform.json +12 -0
  52. package/fixtures/recipes/fillmultistepwizard.json +13 -0
  53. package/fixtures/recipes/fillregistration.json +12 -0
  54. package/fixtures/recipes/fillsearchfilters.json +12 -0
  55. package/fixtures/recipes/filltypeahead.json +11 -0
  56. package/fixtures/recipes/monitoralerts.json +12 -0
  57. package/fixtures/recipes/monitoravailability.json +11 -0
  58. package/fixtures/recipes/monitornetworkquiet.json +12 -0
  59. package/fixtures/recipes/monitorpagechanges.json +11 -0
  60. package/fixtures/recipes/monitorpricechange.json +11 -0
  61. package/fixtures/recipes/monitorschedule.json +12 -0
  62. package/fixtures/recipes/scrapedynamicprices.json +13 -0
  63. package/fixtures/recipes/scrapeimagegallery.json +12 -0
  64. package/fixtures/recipes/scrapeinfinitefeed.json +13 -0
  65. package/fixtures/recipes/scrapenestedlists.json +12 -0
  66. package/fixtures/recipes/scrapeproductgrid.json +12 -0
  67. package/fixtures/recipes/scrapereviews.json +12 -0
  68. package/fixtures/recipes/scrapesearchresults.json +12 -0
  69. package/fixtures/recipes/scrapetablepagination.json +12 -0
  70. package/fixtures/recipes/testaccessibility.json +34 -0
  71. package/fixtures/recipes/testconsoleerrors.json +12 -0
  72. package/fixtures/recipes/testdeeplinks.json +12 -0
  73. package/fixtures/recipes/testformvalidation.json +13 -0
  74. package/fixtures/recipes/testlayoutshift.json +12 -0
  75. package/fixtures/recipes/testlinkcheck.json +12 -0
  76. package/fixtures/recipes/testloadtiming.json +12 -0
  77. package/fixtures/recipes/testvisualdiff.json +12 -0
  78. package/fixtures/workflows/changelog-digest-workflow.json +13 -0
  79. package/fixtures/workflows/form-survey-workflow.json +13 -0
  80. package/gallery.json +706 -0
  81. package/hardening.d.ts.map +1 -0
  82. package/hardening.js +188 -0
  83. package/headless.d.ts.map +1 -0
  84. package/headless.js +11522 -0
  85. package/http.d.ts.map +1 -0
  86. package/http.js +2165 -0
  87. package/index.cjs +54682 -0
  88. package/index.d.ts.map +1 -0
  89. package/index.js +52310 -0
  90. package/index.neutral.js +46343 -0
  91. package/{web/manifest.json → manifest.json} +1 -1
  92. package/mcp.d.ts.map +1 -0
  93. package/mcp.js +20515 -0
  94. package/memory.d.ts.map +1 -0
  95. package/{memory.ts → memory.js} +2310 -3834
  96. package/nativehost.template.json +10 -0
  97. package/node.cjs +54686 -0
  98. package/node.d.ts.map +1 -0
  99. package/pack.d.ts.map +1 -0
  100. package/pack.js +805 -0
  101. package/package.json +83 -176
  102. package/policy.d.ts.map +1 -0
  103. package/{policy.ts → policy.js} +4679 -5073
  104. package/progress.d.ts.map +1 -0
  105. package/progress.js +645 -0
  106. package/protocol.d.ts.map +1 -0
  107. package/protocol.js +8334 -0
  108. package/schemas/audit.schema.json +226 -0
  109. package/schemas/capability.schema.json +113 -0
  110. package/schemas/envelope.schema.json +162 -0
  111. package/schemas/memory.schema.json +51 -0
  112. package/schemas/observation.schema.json +113 -0
  113. package/schemas/plan.schema.json +208 -0
  114. package/schemas/progress.schema.json +126 -0
  115. package/schemas/proposal.schema.json +68 -0
  116. package/schemas/session.schema.json +82 -0
  117. package/schemas/tool.schema.json +94 -0
  118. package/server.d.ts.map +1 -0
  119. package/server.js +28877 -0
  120. package/umd-example.html +18 -0
  121. package/umd.d.ts.map +1 -0
  122. package/accounts.ts +0 -2087
  123. package/agent.ts +0 -2331
  124. package/antigravity.ts +0 -2139
  125. package/apifreeze.ts +0 -4146
  126. package/auth.ts +0 -642
  127. package/background.ts +0 -16837
  128. package/bridge.ts +0 -2080
  129. package/build.ts +0 -54
  130. package/bun.ts +0 -26
  131. package/capture.ts +0 -1614
  132. package/checksecrets.ts +0 -72
  133. package/cjs.ts +0 -9
  134. package/cli.ts +0 -3500
  135. package/commands.ts +0 -1101
  136. package/companion.ts +0 -450
  137. package/compatibility.ts +0 -37
  138. package/config.ts +0 -3137
  139. package/constants.ts +0 -943
  140. package/core.ts +0 -1473
  141. package/crossbrowser.ts +0 -1359
  142. package/dashdone.ts +0 -605
  143. package/data.ts +0 -943
  144. package/database.ts +0 -133
  145. package/debug.ts +0 -850
  146. package/deno.ts +0 -27
  147. package/devthink.ts +0 -707
  148. package/dist/accounts.d.ts +0 -409
  149. package/dist/agent.d.ts +0 -837
  150. package/dist/antigravity.d.ts +0 -380
  151. package/dist/apifreeze.d.ts +0 -113
  152. package/dist/auth.d.ts +0 -224
  153. package/dist/capture.d.ts +0 -595
  154. package/dist/cjs.d.ts +0 -6
  155. package/dist/commands.d.ts +0 -310
  156. package/dist/compatibility.d.ts +0 -17
  157. package/dist/config.d.ts +0 -1853
  158. package/dist/constants.d.ts +0 -637
  159. package/dist/core.d.ts +0 -361
  160. package/dist/data.d.ts +0 -330
  161. package/dist/database.d.ts +0 -24
  162. package/dist/debug.d.ts +0 -270
  163. package/dist/engine.d.ts +0 -74
  164. package/dist/environments.d.ts +0 -244
  165. package/dist/evidence.d.ts +0 -277
  166. package/dist/export.d.ts +0 -311
  167. package/dist/fingerprint.d.ts +0 -594
  168. package/dist/flow.d.ts +0 -300
  169. package/dist/gates.d.ts +0 -103
  170. package/dist/identity.d.ts +0 -51
  171. package/dist/ids.d.ts +0 -7
  172. package/dist/llm.d.ts +0 -652
  173. package/dist/models.d.ts +0 -1807
  174. package/dist/net.d.ts +0 -325
  175. package/dist/neutral.d.ts +0 -41
  176. package/dist/oauth.d.ts +0 -703
  177. package/dist/page.d.ts +0 -1166
  178. package/dist/perf.d.ts +0 -481
  179. package/dist/plan.d.ts +0 -134
  180. package/dist/project.d.ts +0 -395
  181. package/dist/providers.d.ts +0 -37
  182. package/dist/quota.d.ts +0 -453
  183. package/dist/recovery.d.ts +0 -621
  184. package/dist/request.d.ts +0 -1492
  185. package/dist/run.d.ts +0 -779
  186. package/dist/runtime.d.ts +0 -212
  187. package/dist/search.d.ts +0 -375
  188. package/dist/security.d.ts +0 -534
  189. package/dist/serve.d.ts +0 -136
  190. package/dist/session.d.ts +0 -296
  191. package/dist/storage.d.ts +0 -53
  192. package/dist/streaming.d.ts +0 -1119
  193. package/dist/swarm.d.ts +0 -328
  194. package/dist/tools.d.ts +0 -170
  195. package/dist/types.d.ts +0 -8357
  196. package/dist/utils.d.ts +0 -82
  197. package/dist/version.d.ts +0 -7
  198. package/dist/versionregistry.d.ts +0 -406
  199. package/dist/views.d.ts +0 -579
  200. package/dist/workbenchsession.d.ts +0 -69
  201. package/dist/workflow.d.ts +0 -649
  202. package/docs/readme.md +0 -92
  203. package/docs/schemas/antigravity.json +0 -192
  204. package/engine.ts +0 -1524
  205. package/environments.ts +0 -1005
  206. package/evidence.ts +0 -643
  207. package/export.ts +0 -889
  208. package/fingerprint.ts +0 -1458
  209. package/flow.ts +0 -1190
  210. package/gates.ts +0 -307
  211. package/gateway.ts +0 -34
  212. package/hardening.ts +0 -191
  213. package/headless.ts +0 -369
  214. package/http.ts +0 -2110
  215. package/identity.ts +0 -246
  216. package/ids.ts +0 -26
  217. package/index.ts +0 -369
  218. package/llm.ts +0 -2682
  219. package/mcp.ts +0 -1835
  220. package/models.ts +0 -3805
  221. package/modes.ts +0 -43
  222. package/net.ts +0 -1214
  223. package/neutral.ts +0 -362
  224. package/node.ts +0 -26
  225. package/oauth.ts +0 -2216
  226. package/offscreen.ts +0 -170
  227. package/pack.ts +0 -956
  228. package/page.ts +0 -7824
  229. package/pagebridge.ts +0 -1116
  230. package/perf.ts +0 -957
  231. package/plan.ts +0 -672
  232. package/plugin.ts +0 -76
  233. package/prisma.config.ts +0 -27
  234. package/progress.ts +0 -1308
  235. package/project.ts +0 -1291
  236. package/protocol.ts +0 -4187
  237. package/providers.ts +0 -246
  238. package/quota.ts +0 -1887
  239. package/recovery.ts +0 -2433
  240. package/request.ts +0 -4482
  241. package/run.ts +0 -2135
  242. package/runtime.ts +0 -526
  243. package/search.ts +0 -1760
  244. package/security.ts +0 -1673
  245. package/serve.ts +0 -419
  246. package/server.ts +0 -5538
  247. package/session.ts +0 -1162
  248. package/storage.ts +0 -182
  249. package/streaming.ts +0 -4101
  250. package/swarm.ts +0 -884
  251. package/sync.ts +0 -56
  252. package/tests/agentcert.test.ts +0 -128
  253. package/tests/agentfleet.test.ts +0 -214
  254. package/tests/agentreview.test.ts +0 -241
  255. package/tests/agentstream.test.ts +0 -335
  256. package/tests/agentwork.test.ts +0 -356
  257. package/tests/apifreeze.test.ts +0 -290
  258. package/tests/apimap.test.ts +0 -112
  259. package/tests/approvalgate.test.ts +0 -175
  260. package/tests/artifactmanifest.test.ts +0 -204
  261. package/tests/attentionfeed.test.ts +0 -163
  262. package/tests/backgroundruns.test.ts +0 -100
  263. package/tests/batchscheduling.test.ts +0 -131
  264. package/tests/blackboard.test.ts +0 -238
  265. package/tests/bridge.test.ts +0 -1088
  266. package/tests/browsercoverage.test.ts +0 -134
  267. package/tests/browserpolyfills.test.ts +0 -124
  268. package/tests/build.mjs +0 -1260
  269. package/tests/clientauth.test.ts +0 -223
  270. package/tests/clitools.test.ts +0 -578
  271. package/tests/confirmgates.test.ts +0 -190
  272. package/tests/containerpack.test.ts +0 -143
  273. package/tests/controlflow.test.ts +0 -2120
  274. package/tests/coordination.test.ts +0 -345
  275. package/tests/copyscreen.test.ts +0 -139
  276. package/tests/core.test.ts +0 -417
  277. package/tests/costcert.test.ts +0 -95
  278. package/tests/crossbrowser.test.ts +0 -115
  279. package/tests/cspaudit.test.ts +0 -79
  280. package/tests/dashdone.test.ts +0 -615
  281. package/tests/datagrid.test.ts +0 -137
  282. package/tests/doccheck.test.ts +0 -107
  283. package/tests/dombatch.test.ts +0 -109
  284. package/tests/emulation.test.ts +0 -282
  285. package/tests/environments.test.ts +0 -259
  286. package/tests/evidenceviews.test.ts +0 -88
  287. package/tests/exporttools.test.ts +0 -185
  288. package/tests/firefoxprep.test.ts +0 -134
  289. package/tests/flowlibrary.test.ts +0 -242
  290. package/tests/flowrun.test.ts +0 -303
  291. package/tests/forensics.test.ts +0 -439
  292. package/tests/gateway.test.ts +0 -939
  293. package/tests/headlesslib.test.ts +0 -171
  294. package/tests/httpstream.test.ts +0 -277
  295. package/tests/immutablelog.test.ts +0 -157
  296. package/tests/inboundguard.test.ts +0 -177
  297. package/tests/lazyload.test.ts +0 -72
  298. package/tests/librarymodes.test.ts +0 -623
  299. package/tests/llm.test.ts +0 -696
  300. package/tests/manifest.test.ts +0 -226
  301. package/tests/maskinputs.test.ts +0 -150
  302. package/tests/matrixverify.test.ts +0 -111
  303. package/tests/mavenpack.test.ts +0 -75
  304. package/tests/mcpserver.test.ts +0 -704
  305. package/tests/memory.test.ts +0 -4155
  306. package/tests/memorycare.test.ts +0 -230
  307. package/tests/migrateplan.test.ts +0 -349
  308. package/tests/minimization.test.ts +0 -414
  309. package/tests/modelroute.test.ts +0 -112
  310. package/tests/multiagent.test.ts +0 -440
  311. package/tests/nativebridge.test.ts +0 -807
  312. package/tests/navigation.test.ts +0 -284
  313. package/tests/nugetpack.test.ts +0 -83
  314. package/tests/orchestration.test.ts +0 -449
  315. package/tests/originpolicy.test.ts +0 -405
  316. package/tests/outputcompare.test.ts +0 -97
  317. package/tests/package.test.ts +0 -91
  318. package/tests/pagebridge.test.ts +0 -5909
  319. package/tests/pentest.test.ts +0 -75
  320. package/tests/perfrecords.test.ts +0 -257
  321. package/tests/permdiff.test.ts +0 -85
  322. package/tests/phishguard.test.ts +0 -128
  323. package/tests/pickerviews.test.ts +0 -155
  324. package/tests/pipelines.test.ts +0 -381
  325. package/tests/planlint.test.ts +0 -240
  326. package/tests/planreview.test.ts +0 -383
  327. package/tests/platformtargets.test.ts +0 -56
  328. package/tests/policy.test.ts +0 -8284
  329. package/tests/portability.test.ts +0 -96
  330. package/tests/progress.test.ts +0 -1036
  331. package/tests/promptlibrary.test.ts +0 -116
  332. package/tests/protocol.test.ts +0 -4793
  333. package/tests/publishingpipeline.test.ts +0 -314
  334. package/tests/quickactions.test.ts +0 -164
  335. package/tests/readiness.test.ts +0 -78
  336. package/tests/recipes.test.ts +0 -163
  337. package/tests/redactshots.test.ts +0 -216
  338. package/tests/registernvidiakeys.ts +0 -51
  339. package/tests/relayserve.test.ts +0 -304
  340. package/tests/releasenotes.test.ts +0 -276
  341. package/tests/resilience.test.ts +0 -467
  342. package/tests/resourceaware.test.ts +0 -187
  343. package/tests/runbudget.test.ts +0 -125
  344. package/tests/runhistory.test.ts +0 -299
  345. package/tests/runreplay.test.ts +0 -113
  346. package/tests/runresume.test.ts +0 -184
  347. package/tests/runstate.test.ts +0 -288
  348. package/tests/runtimeadapters.test.ts +0 -251
  349. package/tests/safariskeleton.test.ts +0 -115
  350. package/tests/sandboxframe.test.ts +0 -196
  351. package/tests/sbom.test.ts +0 -105
  352. package/tests/secretvault.test.ts +0 -188
  353. package/tests/selcache.test.ts +0 -111
  354. package/tests/sessioninterface.test.ts +0 -791
  355. package/tests/sessions.test.ts +0 -591
  356. package/tests/siteprefs.test.ts +0 -228
  357. package/tests/snapshotdelta.test.ts +0 -165
  358. package/tests/statusviews.test.ts +0 -237
  359. package/tests/stepmeter.test.ts +0 -200
  360. package/tests/streamparse.test.ts +0 -163
  361. package/tests/stylepruning.test.ts +0 -90
  362. package/tests/surfaces.test.ts +0 -282
  363. package/tests/sweep.test.ts +0 -78
  364. package/tests/syncbridge.test.ts +0 -164
  365. package/tests/taskqueue.test.ts +0 -206
  366. package/tests/toolcalls.test.ts +0 -336
  367. package/tests/toolcatalog.test.ts +0 -201
  368. package/tests/torture-agent.test.ts +0 -706
  369. package/tests/torture-auth.test.ts +0 -461
  370. package/tests/torture-commands.test.ts +0 -895
  371. package/tests/torture-data.test.ts +0 -923
  372. package/tests/torture-environments.test.ts +0 -285
  373. package/tests/torture-export.test.ts +0 -263
  374. package/tests/torture-http.test.ts +0 -1379
  375. package/tests/torture-llm.test.ts +0 -808
  376. package/tests/torture-memory.test.ts +0 -1084
  377. package/tests/torture-plan.test.ts +0 -829
  378. package/tests/torture-run.test.ts +0 -626
  379. package/tests/torture-session.test.ts +0 -928
  380. package/tests/torture-swarm.test.ts +0 -1245
  381. package/tests/torture-tools.test.ts +0 -964
  382. package/tests/torture-workflow.test.ts +0 -2354
  383. package/tests/tourviews.test.ts +0 -52
  384. package/tests/transparency.test.ts +0 -147
  385. package/tests/trigger.test.ts +0 -941
  386. package/tests/v1sunset.test.ts +0 -428
  387. package/tests/virtlist.test.ts +0 -94
  388. package/tests/vision.test.ts +0 -540
  389. package/tests/vsixpack.test.ts +0 -145
  390. package/tests/webapi.test.ts +0 -523
  391. package/tests/workflow.test.ts +0 -1785
  392. package/tests/workfloweditor.test.ts +0 -759
  393. package/tests/xpipack.test.ts +0 -128
  394. package/tools.ts +0 -734
  395. package/types.ts +0 -9908
  396. package/umd.ts +0 -9
  397. package/utils.ts +0 -249
  398. package/version.ts +0 -8
  399. package/versionregistry.ts +0 -1310
  400. package/views.ts +0 -2153
  401. package/vitest.config.ts +0 -12
  402. package/web/App.tsx +0 -78
  403. package/web/README.md +0 -47
  404. package/web/capacitor.config.ts +0 -46
  405. package/web/const.ts +0 -19
  406. package/web/control.shell.tsx +0 -66
  407. package/web/dashboardpage.ts +0 -554
  408. package/web/db.ts +0 -493
  409. package/web/design.html +0 -541
  410. package/web/gatewayview/Gateway.tsx +0 -345
  411. package/web/gatewayview/config.ts +0 -1042
  412. package/web/gatewayview/definition.ts +0 -174
  413. package/web/icons.ts +0 -15
  414. package/web/optionspage.ts +0 -691
  415. package/web/popup.ts +0 -1651
  416. package/web/schema.prisma +0 -176
  417. package/web/sidepanel.ts +0 -7941
  418. package/web/sitemanifest.json +0 -13
  419. package/web/terminal-ui.tsx +0 -622
  420. package/web/transparencypage.ts +0 -541
  421. package/web/vite.config.ts +0 -39
  422. package/web/workspace.ts +0 -23
  423. package/workbenchmemory.ts +0 -104
  424. package/workbenchsession.ts +0 -304
  425. package/workflow.ts +0 -4495
  426. /package/{dist/bun.d.ts → bun.d.ts} +0 -0
  427. /package/{dist/cli.d.ts → cli.d.ts} +0 -0
  428. /package/{dist/companion.d.ts → companion.d.ts} +0 -0
  429. /package/{dist/crossbrowser.d.ts → crossbrowser.d.ts} +0 -0
  430. /package/{dist/dashdone.d.ts → dashdone.d.ts} +0 -0
  431. /package/{dist/deno.d.ts → deno.d.ts} +0 -0
  432. /package/{dist/hardening.d.ts → hardening.d.ts} +0 -0
  433. /package/{dist/headless.d.ts → headless.d.ts} +0 -0
  434. /package/{dist/http.d.ts → http.d.ts} +0 -0
  435. /package/{dist/index.d.ts → index.d.ts} +0 -0
  436. /package/{dist/mcp.d.ts → mcp.d.ts} +0 -0
  437. /package/{dist/memory.d.ts → memory.d.ts} +0 -0
  438. /package/{dist/node.d.ts → node.d.ts} +0 -0
  439. /package/{dist/pack.d.ts → pack.d.ts} +0 -0
  440. /package/{dist/policy.d.ts → policy.d.ts} +0 -0
  441. /package/{dist/progress.d.ts → progress.d.ts} +0 -0
  442. /package/{dist/protocol.d.ts → protocol.d.ts} +0 -0
  443. /package/{dist/server.d.ts → server.d.ts} +0 -0
  444. /package/{dist/umd.d.ts → umd.d.ts} +0 -0
package/environments.ts DELETED
@@ -1,1005 +0,0 @@
1
- /**
2
- * The environments module of the 1.1.90 consolidation: every correlated variation of the execution environment, emulation and sandbox rendering logic interned in this one file, so the module family carries one surface without duplicate variations.
3
- * The correlation is the execution environment family: environments holds the base grammar (the per kind requirement table, the executor registry, the offscreen document lifecycle with its worker pool plan, the executor traffic envelopes and the isolated world injection of reviewed arguments); emulation stacks the device, network, location, agent and permission presets as revertible layers with the blackbox rules that hide the stack frames of the host page; and sandboxframe renders the untrusted markup of a reviewed step inside the sandboxed page with scripts, schemes and handlers stripped before render and the result posted back through a per render nonce.
4
- * No pool size, preset, retention or posture is ever hardcoded: every interval, ceiling and grant stays the user's choice, and no environment ever bypasses the human review.
5
- */
6
-
7
- import type {
8
- actionkind,
9
- environmentkind,
10
- environmentrequirement,
11
- offscreenregistryentry,
12
- toolstep,
13
- workerrequest,
14
- workerresponse,
15
- } from "./types.js";
16
-
17
- /**
18
- * Execution environment logic of the 1.1.60 family.
19
- * Correlated rules for the environment grammar live here: the per kind requirement table (the live page, the isolated world of the scripting api, the offscreen document worker pool and the sandboxed frame), the executor registry that maps every environmentkind to its runtime adapter, the offscreen document lifecycle with its spawn on first use, reuse across the steps of one run and close at completion, the worker pool plan that grows and shrinks with the pending parse queue, the executor traffic envelopes with their transferable plan and partial streaming, and the isolated world injection that carries reviewed arguments only.
20
- * No pool size, interval, reason list or environment posture is ever hardcoded: the user choices drive every decision, the capability gate keeps offscreenworker behind the optional offscreen grant with an inline fallback, and no environment ever bypasses the human review.
21
- */
22
-
23
- /** The parse heavy read kinds the offscreen worker pool may take over: large html snapshots, network json payloads, table row reductions, accessibility tree shaping, complex selector evaluation and screenshot stitching. */
24
- const offloadfamilies: ReadonlyArray<{ task: string; kinds: readonly string[] }> = [
25
- { task: "htmlsnapshot", kinds: ["readhtml", "parsehtml", "readertree", "readoutline", "classifypage"] },
26
- { task: "jsonpayload", kinds: ["readjson", "parsejson"] },
27
- { task: "tablerows", kinds: ["readtable", "scrapetable", "detecttables", "deduperows", "transformvalues"] },
28
- { task: "a11ytree", kinds: ["a11ytree"] },
29
- { task: "complexselector", kinds: ["resolvexpath", "deriveselector", "detectvirtual"] },
30
- { task: "stitchshots", kinds: ["contactsheet", "timelapse", "makethumbs"] },
31
- ];
32
-
33
- /** Reads the parse family of one offload eligible kind; a kind outside the families returns undefined. */
34
- export function offfamilyof(kind: actionkind): string | undefined {
35
- return offloadfamilies.find((family) => family.kinds.includes(kind))?.task;
36
- }
37
-
38
- /** Reads the environments one action kind may run in: evaluate runs in the isolated world only, a step carrying untrusted markup renders inside the sandboxframe only, the parse heavy kinds may offload into the offscreen worker pool beside the live page and every other kind keeps the pagecontext of the page bridge. */
39
- export function environmentsof(step: Pick<toolstep, "kind" | "options">): environmentkind[] {
40
- if (markuprenderstep(step)) return ["sandboxframe"];
41
- if (step.kind === "evaluate") return ["isolatedworld"];
42
- if (offfamilyof(step.kind) !== undefined) return ["pagecontext", "offscreenworker"];
43
- return ["pagecontext"];
44
- }
45
-
46
- /** Reads the default environment of one step: the absent environment field routes to the isolated world for evaluate, the sandboxframe for untrusted markup and the pagecontext for everything else including the parse offload candidates until the user turns the offload on. */
47
- export function defaultenvironment(step: Pick<toolstep, "kind" | "options">): environmentkind {
48
- if (markuprenderstep(step)) return "sandboxframe";
49
- if (step.kind === "evaluate") return "isolatedworld";
50
- return "pagecontext";
51
- }
52
-
53
- function offamilyeligible(kind: actionkind): boolean {
54
- return offloadfamilies.some((family) => family.kinds.includes(kind));
55
- }
56
-
57
- /** Lists the parse heavy kinds the offscreen worker pool may take over, grouped by their parse family. */
58
- export function offloadkinds(): Array<{ task: string; kinds: string[] }> {
59
- return offloadfamilies.map((family) => ({ task: family.task, kinds: [...family.kinds] }));
60
- }
61
-
62
- /** Reads whether one step carries untrusted markup for rendering: a reviewed options object with a markup string renders inside the sandboxframe only, so the untrusted markup never reenters the page dom. */
63
- export function markuprenderstep(step: Pick<toolstep, "kind" | "options">): boolean {
64
- if (!step.options) return false;
65
- try {
66
- const parsed = JSON.parse(step.options);
67
- return Boolean(
68
- parsed &&
69
- typeof parsed === "object" &&
70
- !Array.isArray(parsed) &&
71
- typeof (parsed as Record<string, unknown>).markup === "string" &&
72
- ((parsed as Record<string, unknown>).markup as string).trim() !== "",
73
- );
74
- } catch {
75
- return false;
76
- }
77
- }
78
-
79
- /** Builds the per kind environment requirement table the policy exposes: one profile per action kind with its allowed environments and its default. */
80
- export function environmentrequirementsof(kinds: actionkind[]): environmentrequirement[] {
81
- return kinds.map((kind) => {
82
- const bare: Pick<toolstep, "kind" | "options"> = { kind };
83
- const environments = environmentsof(bare);
84
- return { kind, environments, defaultenvironment: defaultenvironment(bare) };
85
- });
86
- }
87
-
88
- /** Maps every environmentkind to its runtime adapter: pagecontext rides the page bridge in the live page, isolatedworld rides the scripting api world page scripts cannot touch, offscreenworker rides the offscreen document worker pool and sandboxframe rides the sandboxed page with no extension privileges. */
89
- export function executorregistry(): Array<{ environment: environmentkind; adapter: string; description: string }> {
90
- return [
91
- {
92
- environment: "pagecontext",
93
- adapter: "pagebridge",
94
- description: "The page bridge executes dom actions inside the live page because page events only fire there.",
95
- },
96
- {
97
- environment: "isolatedworld",
98
- adapter: "scriptingapi",
99
- description:
100
- "The scripting api injects step logic inside the isolated world where page globals stay unreachable from step code.",
101
- },
102
- {
103
- environment: "offscreenworker",
104
- adapter: "offscreendocument",
105
- description:
106
- "The offscreen document hosts the worker pool that parses heavy payloads away from the page; the capability gate keeps it behind the optional offscreen grant with an inline fallback.",
107
- },
108
- {
109
- environment: "sandboxframe",
110
- adapter: "sandboxpage",
111
- description:
112
- "The sandboxed page renders untrusted markup with scripts and handlers stripped before render and posts its result back through a per render nonce.",
113
- },
114
- ];
115
- }
116
-
117
- /** Routes one step to its environment: the reviewed environment field wins when it sits inside the requirement table, the offload toggle moves parse heavy kinds into the offscreen worker pool, and a missing offscreen capability grant falls the step back to inline parsing inside the page instead of refusing the reviewed work. */
118
- export function routeenvironment(
119
- step: toolstep,
120
- input: { offload: boolean; granted: boolean },
121
- ): { environment: environmentkind; fallback: boolean; reason: string } {
122
- const allowed = environmentsof(step);
123
- const named = step.environment;
124
- if (named !== undefined) {
125
- if (!allowed.includes(named))
126
- return {
127
- environment: defaultenvironment(step),
128
- fallback: false,
129
- reason: `The ${named} environment sits outside the ${allowed.join(", ")} the ${step.kind} kind permits, so the executor routes to the ${defaultenvironment(step)} default.`,
130
- };
131
- return {
132
- environment: named,
133
- fallback: false,
134
- reason: `The reviewed step names its ${named} environment and the ${step.kind} kind permits it.`,
135
- };
136
- }
137
- if (markuprenderstep(step))
138
- return {
139
- environment: "sandboxframe",
140
- fallback: false,
141
- reason: `The ${step.kind} step carries untrusted markup, so it renders inside the sandboxframe only.`,
142
- };
143
- if (step.kind === "evaluate")
144
- return {
145
- environment: "isolatedworld",
146
- fallback: false,
147
- reason: "The evaluate kind runs inside the isolated world where page globals stay unreachable from step code.",
148
- };
149
- if (offamilyeligible(step.kind)) {
150
- if (!input.offload)
151
- return {
152
- environment: "pagecontext",
153
- fallback: false,
154
- reason: `The ${step.kind} step stays inside the page because the user keeps the parse offload off.`,
155
- };
156
- if (!input.granted)
157
- return {
158
- environment: "pagecontext",
159
- fallback: true,
160
- reason: `The ${step.kind} step falls back to inline parsing inside the page because the offscreen capability grant stays absent.`,
161
- };
162
- return {
163
- environment: "offscreenworker",
164
- fallback: false,
165
- reason: `The ${step.kind} step offloads into the offscreen worker pool under the granted capability.`,
166
- };
167
- }
168
- return {
169
- environment: "pagecontext",
170
- fallback: false,
171
- reason: `The ${step.kind} step keeps the pagecontext because page events only fire inside the live page.`,
172
- };
173
- }
174
-
175
- /** Builds one executor traffic envelope for the offscreen worker pool: the parse family of the kind, the payload reference and the transferable keys the payload carries. */
176
- export function workerrequestof(input: {
177
- id: string;
178
- runid: string;
179
- stepid: string;
180
- kind: actionkind;
181
- payload: string;
182
- options?: Record<string, unknown>;
183
- sentat: number;
184
- }): workerrequest {
185
- const task = offfamilyof(input.kind);
186
- if (task === undefined) throw new Error(`The ${input.kind} kind stays outside the offscreen worker pool families.`);
187
- if (input.payload.trim() === "") throw new Error("The worker request needs its payload reference.");
188
- return {
189
- id: input.id,
190
- runid: input.runid,
191
- stepid: input.stepid,
192
- task,
193
- payload: input.payload,
194
- transferables: transferablekeys(input.options ?? {}),
195
- sentat: input.sentat,
196
- };
197
- }
198
-
199
- /** Reads the payload keys the executor moves as transferable buffers where possible: every ArrayBuffer value travels as a transferable so the worker owns its bytes without a copy. */
200
- export function transferablekeys(options: Record<string, unknown>): string[] {
201
- return Object.keys(options).filter((key) => options[key] instanceof ArrayBuffer);
202
- }
203
-
204
- /** Builds one worker answer to the executor: the finished result or one streamed partial chunk with its sequence number. */
205
- export function workerresponseof(input: {
206
- id: string;
207
- requestid: string;
208
- ok: boolean;
209
- result?: string;
210
- partial?: number;
211
- summary: string;
212
- receivedat: number;
213
- }): workerresponse {
214
- if (input.summary.trim() === "") throw new Error("The worker answer needs its summary in plain language.");
215
- return {
216
- id: input.id,
217
- requestid: input.requestid,
218
- ok: input.ok,
219
- ...(input.result !== undefined ? { result: input.result } : {}),
220
- ...(input.partial !== undefined ? { partial: input.partial } : {}),
221
- summary: input.summary,
222
- receivedat: input.receivedat,
223
- };
224
- }
225
-
226
- /** Reads one worker answer: a partial chunk streams on while the final answer without a partial sequence completes the request, and a failed answer refuses without a result. */
227
- export function acceptworkerresponse(response: workerresponse): {
228
- done: boolean;
229
- partial: boolean;
230
- result?: string;
231
- reason: string;
232
- } {
233
- if (!response.ok)
234
- return {
235
- done: true,
236
- partial: false,
237
- reason: `The worker refused the request ${response.requestid}: ${response.summary}`,
238
- };
239
- if (response.partial !== undefined)
240
- return {
241
- done: false,
242
- partial: true,
243
- reason: `The partial ${response.partial} of the request ${response.requestid} streams back to the executor.`,
244
- };
245
- return {
246
- done: true,
247
- partial: false,
248
- ...(response.result !== undefined ? { result: response.result } : {}),
249
- reason: `The request ${response.requestid} completed inside the offscreen worker pool.`,
250
- };
251
- }
252
-
253
- /** Plans the worker pool against the pending parse queue: the pool grows with the pending parses and shrinks with the idle workers, a user configured size pins the pool with no engine cap, and an absent size lets the queue alone decide. */
254
- export function poolplan(input: { pending: number; current: number; size?: number }): {
255
- workers: number;
256
- added: number;
257
- retired: number;
258
- reason: string;
259
- } {
260
- if (input.size !== undefined) {
261
- if (!Number.isFinite(input.size) || input.size < 1 || !Number.isInteger(input.size))
262
- return {
263
- workers: input.current,
264
- added: 0,
265
- retired: 0,
266
- reason:
267
- "The configured pool size stays a positive whole number the user chose; the pool keeps its current workers.",
268
- };
269
- const target = input.size;
270
- if (target > input.current)
271
- return {
272
- workers: target,
273
- added: target - input.current,
274
- retired: 0,
275
- reason: `The user configured pool size ${target} adds ${target - input.current} worker${target - input.current === 1 ? "" : "s"} to the pool.`,
276
- };
277
- if (target < input.current)
278
- return {
279
- workers: target,
280
- added: 0,
281
- retired: input.current - target,
282
- reason: `The user configured pool size ${target} retires ${input.current - target} worker${input.current - target === 1 ? "" : "s"} from the pool.`,
283
- };
284
- return {
285
- workers: target,
286
- added: 0,
287
- retired: 0,
288
- reason: `The pool holds the ${target} workers the user configured.`,
289
- };
290
- }
291
- if (input.pending > input.current)
292
- return {
293
- workers: input.pending,
294
- added: input.pending - input.current,
295
- retired: 0,
296
- reason: `The ${input.pending} pending parses grow the pool by ${input.pending - input.current} worker${input.pending - input.current === 1 ? "" : "s"}; no engine cap exists.`,
297
- };
298
- if (input.current > input.pending)
299
- return {
300
- workers: input.pending,
301
- added: 0,
302
- retired: input.current - input.pending,
303
- reason: `The ${input.current - input.pending} idle worker${input.current - input.pending === 1 ? "" : "s"} retire down to the ${input.pending} pending parse${input.pending === 1 ? "" : "s"}.`,
304
- };
305
- return {
306
- workers: input.current,
307
- added: 0,
308
- retired: 0,
309
- reason: `The ${input.current} workers match the ${input.pending} pending parses; the pool stays unchanged.`,
310
- };
311
- }
312
-
313
- /** Spawns the offscreen document of one run on first use and reuses the same document across the steps of the run: a registry entry already open for the run carries on while the first need records its reasons and justification. */
314
- export function openoffscreen(input: {
315
- registry: offscreenregistryentry[];
316
- document: string;
317
- runid: string;
318
- reasons: string[];
319
- justification: string;
320
- now: number;
321
- }): { registry: offscreenregistryentry[]; entry: offscreenregistryentry; reused: boolean } {
322
- if (input.document.trim() === "") throw new Error("The offscreen document needs its user configured path.");
323
- if (input.reasons.length === 0) throw new Error("The offscreen document needs the reasons the user reviewed.");
324
- if (input.justification.trim() === "")
325
- throw new Error("The offscreen document needs its justification in plain language.");
326
- const open = input.registry.find((entry) => entry.runid === input.runid && entry.closedat === undefined);
327
- if (open) return { registry: input.registry, entry: open, reused: true };
328
- const entry: offscreenregistryentry = {
329
- document: input.document,
330
- runid: input.runid,
331
- reasons: [...input.reasons],
332
- justification: input.justification,
333
- createdat: input.now,
334
- };
335
- return { registry: [entry, ...input.registry], entry, reused: false };
336
- }
337
-
338
- /** Closes the offscreen document of one run when the run completes: the registry entry records its close time so the audit reads every spawn with its teardown. */
339
- export function closeoffscreen(
340
- registry: offscreenregistryentry[],
341
- runid: string,
342
- now: number,
343
- ): { registry: offscreenregistryentry[]; closed: boolean } {
344
- const open = registry.find((entry) => entry.runid === runid && entry.closedat === undefined);
345
- if (!open) return { registry, closed: false };
346
- return { registry: registry.map((entry) => (entry === open ? { ...entry, closedat: now } : entry)), closed: true };
347
- }
348
-
349
- /** Builds the isolated world injection of one evaluate step: the scripting api injects the reviewed code inside the isolated world where page globals stay unreachable from step code, and the injection carries reviewed arguments only. */
350
- export function isolatedinjection(step: toolstep): { world: "ISOLATED"; code: string; args: string[] } {
351
- if (step.kind !== "evaluate") throw new Error("The isolated world injection serves the evaluate kind only.");
352
- if (!step.value || step.value.trim() === "") throw new Error("The evaluate step needs its reviewed expression.");
353
- let args: string[] = [];
354
- if (step.options) {
355
- try {
356
- const parsed = JSON.parse(step.options);
357
- if (Array.isArray(parsed)) args = parsed.filter((item) => typeof item === "string") as string[];
358
- } catch {
359
- /* an options object outside the reviewed argument list stays empty */
360
- }
361
- }
362
- return { world: "ISOLATED", code: step.value, args };
363
- }
364
-
365
- /** The runsummary task family of the 1.1.63 session interface: the distillation of a completed run runs as one offscreen worker task beside the parse families. */
366
- export const runsummarytask = "runsummary";
367
-
368
- /** Builds the offscreen worker request of one runsummary distillation: the task family name, the run and session provenance and the distilled payload reference; the step id slot carries the session id because the distillation belongs to the whole run. */
369
- export function summaryrequestof(input: {
370
- id: string;
371
- runid: string;
372
- sessionid: string;
373
- payload: string;
374
- sentat: number;
375
- }): workerrequest {
376
- if (input.payload.trim() === "") throw new Error("The runsummary request needs its payload reference.");
377
- return {
378
- id: input.id,
379
- runid: input.runid,
380
- stepid: input.sessionid,
381
- task: runsummarytask,
382
- payload: input.payload,
383
- transferables: [],
384
- sentat: input.sentat,
385
- };
386
- }
387
-
388
- /* ── Merged from emulation.ts ── */
389
- import type {
390
- agentpreset,
391
- blackboxrule,
392
- devicepreset,
393
- emulationlayer,
394
- emulationstate,
395
- locationconsent,
396
- locationpreset,
397
- networkpreset,
398
- permissiongrant,
399
- permissionstate,
400
- presetlibrary,
401
- stackframe,
402
- } from "./types.js";
403
-
404
- /**
405
- * Emulation layer engine for the 1.1.48 family.
406
- * Every correlated rule for the reviewed masks of the run lives in this file: the preset normalizers of the user curated device, network, location and agent libraries, the reviewed revert plan grammar, the layer apply and revert math with prior state capture and reverse order restore, the stacking conflict order where the last applied layer wins, the latitude, longitude and user agent grammars, the browser permission set, the blackbox pattern matching that hides third party frames from stack traces, the retention expiry of reverted layer states and the versioned preset library import and export.
407
- * True device metric, network condition, geolocation and user agent override needs browser debugger or platform permissions that the manifest gate forbids, so every layer applies page-injected overrides through the scripting api and shapes only the traffic the extension itself initiates; the derivation is recorded on every layer instead of hidden.
408
- */
409
-
410
- /** The emulation kinds of the 1.1.48 family, listed among the available capabilities of every proposal request. */
411
- export const emulationkinds: string[] = [
412
- "emulatedevice",
413
- "emulatenetwork",
414
- "emulatelocate",
415
- "setuseragent",
416
- "overridepermission",
417
- "blackboxscripts",
418
- ];
419
-
420
- /** The reviewed browser permission set of the override grammar; overrides outside this set are refused. */
421
- export const browserpermissions: string[] = [
422
- "geolocation",
423
- "notifications",
424
- "camera",
425
- "microphone",
426
- "clipboard-read",
427
- "clipboard-write",
428
- "midi",
429
- "persistent-storage",
430
- ];
431
-
432
- /** The reviewed permission states of an override: granted, denied or the browser default prompt. */
433
- export const permissionstates: permissionstate[] = ["granted", "denied", "prompt"];
434
-
435
- /** The emulation family of one emulation action kind. */
436
- export function familyofkind(
437
- kind: string,
438
- ): "device" | "network" | "location" | "agent" | "permission" | "blackbox" | undefined {
439
- if (kind === "emulatedevice") return "device";
440
- if (kind === "emulatenetwork") return "network";
441
- if (kind === "emulatelocate") return "location";
442
- if (kind === "setuseragent") return "agent";
443
- if (kind === "overridepermission") return "permission";
444
- if (kind === "blackboxscripts") return "blackbox";
445
- return undefined;
446
- }
447
-
448
- /** Normalizes one user curated device preset: the name, width, height, pixel ratio and the mobile flag with every bound a user choice only. */
449
- export function devicepresetof(value: unknown): devicepreset | undefined {
450
- if (!value || typeof value !== "object" || Array.isArray(value)) return undefined;
451
- const entry = value as Record<string, unknown>;
452
- const name = typeof entry.name === "string" && entry.name.trim() ? entry.name.trim() : undefined;
453
- const width =
454
- typeof entry.width === "number" && Number.isInteger(entry.width) && entry.width > 0 ? entry.width : undefined;
455
- const height =
456
- typeof entry.height === "number" && Number.isInteger(entry.height) && entry.height > 0 ? entry.height : undefined;
457
- const pixelratio =
458
- typeof entry.pixelratio === "number" && Number.isFinite(entry.pixelratio) && entry.pixelratio > 0
459
- ? entry.pixelratio
460
- : undefined;
461
- if (name === undefined || width === undefined || height === undefined || pixelratio === undefined) return undefined;
462
- return { name, width, height, pixelratio, mobile: entry.mobile === true };
463
- }
464
-
465
- /** Normalizes one user curated network preset: the name, latency, download and upload bounds and the offline flag. */
466
- export function networkpresetof(value: unknown): networkpreset | undefined {
467
- if (!value || typeof value !== "object" || Array.isArray(value)) return undefined;
468
- const entry = value as Record<string, unknown>;
469
- const name = typeof entry.name === "string" && entry.name.trim() ? entry.name.trim() : undefined;
470
- const latency =
471
- typeof entry.latency === "number" && Number.isFinite(entry.latency) && entry.latency >= 0
472
- ? entry.latency
473
- : undefined;
474
- const download =
475
- typeof entry.download === "number" && Number.isFinite(entry.download) && entry.download >= 0
476
- ? entry.download
477
- : undefined;
478
- const upload =
479
- typeof entry.upload === "number" && Number.isFinite(entry.upload) && entry.upload >= 0 ? entry.upload : undefined;
480
- if (name === undefined || latency === undefined || download === undefined || upload === undefined) return undefined;
481
- return { name, latency, download, upload, offline: entry.offline === true };
482
- }
483
-
484
- /** Normalizes one user curated location preset: the name, the latitude and longitude inside the reviewed ranges and the non-negative accuracy radius. */
485
- export function locationpresetof(value: unknown): locationpreset | undefined {
486
- if (!value || typeof value !== "object" || Array.isArray(value)) return undefined;
487
- const entry = value as Record<string, unknown>;
488
- const name = typeof entry.name === "string" && entry.name.trim() ? entry.name.trim() : undefined;
489
- const latitude = typeof entry.latitude === "number" && Number.isFinite(entry.latitude) ? entry.latitude : undefined;
490
- const longitude =
491
- typeof entry.longitude === "number" && Number.isFinite(entry.longitude) ? entry.longitude : undefined;
492
- const accuracy =
493
- typeof entry.accuracy === "number" && Number.isFinite(entry.accuracy) && entry.accuracy >= 0
494
- ? entry.accuracy
495
- : undefined;
496
- if (name === undefined || latitude === undefined || longitude === undefined || accuracy === undefined)
497
- return undefined;
498
- if (!locationrangevalid(latitude, longitude)) return undefined;
499
- return { name, latitude, longitude, accuracy };
500
- }
501
-
502
- /** Normalizes one user curated agent preset: the user agent string of the reviewed grammar, the platform and the non-empty brand list reported together. */
503
- export function agentpresetof(value: unknown): agentpreset | undefined {
504
- if (!value || typeof value !== "object" || Array.isArray(value)) return undefined;
505
- const entry = value as Record<string, unknown>;
506
- const name = typeof entry.name === "string" && entry.name.trim() ? entry.name.trim() : undefined;
507
- const useragent = typeof entry.useragent === "string" ? entry.useragent : undefined;
508
- const platform = typeof entry.platform === "string" && entry.platform.trim() ? entry.platform.trim() : undefined;
509
- const brands = Array.isArray(entry.brands)
510
- ? entry.brands.filter((brand): brand is string => typeof brand === "string" && brand.trim().length > 0)
511
- : [];
512
- if (name === undefined || useragent === undefined || platform === undefined || brands.length === 0) return undefined;
513
- if (!agentgrammarvalid(useragent)) return undefined;
514
- return { name, useragent, platform, brands: [...new Set(brands)] };
515
- }
516
-
517
- /** Normalizes one reviewed permission override: the name of the reviewed browser permission set, the state of the reviewed permission states and the run scope flag. */
518
- export function permissiongrantof(value: unknown): permissiongrant | undefined {
519
- if (!value || typeof value !== "object" || Array.isArray(value)) return undefined;
520
- const entry = value as Record<string, unknown>;
521
- const name = typeof entry.name === "string" && browserpermissions.includes(entry.name) ? entry.name : undefined;
522
- const state =
523
- typeof entry.state === "string" && permissionstates.includes(entry.state as permissionstate)
524
- ? (entry.state as permissionstate)
525
- : undefined;
526
- if (name === undefined || state === undefined) return undefined;
527
- return { name, state, runscope: entry.runscope !== false };
528
- }
529
-
530
- /** Normalizes one reviewed blackbox rule: a non-empty url pattern list where every pattern names its origin explicitly and the trace scope of profiles, traces or both. */
531
- export function blackboxruleof(value: unknown): blackboxrule | undefined {
532
- if (!value || typeof value !== "object" || Array.isArray(value)) return undefined;
533
- const entry = value as Record<string, unknown>;
534
- const urlpatterns = Array.isArray(entry.urlpatterns)
535
- ? entry.urlpatterns.filter(
536
- (pattern): pattern is string => typeof pattern === "string" && /^https:\/\//.test(pattern),
537
- )
538
- : [];
539
- const tracescope = entry.tracescope;
540
- if (urlpatterns.length === 0) return undefined;
541
- if (tracescope !== "profiles" && tracescope !== "traces" && tracescope !== "both") return undefined;
542
- return { urlpatterns: [...new Set(urlpatterns)], tracescope };
543
- }
544
-
545
- /** Normalizes one reviewed revert plan: a non-empty ordered list of revert steps kept beside its layer; plans without steps are refused. */
546
- export function revertplanof(value: unknown): string[] | undefined {
547
- const steps = Array.isArray(value)
548
- ? value.filter((step): step is string => typeof step === "string" && step.trim().length > 0)
549
- : [];
550
- return steps.length > 0 ? steps : undefined;
551
- }
552
-
553
- /** Builds one emulation layer record with its origin scope, apply time, captured prior state and reviewed revert plan. */
554
- export function newlayer(input: {
555
- id: string;
556
- runid: string;
557
- stepid: string;
558
- family: emulationlayer["family"];
559
- name: string;
560
- originscope: string;
561
- revertplan: string[];
562
- prior?: Record<string, unknown>;
563
- at: number;
564
- }): emulationlayer {
565
- return {
566
- id: input.id,
567
- runid: input.runid,
568
- stepid: input.stepid,
569
- family: input.family,
570
- name: input.name,
571
- originscope: input.originscope,
572
- appliedat: input.at,
573
- ...(input.prior !== undefined ? { prior: input.prior } : {}),
574
- revertplan: [...input.revertplan],
575
- };
576
- }
577
-
578
- /** Builds the initial emulation state of one run scoped to its tab and origin. */
579
- export function emulationstateof(input: { runid: string; tabid: number; origin: string; now: number }): emulationstate {
580
- return { runid: input.runid, tabid: input.tabid, origin: input.origin, layers: [], updatedat: input.now };
581
- }
582
-
583
- /** Stacks one layer onto the emulation state in apply order: a second layer of the same family replaces the first in effect while both stay in the history, so the last applied layer wins conflicts. */
584
- export function applylayer(state: emulationstate, layer: emulationlayer, at: number): emulationstate {
585
- const layers = [...state.layers.filter((item) => item.id !== layer.id), layer];
586
- return { ...state, layers, updatedat: at };
587
- }
588
-
589
- /** Reverts one layer of the state by its id: the revert time stamps the record while the layer history survives for review. */
590
- export function revertlayer(state: emulationstate, layerid: string, at: number): emulationstate {
591
- const layers = state.layers.map((layer) =>
592
- layer.id === layerid && layer.revertedat === undefined ? { ...layer, revertedat: at } : layer,
593
- );
594
- return { ...state, layers, updatedat: at };
595
- }
596
-
597
- /** Reverts every active layer of the state in reverse apply order: the reversed list is the exact restore sequence and every layer keeps its revert stamp. */
598
- export function revertalllayers(
599
- state: emulationstate,
600
- at: number,
601
- ): { state: emulationstate; reverted: emulationlayer[] } {
602
- const reverted = [...state.layers].reverse().filter((layer) => layer.revertedat === undefined);
603
- const layers = state.layers.map((layer) => (layer.revertedat === undefined ? { ...layer, revertedat: at } : layer));
604
- return { state: { ...state, layers, updatedat: at }, reverted };
605
- }
606
-
607
- /** Returns the active layers of the state in apply order. */
608
- export function activelayers(state: emulationstate | undefined): emulationlayer[] {
609
- return state ? state.layers.filter((layer) => layer.revertedat === undefined) : [];
610
- }
611
-
612
- /** Returns the names of the active layers for the response envelope and the review panel. */
613
- export function layernames(state: emulationstate | undefined): string[] {
614
- return activelayers(state).map((layer) => layer.name);
615
- }
616
-
617
- /** Counts the active layers of one family so the panel can warn when layers stack on one tab. */
618
- export function stackedcount(state: emulationstate | undefined): number {
619
- return activelayers(state).length;
620
- }
621
-
622
- /** True when the reviewed latitude stays inside the -90 to 90 degree range and the longitude inside the -180 to 180 degree range. */
623
- export function locationrangevalid(latitude: number, longitude: number): boolean {
624
- return (
625
- Number.isFinite(latitude) &&
626
- Number.isFinite(longitude) &&
627
- latitude >= -90 &&
628
- latitude <= 90 &&
629
- longitude >= -180 &&
630
- longitude <= 180
631
- );
632
- }
633
-
634
- /** Validates one user agent string against the reviewed grammar: tokens of word characters, separators, slashes, spaces, numbers and version dots; the string must carry at least one token pair and refuse line breaks. */
635
- export function agentgrammarvalid(useragent: string): boolean {
636
- const text = useragent.trim();
637
- if (text.length === 0 || text.length > 512) return false;
638
- if (/[\r\n]/.test(text)) return false;
639
- if (!/^[A-Za-z0-9][A-Za-z0-9._+\-()/:; ,]*$/.test(text)) return false;
640
- return /\/\d/.test(text) || /\d+\.\d+/.test(text);
641
- }
642
-
643
- /** Grades one reviewed permission name by its power: location, camera, microphone and notification overrides carry the user's most sensitive signals and stay the highest grade. */
644
- export function permissiongrade(name: string): "powerful" | "standard" {
645
- return name === "geolocation" || name === "camera" || name === "microphone" || name === "notifications"
646
- ? "powerful"
647
- : "standard";
648
- }
649
-
650
- /** Matches one script url against a reviewed blackbox pattern of an explicit https origin with single star segments and double star subtrees. */
651
- export function blackboxmatches(urlpattern: string, url: string): boolean {
652
- const patternmatch = /^(https:\/\/[^/]+)(\/.*)?$/.exec(urlpattern);
653
- const urlmatch = /^(https:\/\/[^/]+)(\/.*)?$/.exec(url);
654
- if (!patternmatch || !urlmatch) return false;
655
- if (patternmatch[1] !== urlmatch[1]) return false;
656
- const patternpath = (patternmatch[2] ?? "/").split("/").filter((segment) => segment.length > 0);
657
- const urlpath = (urlmatch[2] ?? "/").split("/").filter((segment) => segment.length > 0);
658
- const walk = (patternindex: number, urlindex: number): boolean => {
659
- if (patternindex >= patternpath.length) return urlindex >= urlpath.length;
660
- const segment = patternpath[patternindex];
661
- if (segment === undefined) return false;
662
- if (segment === "**")
663
- return walk(patternindex + 1, urlindex) || (urlindex < urlpath.length && walk(patternindex, urlindex + 1));
664
- if (urlindex >= urlpath.length) return false;
665
- if (segment !== "*" && segment !== urlpath[urlindex]) return false;
666
- return walk(patternindex + 1, urlindex + 1);
667
- };
668
- return walk(0, 0);
669
- }
670
-
671
- /** Hides blackboxed frames from one stack trace: every frame whose url matches a rule pattern with a traces scope of traces or both is dropped so third party frames never enter the shaped trace. */
672
- export function hideblackboxedframes(rules: blackboxrule[], frames: stackframe[]): stackframe[] {
673
- const patterns = rules
674
- .filter((rule) => rule.tracescope === "traces" || rule.tracescope === "both")
675
- .flatMap((rule) => rule.urlpatterns);
676
- if (patterns.length === 0) return frames;
677
- return frames.filter((frame) => !patterns.some((pattern) => blackboxmatches(pattern, frame.url)));
678
- }
679
-
680
- /** Marks one url as blackboxed when any rule with a profiles or both trace scope matches it, so the trace shaping lists the hidden third party urls. */
681
- export function blackboxedurls(rules: blackboxrule[], urls: string[]): string[] {
682
- const patterns = rules.flatMap((rule) => rule.urlpatterns);
683
- return urls.filter((url) => patterns.some((pattern) => blackboxmatches(pattern, url)));
684
- }
685
-
686
- /** Expires the prior states of reverted layers after the retention window while the layer history itself always survives; an absent window keeps every prior state. */
687
- export function expirelayers(state: emulationstate, retention: number | undefined, now: number): emulationstate {
688
- if (retention === undefined) return state;
689
- const layers = state.layers.map((layer) => {
690
- if (layer.revertedat === undefined || layer.prior === undefined || layer.priorexpired === true) return layer;
691
- if (now - layer.revertedat <= retention) return layer;
692
- const { prior, ...metadata } = layer;
693
- void prior;
694
- return { ...metadata, priorexpired: true };
695
- });
696
- return { ...state, layers, updatedat: now };
697
- }
698
-
699
- /** Builds one shareable preset library file of the user curated presets; the version carries the library contract for imports through review. */
700
- export function exportpresetlibrary(input: {
701
- devices: devicepreset[];
702
- networks: networkpreset[];
703
- locations: locationpreset[];
704
- agents: agentpreset[];
705
- now: number;
706
- }): presetlibrary {
707
- return {
708
- version: 1,
709
- devices: [...input.devices],
710
- networks: [...input.networks],
711
- locations: [...input.locations],
712
- agents: [...input.agents],
713
- exportedat: input.now,
714
- };
715
- }
716
-
717
- /** Parses one reviewed preset library file: every preset entry must pass its normalizer and a file without any valid preset is refused; unknown fields stay ignored. */
718
- export function importpresetlibrary(value: unknown): presetlibrary | undefined {
719
- if (!value || typeof value !== "object" || Array.isArray(value)) return undefined;
720
- const entry = value as Record<string, unknown>;
721
- const devices = (Array.isArray(entry.devices) ? entry.devices : []).flatMap((preset) => {
722
- const parsed = devicepresetof(preset);
723
- return parsed !== undefined ? [parsed] : [];
724
- });
725
- const networks = (Array.isArray(entry.networks) ? entry.networks : []).flatMap((preset) => {
726
- const parsed = networkpresetof(preset);
727
- return parsed !== undefined ? [parsed] : [];
728
- });
729
- const locations = (Array.isArray(entry.locations) ? entry.locations : []).flatMap((preset) => {
730
- const parsed = locationpresetof(preset);
731
- return parsed !== undefined ? [parsed] : [];
732
- });
733
- const agents = (Array.isArray(entry.agents) ? entry.agents : []).flatMap((preset) => {
734
- const parsed = agentpresetof(preset);
735
- return parsed !== undefined ? [parsed] : [];
736
- });
737
- if (devices.length + networks.length + locations.length + agents.length === 0) return undefined;
738
- return {
739
- version:
740
- typeof entry.version === "number" && Number.isInteger(entry.version) && entry.version >= 1 ? entry.version : 1,
741
- devices,
742
- networks,
743
- locations,
744
- agents,
745
- exportedat: typeof entry.exportedat === "number" ? entry.exportedat : Date.now(),
746
- };
747
- }
748
-
749
- /** True when one approved location consent of that origin covers the reviewed coordinates; the prompt shows the exact latitude and longitude before the override applies. */
750
- export function locationconsentcovers(
751
- origin: string,
752
- latitude: number,
753
- longitude: number,
754
- consents: locationconsent[],
755
- ): boolean {
756
- return consents.some(
757
- (consent) =>
758
- consent.origin === origin &&
759
- consent.approved === true &&
760
- consent.revokedat === undefined &&
761
- consent.latitude === latitude &&
762
- consent.longitude === longitude,
763
- );
764
- }
765
-
766
- /* ── Merged from sandboxframe.ts ── */
767
- import type { environmentprovenance, sandboxrender, sandboxrenderresult } from "./types.js";
768
-
769
- /**
770
- * Sandbox frame logic of the 1.1.60 family.
771
- * Correlated rules for untrusted markup live here: the render descriptor with its per render nonce, the stripping of scripts and event handlers before any render, the postmessage envelope the sandboxed page answers through, the nonce checked acceptance of every render result and the provenance each render records with its source origin.
772
- * The sandboxed page carries no extension privileges: a render result never reenters the dom outside the frame, and the untrusted markup never touches the page the session granted.
773
- */
774
-
775
- /** Strips every script and event handler from untrusted markup before render through one linear character scan, because a replace chain over reassembled payloads (`<scr<script>ipt>` or `javajavascript:script:`) reads as incomplete sanitization to static analysis even inside a convergence loop. The scan rebuilds the markup instead of removing fragments from it: a well formed tag keeps its name and its safe attributes with the script url schemes stripped from the values, every `on` event handler attribute drops, a script element drops with its whole content through the closing tag, and a malformed tag whose body carries another open angle (the reassembly payloads live on) drops entirely so no `<script` or `on...=` sequence ever survives into the sanitized output. */
776
- export function stripscripts(markup: string): string {
777
- let out = "";
778
- let index = 0;
779
- while (index < markup.length) {
780
- const open = markup.indexOf("<", index);
781
- if (open === -1) {
782
- out += markup.slice(index);
783
- break;
784
- }
785
- out += markup.slice(index, open);
786
- const close = markup.indexOf(">", open);
787
- if (close === -1) {
788
- index = markup.length;
789
- break;
790
- } /* an unterminated tag fragment never renders */
791
- const body = markup.slice(open + 1, close);
792
- if (body.includes("<")) {
793
- index = close + 1;
794
- continue;
795
- } /* a reassembled payload drops as a whole */
796
- const namematch = /^\/?\s*([a-zA-Z][a-zA-Z0-9:-]*)/.exec(body);
797
- const name = (namematch?.[1] ?? "").toLowerCase();
798
- if (name === "script") {
799
- if (body.startsWith("/") || body.endsWith("/")) {
800
- index = close + 1;
801
- continue;
802
- } /* a closing or self closing script tag drops alone */
803
- const rest = markup.slice(close + 1).toLowerCase();
804
- const end = rest.indexOf("</script");
805
- if (end === -1) {
806
- index = markup.length;
807
- break;
808
- } /* an unterminated script element swallows the rest */
809
- const endclose = markup.indexOf(">", close + 1 + end);
810
- index = endclose === -1 ? markup.length : endclose + 1;
811
- continue;
812
- }
813
- out += rebuildtag(name, body);
814
- index = close + 1;
815
- }
816
- return out.trim();
817
- }
818
-
819
- /** Rebuilds one tag with its event handler attributes dropped and its script url schemes stripped: the name keeps its letter case, every kept attribute re-renders with double quoted values, and a non element tag (a comment or a doctype) passes through verbatim because its body already carries no open angle. */
820
- function rebuildtag(name: string, body: string): string {
821
- if (name === "") return `<${body}>`;
822
- if (body.startsWith("/")) return `</${name}>`;
823
- const kept: string[] = [];
824
- let cursor = namelengthof(body);
825
- while (cursor < body.length) {
826
- while (cursor < body.length && /\s/.test(charof(body, cursor))) cursor += 1;
827
- if (cursor >= body.length) break;
828
- const attrstart = cursor;
829
- while (cursor < body.length && !/\s/.test(charof(body, cursor)) && charof(body, cursor) !== "=") cursor += 1;
830
- const attrname = body.slice(attrstart, cursor).toLowerCase();
831
- if (attrname.startsWith("on") && attrname.length > 2) {
832
- cursor = skipattribute(body, cursor);
833
- continue;
834
- }
835
- if (charof(body, cursor) !== "=") {
836
- kept.push(attrname);
837
- continue;
838
- }
839
- cursor += 1;
840
- const quote = charof(body, cursor);
841
- if (quote === '"' || quote === "'") {
842
- cursor += 1;
843
- const valueend = body.indexOf(quote, cursor);
844
- const value = stripschemes(body.slice(cursor, valueend === -1 ? body.length : valueend));
845
- cursor = valueend === -1 ? body.length : valueend + 1;
846
- kept.push(`${attrname}="${value}"`);
847
- continue;
848
- }
849
- const valuestart = cursor;
850
- while (cursor < body.length && !/\s/.test(charof(body, cursor))) cursor += 1;
851
- kept.push(`${attrname}="${stripschemes(body.slice(valuestart, cursor))}"`);
852
- }
853
- return kept.length === 0 ? `<${name}>` : `<${name} ${kept.join(" ")}>`;
854
- }
855
-
856
- /** Reads one character of a body safely: an out of bounds position answers the empty string so the strict index access never surfaces undefined. */
857
- function charof(text: string, at: number): string {
858
- return at >= 0 && at < text.length ? text[at]! : "";
859
- }
860
-
861
- /** Reads the length of the tag name the body starts with: the attribute walk starts right after it. */
862
- function namelengthof(body: string): number {
863
- const match = /^[a-zA-Z][a-zA-Z0-9:-]*/.exec(body);
864
- return match === null ? 0 : match[0].length;
865
- }
866
-
867
- /** Advances past one attribute value (a quoted or bare run) so a dropped event handler leaves nothing behind: the function answers the cursor after the value. */
868
- function skipattribute(body: string, cursor: number): number {
869
- if (charof(body, cursor) !== "=") return cursor;
870
- const at = cursor + 1;
871
- const quote = charof(body, at);
872
- if (quote === '"' || quote === "'") {
873
- const valueend = body.indexOf(quote, at + 1);
874
- return valueend === -1 ? body.length : valueend + 1;
875
- }
876
- let bare = at;
877
- while (bare < body.length && !/\s/.test(charof(body, bare))) bare += 1;
878
- return bare;
879
- }
880
-
881
- /** Strips the script url schemes from one attribute value through plain index finds, so no regular expression runs over untrusted text anywhere in the sanitizer. */
882
- function stripschemes(value: string): string {
883
- let out = value;
884
- for (const scheme of ["javascript:", "vbscript:", "data:"]) {
885
- let at = out.toLowerCase().indexOf(scheme);
886
- while (at >= 0) {
887
- out = out.slice(0, at) + out.slice(at + scheme.length);
888
- at = out.toLowerCase().indexOf(scheme);
889
- }
890
- }
891
- return out;
892
- }
893
-
894
- /** Strips every tag from a render result text through a single linear scan: an open angle starts a tag that runs to the next close angle or, when no close angle follows, to the end of the text, so an unclosed fragment never survives into the result. */
895
- function striptags(text: string): string {
896
- let out = "";
897
- let index = 0;
898
- while (index < text.length) {
899
- const open = text.indexOf("<", index);
900
- if (open === -1) {
901
- out += text.slice(index);
902
- break;
903
- }
904
- out += text.slice(index, open);
905
- const close = text.indexOf(">", open);
906
- if (close === -1) break;
907
- index = close + 1;
908
- }
909
- return out;
910
- }
911
-
912
- /** Issues the per render nonce of one sandbox render: every message the sandboxed page answers carries the nonce of exactly one render so a stale or replayed message never passes. */
913
- export function nonceof(seed: string): string {
914
- const alphabet = "abcdefghijklmnopqrstuvwxyz0123456789";
915
- let hash = 0;
916
- for (let index = 0; index < seed.length; index += 1) hash = (hash * 31 + seed.charCodeAt(index)) >>> 0;
917
- let nonce = "";
918
- let state = hash === 0 ? 0x9e3779b9 : hash;
919
- for (let index = 0; index < 16; index += 1) {
920
- state = (state * 1664525 + 1013904223) >>> 0;
921
- nonce += alphabet[state % alphabet.length];
922
- }
923
- return nonce;
924
- }
925
-
926
- /** Builds one sandbox render descriptor: the sanitized markup, the per render nonce and the source origin of the untrusted markup, ready for the sandboxed page that holds no extension privileges. */
927
- export function sandboxrenderof(input: {
928
- id: string;
929
- markup: string;
930
- sourceorigin: string;
931
- stepid: string;
932
- now: number;
933
- }): sandboxrender {
934
- if (input.markup.trim() === "") throw new Error("The sandbox render needs its untrusted markup.");
935
- if (input.sourceorigin.trim() === "")
936
- throw new Error("The sandbox render needs the source origin of its untrusted markup.");
937
- if (input.stepid.trim() === "") throw new Error("The sandbox render names the reviewed step it renders for.");
938
- return {
939
- id: input.id,
940
- nonce: nonceof(`${input.id}:${input.now}`),
941
- markup: stripscripts(input.markup),
942
- sourceorigin: input.sourceorigin,
943
- stepid: input.stepid,
944
- renderedat: input.now,
945
- };
946
- }
947
-
948
- /** Builds the postmessage envelope the executor posts into the sandboxed page: the render nonce and the sanitized markup travel together so the frame answers with the same nonce only. */
949
- export function rendermessage(render: sandboxrender): { channel: string; type: string; nonce: string; markup: string } {
950
- return { channel: "devthinksandbox", type: "render", nonce: render.nonce, markup: render.markup };
951
- }
952
-
953
- /** Accepts one sandbox render result posted back through postmessage: the nonce must answer exactly one known unanswered render, an unknown, replayed or already answered nonce refuses, and the accepted result stays plain text that never reenters the dom outside the frame. The returned renders carry the answeredat marker of the accepted render so the caller threads the closed render out of its pending set. */
954
- export function acceptrenderresult(input: {
955
- renders: sandboxrender[];
956
- message: { channel?: string; type?: string; nonce?: string; ok?: boolean; text?: string; summary?: string };
957
- now: number;
958
- }): { accepted: boolean; result?: sandboxrenderresult; reason: string; renders: sandboxrender[] } {
959
- if (input.message.channel !== "devthinksandbox")
960
- return {
961
- accepted: false,
962
- reason: "The sandbox message travels the devthinksandbox channel only.",
963
- renders: input.renders,
964
- };
965
- if (input.message.type !== "renderresult")
966
- return {
967
- accepted: false,
968
- reason: "The sandbox message answers with the renderresult type only.",
969
- renders: input.renders,
970
- };
971
- const render = input.renders.find(
972
- (entry) => entry.nonce === input.message.nonce && entry.answeredat === undefined && entry.renderedat <= input.now,
973
- );
974
- if (!render)
975
- return {
976
- accepted: false,
977
- reason:
978
- "The sandbox message carries no nonce of a known unanswered render; a stale, replayed or already answered message never passes.",
979
- renders: input.renders,
980
- };
981
- const text = striptags(input.message.text ?? "");
982
- const result: sandboxrenderresult = {
983
- nonce: render.nonce,
984
- ok: input.message.ok !== false,
985
- text,
986
- summary:
987
- input.message.summary?.trim() ||
988
- `The sandbox frame rendered the markup of the step ${render.stepid} and returned its inert text.`,
989
- at: input.now,
990
- };
991
- const renders = input.renders.map((entry) =>
992
- entry.nonce === render.nonce ? { ...entry, answeredat: input.now } : entry,
993
- );
994
- return {
995
- accepted: true,
996
- result,
997
- reason: `The render result of the step ${render.stepid} answers the nonce of its render; the text stays inside the frame.`,
998
- renders,
999
- };
1000
- }
1001
-
1002
- /** Reads the provenance of one sandbox render: the source origin, the step and the sandboxframe environment land beside the stored render so the audit reads where every untrusted markup came from. */
1003
- export function renderprovenance(render: sandboxrender): environmentprovenance {
1004
- return { origin: render.sourceorigin, stepid: render.stepid, environment: "sandboxframe" };
1005
- }