@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/plan.ts DELETED
@@ -1,672 +0,0 @@
1
- /**
2
- * The plan module of the 1.1.90 consolidation: every correlated variation of the plan logic interned in this one file, so the module family carries one surface without duplicate variations.
3
- * The correlation is the life of a plan file: planlint reads the plan on disk and lints it against the same policy engine the extension runs (originprofile grading, sensitive class consents, portable capability gates, schemastrict unknown field refusal, explicit gate declarations, loop and retry bounds) so an error diagnostic refuses the file before any run starts; planreview renders the review half of the interface the same plan passes on its way to execution (plancards with their risk groups and correction memory matches, stepapprove resolutions with their immutable log events, diffpreviews of write class steps with mask verdicts, stepstimeline nodes derived from progress records with no new state, and the hash chained logstream the surface verifies live as entries arrive).
4
- * The linter never executes anything and the review surface never bypasses the human: every bound, window and buffer stays the user's choice with no engine cap.
5
- */
6
-
7
- /* ── Merged from planlint.ts ── */
8
-
9
- import type {
10
- actionkind,
11
- classconsent,
12
- originprofile,
13
- planfile,
14
- planfilestep,
15
- planlintdiagnostic,
16
- portableruleset,
17
- sensitiveclass,
18
- toolstep,
19
- } from "./types.js";
20
- import { missingclassconsents, originprofileof, profilegrade, sensitiveclassesof } from "./security.js";
21
- import { portablecapabilitygate, resolvedrisk } from "./policy.js";
22
-
23
- /**
24
- * Planlint logic of the 1.1.67 family.
25
- * The plan file on disk lints against the same policy engine the extension runs: every step grades against the originprofile of the plan origin, every sensitive class needs the consent the extension would demand, every kind stays inside the portable capability set of the target runtime, unknown fields refuse under schemastrict, sensitive steps declare their gate explicitly, selectors that cannot resolve without a live page flag, and unbounded loops and missing retry bounds surface before any run starts.
26
- * The linter never executes anything and never touches a page: it reads the plan file and the compiled portable rule set only, and an error diagnostic refuses the file before flowrun ever sees it.
27
- */
28
-
29
- /** The step fields a plan file carries; any other field refuses under schemastrict with the path that names it. */
30
- export const planfilestepfields: readonly string[] = [
31
- "id",
32
- "kind",
33
- "label",
34
- "target",
35
- "value",
36
- "options",
37
- "gate",
38
- "bound",
39
- "attempts",
40
- ];
41
-
42
- /** The control flow kinds whose loop bound the linter reads; an absent bound flags as unbounded. */
43
- export const loopkinds: readonly string[] = ["loop", "whileloop", "repeatuntil"];
44
-
45
- /** The retrying kinds whose attempts the linter reads; absent attempts flag as missing retry bounds. */
46
- export const retrykinds: readonly string[] = ["retryaction", "trycatch"];
47
-
48
- /** Reads one string field of an unknown record or refuses with the path that names it. */
49
- function textfield(value: Record<string, unknown>, field: string, path: string): string {
50
- const fieldvalue = value[field];
51
- if (typeof fieldvalue !== "string" || fieldvalue.trim() === "")
52
- throw new Error(`${path} needs its ${field} as a non-empty string.`);
53
- return fieldvalue;
54
- }
55
-
56
- /** Parses one plan file under schemastrict: the version, goal and origin are mandatory, every step maps onto the plan file step shape and unknown fields refuse with the path that names them. */
57
- export function parseplanfile(value: unknown): planfile {
58
- if (typeof value !== "object" || value === null || Array.isArray(value))
59
- throw new Error("The plan file must be a json object.");
60
- const root = value as Record<string, unknown>;
61
- const rootfields = ["version", "goal", "origin", "steps", "grants", "denials"];
62
- const unknownrootfields = Object.keys(root).filter((key) => !rootfields.includes(key));
63
- if (unknownrootfields.length > 0)
64
- throw new Error(
65
- `The plan file carries the unknown field${unknownrootfields.length === 1 ? "" : "s"} ${unknownrootfields.join(", ")}; schemastrict refuses unknown plan fields.`,
66
- );
67
- const version = textfield(root, "version", "The plan file");
68
- const goal = textfield(root, "goal", "The plan file");
69
- const origin = textfield(root, "origin", "The plan file");
70
- if (!origin.startsWith("https://")) throw new Error("The plan file origin must be an HTTPS origin.");
71
- if (!Array.isArray(root.steps) || root.steps.length === 0) throw new Error("The plan file needs at least one step.");
72
- const steps: planfilestep[] = [];
73
- root.steps.forEach((rawstep, index) => {
74
- const path = `The plan file step at index ${index}`;
75
- if (typeof rawstep !== "object" || rawstep === null || Array.isArray(rawstep))
76
- throw new Error(`${path} must be an object.`);
77
- const step = rawstep as Record<string, unknown>;
78
- const unknownfields = Object.keys(step).filter((key) => !planfilestepfields.includes(key));
79
- if (unknownfields.length > 0)
80
- throw new Error(
81
- `${path} carries the unknown field${unknownfields.length === 1 ? "" : "s"} ${unknownfields.join(", ")}; schemastrict refuses unknown plan fields.`,
82
- );
83
- const parsed: planfilestep = {
84
- id: textfield(step, "id", path),
85
- kind: textfield(step, "kind", path),
86
- label: textfield(step, "label", path),
87
- };
88
- if (step.target !== undefined) parsed.target = textfield(step, "target", path);
89
- if (step.value !== undefined) parsed.value = String(step.value);
90
- if (step.options !== undefined) parsed.options = String(step.options);
91
- if (step.gate !== undefined) {
92
- if (typeof step.gate !== "boolean") throw new Error(`${path} carries a gate declaration that is not a boolean.`);
93
- parsed.gate = step.gate;
94
- }
95
- if (step.bound !== undefined) {
96
- if (typeof step.bound !== "number" || !Number.isInteger(step.bound) || step.bound < 1)
97
- throw new Error(`${path} carries a loop bound that is not a positive integer.`);
98
- parsed.bound = step.bound;
99
- }
100
- if (step.attempts !== undefined) {
101
- if (typeof step.attempts !== "number" || !Number.isInteger(step.attempts) || step.attempts < 1)
102
- throw new Error(`${path} carries retry attempts that are not a positive integer.`);
103
- parsed.attempts = step.attempts;
104
- }
105
- steps.push(parsed);
106
- });
107
- const file: planfile = { version, goal, origin, steps };
108
- if (root.grants !== undefined) {
109
- if (!Array.isArray(root.grants) || !root.grants.every((grant) => typeof grant === "string" && grant.trim() !== ""))
110
- throw new Error("The plan file grants must be a list of non-empty action kind names.");
111
- file.grants = root.grants.filter((grant): grant is string => typeof grant === "string");
112
- }
113
- if (root.denials !== undefined) {
114
- if (
115
- !Array.isArray(root.denials) ||
116
- !root.denials.every((denial) => typeof denial === "string" && denial.trim() !== "")
117
- )
118
- throw new Error("The plan file denials must be a list of non-empty action kind names.");
119
- file.denials = root.denials.filter((denial): denial is string => typeof denial === "string");
120
- }
121
- return file;
122
- }
123
-
124
- /** Reads the originprofile of one plan file: the profile builds from the declared kind grants exactly the way the extension builds it, so the lint and the runtime grade the same step the same way. */
125
- export function planoriginprofile(file: planfile, now: number): originprofile {
126
- return originprofileof({
127
- origin: file.origin,
128
- grants: (file.grants ?? []) as actionkind[],
129
- denials: (file.denials ?? []) as actionkind[],
130
- now,
131
- });
132
- }
133
-
134
- /** Reads the derived risk of one plan file step: the same risk derivation the extension runs, so the lint and the runtime grade one step identically. The 1.1.80 cli family shares this grade with the risk summary planlint prints. */
135
- export function plansteprisk(step: planfilestep): "read" | "interaction" | "sensitive" {
136
- const mapped = {
137
- id: step.id,
138
- kind: step.kind as actionkind,
139
- label: step.label,
140
- summary: step.label,
141
- ...(step.target !== undefined ? { target: step.target } : {}),
142
- ...(step.value !== undefined ? { value: step.value } : {}),
143
- ...(step.options !== undefined ? { options: step.options } : {}),
144
- risk: "sensitive",
145
- } as toolstep;
146
- try {
147
- return resolvedrisk(mapped);
148
- } catch {
149
- return "sensitive";
150
- }
151
- }
152
-
153
- const steprisk = plansteprisk;
154
-
155
- /** Lints one plan file against the compiled portable rule set: every rule of the set runs, every diagnostic carries its code, path and severity, and an error diagnostic refuses the file before any run starts. */
156
- export function lintplanfile(input: {
157
- file: planfile;
158
- ruleset: portableruleset;
159
- capabilities: string[];
160
- consents?: classconsent[];
161
- now: number;
162
- }): planlintdiagnostic[] {
163
- const diagnostics: planlintdiagnostic[] = [];
164
- const rules = new Set(input.ruleset.rules.map((rule) => rule.id));
165
- const profile: originprofile =
166
- input.file.origin.trim() === ""
167
- ? {
168
- profileid: "missing",
169
- origin: input.file.origin,
170
- grants: (input.file.grants ?? []) as actionkind[],
171
- denials: (input.file.denials ?? []) as actionkind[],
172
- createdat: input.now,
173
- updatedat: input.now,
174
- }
175
- : planoriginprofile(input.file, input.now);
176
- input.file.steps.forEach((step, index) => {
177
- const path = `steps[${index}]`;
178
- if (rules.has("originprofilegrade")) {
179
- const grade = profilegrade({ profile, kind: step.kind as actionkind, sensitive: steprisk(step) === "sensitive" });
180
- if (!grade.allowed)
181
- diagnostics.push({
182
- code: "plan.origin.grade",
183
- path: `${path}.kind`,
184
- severity: "error",
185
- message: `The step ${step.id} of kind ${step.kind} fails the originprofile of ${input.file.origin}: ${grade.reason ?? "the originprofile refuses the kind."}`,
186
- });
187
- }
188
- if (rules.has("classconsent")) {
189
- const classes = sensitiveclassesof({
190
- kind: step.kind as actionkind,
191
- ...(step.value !== undefined ? { value: step.value } : {}),
192
- ...(step.options !== undefined ? { options: step.options } : {}),
193
- });
194
- const missing = missingclassconsents({
195
- origin: input.file.origin,
196
- classes: classes.classes,
197
- bydefault: classes.bydefault,
198
- consents: input.consents ?? [],
199
- now: input.now,
200
- });
201
- if (missing.needed)
202
- diagnostics.push({
203
- code: "plan.consent.class",
204
- path: `${path}.kind`,
205
- severity: "error",
206
- message: `The step ${step.id} needs the sensitive class consent${missing.missing.length === 1 ? "" : "s"} ${missing.missing.join(", ")} for ${input.file.origin}; the extension demands the same fresh consent before the step runs.`,
207
- });
208
- }
209
- if (rules.has("portablecapability")) {
210
- const capability = portablecapabilitygate({ kind: step.kind, capabilities: input.capabilities });
211
- if (!capability.allowed)
212
- diagnostics.push({
213
- code: "plan.capability.kind",
214
- path: `${path}.kind`,
215
- severity: "error",
216
- message: capability.reason ?? "The step kind exceeds the portable capability set.",
217
- });
218
- }
219
- if (rules.has("gatedeclaration")) {
220
- const sensitive =
221
- steprisk(step) === "sensitive" ||
222
- sensitiveclassesof({
223
- kind: step.kind as actionkind,
224
- ...(step.value !== undefined ? { value: step.value } : {}),
225
- ...(step.options !== undefined ? { options: step.options } : {}),
226
- }).sensitive;
227
- if (sensitive && step.gate !== true)
228
- diagnostics.push({
229
- code: "plan.gate.declaration",
230
- path: `${path}.gate`,
231
- severity: "error",
232
- message: `The step ${step.id} of kind ${step.kind} is sensitive and declares no gate; a sensitive step without an explicit gate declaration refuses before any run starts.`,
233
- });
234
- }
235
- if (rules.has("staticselector") && step.target !== undefined && /\{\{[^{}]*\}\}/.test(step.target)) {
236
- diagnostics.push({
237
- code: "plan.selector.static",
238
- path: `${path}.target`,
239
- severity: "info",
240
- message: `The target of the step ${step.id} carries the template ${step.target}; a templated selector resolves only against the live page of the run.`,
241
- });
242
- }
243
- if (rules.has("loopbound") && loopkinds.includes(step.kind) && step.bound === undefined) {
244
- diagnostics.push({
245
- code: "plan.control.loopbound",
246
- path: `${path}.bound`,
247
- severity: "warn",
248
- message: `The loop step ${step.id} of kind ${step.kind} carries no bound; the linter flags every unbounded loop so the author states the iteration ceiling they want.`,
249
- });
250
- }
251
- if (rules.has("retrybound") && retrykinds.includes(step.kind) && step.attempts === undefined) {
252
- diagnostics.push({
253
- code: "plan.control.retrybound",
254
- path: `${path}.attempts`,
255
- severity: "warn",
256
- message: `The retrying step ${step.id} of kind ${step.kind} carries no attempts; the linter flags every missing retry bound so the author states the attempts they reviewed.`,
257
- });
258
- }
259
- });
260
- if (input.file.goal.trim() === "")
261
- diagnostics.push({
262
- code: "plan.goal.empty",
263
- path: "goal",
264
- severity: "error",
265
- message: "The plan file carries no goal; a goalless plan reviews nothing.",
266
- });
267
- if (input.file.origin.trim() === "")
268
- diagnostics.push({
269
- code: "plan.origin.empty",
270
- path: "origin",
271
- severity: "error",
272
- message: "The plan file carries no origin; an originless plan matches no profile.",
273
- });
274
- return diagnostics;
275
- }
276
-
277
- /** Formats one diagnostic set for the terminal: the human format lists one line per diagnostic with its severity, path and message, and the json format carries the whole set as one json array. */
278
- export function formatdiagnostics(diagnostics: planlintdiagnostic[], format: "human" | "json"): string {
279
- if (format === "json") return JSON.stringify(diagnostics, null, 2);
280
- if (diagnostics.length === 0) return "No planlint diagnostics.";
281
- return diagnostics
282
- .map(
283
- (diagnostic) =>
284
- `${diagnostic.severity.toUpperCase()} ${diagnostic.code} at ${diagnostic.path}: ${diagnostic.message}`,
285
- )
286
- .join("\n");
287
- }
288
-
289
- /** Reads the exit code of one lint run: any error diagnostic exits non zero, while info and warn diagnostics keep the run green. */
290
- export function planlintexitcode(diagnostics: planlintdiagnostic[]): number {
291
- return diagnostics.some((diagnostic) => diagnostic.severity === "error") ? 1 : 0;
292
- }
293
-
294
- /** Reads the cache key of one compiled portable rule set: the version and the rule ids compose one deterministic key so repeat runs reuse the parsed rules until the set changes. */
295
- export function rulesetcachekey(ruleset: portableruleset): string {
296
- return `planlint-${ruleset.version}-${ruleset.rules.map((rule) => rule.id).join("+")}`;
297
- }
298
-
299
- /** Reads the plan lint summary of one file for the audit trail: the diagnostic counts per severity beside the exit code the run carries. */
300
- export function planlintsummary(diagnostics: planlintdiagnostic[]): {
301
- info: number;
302
- warn: number;
303
- error: number;
304
- exitcode: number;
305
- reason: string;
306
- } {
307
- const info = diagnostics.filter((diagnostic) => diagnostic.severity === "info").length;
308
- const warn = diagnostics.filter((diagnostic) => diagnostic.severity === "warn").length;
309
- const error = diagnostics.filter((diagnostic) => diagnostic.severity === "error").length;
310
- return {
311
- info,
312
- warn,
313
- error,
314
- exitcode: planlintexitcode(diagnostics),
315
- reason: `The plan lint raised ${info} info, ${warn} warn and ${error} error diagnostic${info + warn + error === 1 ? "" : "s"}; ${error > 0 ? "the error diagnostics refuse the plan file before any run starts" : "no error diagnostic refuses the plan file"}.`,
316
- };
317
- }
318
-
319
- /** Reads the sensitive classes one plan file needs consent for, so the flowrun gate waits name the classes the terminal prompt shows. */
320
- export function plansensitiveclasses(file: planfile): sensitiveclass[] {
321
- const classes = new Set<sensitiveclass>();
322
- for (const step of file.steps)
323
- for (const sensitiveclass of sensitiveclassesof({
324
- kind: step.kind as actionkind,
325
- ...(step.value !== undefined ? { value: step.value } : {}),
326
- ...(step.options !== undefined ? { options: step.options } : {}),
327
- }).classes)
328
- classes.add(sensitiveclass);
329
- return [...classes];
330
- }
331
-
332
- /* ── Merged from planreview.ts ── */
333
-
334
- import type {
335
- actionrisk,
336
- agentplan,
337
- correctionentry,
338
- diffchange,
339
- diffpreviewpayload,
340
- gatewaitevidence,
341
- loghash,
342
- loglevel,
343
- logstreamevent,
344
- logstreamfilter,
345
- planprogress,
346
- plancard,
347
- plancardgroup,
348
- stepapproveresolution,
349
- stepoutcome,
350
- stepstimelinenode,
351
- uisurface,
352
- } from "./types.js";
353
- import { defaultenvironment } from "./environments.js";
354
- import { matchingcorrections } from "./session.js";
355
- import { entryhashof } from "./security.js";
356
- import { randomid } from "./memory.js";
357
-
358
- /**
359
- * Plan review surface logic of the 1.1.64 family, part one.
360
- * The review half of the interface lives here: plancards render one card per proposed step with its kind, risk class, environment and options and group by risk class with the sensitive classes expanded by default while the matching corrections of correctionmemory sit beside their steps; stepapprove resolves exactly one step per distinct human action with approve, reject and edit provenance that lands in the immutable log; diffpreview compares the observed before state with the predicted after state of write class steps only and highlights added, changed and removed fields with masked values carrying their mask verdicts; the stepstimeline derives its pending, running, waiting, done, failed and halted nodes from progress records with no new state, showing durations, environment badges and deep link anchors; and the logstream appends live events with level, source and step ref behind a hash chain the surface verifies as entries arrive while the bounded live window keeps the full history in memory.
361
- * No bound is ever hardcoded: the live buffer window stays the user's choice with no engine cap, and no review surface ever bypasses the human review.
362
- */
363
-
364
- /** Builds one plancard per proposed step: the kind, the risk class, the execution environment, the reviewed options, the summary, the matching corrections from correctionmemory and the edit before approve offer of a pending plan. */
365
- export function plancardsof(input: { plan: agentplan; corrections: correctionentry[] }): plancard[] {
366
- return input.plan.steps.map((step: toolstep) => ({
367
- stepid: step.id,
368
- kind: step.kind,
369
- risk: step.risk,
370
- environment: step.environment ?? defaultenvironment(step),
371
- options: step.options ?? "",
372
- summary: step.summary,
373
- corrections: matchingcorrections(input.corrections, { origin: input.plan.origin, kind: step.kind }).map(
374
- (entry) => ({ id: entry.id, source: entry.source, reason: entry.reason }),
375
- ),
376
- editable: input.plan.state === "pending",
377
- }));
378
- }
379
-
380
- /** Groups the plancards by risk class: the sensitive group expands by default while the read and interaction groups start folded. */
381
- export function plancardgroups(cards: plancard[]): plancardgroup[] {
382
- const order: actionrisk[] = ["sensitive", "interaction", "read"];
383
- return order
384
- .map((risk) => ({ risk, cards: cards.filter((card) => card.risk === risk), expanded: risk === "sensitive" }))
385
- .filter((group) => group.cards.length > 0);
386
- }
387
-
388
- /** Builds one stepapprove resolution: the step, the plan, the origin, the decision, the distinct human surface and the edited step shape an edit carries. */
389
- export function stepresolutionof(input: {
390
- stepid: string;
391
- planid: string;
392
- origin: string;
393
- resolution: "approve" | "reject" | "edit";
394
- surface: uisurface;
395
- edited?: string;
396
- at: number;
397
- }): stepapproveresolution {
398
- if (input.stepid.trim() === "") throw new Error("The stepapprove resolution needs its step.");
399
- if (input.resolution === "edit" && (input.edited ?? "").trim() === "")
400
- throw new Error("The edited resolution needs its corrected step shape.");
401
- return {
402
- stepid: input.stepid,
403
- planid: input.planid,
404
- origin: input.origin,
405
- resolution: input.resolution,
406
- surface: input.surface,
407
- ...(input.edited !== undefined && input.edited.trim() !== "" ? { edited: input.edited } : {}),
408
- at: input.at,
409
- };
410
- }
411
-
412
- /** Builds the immutable log event of one stepapprove resolution: the review kind entry the sealed chain keeps. */
413
- export function resolutionlogeventof(resolution: stepapproveresolution): {
414
- kind: "review";
415
- summary: string;
416
- stepid: string;
417
- } {
418
- return {
419
- kind: "review",
420
- stepid: resolution.stepid,
421
- summary:
422
- resolution.resolution === "edit"
423
- ? `The user edited the step ${resolution.stepid} of the plan ${resolution.planid} from the ${resolution.surface} before approving; the corrected shape rides the plan.`
424
- : `The user ${resolution.resolution === "approve" ? "approved" : "rejected"} the step ${resolution.stepid} of the plan ${resolution.planid} from the ${resolution.surface}; one distinct human action resolved the step alone.`,
425
- };
426
- }
427
-
428
- /** Appends one stepapprove resolution to the per origin history: the review provenance stays queryable per origin. */
429
- export function resolutionhistoryafter(
430
- history: stepapproveresolution[],
431
- resolution: stepapproveresolution,
432
- ): stepapproveresolution[] {
433
- return [resolution, ...history];
434
- }
435
-
436
- /** Derives the mask verdicts of one state pair: every sensitive field shape carries its verdict reason while the rest stays unmasked. */
437
- export function maskverdictsof(state: Record<string, string>, sensitivefields: string[]): Record<string, string> {
438
- const verdicts: Record<string, string> = {};
439
- for (const [field, value] of Object.entries(state)) {
440
- if (sensitivefields.includes(field))
441
- verdicts[field] = `The ${field} value stays masked (${value.length} characters) and never renders in the clear.`;
442
- }
443
- return verdicts;
444
- }
445
-
446
- /** Compares the observed before state with the predicted after state of one write class step: added, changed and removed fields with the mask verdicts of masked values. */
447
- export function diffpreviewof(input: {
448
- stepid: string;
449
- before: Record<string, string>;
450
- after: Record<string, string>;
451
- maskverdicts?: Record<string, string>;
452
- provenance: "inline" | "offscreenworker";
453
- }): diffpreviewpayload {
454
- const changes: diffchange[] = [];
455
- const fields = [...new Set([...Object.keys(input.before), ...Object.keys(input.after)])];
456
- for (const field of fields) {
457
- const hasbefore = Object.prototype.hasOwnProperty.call(input.before, field);
458
- const hasafter = Object.prototype.hasOwnProperty.call(input.after, field);
459
- const beforevalue = input.before[field];
460
- const aftervalue = input.after[field];
461
- if (hasbefore && !hasafter && beforevalue !== undefined)
462
- changes.push({ field, kind: "removed", before: beforevalue });
463
- else if (!hasbefore && hasafter && aftervalue !== undefined)
464
- changes.push({ field, kind: "added", after: aftervalue });
465
- else if (
466
- hasbefore &&
467
- hasafter &&
468
- beforevalue !== undefined &&
469
- aftervalue !== undefined &&
470
- beforevalue !== aftervalue
471
- )
472
- changes.push({ field, kind: "changed", before: beforevalue, after: aftervalue });
473
- }
474
- return {
475
- stepid: input.stepid,
476
- before: input.before,
477
- after: input.after,
478
- changes,
479
- maskverdicts: input.maskverdicts ?? {},
480
- provenance: input.provenance,
481
- };
482
- }
483
-
484
- /** Derives the stepstimeline nodes from the plan and its progress records with no new state: pending, running, waiting, done, failed and halted statuses, durations, environment badges, the active mark and the deep link anchors. */
485
- export function stepstimelinenodes(input: {
486
- plan: agentplan;
487
- progress?: planprogress;
488
- now: number;
489
- }): stepstimelinenode[] {
490
- const completed = input.progress?.completedsteps ?? [];
491
- const outcomes: stepoutcome[] = input.progress?.outcomes ?? [];
492
- const environments = input.progress?.environments;
493
- const turnarounds = input.progress?.turnarounds;
494
- const gatewaits = input.progress?.gatewaits;
495
- let activeset = false;
496
- let blocked = false;
497
- return input.plan.steps.map((step: toolstep) => {
498
- const outcome = [...outcomes].reverse().find((candidate) => candidate.stepid === step.id);
499
- const gatewait: gatewaitevidence | undefined = gatewaits?.[step.id];
500
- let status: stepstimelinenode["status"];
501
- if (outcome !== undefined) status = outcome.ok ? "done" : "failed";
502
- else if (gatewait !== undefined) status = "waiting";
503
- else if (completed.includes(step.id)) status = "done";
504
- else if (input.plan.state === "cancelled" || input.plan.state === "expired") status = "halted";
505
- else if (input.plan.state === "rejected") status = "halted";
506
- else if (input.plan.state === "approved" && !activeset && !blocked) {
507
- status = "running";
508
- activeset = true;
509
- } else status = "pending";
510
- if (status === "waiting") blocked = true;
511
- const active = status === "running";
512
- return {
513
- stepid: step.id,
514
- kind: step.kind,
515
- status,
516
- ...(turnarounds?.[step.id] !== undefined ? { durationms: turnarounds[step.id] } : {}),
517
- ...(environments?.[step.id] !== undefined
518
- ? { environment: environments[step.id] }
519
- : step.environment !== undefined
520
- ? { environment: step.environment }
521
- : {}),
522
- active,
523
- anchor: `#step-${step.id}`,
524
- ...(outcome !== undefined ? { resultsummary: outcome.summary } : {}),
525
- };
526
- });
527
- }
528
-
529
- /** Returns the deep link anchor of the active node the timeline scrolls to during a run. */
530
- export function activetimelineanchor(nodes: stepstimelinenode[]): string | undefined {
531
- return nodes.find((node) => node.active)?.anchor;
532
- }
533
-
534
- /** The genesis hash the first logstream event chains to, mirroring the immutable log genesis. */
535
- export const logstreamgenesis = "0".repeat(64);
536
-
537
- /** Builds one logstream event behind the hash chain: the level, the source, the origin, the summary, the optional step ref, the mask verdict of the source payload and the link to the previous event. */
538
- export async function logstreameventof(input: {
539
- level: loglevel;
540
- source: string;
541
- origin: string;
542
- summary: string;
543
- stepid?: string;
544
- masked: boolean;
545
- maskverdict: string;
546
- previous: string;
547
- at: number;
548
- }): Promise<logstreamevent> {
549
- if (input.summary.trim() === "") throw new Error("The logstream event needs its summary.");
550
- const id = randomid();
551
- const hash: loghash = await entryhashof({
552
- previous: input.previous,
553
- entry: {
554
- id,
555
- runid: "surfaces",
556
- kind: "step",
557
- summary: input.summary,
558
- origin: input.origin,
559
- ...(input.stepid !== undefined ? { stepid: input.stepid } : {}),
560
- at: input.at,
561
- },
562
- });
563
- return {
564
- id,
565
- level: input.level,
566
- source: input.source,
567
- origin: input.origin,
568
- summary: input.summary,
569
- ...(input.stepid !== undefined ? { stepid: input.stepid } : {}),
570
- masked: input.masked,
571
- maskverdict: input.maskverdict,
572
- hash,
573
- at: input.at,
574
- };
575
- }
576
-
577
- /** Appends one logstream event to the in memory history: the history stays full while the live window reads its bounded slice. */
578
- export function appendlogstreamevent(events: logstreamevent[], event: logstreamevent): logstreamevent[] {
579
- return [...events, event];
580
- }
581
-
582
- /** Filters the logstream events by level, origin and step ref: an absent filter keeps every event. */
583
- export function filterlogstream(events: logstreamevent[], filter: logstreamfilter): logstreamevent[] {
584
- return events.filter(
585
- (event) =>
586
- (filter.level === undefined || event.level === filter.level) &&
587
- (filter.origin === undefined || filter.origin === "" || event.origin === filter.origin) &&
588
- (filter.stepid === undefined || filter.stepid === "" || event.stepid === filter.stepid),
589
- );
590
- }
591
-
592
- /** Returns the bounded live window of the logstream: the user configured bound keeps the newest events while the full history stays in memory; an absent bound keeps every event live. */
593
- export function livebufferof(events: logstreamevent[], bound: number | undefined): logstreamevent[] {
594
- if (bound === undefined) return events;
595
- if (!Number.isInteger(bound) || bound <= 0) return events;
596
- return events.slice(-bound);
597
- }
598
-
599
- /** Verifies the logstream hash chain live as entries arrive: every event links to its predecessor and a broken link names its position. */
600
- export async function verifylogstream(
601
- events: logstreamevent[],
602
- ): Promise<{ valid: boolean; brokenat?: number; reason: string }> {
603
- for (let index = 0; index < events.length; index += 1) {
604
- const event = events[index];
605
- if (event === undefined) continue;
606
- const predecessor = events[index - 1];
607
- const expectedprevious = index === 0 || predecessor === undefined ? logstreamgenesis : predecessor.hash.current;
608
- if (event.hash.previous !== expectedprevious)
609
- return {
610
- valid: false,
611
- brokenat: index,
612
- reason: `The logstream chain breaks at the event ${event.id}: its previous hash does not link to its predecessor.`,
613
- };
614
- const recomputed = await entryhashof({
615
- previous: event.hash.previous,
616
- entry: {
617
- id: event.id,
618
- runid: "surfaces",
619
- kind: "step",
620
- summary: event.summary,
621
- origin: event.origin,
622
- ...(event.stepid !== undefined ? { stepid: event.stepid } : {}),
623
- at: event.at,
624
- },
625
- });
626
- if (recomputed.current !== event.hash.current)
627
- return {
628
- valid: false,
629
- brokenat: index,
630
- reason: `The logstream chain breaks at the event ${event.id}: its own hash does not reproduce.`,
631
- };
632
- }
633
- return {
634
- valid: true,
635
- reason: `The logstream chain of ${events.length} event${events.length === 1 ? "" : "s"} verifies link by link.`,
636
- };
637
- }
638
-
639
- /** Copies one verified range of the logstream as an audit excerpt: an unverified chain refuses the copy while a verified range renders its entries with level, source and mask verdict. */
640
- export async function auditexcerptof(
641
- events: logstreamevent[],
642
- input: { from: number; to: number },
643
- ): Promise<{ ok: boolean; text: string; reason: string }> {
644
- if (input.from < 0 || input.to <= input.from || input.to > events.length)
645
- return {
646
- ok: false,
647
- text: "",
648
- reason: `The excerpt range ${input.from} to ${input.to} names no contiguous slice of the ${events.length} event${events.length === 1 ? "" : "s"}.`,
649
- };
650
- const range = events.slice(input.from, input.to);
651
- const verification = await verifylogstream(range);
652
- if (!verification.valid)
653
- return { ok: false, text: "", reason: `The excerpt refuses the copy: ${verification.reason}` };
654
- const text = range
655
- .map(
656
- (event) =>
657
- `[${event.at}] ${event.level} ${event.source}${event.stepid !== undefined ? ` step ${event.stepid}` : ""} ${event.origin} — ${event.summary}${event.masked ? ` (${event.maskverdict})` : ""}`,
658
- )
659
- .join("\n");
660
- return {
661
- ok: true,
662
- text,
663
- reason: `The excerpt copied the verified range ${input.from} to ${input.to} of the logstream.`,
664
- };
665
- }
666
-
667
- /** Maps one audit kind onto the logstream level its event carries: errors stream as error, the refusal family streams as warn and the rest streams as info. */
668
- export function loglevelof(kind: string): loglevel {
669
- if (kind === "error") return "error";
670
- if (["deny", "revoke", "stop", "quarantine", "phish", "defer", "schema", "expiry"].includes(kind)) return "warn";
671
- return "info";
672
- }