@wenathlan/devthink 2.0.13 → 2.0.15

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 +27 -0
  2. package/accounts.ts +2087 -0
  3. package/agent.ts +2331 -0
  4. package/antigravity.ts +2139 -0
  5. package/apifreeze.ts +4146 -0
  6. package/auth.ts +642 -0
  7. package/background.ts +16837 -0
  8. package/bridge.ts +2080 -0
  9. package/build.ts +54 -0
  10. package/bun.ts +26 -0
  11. package/capture.ts +1614 -0
  12. package/checksecrets.ts +72 -0
  13. package/cjs.ts +9 -0
  14. package/cli.ts +3500 -0
  15. package/commands.ts +1101 -0
  16. package/companion.ts +450 -0
  17. package/compatibility.ts +37 -0
  18. package/config.ts +3137 -0
  19. package/constants.ts +943 -0
  20. package/core.ts +1473 -0
  21. package/crossbrowser.ts +1359 -0
  22. package/dashdone.ts +605 -0
  23. package/data.ts +943 -0
  24. package/database.ts +133 -0
  25. package/debug.ts +850 -0
  26. package/deno.ts +27 -0
  27. package/devthink.ts +707 -0
  28. package/dist/accounts.d.ts +409 -0
  29. package/dist/agent.d.ts +837 -0
  30. package/dist/antigravity.d.ts +380 -0
  31. package/dist/apifreeze.d.ts +113 -0
  32. package/dist/auth.d.ts +224 -0
  33. package/{bridge.d.ts → dist/bridge.d.ts} +1 -1
  34. package/dist/capture.d.ts +595 -0
  35. package/dist/cjs.d.ts +6 -0
  36. package/{cli.js → dist/cli.js} +2 -2
  37. package/dist/commands.d.ts +310 -0
  38. package/dist/compatibility.d.ts +17 -0
  39. package/dist/config.d.ts +1853 -0
  40. package/dist/constants.d.ts +637 -0
  41. package/dist/core.d.ts +361 -0
  42. package/dist/data.d.ts +330 -0
  43. package/dist/database.d.ts +24 -0
  44. package/dist/debug.d.ts +270 -0
  45. package/dist/engine.d.ts +74 -0
  46. package/dist/environments.d.ts +244 -0
  47. package/dist/evidence.d.ts +277 -0
  48. package/dist/export.d.ts +311 -0
  49. package/dist/fingerprint.d.ts +594 -0
  50. package/dist/flow.d.ts +300 -0
  51. package/dist/gates.d.ts +103 -0
  52. package/dist/identity.d.ts +51 -0
  53. package/dist/ids.d.ts +7 -0
  54. package/dist/llm.d.ts +652 -0
  55. package/dist/models.d.ts +1807 -0
  56. package/dist/net.d.ts +325 -0
  57. package/dist/neutral.d.ts +41 -0
  58. package/dist/oauth.d.ts +703 -0
  59. package/dist/page.d.ts +1166 -0
  60. package/dist/perf.d.ts +481 -0
  61. package/dist/plan.d.ts +134 -0
  62. package/dist/project.d.ts +395 -0
  63. package/dist/providers.d.ts +37 -0
  64. package/dist/quota.d.ts +453 -0
  65. package/dist/recovery.d.ts +621 -0
  66. package/dist/request.d.ts +1492 -0
  67. package/dist/run.d.ts +779 -0
  68. package/dist/runtime.d.ts +212 -0
  69. package/dist/search.d.ts +375 -0
  70. package/dist/security.d.ts +534 -0
  71. package/dist/serve.d.ts +136 -0
  72. package/dist/session.d.ts +296 -0
  73. package/dist/storage.d.ts +53 -0
  74. package/dist/streaming.d.ts +1119 -0
  75. package/dist/swarm.d.ts +328 -0
  76. package/dist/tools.d.ts +170 -0
  77. package/dist/types.d.ts +8357 -0
  78. package/dist/utils.d.ts +82 -0
  79. package/dist/version.d.ts +7 -0
  80. package/dist/versionregistry.d.ts +406 -0
  81. package/dist/views.d.ts +579 -0
  82. package/dist/workbenchsession.d.ts +69 -0
  83. package/dist/workflow.d.ts +649 -0
  84. package/docs/readme.md +92 -0
  85. package/docs/schemas/antigravity.json +192 -0
  86. package/engine.ts +1524 -0
  87. package/environments.ts +1005 -0
  88. package/evidence.ts +643 -0
  89. package/export.ts +889 -0
  90. package/fingerprint.ts +1458 -0
  91. package/flow.ts +1190 -0
  92. package/gates.ts +307 -0
  93. package/gateway.ts +34 -0
  94. package/hardening.ts +191 -0
  95. package/headless.ts +369 -0
  96. package/http.ts +2110 -0
  97. package/identity.ts +246 -0
  98. package/ids.ts +26 -0
  99. package/index.ts +369 -0
  100. package/llm.ts +2682 -0
  101. package/mcp.ts +1835 -0
  102. package/{memory.js → memory.ts} +3834 -2310
  103. package/models.ts +3805 -0
  104. package/modes.ts +43 -0
  105. package/net.ts +1214 -0
  106. package/neutral.ts +362 -0
  107. package/node.ts +26 -0
  108. package/oauth.ts +2216 -0
  109. package/offscreen.ts +170 -0
  110. package/pack.ts +956 -0
  111. package/package.json +175 -82
  112. package/page.ts +7824 -0
  113. package/pagebridge.ts +1116 -0
  114. package/perf.ts +957 -0
  115. package/plan.ts +672 -0
  116. package/plugin.ts +76 -0
  117. package/{policy.js → policy.ts} +5073 -4679
  118. package/prisma.config.ts +27 -0
  119. package/progress.ts +1308 -0
  120. package/project.ts +1291 -0
  121. package/protocol.ts +4187 -0
  122. package/providers.ts +246 -0
  123. package/quota.ts +1887 -0
  124. package/recovery.ts +2433 -0
  125. package/request.ts +4482 -0
  126. package/run.ts +2135 -0
  127. package/runtime.ts +526 -0
  128. package/search.ts +1760 -0
  129. package/security.ts +1673 -0
  130. package/serve.ts +419 -0
  131. package/server.ts +5538 -0
  132. package/session.ts +1162 -0
  133. package/storage.ts +182 -0
  134. package/streaming.ts +4101 -0
  135. package/swarm.ts +884 -0
  136. package/sync.ts +56 -0
  137. package/tests/agentcert.test.ts +128 -0
  138. package/tests/agentfleet.test.ts +214 -0
  139. package/tests/agentreview.test.ts +241 -0
  140. package/tests/agentstream.test.ts +335 -0
  141. package/tests/agentwork.test.ts +356 -0
  142. package/tests/apifreeze.test.ts +290 -0
  143. package/tests/apimap.test.ts +112 -0
  144. package/tests/approvalgate.test.ts +175 -0
  145. package/tests/artifactmanifest.test.ts +204 -0
  146. package/tests/attentionfeed.test.ts +163 -0
  147. package/tests/backgroundruns.test.ts +100 -0
  148. package/tests/batchscheduling.test.ts +131 -0
  149. package/tests/blackboard.test.ts +238 -0
  150. package/tests/bridge.test.ts +1088 -0
  151. package/tests/browsercoverage.test.ts +134 -0
  152. package/tests/browserpolyfills.test.ts +124 -0
  153. package/tests/build.mjs +1260 -0
  154. package/tests/clientauth.test.ts +223 -0
  155. package/tests/clitools.test.ts +578 -0
  156. package/tests/confirmgates.test.ts +190 -0
  157. package/tests/containerpack.test.ts +143 -0
  158. package/tests/controlflow.test.ts +2120 -0
  159. package/tests/coordination.test.ts +345 -0
  160. package/tests/copyscreen.test.ts +139 -0
  161. package/tests/core.test.ts +417 -0
  162. package/tests/costcert.test.ts +95 -0
  163. package/tests/crossbrowser.test.ts +115 -0
  164. package/tests/cspaudit.test.ts +79 -0
  165. package/tests/dashdone.test.ts +615 -0
  166. package/tests/datagrid.test.ts +137 -0
  167. package/tests/doccheck.test.ts +107 -0
  168. package/tests/dombatch.test.ts +109 -0
  169. package/tests/emulation.test.ts +282 -0
  170. package/tests/environments.test.ts +259 -0
  171. package/tests/evidenceviews.test.ts +88 -0
  172. package/tests/exporttools.test.ts +185 -0
  173. package/tests/firefoxprep.test.ts +134 -0
  174. package/tests/flowlibrary.test.ts +242 -0
  175. package/tests/flowrun.test.ts +303 -0
  176. package/tests/forensics.test.ts +439 -0
  177. package/tests/gateway.test.ts +939 -0
  178. package/tests/headlesslib.test.ts +171 -0
  179. package/tests/httpstream.test.ts +277 -0
  180. package/tests/immutablelog.test.ts +157 -0
  181. package/tests/inboundguard.test.ts +177 -0
  182. package/tests/lazyload.test.ts +72 -0
  183. package/tests/librarymodes.test.ts +623 -0
  184. package/tests/llm.test.ts +696 -0
  185. package/tests/manifest.test.ts +226 -0
  186. package/tests/maskinputs.test.ts +150 -0
  187. package/tests/matrixverify.test.ts +111 -0
  188. package/tests/mavenpack.test.ts +75 -0
  189. package/tests/mcpserver.test.ts +704 -0
  190. package/tests/memory.test.ts +4155 -0
  191. package/tests/memorycare.test.ts +230 -0
  192. package/tests/migrateplan.test.ts +349 -0
  193. package/tests/minimization.test.ts +414 -0
  194. package/tests/modelroute.test.ts +112 -0
  195. package/tests/multiagent.test.ts +440 -0
  196. package/tests/nativebridge.test.ts +807 -0
  197. package/tests/navigation.test.ts +284 -0
  198. package/tests/nugetpack.test.ts +83 -0
  199. package/tests/orchestration.test.ts +449 -0
  200. package/tests/originpolicy.test.ts +405 -0
  201. package/tests/outputcompare.test.ts +97 -0
  202. package/tests/package.test.ts +91 -0
  203. package/tests/pagebridge.test.ts +5909 -0
  204. package/tests/pentest.test.ts +75 -0
  205. package/tests/perfrecords.test.ts +257 -0
  206. package/tests/permdiff.test.ts +85 -0
  207. package/tests/phishguard.test.ts +128 -0
  208. package/tests/pickerviews.test.ts +155 -0
  209. package/tests/pipelines.test.ts +381 -0
  210. package/tests/planlint.test.ts +240 -0
  211. package/tests/planreview.test.ts +383 -0
  212. package/tests/platformtargets.test.ts +56 -0
  213. package/tests/policy.test.ts +8284 -0
  214. package/tests/portability.test.ts +96 -0
  215. package/tests/progress.test.ts +1036 -0
  216. package/tests/promptlibrary.test.ts +116 -0
  217. package/tests/protocol.test.ts +4793 -0
  218. package/tests/publishingpipeline.test.ts +314 -0
  219. package/tests/quickactions.test.ts +164 -0
  220. package/tests/readiness.test.ts +78 -0
  221. package/tests/recipes.test.ts +163 -0
  222. package/tests/redactshots.test.ts +216 -0
  223. package/tests/registernvidiakeys.ts +51 -0
  224. package/tests/relayserve.test.ts +304 -0
  225. package/tests/releasenotes.test.ts +276 -0
  226. package/tests/resilience.test.ts +467 -0
  227. package/tests/resourceaware.test.ts +187 -0
  228. package/tests/runbudget.test.ts +125 -0
  229. package/tests/runhistory.test.ts +299 -0
  230. package/tests/runreplay.test.ts +113 -0
  231. package/tests/runresume.test.ts +184 -0
  232. package/tests/runstate.test.ts +288 -0
  233. package/tests/runtimeadapters.test.ts +251 -0
  234. package/tests/safariskeleton.test.ts +115 -0
  235. package/tests/sandboxframe.test.ts +196 -0
  236. package/tests/sbom.test.ts +105 -0
  237. package/tests/secretvault.test.ts +188 -0
  238. package/tests/selcache.test.ts +111 -0
  239. package/tests/sessioninterface.test.ts +791 -0
  240. package/tests/sessions.test.ts +591 -0
  241. package/tests/siteprefs.test.ts +228 -0
  242. package/tests/snapshotdelta.test.ts +165 -0
  243. package/tests/statusviews.test.ts +237 -0
  244. package/tests/stepmeter.test.ts +200 -0
  245. package/tests/streamparse.test.ts +163 -0
  246. package/tests/stylepruning.test.ts +90 -0
  247. package/tests/surfaces.test.ts +282 -0
  248. package/tests/sweep.test.ts +78 -0
  249. package/tests/syncbridge.test.ts +164 -0
  250. package/tests/taskqueue.test.ts +206 -0
  251. package/tests/toolcalls.test.ts +336 -0
  252. package/tests/toolcatalog.test.ts +201 -0
  253. package/tests/torture-agent.test.ts +706 -0
  254. package/tests/torture-auth.test.ts +461 -0
  255. package/tests/torture-commands.test.ts +895 -0
  256. package/tests/torture-data.test.ts +923 -0
  257. package/tests/torture-environments.test.ts +285 -0
  258. package/tests/torture-export.test.ts +263 -0
  259. package/tests/torture-http.test.ts +1379 -0
  260. package/tests/torture-llm.test.ts +808 -0
  261. package/tests/torture-memory.test.ts +1084 -0
  262. package/tests/torture-plan.test.ts +829 -0
  263. package/tests/torture-run.test.ts +626 -0
  264. package/tests/torture-session.test.ts +928 -0
  265. package/tests/torture-swarm.test.ts +1245 -0
  266. package/tests/torture-tools.test.ts +964 -0
  267. package/tests/torture-workflow.test.ts +2354 -0
  268. package/tests/tourviews.test.ts +52 -0
  269. package/tests/transparency.test.ts +147 -0
  270. package/tests/trigger.test.ts +941 -0
  271. package/tests/v1sunset.test.ts +428 -0
  272. package/tests/virtlist.test.ts +94 -0
  273. package/tests/vision.test.ts +540 -0
  274. package/tests/vsixpack.test.ts +145 -0
  275. package/tests/webapi.test.ts +523 -0
  276. package/tests/workflow.test.ts +1785 -0
  277. package/tests/workfloweditor.test.ts +759 -0
  278. package/tests/xpipack.test.ts +128 -0
  279. package/tools.ts +734 -0
  280. package/types.ts +9908 -0
  281. package/umd.ts +9 -0
  282. package/utils.ts +249 -0
  283. package/version.ts +8 -0
  284. package/versionregistry.ts +1310 -0
  285. package/views.ts +2153 -0
  286. package/vitest.config.ts +12 -0
  287. package/web/App.tsx +78 -0
  288. package/web/README.md +47 -0
  289. package/web/capacitor.config.ts +46 -0
  290. package/web/const.ts +19 -0
  291. package/web/control.shell.tsx +66 -0
  292. package/web/dashboardpage.ts +554 -0
  293. package/web/db.ts +493 -0
  294. package/web/design.html +541 -0
  295. package/web/gatewayview/Gateway.tsx +345 -0
  296. package/web/gatewayview/config.ts +1042 -0
  297. package/web/gatewayview/definition.ts +174 -0
  298. package/web/icons.ts +15 -0
  299. package/{manifest.json → web/manifest.json} +1 -1
  300. package/web/optionspage.ts +691 -0
  301. package/web/popup.ts +1651 -0
  302. package/web/schema.prisma +176 -0
  303. package/web/sidepanel.ts +7941 -0
  304. package/web/sitemanifest.json +13 -0
  305. package/web/terminal-ui.tsx +622 -0
  306. package/web/transparencypage.ts +541 -0
  307. package/web/vite.config.ts +39 -0
  308. package/web/workspace.ts +23 -0
  309. package/workbenchmemory.ts +104 -0
  310. package/workbenchsession.ts +304 -0
  311. package/workflow.ts +4495 -0
  312. package/bridge.d.ts.map +0 -1
  313. package/bridge.js +0 -1877
  314. package/bun.d.ts.map +0 -1
  315. package/bun.js +0 -52329
  316. package/caps/background.json +0 -712
  317. package/caps/cli.json +0 -360
  318. package/caps/library.json +0 -3611
  319. package/caps/mcp.json +0 -126
  320. package/caps/pagebridge.json +0 -377
  321. package/caps/popup.json +0 -51
  322. package/caps/sidepanel.json +0 -522
  323. package/checksums.txt +0 -142
  324. package/cli.d.ts.map +0 -1
  325. package/companion.d.ts.map +0 -1
  326. package/companion.js +0 -389
  327. package/crossbrowser.d.ts.map +0 -1
  328. package/crossbrowser.js +0 -1191
  329. package/dashdone.d.ts.map +0 -1
  330. package/dashdone.js +0 -368
  331. package/deno.d.ts.map +0 -1
  332. package/deno.js +0 -52325
  333. package/devthink.umd.js +0 -52346
  334. package/fixtures/bun.ts +0 -7
  335. package/fixtures/cjs.js +0 -6
  336. package/fixtures/deno.ts +0 -6
  337. package/fixtures/example-org-pagestate.json +0 -22
  338. package/fixtures/importers/automa-workflow.json +0 -18
  339. package/fixtures/importers/selenium-side.json +0 -20
  340. package/fixtures/importers/tabular-plan.csv +0 -7
  341. package/fixtures/importers/uivision-macro.json +0 -10
  342. package/fixtures/importers/v1-plan.json +0 -13
  343. package/fixtures/mcp-client.mjs +0 -14
  344. package/fixtures/pages/checkout.html +0 -192
  345. package/fixtures/pages/dashboard.html +0 -110
  346. package/fixtures/pages/feed.html +0 -113
  347. package/fixtures/pages/product-grid.html +0 -150
  348. package/fixtures/plans/form-inventory-plan.json +0 -11
  349. package/fixtures/plans/release-notes-plan.json +0 -11
  350. package/fixtures/recipes/agentscompete.json +0 -11
  351. package/fixtures/recipes/agentsforms.json +0 -11
  352. package/fixtures/recipes/agentsmonitor.json +0 -11
  353. package/fixtures/recipes/agentsreview.json +0 -29
  354. package/fixtures/recipes/agentswarm.json +0 -11
  355. package/fixtures/recipes/fillcalendar.json +0 -12
  356. package/fixtures/recipes/fillcheckout.json +0 -13
  357. package/fixtures/recipes/filldependentdropdowns.json +0 -12
  358. package/fixtures/recipes/fillfromcsv.json +0 -13
  359. package/fixtures/recipes/fillloginform.json +0 -12
  360. package/fixtures/recipes/fillmultistepwizard.json +0 -13
  361. package/fixtures/recipes/fillregistration.json +0 -12
  362. package/fixtures/recipes/fillsearchfilters.json +0 -12
  363. package/fixtures/recipes/filltypeahead.json +0 -11
  364. package/fixtures/recipes/monitoralerts.json +0 -12
  365. package/fixtures/recipes/monitoravailability.json +0 -11
  366. package/fixtures/recipes/monitornetworkquiet.json +0 -12
  367. package/fixtures/recipes/monitorpagechanges.json +0 -11
  368. package/fixtures/recipes/monitorpricechange.json +0 -11
  369. package/fixtures/recipes/monitorschedule.json +0 -12
  370. package/fixtures/recipes/scrapedynamicprices.json +0 -13
  371. package/fixtures/recipes/scrapeimagegallery.json +0 -12
  372. package/fixtures/recipes/scrapeinfinitefeed.json +0 -13
  373. package/fixtures/recipes/scrapenestedlists.json +0 -12
  374. package/fixtures/recipes/scrapeproductgrid.json +0 -12
  375. package/fixtures/recipes/scrapereviews.json +0 -12
  376. package/fixtures/recipes/scrapesearchresults.json +0 -12
  377. package/fixtures/recipes/scrapetablepagination.json +0 -12
  378. package/fixtures/recipes/testaccessibility.json +0 -34
  379. package/fixtures/recipes/testconsoleerrors.json +0 -12
  380. package/fixtures/recipes/testdeeplinks.json +0 -12
  381. package/fixtures/recipes/testformvalidation.json +0 -13
  382. package/fixtures/recipes/testlayoutshift.json +0 -12
  383. package/fixtures/recipes/testlinkcheck.json +0 -12
  384. package/fixtures/recipes/testloadtiming.json +0 -12
  385. package/fixtures/recipes/testvisualdiff.json +0 -12
  386. package/fixtures/workflows/changelog-digest-workflow.json +0 -13
  387. package/fixtures/workflows/form-survey-workflow.json +0 -13
  388. package/gallery.json +0 -706
  389. package/hardening.d.ts.map +0 -1
  390. package/hardening.js +0 -188
  391. package/headless.d.ts.map +0 -1
  392. package/headless.js +0 -11522
  393. package/http.d.ts.map +0 -1
  394. package/http.js +0 -2165
  395. package/index.cjs +0 -54682
  396. package/index.d.ts.map +0 -1
  397. package/index.js +0 -52310
  398. package/index.neutral.js +0 -46343
  399. package/mcp.d.ts.map +0 -1
  400. package/mcp.js +0 -20515
  401. package/memory.d.ts.map +0 -1
  402. package/nativehost.template.json +0 -10
  403. package/node.cjs +0 -54686
  404. package/node.d.ts.map +0 -1
  405. package/pack.d.ts.map +0 -1
  406. package/pack.js +0 -805
  407. package/policy.d.ts.map +0 -1
  408. package/progress.d.ts.map +0 -1
  409. package/progress.js +0 -645
  410. package/protocol.d.ts.map +0 -1
  411. package/protocol.js +0 -8334
  412. package/schemas/audit.schema.json +0 -226
  413. package/schemas/capability.schema.json +0 -113
  414. package/schemas/envelope.schema.json +0 -162
  415. package/schemas/memory.schema.json +0 -51
  416. package/schemas/observation.schema.json +0 -113
  417. package/schemas/plan.schema.json +0 -208
  418. package/schemas/progress.schema.json +0 -126
  419. package/schemas/proposal.schema.json +0 -68
  420. package/schemas/session.schema.json +0 -82
  421. package/schemas/tool.schema.json +0 -94
  422. package/server.d.ts.map +0 -1
  423. package/server.js +0 -28877
  424. package/umd-example.html +0 -18
  425. package/umd.d.ts.map +0 -1
  426. /package/{bun.d.ts → dist/bun.d.ts} +0 -0
  427. /package/{cli.d.ts → dist/cli.d.ts} +0 -0
  428. /package/{companion.d.ts → dist/companion.d.ts} +0 -0
  429. /package/{crossbrowser.d.ts → dist/crossbrowser.d.ts} +0 -0
  430. /package/{dashdone.d.ts → dist/dashdone.d.ts} +0 -0
  431. /package/{deno.d.ts → dist/deno.d.ts} +0 -0
  432. /package/{hardening.d.ts → dist/hardening.d.ts} +0 -0
  433. /package/{headless.d.ts → dist/headless.d.ts} +0 -0
  434. /package/{http.d.ts → dist/http.d.ts} +0 -0
  435. /package/{index.d.ts → dist/index.d.ts} +0 -0
  436. /package/{mcp.d.ts → dist/mcp.d.ts} +0 -0
  437. /package/{memory.d.ts → dist/memory.d.ts} +0 -0
  438. /package/{node.d.ts → dist/node.d.ts} +0 -0
  439. /package/{pack.d.ts → dist/pack.d.ts} +0 -0
  440. /package/{policy.d.ts → dist/policy.d.ts} +0 -0
  441. /package/{progress.d.ts → dist/progress.d.ts} +0 -0
  442. /package/{protocol.d.ts → dist/protocol.d.ts} +0 -0
  443. /package/{server.d.ts → dist/server.d.ts} +0 -0
  444. /package/{umd.d.ts → dist/umd.d.ts} +0 -0
