@wenathlan/devthink 2.0.13 → 2.0.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +27 -0
- package/accounts.ts +2087 -0
- package/agent.ts +2331 -0
- package/antigravity.ts +2139 -0
- package/apifreeze.ts +4146 -0
- package/auth.ts +642 -0
- package/background.ts +16837 -0
- package/bridge.ts +2080 -0
- package/build.ts +54 -0
- package/bun.ts +26 -0
- package/capture.ts +1614 -0
- package/checksecrets.ts +72 -0
- package/cjs.ts +9 -0
- package/cli.ts +3500 -0
- package/commands.ts +1101 -0
- package/companion.ts +450 -0
- package/compatibility.ts +37 -0
- package/config.ts +3137 -0
- package/constants.ts +943 -0
- package/core.ts +1473 -0
- package/crossbrowser.ts +1359 -0
- package/dashdone.ts +605 -0
- package/data.ts +943 -0
- package/database.ts +133 -0
- package/debug.ts +850 -0
- package/deno.ts +27 -0
- package/devthink.ts +707 -0
- package/dist/accounts.d.ts +409 -0
- package/dist/agent.d.ts +837 -0
- package/dist/antigravity.d.ts +380 -0
- package/dist/apifreeze.d.ts +113 -0
- package/dist/auth.d.ts +224 -0
- package/{bridge.d.ts → dist/bridge.d.ts} +1 -1
- package/dist/capture.d.ts +595 -0
- package/dist/cjs.d.ts +6 -0
- package/{cli.js → dist/cli.js} +2 -2
- package/dist/commands.d.ts +310 -0
- package/dist/compatibility.d.ts +17 -0
- package/dist/config.d.ts +1853 -0
- package/dist/constants.d.ts +637 -0
- package/dist/core.d.ts +361 -0
- package/dist/data.d.ts +330 -0
- package/dist/database.d.ts +24 -0
- package/dist/debug.d.ts +270 -0
- package/dist/engine.d.ts +74 -0
- package/dist/environments.d.ts +244 -0
- package/dist/evidence.d.ts +277 -0
- package/dist/export.d.ts +311 -0
- package/dist/fingerprint.d.ts +594 -0
- package/dist/flow.d.ts +300 -0
- package/dist/gates.d.ts +103 -0
- package/dist/identity.d.ts +51 -0
- package/dist/ids.d.ts +7 -0
- package/dist/llm.d.ts +652 -0
- package/dist/models.d.ts +1807 -0
- package/dist/net.d.ts +325 -0
- package/dist/neutral.d.ts +41 -0
- package/dist/oauth.d.ts +703 -0
- package/dist/page.d.ts +1166 -0
- package/dist/perf.d.ts +481 -0
- package/dist/plan.d.ts +134 -0
- package/dist/project.d.ts +395 -0
- package/dist/providers.d.ts +37 -0
- package/dist/quota.d.ts +453 -0
- package/dist/recovery.d.ts +621 -0
- package/dist/request.d.ts +1492 -0
- package/dist/run.d.ts +779 -0
- package/dist/runtime.d.ts +212 -0
- package/dist/search.d.ts +375 -0
- package/dist/security.d.ts +534 -0
- package/dist/serve.d.ts +136 -0
- package/dist/session.d.ts +296 -0
- package/dist/storage.d.ts +53 -0
- package/dist/streaming.d.ts +1119 -0
- package/dist/swarm.d.ts +328 -0
- package/dist/tools.d.ts +170 -0
- package/dist/types.d.ts +8357 -0
- package/dist/utils.d.ts +82 -0
- package/dist/version.d.ts +7 -0
- package/dist/versionregistry.d.ts +406 -0
- package/dist/views.d.ts +579 -0
- package/dist/workbenchsession.d.ts +69 -0
- package/dist/workflow.d.ts +649 -0
- package/docs/readme.md +92 -0
- package/docs/schemas/antigravity.json +192 -0
- package/engine.ts +1524 -0
- package/environments.ts +1005 -0
- package/evidence.ts +643 -0
- package/export.ts +889 -0
- package/fingerprint.ts +1458 -0
- package/flow.ts +1190 -0
- package/gates.ts +307 -0
- package/gateway.ts +34 -0
- package/hardening.ts +191 -0
- package/headless.ts +369 -0
- package/http.ts +2110 -0
- package/identity.ts +246 -0
- package/ids.ts +26 -0
- package/index.ts +369 -0
- package/llm.ts +2682 -0
- package/mcp.ts +1835 -0
- package/{memory.js → memory.ts} +3834 -2310
- package/models.ts +3805 -0
- package/modes.ts +43 -0
- package/net.ts +1214 -0
- package/neutral.ts +362 -0
- package/node.ts +26 -0
- package/oauth.ts +2216 -0
- package/offscreen.ts +170 -0
- package/pack.ts +956 -0
- package/package.json +175 -82
- package/page.ts +7824 -0
- package/pagebridge.ts +1116 -0
- package/perf.ts +957 -0
- package/plan.ts +672 -0
- package/plugin.ts +76 -0
- package/{policy.js → policy.ts} +5073 -4679
- package/prisma.config.ts +27 -0
- package/progress.ts +1308 -0
- package/project.ts +1291 -0
- package/protocol.ts +4187 -0
- package/providers.ts +246 -0
- package/quota.ts +1887 -0
- package/recovery.ts +2433 -0
- package/request.ts +4482 -0
- package/run.ts +2135 -0
- package/runtime.ts +526 -0
- package/search.ts +1760 -0
- package/security.ts +1673 -0
- package/serve.ts +419 -0
- package/server.ts +5538 -0
- package/session.ts +1162 -0
- package/storage.ts +182 -0
- package/streaming.ts +4101 -0
- package/swarm.ts +884 -0
- package/sync.ts +56 -0
- package/tests/agentcert.test.ts +128 -0
- package/tests/agentfleet.test.ts +214 -0
- package/tests/agentreview.test.ts +241 -0
- package/tests/agentstream.test.ts +335 -0
- package/tests/agentwork.test.ts +356 -0
- package/tests/apifreeze.test.ts +290 -0
- package/tests/apimap.test.ts +112 -0
- package/tests/approvalgate.test.ts +175 -0
- package/tests/artifactmanifest.test.ts +204 -0
- package/tests/attentionfeed.test.ts +163 -0
- package/tests/backgroundruns.test.ts +100 -0
- package/tests/batchscheduling.test.ts +131 -0
- package/tests/blackboard.test.ts +238 -0
- package/tests/bridge.test.ts +1088 -0
- package/tests/browsercoverage.test.ts +134 -0
- package/tests/browserpolyfills.test.ts +124 -0
- package/tests/build.mjs +1260 -0
- package/tests/clientauth.test.ts +223 -0
- package/tests/clitools.test.ts +578 -0
- package/tests/confirmgates.test.ts +190 -0
- package/tests/containerpack.test.ts +143 -0
- package/tests/controlflow.test.ts +2120 -0
- package/tests/coordination.test.ts +345 -0
- package/tests/copyscreen.test.ts +139 -0
- package/tests/core.test.ts +417 -0
- package/tests/costcert.test.ts +95 -0
- package/tests/crossbrowser.test.ts +115 -0
- package/tests/cspaudit.test.ts +79 -0
- package/tests/dashdone.test.ts +615 -0
- package/tests/datagrid.test.ts +137 -0
- package/tests/doccheck.test.ts +107 -0
- package/tests/dombatch.test.ts +109 -0
- package/tests/emulation.test.ts +282 -0
- package/tests/environments.test.ts +259 -0
- package/tests/evidenceviews.test.ts +88 -0
- package/tests/exporttools.test.ts +185 -0
- package/tests/firefoxprep.test.ts +134 -0
- package/tests/flowlibrary.test.ts +242 -0
- package/tests/flowrun.test.ts +303 -0
- package/tests/forensics.test.ts +439 -0
- package/tests/gateway.test.ts +939 -0
- package/tests/headlesslib.test.ts +171 -0
- package/tests/httpstream.test.ts +277 -0
- package/tests/immutablelog.test.ts +157 -0
- package/tests/inboundguard.test.ts +177 -0
- package/tests/lazyload.test.ts +72 -0
- package/tests/librarymodes.test.ts +623 -0
- package/tests/llm.test.ts +696 -0
- package/tests/manifest.test.ts +226 -0
- package/tests/maskinputs.test.ts +150 -0
- package/tests/matrixverify.test.ts +111 -0
- package/tests/mavenpack.test.ts +75 -0
- package/tests/mcpserver.test.ts +704 -0
- package/tests/memory.test.ts +4155 -0
- package/tests/memorycare.test.ts +230 -0
- package/tests/migrateplan.test.ts +349 -0
- package/tests/minimization.test.ts +414 -0
- package/tests/modelroute.test.ts +112 -0
- package/tests/multiagent.test.ts +440 -0
- package/tests/nativebridge.test.ts +807 -0
- package/tests/navigation.test.ts +284 -0
- package/tests/nugetpack.test.ts +83 -0
- package/tests/orchestration.test.ts +449 -0
- package/tests/originpolicy.test.ts +405 -0
- package/tests/outputcompare.test.ts +97 -0
- package/tests/package.test.ts +91 -0
- package/tests/pagebridge.test.ts +5909 -0
- package/tests/pentest.test.ts +75 -0
- package/tests/perfrecords.test.ts +257 -0
- package/tests/permdiff.test.ts +85 -0
- package/tests/phishguard.test.ts +128 -0
- package/tests/pickerviews.test.ts +155 -0
- package/tests/pipelines.test.ts +381 -0
- package/tests/planlint.test.ts +240 -0
- package/tests/planreview.test.ts +383 -0
- package/tests/platformtargets.test.ts +56 -0
- package/tests/policy.test.ts +8284 -0
- package/tests/portability.test.ts +96 -0
- package/tests/progress.test.ts +1036 -0
- package/tests/promptlibrary.test.ts +116 -0
- package/tests/protocol.test.ts +4793 -0
- package/tests/publishingpipeline.test.ts +314 -0
- package/tests/quickactions.test.ts +164 -0
- package/tests/readiness.test.ts +78 -0
- package/tests/recipes.test.ts +163 -0
- package/tests/redactshots.test.ts +216 -0
- package/tests/registernvidiakeys.ts +51 -0
- package/tests/relayserve.test.ts +304 -0
- package/tests/releasenotes.test.ts +276 -0
- package/tests/resilience.test.ts +467 -0
- package/tests/resourceaware.test.ts +187 -0
- package/tests/runbudget.test.ts +125 -0
- package/tests/runhistory.test.ts +299 -0
- package/tests/runreplay.test.ts +113 -0
- package/tests/runresume.test.ts +184 -0
- package/tests/runstate.test.ts +288 -0
- package/tests/runtimeadapters.test.ts +251 -0
- package/tests/safariskeleton.test.ts +115 -0
- package/tests/sandboxframe.test.ts +196 -0
- package/tests/sbom.test.ts +105 -0
- package/tests/secretvault.test.ts +188 -0
- package/tests/selcache.test.ts +111 -0
- package/tests/sessioninterface.test.ts +791 -0
- package/tests/sessions.test.ts +591 -0
- package/tests/siteprefs.test.ts +228 -0
- package/tests/snapshotdelta.test.ts +165 -0
- package/tests/statusviews.test.ts +237 -0
- package/tests/stepmeter.test.ts +200 -0
- package/tests/streamparse.test.ts +163 -0
- package/tests/stylepruning.test.ts +90 -0
- package/tests/surfaces.test.ts +282 -0
- package/tests/sweep.test.ts +78 -0
- package/tests/syncbridge.test.ts +164 -0
- package/tests/taskqueue.test.ts +206 -0
- package/tests/toolcalls.test.ts +336 -0
- package/tests/toolcatalog.test.ts +201 -0
- package/tests/torture-agent.test.ts +706 -0
- package/tests/torture-auth.test.ts +461 -0
- package/tests/torture-commands.test.ts +895 -0
- package/tests/torture-data.test.ts +923 -0
- package/tests/torture-environments.test.ts +285 -0
- package/tests/torture-export.test.ts +263 -0
- package/tests/torture-http.test.ts +1379 -0
- package/tests/torture-llm.test.ts +808 -0
- package/tests/torture-memory.test.ts +1084 -0
- package/tests/torture-plan.test.ts +829 -0
- package/tests/torture-run.test.ts +626 -0
- package/tests/torture-session.test.ts +928 -0
- package/tests/torture-swarm.test.ts +1245 -0
- package/tests/torture-tools.test.ts +964 -0
- package/tests/torture-workflow.test.ts +2354 -0
- package/tests/tourviews.test.ts +52 -0
- package/tests/transparency.test.ts +147 -0
- package/tests/trigger.test.ts +941 -0
- package/tests/v1sunset.test.ts +428 -0
- package/tests/virtlist.test.ts +94 -0
- package/tests/vision.test.ts +540 -0
- package/tests/vsixpack.test.ts +145 -0
- package/tests/webapi.test.ts +523 -0
- package/tests/workflow.test.ts +1785 -0
- package/tests/workfloweditor.test.ts +759 -0
- package/tests/xpipack.test.ts +128 -0
- package/tools.ts +734 -0
- package/types.ts +9908 -0
- package/umd.ts +9 -0
- package/utils.ts +249 -0
- package/version.ts +8 -0
- package/versionregistry.ts +1310 -0
- package/views.ts +2153 -0
- package/vitest.config.ts +12 -0
- package/web/App.tsx +78 -0
- package/web/README.md +47 -0
- package/web/capacitor.config.ts +46 -0
- package/web/const.ts +19 -0
- package/web/control.shell.tsx +66 -0
- package/web/dashboardpage.ts +554 -0
- package/web/db.ts +493 -0
- package/web/design.html +541 -0
- package/web/gatewayview/Gateway.tsx +345 -0
- package/web/gatewayview/config.ts +1042 -0
- package/web/gatewayview/definition.ts +174 -0
- package/web/icons.ts +15 -0
- package/{manifest.json → web/manifest.json} +1 -1
- package/web/optionspage.ts +691 -0
- package/web/popup.ts +1651 -0
- package/web/schema.prisma +176 -0
- package/web/sidepanel.ts +7941 -0
- package/web/sitemanifest.json +13 -0
- package/web/terminal-ui.tsx +622 -0
- package/web/transparencypage.ts +541 -0
- package/web/vite.config.ts +39 -0
- package/web/workspace.ts +23 -0
- package/workbenchmemory.ts +104 -0
- package/workbenchsession.ts +304 -0
- package/workflow.ts +4495 -0
- package/bridge.d.ts.map +0 -1
- package/bridge.js +0 -1877
- package/bun.d.ts.map +0 -1
- package/bun.js +0 -52329
- package/caps/background.json +0 -712
- package/caps/cli.json +0 -360
- package/caps/library.json +0 -3611
- package/caps/mcp.json +0 -126
- package/caps/pagebridge.json +0 -377
- package/caps/popup.json +0 -51
- package/caps/sidepanel.json +0 -522
- package/checksums.txt +0 -142
- package/cli.d.ts.map +0 -1
- package/companion.d.ts.map +0 -1
- package/companion.js +0 -389
- package/crossbrowser.d.ts.map +0 -1
- package/crossbrowser.js +0 -1191
- package/dashdone.d.ts.map +0 -1
- package/dashdone.js +0 -368
- package/deno.d.ts.map +0 -1
- package/deno.js +0 -52325
- package/devthink.umd.js +0 -52346
- package/fixtures/bun.ts +0 -7
- package/fixtures/cjs.js +0 -6
- package/fixtures/deno.ts +0 -6
- package/fixtures/example-org-pagestate.json +0 -22
- package/fixtures/importers/automa-workflow.json +0 -18
- package/fixtures/importers/selenium-side.json +0 -20
- package/fixtures/importers/tabular-plan.csv +0 -7
- package/fixtures/importers/uivision-macro.json +0 -10
- package/fixtures/importers/v1-plan.json +0 -13
- package/fixtures/mcp-client.mjs +0 -14
- package/fixtures/pages/checkout.html +0 -192
- package/fixtures/pages/dashboard.html +0 -110
- package/fixtures/pages/feed.html +0 -113
- package/fixtures/pages/product-grid.html +0 -150
- package/fixtures/plans/form-inventory-plan.json +0 -11
- package/fixtures/plans/release-notes-plan.json +0 -11
- package/fixtures/recipes/agentscompete.json +0 -11
- package/fixtures/recipes/agentsforms.json +0 -11
- package/fixtures/recipes/agentsmonitor.json +0 -11
- package/fixtures/recipes/agentsreview.json +0 -29
- package/fixtures/recipes/agentswarm.json +0 -11
- package/fixtures/recipes/fillcalendar.json +0 -12
- package/fixtures/recipes/fillcheckout.json +0 -13
- package/fixtures/recipes/filldependentdropdowns.json +0 -12
- package/fixtures/recipes/fillfromcsv.json +0 -13
- package/fixtures/recipes/fillloginform.json +0 -12
- package/fixtures/recipes/fillmultistepwizard.json +0 -13
- package/fixtures/recipes/fillregistration.json +0 -12
- package/fixtures/recipes/fillsearchfilters.json +0 -12
- package/fixtures/recipes/filltypeahead.json +0 -11
- package/fixtures/recipes/monitoralerts.json +0 -12
- package/fixtures/recipes/monitoravailability.json +0 -11
- package/fixtures/recipes/monitornetworkquiet.json +0 -12
- package/fixtures/recipes/monitorpagechanges.json +0 -11
- package/fixtures/recipes/monitorpricechange.json +0 -11
- package/fixtures/recipes/monitorschedule.json +0 -12
- package/fixtures/recipes/scrapedynamicprices.json +0 -13
- package/fixtures/recipes/scrapeimagegallery.json +0 -12
- package/fixtures/recipes/scrapeinfinitefeed.json +0 -13
- package/fixtures/recipes/scrapenestedlists.json +0 -12
- package/fixtures/recipes/scrapeproductgrid.json +0 -12
- package/fixtures/recipes/scrapereviews.json +0 -12
- package/fixtures/recipes/scrapesearchresults.json +0 -12
- package/fixtures/recipes/scrapetablepagination.json +0 -12
- package/fixtures/recipes/testaccessibility.json +0 -34
- package/fixtures/recipes/testconsoleerrors.json +0 -12
- package/fixtures/recipes/testdeeplinks.json +0 -12
- package/fixtures/recipes/testformvalidation.json +0 -13
- package/fixtures/recipes/testlayoutshift.json +0 -12
- package/fixtures/recipes/testlinkcheck.json +0 -12
- package/fixtures/recipes/testloadtiming.json +0 -12
- package/fixtures/recipes/testvisualdiff.json +0 -12
- package/fixtures/workflows/changelog-digest-workflow.json +0 -13
- package/fixtures/workflows/form-survey-workflow.json +0 -13
- package/gallery.json +0 -706
- package/hardening.d.ts.map +0 -1
- package/hardening.js +0 -188
- package/headless.d.ts.map +0 -1
- package/headless.js +0 -11522
- package/http.d.ts.map +0 -1
- package/http.js +0 -2165
- package/index.cjs +0 -54682
- package/index.d.ts.map +0 -1
- package/index.js +0 -52310
- package/index.neutral.js +0 -46343
- package/mcp.d.ts.map +0 -1
- package/mcp.js +0 -20515
- package/memory.d.ts.map +0 -1
- package/nativehost.template.json +0 -10
- package/node.cjs +0 -54686
- package/node.d.ts.map +0 -1
- package/pack.d.ts.map +0 -1
- package/pack.js +0 -805
- package/policy.d.ts.map +0 -1
- package/progress.d.ts.map +0 -1
- package/progress.js +0 -645
- package/protocol.d.ts.map +0 -1
- package/protocol.js +0 -8334
- package/schemas/audit.schema.json +0 -226
- package/schemas/capability.schema.json +0 -113
- package/schemas/envelope.schema.json +0 -162
- package/schemas/memory.schema.json +0 -51
- package/schemas/observation.schema.json +0 -113
- package/schemas/plan.schema.json +0 -208
- package/schemas/progress.schema.json +0 -126
- package/schemas/proposal.schema.json +0 -68
- package/schemas/session.schema.json +0 -82
- package/schemas/tool.schema.json +0 -94
- package/server.d.ts.map +0 -1
- package/server.js +0 -28877
- package/umd-example.html +0 -18
- package/umd.d.ts.map +0 -1
- /package/{bun.d.ts → dist/bun.d.ts} +0 -0
- /package/{cli.d.ts → dist/cli.d.ts} +0 -0
- /package/{companion.d.ts → dist/companion.d.ts} +0 -0
- /package/{crossbrowser.d.ts → dist/crossbrowser.d.ts} +0 -0
- /package/{dashdone.d.ts → dist/dashdone.d.ts} +0 -0
- /package/{deno.d.ts → dist/deno.d.ts} +0 -0
- /package/{hardening.d.ts → dist/hardening.d.ts} +0 -0
- /package/{headless.d.ts → dist/headless.d.ts} +0 -0
- /package/{http.d.ts → dist/http.d.ts} +0 -0
- /package/{index.d.ts → dist/index.d.ts} +0 -0
- /package/{mcp.d.ts → dist/mcp.d.ts} +0 -0
- /package/{memory.d.ts → dist/memory.d.ts} +0 -0
- /package/{node.d.ts → dist/node.d.ts} +0 -0
- /package/{pack.d.ts → dist/pack.d.ts} +0 -0
- /package/{policy.d.ts → dist/policy.d.ts} +0 -0
- /package/{progress.d.ts → dist/progress.d.ts} +0 -0
- /package/{protocol.d.ts → dist/protocol.d.ts} +0 -0
- /package/{server.d.ts → dist/server.d.ts} +0 -0
- /package/{umd.d.ts → dist/umd.d.ts} +0 -0
|
@@ -0,0 +1,417 @@
|
|
|
1
|
+
import { afterEach, describe, it } from "vitest";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { existsSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
import { createMemoryStore } from "../workbenchmemory.js";
|
|
7
|
+
import {
|
|
8
|
+
migrateLegacyCredentials,
|
|
9
|
+
readAuth,
|
|
10
|
+
readConfig,
|
|
11
|
+
redactAuth,
|
|
12
|
+
redactConfig,
|
|
13
|
+
resolveCredential,
|
|
14
|
+
resolvePaths,
|
|
15
|
+
saveConfig,
|
|
16
|
+
setAuthCredential,
|
|
17
|
+
type DevThinkConfig,
|
|
18
|
+
} from "../config.js";
|
|
19
|
+
import { listModes } from "../modes.js";
|
|
20
|
+
import { parseEventStream, type ChatEvent } from "../streaming.js";
|
|
21
|
+
import { startServer, type ServerHandle } from "../server.js";
|
|
22
|
+
import { appendMessage, createSession, createTab, loadWorkspace } from "../workbenchsession.js";
|
|
23
|
+
import { createPairing, createPairingLink, getIdentity, setIdentityUserId } from "../identity.js";
|
|
24
|
+
import { isCompactId } from "../ids.js";
|
|
25
|
+
import {
|
|
26
|
+
isAllowedOrigin,
|
|
27
|
+
isSecureRemoteEndpoint,
|
|
28
|
+
normalizeBasePath,
|
|
29
|
+
redactProviderError,
|
|
30
|
+
retryDelay,
|
|
31
|
+
} from "../compatibility.js";
|
|
32
|
+
|
|
33
|
+
const temporary: string[] = [];
|
|
34
|
+
const servers: ServerHandle[] = [];
|
|
35
|
+
|
|
36
|
+
afterEach(async () => {
|
|
37
|
+
for (const server of servers.splice(0)) await server.stop();
|
|
38
|
+
for (const path of temporary.splice(0)) rmSync(path, { recursive: true, force: true });
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
function streamResponse(payload: string): Response {
|
|
42
|
+
const body = new ReadableStream<Uint8Array>({
|
|
43
|
+
start(controller) {
|
|
44
|
+
controller.enqueue(new TextEncoder().encode(payload));
|
|
45
|
+
controller.close();
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
return new Response(body, { headers: { "content-type": "text/event-stream" } });
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async function eventsFrom(response: Response, provider: string): Promise<ChatEvent[]> {
|
|
52
|
+
const events: ChatEvent[] = [];
|
|
53
|
+
for await (const event of parseEventStream(response, { provider, model: "test-model" })) events.push(event);
|
|
54
|
+
return events;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
describe("stream normalization", () => {
|
|
58
|
+
it("normalizes OpenAI-shaped text deltas", async () => {
|
|
59
|
+
const events = await eventsFrom(
|
|
60
|
+
streamResponse('data: {"choices":[{"delta":{"content":"hello"}}]}\n\ndata: [DONE]\n\n'),
|
|
61
|
+
"openai",
|
|
62
|
+
);
|
|
63
|
+
assert.equal(
|
|
64
|
+
events.some((event) => event.type === "text" && event.text === "hello"),
|
|
65
|
+
true,
|
|
66
|
+
);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it("normalizes Anthropic named events", async () => {
|
|
70
|
+
const events = await eventsFrom(
|
|
71
|
+
streamResponse(
|
|
72
|
+
'event: content_block_delta\ndata: {"type":"content_block_delta","delta":{"type":"text_delta","text":"hello"}}\n\nevent: message_stop\ndata: {"type":"message_stop"}\n\n',
|
|
73
|
+
),
|
|
74
|
+
"anthropic",
|
|
75
|
+
);
|
|
76
|
+
assert.equal(
|
|
77
|
+
events.some((event) => event.type === "text" && event.text === "hello"),
|
|
78
|
+
true,
|
|
79
|
+
);
|
|
80
|
+
assert.equal(
|
|
81
|
+
events.some((event) => event.type === "finish"),
|
|
82
|
+
true,
|
|
83
|
+
);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it("normalizes Gemini candidates", async () => {
|
|
87
|
+
const events = await eventsFrom(
|
|
88
|
+
streamResponse('data: {"candidates":[{"content":{"parts":[{"text":"hello"}]}}]}\n\n'),
|
|
89
|
+
"google",
|
|
90
|
+
);
|
|
91
|
+
assert.equal(
|
|
92
|
+
events.some((event) => event.type === "text" && event.text === "hello"),
|
|
93
|
+
true,
|
|
94
|
+
);
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
describe("local memory", () => {
|
|
99
|
+
it("resolves session before project and global memory", () => {
|
|
100
|
+
const root = mkdtempSync(join(tmpdir(), "devthink-test-"));
|
|
101
|
+
temporary.push(root);
|
|
102
|
+
const paths = resolvePaths(root);
|
|
103
|
+
const store = createMemoryStore(paths);
|
|
104
|
+
store.remember({ layer: "global", key: "topic", content: "global" });
|
|
105
|
+
store.remember({ layer: "project", key: "topic", content: "project" });
|
|
106
|
+
store.remember({ layer: "session", key: "session-1", content: "session topic" });
|
|
107
|
+
assert.equal(store.resolve({ text: "topic", sessionId: "session-1" })?.content, "session topic");
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
describe("configuration and modes", () => {
|
|
112
|
+
it("redacts configured credentials", () => {
|
|
113
|
+
const config: DevThinkConfig = { provider: "openai", apiKey: "secret-value" };
|
|
114
|
+
assert.equal(String(redactConfig(config).apiKey).includes("secret-value"), false);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it("registers all 20 modes", () => {
|
|
118
|
+
assert.equal(listModes().length, 20);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it("redacts nested provider secrets", () => {
|
|
122
|
+
const config: DevThinkConfig = {
|
|
123
|
+
providers: { zai: { apiKey: "nested-secret-value", auth: { kind: "bearer", value: "nested-token-value" } } },
|
|
124
|
+
};
|
|
125
|
+
const redacted = JSON.stringify(redactConfig(config));
|
|
126
|
+
assert.equal(redacted.includes("nested-secret-value"), false);
|
|
127
|
+
assert.equal(redacted.includes("nested-token-value"), false);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it("stores explicit official credentials in auth.json and resolves them before legacy config", () => {
|
|
131
|
+
const root = mkdtempSync(join(tmpdir(), "devthink-auth-"));
|
|
132
|
+
temporary.push(root);
|
|
133
|
+
const paths = resolvePaths(root);
|
|
134
|
+
setAuthCredential("zai", { kind: "api-key", value: "official-user-key" }, paths);
|
|
135
|
+
assert.equal(paths.auth.endsWith("auth.json"), true);
|
|
136
|
+
assert.equal(existsSync(paths.auth), true);
|
|
137
|
+
assert.equal(
|
|
138
|
+
resolveCredential("zai", { providers: { zai: { apiKey: "legacy-key" } } }, paths),
|
|
139
|
+
"official-user-key",
|
|
140
|
+
);
|
|
141
|
+
assert.equal(JSON.stringify(redactAuth(readAuth(paths))).includes("official-user-key"), false);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it("rejects browser-session fields in auth.json and migrates legacy credential values explicitly", () => {
|
|
145
|
+
const root = mkdtempSync(join(tmpdir(), "devthink-auth-migrate-"));
|
|
146
|
+
temporary.push(root);
|
|
147
|
+
const paths = resolvePaths(root);
|
|
148
|
+
writeFileSync(
|
|
149
|
+
paths.auth,
|
|
150
|
+
JSON.stringify({
|
|
151
|
+
version: 1,
|
|
152
|
+
providers: { zai: { kind: "bearer", value: "unsafe", cookies: ["browser-session"] } },
|
|
153
|
+
}),
|
|
154
|
+
);
|
|
155
|
+
assert.deepEqual(readAuth(paths).providers, {});
|
|
156
|
+
const legacy: DevThinkConfig = { providers: { zai: { apiKey: "legacy-zai-key" } } };
|
|
157
|
+
const result = migrateLegacyCredentials(legacy, paths);
|
|
158
|
+
assert.deepEqual(result.migrated, ["zai"]);
|
|
159
|
+
assert.equal(readAuth(paths).providers.zai?.value, "legacy-zai-key");
|
|
160
|
+
assert.equal(readConfig(paths).providers?.zai?.apiKey, undefined);
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
describe("converted TypeScript compatibility", () => {
|
|
165
|
+
it("bounds retries and redacts bearer text before output", () => {
|
|
166
|
+
assert.equal(retryDelay(0), 250);
|
|
167
|
+
assert.equal(retryDelay(20), 4_000);
|
|
168
|
+
assert.equal(
|
|
169
|
+
redactProviderError("upstream Bearer private-token-value failed").includes("private-token-value"),
|
|
170
|
+
false,
|
|
171
|
+
);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it("requires an explicit origin and a secure remote endpoint", () => {
|
|
175
|
+
assert.equal(isAllowedOrigin("https://pages.example", ["https://pages.example"]), true);
|
|
176
|
+
assert.equal(isAllowedOrigin("https://other.example", ["https://pages.example"]), false);
|
|
177
|
+
assert.equal(isSecureRemoteEndpoint("https://sync.example/api"), true);
|
|
178
|
+
assert.equal(isSecureRemoteEndpoint("http://sync.example/api"), false);
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it("normalizes repository-scoped Pages base paths", () => {
|
|
182
|
+
assert.equal(normalizeBasePath(undefined, "Devthink"), "/Devthink/");
|
|
183
|
+
assert.equal(normalizeBasePath("Devthink", "ignored"), "/Devthink/");
|
|
184
|
+
});
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
describe("embedded gateway configuration", () => {
|
|
188
|
+
it("uses devthink.json and atomically preserves nested provider data", () => {
|
|
189
|
+
const root = mkdtempSync(join(tmpdir(), "devthink-config-"));
|
|
190
|
+
temporary.push(root);
|
|
191
|
+
const paths = resolvePaths(root);
|
|
192
|
+
const config: DevThinkConfig = {
|
|
193
|
+
activeProvider: "zai",
|
|
194
|
+
activeModel: "glm-5",
|
|
195
|
+
providers: { zai: { baseUrl: "https://gateway.example/v1", auth: { kind: "api-key", value: "secret" } } },
|
|
196
|
+
};
|
|
197
|
+
saveConfig(config, paths);
|
|
198
|
+
assert.equal(paths.config.endsWith("devthink.json"), true);
|
|
199
|
+
assert.equal(existsSync(paths.config), true);
|
|
200
|
+
assert.equal(readConfig(paths).providers?.zai?.baseUrl, "https://gateway.example/v1");
|
|
201
|
+
});
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
describe("shared local identity", () => {
|
|
205
|
+
it("persists one workspace, session, tab and message contract into JSON and SQLite", () => {
|
|
206
|
+
const root = mkdtempSync(join(tmpdir(), "devthink-identity-"));
|
|
207
|
+
temporary.push(root);
|
|
208
|
+
const paths = resolvePaths(root);
|
|
209
|
+
const session = createSession(paths, { mode: "chat", provider: "zai", model: "glm-5" });
|
|
210
|
+
const withTab = createTab(paths, session, { label: "route review", sectionId: "inspector" });
|
|
211
|
+
const persisted = appendMessage(
|
|
212
|
+
paths,
|
|
213
|
+
withTab,
|
|
214
|
+
{ role: "user", content: "Preserve this ID." },
|
|
215
|
+
{ tabId: withTab.activeTabId, sectionId: "inspector" },
|
|
216
|
+
);
|
|
217
|
+
assert.equal(isCompactId(persisted.workspaceId), true);
|
|
218
|
+
assert.equal(isCompactId(persisted.id), true);
|
|
219
|
+
assert.equal(isCompactId(persisted.activeTabId), true);
|
|
220
|
+
assert.equal(isCompactId(persisted.messages[0].id), true);
|
|
221
|
+
assert.equal(
|
|
222
|
+
persisted.tabs.some((tab) => tab.id === persisted.activeTabId),
|
|
223
|
+
true,
|
|
224
|
+
);
|
|
225
|
+
assert.equal(persisted.messages[0].sessionId, persisted.id);
|
|
226
|
+
assert.equal(persisted.messages[0].workspaceId, persisted.workspaceId);
|
|
227
|
+
assert.equal(persisted.messages[0].tabId, persisted.activeTabId);
|
|
228
|
+
assert.equal(loadWorkspace(paths, persisted.workspaceId)?.id, persisted.workspaceId);
|
|
229
|
+
assert.equal(existsSync(paths.database), true);
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
it("creates one stable public identity with a temporary pairing code", () => {
|
|
233
|
+
const root = mkdtempSync(join(tmpdir(), "devthink-pairing-"));
|
|
234
|
+
temporary.push(root);
|
|
235
|
+
const paths = resolvePaths(root);
|
|
236
|
+
const identity = getIdentity(paths);
|
|
237
|
+
const pairing = createPairing(paths);
|
|
238
|
+
assert.equal(pairing.identity.userId, identity.userId);
|
|
239
|
+
assert.equal(isCompactId(identity.userId), true);
|
|
240
|
+
assert.equal(isCompactId(identity.deviceId), true);
|
|
241
|
+
assert.equal(isCompactId(pairing.pairingId), true);
|
|
242
|
+
assert.equal(pairing.code.length, 8);
|
|
243
|
+
assert.equal(existsSync(paths.identity), true);
|
|
244
|
+
assert.equal(existsSync(paths.pairings), true);
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
it("keeps a user-selected public ID across CLI identity and pairing records", () => {
|
|
248
|
+
const root = mkdtempSync(join(tmpdir(), "devthink-public-id-"));
|
|
249
|
+
temporary.push(root);
|
|
250
|
+
const paths = resolvePaths(root);
|
|
251
|
+
const updated = setIdentityUserId(paths, "devthinkuser13");
|
|
252
|
+
const pairing = createPairing(paths);
|
|
253
|
+
assert.equal(updated.userId, "devthinkuser13");
|
|
254
|
+
assert.equal(getIdentity(paths).userId, "devthinkuser13");
|
|
255
|
+
assert.equal(pairing.identity.userId, "devthinkuser13");
|
|
256
|
+
assert.throws(() => setIdentityUserId(paths, "short"));
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
it("builds a temporary invitation link without provider credentials", () => {
|
|
260
|
+
const link = createPairingLink(
|
|
261
|
+
"https://wenathlan.github.io/devthink/",
|
|
262
|
+
"http://127.0.0.1:42042",
|
|
263
|
+
"pair_example",
|
|
264
|
+
"ABCDEFGH",
|
|
265
|
+
);
|
|
266
|
+
assert.equal(link?.includes("pair=pair_example"), true);
|
|
267
|
+
assert.equal(link?.includes("code=ABCDEFGH"), true);
|
|
268
|
+
assert.equal(link?.includes("gateway=http%3A%2F%2F127.0.0.1%3A42042"), true);
|
|
269
|
+
assert.equal(link?.includes("token="), false);
|
|
270
|
+
assert.equal(createPairingLink("not-a-url", "http://127.0.0.1:42042", "pair", "ABCDEFGH"), undefined);
|
|
271
|
+
});
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
describe("local server", () => {
|
|
275
|
+
it("serves health on loopback", async () => {
|
|
276
|
+
const server = await startServer({ config: {} });
|
|
277
|
+
servers.push(server);
|
|
278
|
+
const response = await fetch(`${server.address}/health`);
|
|
279
|
+
assert.equal(response.status, 200);
|
|
280
|
+
assert.deepEqual(await response.json(), { status: "ok", service: "devthink" });
|
|
281
|
+
assert.equal(server.address.startsWith("http://127.0.0.1:"), true);
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
it("uses identical IDs through the workspace, session and tab gateway routes", async () => {
|
|
285
|
+
const root = mkdtempSync(join(tmpdir(), "devthink-gateway-"));
|
|
286
|
+
temporary.push(root);
|
|
287
|
+
const paths = resolvePaths(root);
|
|
288
|
+
const server = await startServer({ config: {}, paths });
|
|
289
|
+
servers.push(server);
|
|
290
|
+
const created = await fetch(`${server.address}/sessions`, {
|
|
291
|
+
method: "POST",
|
|
292
|
+
headers: { "content-type": "application/json" },
|
|
293
|
+
body: JSON.stringify({ mode: "chat", provider: "mimo", model: "mimo-v2.5-pro", sectionId: "chat" }),
|
|
294
|
+
});
|
|
295
|
+
assert.equal(created.status, 201);
|
|
296
|
+
const session = (await created.json()) as { id: string; workspaceId: string; activeTabId: string };
|
|
297
|
+
const read = await fetch(`${server.address}/sessions/${session.id}`);
|
|
298
|
+
assert.equal(read.status, 200);
|
|
299
|
+
const tabResponse = await fetch(`${server.address}/sessions/${session.id}/tabs`, {
|
|
300
|
+
method: "POST",
|
|
301
|
+
headers: { "content-type": "application/json" },
|
|
302
|
+
body: JSON.stringify({ label: "inspector route", sectionId: "inspector" }),
|
|
303
|
+
});
|
|
304
|
+
assert.equal(tabResponse.status, 201);
|
|
305
|
+
const updated = (await tabResponse.json()) as {
|
|
306
|
+
workspaceId: string;
|
|
307
|
+
activeTabId: string;
|
|
308
|
+
tabs: Array<{ id: string; sectionId: string }>;
|
|
309
|
+
};
|
|
310
|
+
assert.equal(updated.workspaceId, session.workspaceId);
|
|
311
|
+
assert.equal(
|
|
312
|
+
updated.tabs.some((tab) => tab.id === updated.activeTabId && tab.sectionId === "inspector"),
|
|
313
|
+
true,
|
|
314
|
+
);
|
|
315
|
+
const workspace = await fetch(`${server.address}/workspaces/${session.workspaceId}`);
|
|
316
|
+
assert.equal(workspace.status, 200);
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
it("lists local projects and usage while allowing a paired browser to set the active provider", async () => {
|
|
320
|
+
const root = mkdtempSync(join(tmpdir(), "devthink-dashboard-"));
|
|
321
|
+
temporary.push(root);
|
|
322
|
+
const paths = resolvePaths(root);
|
|
323
|
+
const origin = "https://pages.example";
|
|
324
|
+
const server = await startServer({ config: { web: { allowedOrigins: [origin] } }, paths });
|
|
325
|
+
servers.push(server);
|
|
326
|
+
const pairing = createPairing(paths);
|
|
327
|
+
const paired = await fetch(`${server.address}/pairings/consume`, {
|
|
328
|
+
method: "POST",
|
|
329
|
+
headers: { origin, "content-type": "application/json" },
|
|
330
|
+
body: JSON.stringify({ pairingId: pairing.pairingId, code: pairing.code }),
|
|
331
|
+
});
|
|
332
|
+
const { token } = (await paired.json()) as { token: string };
|
|
333
|
+
const headers = { origin, authorization: `Bearer ${token}`, "content-type": "application/json" };
|
|
334
|
+
const created = await fetch(`${server.address}/sessions`, {
|
|
335
|
+
method: "POST",
|
|
336
|
+
headers,
|
|
337
|
+
body: JSON.stringify({ mode: "chat", provider: "zai", model: "glm-5" }),
|
|
338
|
+
});
|
|
339
|
+
assert.equal(created.status, 201);
|
|
340
|
+
const projects = await fetch(`${server.address}/workspaces`, { headers });
|
|
341
|
+
assert.equal(((await projects.json()) as { workspaces: unknown[] }).workspaces.length, 1);
|
|
342
|
+
const usage = await fetch(`${server.address}/usage`, { headers });
|
|
343
|
+
assert.equal(((await usage.json()) as { sessions: number }).sessions, 1);
|
|
344
|
+
const settings = await fetch(`${server.address}/settings`, { headers });
|
|
345
|
+
assert.equal(settings.status, 200);
|
|
346
|
+
assert.equal(
|
|
347
|
+
((await settings.json()) as { database: { local: boolean; ownerUserId: string } }).database.local,
|
|
348
|
+
true,
|
|
349
|
+
);
|
|
350
|
+
const provider = await fetch(`${server.address}/providers/active`, {
|
|
351
|
+
method: "PATCH",
|
|
352
|
+
headers,
|
|
353
|
+
body: JSON.stringify({ provider: "zai", model: "glm-5" }),
|
|
354
|
+
});
|
|
355
|
+
assert.equal(provider.status, 200);
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
it("consumes a web pairing once and revokes browser access", async () => {
|
|
359
|
+
const root = mkdtempSync(join(tmpdir(), "devthink-web-pairing-"));
|
|
360
|
+
temporary.push(root);
|
|
361
|
+
const paths = resolvePaths(root);
|
|
362
|
+
const origin = "https://pages.example";
|
|
363
|
+
const server = await startServer({ config: { web: { allowedOrigins: [origin] } }, paths });
|
|
364
|
+
servers.push(server);
|
|
365
|
+
const pairing = createPairing(paths);
|
|
366
|
+
const consumed = await fetch(`${server.address}/pairings/consume`, {
|
|
367
|
+
method: "POST",
|
|
368
|
+
headers: { origin, "content-type": "application/json" },
|
|
369
|
+
body: JSON.stringify({ pairingId: pairing.pairingId, code: pairing.code }),
|
|
370
|
+
});
|
|
371
|
+
assert.equal(consumed.status, 201);
|
|
372
|
+
const credential = (await consumed.json()) as { token: string; userId: string };
|
|
373
|
+
const identity = await fetch(`${server.address}/identity`, {
|
|
374
|
+
headers: { origin, authorization: `Bearer ${credential.token}` },
|
|
375
|
+
});
|
|
376
|
+
assert.equal(identity.status, 200);
|
|
377
|
+
assert.equal(((await identity.json()) as { identity: { userId: string } }).identity.userId, credential.userId);
|
|
378
|
+
const selectedIdentity = await fetch(`${server.address}/identity`, {
|
|
379
|
+
method: "PUT",
|
|
380
|
+
headers: { origin, authorization: `Bearer ${credential.token}`, "content-type": "application/json" },
|
|
381
|
+
body: JSON.stringify({ userId: "devthinkuser13" }),
|
|
382
|
+
});
|
|
383
|
+
assert.equal(selectedIdentity.status, 200);
|
|
384
|
+
assert.equal(
|
|
385
|
+
((await selectedIdentity.json()) as { identity: { userId: string } }).identity.userId,
|
|
386
|
+
"devthinkuser13",
|
|
387
|
+
);
|
|
388
|
+
const created = await fetch(`${server.address}/sessions`, {
|
|
389
|
+
method: "POST",
|
|
390
|
+
headers: { origin, authorization: `Bearer ${credential.token}`, "content-type": "application/json" },
|
|
391
|
+
body: JSON.stringify({ mode: "chat", provider: "zai", model: "glm-5" }),
|
|
392
|
+
});
|
|
393
|
+
assert.equal(created.status, 201);
|
|
394
|
+
const expiredPair = createPairing(paths, -1);
|
|
395
|
+
const expiredCode = await fetch(`${server.address}/pairings/consume`, {
|
|
396
|
+
method: "POST",
|
|
397
|
+
headers: { origin, "content-type": "application/json" },
|
|
398
|
+
body: JSON.stringify({ pairingId: expiredPair.pairingId, code: expiredPair.code }),
|
|
399
|
+
});
|
|
400
|
+
assert.equal(expiredCode.status, 401);
|
|
401
|
+
const replay = await fetch(`${server.address}/pairings/consume`, {
|
|
402
|
+
method: "POST",
|
|
403
|
+
headers: { origin, "content-type": "application/json" },
|
|
404
|
+
body: JSON.stringify({ pairingId: pairing.pairingId, code: pairing.code }),
|
|
405
|
+
});
|
|
406
|
+
assert.equal(replay.status, 401);
|
|
407
|
+
const revoke = await fetch(`${server.address}/pairings/revoke`, {
|
|
408
|
+
method: "POST",
|
|
409
|
+
headers: { origin, authorization: `Bearer ${credential.token}` },
|
|
410
|
+
});
|
|
411
|
+
assert.equal(revoke.status, 200);
|
|
412
|
+
const expired = await fetch(`${server.address}/identity`, {
|
|
413
|
+
headers: { origin, authorization: `Bearer ${credential.token}` },
|
|
414
|
+
});
|
|
415
|
+
assert.equal(expired.status, 401);
|
|
416
|
+
});
|
|
417
|
+
});
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { execFile } from "node:child_process";
|
|
3
|
+
import { existsSync } from "node:fs";
|
|
4
|
+
import { readFile } from "node:fs/promises";
|
|
5
|
+
import { promisify } from "node:util";
|
|
6
|
+
|
|
7
|
+
const execute = promisify(execFile);
|
|
8
|
+
|
|
9
|
+
/* ── The 1.1.96 shared accounting certification coverage. ── */
|
|
10
|
+
|
|
11
|
+
describe("the costcert reconciliation artifact", () => {
|
|
12
|
+
it("declares every accounting check of the release candidate reconciliation with its family and module", async () => {
|
|
13
|
+
const source = await readFile("tests/costcert.mjs", "utf8");
|
|
14
|
+
const declarations = [
|
|
15
|
+
...source.matchAll(/await entry\(\{ id: (\d+), title: "([^"]+)", family: "([^"]+)", module: "([^"]+)" \}/g),
|
|
16
|
+
].map((match) => ({ id: Number(match[1]), title: match[2], family: match[3], module: match[4] }));
|
|
17
|
+
expect(declarations).toHaveLength(9);
|
|
18
|
+
expect(declarations.map((entry) => entry.id)).toEqual(Array.from({ length: 9 }, (_, index) => index + 1));
|
|
19
|
+
const families = new Set(declarations.map((entry) => entry.family));
|
|
20
|
+
for (const family of [
|
|
21
|
+
"replay",
|
|
22
|
+
"token accounting",
|
|
23
|
+
"shared accounting",
|
|
24
|
+
"budgets",
|
|
25
|
+
"refunds",
|
|
26
|
+
"routing",
|
|
27
|
+
"export",
|
|
28
|
+
"audit trail",
|
|
29
|
+
"report",
|
|
30
|
+
])
|
|
31
|
+
expect(families.has(family)).toBe(true);
|
|
32
|
+
for (const entry of declarations) expect((entry.title ?? "").length).toBeGreaterThan(10);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it("covers every accounting check with a passing outcome once the gate has run", async () => {
|
|
36
|
+
const artifactpath = "tests/artifacts/costcert.json";
|
|
37
|
+
const built = existsSync("dist/extension/manifest.json");
|
|
38
|
+
const packagejson = JSON.parse(await readFile("package.json", "utf8")) as { version: string };
|
|
39
|
+
/* a built tree runs the gate itself when no fresh artifact of this release exists, so every lane that builds before the suite — the bun lane, the deno lane, a local two-pass validate — verifies the reconciliation artifact without depending on the gate having run before the tests */
|
|
40
|
+
if (built && !existsSync(artifactpath)) await execute("node", ["tests/costcert.mjs"]);
|
|
41
|
+
if (built && existsSync(artifactpath)) {
|
|
42
|
+
const stored = JSON.parse(await readFile(artifactpath, "utf8")) as { release: string };
|
|
43
|
+
if (stored.release !== packagejson.version) await execute("node", ["tests/costcert.mjs"]);
|
|
44
|
+
}
|
|
45
|
+
const stored = existsSync(artifactpath);
|
|
46
|
+
if (built) expect(stored).toBe(true);
|
|
47
|
+
if (!stored) return;
|
|
48
|
+
const report = JSON.parse(await readFile(artifactpath, "utf8")) as {
|
|
49
|
+
release: string;
|
|
50
|
+
entries: Array<{ id: number; title: string; outcome: string; detail: string }>;
|
|
51
|
+
summary: { total: number; passed: number; failed: number };
|
|
52
|
+
};
|
|
53
|
+
expect(report.release).toBe(packagejson.version);
|
|
54
|
+
expect(report.summary.total).toBe(9);
|
|
55
|
+
expect(report.summary.failed).toBe(0);
|
|
56
|
+
expect(report.summary.passed).toBe(9);
|
|
57
|
+
expect(report.entries.map((entry) => entry.id)).toEqual(Array.from({ length: 9 }, (_, index) => index + 1));
|
|
58
|
+
for (const entry of report.entries) {
|
|
59
|
+
expect(entry.outcome).toBe("pass");
|
|
60
|
+
expect(entry.detail.length).toBeGreaterThan(10);
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it("keeps the reconciliation invariants of the artifact summary and the recorded checks", async () => {
|
|
65
|
+
const artifactpath = "tests/artifacts/costcert.json";
|
|
66
|
+
if (!existsSync(artifactpath)) return;
|
|
67
|
+
const report = JSON.parse(await readFile(artifactpath, "utf8")) as {
|
|
68
|
+
release: string;
|
|
69
|
+
entries: Array<{ id: number; outcome: string }>;
|
|
70
|
+
summary: { total: number; passed: number; failed: number };
|
|
71
|
+
};
|
|
72
|
+
/* the reconciliation invariants: the summary total equals the recorded check count, the passed count equals the passing outcomes, the failed count equals the failing outcomes and the totals sum back to the whole */
|
|
73
|
+
const passing = report.entries.filter((entry) => entry.outcome === "pass").length;
|
|
74
|
+
const failing = report.entries.filter((entry) => entry.outcome === "fail").length;
|
|
75
|
+
expect(report.summary.total).toBe(report.entries.length);
|
|
76
|
+
expect(report.summary.passed).toBe(passing);
|
|
77
|
+
expect(report.summary.failed).toBe(failing);
|
|
78
|
+
expect(report.summary.total).toBe(report.summary.passed + report.summary.failed);
|
|
79
|
+
expect(report.summary.failed).toBe(0);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it("exercises the real compiled accounting modules and recomputes the cost lines from recorded evidence", async () => {
|
|
83
|
+
const source = await readFile("tests/costcert.mjs", "utf8");
|
|
84
|
+
expect(source).toContain('import("../dist/index.js")');
|
|
85
|
+
expect(source).toContain('import("../dist/memory.js")');
|
|
86
|
+
expect(source).toContain("usagetotals");
|
|
87
|
+
expect(source).toContain("recordagentusage");
|
|
88
|
+
expect(source).toContain("swarmcosts");
|
|
89
|
+
expect(source).toContain("sharedcostsplit");
|
|
90
|
+
/* the recorded evidence replays through the real sessionmemory store behind the in-memory adapter seam and every fixture time flows from the fixed epoch, so reruns stay byte identical */
|
|
91
|
+
expect(source).toContain("fakeadapter");
|
|
92
|
+
expect(source).toContain("1_800_000_000_000");
|
|
93
|
+
expect(source).toContain("process.exitCode = 1");
|
|
94
|
+
});
|
|
95
|
+
});
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { apimapentries, apimapunmapped } from "../crossbrowser.js";
|
|
3
|
+
import { firefoxprepoverlay, firefoxprepadapt } from "../crossbrowser.js";
|
|
4
|
+
import { xpipackassemble } from "../crossbrowser.js";
|
|
5
|
+
import { apimapentries as firefoxentries } from "../crossbrowser.js";
|
|
6
|
+
|
|
7
|
+
const sourcesmanifest = {
|
|
8
|
+
manifest_version: 3,
|
|
9
|
+
name: "Devthink",
|
|
10
|
+
version: "1.1.86",
|
|
11
|
+
description: "test",
|
|
12
|
+
permissions: ["activeTab", "storage", "scripting", "sidePanel"],
|
|
13
|
+
optional_permissions: ["tabs", "downloads", "clipboardRead", "clipboardWrite", "offscreen", "nativeMessaging"],
|
|
14
|
+
optional_host_permissions: ["https://*/*"],
|
|
15
|
+
background: { service_worker: "background.js", type: "module" },
|
|
16
|
+
action: { default_popup: "popup.html" },
|
|
17
|
+
side_panel: { default_path: "sidepanel.html" },
|
|
18
|
+
options_ui: { page: "optionspage.html", open_in_tab: true },
|
|
19
|
+
offscreen: { document: "offscreen.html", reasons: ["DOM_PARSER", "WORKERS"], justification: "test" },
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
describe("cross browser invariants", () => {
|
|
23
|
+
it("keeps the kind catalog identical on every browser", () => {
|
|
24
|
+
const chromium = apimapentries();
|
|
25
|
+
const firefox = firefoxentries();
|
|
26
|
+
expect(chromium.length).toBe(firefox.length);
|
|
27
|
+
for (let index = 0; index < chromium.length; index += 1) {
|
|
28
|
+
const left = chromium[index];
|
|
29
|
+
const right = firefox[index];
|
|
30
|
+
expect(left?.api).toBe(right?.api);
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("keeps the policy gates identical on every browser through the deny list", () => {
|
|
35
|
+
const overlay = firefoxprepoverlay({ extensionid: "devthink@wenathlan", backgroundscript: "background.js" });
|
|
36
|
+
const adapted = firefoxprepadapt({
|
|
37
|
+
manifest: sourcesmanifest as never,
|
|
38
|
+
overlay,
|
|
39
|
+
backgroundscripts: ["background.js"],
|
|
40
|
+
});
|
|
41
|
+
const forbidden = ["debugger", "cookies", "webRequest", "history", "bookmarks", "proxy", "management"];
|
|
42
|
+
for (const permission of forbidden) {
|
|
43
|
+
expect(adapted.manifest.permissions).not.toContain(permission);
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it("keeps the observation schema byte identical across browsers", () => {
|
|
48
|
+
const overlay = firefoxprepoverlay({ extensionid: "devthink@wenathlan", backgroundscript: "background.js" });
|
|
49
|
+
const adapted = firefoxprepadapt({
|
|
50
|
+
manifest: sourcesmanifest as never,
|
|
51
|
+
overlay,
|
|
52
|
+
backgroundscripts: ["background.js"],
|
|
53
|
+
});
|
|
54
|
+
expect(adapted.manifest.manifest_version).toBe(3);
|
|
55
|
+
expect(adapted.manifest.name).toBe("Devthink");
|
|
56
|
+
expect(adapted.manifest.version).toBe(sourcesmanifest.version);
|
|
57
|
+
expect(adapted.manifest.description).toBe(sourcesmanifest.description);
|
|
58
|
+
expect(adapted.manifest.options_ui).toEqual(sourcesmanifest.options_ui);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("keeps the audit trail format identical across browsers", () => {
|
|
62
|
+
const overlay = firefoxprepoverlay({ extensionid: "devthink@wenathlan", backgroundscript: "background.js" });
|
|
63
|
+
const adapted = firefoxprepadapt({
|
|
64
|
+
manifest: sourcesmanifest as never,
|
|
65
|
+
overlay,
|
|
66
|
+
backgroundscripts: ["background.js"],
|
|
67
|
+
});
|
|
68
|
+
expect(adapted.manifest.content_security_policy?.extension_pages).toContain("script-src");
|
|
69
|
+
expect(adapted.manifest.content_security_policy?.extension_pages).toContain("object-src");
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it("extension id per browser feeds the servercontract handshake", () => {
|
|
73
|
+
const overlay = firefoxprepoverlay({ extensionid: "devthink@wenathlan", backgroundscript: "background.js" });
|
|
74
|
+
const adapted = firefoxprepadapt({
|
|
75
|
+
manifest: sourcesmanifest as never,
|
|
76
|
+
overlay,
|
|
77
|
+
backgroundscripts: ["background.js"],
|
|
78
|
+
});
|
|
79
|
+
expect(adapted.manifest.browser_specific_settings?.gecko?.id).toBe("devthink@wenathlan");
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it("native transport stays chromium first and reports unsupported elsewhere", () => {
|
|
83
|
+
const entries = apimapentries();
|
|
84
|
+
const native = entries.find((entry) => entry.api === "nativeMessaging");
|
|
85
|
+
expect(native).toBeDefined();
|
|
86
|
+
expect(native?.chromium).toBe("chrome.runtime.connectNative");
|
|
87
|
+
expect(native?.firefox).toBe("browser.runtime.connectNative");
|
|
88
|
+
expect(native?.safari).toBe("");
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it("version sync stamps the same version into every browser manifest", () => {
|
|
92
|
+
const overlay = firefoxprepoverlay({ extensionid: "devthink@wenathlan", backgroundscript: "background.js" });
|
|
93
|
+
const adapted = firefoxprepadapt({
|
|
94
|
+
manifest: sourcesmanifest as never,
|
|
95
|
+
overlay,
|
|
96
|
+
backgroundscripts: ["background.js"],
|
|
97
|
+
});
|
|
98
|
+
expect(adapted.manifest.version).toBe(sourcesmanifest.version);
|
|
99
|
+
const xpibuilt = xpipackassemble({
|
|
100
|
+
manifest: adapted.manifest as never,
|
|
101
|
+
bundleentries: [],
|
|
102
|
+
version: sourcesmanifest.version,
|
|
103
|
+
});
|
|
104
|
+
expect(xpibuilt.archive.name).toBe(`devthink-${sourcesmanifest.version}.xpi`);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it("apimap reports every recorded api has a chromium and firefox mapping", () => {
|
|
108
|
+
const report = apimapunmapped();
|
|
109
|
+
expect(report.failed).toBe(false);
|
|
110
|
+
for (const row of report.rows) {
|
|
111
|
+
expect(row.chromium).toBe(true);
|
|
112
|
+
expect(row.firefox).toBe(true);
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
});
|