@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.
- package/CHANGELOG.md +60 -0
- package/{dist/bridge.d.ts → bridge.d.ts} +1 -1
- package/bridge.d.ts.map +1 -0
- package/bridge.js +1877 -0
- package/bun.d.ts.map +1 -0
- package/bun.js +52329 -0
- package/caps/background.json +712 -0
- package/caps/cli.json +360 -0
- package/caps/library.json +3611 -0
- package/caps/mcp.json +126 -0
- package/caps/pagebridge.json +377 -0
- package/caps/popup.json +51 -0
- package/caps/sidepanel.json +522 -0
- package/checksums.txt +142 -0
- package/cli.d.ts.map +1 -0
- package/{dist/cli.js → cli.js} +2 -2
- package/companion.d.ts.map +1 -0
- package/companion.js +389 -0
- package/crossbrowser.d.ts.map +1 -0
- package/crossbrowser.js +1191 -0
- package/dashdone.d.ts.map +1 -0
- package/dashdone.js +368 -0
- package/deno.d.ts.map +1 -0
- package/deno.js +52325 -0
- package/devthink.umd.js +52346 -0
- package/fixtures/bun.ts +7 -0
- package/fixtures/cjs.js +6 -0
- package/fixtures/deno.ts +6 -0
- package/fixtures/example-org-pagestate.json +22 -0
- package/fixtures/importers/automa-workflow.json +18 -0
- package/fixtures/importers/selenium-side.json +20 -0
- package/fixtures/importers/tabular-plan.csv +7 -0
- package/fixtures/importers/uivision-macro.json +10 -0
- package/fixtures/importers/v1-plan.json +13 -0
- package/fixtures/mcp-client.mjs +14 -0
- package/fixtures/pages/checkout.html +192 -0
- package/fixtures/pages/dashboard.html +110 -0
- package/fixtures/pages/feed.html +113 -0
- package/fixtures/pages/product-grid.html +150 -0
- package/fixtures/plans/form-inventory-plan.json +11 -0
- package/fixtures/plans/release-notes-plan.json +11 -0
- package/fixtures/recipes/agentscompete.json +11 -0
- package/fixtures/recipes/agentsforms.json +11 -0
- package/fixtures/recipes/agentsmonitor.json +11 -0
- package/fixtures/recipes/agentsreview.json +29 -0
- package/fixtures/recipes/agentswarm.json +11 -0
- package/fixtures/recipes/fillcalendar.json +12 -0
- package/fixtures/recipes/fillcheckout.json +13 -0
- package/fixtures/recipes/filldependentdropdowns.json +12 -0
- package/fixtures/recipes/fillfromcsv.json +13 -0
- package/fixtures/recipes/fillloginform.json +12 -0
- package/fixtures/recipes/fillmultistepwizard.json +13 -0
- package/fixtures/recipes/fillregistration.json +12 -0
- package/fixtures/recipes/fillsearchfilters.json +12 -0
- package/fixtures/recipes/filltypeahead.json +11 -0
- package/fixtures/recipes/monitoralerts.json +12 -0
- package/fixtures/recipes/monitoravailability.json +11 -0
- package/fixtures/recipes/monitornetworkquiet.json +12 -0
- package/fixtures/recipes/monitorpagechanges.json +11 -0
- package/fixtures/recipes/monitorpricechange.json +11 -0
- package/fixtures/recipes/monitorschedule.json +12 -0
- package/fixtures/recipes/scrapedynamicprices.json +13 -0
- package/fixtures/recipes/scrapeimagegallery.json +12 -0
- package/fixtures/recipes/scrapeinfinitefeed.json +13 -0
- package/fixtures/recipes/scrapenestedlists.json +12 -0
- package/fixtures/recipes/scrapeproductgrid.json +12 -0
- package/fixtures/recipes/scrapereviews.json +12 -0
- package/fixtures/recipes/scrapesearchresults.json +12 -0
- package/fixtures/recipes/scrapetablepagination.json +12 -0
- package/fixtures/recipes/testaccessibility.json +34 -0
- package/fixtures/recipes/testconsoleerrors.json +12 -0
- package/fixtures/recipes/testdeeplinks.json +12 -0
- package/fixtures/recipes/testformvalidation.json +13 -0
- package/fixtures/recipes/testlayoutshift.json +12 -0
- package/fixtures/recipes/testlinkcheck.json +12 -0
- package/fixtures/recipes/testloadtiming.json +12 -0
- package/fixtures/recipes/testvisualdiff.json +12 -0
- package/fixtures/workflows/changelog-digest-workflow.json +13 -0
- package/fixtures/workflows/form-survey-workflow.json +13 -0
- package/gallery.json +706 -0
- package/hardening.d.ts.map +1 -0
- package/hardening.js +188 -0
- package/headless.d.ts.map +1 -0
- package/headless.js +11522 -0
- package/http.d.ts.map +1 -0
- package/http.js +2165 -0
- package/index.cjs +54682 -0
- package/index.d.ts.map +1 -0
- package/index.js +52310 -0
- package/index.neutral.js +46343 -0
- package/{web/manifest.json → manifest.json} +1 -1
- package/mcp.d.ts.map +1 -0
- package/mcp.js +20515 -0
- package/memory.d.ts.map +1 -0
- package/{memory.ts → memory.js} +2310 -3834
- package/nativehost.template.json +10 -0
- package/node.cjs +54686 -0
- package/node.d.ts.map +1 -0
- package/pack.d.ts.map +1 -0
- package/pack.js +805 -0
- package/package.json +83 -176
- package/policy.d.ts.map +1 -0
- package/{policy.ts → policy.js} +4679 -5073
- package/progress.d.ts.map +1 -0
- package/progress.js +645 -0
- package/protocol.d.ts.map +1 -0
- package/protocol.js +8334 -0
- package/schemas/audit.schema.json +226 -0
- package/schemas/capability.schema.json +113 -0
- package/schemas/envelope.schema.json +162 -0
- package/schemas/memory.schema.json +51 -0
- package/schemas/observation.schema.json +113 -0
- package/schemas/plan.schema.json +208 -0
- package/schemas/progress.schema.json +126 -0
- package/schemas/proposal.schema.json +68 -0
- package/schemas/session.schema.json +82 -0
- package/schemas/tool.schema.json +94 -0
- package/server.d.ts.map +1 -0
- package/server.js +28877 -0
- package/umd-example.html +18 -0
- package/umd.d.ts.map +1 -0
- package/accounts.ts +0 -2087
- package/agent.ts +0 -2331
- package/antigravity.ts +0 -2139
- package/apifreeze.ts +0 -4146
- package/auth.ts +0 -642
- package/background.ts +0 -16837
- package/bridge.ts +0 -2080
- package/build.ts +0 -54
- package/bun.ts +0 -26
- package/capture.ts +0 -1614
- package/checksecrets.ts +0 -72
- package/cjs.ts +0 -9
- package/cli.ts +0 -3500
- package/commands.ts +0 -1101
- package/companion.ts +0 -450
- package/compatibility.ts +0 -37
- package/config.ts +0 -3137
- package/constants.ts +0 -943
- package/core.ts +0 -1473
- package/crossbrowser.ts +0 -1359
- package/dashdone.ts +0 -605
- package/data.ts +0 -943
- package/database.ts +0 -133
- package/debug.ts +0 -850
- package/deno.ts +0 -27
- package/devthink.ts +0 -707
- package/dist/accounts.d.ts +0 -409
- package/dist/agent.d.ts +0 -837
- package/dist/antigravity.d.ts +0 -380
- package/dist/apifreeze.d.ts +0 -113
- package/dist/auth.d.ts +0 -224
- package/dist/capture.d.ts +0 -595
- package/dist/cjs.d.ts +0 -6
- package/dist/commands.d.ts +0 -310
- package/dist/compatibility.d.ts +0 -17
- package/dist/config.d.ts +0 -1853
- package/dist/constants.d.ts +0 -637
- package/dist/core.d.ts +0 -361
- package/dist/data.d.ts +0 -330
- package/dist/database.d.ts +0 -24
- package/dist/debug.d.ts +0 -270
- package/dist/engine.d.ts +0 -74
- package/dist/environments.d.ts +0 -244
- package/dist/evidence.d.ts +0 -277
- package/dist/export.d.ts +0 -311
- package/dist/fingerprint.d.ts +0 -594
- package/dist/flow.d.ts +0 -300
- package/dist/gates.d.ts +0 -103
- package/dist/identity.d.ts +0 -51
- package/dist/ids.d.ts +0 -7
- package/dist/llm.d.ts +0 -652
- package/dist/models.d.ts +0 -1807
- package/dist/net.d.ts +0 -325
- package/dist/neutral.d.ts +0 -41
- package/dist/oauth.d.ts +0 -703
- package/dist/page.d.ts +0 -1166
- package/dist/perf.d.ts +0 -481
- package/dist/plan.d.ts +0 -134
- package/dist/project.d.ts +0 -395
- package/dist/providers.d.ts +0 -37
- package/dist/quota.d.ts +0 -453
- package/dist/recovery.d.ts +0 -621
- package/dist/request.d.ts +0 -1492
- package/dist/run.d.ts +0 -779
- package/dist/runtime.d.ts +0 -212
- package/dist/search.d.ts +0 -375
- package/dist/security.d.ts +0 -534
- package/dist/serve.d.ts +0 -136
- package/dist/session.d.ts +0 -296
- package/dist/storage.d.ts +0 -53
- package/dist/streaming.d.ts +0 -1119
- package/dist/swarm.d.ts +0 -328
- package/dist/tools.d.ts +0 -170
- package/dist/types.d.ts +0 -8357
- package/dist/utils.d.ts +0 -82
- package/dist/version.d.ts +0 -7
- package/dist/versionregistry.d.ts +0 -406
- package/dist/views.d.ts +0 -579
- package/dist/workbenchsession.d.ts +0 -69
- package/dist/workflow.d.ts +0 -649
- package/docs/readme.md +0 -92
- package/docs/schemas/antigravity.json +0 -192
- package/engine.ts +0 -1524
- package/environments.ts +0 -1005
- package/evidence.ts +0 -643
- package/export.ts +0 -889
- package/fingerprint.ts +0 -1458
- package/flow.ts +0 -1190
- package/gates.ts +0 -307
- package/gateway.ts +0 -34
- package/hardening.ts +0 -191
- package/headless.ts +0 -369
- package/http.ts +0 -2110
- package/identity.ts +0 -246
- package/ids.ts +0 -26
- package/index.ts +0 -369
- package/llm.ts +0 -2682
- package/mcp.ts +0 -1835
- package/models.ts +0 -3805
- package/modes.ts +0 -43
- package/net.ts +0 -1214
- package/neutral.ts +0 -362
- package/node.ts +0 -26
- package/oauth.ts +0 -2216
- package/offscreen.ts +0 -170
- package/pack.ts +0 -956
- package/page.ts +0 -7824
- package/pagebridge.ts +0 -1116
- package/perf.ts +0 -957
- package/plan.ts +0 -672
- package/plugin.ts +0 -76
- package/prisma.config.ts +0 -27
- package/progress.ts +0 -1308
- package/project.ts +0 -1291
- package/protocol.ts +0 -4187
- package/providers.ts +0 -246
- package/quota.ts +0 -1887
- package/recovery.ts +0 -2433
- package/request.ts +0 -4482
- package/run.ts +0 -2135
- package/runtime.ts +0 -526
- package/search.ts +0 -1760
- package/security.ts +0 -1673
- package/serve.ts +0 -419
- package/server.ts +0 -5538
- package/session.ts +0 -1162
- package/storage.ts +0 -182
- package/streaming.ts +0 -4101
- package/swarm.ts +0 -884
- package/sync.ts +0 -56
- package/tests/agentcert.test.ts +0 -128
- package/tests/agentfleet.test.ts +0 -214
- package/tests/agentreview.test.ts +0 -241
- package/tests/agentstream.test.ts +0 -335
- package/tests/agentwork.test.ts +0 -356
- package/tests/apifreeze.test.ts +0 -290
- package/tests/apimap.test.ts +0 -112
- package/tests/approvalgate.test.ts +0 -175
- package/tests/artifactmanifest.test.ts +0 -204
- package/tests/attentionfeed.test.ts +0 -163
- package/tests/backgroundruns.test.ts +0 -100
- package/tests/batchscheduling.test.ts +0 -131
- package/tests/blackboard.test.ts +0 -238
- package/tests/bridge.test.ts +0 -1088
- package/tests/browsercoverage.test.ts +0 -134
- package/tests/browserpolyfills.test.ts +0 -124
- package/tests/build.mjs +0 -1260
- package/tests/clientauth.test.ts +0 -223
- package/tests/clitools.test.ts +0 -578
- package/tests/confirmgates.test.ts +0 -190
- package/tests/containerpack.test.ts +0 -143
- package/tests/controlflow.test.ts +0 -2120
- package/tests/coordination.test.ts +0 -345
- package/tests/copyscreen.test.ts +0 -139
- package/tests/core.test.ts +0 -417
- package/tests/costcert.test.ts +0 -95
- package/tests/crossbrowser.test.ts +0 -115
- package/tests/cspaudit.test.ts +0 -79
- package/tests/dashdone.test.ts +0 -615
- package/tests/datagrid.test.ts +0 -137
- package/tests/doccheck.test.ts +0 -107
- package/tests/dombatch.test.ts +0 -109
- package/tests/emulation.test.ts +0 -282
- package/tests/environments.test.ts +0 -259
- package/tests/evidenceviews.test.ts +0 -88
- package/tests/exporttools.test.ts +0 -185
- package/tests/firefoxprep.test.ts +0 -134
- package/tests/flowlibrary.test.ts +0 -242
- package/tests/flowrun.test.ts +0 -303
- package/tests/forensics.test.ts +0 -439
- package/tests/gateway.test.ts +0 -939
- package/tests/headlesslib.test.ts +0 -171
- package/tests/httpstream.test.ts +0 -277
- package/tests/immutablelog.test.ts +0 -157
- package/tests/inboundguard.test.ts +0 -177
- package/tests/lazyload.test.ts +0 -72
- package/tests/librarymodes.test.ts +0 -623
- package/tests/llm.test.ts +0 -696
- package/tests/manifest.test.ts +0 -226
- package/tests/maskinputs.test.ts +0 -150
- package/tests/matrixverify.test.ts +0 -111
- package/tests/mavenpack.test.ts +0 -75
- package/tests/mcpserver.test.ts +0 -704
- package/tests/memory.test.ts +0 -4155
- package/tests/memorycare.test.ts +0 -230
- package/tests/migrateplan.test.ts +0 -349
- package/tests/minimization.test.ts +0 -414
- package/tests/modelroute.test.ts +0 -112
- package/tests/multiagent.test.ts +0 -440
- package/tests/nativebridge.test.ts +0 -807
- package/tests/navigation.test.ts +0 -284
- package/tests/nugetpack.test.ts +0 -83
- package/tests/orchestration.test.ts +0 -449
- package/tests/originpolicy.test.ts +0 -405
- package/tests/outputcompare.test.ts +0 -97
- package/tests/package.test.ts +0 -91
- package/tests/pagebridge.test.ts +0 -5909
- package/tests/pentest.test.ts +0 -75
- package/tests/perfrecords.test.ts +0 -257
- package/tests/permdiff.test.ts +0 -85
- package/tests/phishguard.test.ts +0 -128
- package/tests/pickerviews.test.ts +0 -155
- package/tests/pipelines.test.ts +0 -381
- package/tests/planlint.test.ts +0 -240
- package/tests/planreview.test.ts +0 -383
- package/tests/platformtargets.test.ts +0 -56
- package/tests/policy.test.ts +0 -8284
- package/tests/portability.test.ts +0 -96
- package/tests/progress.test.ts +0 -1036
- package/tests/promptlibrary.test.ts +0 -116
- package/tests/protocol.test.ts +0 -4793
- package/tests/publishingpipeline.test.ts +0 -314
- package/tests/quickactions.test.ts +0 -164
- package/tests/readiness.test.ts +0 -78
- package/tests/recipes.test.ts +0 -163
- package/tests/redactshots.test.ts +0 -216
- package/tests/registernvidiakeys.ts +0 -51
- package/tests/relayserve.test.ts +0 -304
- package/tests/releasenotes.test.ts +0 -276
- package/tests/resilience.test.ts +0 -467
- package/tests/resourceaware.test.ts +0 -187
- package/tests/runbudget.test.ts +0 -125
- package/tests/runhistory.test.ts +0 -299
- package/tests/runreplay.test.ts +0 -113
- package/tests/runresume.test.ts +0 -184
- package/tests/runstate.test.ts +0 -288
- package/tests/runtimeadapters.test.ts +0 -251
- package/tests/safariskeleton.test.ts +0 -115
- package/tests/sandboxframe.test.ts +0 -196
- package/tests/sbom.test.ts +0 -105
- package/tests/secretvault.test.ts +0 -188
- package/tests/selcache.test.ts +0 -111
- package/tests/sessioninterface.test.ts +0 -791
- package/tests/sessions.test.ts +0 -591
- package/tests/siteprefs.test.ts +0 -228
- package/tests/snapshotdelta.test.ts +0 -165
- package/tests/statusviews.test.ts +0 -237
- package/tests/stepmeter.test.ts +0 -200
- package/tests/streamparse.test.ts +0 -163
- package/tests/stylepruning.test.ts +0 -90
- package/tests/surfaces.test.ts +0 -282
- package/tests/sweep.test.ts +0 -78
- package/tests/syncbridge.test.ts +0 -164
- package/tests/taskqueue.test.ts +0 -206
- package/tests/toolcalls.test.ts +0 -336
- package/tests/toolcatalog.test.ts +0 -201
- package/tests/torture-agent.test.ts +0 -706
- package/tests/torture-auth.test.ts +0 -461
- package/tests/torture-commands.test.ts +0 -895
- package/tests/torture-data.test.ts +0 -923
- package/tests/torture-environments.test.ts +0 -285
- package/tests/torture-export.test.ts +0 -263
- package/tests/torture-http.test.ts +0 -1379
- package/tests/torture-llm.test.ts +0 -808
- package/tests/torture-memory.test.ts +0 -1084
- package/tests/torture-plan.test.ts +0 -829
- package/tests/torture-run.test.ts +0 -626
- package/tests/torture-session.test.ts +0 -928
- package/tests/torture-swarm.test.ts +0 -1245
- package/tests/torture-tools.test.ts +0 -964
- package/tests/torture-workflow.test.ts +0 -2354
- package/tests/tourviews.test.ts +0 -52
- package/tests/transparency.test.ts +0 -147
- package/tests/trigger.test.ts +0 -941
- package/tests/v1sunset.test.ts +0 -428
- package/tests/virtlist.test.ts +0 -94
- package/tests/vision.test.ts +0 -540
- package/tests/vsixpack.test.ts +0 -145
- package/tests/webapi.test.ts +0 -523
- package/tests/workflow.test.ts +0 -1785
- package/tests/workfloweditor.test.ts +0 -759
- package/tests/xpipack.test.ts +0 -128
- package/tools.ts +0 -734
- package/types.ts +0 -9908
- package/umd.ts +0 -9
- package/utils.ts +0 -249
- package/version.ts +0 -8
- package/versionregistry.ts +0 -1310
- package/views.ts +0 -2153
- package/vitest.config.ts +0 -12
- package/web/App.tsx +0 -78
- package/web/README.md +0 -47
- package/web/capacitor.config.ts +0 -46
- package/web/const.ts +0 -19
- package/web/control.shell.tsx +0 -66
- package/web/dashboardpage.ts +0 -554
- package/web/db.ts +0 -493
- package/web/design.html +0 -541
- package/web/gatewayview/Gateway.tsx +0 -345
- package/web/gatewayview/config.ts +0 -1042
- package/web/gatewayview/definition.ts +0 -174
- package/web/icons.ts +0 -15
- package/web/optionspage.ts +0 -691
- package/web/popup.ts +0 -1651
- package/web/schema.prisma +0 -176
- package/web/sidepanel.ts +0 -7941
- package/web/sitemanifest.json +0 -13
- package/web/terminal-ui.tsx +0 -622
- package/web/transparencypage.ts +0 -541
- package/web/vite.config.ts +0 -39
- package/web/workspace.ts +0 -23
- package/workbenchmemory.ts +0 -104
- package/workbenchsession.ts +0 -304
- package/workflow.ts +0 -4495
- /package/{dist/bun.d.ts → bun.d.ts} +0 -0
- /package/{dist/cli.d.ts → cli.d.ts} +0 -0
- /package/{dist/companion.d.ts → companion.d.ts} +0 -0
- /package/{dist/crossbrowser.d.ts → crossbrowser.d.ts} +0 -0
- /package/{dist/dashdone.d.ts → dashdone.d.ts} +0 -0
- /package/{dist/deno.d.ts → deno.d.ts} +0 -0
- /package/{dist/hardening.d.ts → hardening.d.ts} +0 -0
- /package/{dist/headless.d.ts → headless.d.ts} +0 -0
- /package/{dist/http.d.ts → http.d.ts} +0 -0
- /package/{dist/index.d.ts → index.d.ts} +0 -0
- /package/{dist/mcp.d.ts → mcp.d.ts} +0 -0
- /package/{dist/memory.d.ts → memory.d.ts} +0 -0
- /package/{dist/node.d.ts → node.d.ts} +0 -0
- /package/{dist/pack.d.ts → pack.d.ts} +0 -0
- /package/{dist/policy.d.ts → policy.d.ts} +0 -0
- /package/{dist/progress.d.ts → progress.d.ts} +0 -0
- /package/{dist/protocol.d.ts → protocol.d.ts} +0 -0
- /package/{dist/server.d.ts → server.d.ts} +0 -0
- /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
|
-
}
|