package/web/db.ts ADDED
@@ -0,0 +1,493 @@
1
+ /** DevThink v1.1.16: browser-local, non-secret workbench records. IndexedDB is an offline cache, never a credential vault or remote database. */
2
+
3
+ export type BrowserIdentity = { userId: string; deviceId: string; createdAt: string };
4
+ export type BrowserRoute = { workspaceId: string; sessionId: string; tabId: string; sectionId: string };
5
+
6
+ type LocalRecord = {
7
+ id: string;
8
+ ownerId: string;
9
+ updatedAt: string;
10
+ deviceId: string;
11
+ revision: number;
12
+ tombstone: boolean;
13
+ };
14
+ export type BrowserWorkspace = LocalRecord & { title: string; createdAt: string };
15
+ export type BrowserSession = LocalRecord & {
16
+ workspaceId: string;
17
+ title: string;
18
+ mode: string;
19
+ model?: string;
20
+ provider?: string;
21
+ activeTabId: string;
22
+ createdAt: string;
23
+ };
24
+ export type BrowserTab = LocalRecord & {
25
+ sessionId: string;
26
+ workspaceId: string;
27
+ label: string;
28
+ provider?: string;
29
+ sectionId: string;
30
+ createdAt: string;
31
+ };
32
+ export type BrowserMessage = LocalRecord & {
33
+ sessionId: string;
34
+ workspaceId: string;
35
+ tabId: string;
36
+ sectionId: string;
37
+ role: "user" | "assistant" | "system";
38
+ content: string;
39
+ createdAt: string;
40
+ };
41
+ export type BrowserPreference = LocalRecord & { key: string; value: string };
42
+ export type BrowserCredential = {
43
+ id: string;
44
+ ownerId: string;
45
+ providerId: string;
46
+ value: string;
47
+ deviceId: string;
48
+ createdAt: string;
49
+ updatedAt: string;
50
+ localOnly: true;
51
+ };
52
+ export type BrowserSessionSnapshot = {
53
+ identity: BrowserIdentity;
54
+ workspace?: BrowserWorkspace;
55
+ session?: BrowserSession;
56
+ tabs: BrowserTab[];
57
+ messages: BrowserMessage[];
58
+ preferences: Record<string, string>;
59
+ };
60
+ export type BrowserStoreSummary = {
61
+ available: boolean;
62
+ database: string;
63
+ ownerId?: string;
64
+ deviceId?: string;
65
+ workspaces: number;
66
+ sessions: number;
67
+ tabs: number;
68
+ messages: number;
69
+ preferences: number;
70
+ mode: "local-only" | "paired-gateway" | "remote-adapter";
71
+ };
72
+
73
+ type StoreName = "meta" | "workspaces" | "sessions" | "tabs" | "messages" | "preferences" | "credentials";
74
+ type MetaEntry = { key: string; value: BrowserIdentity };
75
+
76
+ const databaseName = "devthink.db";
77
+ const databaseVersion = 2;
78
+ const dataStores: Array<Exclude<StoreName, "meta">> = ["workspaces", "sessions", "tabs", "messages", "preferences"];
79
+ const ownerExpression = /^[a-z][a-z0-9]{9,14}$/;
80
+
81
+ function timestamp(): string {
82
+ return new Date().toISOString();
83
+ }
84
+
85
+ function compactToken(length = 10): string {
86
+ const alphabet = "0123456789abcdefghjkmnpqrstvwxyz";
87
+ const bytes = new Uint8Array(length);
88
+ globalThis.crypto?.getRandomValues?.(bytes);
89
+ return Array.from(bytes, (value, index) => alphabet[value ? value & 31 : (Date.now() + index) & 31]).join("");
90
+ }
91
+
92
+ function request<T>(source: IDBRequest<T>): Promise<T> {
93
+ return new Promise((resolve, reject) => {
94
+ source.onsuccess = () => resolve(source.result);
95
+ source.onerror = () => reject(source.error || new Error("DevThink browser store request failed."));
96
+ });
97
+ }
98
+
99
+ function finished(transaction: IDBTransaction): Promise<void> {
100
+ return new Promise((resolve, reject) => {
101
+ transaction.oncomplete = () => resolve();
102
+ transaction.onabort = () => reject(transaction.error || new Error("DevThink browser store transaction aborted."));
103
+ transaction.onerror = () => reject(transaction.error || new Error("DevThink browser store transaction failed."));
104
+ });
105
+ }
106
+
107
+ function openStore(): Promise<IDBDatabase> {
108
+ if (!("indexedDB" in globalThis)) return Promise.reject(new Error("IndexedDB is unavailable in this browser."));
109
+ return new Promise((resolve, reject) => {
110
+ const open = globalThis.indexedDB.open(databaseName, databaseVersion);
111
+ open.onupgradeneeded = () => {
112
+ const database = open.result;
113
+ if (!database.objectStoreNames.contains("meta")) database.createObjectStore("meta", { keyPath: "key" });
114
+ for (const storeName of dataStores) {
115
+ if (database.objectStoreNames.contains(storeName)) continue;
116
+ const store = database.createObjectStore(storeName, { keyPath: "id" });
117
+ store.createIndex("ownerId", "ownerId", { unique: false });
118
+ if (storeName === "sessions") store.createIndex("workspaceId", "workspaceId", { unique: false });
119
+ if (storeName === "tabs" || storeName === "messages")
120
+ store.createIndex("sessionId", "sessionId", { unique: false });
121
+ }
122
+ if (!database.objectStoreNames.contains("credentials")) {
123
+ const credentials = database.createObjectStore("credentials", { keyPath: "id" });
124
+ credentials.createIndex("ownerId", "ownerId", { unique: false });
125
+ credentials.createIndex("providerId", "providerId", { unique: false });
126
+ }
127
+ };
128
+ open.onsuccess = () => resolve(open.result);
129
+ open.onerror = () => reject(open.error || new Error("DevThink browser store could not open."));
130
+ });
131
+ }
132
+
133
+ async function withStore<T>(
134
+ storeName: StoreName,
135
+ mode: IDBTransactionMode,
136
+ operation: (store: IDBObjectStore) => Promise<T>,
137
+ ): Promise<T> {
138
+ const database = await openStore();
139
+ try {
140
+ const transaction = database.transaction(storeName, mode);
141
+ const result = await operation(transaction.objectStore(storeName));
142
+ await finished(transaction);
143
+ return result;
144
+ } finally {
145
+ database.close();
146
+ }
147
+ }
148
+
149
+ async function record<T>(storeName: StoreName, id: string): Promise<T | undefined> {
150
+ return withStore(storeName, "readonly", async (store) => (await request(store.get(id))) as T | undefined);
151
+ }
152
+
153
+ async function allOwned<T extends LocalRecord>(storeName: Exclude<StoreName, "meta">, ownerId: string): Promise<T[]> {
154
+ return withStore(storeName, "readonly", async (store) =>
155
+ ((await request(store.index("ownerId").getAll(IDBKeyRange.only(ownerId)))) as T[]).filter(
156
+ (item) => !item.tombstone,
157
+ ),
158
+ );
159
+ }
160
+
161
+ async function put<T>(storeName: StoreName, value: T): Promise<T> {
162
+ return withStore(storeName, "readwrite", async (store) => {
163
+ await request(store.put(value));
164
+ return value;
165
+ });
166
+ }
167
+
168
+ async function updateRecord<T extends LocalRecord>(
169
+ storeName: Exclude<StoreName, "meta">,
170
+ identity: BrowserIdentity,
171
+ id: string,
172
+ value: Omit<T, keyof LocalRecord> & Partial<Pick<T, "updatedAt" | "tombstone">>,
173
+ ): Promise<T> {
174
+ const current = await record<T>(storeName, id);
175
+ const next = {
176
+ ...current,
177
+ ...value,
178
+ id,
179
+ ownerId: identity.userId,
180
+ deviceId: identity.deviceId,
181
+ updatedAt: value.updatedAt || timestamp(),
182
+ revision: Math.max(0, current?.revision || 0) + 1,
183
+ tombstone: value.tombstone || false,
184
+ } as T;
185
+ return put(storeName, next);
186
+ }
187
+
188
+ /** Creates one browser-local identity only when a paired CLI identity is not available. */
189
+ export async function browserIdentity(): Promise<BrowserIdentity> {
190
+ const existing = await record<MetaEntry>("meta", "identity");
191
+ if (existing?.value && ownerExpression.test(existing.value.userId)) return existing.value;
192
+ const identity = { userId: `b${compactToken(11)}`, deviceId: `d_${compactToken(10)}`, createdAt: timestamp() };
193
+ await put("meta", { key: "identity", value: identity } satisfies MetaEntry);
194
+ return identity;
195
+ }
196
+
197
+ /** Caches the public identity supplied by a paired local gateway without importing any authentication material. */
198
+ export async function cacheBrowserIdentity(identity: BrowserIdentity): Promise<BrowserIdentity> {
199
+ if (!ownerExpression.test(identity.userId)) return browserIdentity();
200
+ const current = await browserIdentity();
201
+ if (current.userId !== identity.userId) {
202
+ for (const storeName of dataStores) {
203
+ const records = await allOwned<LocalRecord>(storeName, current.userId);
204
+ for (const item of records)
205
+ await put(storeName, {
206
+ ...item,
207
+ ownerId: identity.userId,
208
+ deviceId: current.deviceId,
209
+ updatedAt: timestamp(),
210
+ revision: item.revision + 1,
211
+ });
212
+ }
213
+ }
214
+ const next = { ...current, userId: identity.userId };
215
+ await put("meta", { key: "identity", value: next } satisfies MetaEntry);
216
+ return next;
217
+ }
218
+
219
+ export async function readBrowserPreferences(): Promise<Record<string, string>> {
220
+ const identity = await browserIdentity();
221
+ const preferences = await allOwned<BrowserPreference>("preferences", identity.userId);
222
+ return Object.fromEntries(preferences.map((preference) => [preference.key, preference.value]));
223
+ }
224
+
225
+ /** Persists a non-secret interface preference or provider selection locally in this browser. */
226
+ export async function saveBrowserPreference(key: string, value: string): Promise<BrowserPreference> {
227
+ const identity = await browserIdentity();
228
+ return updateRecord<BrowserPreference>("preferences", identity, `${identity.userId}:${key}`, { key, value });
229
+ }
230
+
231
+ /** Stores a provider credential only after an explicit browser-local consent action. It is never included in summaries, gateway payloads or remote sync records. */
232
+ export async function saveBrowserCredential(providerId: string, value: string): Promise<void> {
233
+ const identity = await browserIdentity();
234
+ const now = timestamp();
235
+ const id = `${identity.userId}:${providerId}`;
236
+ const existing = await record<BrowserCredential>("credentials", id);
237
+ await put("credentials", {
238
+ id,
239
+ ownerId: identity.userId,
240
+ providerId,
241
+ value,
242
+ deviceId: identity.deviceId,
243
+ createdAt: existing?.createdAt || now,
244
+ updatedAt: now,
245
+ localOnly: true,
246
+ } satisfies BrowserCredential);
247
+ }
248
+
249
+ export async function removeBrowserCredential(providerId: string): Promise<void> {
250
+ const identity = await browserIdentity();
251
+ await withStore("credentials", "readwrite", async (store) => {
252
+ await request(store.delete(`${identity.userId}:${providerId}`));
253
+ });
254
+ }
255
+
256
+ export async function browserCredentialProviders(): Promise<string[]> {
257
+ const identity = await browserIdentity();
258
+ return withStore("credentials", "readonly", async (store) =>
259
+ ((await request(store.index("ownerId").getAll(IDBKeyRange.only(identity.userId)))) as BrowserCredential[])
260
+ .filter((credential) => credential.localOnly)
261
+ .map((credential) => credential.providerId),
262
+ );
263
+ }
264
+
265
+ /** Ensures a URL-addressable workspace, session and tab exist in browser-local storage. */
266
+ export async function ensureBrowserSession(
267
+ route: BrowserRoute,
268
+ input: { provider?: string; model?: string; title?: string; mode?: string; tabLabel?: string } = {},
269
+ ): Promise<BrowserSessionSnapshot> {
270
+ const identity = await browserIdentity();
271
+ const now = timestamp();
272
+ const workspace = await record<BrowserWorkspace>("workspaces", route.workspaceId);
273
+ if (!workspace || workspace.ownerId !== identity.userId)
274
+ await updateRecord<BrowserWorkspace>("workspaces", identity, route.workspaceId, {
275
+ title: input.title || "Local workspace",
276
+ createdAt: now,
277
+ });
278
+ const session = await record<BrowserSession>("sessions", route.sessionId);
279
+ if (!session || session.ownerId !== identity.userId)
280
+ await updateRecord<BrowserSession>("sessions", identity, route.sessionId, {
281
+ workspaceId: route.workspaceId,
282
+ title: input.title || "Local session",
283
+ mode: input.mode || "chat",
284
+ model: input.model,
285
+ provider: input.provider,
286
+ activeTabId: route.tabId,
287
+ createdAt: now,
288
+ });
289
+ const tab = await record<BrowserTab>("tabs", route.tabId);
290
+ if (!tab || tab.ownerId !== identity.userId)
291
+ await updateRecord<BrowserTab>("tabs", identity, route.tabId, {
292
+ sessionId: route.sessionId,
293
+ workspaceId: route.workspaceId,
294
+ label: input.tabLabel || "local session",
295
+ provider: input.provider,
296
+ sectionId: route.sectionId,
297
+ createdAt: now,
298
+ });
299
+ return loadBrowserSession(route.sessionId);
300
+ }
301
+
302
+ export async function loadBrowserSession(sessionId: string): Promise<BrowserSessionSnapshot> {
303
+ const identity = await browserIdentity();
304
+ const session = await record<BrowserSession>("sessions", sessionId);
305
+ const [tabs, messages, preferences] = await Promise.all([
306
+ allOwned<BrowserTab>("tabs", identity.userId),
307
+ allOwned<BrowserMessage>("messages", identity.userId),
308
+ readBrowserPreferences(),
309
+ ]);
310
+ return {
311
+ identity,
312
+ workspace:
313
+ session?.ownerId === identity.userId
314
+ ? await record<BrowserWorkspace>("workspaces", session.workspaceId)
315
+ : undefined,
316
+ session: session?.ownerId === identity.userId && !session.tombstone ? session : undefined,
317
+ tabs: tabs
318
+ .filter((tab) => tab.sessionId === sessionId)
319
+ .sort((left, right) => left.createdAt.localeCompare(right.createdAt)),
320
+ messages: messages
321
+ .filter((message) => message.sessionId === sessionId)
322
+ .sort((left, right) => left.createdAt.localeCompare(right.createdAt)),
323
+ preferences,
324
+ };
325
+ }
326
+
327
+ export async function saveBrowserTab(
328
+ route: BrowserRoute,
329
+ input: { label: string; provider?: string; sectionId?: string },
330
+ ): Promise<BrowserTab> {
331
+ const identity = await browserIdentity();
332
+ await ensureBrowserSession(route, { provider: input.provider });
333
+ return updateRecord<BrowserTab>("tabs", identity, route.tabId, {
334
+ sessionId: route.sessionId,
335
+ workspaceId: route.workspaceId,
336
+ label: input.label,
337
+ provider: input.provider,
338
+ sectionId: input.sectionId || route.sectionId,
339
+ createdAt: (await record<BrowserTab>("tabs", route.tabId))?.createdAt || timestamp(),
340
+ });
341
+ }
342
+
343
+ export async function removeBrowserTab(id: string): Promise<void> {
344
+ const identity = await browserIdentity();
345
+ const tab = await record<BrowserTab>("tabs", id);
346
+ if (!tab || tab.ownerId !== identity.userId) return;
347
+ await updateRecord<BrowserTab>("tabs", identity, id, { ...tab, tombstone: true });
348
+ }
349
+
350
+ export async function removeBrowserMessage(id: string): Promise<void> {
351
+ const identity = await browserIdentity();
352
+ const message = await record<BrowserMessage>("messages", id);
353
+ if (!message || message.ownerId !== identity.userId) return;
354
+ await updateRecord<BrowserMessage>("messages", identity, id, { ...message, tombstone: true });
355
+ }
356
+
357
+ export async function saveBrowserMessages(
358
+ route: BrowserRoute,
359
+ messages: Array<Pick<BrowserMessage, "id" | "role" | "content"> & Partial<Pick<BrowserMessage, "createdAt">>>,
360
+ ): Promise<void> {
361
+ const identity = await browserIdentity();
362
+ await ensureBrowserSession(route);
363
+ for (const message of messages)
364
+ await updateRecord<BrowserMessage>("messages", identity, message.id, {
365
+ sessionId: route.sessionId,
366
+ workspaceId: route.workspaceId,
367
+ tabId: route.tabId,
368
+ sectionId: route.sectionId,
369
+ role: message.role,
370
+ content: message.content,
371
+ createdAt: message.createdAt || (await record<BrowserMessage>("messages", message.id))?.createdAt || timestamp(),
372
+ });
373
+ }
374
+
375
+ /** Mirrors a safe gateway payload in the browser cache so it remains usable if the local gateway is later unavailable. */
376
+ export async function cacheGatewaySession(payload: {
377
+ id: string;
378
+ workspaceId: string;
379
+ title: string;
380
+ mode?: string;
381
+ model?: string;
382
+ provider?: string;
383
+ activeTabId: string;
384
+ createdAt?: string;
385
+ updatedAt?: string;
386
+ tabs: Array<{
387
+ id: string;
388
+ workspaceId: string;
389
+ sessionId: string;
390
+ label: string;
391
+ provider?: string;
392
+ sectionId: string;
393
+ createdAt: string;
394
+ updatedAt: string;
395
+ }>;
396
+ messages: Array<{
397
+ id: string;
398
+ workspaceId: string;
399
+ sessionId: string;
400
+ tabId: string;
401
+ sectionId: string;
402
+ role: "user" | "assistant" | "system";
403
+ content: string;
404
+ createdAt: string;
405
+ }>;
406
+ }): Promise<void> {
407
+ const identity = await browserIdentity();
408
+ const now = timestamp();
409
+ await updateRecord<BrowserWorkspace>("workspaces", identity, payload.workspaceId, {
410
+ title: payload.title || "Paired workspace",
411
+ createdAt: payload.createdAt || now,
412
+ updatedAt: payload.updatedAt || now,
413
+ });
414
+ await updateRecord<BrowserSession>("sessions", identity, payload.id, {
415
+ workspaceId: payload.workspaceId,
416
+ title: payload.title,
417
+ mode: payload.mode || "chat",
418
+ model: payload.model,
419
+ provider: payload.provider,
420
+ activeTabId: payload.activeTabId,
421
+ createdAt: payload.createdAt || now,
422
+ updatedAt: payload.updatedAt || now,
423
+ });
424
+ for (const tab of payload.tabs)
425
+ await updateRecord<BrowserTab>("tabs", identity, tab.id, {
426
+ sessionId: payload.id,
427
+ workspaceId: payload.workspaceId,
428
+ label: tab.label,
429
+ provider: tab.provider,
430
+ sectionId: tab.sectionId,
431
+ createdAt: tab.createdAt,
432
+ updatedAt: tab.updatedAt,
433
+ });
434
+ for (const message of payload.messages)
435
+ await updateRecord<BrowserMessage>("messages", identity, message.id, {
436
+ sessionId: payload.id,
437
+ workspaceId: payload.workspaceId,
438
+ tabId: message.tabId,
439
+ sectionId: message.sectionId,
440
+ role: message.role,
441
+ content: message.content,
442
+ createdAt: message.createdAt,
443
+ updatedAt: message.createdAt,
444
+ });
445
+ }
446
+
447
+ export async function browserStoreSummary(paired = false): Promise<BrowserStoreSummary> {
448
+ if (!("indexedDB" in globalThis))
449
+ return {
450
+ available: false,
451
+ database: databaseName,
452
+ workspaces: 0,
453
+ sessions: 0,
454
+ tabs: 0,
455
+ messages: 0,
456
+ preferences: 0,
457
+ mode: paired ? "paired-gateway" : "local-only",
458
+ };
459
+ const identity = await browserIdentity();
460
+ const [workspaces, sessions, tabs, messages, preferences] = await Promise.all(
461
+ dataStores.map((storeName) => allOwned<LocalRecord>(storeName, identity.userId)),
462
+ );
463
+ return {
464
+ available: true,
465
+ database: databaseName,
466
+ ownerId: identity.userId,
467
+ deviceId: identity.deviceId,
468
+ workspaces: workspaces.length,
469
+ sessions: sessions.length,
470
+ tabs: tabs.length,
471
+ messages: messages.length,
472
+ preferences: preferences.length,
473
+ mode: paired ? "paired-gateway" : "local-only",
474
+ };
475
+ }
476
+
477
+ export async function browserWorkspaces(): Promise<
478
+ Array<{ id: string; title: string; updatedAt: string; sessionCount: number }>
479
+ > {
480
+ const identity = await browserIdentity();
481
+ const [workspaces, sessions] = await Promise.all([
482
+ allOwned<BrowserWorkspace>("workspaces", identity.userId),
483
+ allOwned<BrowserSession>("sessions", identity.userId),
484
+ ]);
485
+ return workspaces
486
+ .sort((left, right) => right.updatedAt.localeCompare(left.updatedAt))
487
+ .map((workspace) => ({
488
+ id: workspace.id,
489
+ title: workspace.title,
490
+ updatedAt: workspace.updatedAt,
491
+ sessionCount: sessions.filter((session) => session.workspaceId === workspace.id).length,
492
+ }));
493
+ }