@wenathlan/devthink 2.0.15 → 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 +45 -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/export.ts DELETED
@@ -1,889 +0,0 @@
1
- /** The export module of the 1.1.90 consolidation: every correlated variation of the data egress, the minimization and the portability logic interned in this one file, so the module family carries one surface without duplicate variations. */
2
-
3
- /* ── Merged from exporttools.ts: the 1.1.90 consolidation interns the correlated exporttools logic here, so no variation of the same file lives beside another. ── */
4
- import type {
5
- cookiejarrecord,
6
- datainventory,
7
- dropimportsession,
8
- exportallbundle,
9
- exportdescriptor,
10
- exportresult,
11
- immutablelogentry,
12
- importexportpayload,
13
- localrule,
14
- purgepolicy,
15
- quarantineentry,
16
- scanverdict,
17
- sitenote,
18
- storedrunlog,
19
- syncsettings,
20
- syncrecord,
21
- telemetrypolicy,
22
- } from "./types.js";
23
- import { defaultmaskshapes, maskexport, maskmarker } from "./security.js";
24
- import { exportchaingate, exportmaskgate, importexportgate } from "./policy.js";
25
- import { verifylogchain } from "./security.js";
26
-
27
- /**
28
- * Exporttools logic of the 1.1.67 family.
29
- * Runs, extractions and notes move to disk here: every export carries its descriptor with the format and the scope, the log chain verifies before any byte leaves the store, the maskinputs verdicts apply in every format, and an unmasked value refuses the export in full.
30
- * The tools never widen an export: a refused export writes nothing, and no format ships a value the mask verdicts held back.
31
- */
32
-
33
- /** The export formats the tools render: csv for tables, json for structured consumers, log for the chain lines, jsonl for the json lines streams and markdown for the table documents. */
34
- export const exportformats: readonly string[] = ["csv", "json", "log", "jsonl", "markdown"];
35
-
36
- /** The export scopes the tools serve: the runs of a chain, the extractions of a run, the notes of a profile, beside the session, audit and extraction scopes the 1.1.80 exportdata command exports. */
37
- export const exportscopes: readonly string[] = ["runs", "extractions", "notes", "session", "audit", "extraction"];
38
-
39
- /** Builds and validates one export descriptor: the format and the scope stay the reviewed pair the command declared. */
40
- export function exportdescriptorof(format: string, scope: string): exportdescriptor {
41
- if (!exportformats.includes(format))
42
- throw new Error(`The export format ${format} stays outside csv, json, log, jsonl and markdown.`);
43
- if (!exportscopes.includes(scope))
44
- throw new Error(`The export scope ${scope} stays outside runs, extractions, notes, session, audit and extraction.`);
45
- return { format: format as exportdescriptor["format"], scope: scope as exportdescriptor["scope"] };
46
- }
47
-
48
- /** Escapes one csv field: the field quotes when it carries a comma, a quote or a newline, and a quote doubles inside. */
49
- export function csvfield(value: string): string {
50
- return /[",\n]/.test(value) ? `"${value.replace(/"/g, '""')}"` : value;
51
- }
52
-
53
- /** Renders rows as csv: the header carries the sorted field names and every row escapes its fields the csv way. */
54
- export function csvof(rows: Array<Record<string, string>>): string {
55
- if (rows.length === 0) return "";
56
- const fields = [...new Set(rows.flatMap((row) => Object.keys(row)))].sort();
57
- const header = fields.map(csvfield).join(",");
58
- const body = rows.map((row) => fields.map((field) => csvfield(row[field] ?? "")).join(","));
59
- return [header, ...body].join("\n");
60
- }
61
-
62
- /** Escapes one markdown table cell: the backslash escapes first so a value that already carries one never collides with the pipe escape, the pipe follows and a newline breaks the cell into a line break the table carries literally. */
63
- export function markdownfield(value: string): string {
64
- return value.replace(/\\/g, "\\\\").replace(/\|/g, "\\|").replace(/\r?\n/g, "<br>");
65
- }
66
-
67
- /** Renders rows as one markdown table: the header carries the sorted field names, the separator row fixes the column count and every row escapes its cells the markdown way. */
68
- export function markdownof(rows: Array<Record<string, string>>): string {
69
- if (rows.length === 0) return "";
70
- const fields = [...new Set(rows.flatMap((row) => Object.keys(row)))].sort();
71
- const header = `| ${fields.map(markdownfield).join(" | ")} |`;
72
- const separator = `| ${fields.map(() => "---").join(" | ")} |`;
73
- const body = rows.map((row) => `| ${fields.map((field) => markdownfield(row[field] ?? "")).join(" | ")} |`);
74
- return [header, separator, ...body].join("\n");
75
- }
76
-
77
- /** Renders records as json lines: one compact json document per line, so a stream consumer reads one record at a time with no delimiter bookkeeping. */
78
- export function jsonlinesof(records: Array<Record<string, unknown>>): string {
79
- if (records.length === 0) return "";
80
- return records.map((record) => JSON.stringify(record)).join("\n");
81
- }
82
-
83
- /** The secret field shapes an export refuses in full when their values carry no mask marker: the same shapes the importexport bundle refuses, so no format launders an unmasked store value. */
84
- export const secretfieldshapes: readonly string[] = ["secret", "token", "password", "apikey", "authorization"];
85
-
86
- /** Reads the secret named fields whose raw values carry no mask marker: every one of them refuses the export under any format, because masking at export time would launder an unmasked store value. */
87
- export function unmaskedfieldsof(records: Array<Record<string, unknown>>): string[] {
88
- const unmasked = new Set<string>();
89
- for (const record of records) {
90
- for (const [name, value] of Object.entries(record)) {
91
- if (typeof value !== "string" || value.trim() === "") continue;
92
- if (secretfieldshapes.some((shape) => name.toLowerCase().includes(shape)) && !value.includes(maskmarker))
93
- unmasked.add(name);
94
- }
95
- }
96
- return [...unmasked].sort();
97
- }
98
-
99
- /** Exports records in the descriptor format with the mask verdicts honored: the verdicts mask the fields they hold back in every format, a raw secret value refuses the export in full because masking at export time would launder an unmasked store, and a refused export writes nothing. */
100
- export function exportrecords(input: {
101
- descriptor: exportdescriptor;
102
- records: Array<Record<string, unknown>>;
103
- shapes: string[];
104
- path?: string;
105
- }): { result: exportresult; content: string } {
106
- const unmasked = unmaskedfieldsof(input.records);
107
- const maskgate = exportmaskgate({ unmasked });
108
- if (!maskgate.allowed)
109
- return {
110
- result: {
111
- descriptor: input.descriptor,
112
- bytes: 0,
113
- rows: 0,
114
- ...(input.path !== undefined ? { path: input.path } : {}),
115
- reason: maskgate.reason ?? "The export refused an unmasked value.",
116
- },
117
- content: "",
118
- };
119
- const masked = input.records.map((record) => maskexport(record, input.shapes) as Record<string, unknown>);
120
- const stringrows = masked.map(
121
- (record) =>
122
- Object.fromEntries(
123
- Object.entries(record).map(([name, value]) => [
124
- name,
125
- typeof value === "string" ? value : JSON.stringify(value),
126
- ]),
127
- ) as Record<string, string>,
128
- );
129
- if (input.descriptor.format === "csv") {
130
- const content = csvof(stringrows);
131
- return {
132
- result: {
133
- descriptor: input.descriptor,
134
- bytes: content.length,
135
- rows: stringrows.length,
136
- ...(input.path !== undefined ? { path: input.path } : {}),
137
- },
138
- content,
139
- };
140
- }
141
- if (input.descriptor.format === "json") {
142
- const content = JSON.stringify(masked, null, 2);
143
- return {
144
- result: {
145
- descriptor: input.descriptor,
146
- bytes: content.length,
147
- rows: masked.length,
148
- ...(input.path !== undefined ? { path: input.path } : {}),
149
- },
150
- content,
151
- };
152
- }
153
- if (input.descriptor.format === "jsonl") {
154
- const content = jsonlinesof(masked);
155
- return {
156
- result: {
157
- descriptor: input.descriptor,
158
- bytes: content.length,
159
- rows: masked.length,
160
- ...(input.path !== undefined ? { path: input.path } : {}),
161
- },
162
- content,
163
- };
164
- }
165
- if (input.descriptor.format === "markdown") {
166
- const content = markdownof(stringrows);
167
- return {
168
- result: {
169
- descriptor: input.descriptor,
170
- bytes: content.length,
171
- rows: stringrows.length,
172
- ...(input.path !== undefined ? { path: input.path } : {}),
173
- },
174
- content,
175
- };
176
- }
177
- const content = masked
178
- .map((record) =>
179
- Object.entries(record)
180
- .map(([name, value]) => `${name}=${typeof value === "string" ? value : JSON.stringify(value)}`)
181
- .join(" "),
182
- )
183
- .join("\n");
184
- return {
185
- result: {
186
- descriptor: input.descriptor,
187
- bytes: content.length,
188
- rows: masked.length,
189
- ...(input.path !== undefined ? { path: input.path } : {}),
190
- },
191
- content,
192
- };
193
- }
194
-
195
- /** Reads the row view of one immutable log chain: one row per entry with its identity, kind, origin, step, summary and time. */
196
- export function chainrows(entries: immutablelogentry[]): Array<Record<string, string>> {
197
- return entries.map((entry) => ({
198
- id: entry.id,
199
- kind: entry.kind,
200
- origin: entry.origin,
201
- ...(entry.stepid !== undefined ? { stepid: entry.stepid } : {}),
202
- summary: entry.summary,
203
- at: String(entry.at),
204
- }));
205
- }
206
-
207
- /** Exports one sealed run log in the descriptor format: the chain verifies before any byte leaves the store, so a broken chain refuses the export in full. */
208
- export async function exportchain(input: {
209
- descriptor: exportdescriptor;
210
- log: storedrunlog;
211
- shapes: string[];
212
- path?: string;
213
- }): Promise<{ result: exportresult; content: string }> {
214
- const verification = await verifylogchain(input.log.entries);
215
- const chaingate = exportchaingate({
216
- chainvalid: verification.valid,
217
- ...(verification.reason !== "" ? { reason: verification.reason } : {}),
218
- });
219
- if (!chaingate.allowed)
220
- return {
221
- result: {
222
- descriptor: input.descriptor,
223
- bytes: 0,
224
- rows: 0,
225
- ...(input.path !== undefined ? { path: input.path } : {}),
226
- reason: chaingate.reason ?? "The export refused an unverified chain.",
227
- },
228
- content: "",
229
- };
230
- return exportrecords({
231
- descriptor: input.descriptor,
232
- records: chainrows(input.log.entries),
233
- shapes: input.shapes,
234
- ...(input.path !== undefined ? { path: input.path } : {}),
235
- });
236
- }
237
-
238
- /** Reads the export row view of one site note: the identity and provenance beside the plain body or the sealed body exactly as the note stores it, so a sensitive note never ships its plaintext. */
239
- export function noterow(note: sitenote): Record<string, string> {
240
- return {
241
- id: note.id,
242
- origin: note.origin,
243
- title: note.title,
244
- author: note.author,
245
- sensitive: String(note.sensitive),
246
- ...(note.body !== undefined ? { body: note.body } : {}),
247
- ...(note.sealedbody !== undefined ? { sealedbody: note.sealedbody } : {}),
248
- updatedat: String(note.updatedat),
249
- };
250
- }
251
-
252
- /** Exports notes in the descriptor format with the mask verdicts honored: the plain bodies and the sealed bodies ride the same masking gate as every other record. */
253
- export function exportnotes(input: {
254
- descriptor: exportdescriptor;
255
- notes: sitenote[];
256
- shapes: string[];
257
- path?: string;
258
- }): { result: exportresult; content: string } {
259
- return exportrecords({
260
- descriptor: input.descriptor,
261
- records: input.notes.map(noterow),
262
- shapes: input.shapes,
263
- ...(input.path !== undefined ? { path: input.path } : {}),
264
- });
265
- }
266
-
267
- /** Exports extraction rows in the descriptor format: the extracted values mask under the same verdicts, and an unmasked sensitive value refuses the export. */
268
- export function exportextractions(input: {
269
- descriptor: exportdescriptor;
270
- rows: Array<Record<string, unknown>>;
271
- shapes: string[];
272
- path?: string;
273
- }): { result: exportresult; content: string } {
274
- return exportrecords({
275
- descriptor: input.descriptor,
276
- records: input.rows,
277
- shapes: input.shapes,
278
- ...(input.path !== undefined ? { path: input.path } : {}),
279
- });
280
- }
281
-
282
- /* ── Merged from minimization.ts: the 1.1.90 consolidation interns the correlated minimization logic here, so no variation of the same file lives beside another. ── */
283
- import { randomid } from "./memory.js";
284
-
285
- /**
286
- * Data minimization logic of the 1.1.79 family.
287
- * Every correlated rule for the smallest possible data footprint lives in this one module: the localfirst pass that keeps extraction, aggregation and diffing on the device and strips the identity fields the reviewed plan never listed while the localrule fields never leave the device at all; the notelemetry posture that keeps every counter inside the local memory with the worker free of outbound usage calls; the optinsync consent that stays disabled until the user turns each data class on and stamps the consent per class; the encryptsync pass that derives the key from the user passphrase, encrypts every payload before the transport and stamps the format version while refusing a sync without the passphrase; the purgeonrequest pass that deletes stored data by scope on the user request, demands the typed confirmation for the full scope, preserves the immutable audit hashes and reports every deleted key; the exportall bundle that carries every stored record — runs, memory, captures, settings and provenance — into one portable file the streaming pass walks without a size cap; the cookiejar isolation that assigns one jar per task run, scopes every cookie read and write to the active jar and seals the jar at the run completion under the user expiry window; the cleanupafterrun pass that clears the task artifacts of a completed run while the artifacts the user flagged for retention stay; and the enforcequarantine rule that holds every download in the sandbox folder until the scanner verdict arrives and releases or deletes the file by the verdict alone.
288
- * The module extends the reviewed families instead of duplicating them: the cookie entries ride the cookierecord shapes the cookie control family already writes, the quarantine verdicts reuse the scanverdict grammar and the quarantineentry record of the security family with its new lifecycle status, the exportall provenance composes the provlog entries of the pipeline family, and the purge receipts answer the audit trail the immutable log family already seals.
289
- * Every impure move stays behind an injected seam — the key derivation, the payload encryption, the sync transport and the scanner hook resolve through functions the executor wires — while every cadence, retention, scope, timing and expiry stays the user's with no code default: an absent sync cadence keeps every pass off, an absent cleanup timing leaves every pass to the explicit user action, an absent jar expiry keeps every sealed jar until the user purges it, and no minimization path ever bypasses a review.
290
- */
291
-
292
- /** Key derivation seam: one user passphrase resolves to the derived key material; the extension executor wires the webcrypto derivation, tests wire plain fixtures. The passphrase itself never persists and never rides a payload. */
293
- export type keyderive = (passphrase: string) => Promise<string>;
294
-
295
- /** Payload encryption seam: one derived key with one plaintext payload resolves to the cipher text; the extension executor wires the webcrypto cipher, tests wire plain fixtures. */
296
- export type payloadencrypt = (key: string, payload: string) => Promise<string>;
297
-
298
- /** Sync transport seam: one encrypted payload with the classes it carries resolves to the delivery state; the extension executor wires the user configured sync backend, tests wire plain fixtures and counters. */
299
- export type syncsend = (payload: string, classes: string[]) => Promise<{ delivered: boolean; endpoint?: string }>;
300
-
301
- /** Scanner hook seam: one quarantined file resolves to the verdict of the user configured scanner; the extension executor wires the configured scan hook endpoint, tests wire plain fixtures. */
302
- export type scannerhook = (entry: { path: string; reason: string; digest?: string }) => Promise<scanverdict>;
303
-
304
- /** The minimization kinds of the 1.1.79 family, listed among the available capabilities of every proposal request. */
305
- export const minimizationkinds: string[] = [
306
- "localfirst",
307
- "notelemetry",
308
- "optinsync",
309
- "encryptsync",
310
- "purgeonrequest",
311
- "exportall",
312
- "cookiejar",
313
- "cleanupartifacts",
314
- "enforcequarantine",
315
- ];
316
-
317
- /** Reads the identity field names the localfirst pass strips when the reviewed plan never listed them: the common account, session and tracking shapes a page carries that an extraction keeps only when the review names the field. */
318
- const identityfields: string[] = [
319
- "email",
320
- "phone",
321
- "name",
322
- "address",
323
- "account",
324
- "sessionid",
325
- "userid",
326
- "ip",
327
- "geolocation",
328
- "cookie",
329
- "token",
330
- ];
331
-
332
- /**
333
- * Processes one extraction through the local first pass: extraction, aggregation and diffing stay on the device, the identity fields strip unless the reviewed plan listed them, and the localrule fields of the origin strip always — they never leave the device for any review — while the report names every stripped and every local held field so the audit trail answers what the extraction stopped carrying.
334
- */
335
- export function localfirst(input: {
336
- origin: string;
337
- rows: Array<Record<string, string>>;
338
- reviewedfields?: string[];
339
- rules: localrule[];
340
- now: number;
341
- }): { rows: Array<Record<string, string>>; stripped: string[]; localheld: string[]; reason: string } {
342
- if (input.origin.trim() === "")
343
- throw new Error("The localfirst pass names its origin; an unnamespaced extraction answers no review.");
344
- const reviewed = new Set(
345
- (input.reviewedfields ?? []).map((field) => field.trim().toLowerCase()).filter((field) => field !== ""),
346
- );
347
- const localfields = new Set(
348
- input.rules
349
- .filter((rule) => rule.origin === input.origin || rule.origin === "*")
350
- .flatMap((rule) => rule.fields.map((field) => field.trim().toLowerCase()).filter((field) => field !== "")),
351
- );
352
- const stripped = new Set<string>();
353
- const localheld = new Set<string>();
354
- const rows = input.rows.map((row) => {
355
- const next: Record<string, string> = {};
356
- for (const [field, value] of Object.entries(row)) {
357
- const key = field.trim().toLowerCase();
358
- if (localfields.has(key)) {
359
- localheld.add(field);
360
- continue;
361
- }
362
- if (
363
- identityfields.some(
364
- (identity) => key === identity || key.endsWith(`_${identity}`) || key.endsWith(`-${identity}`),
365
- ) &&
366
- !reviewed.has(key)
367
- ) {
368
- stripped.add(field);
369
- continue;
370
- }
371
- next[field] = value;
372
- }
373
- return next;
374
- });
375
- return {
376
- rows,
377
- stripped: [...stripped],
378
- localheld: [...localheld],
379
- reason: `The localfirst pass kept ${rows.length} row${rows.length === 1 ? "" : "s"} on the device${stripped.size > 0 ? `, stripped ${stripped.size} identity field${stripped.size === 1 ? "" : "s"} the review never listed` : ""}${localheld.size > 0 ? ` and held ${localheld.size} local rule field${localheld.size === 1 ? "" : "s"} that never leave the device` : ""}.`,
380
- };
381
- }
382
-
383
- /**
384
- * Lists the local rule fields an outbound payload carries: the localgate and the protocol boundary refuse any payload that answers with one entry or more, because a field marked local never leaves the device for any transport.
385
- */
386
- export function localfieldsof(payload: Record<string, unknown>, rules: localrule[]): string[] {
387
- const keys = Object.keys(payload).map((key) => key.trim().toLowerCase());
388
- return rules
389
- .flatMap((rule) => rule.fields.map((field) => field.trim().toLowerCase()))
390
- .filter((field) => field !== "" && keys.includes(field));
391
- }
392
-
393
- /**
394
- * Reads the telemetry policy fixed to off: the enabled literal stays false by construction — the type makes an on state unrepresentable — and every counter keeps living inside the local memory, so the popup names the posture and the tests hold the invariant.
395
- */
396
- export function fixedtelemetrypolicy(now: number): telemetrypolicy {
397
- return { enabled: false, counters: "local", at: now };
398
- }
399
-
400
- /**
401
- * Turns the sync opt in on per data class: sync stays disabled until the user lists every data class and enables each one, every enablement stamps the consent per class for the audit trail, and a class the listing never offered never enables — an unlisted class answers no consent.
402
- */
403
- export function optinsync(input: { current?: syncsettings; offered: string[]; enable: string[]; now: number }): {
404
- settings: syncsettings;
405
- consent: Array<{ dataclass: string; at: number }>;
406
- } {
407
- const offered = input.offered.map((item) => item.trim().toLowerCase()).filter((item) => item !== "");
408
- if (offered.length === 0)
409
- throw new Error("The sync opt in lists every data class before any enablement; an empty listing enables nothing.");
410
- const enable = input.enable.map((item) => item.trim().toLowerCase()).filter((item) => item !== "");
411
- const unknown = enable.filter((item) => !offered.includes(item));
412
- if (unknown.length > 0)
413
- throw new Error(
414
- `The sync opt in never enables a class the listing did not offer: ${unknown.join(", ")} stay${unknown.length === 1 ? "s" : ""} off until the user lists them.`,
415
- );
416
- const consented = new Map((input.current?.consent ?? []).map((entry) => [entry.dataclass, entry.at]));
417
- const enabled = new Set(input.current?.enabled ?? []);
418
- for (const dataclass of enable) {
419
- enabled.add(dataclass);
420
- consented.set(dataclass, input.now);
421
- }
422
- return {
423
- settings: {
424
- enabled: [...enabled],
425
- ...(input.current?.cadence !== undefined ? { cadence: input.current.cadence } : {}),
426
- consent: [...consented.entries()].map(([dataclass, at]) => ({ dataclass, at })),
427
- at: input.now,
428
- },
429
- consent: enable.map((dataclass) => ({ dataclass, at: input.now })),
430
- };
431
- }
432
-
433
- /**
434
- * Encrypts one sync payload before the transport: the key derives from the user passphrase through the derivation seam, the payload encrypts through the cipher seam, the format version tags the envelope, and a sync without the passphrase refuses — a passphrase the user never typed never defaults in code.
435
- */
436
- export async function encryptsync(input: {
437
- payload: string;
438
- passphrase?: string;
439
- derive: keyderive;
440
- encrypt: payloadencrypt;
441
- }): Promise<{ cipher: string; formattag: string; reason: string }> {
442
- const passphrase = input.passphrase ?? "";
443
- if (passphrase.trim() === "")
444
- throw new Error(
445
- "The encryptsync pass needs the user passphrase; a sync without the passphrase never transports because the key never defaults in code.",
446
- );
447
- if (input.payload.length === 0)
448
- throw new Error("The encryptsync pass encrypts a non-empty payload; an empty sync carries nothing to sync.");
449
- const key = await input.derive(passphrase);
450
- const cipher = await input.encrypt(key, input.payload);
451
- const formattag = "devthink-sync-1";
452
- return {
453
- cipher,
454
- formattag,
455
- reason: `The encryptsync pass derived its key from the user passphrase, encrypted the ${input.payload.length} character payload and stamped the ${formattag} format version; the passphrase never rides the envelope.`,
456
- };
457
- }
458
-
459
- /**
460
- * Runs one sync pass through the seams: only the classes the user opted in ride the payload, the encryptsync pass encrypts before the transport seam fires, and the syncrecord carries the payload hash with the format tag — a plaintext or an unconsented class never reaches the transport.
461
- */
462
- export async function syncpass(input: {
463
- payload: string;
464
- classes: string[];
465
- settings: syncsettings;
466
- passphrase?: string;
467
- derive: keyderive;
468
- encrypt: payloadencrypt;
469
- send: syncsend;
470
- now: number;
471
- }): Promise<{ record: syncrecord; delivered: boolean; reason: string }> {
472
- const enabled = new Set(input.settings.enabled.map((item) => item.toLowerCase()));
473
- const unconsented = input.classes.filter((item) => !enabled.has(item.toLowerCase()));
474
- if (unconsented.length > 0)
475
- throw new Error(
476
- `The sync pass carries the classes ${unconsented.join(", ")} the user never opted in; an unconsented class never reaches the transport.`,
477
- );
478
- const encrypted = await encryptsync({
479
- payload: input.payload,
480
- ...(input.passphrase !== undefined ? { passphrase: input.passphrase } : {}),
481
- derive: input.derive,
482
- encrypt: input.encrypt,
483
- });
484
- const delivery = await input.send(encrypted.cipher, input.classes);
485
- const record: syncrecord = {
486
- id: randomid(),
487
- classes: input.classes,
488
- payloadhash: await shaof(encrypted.cipher),
489
- formattag: encrypted.formattag,
490
- encrypted: true,
491
- syncedat: input.now,
492
- };
493
- return {
494
- record,
495
- delivered: delivery.delivered,
496
- reason: `The sync pass encrypted the ${input.classes.join(", ")} payload, delivered it${delivery.endpoint !== undefined ? ` through ${delivery.endpoint}` : ""} and recorded the payload hash; every class rode with its own consent stamp.`,
497
- };
498
- }
499
-
500
- /**
501
- * Deletes stored data by scope on the user request: the purge answers the inventory the device holds, the full scope demands the typed confirmation phrase, the audit class never deletes because the immutable audit hashes survive every purge, and the report lists every deleted key for the audit trail.
502
- */
503
- export function purgeonrequest(input: {
504
- policy: purgepolicy;
505
- typed: string;
506
- inventory: datainventory[];
507
- now: number;
508
- }): { deleted: string[]; kept: string[]; audithashespreserved: boolean; reason: string } {
509
- const scope = input.policy.scope.map((item) => item.trim().toLowerCase()).filter((item) => item !== "");
510
- if (scope.length === 0)
511
- throw new Error("The purge request names its scope; a purge without a scope deletes nothing.");
512
- const full =
513
- scope.includes("all") ||
514
- (scope.includes("runs") &&
515
- scope.includes("memory") &&
516
- scope.includes("captures") &&
517
- scope.includes("settings") &&
518
- scope.includes("provenance"));
519
- if (full && input.typed.trim() !== input.policy.confirmation)
520
- throw new Error("The full scope purge needs the typed confirmation phrase; a purge nobody typed out never runs.");
521
- const deleted: string[] = [];
522
- const kept: string[] = [];
523
- for (const entry of input.inventory) {
524
- if (entry.dataclass === "audit") {
525
- kept.push(entry.key);
526
- continue;
527
- }
528
- if (scope.includes(entry.dataclass) || full) deleted.push(entry.key);
529
- else kept.push(entry.key);
530
- }
531
- return {
532
- deleted,
533
- kept,
534
- audithashespreserved: true,
535
- reason: `The purge deleted ${deleted.length} stored key${deleted.length === 1 ? "" : "s"} of the ${scope.join(", ")} scope${full ? " after the typed confirmation" : ""} — ${deleted.join(", ") || "none"} — while the audit hashes survived${kept.length > 0 ? ` and ${kept.length} key${kept.length === 1 ? "" : "s"} stayed` : ""}; every deleted key entered the audit trail.`,
536
- };
537
- }
538
-
539
- /**
540
- * Bundles every stored record into one portable file: the runs, the memory items, the captures, the settings and the provenance entries ride one exportall bundle the user explicitly asked for, the record count and the byte size name what left, and nothing here caps the size because a bundle the user asked for streams whole.
541
- */
542
- export function exportall(input: {
543
- runs: string[];
544
- memory: string[];
545
- captures: string[];
546
- settings: boolean;
547
- provenance: string[];
548
- now: number;
549
- }): exportallbundle {
550
- if (
551
- input.runs.length === 0 &&
552
- input.memory.length === 0 &&
553
- input.captures.length === 0 &&
554
- input.provenance.length === 0 &&
555
- !input.settings
556
- )
557
- throw new Error("The exportall bundle carries the stored records; a device holding nothing exports nothing.");
558
- const records =
559
- input.runs.length +
560
- input.memory.length +
561
- input.captures.length +
562
- input.provenance.length +
563
- (input.settings ? 1 : 0);
564
- const bytes = JSON.stringify({
565
- runs: input.runs,
566
- memory: input.memory,
567
- captures: input.captures,
568
- settings: input.settings,
569
- provenance: input.provenance,
570
- }).length;
571
- return {
572
- id: randomid(),
573
- runs: input.runs,
574
- memory: input.memory,
575
- captures: input.captures,
576
- settings: input.settings,
577
- provenance: input.provenance,
578
- records,
579
- bytes,
580
- at: input.now,
581
- };
582
- }
583
-
584
- /**
585
- * Streams one exportall bundle in chunks without a size cap: the chunk size stays the explicit choice of the caller because no code ceiling ever refuses a large bundle; the chunks concatenate back into the bundle payload byte for byte.
586
- */
587
- export function streambundle(input: { bundle: exportallbundle; chunksize: number }): string[] {
588
- if (!Number.isInteger(input.chunksize) || input.chunksize <= 0)
589
- throw new Error(
590
- "The bundle streaming names its chunk size as a positive whole number of characters; the chunk size never defaults in code.",
591
- );
592
- const payload = JSON.stringify(input.bundle);
593
- const chunks: string[] = [];
594
- for (let offset = 0; offset < payload.length; offset += input.chunksize)
595
- chunks.push(payload.slice(offset, offset + input.chunksize));
596
- return chunks;
597
- }
598
-
599
- /**
600
- * Assigns one cookie jar to a task run: the jar carries its id, the run it isolates and the cookie entries scoped inside it, so one task run never shares its cookie state with another; an empty jar at the run start holds nothing until a reviewed cookie step writes.
601
- */
602
- export function newjar(input: { runid: string; now: number; id?: string }): cookiejarrecord {
603
- if (input.runid.trim() === "") throw new Error("The cookie jar names its run; an unnamespaced jar isolates no task.");
604
- return {
605
- jarid: input.id ?? randomid(),
606
- runid: input.runid,
607
- cookies: [],
608
- sealed: false,
609
- createdat: input.now,
610
- updatedat: input.now,
611
- };
612
- }
613
-
614
- /**
615
- * Scopes one cookie read or write to the active jar: a write appends or replaces the entries inside the jar it belongs to — and a sealed jar refuses every write because a completed run never changes its cookie state — while a read answers exactly the entries the jar holds for the domain the step named.
616
- */
617
- export function jarscope(input: {
618
- jar: cookiejarrecord;
619
- operation: "read" | "write";
620
- cookies?: Array<{ name: string; domain: string; value: string; path?: string; expiresat?: number }>;
621
- domain?: string;
622
- now: number;
623
- }): {
624
- jar: cookiejarrecord;
625
- entries: Array<{ name: string; domain: string; value: string; path?: string; expiresat?: number }>;
626
- reason: string;
627
- } {
628
- if (input.operation === "write") {
629
- if (input.jar.sealed)
630
- throw new Error(
631
- `The jar ${input.jar.jarid} of the run ${input.jar.runid} sealed at the run completion; a sealed jar refuses every write.`,
632
- );
633
- const cookies = input.cookies ?? [];
634
- if (cookies.length === 0)
635
- throw new Error("The jar write carries its cookie entries; an empty write changes nothing.");
636
- const merged = [...input.jar.cookies];
637
- for (const cookie of cookies) {
638
- const index = merged.findIndex((entry) => entry.name === cookie.name && entry.domain === cookie.domain);
639
- if (index >= 0) merged[index] = cookie;
640
- else merged.push(cookie);
641
- }
642
- return {
643
- jar: { ...input.jar, cookies: merged, updatedat: input.now },
644
- entries: cookies,
645
- reason: `The jar write scoped ${cookies.length} cookie entr${cookies.length === 1 ? "y" : "ies"} inside the jar ${input.jar.jarid} of the run ${input.jar.runid}; no other run reads them.`,
646
- };
647
- }
648
- const entries = input.jar.cookies.filter((entry) => input.domain === undefined || entry.domain === input.domain);
649
- return {
650
- jar: input.jar,
651
- entries,
652
- reason: `The jar read answered ${entries.length} cookie entr${entries.length === 1 ? "y" : "ies"} of the jar ${input.jar.jarid}${input.domain !== undefined ? ` for the domain ${input.domain}` : ""}; the read stays inside the active jar.`,
653
- };
654
- }
655
-
656
- /**
657
- * Seals the jar of a completed run: the seal stamps the completion time, the user configured expiry window marks when the sealed jar's entries expire for the automatic cleanup, and an absent window keeps the jar until the user purges it because the expiry never defaults in code.
658
- */
659
- export function sealjar(input: { jar: cookiejarrecord; now: number; expiry?: number }): cookiejarrecord {
660
- if (input.jar.sealed) return { ...input.jar, updatedat: input.now };
661
- if (input.expiry !== undefined && !(input.expiry > 0))
662
- throw new Error(
663
- "The jar expiry stays a positive number of milliseconds the user chose; the expiry never defaults in code.",
664
- );
665
- return {
666
- ...input.jar,
667
- sealed: true,
668
- sealedat: input.now,
669
- ...(input.expiry !== undefined ? { expiresat: input.now + input.expiry } : {}),
670
- updatedat: input.now,
671
- };
672
- }
673
-
674
- /**
675
- * Clears the task artifacts of a completed run: the classes the cleanup schedule names leave while the artifacts the user flagged for retention stay, and an absent class list clears nothing because the cleanup stays the user's schedule with no code default.
676
- */
677
- export function cleanupafterrun(input: {
678
- runid: string;
679
- artifacts: Array<{ id: string; dataclass: string; retained?: boolean }>;
680
- classes: string[];
681
- now: number;
682
- }): { cleared: string[]; kept: string[]; reason: string } {
683
- if (input.runid.trim() === "")
684
- throw new Error("The cleanup pass names its run; an unnamespaced cleanup clears nothing.");
685
- const classes = input.classes.map((item) => item.trim().toLowerCase()).filter((item) => item !== "");
686
- if (classes.includes("audit"))
687
- throw new Error(
688
- "The cleanup pass never deletes the audit history without the user consent; the immutable hashes survive every pass.",
689
- );
690
- const cleared: string[] = [];
691
- const kept: string[] = [];
692
- for (const artifact of input.artifacts) {
693
- if (artifact.retained === true) {
694
- kept.push(artifact.id);
695
- continue;
696
- }
697
- if (classes.includes(artifact.dataclass.toLowerCase())) cleared.push(artifact.id);
698
- else kept.push(artifact.id);
699
- }
700
- return {
701
- cleared,
702
- kept,
703
- reason: `The cleanup pass cleared ${cleared.length} artifact${cleared.length === 1 ? "" : "s"} of the run ${input.runid} under the classes ${classes.join(", ") || "none"} while ${kept.length} stayed${kept.length > 0 ? " — the retained artifacts the user flagged never leave" : ""}.`,
704
- };
705
- }
706
-
707
- /**
708
- * Holds a download in the sandbox folder until the scanner verdict arrives and releases or deletes the file by the verdict alone: a clean verdict releases, a flagged verdict deletes, and a pending or error verdict holds — a file without a verdict never opens, and a hook failure never releases anything.
709
- */
710
- export function enforcequarantine(input: {
711
- path: string;
712
- reason: string;
713
- scan?: scanverdict;
714
- digest?: string;
715
- now: number;
716
- id?: string;
717
- }): { entry: quarantineentry; action: "hold" | "release" | "delete"; reason: string } {
718
- if (input.path.trim() === "")
719
- throw new Error("The quarantine names its file path; a download without a path quarantines nowhere.");
720
- const scan = input.scan ?? "pending";
721
- const entry: quarantineentry = {
722
- id: input.id ?? randomid(),
723
- path: input.path,
724
- reason: input.reason,
725
- scan,
726
- status: scan === "clean" ? "released" : scan === "flagged" ? "deleted" : "held",
727
- ...(input.digest !== undefined && /^[0-9a-f]{64}$/.test(input.digest.trim().toLowerCase())
728
- ? { digest: input.digest.trim().toLowerCase() }
729
- : {}),
730
- at: input.now,
731
- updatedat: input.now,
732
- };
733
- if (scan === "clean")
734
- return {
735
- entry,
736
- action: "release",
737
- reason: `The scanner verdict read clean so the quarantined file ${input.path} releases from the sandbox folder to the reviewed download flow.`,
738
- };
739
- if (scan === "flagged")
740
- return {
741
- entry,
742
- action: "delete",
743
- reason: `The scanner verdict read flagged so the quarantined file ${input.path} deletes from the sandbox folder; a flagged download never reaches the downloads folder.`,
744
- };
745
- return {
746
- entry,
747
- action: "hold",
748
- reason: `The scanner verdict reads ${scan} so the quarantined file ${input.path} stays held in the sandbox folder; a file without a clean verdict never opens.`,
749
- };
750
- }
751
-
752
- /**
753
- * Builds one data inventory entry of a stored family: the serialized size and the record count answer what the device holds, so the purge and the exportall bundle scope exactly over the inventory and never guess.
754
- */
755
- export function inventoryentry(input: { key: string; dataclass: string; value: unknown; now: number }): datainventory {
756
- if (input.key.trim() === "")
757
- throw new Error("The inventory entry names its storage key; an unnamespaced key answers no family.");
758
- const serialized = JSON.stringify(input.value ?? null) ?? "null";
759
- const records = Array.isArray(input.value)
760
- ? input.value.length
761
- : input.value === undefined || input.value === null
762
- ? 0
763
- : 1;
764
- return { key: input.key, dataclass: input.dataclass, size: serialized.length, records, at: input.now };
765
- }
766
-
767
- /** Hashes one payload with the subtle crypto digest when available and falls back to the plain length mix the tests seed, because the payload hash names the sync and never decrypts anything. */
768
- async function shaof(payload: string): Promise<string> {
769
- const subtle = globalThis.crypto?.subtle;
770
- if (subtle !== undefined) {
771
- const digest = await subtle.digest("SHA-256", new TextEncoder().encode(payload));
772
- return [...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
773
- }
774
- return `len-${payload.length}`;
775
- }
776
-
777
- /* ── Merged from portability.ts: the 1.1.90 consolidation interns the correlated portability logic here, so no variation of the same file lives beside another. ── */
778
- /**
779
- * Importexport and dropimport logic of the 1.1.65 family.
780
- * Settings and data move between profiles here: the importexport payload carries the originprofiles, the siteprofiles, the site notes and the preferences of one profile while the secretvault values and the unmasked logs never enter any bundle under any flag; and the dropimport accepts the csv, json and workflow files the user drops on the optionspage and the dashboardpage with its file kind detection naming what it found.
781
- * No bundle is ever forced: every import lands under the same review, and no export path ever ships a secret or an unmasked log value.
782
- */
783
-
784
- /** Builds one importexport payload: the settings and data bundle of one profile with the originprofiles, the siteprofiles, the notes and the preferences and the honest exclusion list of everything that stayed out. */
785
- export function importexportpayloadof(input: {
786
- profile: string;
787
- originprofiles: Array<Record<string, unknown>>;
788
- siteprofiles: Array<Record<string, unknown>>;
789
- notes: Array<Record<string, unknown>>;
790
- preferences: Record<string, unknown>;
791
- at: number;
792
- }): importexportpayload {
793
- if (input.profile.trim() === "") throw new Error("The importexport payload needs its profile name.");
794
- const secrets =
795
- [...input.originprofiles, ...input.siteprofiles, ...input.notes, ...Object.values(input.preferences)].find(
796
- (record) => secretcarrying(record),
797
- ) !== undefined;
798
- const gate = importexportgate({ containssecrets: secrets, unmaskedlogs: false });
799
- if (!gate.allowed) throw new Error(gate.reason);
800
- return {
801
- version: 1,
802
- kind: "settings",
803
- profile: input.profile.trim(),
804
- exportedat: input.at,
805
- contents: {
806
- originprofiles: input.originprofiles,
807
- siteprofiles: input.siteprofiles,
808
- notes: input.notes,
809
- preferences: input.preferences,
810
- },
811
- exclusions: ["secretvault values", "unmasked logs"],
812
- };
813
- }
814
-
815
- /** Detects whether one record carries a secret vault value by its key shapes so the export refuses before any bundle ships. */
816
- function secretcarrying(record: unknown): boolean {
817
- if (record === null || typeof record !== "object") return false;
818
- const entries = Object.entries(record as Record<string, unknown>);
819
- const secretkeys = ["secret", "token", "password", "apikey", "authorization"];
820
- return entries.some(
821
- ([key, value]) =>
822
- secretkeys.some((shape) => key.toLowerCase().includes(shape)) && typeof value === "string" && value.trim() !== "",
823
- );
824
- }
825
-
826
- /** Validates one importexport payload for import: a bundle that carries secretvault values or unmasked logs refuses under any flag, and the payload needs its profile and contents. */
827
- export function importexportvalidate(payload: importexportpayload): { ok: boolean; reason: string } {
828
- const records = [
829
- ...payload.contents.originprofiles,
830
- ...payload.contents.siteprofiles,
831
- ...payload.contents.notes,
832
- ...Object.values(payload.contents.preferences),
833
- ];
834
- const preferencessecrets = Object.entries(payload.contents.preferences).some(([key, value]) =>
835
- secretcarrying({ [key]: value }),
836
- );
837
- const gate = importexportgate({
838
- containssecrets: records.some((record) => secretcarrying(record)) || preferencessecrets,
839
- unmaskedlogs: payload.contents.unmaskedlogs !== undefined,
840
- });
841
- if (!gate.allowed) return { ok: false, reason: gate.reason ?? "The importexport bundle refuses." };
842
- if (payload.profile.trim() === "") return { ok: false, reason: "The importexport bundle needs its profile name." };
843
- return {
844
- ok: true,
845
- reason: `The importexport bundle of the profile ${payload.profile} validates with ${payload.contents.originprofiles.length} origin profile${payload.contents.originprofiles.length === 1 ? "" : "s"}, ${payload.contents.siteprofiles.length} site profile${payload.contents.siteprofiles.length === 1 ? "" : "s"} and ${payload.contents.notes.length} note${payload.contents.notes.length === 1 ? "" : "s"}; ${payload.exclusions.join(" and ")} never enter any bundle.`,
846
- };
847
- }
848
-
849
- /** Applies one importexport payload to the current preferences: the imported keys replace their current values while the secrets exclusion list stays untouched because no secret ever rides a bundle. */
850
- export function applyimport(
851
- payload: importexportpayload,
852
- current: Record<string, unknown>,
853
- ): { preferences: Record<string, unknown>; applied: string[] } {
854
- const validation = importexportvalidate(payload);
855
- if (!validation.ok) throw new Error(validation.reason);
856
- const applied = Object.keys(payload.contents.preferences);
857
- return { preferences: { ...current, ...payload.contents.preferences }, applied };
858
- }
859
-
860
- /** Detects the file kind of one dropped file: the extension names csv, json and workflow files while the head of the content confirms the json and workflow shapes. */
861
- export function detectfilekind(filename: string, head: string): dropimportsession["kind"] | undefined {
862
- const extension = filename.toLowerCase().split(".").pop() ?? "";
863
- if (extension === "csv") return "csv";
864
- if (extension === "json") {
865
- const trimmed = head.trim();
866
- if (trimmed.startsWith("{") || trimmed.startsWith("[")) return trimmed.includes('"steps"') ? "workflow" : "json";
867
- return "json";
868
- }
869
- if (extension === "yaml" || extension === "yml") return "workflow";
870
- return undefined;
871
- }
872
-
873
- /** Builds one dropimport session of a file dropped on the optionspage or the dashboardpage: the detected kind names what the import path takes while an unknown kind refuses the session. */
874
- export function dropimportof(input: { filename: string; bytes: number; head: string; at: number }): dropimportsession {
875
- if (input.filename.trim() === "") throw new Error("The dropimport session needs its filename.");
876
- const kind = detectfilekind(input.filename, input.head);
877
- if (kind === undefined)
878
- throw new Error(
879
- `The dropimport detects no csv, json or workflow kind in ${input.filename}; the import path refuses the file.`,
880
- );
881
- return {
882
- id: `${input.filename}:${input.at}`,
883
- filename: input.filename,
884
- kind,
885
- bytes: input.bytes,
886
- accepted: true,
887
- at: input.at,
888
- };
889
- }