@wenathlan/devthink 2.0.12 → 2.0.14
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 +20 -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,1310 @@
|
|
|
1
|
+
/* ── Merged: the grand merge section ── the correlated antigravity version resolution logics of the merged repository interned here, one surface without duplicate variations ── */
|
|
2
|
+
/**
|
|
3
|
+
* @module versions/registry
|
|
4
|
+
* @description
|
|
5
|
+
* Dynamic Antigravity version resolver of the merged repository
|
|
6
|
+
* (merged superset of the two prior variants).
|
|
7
|
+
*
|
|
8
|
+
* Two complementary resolution chains are unified here:
|
|
9
|
+
*
|
|
10
|
+
* Legacy chain (4 tiers):
|
|
11
|
+
* 1. Remote auto-updater manifests (linux_amd64.json / ide updater API)
|
|
12
|
+
* 2. Changelog scrape (antigravity.google.com, cloud.google.com, updater)
|
|
13
|
+
* 3. Stale disk cache (~/.config/opencode/antigravity-version.json)
|
|
14
|
+
* 4. Hardcoded fallback 1.15.8 / 2.0.11
|
|
15
|
+
*
|
|
16
|
+
* Observer chain (replicating Antigravity Manager startup):
|
|
17
|
+
* 1. Memory Map cache (6h TTL)
|
|
18
|
+
* 2. Remote registries: npm registry, GitHub Releases, storage.googleapis
|
|
19
|
+
* 3. Local cache file written atomically (tmp + rename, chmod 0600)
|
|
20
|
+
* 4. Governed fallback 1.19.2 (>= min supported, not blocked)
|
|
21
|
+
*
|
|
22
|
+
* Governance constants: ANTIGRAVITY_BLOCKED_VERSIONS hard-ban list,
|
|
23
|
+
* ANTIGRAVITY_VERSION_MIN_SUPPORTED ("1.15.8") and semver utilities
|
|
24
|
+
* (cleanSemver / compareSemver / shouldUpdate / isValidSemver /
|
|
25
|
+
* isBlockedVersion / isSupportedVersion).
|
|
26
|
+
*
|
|
27
|
+
* Reconciliation notes:
|
|
28
|
+
* - getVersion()/getVersionSync() accept BOTH call shapes (no args or options
|
|
29
|
+
* object) and consult both chains plus both cache stores. The observer
|
|
30
|
+
* chain is gated by min-supported/blocked checks; legacy chain results are
|
|
31
|
+
* honored unless explicitly blocked. When everything fails offline, the
|
|
32
|
+
* governed fallback 1.19.2 is returned by the merged getters, while the
|
|
33
|
+
* dedicated legacy getters (getVersionInfo*, fetchAntigravityVersion) keep
|
|
34
|
+
* their original 1.15.8 fallback untouched.
|
|
35
|
+
* - Cache persistence uses atomic tmp-file + rename with chmod 0600.
|
|
36
|
+
*
|
|
37
|
+
* Runtime: only node:* builtins + global fetch. Zero external deps. The
|
|
38
|
+
* registry is server and cli surface — the neutral core barrel never
|
|
39
|
+
* carries it, so the browser bundles stay free of node imports.
|
|
40
|
+
*
|
|
41
|
+
* @license MIT
|
|
42
|
+
*/
|
|
43
|
+
|
|
44
|
+
import * as fs from "node:fs";
|
|
45
|
+
import * as fsp from "node:fs/promises";
|
|
46
|
+
import * as path from "node:path";
|
|
47
|
+
import * as os from "node:os";
|
|
48
|
+
import * as crypto from "node:crypto";
|
|
49
|
+
|
|
50
|
+
// Deduplicated against the frozen owner (constants.ts): symbols whose local
|
|
51
|
+
// definitions were value-identical are imported and re-exported below, so each
|
|
52
|
+
// has exactly one definition (library dedupe task 2-d1).
|
|
53
|
+
import {
|
|
54
|
+
ANTIGRAVITY_VERSION_FALLBACK,
|
|
55
|
+
ANTIGRAVITY_VERSION_MIN_SUPPORTED,
|
|
56
|
+
ANTIGRAVITY_BLOCKED_VERSIONS,
|
|
57
|
+
ANTIGRAVITY_VERSION_REMOTE_URLS,
|
|
58
|
+
VERSION_FETCH_TIMEOUT_MS,
|
|
59
|
+
FETCH_TIMEOUT_MS,
|
|
60
|
+
GEMINI_CLI_OAUTH_CLIENT_ID,
|
|
61
|
+
GEMINI_CLI_OAUTH_CLIENT_SECRET,
|
|
62
|
+
GEMINI_CLI_SCOPES,
|
|
63
|
+
GEMINI_CLI_USER_AGENT,
|
|
64
|
+
CLIENT_METADATA_STRING,
|
|
65
|
+
CLOUDCODE_BASE_URL,
|
|
66
|
+
CODE_ASSIST_PATH_MAP,
|
|
67
|
+
PROJECT_FALLBACK,
|
|
68
|
+
} from "./constants.js";
|
|
69
|
+
// v2.1.16 single-owner fix: the MODELS_2026 reference list comes from
|
|
70
|
+
// models.js (model identification owner — the 11-id legacy list).
|
|
71
|
+
import { MODELS_2026_LEGACY as MODELS_2026_REFERENCE } from "./models.js";
|
|
72
|
+
import { X_GOOG_API_CLIENT_GEMINI_CLI } from "./fingerprint.js";
|
|
73
|
+
import { fetchWithTimeout } from "./core.js";
|
|
74
|
+
import { isValidSemver } from "./config.js";
|
|
75
|
+
export { isValidSemver };
|
|
76
|
+
|
|
77
|
+
// Re-exports preserve this module's public surface after the dedupe.
|
|
78
|
+
export {
|
|
79
|
+
ANTIGRAVITY_VERSION_FALLBACK,
|
|
80
|
+
ANTIGRAVITY_VERSION_MIN_SUPPORTED,
|
|
81
|
+
ANTIGRAVITY_BLOCKED_VERSIONS,
|
|
82
|
+
ANTIGRAVITY_VERSION_REMOTE_URLS,
|
|
83
|
+
VERSION_FETCH_TIMEOUT_MS,
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
// ---------------------------------------------------------------------------
|
|
87
|
+
// Section: Constants / fallbacks (legacy chain)
|
|
88
|
+
// ---------------------------------------------------------------------------
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Official Antigravity auto-updater endpoints (same infra as Gemini CLI /
|
|
92
|
+
* Antigravity IDE). Both return JSON with `version` shaped "1.15.8-57246...".
|
|
93
|
+
*/
|
|
94
|
+
export const REMOTE_MANIFEST_URLS = [
|
|
95
|
+
"https://antigravity-auto-updater-974169037036.us-central1.run.app/manifests/linux_amd64.json",
|
|
96
|
+
"https://antigravity-ide-auto-updater-974169037036.us-central1.run.app/api/update/linux/x64/stable/0",
|
|
97
|
+
] as const;
|
|
98
|
+
|
|
99
|
+
/** Changelog URLs for secondary scrape when manifests fail. */
|
|
100
|
+
export const CHANGELOG_URLS = [
|
|
101
|
+
"https://antigravity.google.com/docs/changelog",
|
|
102
|
+
"https://antigravity.google.com/changelog",
|
|
103
|
+
"https://cloud.google.com/antigravity/docs/release-notes",
|
|
104
|
+
"https://antigravity-auto-updater-974169037036.us-central1.run.app/changelog",
|
|
105
|
+
] as const;
|
|
106
|
+
|
|
107
|
+
/** Hardened fallback - last known stable IDE version. */
|
|
108
|
+
export const FALLBACK_VERSION = "1.15.8" as const;
|
|
109
|
+
export const FALLBACK_FULL_VERSION = "1.15.8-5724687216017408" as const;
|
|
110
|
+
|
|
111
|
+
/** Alternative fallback - CLI/Antigravity v2 track compatibility. */
|
|
112
|
+
export const FALLBACK_ALTERNATIVE_VERSION = "2.0.11" as const;
|
|
113
|
+
export const FALLBACK_ALTERNATIVE_FULL = "2.0.11-0000000000000000" as const;
|
|
114
|
+
|
|
115
|
+
/** Legacy cache TTL: 6 hours in ms. */
|
|
116
|
+
export const VERSION_CACHE_TTL_MS = 6 * 60 * 60 * 1000;
|
|
117
|
+
|
|
118
|
+
/** Legacy cache file location - XDG style. */
|
|
119
|
+
export const VERSION_CACHE_FILE = path.join(os.homedir(), ".config", "opencode", "antigravity-version.json");
|
|
120
|
+
|
|
121
|
+
/** Default fetch timeout. */
|
|
122
|
+
// local variant: diverges from models (5s legacy-chain default vs models' 10s FETCH_TIMEOUT_MS)
|
|
123
|
+
|
|
124
|
+
// ---------------------------------------------------------------------------
|
|
125
|
+
// Section: Constants / governance (observer chain)
|
|
126
|
+
// ---------------------------------------------------------------------------
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Governed fallback version, verified safe on 2026-08-25:
|
|
130
|
+
* versions below 1.15.8 are banned; 1.19.2 avoids the forced-update prompt.
|
|
131
|
+
*/
|
|
132
|
+
// ANTIGRAVITY_VERSION_FALLBACK / ANTIGRAVITY_VERSION_MIN_SUPPORTED /
|
|
133
|
+
// ANTIGRAVITY_BLOCKED_VERSIONS / ANTIGRAVITY_VERSION_REMOTE_URLS /
|
|
134
|
+
// VERSION_FETCH_TIMEOUT_MS: identical values — imported and re-exported above.
|
|
135
|
+
|
|
136
|
+
/** Observer memory cache TTL - 6h. */
|
|
137
|
+
export const VERSION_MEMORY_TTL_MS = 6 * 60 * 60 * 1000;
|
|
138
|
+
|
|
139
|
+
/** Observer file cache TTL - 24h before considered stale (still usable). */
|
|
140
|
+
export const VERSION_FILE_TTL_MS = 24 * 60 * 60 * 1000;
|
|
141
|
+
|
|
142
|
+
const OBSERVER_CACHE_FILE_NAME = "antigravity-version.json";
|
|
143
|
+
const OBSERVER_FILE_MODE = 0o600;
|
|
144
|
+
const OBSERVER_DIR_MODE = 0o755;
|
|
145
|
+
const MEMORY_CACHE_KEY = "latest";
|
|
146
|
+
|
|
147
|
+
const SEMVER_RE = /^(\d+)\.(\d+)\.(\d+)(?:-[\w.-]+)?(?:\+[\w.-]+)?$/;
|
|
148
|
+
const SEMVER_EXTRACT_RE = /(\d+\.\d+\.\d+(?:-[\w.-]+)?(?:\+[\w.-]+)?)/;
|
|
149
|
+
|
|
150
|
+
// ---------------------------------------------------------------------------
|
|
151
|
+
// Section: Auth constants (plugin integration reference, not used in fetch)
|
|
152
|
+
// v2.1.16 single-owner fix: every member is imported from its owner
|
|
153
|
+
// (constants.js raw values; models.js for the model-id list) — the former
|
|
154
|
+
// local literals were byte-identical duplicates.
|
|
155
|
+
// ---------------------------------------------------------------------------
|
|
156
|
+
|
|
157
|
+
export const ANTIGRAVITY_AUTH_CONSTANTS = {
|
|
158
|
+
CLIENT_ID: GEMINI_CLI_OAUTH_CLIENT_ID,
|
|
159
|
+
CLIENT_SECRET: GEMINI_CLI_OAUTH_CLIENT_SECRET,
|
|
160
|
+
SCOPES: GEMINI_CLI_SCOPES,
|
|
161
|
+
/** UA templates - production should vary platform dynamically.
|
|
162
|
+
* v2.1.16: derived from the canonical full UA by platform substitution
|
|
163
|
+
* (single owner: constants.GEMINI_CLI_USER_AGENT). */
|
|
164
|
+
USER_AGENT_TEMPLATES: {
|
|
165
|
+
darwin_arm64: GEMINI_CLI_USER_AGENT.replace("(linux; x64; GitHub)", "(darwin; arm64; GitHub)"),
|
|
166
|
+
darwin_x64: GEMINI_CLI_USER_AGENT.replace("(linux; x64; GitHub)", "(darwin; x64; GitHub)"),
|
|
167
|
+
linux_x64: GEMINI_CLI_USER_AGENT,
|
|
168
|
+
win32_x64: GEMINI_CLI_USER_AGENT.replace("(linux; x64; GitHub)", "(win32; x64; GitHub)"),
|
|
169
|
+
},
|
|
170
|
+
X_GOOG_API_CLIENT: X_GOOG_API_CLIENT_GEMINI_CLI,
|
|
171
|
+
CLIENT_METADATA: CLIENT_METADATA_STRING,
|
|
172
|
+
ENDPOINT_BASE: CLOUDCODE_BASE_URL,
|
|
173
|
+
ENDPOINTS: CODE_ASSIST_PATH_MAP,
|
|
174
|
+
PROJECT_FALLBACK: PROJECT_FALLBACK,
|
|
175
|
+
MODELS_2026: MODELS_2026_REFERENCE,
|
|
176
|
+
} as const;
|
|
177
|
+
|
|
178
|
+
// ---------------------------------------------------------------------------
|
|
179
|
+
// Section: Types
|
|
180
|
+
// ---------------------------------------------------------------------------
|
|
181
|
+
|
|
182
|
+
export type VersionSource =
|
|
183
|
+
| "remote-manifest"
|
|
184
|
+
| "ide-updater"
|
|
185
|
+
| "changelog"
|
|
186
|
+
| "cache-file"
|
|
187
|
+
| "memory-cache"
|
|
188
|
+
| "hardcoded-fallback";
|
|
189
|
+
|
|
190
|
+
export interface AntigravityVersionInfo {
|
|
191
|
+
/** Normalized version without build metadata, e.g. "1.15.8" */
|
|
192
|
+
version: string;
|
|
193
|
+
/** Full original version, e.g. "1.15.8-5724687216017408" */
|
|
194
|
+
fullVersion: string;
|
|
195
|
+
/** Where this version came from */
|
|
196
|
+
source: VersionSource;
|
|
197
|
+
/** Epoch ms */
|
|
198
|
+
fetchedAt: number;
|
|
199
|
+
/** Computed expiration */
|
|
200
|
+
expiresAt: number;
|
|
201
|
+
/** Optional URL that produced the version */
|
|
202
|
+
url?: string;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/** Observer memory-cache entry shape. */
|
|
206
|
+
export interface CacheEntry {
|
|
207
|
+
version: string;
|
|
208
|
+
fetchedAt: number; // epoch ms
|
|
209
|
+
source: string; // url or "file" or "memory"
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/** Observer on-disk cache file shape. */
|
|
213
|
+
export interface VersionCacheFile {
|
|
214
|
+
version: string;
|
|
215
|
+
fetchedAt: number;
|
|
216
|
+
source: string;
|
|
217
|
+
expiresAt: number;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/** Observer fetch overrides. */
|
|
221
|
+
export interface FetchOptions {
|
|
222
|
+
/** Timeout per request ms - defaults to 5000 */
|
|
223
|
+
timeoutMs?: number;
|
|
224
|
+
/** Force remote fetch bypassing memory cache */
|
|
225
|
+
force?: boolean;
|
|
226
|
+
/** Custom remote URLs override (testing) */
|
|
227
|
+
remoteUrls?: readonly string[];
|
|
228
|
+
/** Custom cache path override */
|
|
229
|
+
cachePath?: string;
|
|
230
|
+
/** Extra User-Agent header value (GitHub rate-limit friendly) */
|
|
231
|
+
userAgent?: string;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
export interface GetVersionOptions extends FetchOptions {
|
|
235
|
+
/** Allow returning stale file cache when remote fails - default true */
|
|
236
|
+
allowStale?: boolean;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/** Shape persisted on disk for the legacy chain. */
|
|
240
|
+
interface CachedFilePayload extends AntigravityVersionInfo {}
|
|
241
|
+
|
|
242
|
+
// ---------------------------------------------------------------------------
|
|
243
|
+
// Section: Internal state (legacy memory cache + dedup, observer Map)
|
|
244
|
+
// ---------------------------------------------------------------------------
|
|
245
|
+
|
|
246
|
+
let memoryCache: AntigravityVersionInfo | null = null;
|
|
247
|
+
let inflightPromise: Promise<AntigravityVersionInfo> | null = null;
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Observer memory cache keyed by MEMORY_CACHE_KEY ("latest").
|
|
251
|
+
* Exported for introspection/testing; treat as read-only in production.
|
|
252
|
+
*/
|
|
253
|
+
export const versionMemoryCache = new Map<string, CacheEntry>();
|
|
254
|
+
|
|
255
|
+
// ---------------------------------------------------------------------------
|
|
256
|
+
// Section: Semver utilities (observer chain, dependency-free)
|
|
257
|
+
// ---------------------------------------------------------------------------
|
|
258
|
+
|
|
259
|
+
/** Validates strict semver shape. */
|
|
260
|
+
// v2.1.15 Phase C: isValidSemver moved to config.js (validator owner) — re-exported below.
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Extracts the first semver token from dirty input such as "v1.19.2" or
|
|
264
|
+
* "antigravity/1.19.2 darwin/arm64".
|
|
265
|
+
* @returns cleaned semver or null
|
|
266
|
+
*/
|
|
267
|
+
export function cleanSemver(raw: unknown): string | null {
|
|
268
|
+
if (!raw || typeof raw !== "string") return null;
|
|
269
|
+
const trimmed = raw.trim();
|
|
270
|
+
if (SEMVER_RE.test(trimmed)) return trimmed;
|
|
271
|
+
const m = trimmed.match(SEMVER_EXTRACT_RE);
|
|
272
|
+
if (m && m[1] && SEMVER_RE.test(m[1])) return m[1];
|
|
273
|
+
return null;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Compares two semver strings.
|
|
278
|
+
* @returns -1 if a < b, 0 if equal, 1 if a > b (invalid sorts lowest)
|
|
279
|
+
*/
|
|
280
|
+
export function compareSemver(a: string, b: string): -1 | 0 | 1 {
|
|
281
|
+
const ca = cleanSemver(a);
|
|
282
|
+
const cb = cleanSemver(b);
|
|
283
|
+
if (!ca && !cb) return 0;
|
|
284
|
+
if (!ca) return -1;
|
|
285
|
+
if (!cb) return 1;
|
|
286
|
+
|
|
287
|
+
const pa = ca.split(/[.-]/);
|
|
288
|
+
const pb = cb.split(/[.-]/);
|
|
289
|
+
|
|
290
|
+
for (let i = 0; i < 3; i++) {
|
|
291
|
+
const na = Number.parseInt(pa[i] ?? "0", 10);
|
|
292
|
+
const nb = Number.parseInt(pb[i] ?? "0", 10);
|
|
293
|
+
if (Number.isNaN(na) || Number.isNaN(nb)) continue;
|
|
294
|
+
if (na < nb) return -1;
|
|
295
|
+
if (na > nb) return 1;
|
|
296
|
+
}
|
|
297
|
+
// Prerelease suffix ordering: 1.19.2-preview < 1.19.2
|
|
298
|
+
const hasPreA = ca.includes("-");
|
|
299
|
+
const hasPreB = cb.includes("-");
|
|
300
|
+
if (hasPreA && !hasPreB) return -1;
|
|
301
|
+
if (!hasPreA && hasPreB) return 1;
|
|
302
|
+
return 0;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/** True when latest > current - triggers an update notification. */
|
|
306
|
+
export function shouldUpdate(current: string, latest: string): boolean {
|
|
307
|
+
const c = cleanSemver(current);
|
|
308
|
+
const l = cleanSemver(latest);
|
|
309
|
+
if (!c || !l) return false;
|
|
310
|
+
return compareSemver(c, l) === -1;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/** Checks whether a version is on the hard block list. */
|
|
314
|
+
export function isBlockedVersion(v: string): boolean {
|
|
315
|
+
const cleaned = cleanSemver(v);
|
|
316
|
+
if (!cleaned) return false;
|
|
317
|
+
return (ANTIGRAVITY_BLOCKED_VERSIONS as readonly string[]).includes(cleaned);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/** True when version >= min supported and not blocked. */
|
|
321
|
+
export function isSupportedVersion(v: string): boolean {
|
|
322
|
+
const cleaned = cleanSemver(v);
|
|
323
|
+
if (!cleaned) return false;
|
|
324
|
+
if (isBlockedVersion(cleaned)) return false;
|
|
325
|
+
return compareSemver(cleaned, ANTIGRAVITY_VERSION_MIN_SUPPORTED) >= 0;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// ---------------------------------------------------------------------------
|
|
329
|
+
// Section: Payload parsing - heterogeneous JSON shapes (npm/GitHub/GCS)
|
|
330
|
+
// ---------------------------------------------------------------------------
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Extracts a version from unknown JSON payload shapes:
|
|
334
|
+
* npm `{version}`, GitHub `{tag_name}` / `{name}`, GCS `{version|latest|
|
|
335
|
+
* latestVersion}`, nested `{data:{version}}`; falls back to regex over the
|
|
336
|
+
* serialized payload.
|
|
337
|
+
*/
|
|
338
|
+
export function parseVersionFromPayload(payload: unknown): string | null {
|
|
339
|
+
if (!payload || typeof payload !== "object") return null;
|
|
340
|
+
const obj = payload as Record<string, unknown>;
|
|
341
|
+
|
|
342
|
+
const candidates: unknown[] = [
|
|
343
|
+
obj["version"],
|
|
344
|
+
obj["latestVersion"],
|
|
345
|
+
obj["latest"],
|
|
346
|
+
obj["tag_name"],
|
|
347
|
+
obj["name"],
|
|
348
|
+
(obj["data"] as Record<string, unknown> | undefined)?.["version"],
|
|
349
|
+
(obj["data"] as Record<string, unknown> | undefined)?.["latestVersion"],
|
|
350
|
+
];
|
|
351
|
+
|
|
352
|
+
for (const cand of candidates) {
|
|
353
|
+
if (typeof cand === "string") {
|
|
354
|
+
const cleaned = cleanSemver(cand);
|
|
355
|
+
if (cleaned) return cleaned;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
try {
|
|
360
|
+
const asString = JSON.stringify(payload);
|
|
361
|
+
const m = asString.match(SEMVER_EXTRACT_RE);
|
|
362
|
+
if (m && m[1]) {
|
|
363
|
+
const cleaned = cleanSemver(m[1]);
|
|
364
|
+
if (cleaned) return cleaned;
|
|
365
|
+
}
|
|
366
|
+
} catch {
|
|
367
|
+
// ignore
|
|
368
|
+
}
|
|
369
|
+
return null;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// ---------------------------------------------------------------------------
|
|
373
|
+
// Section: Helpers (legacy chain)
|
|
374
|
+
// ---------------------------------------------------------------------------
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* Parses "1.15.8-5724687216017408" into normalized + full forms,
|
|
378
|
+
* keeping a safe fallback when the regex does not match.
|
|
379
|
+
*/
|
|
380
|
+
export function parseAntigravityVersion(raw: string): {
|
|
381
|
+
normalized: string;
|
|
382
|
+
full: string;
|
|
383
|
+
} {
|
|
384
|
+
if (!raw || typeof raw !== "string") {
|
|
385
|
+
return { normalized: FALLBACK_VERSION, full: FALLBACK_FULL_VERSION };
|
|
386
|
+
}
|
|
387
|
+
const trimmed = raw.trim();
|
|
388
|
+
const m = trimmed.match(/(\d+\.\d+\.\d+(?:\.\d+)?)/);
|
|
389
|
+
if (!m) {
|
|
390
|
+
const cleaned = trimmed.replace(/[^0-9.\-]/g, "");
|
|
391
|
+
return {
|
|
392
|
+
normalized: cleaned.split("-")[0] || FALLBACK_VERSION,
|
|
393
|
+
full: trimmed,
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
return { normalized: m[1] || FALLBACK_VERSION, full: trimmed };
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
/**
|
|
400
|
+
* Fetch with timeout via AbortController (global fetch, Node 18+).
|
|
401
|
+
* Injects Gemini-CLI-like baseline headers; caller headers take precedence.
|
|
402
|
+
*/
|
|
403
|
+
// local variant: diverges from models (injects Gemini-CLI baseline headers; no external-signal merge; 5s default)
|
|
404
|
+
|
|
405
|
+
/** Ensures the legacy cache directory exists. */
|
|
406
|
+
function ensureCacheDirSync(): void {
|
|
407
|
+
try {
|
|
408
|
+
const dir = path.dirname(VERSION_CACHE_FILE);
|
|
409
|
+
if (!fs.existsSync(dir)) {
|
|
410
|
+
fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
411
|
+
}
|
|
412
|
+
} catch {
|
|
413
|
+
// silent - disk may be read-only, memory cache still works
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
/** Reads the legacy cache from disk synchronously when valid. */
|
|
418
|
+
export function readCacheFileSync(): AntigravityVersionInfo | null {
|
|
419
|
+
try {
|
|
420
|
+
if (!fs.existsSync(VERSION_CACHE_FILE)) return null;
|
|
421
|
+
const raw = fs.readFileSync(VERSION_CACHE_FILE, "utf8");
|
|
422
|
+
const parsed = JSON.parse(raw) as CachedFilePayload;
|
|
423
|
+
if (!parsed.version || !parsed.fetchedAt) return null;
|
|
424
|
+
if (Date.now() > parsed.expiresAt) return null;
|
|
425
|
+
return parsed;
|
|
426
|
+
} catch {
|
|
427
|
+
return null;
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
/** Reads the legacy cache from disk asynchronously. */
|
|
432
|
+
export async function readCacheFile(): Promise<AntigravityVersionInfo | null> {
|
|
433
|
+
try {
|
|
434
|
+
await fs.promises.access(VERSION_CACHE_FILE, fs.constants.R_OK);
|
|
435
|
+
const raw = await fs.promises.readFile(VERSION_CACHE_FILE, "utf8");
|
|
436
|
+
const parsed = JSON.parse(raw) as CachedFilePayload;
|
|
437
|
+
if (!parsed.version || !parsed.fetchedAt) return null;
|
|
438
|
+
if (Date.now() > parsed.expiresAt) return null;
|
|
439
|
+
return { ...parsed, source: "cache-file" };
|
|
440
|
+
} catch {
|
|
441
|
+
return null;
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
/**
|
|
446
|
+
* Writes the legacy cache file atomically: content goes to a unique temp
|
|
447
|
+
* file (chmod 0600), then rename over the destination. Best-effort, never
|
|
448
|
+
* throws into the caller.
|
|
449
|
+
*/
|
|
450
|
+
export async function writeCacheFile(info: AntigravityVersionInfo): Promise<void> {
|
|
451
|
+
const dir = path.dirname(VERSION_CACHE_FILE);
|
|
452
|
+
try {
|
|
453
|
+
ensureCacheDirSync();
|
|
454
|
+
const payload: CachedFilePayload = { ...info };
|
|
455
|
+
const content = JSON.stringify(payload, null, 2);
|
|
456
|
+
const rand = crypto.randomBytes(6).toString("hex");
|
|
457
|
+
const tmp = path.join(dir, `.${path.basename(VERSION_CACHE_FILE)}.${process.pid}.${rand}.tmp`);
|
|
458
|
+
try {
|
|
459
|
+
await fsp.writeFile(tmp, content, { encoding: "utf8", mode: 0o600 });
|
|
460
|
+
try {
|
|
461
|
+
await fsp.chmod(tmp, 0o600);
|
|
462
|
+
} catch {
|
|
463
|
+
/* best effort */
|
|
464
|
+
}
|
|
465
|
+
await fsp.rename(tmp, VERSION_CACHE_FILE);
|
|
466
|
+
try {
|
|
467
|
+
await fsp.chmod(VERSION_CACHE_FILE, 0o600);
|
|
468
|
+
} catch {
|
|
469
|
+
/* best effort */
|
|
470
|
+
}
|
|
471
|
+
} finally {
|
|
472
|
+
try {
|
|
473
|
+
await fsp.unlink(tmp);
|
|
474
|
+
} catch {
|
|
475
|
+
/* temp may not exist anymore */
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
} catch {
|
|
479
|
+
// ignore - memory cache still works
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
// ---------------------------------------------------------------------------
|
|
484
|
+
// Section: Observer filesystem helpers
|
|
485
|
+
// ---------------------------------------------------------------------------
|
|
486
|
+
|
|
487
|
+
/**
|
|
488
|
+
* Resolves the observer cache dir, honoring OPENCODE_CONFIG_DIR and
|
|
489
|
+
* XDG_CONFIG_HOME (same contract as config.ts).
|
|
490
|
+
*/
|
|
491
|
+
function resolveConfigDir(): string {
|
|
492
|
+
const envDir = process.env.OPENCODE_CONFIG_DIR?.trim();
|
|
493
|
+
if (envDir) return envDir;
|
|
494
|
+
const xdg = process.env.XDG_CONFIG_HOME?.trim();
|
|
495
|
+
if (xdg) return path.join(xdg, "opencode");
|
|
496
|
+
return path.join(os.homedir(), ".config", "opencode");
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
function resolveObserverCachePath(customPath?: string): string {
|
|
500
|
+
if (customPath?.trim()) return path.resolve(customPath.trim());
|
|
501
|
+
return path.join(resolveConfigDir(), OBSERVER_CACHE_FILE_NAME);
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
async function readObserverCacheFileAsync(cachePath: string): Promise<VersionCacheFile | null> {
|
|
505
|
+
try {
|
|
506
|
+
await fsp.access(cachePath, fs.constants.F_OK);
|
|
507
|
+
} catch {
|
|
508
|
+
return null;
|
|
509
|
+
}
|
|
510
|
+
try {
|
|
511
|
+
const text = await fsp.readFile(cachePath, "utf8");
|
|
512
|
+
if (!text.trim()) return null;
|
|
513
|
+
const parsed = JSON.parse(text) as VersionCacheFile;
|
|
514
|
+
const cleaned = cleanSemver(parsed?.version);
|
|
515
|
+
if (!cleaned) return null;
|
|
516
|
+
return {
|
|
517
|
+
version: cleaned,
|
|
518
|
+
fetchedAt: typeof parsed.fetchedAt === "number" ? parsed.fetchedAt : Date.now(),
|
|
519
|
+
source: typeof parsed.source === "string" ? parsed.source : "file",
|
|
520
|
+
expiresAt: typeof parsed.expiresAt === "number" ? parsed.expiresAt : Date.now() + VERSION_FILE_TTL_MS,
|
|
521
|
+
};
|
|
522
|
+
} catch {
|
|
523
|
+
return null;
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
function readObserverCacheFileSync(cachePath: string): VersionCacheFile | null {
|
|
528
|
+
try {
|
|
529
|
+
fs.accessSync(cachePath, fs.constants.F_OK);
|
|
530
|
+
} catch {
|
|
531
|
+
return null;
|
|
532
|
+
}
|
|
533
|
+
try {
|
|
534
|
+
const text = fs.readFileSync(cachePath, "utf8");
|
|
535
|
+
if (!text.trim()) return null;
|
|
536
|
+
const parsed = JSON.parse(text) as VersionCacheFile;
|
|
537
|
+
const cleaned = cleanSemver(parsed?.version);
|
|
538
|
+
if (!cleaned) return null;
|
|
539
|
+
return {
|
|
540
|
+
version: cleaned,
|
|
541
|
+
fetchedAt: typeof parsed.fetchedAt === "number" ? parsed.fetchedAt : Date.now(),
|
|
542
|
+
source: typeof parsed.source === "string" ? parsed.source : "file",
|
|
543
|
+
expiresAt: typeof parsed.expiresAt === "number" ? parsed.expiresAt : Date.now() + VERSION_FILE_TTL_MS,
|
|
544
|
+
};
|
|
545
|
+
} catch {
|
|
546
|
+
return null;
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
/**
|
|
551
|
+
* Atomic observer cache write: mkdir dir, unique tmp file with mode 0600,
|
|
552
|
+
* chmod hardening, rename over destination, tmp cleanup in finally.
|
|
553
|
+
*/
|
|
554
|
+
async function writeObserverCacheFileAtomic(cachePath: string, entry: VersionCacheFile): Promise<void> {
|
|
555
|
+
const dir = path.dirname(cachePath);
|
|
556
|
+
try {
|
|
557
|
+
await fsp.mkdir(dir, { recursive: true, mode: OBSERVER_DIR_MODE });
|
|
558
|
+
} catch {
|
|
559
|
+
// best effort
|
|
560
|
+
}
|
|
561
|
+
try {
|
|
562
|
+
await fsp.chmod(dir, OBSERVER_DIR_MODE);
|
|
563
|
+
} catch {
|
|
564
|
+
// ignore chmod failures on some filesystems
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
const content = JSON.stringify(entry, null, 2) + "\n";
|
|
568
|
+
const rand = crypto.randomBytes(6).toString("hex");
|
|
569
|
+
const tmp = path.join(dir, `.${path.basename(cachePath)}.${process.pid}.${rand}.tmp`);
|
|
570
|
+
|
|
571
|
+
try {
|
|
572
|
+
await fsp.writeFile(tmp, content, { encoding: "utf8", mode: OBSERVER_FILE_MODE });
|
|
573
|
+
try {
|
|
574
|
+
await fsp.chmod(tmp, OBSERVER_FILE_MODE);
|
|
575
|
+
} catch {
|
|
576
|
+
/* best effort */
|
|
577
|
+
}
|
|
578
|
+
await fsp.rename(tmp, cachePath);
|
|
579
|
+
try {
|
|
580
|
+
await fsp.chmod(cachePath, OBSERVER_FILE_MODE);
|
|
581
|
+
} catch {
|
|
582
|
+
/* best effort */
|
|
583
|
+
}
|
|
584
|
+
} finally {
|
|
585
|
+
try {
|
|
586
|
+
await fsp.unlink(tmp);
|
|
587
|
+
} catch {
|
|
588
|
+
/* temp already renamed */
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
// ---------------------------------------------------------------------------
|
|
594
|
+
// Section: Remote fetchers - legacy chain (manifests + changelog)
|
|
595
|
+
// ---------------------------------------------------------------------------
|
|
596
|
+
|
|
597
|
+
/** Chain tier 1a: auto-updater manifests (heterogeneous JSON shapes). */
|
|
598
|
+
async function fetchFromManifest(url: string): Promise<AntigravityVersionInfo | null> {
|
|
599
|
+
try {
|
|
600
|
+
const res = await fetchWithTimeout(url, { method: "GET" });
|
|
601
|
+
if (!res.ok) return null;
|
|
602
|
+
|
|
603
|
+
const text = await res.text();
|
|
604
|
+
let json: any = null;
|
|
605
|
+
try {
|
|
606
|
+
json = JSON.parse(text);
|
|
607
|
+
} catch {
|
|
608
|
+
// Response may be plain text containing a version.
|
|
609
|
+
const parsed = parseAntigravityVersion(text);
|
|
610
|
+
if (parsed.normalized !== FALLBACK_VERSION || text.includes(".")) {
|
|
611
|
+
const now = Date.now();
|
|
612
|
+
return {
|
|
613
|
+
version: parsed.normalized,
|
|
614
|
+
fullVersion: parsed.full,
|
|
615
|
+
source: url.includes("ide-auto-updater") ? "ide-updater" : "remote-manifest",
|
|
616
|
+
fetchedAt: now,
|
|
617
|
+
expiresAt: now + VERSION_CACHE_TTL_MS,
|
|
618
|
+
url,
|
|
619
|
+
};
|
|
620
|
+
}
|
|
621
|
+
return null;
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
const candidates: (string | undefined)[] = [
|
|
625
|
+
json?.version,
|
|
626
|
+
json?.latestVersion,
|
|
627
|
+
json?.latest,
|
|
628
|
+
json?.stable?.version,
|
|
629
|
+
json?.manifest?.version,
|
|
630
|
+
json?.manifests?.linux_amd64?.version,
|
|
631
|
+
json?.manifests?.["linux/amd64"]?.version,
|
|
632
|
+
json?.data?.version,
|
|
633
|
+
json?.result?.version,
|
|
634
|
+
];
|
|
635
|
+
|
|
636
|
+
if (Array.isArray(json?.manifests)) {
|
|
637
|
+
for (const m of json.manifests) {
|
|
638
|
+
if (m?.version) candidates.push(m.version);
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
// Bounded deep scan looking for any version-looking string.
|
|
643
|
+
if (candidates.every((c) => !c)) {
|
|
644
|
+
const stack = [json];
|
|
645
|
+
let depth = 0;
|
|
646
|
+
while (stack.length && depth < 20) {
|
|
647
|
+
const cur = stack.pop();
|
|
648
|
+
depth++;
|
|
649
|
+
if (cur && typeof cur === "object") {
|
|
650
|
+
for (const v of Object.values(cur)) {
|
|
651
|
+
if (typeof v === "string" && /^\d+\.\d+\.\d+-?\d*/.test(v)) {
|
|
652
|
+
candidates.push(v);
|
|
653
|
+
break;
|
|
654
|
+
}
|
|
655
|
+
if (v && typeof v === "object") stack.push(v);
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
if (candidates.filter(Boolean).length) break;
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
const rawVersion = candidates.find((c): c is string => !!c && typeof c === "string");
|
|
663
|
+
if (!rawVersion) return null;
|
|
664
|
+
|
|
665
|
+
const { normalized, full } = parseAntigravityVersion(rawVersion);
|
|
666
|
+
if (!normalized) return null;
|
|
667
|
+
|
|
668
|
+
const now = Date.now();
|
|
669
|
+
return {
|
|
670
|
+
version: normalized,
|
|
671
|
+
fullVersion: full,
|
|
672
|
+
source: url.includes("ide-auto-updater") ? "ide-updater" : "remote-manifest",
|
|
673
|
+
fetchedAt: now,
|
|
674
|
+
expiresAt: now + VERSION_CACHE_TTL_MS,
|
|
675
|
+
url,
|
|
676
|
+
};
|
|
677
|
+
} catch {
|
|
678
|
+
return null;
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
/** Chain tier 2: changelog scrape (best-effort regex extraction). */
|
|
683
|
+
async function fetchFromChangelog(): Promise<AntigravityVersionInfo | null> {
|
|
684
|
+
const versionRegex = /(?:antigravity[^0-9]{0,20}|version[^0-9]{0,10}|release[^0-9]{0,10})?(\d+\.\d+\.\d+(?:-\d+)?)/gi;
|
|
685
|
+
const semverOnly = /(\d+\.\d+\.\d+)/g;
|
|
686
|
+
|
|
687
|
+
for (const url of CHANGELOG_URLS) {
|
|
688
|
+
try {
|
|
689
|
+
const res = await fetchWithTimeout(url, { method: "GET" }, 5000);
|
|
690
|
+
if (!res.ok) continue;
|
|
691
|
+
const text = await res.text();
|
|
692
|
+
if (!text || text.length < 10) continue;
|
|
693
|
+
|
|
694
|
+
let match: RegExpExecArray | null;
|
|
695
|
+
const found: string[] = [];
|
|
696
|
+
while ((match = versionRegex.exec(text)) !== null) {
|
|
697
|
+
if (match[1]) found.push(match[1]);
|
|
698
|
+
if (found.length >= 10) break;
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
if (found.length === 0) {
|
|
702
|
+
let m2: RegExpExecArray | null;
|
|
703
|
+
while ((m2 = semverOnly.exec(text)) !== null) {
|
|
704
|
+
if (m2 && m2[1]) {
|
|
705
|
+
// Heuristic: skip tiny generic versions unless context matched.
|
|
706
|
+
const parts = m2[1].split(".").map(Number);
|
|
707
|
+
if ((parts[0] ?? 0) >= 1) found.push(m2[1]);
|
|
708
|
+
}
|
|
709
|
+
if (found.length >= 20) break;
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
if (found.length === 0) continue;
|
|
714
|
+
|
|
715
|
+
const raw = found[0] ?? "";
|
|
716
|
+
const { normalized, full } = parseAntigravityVersion(raw);
|
|
717
|
+
const now = Date.now();
|
|
718
|
+
return {
|
|
719
|
+
version: normalized,
|
|
720
|
+
fullVersion: full.includes("-") ? full : `${normalized}-0000000000000000`,
|
|
721
|
+
source: "changelog",
|
|
722
|
+
fetchedAt: now,
|
|
723
|
+
expiresAt: now + VERSION_CACHE_TTL_MS,
|
|
724
|
+
url,
|
|
725
|
+
};
|
|
726
|
+
} catch {
|
|
727
|
+
continue;
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
return null;
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
// ---------------------------------------------------------------------------
|
|
734
|
+
// Section: Remote fetch - observer chain (npm / GitHub / GCS)
|
|
735
|
+
// ---------------------------------------------------------------------------
|
|
736
|
+
|
|
737
|
+
/** Independent timeout fetch used by the observer chain. */
|
|
738
|
+
async function fetchLatestWithTimeout(url: string, timeoutMs: number, userAgent: string): Promise<Response> {
|
|
739
|
+
const controller = new AbortController();
|
|
740
|
+
const t = setTimeout(() => controller.abort(), timeoutMs);
|
|
741
|
+
try {
|
|
742
|
+
const res = await fetch(url, {
|
|
743
|
+
signal: controller.signal,
|
|
744
|
+
headers: {
|
|
745
|
+
Accept: "application/json, text/plain, */*",
|
|
746
|
+
"User-Agent": userAgent,
|
|
747
|
+
},
|
|
748
|
+
});
|
|
749
|
+
return res;
|
|
750
|
+
} finally {
|
|
751
|
+
clearTimeout(t);
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
/**
|
|
756
|
+
* Attempts to fetch the latest Antigravity version from the observer remote
|
|
757
|
+
* chain. Each URL gets an independent AbortController timeout. Valid semver
|
|
758
|
+
* results are stored in the memory Map and persisted atomically to disk.
|
|
759
|
+
*
|
|
760
|
+
* @param opts - optional overrides
|
|
761
|
+
* @returns latest version string or null when every remote fails
|
|
762
|
+
*/
|
|
763
|
+
export async function fetchLatestVersion(opts: FetchOptions = {}): Promise<string | null> {
|
|
764
|
+
const timeoutMs = opts.timeoutMs ?? VERSION_FETCH_TIMEOUT_MS;
|
|
765
|
+
const remoteUrls = opts.remoteUrls ?? ANTIGRAVITY_VERSION_REMOTE_URLS;
|
|
766
|
+
const cachePath = resolveObserverCachePath(opts.cachePath);
|
|
767
|
+
const userAgent = opts.userAgent ?? `antigravity/${ANTIGRAVITY_VERSION_FALLBACK}`;
|
|
768
|
+
|
|
769
|
+
// Memory fast path unless forced.
|
|
770
|
+
if (!opts.force) {
|
|
771
|
+
const mem = versionMemoryCache.get(MEMORY_CACHE_KEY);
|
|
772
|
+
if (mem) {
|
|
773
|
+
const cleaned = cleanSemver(mem.version);
|
|
774
|
+
if (cleaned && Date.now() - mem.fetchedAt < VERSION_MEMORY_TTL_MS && isSupportedVersion(cleaned)) {
|
|
775
|
+
return cleaned;
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
for (const url of remoteUrls) {
|
|
781
|
+
try {
|
|
782
|
+
const res = await fetchLatestWithTimeout(url, timeoutMs, userAgent);
|
|
783
|
+
if (!res.ok) continue;
|
|
784
|
+
|
|
785
|
+
let payload: unknown;
|
|
786
|
+
const ct = res.headers.get("content-type") ?? "";
|
|
787
|
+
if (ct.includes("application/json") || ct.includes("text/json")) {
|
|
788
|
+
try {
|
|
789
|
+
payload = await res.json();
|
|
790
|
+
} catch {
|
|
791
|
+
const txt = await res.text();
|
|
792
|
+
try {
|
|
793
|
+
payload = JSON.parse(txt);
|
|
794
|
+
} catch {
|
|
795
|
+
payload = { version: txt.trim() };
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
} else {
|
|
799
|
+
// Some GCS buckets serve text/plain versions.
|
|
800
|
+
const txt = await res.text();
|
|
801
|
+
const trimmed = txt.trim();
|
|
802
|
+
try {
|
|
803
|
+
payload = JSON.parse(trimmed);
|
|
804
|
+
} catch {
|
|
805
|
+
payload = { version: trimmed };
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
const parsedVersion = parseVersionFromPayload(payload);
|
|
810
|
+
if (!parsedVersion) continue;
|
|
811
|
+
if (!isValidSemver(parsedVersion)) continue;
|
|
812
|
+
if (isBlockedVersion(parsedVersion)) continue;
|
|
813
|
+
|
|
814
|
+
// Success - populate both cache layers.
|
|
815
|
+
const entry: CacheEntry = {
|
|
816
|
+
version: parsedVersion,
|
|
817
|
+
fetchedAt: Date.now(),
|
|
818
|
+
source: url,
|
|
819
|
+
};
|
|
820
|
+
versionMemoryCache.set(MEMORY_CACHE_KEY, entry);
|
|
821
|
+
|
|
822
|
+
const fileEntry: VersionCacheFile = {
|
|
823
|
+
version: parsedVersion,
|
|
824
|
+
fetchedAt: entry.fetchedAt,
|
|
825
|
+
source: url,
|
|
826
|
+
expiresAt: Date.now() + VERSION_FILE_TTL_MS,
|
|
827
|
+
};
|
|
828
|
+
// Fire-and-forget persistence; observer never throws on cache writes.
|
|
829
|
+
try {
|
|
830
|
+
await writeObserverCacheFileAtomic(cachePath, fileEntry);
|
|
831
|
+
} catch {
|
|
832
|
+
// swallow - memory cache already saved
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
return parsedVersion;
|
|
836
|
+
} catch {
|
|
837
|
+
// Per-URL failure swallowed - continue down the chain.
|
|
838
|
+
continue;
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
return null;
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
// ---------------------------------------------------------------------------
|
|
846
|
+
// Section: Public API - legacy chain
|
|
847
|
+
// ---------------------------------------------------------------------------
|
|
848
|
+
|
|
849
|
+
/**
|
|
850
|
+
* Legacy main fetcher - resilient chain:
|
|
851
|
+
* 1. remote manifests (parallel, sequential consumption)
|
|
852
|
+
* 2. changelog scrape
|
|
853
|
+
* 3. expired disk cache as last resort before fallback
|
|
854
|
+
* 4. hardcoded fallback 1.15.8
|
|
855
|
+
*
|
|
856
|
+
* Always resolves with an AntigravityVersionInfo (fallback guaranteed).
|
|
857
|
+
*/
|
|
858
|
+
export async function fetchAntigravityVersion(): Promise<AntigravityVersionInfo> {
|
|
859
|
+
const manifestPromises = REMOTE_MANIFEST_URLS.map((url) => fetchFromManifest(url));
|
|
860
|
+
|
|
861
|
+
for (const p of manifestPromises) {
|
|
862
|
+
try {
|
|
863
|
+
const result = await p;
|
|
864
|
+
if (result && result.version) {
|
|
865
|
+
memoryCache = result;
|
|
866
|
+
await writeCacheFile(result);
|
|
867
|
+
return result;
|
|
868
|
+
}
|
|
869
|
+
} catch {
|
|
870
|
+
// continue chain
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
try {
|
|
875
|
+
const changelogResult = await fetchFromChangelog();
|
|
876
|
+
if (changelogResult && changelogResult.version) {
|
|
877
|
+
memoryCache = changelogResult;
|
|
878
|
+
await writeCacheFile(changelogResult);
|
|
879
|
+
return changelogResult;
|
|
880
|
+
}
|
|
881
|
+
} catch {
|
|
882
|
+
// ignored
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
try {
|
|
886
|
+
if (fs.existsSync(VERSION_CACHE_FILE)) {
|
|
887
|
+
const raw = await fs.promises.readFile(VERSION_CACHE_FILE, "utf8");
|
|
888
|
+
const parsed = JSON.parse(raw) as CachedFilePayload;
|
|
889
|
+
if (parsed.version) {
|
|
890
|
+
const now = Date.now();
|
|
891
|
+
const reused: AntigravityVersionInfo = {
|
|
892
|
+
...parsed,
|
|
893
|
+
fetchedAt: now,
|
|
894
|
+
expiresAt: now + VERSION_CACHE_TTL_MS,
|
|
895
|
+
source: "cache-file",
|
|
896
|
+
};
|
|
897
|
+
memoryCache = reused;
|
|
898
|
+
return reused;
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
} catch {
|
|
902
|
+
// ignored
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
const now = Date.now();
|
|
906
|
+
const fallback: AntigravityVersionInfo = {
|
|
907
|
+
version: FALLBACK_VERSION,
|
|
908
|
+
fullVersion: FALLBACK_FULL_VERSION,
|
|
909
|
+
source: "hardcoded-fallback",
|
|
910
|
+
fetchedAt: now,
|
|
911
|
+
expiresAt: now + VERSION_CACHE_TTL_MS,
|
|
912
|
+
};
|
|
913
|
+
memoryCache = fallback;
|
|
914
|
+
await writeCacheFile(fallback);
|
|
915
|
+
return fallback;
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
/**
|
|
919
|
+
* Legacy lazy getter - memory, then file, then fetch with promise dedup so
|
|
920
|
+
* concurrent calls share one inflight request.
|
|
921
|
+
*/
|
|
922
|
+
export async function getVersionInfo(): Promise<AntigravityVersionInfo> {
|
|
923
|
+
if (memoryCache && Date.now() < memoryCache.expiresAt) {
|
|
924
|
+
return {
|
|
925
|
+
...memoryCache,
|
|
926
|
+
source: memoryCache.source === "hardcoded-fallback" ? memoryCache.source : ("memory-cache" as VersionSource),
|
|
927
|
+
};
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
if (inflightPromise) {
|
|
931
|
+
return inflightPromise;
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
const fileCache = await readCacheFile();
|
|
935
|
+
if (fileCache) {
|
|
936
|
+
memoryCache = fileCache;
|
|
937
|
+
return fileCache;
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
inflightPromise = fetchAntigravityVersion().finally(() => {
|
|
941
|
+
inflightPromise = null;
|
|
942
|
+
});
|
|
943
|
+
|
|
944
|
+
return inflightPromise;
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
/**
|
|
948
|
+
* Synchronous complete-info getter - cache when present, otherwise the
|
|
949
|
+
* legacy fallback object (never touches the network).
|
|
950
|
+
*/
|
|
951
|
+
export function getVersionInfoSync(): AntigravityVersionInfo {
|
|
952
|
+
if (memoryCache && Date.now() < memoryCache.expiresAt) {
|
|
953
|
+
return memoryCache;
|
|
954
|
+
}
|
|
955
|
+
const file = readCacheFileSync();
|
|
956
|
+
if (file) {
|
|
957
|
+
memoryCache = file;
|
|
958
|
+
return file;
|
|
959
|
+
}
|
|
960
|
+
const now = Date.now();
|
|
961
|
+
return {
|
|
962
|
+
version: FALLBACK_VERSION,
|
|
963
|
+
fullVersion: FALLBACK_FULL_VERSION,
|
|
964
|
+
source: "hardcoded-fallback",
|
|
965
|
+
fetchedAt: now,
|
|
966
|
+
expiresAt: now + VERSION_CACHE_TTL_MS,
|
|
967
|
+
};
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
/** Invalidates both legacy caches (memory + disk). */
|
|
971
|
+
export async function invalidateVersionCache(): Promise<void> {
|
|
972
|
+
memoryCache = null;
|
|
973
|
+
inflightPromise = null;
|
|
974
|
+
try {
|
|
975
|
+
await fs.promises.unlink(VERSION_CACHE_FILE);
|
|
976
|
+
} catch {
|
|
977
|
+
// missing or no permission - ignored
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
/** Invalidates only the legacy memory cache. */
|
|
982
|
+
export function invalidateMemoryCache(): void {
|
|
983
|
+
memoryCache = null;
|
|
984
|
+
inflightPromise = null;
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
// ---------------------------------------------------------------------------
|
|
988
|
+
// Section: Public API - merged getters (both behaviors preserved)
|
|
989
|
+
// ---------------------------------------------------------------------------
|
|
990
|
+
|
|
991
|
+
/**
|
|
992
|
+
* Merged lazy getter supporting BOTH historical call shapes:
|
|
993
|
+
*
|
|
994
|
+
* - `getVersion()` - legacy behavior: resolves through manifests/changelog,
|
|
995
|
+
* caches 6h in memory + disk, never rejects.
|
|
996
|
+
* - `getVersion({...})` - observer behavior: honors force/timeoutMs/
|
|
997
|
+
* remoteUrls/cachePath/userAgent/allowStale, gates results through
|
|
998
|
+
* isSupportedVersion/isBlockedVersion, persists atomically.
|
|
999
|
+
*
|
|
1000
|
+
* Resolution order: observer memory Map -> legacy memory info ->
|
|
1001
|
+
* observer remote chain (npm/GitHub/GCS) -> legacy remote chain
|
|
1002
|
+
* (manifests/changelog, results rejected only when explicitly blocked or
|
|
1003
|
+
* merely the legacy hardcoded fallback) -> both file caches ->
|
|
1004
|
+
* governed fallback ANTIGRAVITY_VERSION_FALLBACK ("1.19.2").
|
|
1005
|
+
*/
|
|
1006
|
+
export async function getVersion(opts: GetVersionOptions = {}): Promise<string> {
|
|
1007
|
+
const force = opts.force === true;
|
|
1008
|
+
|
|
1009
|
+
// 0a) observer memory cache
|
|
1010
|
+
if (!force) {
|
|
1011
|
+
const mem = versionMemoryCache.get(MEMORY_CACHE_KEY);
|
|
1012
|
+
if (mem) {
|
|
1013
|
+
const cleaned = cleanSemver(mem.version);
|
|
1014
|
+
if (cleaned && Date.now() - mem.fetchedAt < VERSION_MEMORY_TTL_MS && isSupportedVersion(cleaned)) {
|
|
1015
|
+
return cleaned;
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
// 0b) legacy memory info cache
|
|
1019
|
+
if (memoryCache && Date.now() < memoryCache.expiresAt && !isBlockedVersion(memoryCache.version)) {
|
|
1020
|
+
return memoryCache.version;
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
// 1) remote chains: observer first (gated), then legacy manifests/changelog.
|
|
1025
|
+
let resolved: string | null = null;
|
|
1026
|
+
try {
|
|
1027
|
+
const remote = await fetchLatestVersion(opts);
|
|
1028
|
+
if (remote && isSupportedVersion(remote)) {
|
|
1029
|
+
resolved = remote;
|
|
1030
|
+
}
|
|
1031
|
+
} catch {
|
|
1032
|
+
// swallowed - fall through
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
if (!resolved) {
|
|
1036
|
+
try {
|
|
1037
|
+
const legacy = await fetchAntigravityVersion();
|
|
1038
|
+
if (legacy && legacy.version && legacy.source !== "hardcoded-fallback" && !isBlockedVersion(legacy.version)) {
|
|
1039
|
+
resolved = legacy.version;
|
|
1040
|
+
versionMemoryCache.set(MEMORY_CACHE_KEY, {
|
|
1041
|
+
version: legacy.version,
|
|
1042
|
+
fetchedAt: legacy.fetchedAt,
|
|
1043
|
+
source: legacy.url ?? legacy.source,
|
|
1044
|
+
});
|
|
1045
|
+
}
|
|
1046
|
+
} catch {
|
|
1047
|
+
// swallowed - fall through
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
if (resolved) return resolved;
|
|
1052
|
+
|
|
1053
|
+
// 2) file caches: observer path (stale allowed by default) + legacy path.
|
|
1054
|
+
const allowStale = opts.allowStale ?? true;
|
|
1055
|
+
const observerPath = resolveObserverCachePath(opts.cachePath);
|
|
1056
|
+
|
|
1057
|
+
try {
|
|
1058
|
+
const fileCache = await readObserverCacheFileAsync(observerPath);
|
|
1059
|
+
if (fileCache) {
|
|
1060
|
+
const fresh = Date.now() < fileCache.expiresAt;
|
|
1061
|
+
if (fresh || allowStale) {
|
|
1062
|
+
const cleaned = cleanSemver(fileCache.version);
|
|
1063
|
+
if (cleaned && isSupportedVersion(cleaned)) {
|
|
1064
|
+
versionMemoryCache.set(MEMORY_CACHE_KEY, {
|
|
1065
|
+
version: cleaned,
|
|
1066
|
+
fetchedAt: fileCache.fetchedAt,
|
|
1067
|
+
source: fileCache.source,
|
|
1068
|
+
});
|
|
1069
|
+
return cleaned;
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
} catch {
|
|
1074
|
+
// swallowed
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
try {
|
|
1078
|
+
const legacyFile = await readCacheFile();
|
|
1079
|
+
if (legacyFile && !isBlockedVersion(legacyFile.version)) {
|
|
1080
|
+
return legacyFile.version;
|
|
1081
|
+
}
|
|
1082
|
+
} catch {
|
|
1083
|
+
// swallowed
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
// 3) governed fallback - guaranteed valid semver.
|
|
1087
|
+
const fallback = ANTIGRAVITY_VERSION_FALLBACK;
|
|
1088
|
+
versionMemoryCache.set(MEMORY_CACHE_KEY, {
|
|
1089
|
+
version: fallback,
|
|
1090
|
+
fetchedAt: Date.now(),
|
|
1091
|
+
source: "fallback",
|
|
1092
|
+
});
|
|
1093
|
+
return fallback;
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
/**
|
|
1097
|
+
* Merged synchronous getter (no network). Supports both call shapes:
|
|
1098
|
+
* `getVersionSync()` (legacy) and `getVersionSync({cachePath,allowStale})`
|
|
1099
|
+
* (observer). Reads observer Map -> legacy memory -> observer file ->
|
|
1100
|
+
* legacy file -> governed fallback 1.19.2.
|
|
1101
|
+
*/
|
|
1102
|
+
export function getVersionSync(opts: { cachePath?: string; allowStale?: boolean } = {}): string {
|
|
1103
|
+
const allowStale = opts.allowStale ?? true;
|
|
1104
|
+
const observerPath = resolveObserverCachePath(opts.cachePath);
|
|
1105
|
+
|
|
1106
|
+
const mem = versionMemoryCache.get(MEMORY_CACHE_KEY);
|
|
1107
|
+
if (mem) {
|
|
1108
|
+
const cleaned = cleanSemver(mem.version);
|
|
1109
|
+
if (cleaned && Date.now() - mem.fetchedAt < VERSION_MEMORY_TTL_MS && isSupportedVersion(cleaned)) {
|
|
1110
|
+
return cleaned;
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
if (memoryCache && Date.now() < memoryCache.expiresAt && !isBlockedVersion(memoryCache.version)) {
|
|
1115
|
+
return memoryCache.version;
|
|
1116
|
+
}
|
|
1117
|
+
|
|
1118
|
+
const fileCache = readObserverCacheFileSync(observerPath);
|
|
1119
|
+
if (fileCache) {
|
|
1120
|
+
const fresh = Date.now() < fileCache.expiresAt;
|
|
1121
|
+
if (fresh || allowStale) {
|
|
1122
|
+
const cleaned = cleanSemver(fileCache.version);
|
|
1123
|
+
if (cleaned && isSupportedVersion(cleaned)) {
|
|
1124
|
+
versionMemoryCache.set(MEMORY_CACHE_KEY, {
|
|
1125
|
+
version: cleaned,
|
|
1126
|
+
fetchedAt: fileCache.fetchedAt,
|
|
1127
|
+
source: fileCache.source,
|
|
1128
|
+
});
|
|
1129
|
+
return cleaned;
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
const legacyFile = readCacheFileSync();
|
|
1135
|
+
if (legacyFile && !isBlockedVersion(legacyFile.version)) {
|
|
1136
|
+
return legacyFile.version;
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
return ANTIGRAVITY_VERSION_FALLBACK;
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1142
|
+
// ---------------------------------------------------------------------------
|
|
1143
|
+
// Section: Cache invalidation - observer chain
|
|
1144
|
+
// ---------------------------------------------------------------------------
|
|
1145
|
+
|
|
1146
|
+
/**
|
|
1147
|
+
* Clears the observer memory entry and removes its cache file.
|
|
1148
|
+
* Use {@link invalidateVersionCache} for the legacy stores.
|
|
1149
|
+
*/
|
|
1150
|
+
export async function clearVersionCache(opts: { cachePath?: string } = {}): Promise<void> {
|
|
1151
|
+
versionMemoryCache.delete(MEMORY_CACHE_KEY);
|
|
1152
|
+
const cachePath = resolveObserverCachePath(opts.cachePath);
|
|
1153
|
+
try {
|
|
1154
|
+
await fsp.unlink(cachePath);
|
|
1155
|
+
} catch {
|
|
1156
|
+
// ignore missing
|
|
1157
|
+
}
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1160
|
+
/** Synchronous variant of {@link clearVersionCache}. */
|
|
1161
|
+
export function clearVersionCacheSync(opts: { cachePath?: string } = {}): void {
|
|
1162
|
+
versionMemoryCache.delete(MEMORY_CACHE_KEY);
|
|
1163
|
+
const cachePath = resolveObserverCachePath(opts.cachePath);
|
|
1164
|
+
try {
|
|
1165
|
+
fs.unlinkSync(cachePath);
|
|
1166
|
+
} catch {
|
|
1167
|
+
// ignore missing
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1171
|
+
// ---------------------------------------------------------------------------
|
|
1172
|
+
// Section: User-Agent helpers
|
|
1173
|
+
// ---------------------------------------------------------------------------
|
|
1174
|
+
|
|
1175
|
+
/**
|
|
1176
|
+
* Dynamic legacy UA varying platform (darwin/linux/win32) for coherent
|
|
1177
|
+
* bypass; embeds the fetched Antigravity version as a safe comment segment.
|
|
1178
|
+
*/
|
|
1179
|
+
// local variant: diverges from constants (async template-based GeminiCLI UA vs constants' sync antigravity/{ver} {os}/{arch} UA)
|
|
1180
|
+
export async function getVersionedUserAgent(): Promise<string> {
|
|
1181
|
+
const platform = os.platform();
|
|
1182
|
+
const arch = os.arch();
|
|
1183
|
+
const key = `${platform}_${arch}` as keyof typeof ANTIGRAVITY_AUTH_CONSTANTS.USER_AGENT_TEMPLATES;
|
|
1184
|
+
const template =
|
|
1185
|
+
ANTIGRAVITY_AUTH_CONSTANTS.USER_AGENT_TEMPLATES[key] ||
|
|
1186
|
+
(platform === "darwin"
|
|
1187
|
+
? ANTIGRAVITY_AUTH_CONSTANTS.USER_AGENT_TEMPLATES.darwin_arm64
|
|
1188
|
+
: ANTIGRAVITY_AUTH_CONSTANTS.USER_AGENT_TEMPLATES.linux_x64);
|
|
1189
|
+
|
|
1190
|
+
const ver = await getVersion().catch(() => FALLBACK_VERSION);
|
|
1191
|
+
return template.replace("GitHub)", `GitHub; Antigravity/${ver})`);
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1194
|
+
/** Simple semver gt comparison retained from the legacy API. */
|
|
1195
|
+
export function isNewerVersion(a: string, b: string): boolean {
|
|
1196
|
+
const toNums = (s: string) => (s.split("-")[0] ?? "").split(".").map((n) => parseInt(n, 10) || 0);
|
|
1197
|
+
const A = toNums(a);
|
|
1198
|
+
const B = toNums(b);
|
|
1199
|
+
for (let i = 0; i < Math.max(A.length, B.length); i++) {
|
|
1200
|
+
const av = A[i] || 0;
|
|
1201
|
+
const bv = B[i] || 0;
|
|
1202
|
+
if (av > bv) return true;
|
|
1203
|
+
if (av < bv) return false;
|
|
1204
|
+
}
|
|
1205
|
+
return false;
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1208
|
+
/**
|
|
1209
|
+
* Observer-style User-Agent for a given version:
|
|
1210
|
+
* `antigravity/{version} {platform}/{arch}` (coherent per host).
|
|
1211
|
+
* @param version - semver; falls back to ANTIGRAVITY_VERSION_FALLBACK
|
|
1212
|
+
*/
|
|
1213
|
+
export function getAntigravityUserAgent(version?: string): string {
|
|
1214
|
+
const ver = cleanSemver(version ?? "") ?? ANTIGRAVITY_VERSION_FALLBACK;
|
|
1215
|
+
const plat = os.platform();
|
|
1216
|
+
const arch = os.arch();
|
|
1217
|
+
return `antigravity/${ver} ${plat}/${arch}`;
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1220
|
+
/**
|
|
1221
|
+
* Async observer UA ensuring the latest version first. Pass cachedVersion to
|
|
1222
|
+
* avoid network round-trips.
|
|
1223
|
+
*/
|
|
1224
|
+
export async function getDynamicUserAgentVersioned(cachedVersion?: string, opts: FetchOptions = {}): Promise<string> {
|
|
1225
|
+
const ver = cachedVersion && isValidSemver(cachedVersion) ? cachedVersion : await getVersion(opts);
|
|
1226
|
+
return getAntigravityUserAgent(ver);
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1229
|
+
// ---------------------------------------------------------------------------
|
|
1230
|
+
// Section: Barrels
|
|
1231
|
+
// ---------------------------------------------------------------------------
|
|
1232
|
+
|
|
1233
|
+
/** Observer barrel export. */
|
|
1234
|
+
export const Version = {
|
|
1235
|
+
fallback: ANTIGRAVITY_VERSION_FALLBACK,
|
|
1236
|
+
minSupported: ANTIGRAVITY_VERSION_MIN_SUPPORTED,
|
|
1237
|
+
blocked: ANTIGRAVITY_BLOCKED_VERSIONS,
|
|
1238
|
+
remoteUrls: ANTIGRAVITY_VERSION_REMOTE_URLS,
|
|
1239
|
+
timeoutMs: VERSION_FETCH_TIMEOUT_MS,
|
|
1240
|
+
memoryTtlMs: VERSION_MEMORY_TTL_MS,
|
|
1241
|
+
fileTtlMs: VERSION_FILE_TTL_MS,
|
|
1242
|
+
memoryCache: versionMemoryCache,
|
|
1243
|
+
isValidSemver,
|
|
1244
|
+
cleanSemver,
|
|
1245
|
+
compareSemver,
|
|
1246
|
+
shouldUpdate,
|
|
1247
|
+
isBlockedVersion,
|
|
1248
|
+
isSupportedVersion,
|
|
1249
|
+
parseVersionFromPayload,
|
|
1250
|
+
fetchLatestVersion,
|
|
1251
|
+
getVersion,
|
|
1252
|
+
getVersionSync,
|
|
1253
|
+
clearVersionCache,
|
|
1254
|
+
clearVersionCacheSync,
|
|
1255
|
+
getAntigravityUserAgent,
|
|
1256
|
+
getDynamicUserAgentVersioned,
|
|
1257
|
+
} as const;
|
|
1258
|
+
|
|
1259
|
+
/** Default export - full merged module barrel (legacy + observer). */
|
|
1260
|
+
const versionModule = {
|
|
1261
|
+
// Legacy surface
|
|
1262
|
+
REMOTE_MANIFEST_URLS,
|
|
1263
|
+
CHANGELOG_URLS,
|
|
1264
|
+
FALLBACK_VERSION,
|
|
1265
|
+
FALLBACK_FULL_VERSION,
|
|
1266
|
+
FALLBACK_ALTERNATIVE_VERSION,
|
|
1267
|
+
FALLBACK_ALTERNATIVE_FULL,
|
|
1268
|
+
VERSION_CACHE_FILE,
|
|
1269
|
+
VERSION_CACHE_TTL_MS,
|
|
1270
|
+
FETCH_TIMEOUT_MS,
|
|
1271
|
+
ANTIGRAVITY_AUTH_CONSTANTS,
|
|
1272
|
+
fetchAntigravityVersion,
|
|
1273
|
+
getVersion,
|
|
1274
|
+
getVersionInfo,
|
|
1275
|
+
getVersionSync,
|
|
1276
|
+
getVersionInfoSync,
|
|
1277
|
+
parseAntigravityVersion,
|
|
1278
|
+
fetchWithTimeout,
|
|
1279
|
+
readCacheFile,
|
|
1280
|
+
readCacheFileSync,
|
|
1281
|
+
writeCacheFile,
|
|
1282
|
+
invalidateVersionCache,
|
|
1283
|
+
invalidateMemoryCache,
|
|
1284
|
+
getVersionedUserAgent,
|
|
1285
|
+
isNewerVersion,
|
|
1286
|
+
// Observer surface
|
|
1287
|
+
ANTIGRAVITY_VERSION_FALLBACK,
|
|
1288
|
+
ANTIGRAVITY_VERSION_MIN_SUPPORTED,
|
|
1289
|
+
ANTIGRAVITY_BLOCKED_VERSIONS,
|
|
1290
|
+
ANTIGRAVITY_VERSION_REMOTE_URLS,
|
|
1291
|
+
VERSION_FETCH_TIMEOUT_MS,
|
|
1292
|
+
VERSION_MEMORY_TTL_MS,
|
|
1293
|
+
VERSION_FILE_TTL_MS,
|
|
1294
|
+
versionMemoryCache,
|
|
1295
|
+
isValidSemver,
|
|
1296
|
+
cleanSemver,
|
|
1297
|
+
compareSemver,
|
|
1298
|
+
shouldUpdate,
|
|
1299
|
+
isBlockedVersion,
|
|
1300
|
+
isSupportedVersion,
|
|
1301
|
+
parseVersionFromPayload,
|
|
1302
|
+
fetchLatestVersion,
|
|
1303
|
+
clearVersionCache,
|
|
1304
|
+
clearVersionCacheSync,
|
|
1305
|
+
getAntigravityUserAgent,
|
|
1306
|
+
getDynamicUserAgentVersioned,
|
|
1307
|
+
Version,
|
|
1308
|
+
};
|
|
1309
|
+
|
|
1310
|
+
export default versionModule;
|