@wenathlan/devthink 2.0.14 → 2.0.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (444) hide show
  1. package/CHANGELOG.md +60 -0
  2. package/{dist/bridge.d.ts → bridge.d.ts} +1 -1
  3. package/bridge.d.ts.map +1 -0
  4. package/bridge.js +1877 -0
  5. package/bun.d.ts.map +1 -0
  6. package/bun.js +52329 -0
  7. package/caps/background.json +712 -0
  8. package/caps/cli.json +360 -0
  9. package/caps/library.json +3611 -0
  10. package/caps/mcp.json +126 -0
  11. package/caps/pagebridge.json +377 -0
  12. package/caps/popup.json +51 -0
  13. package/caps/sidepanel.json +522 -0
  14. package/checksums.txt +142 -0
  15. package/cli.d.ts.map +1 -0
  16. package/{dist/cli.js → cli.js} +2 -2
  17. package/companion.d.ts.map +1 -0
  18. package/companion.js +389 -0
  19. package/crossbrowser.d.ts.map +1 -0
  20. package/crossbrowser.js +1191 -0
  21. package/dashdone.d.ts.map +1 -0
  22. package/dashdone.js +368 -0
  23. package/deno.d.ts.map +1 -0
  24. package/deno.js +52325 -0
  25. package/devthink.umd.js +52346 -0
  26. package/fixtures/bun.ts +7 -0
  27. package/fixtures/cjs.js +6 -0
  28. package/fixtures/deno.ts +6 -0
  29. package/fixtures/example-org-pagestate.json +22 -0
  30. package/fixtures/importers/automa-workflow.json +18 -0
  31. package/fixtures/importers/selenium-side.json +20 -0
  32. package/fixtures/importers/tabular-plan.csv +7 -0
  33. package/fixtures/importers/uivision-macro.json +10 -0
  34. package/fixtures/importers/v1-plan.json +13 -0
  35. package/fixtures/mcp-client.mjs +14 -0
  36. package/fixtures/pages/checkout.html +192 -0
  37. package/fixtures/pages/dashboard.html +110 -0
  38. package/fixtures/pages/feed.html +113 -0
  39. package/fixtures/pages/product-grid.html +150 -0
  40. package/fixtures/plans/form-inventory-plan.json +11 -0
  41. package/fixtures/plans/release-notes-plan.json +11 -0
  42. package/fixtures/recipes/agentscompete.json +11 -0
  43. package/fixtures/recipes/agentsforms.json +11 -0
  44. package/fixtures/recipes/agentsmonitor.json +11 -0
  45. package/fixtures/recipes/agentsreview.json +29 -0
  46. package/fixtures/recipes/agentswarm.json +11 -0
  47. package/fixtures/recipes/fillcalendar.json +12 -0
  48. package/fixtures/recipes/fillcheckout.json +13 -0
  49. package/fixtures/recipes/filldependentdropdowns.json +12 -0
  50. package/fixtures/recipes/fillfromcsv.json +13 -0
  51. package/fixtures/recipes/fillloginform.json +12 -0
  52. package/fixtures/recipes/fillmultistepwizard.json +13 -0
  53. package/fixtures/recipes/fillregistration.json +12 -0
  54. package/fixtures/recipes/fillsearchfilters.json +12 -0
  55. package/fixtures/recipes/filltypeahead.json +11 -0
  56. package/fixtures/recipes/monitoralerts.json +12 -0
  57. package/fixtures/recipes/monitoravailability.json +11 -0
  58. package/fixtures/recipes/monitornetworkquiet.json +12 -0
  59. package/fixtures/recipes/monitorpagechanges.json +11 -0
  60. package/fixtures/recipes/monitorpricechange.json +11 -0
  61. package/fixtures/recipes/monitorschedule.json +12 -0
  62. package/fixtures/recipes/scrapedynamicprices.json +13 -0
  63. package/fixtures/recipes/scrapeimagegallery.json +12 -0
  64. package/fixtures/recipes/scrapeinfinitefeed.json +13 -0
  65. package/fixtures/recipes/scrapenestedlists.json +12 -0
  66. package/fixtures/recipes/scrapeproductgrid.json +12 -0
  67. package/fixtures/recipes/scrapereviews.json +12 -0
  68. package/fixtures/recipes/scrapesearchresults.json +12 -0
  69. package/fixtures/recipes/scrapetablepagination.json +12 -0
  70. package/fixtures/recipes/testaccessibility.json +34 -0
  71. package/fixtures/recipes/testconsoleerrors.json +12 -0
  72. package/fixtures/recipes/testdeeplinks.json +12 -0
  73. package/fixtures/recipes/testformvalidation.json +13 -0
  74. package/fixtures/recipes/testlayoutshift.json +12 -0
  75. package/fixtures/recipes/testlinkcheck.json +12 -0
  76. package/fixtures/recipes/testloadtiming.json +12 -0
  77. package/fixtures/recipes/testvisualdiff.json +12 -0
  78. package/fixtures/workflows/changelog-digest-workflow.json +13 -0
  79. package/fixtures/workflows/form-survey-workflow.json +13 -0
  80. package/gallery.json +706 -0
  81. package/hardening.d.ts.map +1 -0
  82. package/hardening.js +188 -0
  83. package/headless.d.ts.map +1 -0
  84. package/headless.js +11522 -0
  85. package/http.d.ts.map +1 -0
  86. package/http.js +2165 -0
  87. package/index.cjs +54682 -0
  88. package/index.d.ts.map +1 -0
  89. package/index.js +52310 -0
  90. package/index.neutral.js +46343 -0
  91. package/{web/manifest.json → manifest.json} +1 -1
  92. package/mcp.d.ts.map +1 -0
  93. package/mcp.js +20515 -0
  94. package/memory.d.ts.map +1 -0
  95. package/{memory.ts → memory.js} +2310 -3834
  96. package/nativehost.template.json +10 -0
  97. package/node.cjs +54686 -0
  98. package/node.d.ts.map +1 -0
  99. package/pack.d.ts.map +1 -0
  100. package/pack.js +805 -0
  101. package/package.json +83 -176
  102. package/policy.d.ts.map +1 -0
  103. package/{policy.ts → policy.js} +4679 -5073
  104. package/progress.d.ts.map +1 -0
  105. package/progress.js +645 -0
  106. package/protocol.d.ts.map +1 -0
  107. package/protocol.js +8334 -0
  108. package/schemas/audit.schema.json +226 -0
  109. package/schemas/capability.schema.json +113 -0
  110. package/schemas/envelope.schema.json +162 -0
  111. package/schemas/memory.schema.json +51 -0
  112. package/schemas/observation.schema.json +113 -0
  113. package/schemas/plan.schema.json +208 -0
  114. package/schemas/progress.schema.json +126 -0
  115. package/schemas/proposal.schema.json +68 -0
  116. package/schemas/session.schema.json +82 -0
  117. package/schemas/tool.schema.json +94 -0
  118. package/server.d.ts.map +1 -0
  119. package/server.js +28877 -0
  120. package/umd-example.html +18 -0
  121. package/umd.d.ts.map +1 -0
  122. package/accounts.ts +0 -2087
  123. package/agent.ts +0 -2331
  124. package/antigravity.ts +0 -2139
  125. package/apifreeze.ts +0 -4146
  126. package/auth.ts +0 -642
  127. package/background.ts +0 -16837
  128. package/bridge.ts +0 -2080
  129. package/build.ts +0 -54
  130. package/bun.ts +0 -26
  131. package/capture.ts +0 -1614
  132. package/checksecrets.ts +0 -72
  133. package/cjs.ts +0 -9
  134. package/cli.ts +0 -3500
  135. package/commands.ts +0 -1101
  136. package/companion.ts +0 -450
  137. package/compatibility.ts +0 -37
  138. package/config.ts +0 -3137
  139. package/constants.ts +0 -943
  140. package/core.ts +0 -1473
  141. package/crossbrowser.ts +0 -1359
  142. package/dashdone.ts +0 -605
  143. package/data.ts +0 -943
  144. package/database.ts +0 -133
  145. package/debug.ts +0 -850
  146. package/deno.ts +0 -27
  147. package/devthink.ts +0 -707
  148. package/dist/accounts.d.ts +0 -409
  149. package/dist/agent.d.ts +0 -837
  150. package/dist/antigravity.d.ts +0 -380
  151. package/dist/apifreeze.d.ts +0 -113
  152. package/dist/auth.d.ts +0 -224
  153. package/dist/capture.d.ts +0 -595
  154. package/dist/cjs.d.ts +0 -6
  155. package/dist/commands.d.ts +0 -310
  156. package/dist/compatibility.d.ts +0 -17
  157. package/dist/config.d.ts +0 -1853
  158. package/dist/constants.d.ts +0 -637
  159. package/dist/core.d.ts +0 -361
  160. package/dist/data.d.ts +0 -330
  161. package/dist/database.d.ts +0 -24
  162. package/dist/debug.d.ts +0 -270
  163. package/dist/engine.d.ts +0 -74
  164. package/dist/environments.d.ts +0 -244
  165. package/dist/evidence.d.ts +0 -277
  166. package/dist/export.d.ts +0 -311
  167. package/dist/fingerprint.d.ts +0 -594
  168. package/dist/flow.d.ts +0 -300
  169. package/dist/gates.d.ts +0 -103
  170. package/dist/identity.d.ts +0 -51
  171. package/dist/ids.d.ts +0 -7
  172. package/dist/llm.d.ts +0 -652
  173. package/dist/models.d.ts +0 -1807
  174. package/dist/net.d.ts +0 -325
  175. package/dist/neutral.d.ts +0 -41
  176. package/dist/oauth.d.ts +0 -703
  177. package/dist/page.d.ts +0 -1166
  178. package/dist/perf.d.ts +0 -481
  179. package/dist/plan.d.ts +0 -134
  180. package/dist/project.d.ts +0 -395
  181. package/dist/providers.d.ts +0 -37
  182. package/dist/quota.d.ts +0 -453
  183. package/dist/recovery.d.ts +0 -621
  184. package/dist/request.d.ts +0 -1492
  185. package/dist/run.d.ts +0 -779
  186. package/dist/runtime.d.ts +0 -212
  187. package/dist/search.d.ts +0 -375
  188. package/dist/security.d.ts +0 -534
  189. package/dist/serve.d.ts +0 -136
  190. package/dist/session.d.ts +0 -296
  191. package/dist/storage.d.ts +0 -53
  192. package/dist/streaming.d.ts +0 -1119
  193. package/dist/swarm.d.ts +0 -328
  194. package/dist/tools.d.ts +0 -170
  195. package/dist/types.d.ts +0 -8357
  196. package/dist/utils.d.ts +0 -82
  197. package/dist/version.d.ts +0 -7
  198. package/dist/versionregistry.d.ts +0 -406
  199. package/dist/views.d.ts +0 -579
  200. package/dist/workbenchsession.d.ts +0 -69
  201. package/dist/workflow.d.ts +0 -649
  202. package/docs/readme.md +0 -92
  203. package/docs/schemas/antigravity.json +0 -192
  204. package/engine.ts +0 -1524
  205. package/environments.ts +0 -1005
  206. package/evidence.ts +0 -643
  207. package/export.ts +0 -889
  208. package/fingerprint.ts +0 -1458
  209. package/flow.ts +0 -1190
  210. package/gates.ts +0 -307
  211. package/gateway.ts +0 -34
  212. package/hardening.ts +0 -191
  213. package/headless.ts +0 -369
  214. package/http.ts +0 -2110
  215. package/identity.ts +0 -246
  216. package/ids.ts +0 -26
  217. package/index.ts +0 -369
  218. package/llm.ts +0 -2682
  219. package/mcp.ts +0 -1835
  220. package/models.ts +0 -3805
  221. package/modes.ts +0 -43
  222. package/net.ts +0 -1214
  223. package/neutral.ts +0 -362
  224. package/node.ts +0 -26
  225. package/oauth.ts +0 -2216
  226. package/offscreen.ts +0 -170
  227. package/pack.ts +0 -956
  228. package/page.ts +0 -7824
  229. package/pagebridge.ts +0 -1116
  230. package/perf.ts +0 -957
  231. package/plan.ts +0 -672
  232. package/plugin.ts +0 -76
  233. package/prisma.config.ts +0 -27
  234. package/progress.ts +0 -1308
  235. package/project.ts +0 -1291
  236. package/protocol.ts +0 -4187
  237. package/providers.ts +0 -246
  238. package/quota.ts +0 -1887
  239. package/recovery.ts +0 -2433
  240. package/request.ts +0 -4482
  241. package/run.ts +0 -2135
  242. package/runtime.ts +0 -526
  243. package/search.ts +0 -1760
  244. package/security.ts +0 -1673
  245. package/serve.ts +0 -419
  246. package/server.ts +0 -5538
  247. package/session.ts +0 -1162
  248. package/storage.ts +0 -182
  249. package/streaming.ts +0 -4101
  250. package/swarm.ts +0 -884
  251. package/sync.ts +0 -56
  252. package/tests/agentcert.test.ts +0 -128
  253. package/tests/agentfleet.test.ts +0 -214
  254. package/tests/agentreview.test.ts +0 -241
  255. package/tests/agentstream.test.ts +0 -335
  256. package/tests/agentwork.test.ts +0 -356
  257. package/tests/apifreeze.test.ts +0 -290
  258. package/tests/apimap.test.ts +0 -112
  259. package/tests/approvalgate.test.ts +0 -175
  260. package/tests/artifactmanifest.test.ts +0 -204
  261. package/tests/attentionfeed.test.ts +0 -163
  262. package/tests/backgroundruns.test.ts +0 -100
  263. package/tests/batchscheduling.test.ts +0 -131
  264. package/tests/blackboard.test.ts +0 -238
  265. package/tests/bridge.test.ts +0 -1088
  266. package/tests/browsercoverage.test.ts +0 -134
  267. package/tests/browserpolyfills.test.ts +0 -124
  268. package/tests/build.mjs +0 -1260
  269. package/tests/clientauth.test.ts +0 -223
  270. package/tests/clitools.test.ts +0 -578
  271. package/tests/confirmgates.test.ts +0 -190
  272. package/tests/containerpack.test.ts +0 -143
  273. package/tests/controlflow.test.ts +0 -2120
  274. package/tests/coordination.test.ts +0 -345
  275. package/tests/copyscreen.test.ts +0 -139
  276. package/tests/core.test.ts +0 -417
  277. package/tests/costcert.test.ts +0 -95
  278. package/tests/crossbrowser.test.ts +0 -115
  279. package/tests/cspaudit.test.ts +0 -79
  280. package/tests/dashdone.test.ts +0 -615
  281. package/tests/datagrid.test.ts +0 -137
  282. package/tests/doccheck.test.ts +0 -107
  283. package/tests/dombatch.test.ts +0 -109
  284. package/tests/emulation.test.ts +0 -282
  285. package/tests/environments.test.ts +0 -259
  286. package/tests/evidenceviews.test.ts +0 -88
  287. package/tests/exporttools.test.ts +0 -185
  288. package/tests/firefoxprep.test.ts +0 -134
  289. package/tests/flowlibrary.test.ts +0 -242
  290. package/tests/flowrun.test.ts +0 -303
  291. package/tests/forensics.test.ts +0 -439
  292. package/tests/gateway.test.ts +0 -939
  293. package/tests/headlesslib.test.ts +0 -171
  294. package/tests/httpstream.test.ts +0 -277
  295. package/tests/immutablelog.test.ts +0 -157
  296. package/tests/inboundguard.test.ts +0 -177
  297. package/tests/lazyload.test.ts +0 -72
  298. package/tests/librarymodes.test.ts +0 -623
  299. package/tests/llm.test.ts +0 -696
  300. package/tests/manifest.test.ts +0 -226
  301. package/tests/maskinputs.test.ts +0 -150
  302. package/tests/matrixverify.test.ts +0 -111
  303. package/tests/mavenpack.test.ts +0 -75
  304. package/tests/mcpserver.test.ts +0 -704
  305. package/tests/memory.test.ts +0 -4155
  306. package/tests/memorycare.test.ts +0 -230
  307. package/tests/migrateplan.test.ts +0 -349
  308. package/tests/minimization.test.ts +0 -414
  309. package/tests/modelroute.test.ts +0 -112
  310. package/tests/multiagent.test.ts +0 -440
  311. package/tests/nativebridge.test.ts +0 -807
  312. package/tests/navigation.test.ts +0 -284
  313. package/tests/nugetpack.test.ts +0 -83
  314. package/tests/orchestration.test.ts +0 -449
  315. package/tests/originpolicy.test.ts +0 -405
  316. package/tests/outputcompare.test.ts +0 -97
  317. package/tests/package.test.ts +0 -91
  318. package/tests/pagebridge.test.ts +0 -5909
  319. package/tests/pentest.test.ts +0 -75
  320. package/tests/perfrecords.test.ts +0 -257
  321. package/tests/permdiff.test.ts +0 -85
  322. package/tests/phishguard.test.ts +0 -128
  323. package/tests/pickerviews.test.ts +0 -155
  324. package/tests/pipelines.test.ts +0 -381
  325. package/tests/planlint.test.ts +0 -240
  326. package/tests/planreview.test.ts +0 -383
  327. package/tests/platformtargets.test.ts +0 -56
  328. package/tests/policy.test.ts +0 -8284
  329. package/tests/portability.test.ts +0 -96
  330. package/tests/progress.test.ts +0 -1036
  331. package/tests/promptlibrary.test.ts +0 -116
  332. package/tests/protocol.test.ts +0 -4793
  333. package/tests/publishingpipeline.test.ts +0 -314
  334. package/tests/quickactions.test.ts +0 -164
  335. package/tests/readiness.test.ts +0 -78
  336. package/tests/recipes.test.ts +0 -163
  337. package/tests/redactshots.test.ts +0 -216
  338. package/tests/registernvidiakeys.ts +0 -51
  339. package/tests/relayserve.test.ts +0 -304
  340. package/tests/releasenotes.test.ts +0 -276
  341. package/tests/resilience.test.ts +0 -467
  342. package/tests/resourceaware.test.ts +0 -187
  343. package/tests/runbudget.test.ts +0 -125
  344. package/tests/runhistory.test.ts +0 -299
  345. package/tests/runreplay.test.ts +0 -113
  346. package/tests/runresume.test.ts +0 -184
  347. package/tests/runstate.test.ts +0 -288
  348. package/tests/runtimeadapters.test.ts +0 -251
  349. package/tests/safariskeleton.test.ts +0 -115
  350. package/tests/sandboxframe.test.ts +0 -196
  351. package/tests/sbom.test.ts +0 -105
  352. package/tests/secretvault.test.ts +0 -188
  353. package/tests/selcache.test.ts +0 -111
  354. package/tests/sessioninterface.test.ts +0 -791
  355. package/tests/sessions.test.ts +0 -591
  356. package/tests/siteprefs.test.ts +0 -228
  357. package/tests/snapshotdelta.test.ts +0 -165
  358. package/tests/statusviews.test.ts +0 -237
  359. package/tests/stepmeter.test.ts +0 -200
  360. package/tests/streamparse.test.ts +0 -163
  361. package/tests/stylepruning.test.ts +0 -90
  362. package/tests/surfaces.test.ts +0 -282
  363. package/tests/sweep.test.ts +0 -78
  364. package/tests/syncbridge.test.ts +0 -164
  365. package/tests/taskqueue.test.ts +0 -206
  366. package/tests/toolcalls.test.ts +0 -336
  367. package/tests/toolcatalog.test.ts +0 -201
  368. package/tests/torture-agent.test.ts +0 -706
  369. package/tests/torture-auth.test.ts +0 -461
  370. package/tests/torture-commands.test.ts +0 -895
  371. package/tests/torture-data.test.ts +0 -923
  372. package/tests/torture-environments.test.ts +0 -285
  373. package/tests/torture-export.test.ts +0 -263
  374. package/tests/torture-http.test.ts +0 -1379
  375. package/tests/torture-llm.test.ts +0 -808
  376. package/tests/torture-memory.test.ts +0 -1084
  377. package/tests/torture-plan.test.ts +0 -829
  378. package/tests/torture-run.test.ts +0 -626
  379. package/tests/torture-session.test.ts +0 -928
  380. package/tests/torture-swarm.test.ts +0 -1245
  381. package/tests/torture-tools.test.ts +0 -964
  382. package/tests/torture-workflow.test.ts +0 -2354
  383. package/tests/tourviews.test.ts +0 -52
  384. package/tests/transparency.test.ts +0 -147
  385. package/tests/trigger.test.ts +0 -941
  386. package/tests/v1sunset.test.ts +0 -428
  387. package/tests/virtlist.test.ts +0 -94
  388. package/tests/vision.test.ts +0 -540
  389. package/tests/vsixpack.test.ts +0 -145
  390. package/tests/webapi.test.ts +0 -523
  391. package/tests/workflow.test.ts +0 -1785
  392. package/tests/workfloweditor.test.ts +0 -759
  393. package/tests/xpipack.test.ts +0 -128
  394. package/tools.ts +0 -734
  395. package/types.ts +0 -9908
  396. package/umd.ts +0 -9
  397. package/utils.ts +0 -249
  398. package/version.ts +0 -8
  399. package/versionregistry.ts +0 -1310
  400. package/views.ts +0 -2153
  401. package/vitest.config.ts +0 -12
  402. package/web/App.tsx +0 -78
  403. package/web/README.md +0 -47
  404. package/web/capacitor.config.ts +0 -46
  405. package/web/const.ts +0 -19
  406. package/web/control.shell.tsx +0 -66
  407. package/web/dashboardpage.ts +0 -554
  408. package/web/db.ts +0 -493
  409. package/web/design.html +0 -541
  410. package/web/gatewayview/Gateway.tsx +0 -345
  411. package/web/gatewayview/config.ts +0 -1042
  412. package/web/gatewayview/definition.ts +0 -174
  413. package/web/icons.ts +0 -15
  414. package/web/optionspage.ts +0 -691
  415. package/web/popup.ts +0 -1651
  416. package/web/schema.prisma +0 -176
  417. package/web/sidepanel.ts +0 -7941
  418. package/web/sitemanifest.json +0 -13
  419. package/web/terminal-ui.tsx +0 -622
  420. package/web/transparencypage.ts +0 -541
  421. package/web/vite.config.ts +0 -39
  422. package/web/workspace.ts +0 -23
  423. package/workbenchmemory.ts +0 -104
  424. package/workbenchsession.ts +0 -304
  425. package/workflow.ts +0 -4495
  426. /package/{dist/bun.d.ts → bun.d.ts} +0 -0
  427. /package/{dist/cli.d.ts → cli.d.ts} +0 -0
  428. /package/{dist/companion.d.ts → companion.d.ts} +0 -0
  429. /package/{dist/crossbrowser.d.ts → crossbrowser.d.ts} +0 -0
  430. /package/{dist/dashdone.d.ts → dashdone.d.ts} +0 -0
  431. /package/{dist/deno.d.ts → deno.d.ts} +0 -0
  432. /package/{dist/hardening.d.ts → hardening.d.ts} +0 -0
  433. /package/{dist/headless.d.ts → headless.d.ts} +0 -0
  434. /package/{dist/http.d.ts → http.d.ts} +0 -0
  435. /package/{dist/index.d.ts → index.d.ts} +0 -0
  436. /package/{dist/mcp.d.ts → mcp.d.ts} +0 -0
  437. /package/{dist/memory.d.ts → memory.d.ts} +0 -0
  438. /package/{dist/node.d.ts → node.d.ts} +0 -0
  439. /package/{dist/pack.d.ts → pack.d.ts} +0 -0
  440. /package/{dist/policy.d.ts → policy.d.ts} +0 -0
  441. /package/{dist/progress.d.ts → progress.d.ts} +0 -0
  442. /package/{dist/protocol.d.ts → protocol.d.ts} +0 -0
  443. /package/{dist/server.d.ts → server.d.ts} +0 -0
  444. /package/{dist/umd.d.ts → umd.d.ts} +0 -0
package/web/popup.ts DELETED
@@ -1,1651 +0,0 @@
1
- import { hostpattern, normalizeendpoint } from "../policy.js";
2
- import { bridgestatuslabel } from "../bridge.js";
3
- import type {
4
- capabilityreport,
5
- captchahandoff,
6
- clipboardconsentrecord,
7
- consoleconsentrecord,
8
- dataset,
9
- dialogdecision,
10
- downloadrecord,
11
- keyholdstate,
12
- navqueues,
13
- navstate,
14
- quarantineentry,
15
- streamstate,
16
- tabbadge,
17
- waitprofilerecord,
18
- } from "../types.js";
19
- import type { windowshape } from "../commands.js";
20
-
21
- type pagesignallike = { language?: string; template?: string; scrolllocked?: boolean; banner?: string };
22
-
23
- const endpointinput = document.querySelector<HTMLInputElement>("#endpoint");
24
- const statusnode = document.querySelector<HTMLElement>("#status");
25
- const capabilitiesnode = document.querySelector<HTMLElement>("#capabilities");
26
- const livenode = document.querySelector<HTMLElement>("#livelogic");
27
- const navstatenode = document.querySelector<HTMLElement>("#navstate");
28
- const windowlayoutnode = document.querySelector<HTMLElement>("#windowlayout");
29
- const captchastatenode = document.querySelector<HTMLElement>("#captchastate");
30
- const datasetstatenode = document.querySelector<HTMLElement>("#datasetstate");
31
- const downloadstatenode = document.querySelector<HTMLElement>("#downloadstate");
32
- const clipboardstatenode = document.querySelector<HTMLElement>("#clipboardstate");
33
- const capturestatenode = document.querySelector<HTMLElement>("#capturestate");
34
- const mediastatenode = document.querySelector<HTMLElement>("#mediastate");
35
- const callstatenode = document.querySelector<HTMLElement>("#callstate");
36
- const rulestatenode = document.querySelector<HTMLElement>("#rulestate");
37
- const netstatenode = document.querySelector<HTMLElement>("#netstate");
38
- const debugstatenode = document.querySelector<HTMLElement>("#debugstate");
39
- const emulationstatenode = document.querySelector<HTMLElement>("#emulationstate");
40
- const sessionstatenode = document.querySelector<HTMLElement>("#sessionstate");
41
- const workflowstatenode = document.querySelector<HTMLElement>("#workflowstate");
42
- const triggerstatenode = document.querySelector<HTMLElement>("#triggerstate");
43
- const mcpstatenode = document.querySelector<HTMLElement>("#mcpstate");
44
- const modelstatenode = document.querySelector<HTMLElement>("#modelstate");
45
- const swarmstatenode = document.querySelector<HTMLElement>("#swarmstate");
46
- const environmentstatenode = document.querySelector<HTMLElement>("#environmentstate");
47
- const perfstatenode = document.querySelector<HTMLElement>("#perfstate");
48
- const securitystatenode = document.querySelector<HTMLElement>("#securitystate");
49
- const securitycontrols = document.querySelector<HTMLElement>("#securitycontrols");
50
- const sessiongridstatenode = document.querySelector<HTMLElement>("#sessiongridstate");
51
- const sessiongridcontrols = document.querySelector<HTMLElement>("#sessiongridcontrols");
52
- const environmentcontrols = document.querySelector<HTMLElement>("#environmentcontrols");
53
- const workflowcontrols = document.querySelector<HTMLElement>("#workflowcontrols");
54
- const windowcontrols = document.querySelector<HTMLElement>("#windowcontrols");
55
- const connectbutton = document.querySelector<HTMLButtonElement>("#connect");
56
- const sessionbutton = document.querySelector<HTMLButtonElement>("#session");
57
- const pausebutton = document.querySelector<HTMLButtonElement>("#pause");
58
- const stopbutton = document.querySelector<HTMLButtonElement>("#stop");
59
- const killswitchbutton = document.querySelector<HTMLButtonElement>("#killswitch");
60
- const openbutton = document.querySelector<HTMLButtonElement>("#openpanel");
61
-
62
- function status(message: string, error = false): void {
63
- if (statusnode) {
64
- statusnode.textContent = message;
65
- statusnode.dataset.state = error ? "error" : "ready";
66
- }
67
- }
68
- async function request(message: unknown): Promise<unknown> {
69
- const response = (await chrome.runtime.sendMessage(message)) as { ok: boolean; value?: unknown; error?: string };
70
- if (!response.ok) throw new Error(response.error);
71
- return response.value;
72
- }
73
-
74
- function pauselabel(paused: boolean): string {
75
- return paused ? "Resume session" : "Pause session";
76
- }
77
-
78
- /** Renders the live optional capability set the user has granted. */
79
- function rendercapabilities(report?: capabilityreport): void {
80
- if (!capabilitiesnode) return;
81
- if (!report) {
82
- capabilitiesnode.textContent = "Capabilities unknown.";
83
- return;
84
- }
85
- const granted = [
86
- `tabs ${report.tabs ? "granted" : "absent"}`,
87
- `downloads ${report.downloads ? "granted" : "absent"}`,
88
- `clipboard read ${report.clipboardread ? "granted" : "absent"}`,
89
- `clipboard write ${report.clipboardwrite ? "granted" : "absent"}`,
90
- ];
91
- capabilitiesnode.textContent = `Optional capabilities: ${granted.join(" · ")}.`;
92
- }
93
-
94
- /** Renders the currently held keys and the dialogs answered by the reviewed policy. */
95
- function renderlivestate(holds?: keyholdstate[], dialogs?: dialogdecision[], signals?: pagesignallike): void {
96
- if (!livenode) return;
97
- const held = (holds ?? []).filter((hold) => hold.releasedat === undefined);
98
- const heldsummary =
99
- held.length > 0 ? held.map((hold) => `${hold.key}${hold.holdid ? ` (${hold.holdid})` : ""}`).join(", ") : "none";
100
- const dialogsummary = (dialogs ?? []).length > 0 ? `${dialogs?.length} answered` : "none";
101
- const pages =
102
- signals && (signals.language !== undefined || signals.template !== undefined || signals.scrolllocked !== undefined)
103
- ? [
104
- signals.language !== undefined ? `language ${signals.language || "unknown"}` : "",
105
- signals.template !== undefined ? `template ${signals.template}` : "",
106
- signals.scrolllocked !== undefined ? `scroll lock ${signals.scrolllocked ? "on" : "off"}` : "",
107
- ]
108
- .filter(Boolean)
109
- .join(" · ")
110
- : "unknown";
111
- livenode.textContent = `Held keys: ${heldsummary} · Open dialogs answered: ${dialogsummary} · Page signals: ${pages}.`;
112
- }
113
-
114
- /** Renders the load phase, offline state, active wait profile and queued navigation targets of the session tab. */
115
- function rendernavstate(state?: navstate, offline?: boolean, profile?: waitprofilerecord, queues?: navqueues): void {
116
- if (!navstatenode) return;
117
- const phase = state?.phase ? `load phase ${state.phase}` : "load phase unknown";
118
- const offlinestate = offline === undefined ? "connectivity unknown" : offline ? "offline" : "online";
119
- const wait = profile ? `wait profile ${profile.profile.signals.join("+")}` : "no wait profile";
120
- const queued = queues
121
- ? `queued targets ${queues.prefetch + queues.batchopen} (${queues.prefetch} prefetch · ${queues.batchopen} batch open)`
122
- : "no queued targets";
123
- navstatenode.textContent = `Navigation: ${phase} · ${offlinestate} · ${wait} · ${queued}.`;
124
- }
125
-
126
- /** Renders the current window layout and the quick window state buttons. */
127
- function renderwindowlayout(windows?: windowshape[], badges?: tabbadge[]): void {
128
- if (!windowlayoutnode || !windowcontrols) return;
129
- windowlayoutnode.textContent =
130
- windows === undefined
131
- ? "Window layout: unknown."
132
- : `Window layout: ${windows.length} window${windows.length === 1 ? "" : "s"}${windows.length > 0 ? ` (${windows.map((item) => `${item.windowid}: ${item.state}${item.incognito ? " incognito" : ""}`).join(" · ")})` : ""} · ${new Set((badges ?? []).map((badge) => badge.tabid)).size} tab${new Set((badges ?? []).map((badge) => badge.tabid)).size === 1 ? "" : "s"} with active tasks.`;
133
- windowcontrols.replaceChildren();
134
- for (const item of (windows ?? []).slice(0, 4)) {
135
- for (const state of ["maximized", "minimized", "normal"] as const) {
136
- const quick = document.createElement("button");
137
- quick.type = "button";
138
- quick.className = "secondary";
139
- quick.textContent = `${state === "normal" ? "restore" : state} ${item.windowid}`;
140
- quick.addEventListener("click", () =>
141
- request({ kind: "windowstate", windowid: item.windowid, state })
142
- .then(() => {
143
- status(`Window ${item.windowid} set to ${state}.`);
144
- return restore();
145
- })
146
- .catch((error) => status(error instanceof Error ? error.message : String(error), true)),
147
- );
148
- windowcontrols.append(quick);
149
- }
150
- }
151
- }
152
-
153
- /** Renders the captcha handoff state while a plan waits for the user. */
154
- function rendercaptchastate(captchas?: captchahandoff[]): void {
155
- if (!captchastatenode) return;
156
- const open = (captchas ?? []).find((handoff) => !handoff.resolved);
157
- if (open) {
158
- captchastatenode.dataset.state = "error";
159
- captchastatenode.textContent = `Captcha handoff open on ${open.origin}; the plan waits until you resolve it in the review panel.`;
160
- return;
161
- }
162
- delete captchastatenode.dataset.state;
163
- captchastatenode.textContent = "Captcha handoff: none.";
164
- }
165
-
166
- /** Renders the active dataset row count and the stream state of the session. */
167
- function renderdatasetstate(datasets?: dataset[], streams?: streamstate[]): void {
168
- if (!datasetstatenode) return;
169
- const rows = (datasets ?? []).reduce((total, datasetvalue) => total + datasetvalue.rows.length, 0);
170
- const count = datasets?.length ?? 0;
171
- const stream = (streams ?? []).find((item) => item.done !== true) ?? (streams ?? [])[0];
172
- const streamsummary = stream
173
- ? ` · stream ${stream.name} chunk ${stream.chunk} of ${stream.chunks}${stream.done ? " complete" : ""}`
174
- : "";
175
- datasetstatenode.textContent =
176
- count === 0
177
- ? "Datasets: none collected yet."
178
- : `Datasets: ${count} collected (${rows} row${rows === 1 ? "" : "s"})${streamsummary}.`;
179
- }
180
-
181
- /** Renders the live batch download states and the quarantined files awaiting scan verdicts. */
182
- function renderdownloadstate(downloads?: downloadrecord[], quarantines?: quarantineentry[]): void {
183
- if (!downloadstatenode) return;
184
- const states = (downloads ?? []).filter(
185
- (record) => record.state === "running" || record.state === "queued" || record.state === "paused",
186
- );
187
- const completed = (downloads ?? []).filter((record) => record.state === "complete").length;
188
- const failed = (downloads ?? []).filter((record) => record.state === "failed").length;
189
- const pending = (quarantines ?? []).filter((entry) => entry.scan === "pending").length;
190
- const parts = [
191
- states.length > 0 ? `${states.length} in flight (${states.map((record) => record.state).join(" · ")})` : "",
192
- completed > 0 ? `${completed} completed` : "",
193
- failed > 0 ? `${failed} failed` : "",
194
- pending > 0 ? `${pending} quarantined awaiting scan` : "",
195
- ].filter(Boolean);
196
- downloadstatenode.textContent =
197
- (downloads ?? []).length === 0 && pending === 0
198
- ? "Downloads: none."
199
- : `Downloads: ${parts.join(" · ") || "none active"}.`;
200
- }
201
-
202
- /** Renders the clipboard consent state while a read waits for the user approval. */
203
- function renderclipboardstate(consents?: clipboardconsentrecord[]): void {
204
- if (!clipboardstatenode) return;
205
- const waiting = (consents ?? []).filter((record) => record.approved === undefined);
206
- if (waiting.length > 0) {
207
- clipboardstatenode.dataset.state = "error";
208
- clipboardstatenode.textContent = `Clipboard consent: ${waiting.length} read prompt${waiting.length === 1 ? "" : "s"} waiting for your approval in the review panel.`;
209
- return;
210
- }
211
- delete clipboardstatenode.dataset.state;
212
- clipboardstatenode.textContent = "Clipboard consent: none waiting.";
213
- }
214
-
215
- /** Renders the capture count of the active run beside the capture policy. */
216
- function rendercapturestate(captures?: Array<{ id: string }>, policy?: string): void {
217
- if (!capturestatenode) return;
218
- const count = captures?.length ?? 0;
219
- const mode = policy ?? "manual";
220
- capturestatenode.textContent =
221
- count === 0 ? `Captures: none (policy ${mode}).` : `Captures: ${count} of the run (policy ${mode}).`;
222
- }
223
-
224
- /** Renders the observed request count of the active run beside the live channel and stream state. */
225
- function rendernetstate(exchanges?: Array<{ id: string }>, channels?: Array<{ id: string; state: string }>): void {
226
- if (!netstatenode) return;
227
- const count = exchanges?.length ?? 0;
228
- const live = (channels ?? []).filter((channel) => channel.state === "open" || channel.state === "connecting").length;
229
- netstatenode.textContent = `Observed requests: ${count} of the run${live > 0 ? ` · ${live} live channel${live === 1 ? "" : "s"}` : ""}.`;
230
- }
231
-
232
- /** Renders the error count captured in the active run beside pending console capture consent prompts, the debugger attached badge of devtools sessions and the profiling badge of active profile or trace instruments. */
233
- function renderdebugstate(
234
- timeline?: { errors?: Array<{ id: string }>; rejections?: Array<{ id: string }> },
235
- consoleconsents?: consoleconsentrecord[],
236
- debuggergrants?: Array<{ approved?: boolean; revokedat?: number }>,
237
- cdpattached?: number,
238
- profileactive?: number,
239
- sourcemapconsents?: Array<{ approved?: boolean; revokedat?: number }>,
240
- ): void {
241
- if (!debugstatenode) return;
242
- const errors = timeline?.errors?.length ?? 0;
243
- const rejections = timeline?.rejections?.length ?? 0;
244
- const waiting = (consoleconsents ?? []).filter((consent) => consent.approved === undefined).length;
245
- const debuggerwaiting = (debuggergrants ?? []).filter(
246
- (grant) => grant.approved === undefined && grant.revokedat === undefined,
247
- ).length;
248
- const attached = cdpattached ?? 0;
249
- const profiling = profileactive ?? 0;
250
- const sourcemapwaiting = (sourcemapconsents ?? []).filter(
251
- (consent) => consent.approved === undefined && consent.revokedat === undefined,
252
- ).length;
253
- debugstatenode.textContent =
254
- errors === 0 &&
255
- rejections === 0 &&
256
- waiting === 0 &&
257
- debuggerwaiting === 0 &&
258
- attached === 0 &&
259
- profiling === 0 &&
260
- sourcemapwaiting === 0
261
- ? "Timeline: no errors captured."
262
- : `Timeline: ${errors} error${errors === 1 ? "" : "s"}${rejections > 0 ? ` · ${rejections} rejection${rejections === 1 ? "" : "s"}` : ""} captured in the active run${waiting > 0 ? ` · ${waiting} console consent prompt${waiting === 1 ? "" : "s"} waiting` : ""}${debuggerwaiting > 0 ? ` · ${debuggerwaiting} debugger consent prompt${debuggerwaiting === 1 ? "" : "s"} waiting` : ""}${attached > 0 ? ` · debugger attached on ${attached} session${attached === 1 ? "" : "s"}` : ""}${profiling > 0 ? ` · profiling active on ${profiling} instrument${profiling === 1 ? "" : "s"}` : ""}${sourcemapwaiting > 0 ? ` · ${sourcemapwaiting} source map prompt${sourcemapwaiting === 1 ? "" : "s"} waiting` : ""}.`;
263
- if (errors > 0 || waiting > 0 || debuggerwaiting > 0 || sourcemapwaiting > 0) debugstatenode.dataset.state = "error";
264
- else delete debugstatenode.dataset.state;
265
- if (attached > 0) debugstatenode.dataset.debugger = "attached";
266
- else delete debugstatenode.dataset.debugger;
267
- if (profiling > 0) debugstatenode.dataset.profile = "active";
268
- else delete debugstatenode.dataset.profile;
269
- }
270
-
271
- /** Renders the active traffic rule count badge of controlled runs. */
272
- function renderrulestate(activerules?: number, blocked?: number, mocked?: number): void {
273
- if (!rulestatenode) return;
274
- const rules = activerules ?? 0;
275
- rulestatenode.textContent =
276
- rules === 0
277
- ? "Traffic rules: none active."
278
- : `Traffic rules: ${rules} active of the run${blocked !== undefined && blocked > 0 ? ` · ${blocked} blocked request${blocked === 1 ? "" : "s"}` : ""}${mocked !== undefined && mocked > 0 ? ` · ${mocked} mocked response${mocked === 1 ? "" : "s"}` : ""}.`;
279
- if (rules > 0) rulestatenode.dataset.state = "controlled";
280
- else delete rulestatenode.dataset.state;
281
- }
282
-
283
- /** Renders the media state badge: the recording indicator of active recordings beside the stored media record count. */
284
- function rendercallstate(calls?: Array<{ id: string }>, fetchconsents?: Array<{ approved?: boolean }>): void {
285
- if (!callstatenode) return;
286
- const count = calls?.length ?? 0;
287
- const waiting = (fetchconsents ?? []).filter((consent) => consent.approved === undefined).length;
288
- callstatenode.textContent = `Outbound calls: ${count} of the run${waiting > 0 ? ` · ${waiting} header consent prompt${waiting === 1 ? "" : "s"} waiting` : ""}.`;
289
- }
290
-
291
- function rendermediastate(
292
- media?: Array<{ id: string }>,
293
- recordingactive?: Array<{ id: string; kind: string }>,
294
- recordingconsents?: Array<{ approved?: boolean }>,
295
- ): void {
296
- if (!mediastatenode) return;
297
- const active = (recordingactive ?? []).length;
298
- const waiting = (recordingconsents ?? []).filter((record) => record.approved === undefined).length;
299
- const count = media?.length ?? 0;
300
- const recording =
301
- active > 0
302
- ? `● recording ${active} active capture${active === 1 ? "" : "s"}`
303
- : waiting > 0
304
- ? `recording consent: ${waiting} prompt${waiting === 1 ? "" : "s"} waiting`
305
- : "";
306
- mediastatenode.textContent = `Media: ${count} record${count === 1 ? "" : "s"}${recording ? ` · ${recording}` : ""}.`;
307
- if (active > 0) mediastatenode.dataset.state = "recording";
308
- else delete mediastatenode.dataset.state;
309
- }
310
-
311
- /** Renders the active emulation badge of the run: the layer names and the pending location consent prompts. */
312
- function renderemulationstate(layers?: string[], locationconsents?: Array<{ approved?: boolean }>): void {
313
- if (!emulationstatenode) return;
314
- const active = layers ?? [];
315
- const waiting = (locationconsents ?? []).filter((consent) => consent.approved === undefined).length;
316
- emulationstatenode.textContent = `Emulation: ${active.length > 0 ? `${active.length} active layer${active.length === 1 ? "" : "s"} (${active.join(", ")})` : "no active layer"}${waiting > 0 ? ` · ${waiting} location consent prompt${waiting === 1 ? "" : "s"} waiting` : ""}.`;
317
- if (active.length > 0) emulationstatenode.dataset.emulated = "active";
318
- else delete emulationstatenode.dataset.emulated;
319
- if (waiting > 0) emulationstatenode.dataset.state = "error";
320
- else delete emulationstatenode.dataset.state;
321
- }
322
-
323
- /** Renders the saved session count and the auto snapshot state with its interval and count. */
324
- function rendersessionstate(
325
- memory?: {
326
- records: Array<{ id: string; name: string }>;
327
- auto?: { period: number; maxsnapshots: number; expiry: number };
328
- },
329
- auto?: { interval: { period: number; maxsnapshots: number; expiry: number }; count: number },
330
- ): void {
331
- if (!sessionstatenode) return;
332
- const saved = memory?.records.length ?? 0;
333
- const interval = auto?.interval ?? memory?.auto;
334
- sessionstatenode.textContent = `Sessions: ${saved} saved${interval !== undefined ? ` · auto snapshot every ${interval.period} ms (${auto?.count ?? 0} of ${interval.maxsnapshots})` : " · no auto interval"}.`;
335
- if (interval !== undefined) sessionstatenode.dataset.auto = "active";
336
- else delete sessionstatenode.dataset.auto;
337
- }
338
-
339
- /** Renders the active workflow state with pause and cancel buttons, the count of workflows running in the background and the active loop iteration count of the running runs. */
340
- function renderworkflowstate(
341
- runs?: Array<{ id: string; state: string; cursor: number; workflowid: string; dryrun?: boolean }>,
342
- control?: Array<{ runid: string; kind: string; loops?: Array<{ path: string }> }>,
343
- ): void {
344
- if (!workflowstatenode) return;
345
- const all = runs ?? [];
346
- const running = all.filter((run) => run.state === "running");
347
- const paused = all.filter((run) => run.state === "paused");
348
- const iterations = (control ?? [])
349
- .filter((decision) => decision.kind === "loop" && running.some((run) => run.id === decision.runid))
350
- .reduce((total, decision) => total + (decision.loops?.length ?? 0), 0);
351
- workflowstatenode.textContent = `Workflows: ${running.length} running in the background${paused.length > 0 ? ` · ${paused.length} paused` : ""}${running.length > 0 && iterations > 0 ? ` · loop iteration ${iterations}` : ""}${all.length > 0 ? ` · latest ${all[0]?.state ?? ""}` : " · none composed yet"}.`;
352
- if (running.length > 0) workflowstatenode.dataset.state = "active";
353
- else delete workflowstatenode.dataset.state;
354
- if (!workflowcontrols) return;
355
- workflowcontrols.replaceChildren();
356
- for (const run of [...running, ...paused]) {
357
- const controls = document.createElement("span");
358
- const pause = document.createElement("button");
359
- pause.className = "secondary";
360
- pause.textContent = run.state === "running" ? `Pause ${run.id.slice(0, 6)}` : `Resume ${run.id.slice(0, 6)}`;
361
- pause.disabled = false;
362
- pause.addEventListener("click", async () => {
363
- try {
364
- await request({ kind: run.state === "running" ? "pauseworkflowrun" : "resumeworkflowrun", runid: run.id });
365
- status(
366
- run.state === "running"
367
- ? "Workflow run paused at its checkpoint."
368
- : "Workflow run resumed from its checkpoint.",
369
- );
370
- await restore();
371
- } catch (error) {
372
- status(error instanceof Error ? error.message : String(error), true);
373
- }
374
- });
375
- const cancel = document.createElement("button");
376
- cancel.className = "danger";
377
- cancel.textContent = `Cancel ${run.id.slice(0, 6)}`;
378
- cancel.addEventListener("click", async () => {
379
- try {
380
- await request({ kind: "cancelworkflowrun", runid: run.id, reason: "popup cancel" });
381
- status("Workflow run cancelled with its reason recorded.");
382
- await restore();
383
- } catch (error) {
384
- status(error instanceof Error ? error.message : String(error), true);
385
- }
386
- });
387
- controls.append(pause, " ", cancel);
388
- workflowcontrols.append(controls);
389
- }
390
- }
391
-
392
- /** Renders the trigger layer: the armed rule count, the queued fires held while the session is paused and the button rule status of the toolbar click. */
393
- /** Renders the mcp server running indicator of the agent protocol: the running state, the localhost bind, the connected clients, the paired count, the pending approval gates as notifications and the long tool progress of the in flight calls that announces itself while the panel stays hidden. */
394
- /** Renders the active model per running task kind: every routed task kind shows its provider and model so the user reads which model drives which task. */
395
- function rendermodelstate(state?: {
396
- providers: Array<{ id: string; name: string; status: string }>;
397
- tasks: Array<{ kind: string; provider?: string; model?: string }>;
398
- usage: { totaltokens: number; cost: number; calls: number };
399
- }): void {
400
- if (!modelstatenode) return;
401
- if (!state) {
402
- modelstatenode.textContent = "Models: none connected.";
403
- return;
404
- }
405
- const routed = state.tasks.filter((task) => task.provider !== undefined);
406
- modelstatenode.textContent = `Models: ${
407
- routed.length > 0
408
- ? routed
409
- .slice(0, 3)
410
- .map((task) => `${task.kind} ${task.model}`)
411
- .join(" · ")
412
- : "no route yet"
413
- } · ${state.usage.calls} call${state.usage.calls === 1 ? "" : "s"} · ${state.usage.totaltokens} tokens.`;
414
- }
415
-
416
- /** Renders the swarm summary: the popup reads the agent count with the active, paused and stopped split, the claimed tasks of the shared queue, the elected leader and the held lock count, so the user reads the swarm at a glance. */
417
- /** Renders the execution environment status of the running session: the environments of the open run, the keepalive heartbeat indicator, the offscreen capability state and the zombie warning with its reap action. */
418
- function renderenvironmentstate(view?: {
419
- report: {
420
- environments: Array<{ stepid: string; environment: string }>;
421
- workers: number;
422
- keepalive?: { runid: string; state: string; beats: number; lastbeatat: number; portopen: boolean };
423
- };
424
- offscreengranted: boolean;
425
- parseoffload: boolean;
426
- zombies: string[];
427
- grants?: string[];
428
- }): void {
429
- if (!environmentstatenode) return;
430
- if (!view) {
431
- environmentstatenode.textContent = "Environments: no run state.";
432
- return;
433
- }
434
- const counted = new Map<string, number>();
435
- for (const entry of view.report.environments)
436
- counted.set(entry.environment, (counted.get(entry.environment) ?? 0) + 1);
437
- const breakdown = [...counted.entries()].map(([environment, count]) => `${environment} ${count}`).join(" · ");
438
- const keepalive = view.report.keepalive;
439
- environmentstatenode.textContent = `Environments: ${breakdown.length > 0 ? breakdown : "no step executed"} · offscreen ${view.offscreengranted ? "granted" : "absent"} · offload ${view.parseoffload ? "on" : "off"}${view.report.workers > 0 ? ` · workers ${view.report.workers}` : ""} · keepalive ${keepalive ? `${keepalive.state === "active" && keepalive.portopen ? "♥" : "closed"} ${keepalive.beats} beat${keepalive.beats === 1 ? "" : "s"}` : "none"}${view.zombies.length > 0 ? ` · zombie runs ${view.zombies.join(", ")}` : ""}${view.grants !== undefined ? ` · grants ${view.grants.length > 0 ? view.grants.join(", ") : "default"}` : ""}.`;
440
- if (keepalive?.state === "active" && keepalive.portopen) environmentstatenode.dataset.state = "active";
441
- else delete environmentstatenode.dataset.state;
442
- if (environmentcontrols) {
443
- environmentcontrols.replaceChildren();
444
- if (view.zombies.length > 0) {
445
- const reap = document.createElement("button");
446
- reap.className = "secondary";
447
- reap.textContent = "Reap zombie runs";
448
- reap.addEventListener("click", () => {
449
- void request({ kind: "runstate", reap: true })
450
- .then(() => restore())
451
- .catch((error) => status(error instanceof Error ? error.message : String(error), true));
452
- });
453
- environmentcontrols.append(reap);
454
- }
455
- if (!view.offscreengranted) {
456
- const grant = document.createElement("button");
457
- grant.className = "secondary";
458
- grant.textContent = "Request offscreen grant";
459
- grant.addEventListener("click", () => {
460
- void request({ kind: "environments", requestcapability: true })
461
- .then(() => restore())
462
- .catch((error) => status(error instanceof Error ? error.message : String(error), true));
463
- });
464
- environmentcontrols.append(grant);
465
- }
466
- }
467
- }
468
-
469
- /** Renders the security posture of the active tab: the origin allowlist state, the originprofile summary, the consent window state, the denydefault notice and the safedefaults notice of an unprofiled origin that links to the transparencypage originprofile editor, with the allowlist grant request for a new origin. */
470
- function rendersecuritystate(
471
- view?: {
472
- posture: string;
473
- allowlist: Array<{ origin: string; profileid: string }>;
474
- profiles: Array<{ origin: string; grants: string[]; denials: string[] }>;
475
- windows: Array<{ origin: string; state: string; expiresat: number }>;
476
- sessionorigin?: string;
477
- chain: Array<{ runid: string; valid: boolean; entries: number; sealhash?: string }>;
478
- },
479
- activeorigin?: string,
480
- ): void {
481
- if (!securitystatenode) return;
482
- if (!view) {
483
- securitystatenode.textContent = "Security: state unknown.";
484
- return;
485
- }
486
- const origin = activeorigin ?? view.sessionorigin ?? "the active tab";
487
- const granted = view.allowlist.some((entry) => entry.origin === origin);
488
- const profile = view.profiles.find((candidate) => candidate.origin === origin);
489
- const windowstate = view.windows.find((candidate) => candidate.origin === origin && candidate.state === "active");
490
- const verified = view.chain.filter((chain) => chain.valid).length;
491
- securitystatenode.textContent = `Security: ${origin} ${granted ? "allowlisted" : "ungranted (denydefault)"}${profile !== undefined ? ` · profile ${profile.grants.length} granted / ${profile.denials.length} denied` : " · no origin profile"}${windowstate !== undefined ? ` · consent window until ${new Date(windowstate.expiresat).toLocaleTimeString()}` : " · no active consent window"} · ${verified}/${view.chain.length} log chain${view.chain.length === 1 ? "" : "s"} verified.`;
492
- if (granted) delete securitystatenode.dataset.state;
493
- else securitystatenode.dataset.state = "error";
494
- if (securitycontrols) {
495
- securitycontrols.replaceChildren();
496
- if (!granted) {
497
- const grant = document.createElement("button");
498
- grant.className = "secondary";
499
- grant.textContent = `Grant ${origin} allowlist entry`;
500
- grant.addEventListener("click", () => {
501
- void request({ kind: "security", allowlist: { add: { origin } } })
502
- .then(() => restore())
503
- .catch((error) => status(error instanceof Error ? error.message : String(error), true));
504
- });
505
- securitycontrols.append(grant);
506
- }
507
- if (profile === undefined && origin !== "the active tab") {
508
- const safedefaults = document.createElement("button");
509
- safedefaults.className = "secondary";
510
- safedefaults.textContent = `Safedefaults on ${origin}: reads only — open the originprofile editor`;
511
- safedefaults.addEventListener("click", () => {
512
- try {
513
- chrome.runtime.openOptionsPage(() => {
514
- void chrome.runtime.lastError;
515
- });
516
- } catch {
517
- status("The transparencypage did not open; open it from the extensions page.", true);
518
- }
519
- });
520
- securitycontrols.append(safedefaults);
521
- }
522
- if (view.chain.length > 0) {
523
- const seal = view.chain.find((chain) => chain.sealhash !== undefined);
524
- if (seal?.sealhash !== undefined) {
525
- const sealinfo = document.createElement("span");
526
- sealinfo.className = "muted";
527
- sealinfo.textContent = `Seal ${seal.sealhash.slice(0, 10)}…`;
528
- securitycontrols.append(sealinfo);
529
- }
530
- }
531
- }
532
- }
533
-
534
- function renderswarmstate(state?: {
535
- overview: { agents: number; active: number; paused: number; stopped: number; claimed: number; queued: number };
536
- killswitch: { engaged: boolean };
537
- topology?: { leaderid: string };
538
- locks?: Array<{ key: string }>;
539
- }): void {
540
- if (!swarmstatenode) return;
541
- if (!state) {
542
- swarmstatenode.textContent = "Swarm: no agents.";
543
- return;
544
- }
545
- swarmstatenode.textContent = `Swarm: ${state.overview.agents} agent${state.overview.agents === 1 ? "" : "s"}${state.overview.agents > 0 ? ` (${state.overview.active} active${state.overview.paused > 0 ? `, ${state.overview.paused} paused` : ""}${state.overview.stopped > 0 ? `, ${state.overview.stopped} stopped` : ""})` : ""} · ${state.overview.claimed} active task${state.overview.claimed === 1 ? "" : "s"}${state.overview.queued > 0 ? ` · ${state.overview.queued} queued` : ""}${state.topology !== undefined ? ` · leader ${state.topology.leaderid}` : ""}${state.locks !== undefined && state.locks.length > 0 ? ` · ${state.locks.length} lock${state.locks.length === 1 ? "" : "s"}` : ""}${state.killswitch.engaged ? " · killswitch engaged" : ""}.`;
546
- if (state.overview.active > 0 || state.killswitch.engaged) swarmstatenode.dataset.state = "active";
547
- else delete swarmstatenode.dataset.state;
548
- }
549
-
550
- function rendermcpstate(state?: {
551
- state: string;
552
- bind?: string;
553
- port: number;
554
- localhost: boolean;
555
- clients?: Array<{ id: string; paired: boolean }>;
556
- approvals?: Array<{ id: string; tool: string; state: string }>;
557
- inflight?: Array<{ callid: string; tool: string; chunks: number }>;
558
- progressnotices?: Array<{ callid: string; percent?: number; message: string; cancellable?: boolean; at: number }>;
559
- }): void {
560
- if (!mcpstatenode) return;
561
- const running = state?.state === "running";
562
- const clients = state?.clients ?? [];
563
- const paired = clients.filter((client) => client.paired).length;
564
- const pending = (state?.approvals ?? []).filter((approval) => approval.state === "pending");
565
- const inflight = state?.inflight ?? [];
566
- const notice = (state?.progressnotices ?? [])[0];
567
- mcpstatenode.textContent = `Mcp server: ${running ? `running on ${state?.bind ?? "127.0.0.1"}:${state?.port ?? 7436}${state?.localhost === false ? " (remote bind)" : ""} · ${clients.length} client${clients.length === 1 ? "" : "s"}${paired !== clients.length ? ` · ${paired} paired` : ""}${pending.length > 0 ? ` · ${pending.length} approval gate${pending.length === 1 ? "" : "s"} waiting (${[...new Set(pending.map((approval) => approval.tool))].slice(0, 3).join(", ")})` : ""}${inflight.length > 0 ? ` · ${inflight.length} call${inflight.length === 1 ? "" : "s"} in flight${notice !== undefined ? ` (${notice.percent !== undefined ? `${notice.percent}%` : notice.message}${notice.cancellable === false ? "" : ", cancellable in the panel"})` : ""}` : ""}` : (state?.state ?? "stopped")}.`;
568
- if (running) mcpstatenode.dataset.state = "active";
569
- else delete mcpstatenode.dataset.state;
570
- }
571
-
572
- function rendertriggerstate(
573
- rules?: Array<{ id: string; kind: string; enabled: boolean; paused?: boolean; nextfireat?: number }>,
574
- queued?: number,
575
- paused?: boolean,
576
- buttonfired?: boolean,
577
- ): void {
578
- if (!triggerstatenode) return;
579
- const armed = rules ?? [];
580
- const enabled = armed.filter((rule) => rule.enabled && rule.paused !== true);
581
- const scheduled = enabled.filter((rule) => rule.nextfireat !== undefined);
582
- const buttons = enabled.filter((rule) => rule.kind === "button");
583
- triggerstatenode.textContent = `Triggers: ${armed.length} armed rule${armed.length === 1 ? "" : "s"}${enabled.length !== armed.length ? ` · ${enabled.length} enabled` : ""}${scheduled.length > 0 ? ` · ${scheduled.length} scheduled` : ""}${(queued ?? 0) > 0 ? ` · ${queued} fire${queued === 1 ? "" : "s"} ${paused ? "held while the session is paused" : "queued"}` : ""}${buttons.length > 0 ? ` · toolbar button ${buttonfired ? "fired" : "ready"}` : ""}.`;
584
- if (enabled.length > 0) triggerstatenode.dataset.state = "active";
585
- else delete triggerstatenode.dataset.state;
586
- }
587
-
588
- /** Renders the 1.1.68 performance state of the popup: the active worker queue depth with the deferred count the backpressure holds and the startup module budget of the user prewarm set. */
589
- /** Renders the run heartbeat staleness of the long task: the last beat, its age against the user window and the zombie risk the popup warns about. */
590
- async function renderheartbeatstate(): Promise<void> {
591
- const node = document.querySelector<HTMLElement>("#heartbeatstate");
592
- if (!node) return;
593
- try {
594
- const view = (await request({ kind: "resilience", view: true })) as {
595
- runs: Array<{ runid: string; state: string; updatedat: number; heartbeat?: number; beats?: number }>;
596
- window: number;
597
- };
598
- const running = view.runs.filter((run) => run.state === "running");
599
- if (running.length === 0) {
600
- node.textContent = "Heartbeat: no running run heartbeats yet.";
601
- delete node.dataset.state;
602
- return;
603
- }
604
- const newest = running.reduce((one, two) => ((two.heartbeat ?? 0) > (one.heartbeat ?? 0) ? two : one));
605
- const beats = newest.beats ?? 0;
606
- const age = newest.heartbeat !== undefined ? Math.max(0, Date.now() - newest.heartbeat) : undefined;
607
- const stale = age !== undefined && age > view.window;
608
- node.textContent = `Heartbeat: run ${newest.runid} beat ${beats} time${beats === 1 ? "" : "s"}${age !== undefined ? ` · last beat ${Math.round(age / 1000)}s ago` : " · no beat recorded"} of the ${Math.round(view.window / 1000)}s window${stale ? " · stale, the zombiecheck may reap the run" : ""}.`;
609
- if (stale) node.dataset.state = "stale";
610
- else delete node.dataset.state;
611
- } catch {
612
- node.textContent = "Heartbeat: no run heartbeats yet.";
613
- }
614
- }
615
-
616
- /** Renders the at rest encryption status of the memory workspace: whether encryptrest stays on, how many stored items encrypt and whether every provenance stands complete for the export. */
617
- async function renderencryptstate(): Promise<void> {
618
- const node = document.querySelector<HTMLElement>("#encryptstate");
619
- if (!node) return;
620
- try {
621
- const view = (await request({ kind: "state", view: true })) as {
622
- memory: { items: number; encrypted: number; provenancecomplete: boolean };
623
- encryptrest: boolean;
624
- };
625
- node.textContent = `Encryption: encryptrest ${view.encryptrest ? "on" : "off"} · ${view.memory.encrypted} of ${view.memory.items} item${view.memory.items === 1 ? "" : "s"} encrypted${view.encryptrest ? " through the webcrypto derived key; the key never persists and the secret entry stays a consent prompt" : "; sensitive classes refuse plaintext writes once the user turns it on"}${view.memory.provenancecomplete ? " · provenance complete for the export" : " · provenance incomplete for the export"}.`;
626
- if (view.encryptrest) node.dataset.state = "encrypted";
627
- else delete node.dataset.state;
628
- } catch {
629
- node.textContent = "Encryption: no memory workspace yet.";
630
- delete node.dataset.state;
631
- }
632
- }
633
-
634
- /** Renders the fleet state of the 1.1.72 family: the agent count with its control states, the open escalations and reviews and the timestamp of the last killswitch stop. */
635
- async function renderfleetstate(): Promise<void> {
636
- const node = document.querySelector<HTMLElement>("#fleetstate");
637
- if (!node) return;
638
- try {
639
- const view = (await request({ kind: "fleet", view: true })) as {
640
- overview: {
641
- agents: number;
642
- active: number;
643
- paused: number;
644
- stopped: number;
645
- openescalations: number;
646
- openreviews: number;
647
- lastkillswitchat?: number;
648
- };
649
- };
650
- node.textContent = `Fleet: ${view.overview.agents} agent${view.overview.agents === 1 ? "" : "s"} (${view.overview.active} active, ${view.overview.paused} paused, ${view.overview.stopped} stopped)${view.overview.openescalations > 0 ? ` · ${view.overview.openescalations} open escalation${view.overview.openescalations === 1 ? "" : "s"}` : ""}${view.overview.openreviews > 0 ? ` · ${view.overview.openreviews} open review${view.overview.openreviews === 1 ? "" : "s"}` : ""}${view.overview.lastkillswitchat !== undefined ? ` · last killswitch ${new Date(view.overview.lastkillswitchat).toISOString()}` : " · killswitch never engaged"}.`;
651
- if (view.overview.paused > 0) node.dataset.state = "paused";
652
- else delete node.dataset.state;
653
- } catch {
654
- node.textContent = "Fleet: no agents.";
655
- delete node.dataset.state;
656
- }
657
- }
658
-
659
- /** Renders the worker state of the 1.1.73 family: the live worker count per origin the scaleworkers pass reads, so the popup shows where the fleet stands. */
660
- async function renderworkstate(): Promise<void> {
661
- const node = document.querySelector<HTMLElement>("#workstate");
662
- if (!node) return;
663
- try {
664
- const view = (await request({ kind: "work", view: true })) as {
665
- workersperorigin: Array<{ origin: string; count: number }>;
666
- suggestions: Array<{ origin: string; suggestion: string; reason: string }>;
667
- };
668
- if (view.workersperorigin.length === 0) {
669
- node.textContent = "Workers: none.";
670
- delete node.dataset.state;
671
- return;
672
- }
673
- node.textContent = `Workers: ${view.workersperorigin.map((entry) => `${entry.count} on ${entry.origin}`).join(" · ")}${view.suggestions.some((suggestion) => suggestion.suggestion !== "hold") ? ` · ${view.suggestions.filter((suggestion) => suggestion.suggestion !== "hold").length} scale suggestion${view.suggestions.filter((suggestion) => suggestion.suggestion !== "hold").length === 1 ? "" : "s"} in the sidepanel` : ""}.`;
674
- if (view.workersperorigin.some((entry) => entry.count > 0)) node.dataset.state = "active";
675
- else delete node.dataset.state;
676
- } catch {
677
- node.textContent = "Workers: none.";
678
- delete node.dataset.state;
679
- }
680
- }
681
-
682
- async function renderpipelinestate(): Promise<void> {
683
- const node = document.querySelector<HTMLElement>("#pipelinestate");
684
- if (!node) return;
685
- try {
686
- const view = (await request({ kind: "pipeline", view: true })) as {
687
- state: { running: number; paused: number; finished: number };
688
- progress: Array<{ rows: number; bytes: number; state: string }>;
689
- };
690
- const active = view.state.running + view.state.paused;
691
- if (active === 0 && view.state.finished === 0) {
692
- node.textContent = "Pipelines: none.";
693
- delete node.dataset.state;
694
- return;
695
- }
696
- node.textContent = `Pipelines: ${view.state.running} running · ${view.state.paused} paused · ${view.state.finished} finished${
697
- view.progress.some((entry) => entry.state !== "finished")
698
- ? ` · ${view.progress
699
- .filter((entry) => entry.state !== "finished")
700
- .reduce((total, entry) => total + entry.rows, 0)
701
- .toLocaleString()} row${view.progress.filter((entry) => entry.state !== "finished").reduce((total, entry) => total + entry.rows, 0) === 1 ? "" : "s"} streamed`
702
- : ""
703
- }.`;
704
- if (active > 0) node.dataset.state = "active";
705
- else delete node.dataset.state;
706
- } catch {
707
- node.textContent = "Pipelines: none.";
708
- delete node.dataset.state;
709
- }
710
- }
711
-
712
- async function rendertransportstate(): Promise<void> {
713
- const node = document.querySelector<HTMLElement>("#transportstate");
714
- if (!node) return;
715
- try {
716
- const view = (await request({ kind: "webapi", view: true })) as {
717
- subscriptions: Array<{ state: string }>;
718
- graphqlchannels: Array<{ completed: boolean }>;
719
- ratelimits: Array<{ origin: string }>;
720
- cache: Array<{ hits: number }>;
721
- };
722
- const open =
723
- view.subscriptions.filter((record) => record.state === "open").length +
724
- view.graphqlchannels.filter((channel) => !channel.completed).length;
725
- const hits = view.cache.reduce((total, entry) => total + entry.hits, 0);
726
- if (open === 0 && view.ratelimits.length === 0 && hits === 0) {
727
- node.textContent = "Transports: none open.";
728
- delete node.dataset.state;
729
- return;
730
- }
731
- node.textContent = `Transports: ${open} open${view.ratelimits.length > 0 ? ` · ${view.ratelimits.length} rate directive${view.ratelimits.length === 1 ? "" : "s"}` : ""}${hits > 0 ? ` · ${hits} cache hit${hits === 1 ? "" : "s"}` : ""}.`;
732
- if (open > 0) node.dataset.state = "active";
733
- else delete node.dataset.state;
734
- } catch {
735
- node.textContent = "Transports: none open.";
736
- delete node.dataset.state;
737
- }
738
- }
739
-
740
- /** Renders the popup vision model status of the 1.1.77 family: the configured model with the ocr, description and cache hit counts of the run, or the unconfigured refusal because no recognition ships inside the extension. */
741
- async function rendervisionstate(): Promise<void> {
742
- const node = document.querySelector<HTMLElement>("#visionstate");
743
- if (!node) return;
744
- try {
745
- const view = (await request({ kind: "vision", view: true })) as {
746
- ocrs: Array<{ words: number }>;
747
- visions: Array<{ regions: number }>;
748
- cachehits: number;
749
- cost: { modelcalls: number; calls: number };
750
- settings: { visionmodel?: string; visionendpoint?: string };
751
- };
752
- const words = view.ocrs.reduce((total, record) => total + record.words, 0);
753
- if (view.ocrs.length === 0 && view.visions.length === 0) {
754
- node.textContent =
755
- view.settings.visionmodel !== undefined
756
- ? `Vision: model ${view.settings.visionmodel} configured, none read yet.`
757
- : "Vision: no model configured.";
758
- delete node.dataset.state;
759
- return;
760
- }
761
- node.textContent = `Vision: ${view.settings.visionmodel !== undefined ? view.settings.visionmodel : "unconfigured model"} · ${view.ocrs.length} ocr result${view.ocrs.length === 1 ? "" : "s"} of ${words} word${words === 1 ? "" : "s"} · ${view.visions.length} description${view.visions.length === 1 ? "" : "s"} · ${view.cachehits} cache hit${view.cachehits === 1 ? "" : "s"} · ${view.cost.modelcalls} model call${view.cost.modelcalls === 1 ? "" : "s"}.`;
762
- if (view.cost.modelcalls > 0) node.dataset.state = "active";
763
- else delete node.dataset.state;
764
- } catch {
765
- node.textContent = "Vision: no model configured.";
766
- delete node.dataset.state;
767
- }
768
- }
769
-
770
- /** Renders the popup forensic evidence status of the 1.1.78 family: the before after pair, console, net and regression counts of the run with the latest diff score, so the popup names the evidence the run gathered at a glance. */
771
- async function renderforensicsstate(): Promise<void> {
772
- const node = document.querySelector<HTMLElement>("#forensicsstate");
773
- if (!node) return;
774
- try {
775
- const view = (await request({ kind: "forensics", view: true })) as {
776
- pairs: Array<{ stepkind: string }>;
777
- consoleentries: Array<{ level: string }>;
778
- netentries: Array<{ status: number }>;
779
- diffs: Array<{ regression: boolean; score: number }>;
780
- lastdiffscore?: number;
781
- };
782
- const errors = view.consoleentries.filter((entry) => entry.level === "error").length;
783
- const regressions = view.diffs.filter((diff) => diff.regression).length;
784
- if (
785
- view.pairs.length === 0 &&
786
- view.consoleentries.length === 0 &&
787
- view.netentries.length === 0 &&
788
- view.diffs.length === 0
789
- ) {
790
- node.textContent = "Forensics: none.";
791
- delete node.dataset.state;
792
- return;
793
- }
794
- node.textContent = `Forensics: ${view.pairs.length} pair${view.pairs.length === 1 ? "" : "s"} · ${view.consoleentries.length} console entr${view.consoleentries.length === 1 ? "y" : "ies"} (${errors} error${errors === 1 ? "" : "s"}) · ${view.netentries.length} net entr${view.netentries.length === 1 ? "y" : "ies"} · ${regressions} regression${regressions === 1 ? "" : "s"}${view.lastdiffscore !== undefined ? ` · last diff ${view.lastdiffscore}` : ""}.`;
795
- if (errors > 0 || regressions > 0) node.dataset.state = "active";
796
- else delete node.dataset.state;
797
- } catch {
798
- node.textContent = "Forensics: none.";
799
- delete node.dataset.state;
800
- }
801
- }
802
-
803
- /** Renders the popup telemetry status of the 1.1.79 minimization family: the policy stays fixed to off by default and by construction — the enabled literal of the telemetrypolicy type makes an on state unrepresentable — so the popup names the posture and every counter keeps living inside the local memory. */
804
- async function renderbridgestate(): Promise<void> {
805
- const node = document.querySelector<HTMLElement>("#bridgestate");
806
- if (!node) return;
807
- try {
808
- const view = (await request({ kind: "bridge", view: true })) as {
809
- status: { status: string; paired: boolean; queued: number };
810
- };
811
- node.textContent = bridgestatuslabel(view.status);
812
- if (view.status.status === "connected") node.dataset.state = "active";
813
- else delete node.dataset.state;
814
- } catch {
815
- node.textContent = "Bridge: disabled (no relay url set).";
816
- delete node.dataset.state;
817
- }
818
- }
819
-
820
- async function rendertelemetrystate(): Promise<void> {
821
- const node = document.querySelector<HTMLElement>("#telemetrystate");
822
- if (!node) return;
823
- try {
824
- const view = (await request({ kind: "minimization", view: true })) as {
825
- telemetry: { enabled: boolean; counters: string };
826
- inventory: Array<{ key: string; dataclass: string; size: number; records: number }>;
827
- };
828
- const localsize = view.inventory.reduce((total, entry) => total + entry.size, 0);
829
- node.textContent = `Telemetry: ${view.telemetry.enabled === false ? "off" : "off"} (fixed) · ${view.telemetry.counters} counters · ${view.inventory.length} stored famil${view.inventory.length === 1 ? "y" : "ies"} of ${localsize} byte${localsize === 1 ? "" : "s"} stay on the device.`;
830
- delete node.dataset.state;
831
- } catch {
832
- node.textContent = "Telemetry: off by default and by construction.";
833
- delete node.dataset.state;
834
- }
835
- }
836
-
837
- async function renderperfstate(): Promise<void> {
838
- if (!perfstatenode) return;
839
- try {
840
- const view = (await request({ kind: "perf", view: true })) as {
841
- queue: { depth: number; deferred: number; peak: number; configured?: number };
842
- startup: { prewarmed: string[]; lazy: string[]; budget?: number; over: boolean };
843
- };
844
- perfstatenode.textContent = `Performance: worker queue ${view.queue.depth} pending${view.queue.deferred > 0 ? ` · ${view.queue.deferred} deferred` : ""}${view.queue.peak > 0 ? ` · peak ${view.queue.peak}` : ""}${view.queue.configured !== undefined ? ` · user depth ${view.queue.configured}` : ""} · startup ${view.startup.prewarmed.length} prewarmed of ${view.startup.prewarmed.length + view.startup.lazy.length} module${view.startup.prewarmed.length + view.startup.lazy.length === 1 ? "" : "s"}${view.startup.budget !== undefined ? ` · budget ${view.startup.budget}${view.startup.over ? " (over, reported never refused)" : ""}` : ""}.`;
845
- if (view.queue.depth > 0) perfstatenode.dataset.state = "active";
846
- else delete perfstatenode.dataset.state;
847
- } catch {
848
- perfstatenode.textContent = "Performance: no run measured.";
849
- delete perfstatenode.dataset.state;
850
- }
851
- }
852
-
853
- async function restore(): Promise<void> {
854
- const context = (await request({ kind: "context" })) as {
855
- config?: { endpoint: string };
856
- session?: { stoppedat?: number; pausedat?: number; expiresat: number; origin?: string };
857
- capabilities?: capabilityreport;
858
- holds?: { heldkeys: keyholdstate[] };
859
- dialogs?: dialogdecision[];
860
- signals?: pagesignallike;
861
- navstate?: navstate;
862
- offline?: boolean;
863
- waitprofile?: waitprofilerecord;
864
- navqueues?: navqueues;
865
- windows?: windowshape[];
866
- badges?: tabbadge[];
867
- captchas?: captchahandoff[];
868
- datasets?: dataset[];
869
- streams?: streamstate[];
870
- downloads?: downloadrecord[];
871
- clipconsents?: clipboardconsentrecord[];
872
- quarantines?: quarantineentry[];
873
- captures?: Array<{ id: string }>;
874
- capturepolicy?: string;
875
- media?: Array<{ id: string }>;
876
- recordingactive?: Array<{ id: string; kind: string }>;
877
- recordingconsents?: Array<{ approved?: boolean }>;
878
- calls?: Array<{ id: string }>;
879
- fetchconsents?: Array<{ approved?: boolean }>;
880
- exchanges?: Array<{ id: string }>;
881
- channels?: Array<{ id: string; state: string }>;
882
- activerules?: number;
883
- timeline?: { errors?: Array<{ id: string }>; rejections?: Array<{ id: string }> };
884
- consoleconsents?: consoleconsentrecord[];
885
- debuggergrants?: Array<{ approved?: boolean; revokedat?: number }>;
886
- cdpattached?: number;
887
- profileactive?: number;
888
- profile?: { consents?: Array<{ approved?: boolean; revokedat?: number }> };
889
- emulatedlayers?: string[];
890
- emulation?: { consents: Array<{ approved?: boolean }> };
891
- traffic?: {
892
- blocks: Array<{ hits: number; revertedat?: number }>;
893
- mocks: Array<{ hits: number; revertedat?: number }>;
894
- };
895
- sessionmemory?: {
896
- records: Array<{ id: string; name: string }>;
897
- auto?: { period: number; maxsnapshots: number; expiry: number };
898
- };
899
- autosnapshotstate?: { interval: { period: number; maxsnapshots: number; expiry: number }; count: number };
900
- workflow?: {
901
- runs: Array<{ id: string; state: string; cursor: number; workflowid: string; dryrun?: boolean }>;
902
- control?: Array<{ runid: string; kind: string; loops?: Array<{ path: string }> }>;
903
- };
904
- trigger?: {
905
- rules: Array<{ id: string; kind: string; enabled: boolean; paused?: boolean; nextfireat?: number }>;
906
- queued: number;
907
- };
908
- mcp?: {
909
- state: string;
910
- bind?: string;
911
- port: number;
912
- localhost: boolean;
913
- clients: Array<{ id: string; paired: boolean }>;
914
- approvals: Array<{ id: string; tool: string; state: string }>;
915
- inflight?: Array<{ callid: string; tool: string; chunks: number }>;
916
- progressnotices?: Array<{ callid: string; percent?: number; message: string; cancellable?: boolean; at: number }>;
917
- };
918
- llm?: {
919
- providers: Array<{ id: string; name: string; status: string }>;
920
- tasks: Array<{ kind: string; provider?: string; model?: string }>;
921
- usage: { totaltokens: number; cost: number; calls: number };
922
- };
923
- swarm?: {
924
- overview: { agents: number; active: number; paused: number; stopped: number; claimed: number; queued: number };
925
- killswitch: { engaged: boolean };
926
- topology?: { leaderid: string };
927
- locks?: Array<{ key: string }>;
928
- };
929
- environments?: {
930
- report: {
931
- environments: Array<{ stepid: string; environment: string }>;
932
- workers: number;
933
- keepalive?: { runid: string; state: string; beats: number; lastbeatat: number; portopen: boolean };
934
- };
935
- offscreengranted: boolean;
936
- parseoffload: boolean;
937
- zombies: string[];
938
- grants?: string[];
939
- };
940
- security?: {
941
- posture: string;
942
- allowlist: Array<{ origin: string; profileid: string }>;
943
- profiles: Array<{ origin: string; grants: string[]; denials: string[] }>;
944
- windows: Array<{ origin: string; state: string; expiresat: number }>;
945
- sessionorigin?: string;
946
- chain: Array<{ runid: string; valid: boolean; entries: number; sealhash?: string }>;
947
- };
948
- sessionview?: {
949
- grid: Array<{
950
- sessionid: string;
951
- runid: string;
952
- origins: string[];
953
- state: string;
954
- outcome: string;
955
- steps: number;
956
- completed: number;
957
- lock: string;
958
- tabid?: number;
959
- sealhash?: string;
960
- updatedat: number;
961
- actions: string[];
962
- }>;
963
- };
964
- v1sunset?: { at: number; declared: number; clientid: string; dismissedat?: number };
965
- migrationprompt?: {
966
- promptedat: number;
967
- previousversion: string;
968
- command: string;
969
- migrationpromptdismissed?: boolean;
970
- dismissedat?: number;
971
- };
972
- };
973
- if (endpointinput && context.config) endpointinput.value = context.config.endpoint;
974
- rendercapabilities(context.capabilities);
975
- renderlivestate(context.holds?.heldkeys, context.dialogs, context.signals);
976
- rendernavstate(context.navstate, context.offline, context.waitprofile, context.navqueues);
977
- renderwindowlayout(context.windows, context.badges);
978
- rendercaptchastate(context.captchas);
979
- renderdatasetstate(context.datasets, context.streams);
980
- renderdownloadstate(context.downloads, context.quarantines);
981
- renderclipboardstate(context.clipconsents);
982
- rendercapturestate(context.captures, context.capturepolicy);
983
- rendermediastate(context.media, context.recordingactive, context.recordingconsents);
984
- rendercallstate(context.calls, context.fetchconsents);
985
- rendernetstate(context.exchanges, context.channels);
986
- renderdebugstate(
987
- context.timeline,
988
- context.consoleconsents,
989
- context.debuggergrants,
990
- context.cdpattached,
991
- context.profileactive,
992
- context.profile?.consents,
993
- );
994
- renderemulationstate(context.emulatedlayers, context.emulation?.consents);
995
- rendersessionstate(context.sessionmemory, context.autosnapshotstate);
996
- renderworkflowstate(context.workflow?.runs, context.workflow?.control);
997
- rendertriggerstate(
998
- context.trigger?.rules,
999
- context.trigger?.queued,
1000
- context.session?.pausedat !== undefined,
1001
- triggerbuttonfired,
1002
- );
1003
- rendermcpstate(context.mcp);
1004
- rendermodelstate(context.llm);
1005
- renderswarmstate(context.swarm);
1006
- renderenvironmentstate(context.environments);
1007
- await renderperfstate();
1008
- await renderheartbeatstate();
1009
- await renderencryptstate();
1010
- await renderfleetstate();
1011
- await renderworkstate();
1012
- await renderpipelinestate();
1013
- await rendertransportstate();
1014
- await rendervisionstate();
1015
- await renderforensicsstate();
1016
- await rendertelemetrystate();
1017
- await renderbridgestate();
1018
- rendersecuritystate(context.security, context.session?.origin);
1019
- renderv1sunsetbanner(context.v1sunset);
1020
- rendermigrationpromptbanner(context.migrationprompt);
1021
- rendersessiongridstate(context.sessionview?.grid);
1022
- renderrulestate(
1023
- context.activerules,
1024
- (context.traffic?.blocks ?? [])
1025
- .filter((rule) => rule.revertedat === undefined)
1026
- .reduce((total, rule) => total + rule.hits, 0),
1027
- (context.traffic?.mocks ?? [])
1028
- .filter((spec) => spec.revertedat === undefined)
1029
- .reduce((total, spec) => total + spec.hits, 0),
1030
- );
1031
- const active = context.session && !context.session.stoppedat && context.session.expiresat > Date.now();
1032
- if (pausebutton) {
1033
- pausebutton.disabled = !active;
1034
- pausebutton.textContent = pauselabel(Boolean(context.session?.pausedat));
1035
- }
1036
- if (active && context.session?.pausedat) status("Session paused. Reviewed actions are blocked until resume.");
1037
- else status(active ? "Session active. Review the plan in the side panel." : "No active browser session.");
1038
- }
1039
-
1040
- /** The popup open is the toolbar button click of a default popup: an armed button rule evaluates behind the gates once per popup open. */
1041
- let triggerbuttonfired = false;
1042
- void (async () => {
1043
- try {
1044
- const result = (await request({ kind: "buttontrigger" })) as { fired: boolean; reason?: string };
1045
- if (result.fired) {
1046
- triggerbuttonfired = true;
1047
- status("The toolbar button rule fired behind the consent gates.");
1048
- }
1049
- } catch {
1050
- /* no armed button rule or the gates refused the fire; the panel shows the reason */
1051
- }
1052
- })();
1053
-
1054
- connectbutton?.addEventListener("click", async () => {
1055
- try {
1056
- const config = normalizeendpoint(endpointinput?.value ?? "");
1057
- const granted = await chrome.permissions.request({ origins: [hostpattern(config.origin)] });
1058
- if (!granted) throw new Error("Origin permission was not granted.");
1059
- await request({ kind: "configure", endpoint: config.endpoint });
1060
- status(`Endpoint approved for ${config.origin}.`);
1061
- } catch (error) {
1062
- status(error instanceof Error ? error.message : String(error), true);
1063
- }
1064
- });
1065
- sessionbutton?.addEventListener("click", async () => {
1066
- try {
1067
- await request({ kind: "startsession" });
1068
- status("Session started for the active HTTPS tab.");
1069
- await restore();
1070
- } catch (error) {
1071
- status(error instanceof Error ? error.message : String(error), true);
1072
- }
1073
- });
1074
- pausebutton?.addEventListener("click", async () => {
1075
- try {
1076
- const context = (await request({ kind: "context" })) as { session?: { pausedat?: number } };
1077
- await request({ kind: context.session?.pausedat ? "resumesession" : "pausesession" });
1078
- await restore();
1079
- } catch (error) {
1080
- status(error instanceof Error ? error.message : String(error), true);
1081
- }
1082
- });
1083
- stopbutton?.addEventListener("click", async () => {
1084
- try {
1085
- await request({ kind: "stop" });
1086
- status("Session stopped. No action can continue.");
1087
- await restore();
1088
- } catch (error) {
1089
- status(error instanceof Error ? error.message : String(error), true);
1090
- }
1091
- });
1092
- killswitchbutton?.addEventListener("click", async () => {
1093
- try {
1094
- await request({ kind: "fleet", killswitch: { reason: "The popup killswitch button." } });
1095
- status(
1096
- "The killswitch engaged: every fleet record stopped, every agent run cancelled and every queue cleared; the switch stays user triggered only.",
1097
- );
1098
- await restore();
1099
- } catch (error) {
1100
- status(error instanceof Error ? error.message : String(error), true);
1101
- }
1102
- });
1103
- openbutton?.addEventListener("click", () => chrome.sidePanel.open({ windowId: chrome.windows.WINDOW_ID_CURRENT }));
1104
- restore().catch((error) => status(error instanceof Error ? error.message : String(error), true));
1105
-
1106
- /** Renders the compact sessiongrid of recent sessions: every live and saved run in one row with its state, outcome and lock, and a deep link that opens the sidepanel run view. */
1107
- function rendersessiongridstate(
1108
- grid?: Array<{
1109
- sessionid: string;
1110
- runid: string;
1111
- origins: string[];
1112
- state: string;
1113
- outcome: string;
1114
- steps: number;
1115
- completed: number;
1116
- lock: string;
1117
- tabid?: number;
1118
- sealhash?: string;
1119
- updatedat: number;
1120
- actions: string[];
1121
- }>,
1122
- ): void {
1123
- if (!sessiongridstatenode) return;
1124
- if (!grid || grid.length === 0) {
1125
- sessiongridstatenode.textContent = "Session grid: no run yet.";
1126
- if (sessiongridcontrols) sessiongridcontrols.replaceChildren();
1127
- return;
1128
- }
1129
- const recent = grid.slice(0, 3);
1130
- sessiongridstatenode.textContent = `Session grid: ${grid.length} run${grid.length === 1 ? "" : "s"} — ${recent.map((row) => `${row.state} ${row.runid.slice(0, 8)} ${row.completed}/${row.steps} ${row.lock}`).join(" · ")}.`;
1131
- if (!sessiongridcontrols) return;
1132
- sessiongridcontrols.replaceChildren();
1133
- for (const row of recent) {
1134
- const deep = document.createElement("button");
1135
- deep.type = "button";
1136
- deep.className = "secondary";
1137
- deep.textContent = `Open ${row.state} run ${row.runid.slice(0, 8)}`;
1138
- deep.addEventListener("click", () =>
1139
- request({ kind: "sessions", grid: { open: { runid: row.runid } } })
1140
- .then(() => {
1141
- status(`The session grid row of the run ${row.runid} opened in the sidepanel run view.`);
1142
- return restore();
1143
- })
1144
- .catch((error) => status(error instanceof Error ? error.message : String(error), true)),
1145
- );
1146
- sessiongridcontrols.append(deep);
1147
- }
1148
- }
1149
-
1150
- /** Command surface of the 1.1.64 family: the popup carries the task input, the quick run actions, the command palette with its keyboard shortcut, the onboarding walkthrough and the live broadcast refresh. */
1151
- const taskinputnode = document.querySelector<HTMLTextAreaElement>("#taskinput");
1152
- const tasksubmitbutton = document.querySelector<HTMLButtonElement>("#tasksubmit");
1153
- const proposalstatusnode = document.querySelector<HTMLElement>("#proposalstatus");
1154
- const runquickbutton = document.querySelector<HTMLButtonElement>("#runquick");
1155
- const cancelquickbutton = document.querySelector<HTMLButtonElement>("#cancelquick");
1156
- const opendashboardbutton = document.querySelector<HTMLButtonElement>("#opendashboard");
1157
- const openoptionsbutton = document.querySelector<HTMLButtonElement>("#openoptions");
1158
- const paletteinput = document.querySelector<HTMLInputElement>("#paletteinput");
1159
- const paletteopenbutton = document.querySelector<HTMLButtonElement>("#paletteopen");
1160
- const palettematchesnode = document.querySelector<HTMLElement>("#palettematches");
1161
- const onboardingcardnode = document.querySelector<HTMLElement>("#onboardingcard");
1162
-
1163
- /** The single broadcast channel every surface subscribes to: the popup refreshes its live status from each frame. */
1164
- const surfacechannel: BroadcastChannel | undefined =
1165
- typeof BroadcastChannel === "function" ? new BroadcastChannel("devthinksurfaces") : undefined;
1166
- surfacechannel?.addEventListener("message", () => {
1167
- void restore().catch(() => {
1168
- /* a failing refresh keeps the last rendered state */
1169
- });
1170
- void renderonboarding().catch(() => {
1171
- /* a failing walkthrough render keeps the card as it was */
1172
- });
1173
- });
1174
-
1175
- /** Shows the proposal status of the taskinput while the plan generates. */
1176
- function proposalstatus(status: "idle" | "generating" | "ready" | "failed", detail = ""): void {
1177
- if (!proposalstatusnode) return;
1178
- proposalstatusnode.textContent = detail === "" ? status : `${status} — ${detail}`;
1179
- proposalstatusnode.dataset.state = status;
1180
- }
1181
-
1182
- /** Submits the taskinput goal through the same proposal flow as the api with the active origin and page outline attached. */
1183
- tasksubmitbutton?.addEventListener("click", async () => {
1184
- const text = taskinputnode?.value ?? "";
1185
- if (text.trim() === "") {
1186
- status("Describe the goal first; the proposal flow needs its natural language task.", true);
1187
- return;
1188
- }
1189
- proposalstatus("generating");
1190
- try {
1191
- const result = (await request({ kind: "surface", task: { submit: { text, surface: "popup" } } })) as {
1192
- plan?: { id: string; state: string };
1193
- status?: string;
1194
- };
1195
- proposalstatus("ready", `plan ${result.plan?.id ?? ""} awaits its plancard review`);
1196
- status(`The taskinput goal became the plan ${result.plan?.id ?? ""}; review its plancards in the side panel.`);
1197
- await restore();
1198
- } catch (error) {
1199
- proposalstatus("failed", error instanceof Error ? error.message : String(error));
1200
- status(error instanceof Error ? error.message : String(error), true);
1201
- }
1202
- });
1203
-
1204
- /** Runs the quick actions of the command surface through the command bus. */
1205
- async function busaction(command: string, payload = "", stepid?: string): Promise<void> {
1206
- const action = {
1207
- surface: "popup",
1208
- command,
1209
- ...(payload !== "" ? { payload } : {}),
1210
- ...(stepid !== undefined ? { stepid } : {}),
1211
- };
1212
- const route = (await request({ kind: "surface", bus: { action } })) as {
1213
- route: { dispatched: boolean; reason: string };
1214
- };
1215
- status(route.route.reason);
1216
- await restore();
1217
- }
1218
- runquickbutton?.addEventListener("click", () => {
1219
- void busaction("starttask", taskinputnode?.value ?? "").catch((error) =>
1220
- status(error instanceof Error ? error.message : String(error), true),
1221
- );
1222
- });
1223
- cancelquickbutton?.addEventListener("click", () => {
1224
- void busaction("cancelrun").catch((error) => status(error instanceof Error ? error.message : String(error), true));
1225
- });
1226
- opendashboardbutton?.addEventListener("click", () => {
1227
- void chrome.tabs.create({ url: chrome.runtime.getURL("dashboardpage.html") });
1228
- });
1229
- openoptionsbutton?.addEventListener("click", () => {
1230
- void chrome.tabs.create({ url: chrome.runtime.getURL("optionspage.html") });
1231
- });
1232
-
1233
- /** Renders the fuzzy palette matches with the recent commands ranked first. */
1234
- async function renderpalette(): Promise<void> {
1235
- if (!palettematchesnode) return;
1236
- const text = paletteinput?.value ?? "";
1237
- try {
1238
- const result = (await request({ kind: "surface", palette: { query: { text } } })) as {
1239
- matches: Array<{
1240
- entry: { id: string; label: string; action: { command: string; surface: string } };
1241
- reason: string;
1242
- }>;
1243
- };
1244
- palettematchesnode.replaceChildren();
1245
- for (const match of result.matches.slice(0, 6)) {
1246
- const item = document.createElement("button");
1247
- item.type = "button";
1248
- item.className = "secondary";
1249
- item.textContent = `${match.entry.label} (${match.entry.action.surface})`;
1250
- item.title = match.reason;
1251
- item.addEventListener("click", () => {
1252
- void runpalettecommand(match.entry.action.command).catch((error) =>
1253
- status(error instanceof Error ? error.message : String(error), true),
1254
- );
1255
- });
1256
- palettematchesnode.append(item, " ");
1257
- }
1258
- if (result.matches.length === 0)
1259
- palettematchesnode.textContent =
1260
- "No command matches; the palette lists only the actions the current capability set allows.";
1261
- } catch (error) {
1262
- palettematchesnode.textContent = error instanceof Error ? error.message : String(error);
1263
- }
1264
- }
1265
-
1266
- /** Runs one commandpalette command: the navigation commands open their surface while every other command rides the command bus and its use records for the ranking. */
1267
- async function runpalettecommand(command: string): Promise<void> {
1268
- await request({ kind: "surface", palette: { used: { command } } });
1269
- if (command === "opendashboardpage") {
1270
- await chrome.tabs.create({ url: chrome.runtime.getURL("dashboardpage.html") });
1271
- status("The dashboardpage opened in a new tab.");
1272
- return;
1273
- }
1274
- if (command === "openoptionspage") {
1275
- await chrome.tabs.create({ url: chrome.runtime.getURL("optionspage.html") });
1276
- status("The optionspage opened in a new tab.");
1277
- return;
1278
- }
1279
- if (command === "opentransparencypage") {
1280
- await chrome.tabs.create({ url: chrome.runtime.getURL("transparencypage.html") });
1281
- status("The transparencypage opened in a new tab.");
1282
- return;
1283
- }
1284
- if (command === "replayonboarding") {
1285
- await request({ kind: "surface", onboarding: { replay: true } });
1286
- await renderonboarding();
1287
- status("The onboarding walkthrough replays.");
1288
- return;
1289
- }
1290
- if (command === "historysearch") {
1291
- await chrome.tabs.create({ url: chrome.runtime.getURL("dashboardpage.html") });
1292
- status("The dashboardpage opened with the historysearch view.");
1293
- return;
1294
- }
1295
- if (command === "copyauditexcerpt") {
1296
- const result = (await request({ kind: "surface", logstream: { excerpt: {} } })) as { excerpt: string };
1297
- await navigator.clipboard?.writeText(result.excerpt).catch(() => {
1298
- /* the clipboard write needs its reviewed clipboard consent */
1299
- });
1300
- status("The verified range copied as an audit excerpt.");
1301
- return;
1302
- }
1303
- await busaction(command);
1304
- await renderpalette();
1305
- }
1306
- paletteinput?.addEventListener("input", () => {
1307
- void renderpalette();
1308
- });
1309
- paletteopenbutton?.addEventListener("click", () => {
1310
- paletteinput?.focus();
1311
- void renderpalette();
1312
- });
1313
- document.addEventListener("keydown", (event) => {
1314
- const shortcut = event.ctrlKey && event.key === ".";
1315
- if (!shortcut) return;
1316
- event.preventDefault();
1317
- paletteinput?.focus();
1318
- void renderpalette();
1319
- });
1320
- void renderpalette();
1321
-
1322
- /** Renders the onboarding walkthrough card while the walkthrough runs: the current step with its body and its completion button. */
1323
- async function renderonboarding(): Promise<void> {
1324
- if (!onboardingcardnode) return;
1325
- try {
1326
- const result = (await request({ kind: "surface", onboarding: {} })) as {
1327
- steps?: Array<{ id: string; surface: string; title: string; body: string; completion: string }>;
1328
- state?: { stepscompleted: string[]; done: boolean };
1329
- };
1330
- const steps = result.steps ?? [];
1331
- const state = result.state;
1332
- if (!state || state.done || steps.length === 0) {
1333
- onboardingcardnode.hidden = true;
1334
- onboardingcardnode.replaceChildren();
1335
- return;
1336
- }
1337
- const current = steps.find((step) => !state.stepscompleted.includes(step.id)) ?? steps[0];
1338
- if (current === undefined) {
1339
- onboardingcardnode.hidden = true;
1340
- return;
1341
- }
1342
- onboardingcardnode.hidden = false;
1343
- onboardingcardnode.replaceChildren();
1344
- const title = document.createElement("strong");
1345
- title.textContent = `Onboarding — ${current.title} (${state.stepscompleted.length}/${steps.length})`;
1346
- const body = document.createElement("p");
1347
- body.textContent = current.body;
1348
- const done = document.createElement("button");
1349
- done.type = "button";
1350
- done.textContent = `Mark ${current.title} done`;
1351
- done.addEventListener("click", () => {
1352
- void request({ kind: "surface", onboarding: { complete: { stepid: current.id } } })
1353
- .then(() => renderonboarding())
1354
- .then(() => restore())
1355
- .catch((error) => status(error instanceof Error ? error.message : String(error), true));
1356
- });
1357
- onboardingcardnode.append(title, body, done);
1358
- } catch {
1359
- onboardingcardnode.hidden = true;
1360
- }
1361
- }
1362
- void renderonboarding();
1363
-
1364
- /** The version one sunset notice record the context carries: the time the first below floor declaration arrived, the major the client declared, the client that declared it and the session dismissal time. */
1365
- type v1sunsetnotice = { at: number; declared: number; clientid: string; dismissedat?: number };
1366
-
1367
- /** The version one migration prompt record the context carries: the time the prompt fired, the 1.x release the update upgraded from, the migrateplan command it surfaces and the persistent dismissal flag. */
1368
- type migrationpromptnotice = {
1369
- promptedat: number;
1370
- previousversion: string;
1371
- command: string;
1372
- migrationpromptdismissed?: boolean;
1373
- dismissedat?: number;
1374
- };
1375
-
1376
- /** Builds the version one sunset banner card content: the plain language sunset notice the negotiation refusal texts carry, the migration guide path the banner references, the migration command the card names and the visibility the session state decides. */
1377
- export function v1sunsetbannercard(state: v1sunsetnotice | undefined): {
1378
- title: string;
1379
- body: string;
1380
- guide: string;
1381
- command: string;
1382
- dismiss: string;
1383
- visible: boolean;
1384
- } {
1385
- return {
1386
- title: "Protocol v1 retired",
1387
- body: "Protocol v1 retired — this client cannot connect. The migration guide and the migrateplan command upgrade v1 plans.",
1388
- guide: "docs/migrationguide.md",
1389
- command: "devthink migrateplan",
1390
- dismiss: "Dismiss for this session",
1391
- visible: state !== undefined && state.dismissedat === undefined,
1392
- };
1393
- }
1394
-
1395
- /** Builds the version one migration prompt card content: the one time upgrade notice for an affected upgrader, the guide path, the migrateplan command the Migrate action surfaces and the visibility the persistent dismissal flag decides. */
1396
- export function migrationpromptcard(state: migrationpromptnotice | undefined): {
1397
- title: string;
1398
- body: string;
1399
- guide: string;
1400
- command: string;
1401
- migrate: string;
1402
- dismiss: string;
1403
- visible: boolean;
1404
- } {
1405
- return {
1406
- title: "Version one migration",
1407
- body: `The update from ${state?.previousversion ?? "a version one release"} closed the protocol v1 window; convert v1 plans before the next connection.`,
1408
- guide: "docs/migrationguide.md",
1409
- command: state?.command ?? "devthink migrateplan",
1410
- migrate: "Migrate",
1411
- dismiss: "Dismiss permanently",
1412
- visible: state !== undefined && state.migrationpromptdismissed !== true && state.dismissedat === undefined,
1413
- };
1414
- }
1415
-
1416
- /** The lazily created banner cards the popup appends to its body: the design file carries no dedicated sunset node, so the popup builds the two cards it needs — the version one sunset banner and the version one migration prompt — and removes them when their state hides. */
1417
- const sunsetbannersection = typeof document === "undefined" ? undefined : document.createElement("section");
1418
- const migrationbannersection = typeof document === "undefined" ? undefined : document.createElement("section");
1419
-
1420
- /** Renders the version one sunset banner card: the notice names the retired protocol, the guide path and the migration command, and the dismissal resets it for the session through the surface command family. */
1421
- export function renderv1sunsetbanner(state: v1sunsetnotice | undefined): void {
1422
- if (sunsetbannersection === undefined || typeof document.body?.append !== "function") return;
1423
- const card = v1sunsetbannercard(state);
1424
- if (!card.visible) {
1425
- sunsetbannersection.remove();
1426
- return;
1427
- }
1428
- sunsetbannersection.className = "panel";
1429
- sunsetbannersection.setAttribute("role", "status");
1430
- sunsetbannersection.replaceChildren();
1431
- const title = document.createElement("strong");
1432
- title.textContent = card.title;
1433
- const body = document.createElement("p");
1434
- body.textContent = `${card.body} Guide: ${card.guide}. Command: ${card.command}.`;
1435
- const dismiss = document.createElement("button");
1436
- dismiss.type = "button";
1437
- dismiss.className = "secondary";
1438
- dismiss.textContent = card.dismiss;
1439
- dismiss.addEventListener("click", () => {
1440
- void request({ kind: "surface", v1sunset: { dismiss: true } })
1441
- .then(() => renderv1sunsetbanner(undefined))
1442
- .catch((error) => status(error instanceof Error ? error.message : String(error), true));
1443
- });
1444
- sunsetbannersection.append(title, body, dismiss);
1445
- document.body.append(sunsetbannersection);
1446
- }
1447
-
1448
- /** Renders the version one migration prompt card: the Migrate action surfaces the migrateplan command the prompt record carries, and the dismissal writes the persistent flag through the surface command family so the prompt never appears again. */
1449
- export function rendermigrationpromptbanner(state: migrationpromptnotice | undefined): void {
1450
- if (migrationbannersection === undefined || typeof document.body?.append !== "function") return;
1451
- const card = migrationpromptcard(state);
1452
- if (!card.visible) {
1453
- migrationbannersection.remove();
1454
- return;
1455
- }
1456
- migrationbannersection.className = "panel";
1457
- migrationbannersection.setAttribute("role", "status");
1458
- migrationbannersection.replaceChildren();
1459
- const title = document.createElement("strong");
1460
- title.textContent = card.title;
1461
- const body = document.createElement("p");
1462
- body.textContent = `${card.body} Guide: ${card.guide}.`;
1463
- const migrate = document.createElement("button");
1464
- migrate.type = "button";
1465
- migrate.className = "secondary";
1466
- migrate.textContent = card.migrate;
1467
- migrate.addEventListener("click", () => {
1468
- void request({ kind: "surface", migrationprompt: { migrate: true } })
1469
- .then(() => status(`The migration command: ${card.command}. The guide at ${card.guide} records the full path.`))
1470
- .catch((error) => status(error instanceof Error ? error.message : String(error), true));
1471
- });
1472
- const dismiss = document.createElement("button");
1473
- dismiss.type = "button";
1474
- dismiss.className = "secondary";
1475
- dismiss.textContent = card.dismiss;
1476
- dismiss.addEventListener("click", () => {
1477
- void request({ kind: "surface", migrationprompt: { dismiss: true } })
1478
- .then(() => rendermigrationpromptbanner(undefined))
1479
- .catch((error) => status(error instanceof Error ? error.message : String(error), true));
1480
- });
1481
- migrationbannersection.append(title, body, migrate, " ", dismiss);
1482
- document.body.append(migrationbannersection);
1483
- }
1484
-
1485
- /** Recenttray of the 1.1.65 family: the popup lists the latest runs with resume for a halted run and reopen for a completed one through the same sessions seam. */
1486
- const recenttraynode = document.querySelector<HTMLElement>("#recenttray");
1487
- async function renderrecenttray(): Promise<void> {
1488
- if (!recenttraynode) return;
1489
- try {
1490
- const result = (await request({ kind: "views", recent: { list: true } })) as {
1491
- tray?: Array<{
1492
- runid: string;
1493
- origin: string;
1494
- outcome: string;
1495
- title: string;
1496
- at: number;
1497
- resumable: boolean;
1498
- reopenable: boolean;
1499
- }>;
1500
- };
1501
- const tray = result.tray ?? [];
1502
- recenttraynode.replaceChildren();
1503
- if (tray.length === 0)
1504
- recenttraynode.textContent = "No run yet; the tray lists the latest runs with resume and reopen.";
1505
- else {
1506
- const list = document.createElement("ul");
1507
- list.className = "audit";
1508
- for (const entry of tray) {
1509
- const item = document.createElement("li");
1510
- item.textContent = `${entry.outcome} — ${entry.title || entry.runid} (${entry.origin})`;
1511
- if (entry.resumable) {
1512
- const resume = document.createElement("button");
1513
- resume.type = "button";
1514
- resume.className = "secondary";
1515
- resume.textContent = "Resume";
1516
- resume.addEventListener("click", () => {
1517
- void (async () => {
1518
- await request({ kind: "sessions", grid: { resume: { runid: entry.runid } } });
1519
- status(`The run ${entry.runid} resumed through the sessions seam.`);
1520
- })().catch((error) => status(error instanceof Error ? error.message : String(error), true));
1521
- });
1522
- item.append(" ", resume);
1523
- }
1524
- if (entry.reopenable) {
1525
- const reopen = document.createElement("button");
1526
- reopen.type = "button";
1527
- reopen.className = "secondary";
1528
- reopen.textContent = "Reopen";
1529
- reopen.addEventListener("click", () => {
1530
- void (async () => {
1531
- await request({ kind: "sessions", grid: { reopen: { runid: entry.runid } } });
1532
- status(`The run ${entry.runid} reopened through the sessions seam.`);
1533
- })().catch((error) => status(error instanceof Error ? error.message : String(error), true));
1534
- });
1535
- item.append(" ", reopen);
1536
- }
1537
- list.append(item);
1538
- }
1539
- recenttraynode.append(list);
1540
- }
1541
- } catch (error) {
1542
- recenttraynode.textContent = error instanceof Error ? error.message : String(error);
1543
- }
1544
- try {
1545
- const background = (await request({ kind: "ecosystem", background: { list: true } })) as {
1546
- tray: Array<{ runid: string; title: string; outcome: string }>;
1547
- };
1548
- if (background.tray.length > 0) {
1549
- const backgroundlist = document.createElement("ul");
1550
- backgroundlist.className = "audit";
1551
- const headline = document.createElement("li");
1552
- headline.textContent = `Background runs: ${background.tray.length} finished entr${background.tray.length === 1 ? "y" : "ies"} from the background queue.`;
1553
- backgroundlist.append(headline);
1554
- for (const entry of background.tray.slice(0, 3))
1555
- backgroundlist.append(
1556
- Object.assign(document.createElement("li"), { textContent: `${entry.outcome} — ${entry.title}` }),
1557
- );
1558
- recenttraynode.append(backgroundlist);
1559
- }
1560
- } catch {
1561
- /* a failing background tray read keeps the foreground tray rendered */
1562
- }
1563
- }
1564
-
1565
- /** Quickactions of the 1.1.65 family: only the actions the origin allowlist of the clicked tab permits surface in the popup. */
1566
- const quickactionsnode = document.querySelector<HTMLElement>("#quickactions");
1567
- async function renderquickactions(): Promise<void> {
1568
- if (!quickactionsnode) return;
1569
- try {
1570
- const result = (await request({ kind: "views", quickaction: { list: {}, surface: true } })) as {
1571
- actions?: Array<{ id: string; label: string; command: string }>;
1572
- };
1573
- const actions = result.actions ?? [];
1574
- quickactionsnode.replaceChildren();
1575
- if (actions.length === 0) {
1576
- quickactionsnode.textContent =
1577
- "No quickaction registers on this tab; grant its origin and the permitted actions surface.";
1578
- return;
1579
- }
1580
- const list = document.createElement("ul");
1581
- list.className = "audit";
1582
- for (const action of actions) {
1583
- const item = document.createElement("li");
1584
- item.textContent = action.label;
1585
- const run = document.createElement("button");
1586
- run.type = "button";
1587
- run.className = "secondary";
1588
- run.textContent = "Run";
1589
- run.addEventListener("click", () => {
1590
- void (async () => {
1591
- if (action.command === "opendashboardpage") {
1592
- await chrome.tabs.create({ url: chrome.runtime.getURL("dashboardpage.html") });
1593
- status("The dashboardpage opened in a new tab.");
1594
- } else {
1595
- await busaction("starttask");
1596
- status(`The ${action.id} quickaction routed through the command bus.`);
1597
- }
1598
- })().catch((error) => status(error instanceof Error ? error.message : String(error), true));
1599
- });
1600
- item.append(" ", run);
1601
- list.append(item);
1602
- }
1603
- quickactionsnode.append(list);
1604
- } catch (error) {
1605
- quickactionsnode.textContent = error instanceof Error ? error.message : String(error);
1606
- }
1607
- }
1608
- void renderrecenttray();
1609
- void renderquickactions();
1610
-
1611
- /** Closedtabs of the 1.1.74 family: the popup lists the recently closed tab records under the user retention window and reopens one on request after the grant recheck. */
1612
- const closedtabsnode = document.querySelector<HTMLElement>("#closedtabs");
1613
- async function renderclosedtabs(): Promise<void> {
1614
- if (!closedtabsnode) return;
1615
- try {
1616
- const view = (await request({ kind: "nav", view: true })) as {
1617
- closedtabs?: Array<{ id: string; url: string; title: string; closedat: number; reopenedat?: number }>;
1618
- };
1619
- const records = view.closedtabs ?? [];
1620
- closedtabsnode.replaceChildren();
1621
- if (records.length === 0) {
1622
- closedtabsnode.textContent = "No closed tab yet; the records stay under the user retention window.";
1623
- return;
1624
- }
1625
- const list = document.createElement("ul");
1626
- list.className = "audit";
1627
- for (const record of records.slice(0, 6)) {
1628
- const item = document.createElement("li");
1629
- item.textContent = `${record.title || record.url}${record.reopenedat !== undefined ? " (already reopened)" : ""}`;
1630
- if (record.reopenedat === undefined) {
1631
- const reopen = document.createElement("button");
1632
- reopen.type = "button";
1633
- reopen.className = "secondary";
1634
- reopen.textContent = "Reopen";
1635
- reopen.addEventListener("click", () => {
1636
- void (async () => {
1637
- await request({ kind: "nav", reopen: { id: record.id } });
1638
- status(`The closed tab ${record.url} reopened after the grant recheck.`);
1639
- await renderclosedtabs();
1640
- })().catch((error) => status(error instanceof Error ? error.message : String(error), true));
1641
- });
1642
- item.append(" ", reopen);
1643
- }
1644
- list.append(item);
1645
- }
1646
- closedtabsnode.append(list);
1647
- } catch (error) {
1648
- closedtabsnode.textContent = error instanceof Error ? error.message : String(error);
1649
- }
1650
- }
1651
- void renderclosedtabs();