@wenathlan/devthink 2.0.15 → 2.0.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +45 -0
- package/{dist/bridge.d.ts → bridge.d.ts} +1 -1
- package/bridge.d.ts.map +1 -0
- package/bridge.js +1877 -0
- package/bun.d.ts.map +1 -0
- package/bun.js +52329 -0
- package/caps/background.json +712 -0
- package/caps/cli.json +360 -0
- package/caps/library.json +3611 -0
- package/caps/mcp.json +126 -0
- package/caps/pagebridge.json +377 -0
- package/caps/popup.json +51 -0
- package/caps/sidepanel.json +522 -0
- package/checksums.txt +142 -0
- package/cli.d.ts.map +1 -0
- package/{dist/cli.js → cli.js} +2 -2
- package/companion.d.ts.map +1 -0
- package/companion.js +389 -0
- package/crossbrowser.d.ts.map +1 -0
- package/crossbrowser.js +1191 -0
- package/dashdone.d.ts.map +1 -0
- package/dashdone.js +368 -0
- package/deno.d.ts.map +1 -0
- package/deno.js +52325 -0
- package/devthink.umd.js +52346 -0
- package/fixtures/bun.ts +7 -0
- package/fixtures/cjs.js +6 -0
- package/fixtures/deno.ts +6 -0
- package/fixtures/example-org-pagestate.json +22 -0
- package/fixtures/importers/automa-workflow.json +18 -0
- package/fixtures/importers/selenium-side.json +20 -0
- package/fixtures/importers/tabular-plan.csv +7 -0
- package/fixtures/importers/uivision-macro.json +10 -0
- package/fixtures/importers/v1-plan.json +13 -0
- package/fixtures/mcp-client.mjs +14 -0
- package/fixtures/pages/checkout.html +192 -0
- package/fixtures/pages/dashboard.html +110 -0
- package/fixtures/pages/feed.html +113 -0
- package/fixtures/pages/product-grid.html +150 -0
- package/fixtures/plans/form-inventory-plan.json +11 -0
- package/fixtures/plans/release-notes-plan.json +11 -0
- package/fixtures/recipes/agentscompete.json +11 -0
- package/fixtures/recipes/agentsforms.json +11 -0
- package/fixtures/recipes/agentsmonitor.json +11 -0
- package/fixtures/recipes/agentsreview.json +29 -0
- package/fixtures/recipes/agentswarm.json +11 -0
- package/fixtures/recipes/fillcalendar.json +12 -0
- package/fixtures/recipes/fillcheckout.json +13 -0
- package/fixtures/recipes/filldependentdropdowns.json +12 -0
- package/fixtures/recipes/fillfromcsv.json +13 -0
- package/fixtures/recipes/fillloginform.json +12 -0
- package/fixtures/recipes/fillmultistepwizard.json +13 -0
- package/fixtures/recipes/fillregistration.json +12 -0
- package/fixtures/recipes/fillsearchfilters.json +12 -0
- package/fixtures/recipes/filltypeahead.json +11 -0
- package/fixtures/recipes/monitoralerts.json +12 -0
- package/fixtures/recipes/monitoravailability.json +11 -0
- package/fixtures/recipes/monitornetworkquiet.json +12 -0
- package/fixtures/recipes/monitorpagechanges.json +11 -0
- package/fixtures/recipes/monitorpricechange.json +11 -0
- package/fixtures/recipes/monitorschedule.json +12 -0
- package/fixtures/recipes/scrapedynamicprices.json +13 -0
- package/fixtures/recipes/scrapeimagegallery.json +12 -0
- package/fixtures/recipes/scrapeinfinitefeed.json +13 -0
- package/fixtures/recipes/scrapenestedlists.json +12 -0
- package/fixtures/recipes/scrapeproductgrid.json +12 -0
- package/fixtures/recipes/scrapereviews.json +12 -0
- package/fixtures/recipes/scrapesearchresults.json +12 -0
- package/fixtures/recipes/scrapetablepagination.json +12 -0
- package/fixtures/recipes/testaccessibility.json +34 -0
- package/fixtures/recipes/testconsoleerrors.json +12 -0
- package/fixtures/recipes/testdeeplinks.json +12 -0
- package/fixtures/recipes/testformvalidation.json +13 -0
- package/fixtures/recipes/testlayoutshift.json +12 -0
- package/fixtures/recipes/testlinkcheck.json +12 -0
- package/fixtures/recipes/testloadtiming.json +12 -0
- package/fixtures/recipes/testvisualdiff.json +12 -0
- package/fixtures/workflows/changelog-digest-workflow.json +13 -0
- package/fixtures/workflows/form-survey-workflow.json +13 -0
- package/gallery.json +706 -0
- package/hardening.d.ts.map +1 -0
- package/hardening.js +188 -0
- package/headless.d.ts.map +1 -0
- package/headless.js +11522 -0
- package/http.d.ts.map +1 -0
- package/http.js +2165 -0
- package/index.cjs +54682 -0
- package/index.d.ts.map +1 -0
- package/index.js +52310 -0
- package/index.neutral.js +46343 -0
- package/{web/manifest.json → manifest.json} +1 -1
- package/mcp.d.ts.map +1 -0
- package/mcp.js +20515 -0
- package/memory.d.ts.map +1 -0
- package/{memory.ts → memory.js} +2310 -3834
- package/nativehost.template.json +10 -0
- package/node.cjs +54686 -0
- package/node.d.ts.map +1 -0
- package/pack.d.ts.map +1 -0
- package/pack.js +805 -0
- package/package.json +83 -176
- package/policy.d.ts.map +1 -0
- package/{policy.ts → policy.js} +4679 -5073
- package/progress.d.ts.map +1 -0
- package/progress.js +645 -0
- package/protocol.d.ts.map +1 -0
- package/protocol.js +8334 -0
- package/schemas/audit.schema.json +226 -0
- package/schemas/capability.schema.json +113 -0
- package/schemas/envelope.schema.json +162 -0
- package/schemas/memory.schema.json +51 -0
- package/schemas/observation.schema.json +113 -0
- package/schemas/plan.schema.json +208 -0
- package/schemas/progress.schema.json +126 -0
- package/schemas/proposal.schema.json +68 -0
- package/schemas/session.schema.json +82 -0
- package/schemas/tool.schema.json +94 -0
- package/server.d.ts.map +1 -0
- package/server.js +28877 -0
- package/umd-example.html +18 -0
- package/umd.d.ts.map +1 -0
- package/accounts.ts +0 -2087
- package/agent.ts +0 -2331
- package/antigravity.ts +0 -2139
- package/apifreeze.ts +0 -4146
- package/auth.ts +0 -642
- package/background.ts +0 -16837
- package/bridge.ts +0 -2080
- package/build.ts +0 -54
- package/bun.ts +0 -26
- package/capture.ts +0 -1614
- package/checksecrets.ts +0 -72
- package/cjs.ts +0 -9
- package/cli.ts +0 -3500
- package/commands.ts +0 -1101
- package/companion.ts +0 -450
- package/compatibility.ts +0 -37
- package/config.ts +0 -3137
- package/constants.ts +0 -943
- package/core.ts +0 -1473
- package/crossbrowser.ts +0 -1359
- package/dashdone.ts +0 -605
- package/data.ts +0 -943
- package/database.ts +0 -133
- package/debug.ts +0 -850
- package/deno.ts +0 -27
- package/devthink.ts +0 -707
- package/dist/accounts.d.ts +0 -409
- package/dist/agent.d.ts +0 -837
- package/dist/antigravity.d.ts +0 -380
- package/dist/apifreeze.d.ts +0 -113
- package/dist/auth.d.ts +0 -224
- package/dist/capture.d.ts +0 -595
- package/dist/cjs.d.ts +0 -6
- package/dist/commands.d.ts +0 -310
- package/dist/compatibility.d.ts +0 -17
- package/dist/config.d.ts +0 -1853
- package/dist/constants.d.ts +0 -637
- package/dist/core.d.ts +0 -361
- package/dist/data.d.ts +0 -330
- package/dist/database.d.ts +0 -24
- package/dist/debug.d.ts +0 -270
- package/dist/engine.d.ts +0 -74
- package/dist/environments.d.ts +0 -244
- package/dist/evidence.d.ts +0 -277
- package/dist/export.d.ts +0 -311
- package/dist/fingerprint.d.ts +0 -594
- package/dist/flow.d.ts +0 -300
- package/dist/gates.d.ts +0 -103
- package/dist/identity.d.ts +0 -51
- package/dist/ids.d.ts +0 -7
- package/dist/llm.d.ts +0 -652
- package/dist/models.d.ts +0 -1807
- package/dist/net.d.ts +0 -325
- package/dist/neutral.d.ts +0 -41
- package/dist/oauth.d.ts +0 -703
- package/dist/page.d.ts +0 -1166
- package/dist/perf.d.ts +0 -481
- package/dist/plan.d.ts +0 -134
- package/dist/project.d.ts +0 -395
- package/dist/providers.d.ts +0 -37
- package/dist/quota.d.ts +0 -453
- package/dist/recovery.d.ts +0 -621
- package/dist/request.d.ts +0 -1492
- package/dist/run.d.ts +0 -779
- package/dist/runtime.d.ts +0 -212
- package/dist/search.d.ts +0 -375
- package/dist/security.d.ts +0 -534
- package/dist/serve.d.ts +0 -136
- package/dist/session.d.ts +0 -296
- package/dist/storage.d.ts +0 -53
- package/dist/streaming.d.ts +0 -1119
- package/dist/swarm.d.ts +0 -328
- package/dist/tools.d.ts +0 -170
- package/dist/types.d.ts +0 -8357
- package/dist/utils.d.ts +0 -82
- package/dist/version.d.ts +0 -7
- package/dist/versionregistry.d.ts +0 -406
- package/dist/views.d.ts +0 -579
- package/dist/workbenchsession.d.ts +0 -69
- package/dist/workflow.d.ts +0 -649
- package/docs/readme.md +0 -92
- package/docs/schemas/antigravity.json +0 -192
- package/engine.ts +0 -1524
- package/environments.ts +0 -1005
- package/evidence.ts +0 -643
- package/export.ts +0 -889
- package/fingerprint.ts +0 -1458
- package/flow.ts +0 -1190
- package/gates.ts +0 -307
- package/gateway.ts +0 -34
- package/hardening.ts +0 -191
- package/headless.ts +0 -369
- package/http.ts +0 -2110
- package/identity.ts +0 -246
- package/ids.ts +0 -26
- package/index.ts +0 -369
- package/llm.ts +0 -2682
- package/mcp.ts +0 -1835
- package/models.ts +0 -3805
- package/modes.ts +0 -43
- package/net.ts +0 -1214
- package/neutral.ts +0 -362
- package/node.ts +0 -26
- package/oauth.ts +0 -2216
- package/offscreen.ts +0 -170
- package/pack.ts +0 -956
- package/page.ts +0 -7824
- package/pagebridge.ts +0 -1116
- package/perf.ts +0 -957
- package/plan.ts +0 -672
- package/plugin.ts +0 -76
- package/prisma.config.ts +0 -27
- package/progress.ts +0 -1308
- package/project.ts +0 -1291
- package/protocol.ts +0 -4187
- package/providers.ts +0 -246
- package/quota.ts +0 -1887
- package/recovery.ts +0 -2433
- package/request.ts +0 -4482
- package/run.ts +0 -2135
- package/runtime.ts +0 -526
- package/search.ts +0 -1760
- package/security.ts +0 -1673
- package/serve.ts +0 -419
- package/server.ts +0 -5538
- package/session.ts +0 -1162
- package/storage.ts +0 -182
- package/streaming.ts +0 -4101
- package/swarm.ts +0 -884
- package/sync.ts +0 -56
- package/tests/agentcert.test.ts +0 -128
- package/tests/agentfleet.test.ts +0 -214
- package/tests/agentreview.test.ts +0 -241
- package/tests/agentstream.test.ts +0 -335
- package/tests/agentwork.test.ts +0 -356
- package/tests/apifreeze.test.ts +0 -290
- package/tests/apimap.test.ts +0 -112
- package/tests/approvalgate.test.ts +0 -175
- package/tests/artifactmanifest.test.ts +0 -204
- package/tests/attentionfeed.test.ts +0 -163
- package/tests/backgroundruns.test.ts +0 -100
- package/tests/batchscheduling.test.ts +0 -131
- package/tests/blackboard.test.ts +0 -238
- package/tests/bridge.test.ts +0 -1088
- package/tests/browsercoverage.test.ts +0 -134
- package/tests/browserpolyfills.test.ts +0 -124
- package/tests/build.mjs +0 -1260
- package/tests/clientauth.test.ts +0 -223
- package/tests/clitools.test.ts +0 -578
- package/tests/confirmgates.test.ts +0 -190
- package/tests/containerpack.test.ts +0 -143
- package/tests/controlflow.test.ts +0 -2120
- package/tests/coordination.test.ts +0 -345
- package/tests/copyscreen.test.ts +0 -139
- package/tests/core.test.ts +0 -417
- package/tests/costcert.test.ts +0 -95
- package/tests/crossbrowser.test.ts +0 -115
- package/tests/cspaudit.test.ts +0 -79
- package/tests/dashdone.test.ts +0 -615
- package/tests/datagrid.test.ts +0 -137
- package/tests/doccheck.test.ts +0 -107
- package/tests/dombatch.test.ts +0 -109
- package/tests/emulation.test.ts +0 -282
- package/tests/environments.test.ts +0 -259
- package/tests/evidenceviews.test.ts +0 -88
- package/tests/exporttools.test.ts +0 -185
- package/tests/firefoxprep.test.ts +0 -134
- package/tests/flowlibrary.test.ts +0 -242
- package/tests/flowrun.test.ts +0 -303
- package/tests/forensics.test.ts +0 -439
- package/tests/gateway.test.ts +0 -939
- package/tests/headlesslib.test.ts +0 -171
- package/tests/httpstream.test.ts +0 -277
- package/tests/immutablelog.test.ts +0 -157
- package/tests/inboundguard.test.ts +0 -177
- package/tests/lazyload.test.ts +0 -72
- package/tests/librarymodes.test.ts +0 -623
- package/tests/llm.test.ts +0 -696
- package/tests/manifest.test.ts +0 -226
- package/tests/maskinputs.test.ts +0 -150
- package/tests/matrixverify.test.ts +0 -111
- package/tests/mavenpack.test.ts +0 -75
- package/tests/mcpserver.test.ts +0 -704
- package/tests/memory.test.ts +0 -4155
- package/tests/memorycare.test.ts +0 -230
- package/tests/migrateplan.test.ts +0 -349
- package/tests/minimization.test.ts +0 -414
- package/tests/modelroute.test.ts +0 -112
- package/tests/multiagent.test.ts +0 -440
- package/tests/nativebridge.test.ts +0 -807
- package/tests/navigation.test.ts +0 -284
- package/tests/nugetpack.test.ts +0 -83
- package/tests/orchestration.test.ts +0 -449
- package/tests/originpolicy.test.ts +0 -405
- package/tests/outputcompare.test.ts +0 -97
- package/tests/package.test.ts +0 -91
- package/tests/pagebridge.test.ts +0 -5909
- package/tests/pentest.test.ts +0 -75
- package/tests/perfrecords.test.ts +0 -257
- package/tests/permdiff.test.ts +0 -85
- package/tests/phishguard.test.ts +0 -128
- package/tests/pickerviews.test.ts +0 -155
- package/tests/pipelines.test.ts +0 -381
- package/tests/planlint.test.ts +0 -240
- package/tests/planreview.test.ts +0 -383
- package/tests/platformtargets.test.ts +0 -56
- package/tests/policy.test.ts +0 -8284
- package/tests/portability.test.ts +0 -96
- package/tests/progress.test.ts +0 -1036
- package/tests/promptlibrary.test.ts +0 -116
- package/tests/protocol.test.ts +0 -4793
- package/tests/publishingpipeline.test.ts +0 -314
- package/tests/quickactions.test.ts +0 -164
- package/tests/readiness.test.ts +0 -78
- package/tests/recipes.test.ts +0 -163
- package/tests/redactshots.test.ts +0 -216
- package/tests/registernvidiakeys.ts +0 -51
- package/tests/relayserve.test.ts +0 -304
- package/tests/releasenotes.test.ts +0 -276
- package/tests/resilience.test.ts +0 -467
- package/tests/resourceaware.test.ts +0 -187
- package/tests/runbudget.test.ts +0 -125
- package/tests/runhistory.test.ts +0 -299
- package/tests/runreplay.test.ts +0 -113
- package/tests/runresume.test.ts +0 -184
- package/tests/runstate.test.ts +0 -288
- package/tests/runtimeadapters.test.ts +0 -251
- package/tests/safariskeleton.test.ts +0 -115
- package/tests/sandboxframe.test.ts +0 -196
- package/tests/sbom.test.ts +0 -105
- package/tests/secretvault.test.ts +0 -188
- package/tests/selcache.test.ts +0 -111
- package/tests/sessioninterface.test.ts +0 -791
- package/tests/sessions.test.ts +0 -591
- package/tests/siteprefs.test.ts +0 -228
- package/tests/snapshotdelta.test.ts +0 -165
- package/tests/statusviews.test.ts +0 -237
- package/tests/stepmeter.test.ts +0 -200
- package/tests/streamparse.test.ts +0 -163
- package/tests/stylepruning.test.ts +0 -90
- package/tests/surfaces.test.ts +0 -282
- package/tests/sweep.test.ts +0 -78
- package/tests/syncbridge.test.ts +0 -164
- package/tests/taskqueue.test.ts +0 -206
- package/tests/toolcalls.test.ts +0 -336
- package/tests/toolcatalog.test.ts +0 -201
- package/tests/torture-agent.test.ts +0 -706
- package/tests/torture-auth.test.ts +0 -461
- package/tests/torture-commands.test.ts +0 -895
- package/tests/torture-data.test.ts +0 -923
- package/tests/torture-environments.test.ts +0 -285
- package/tests/torture-export.test.ts +0 -263
- package/tests/torture-http.test.ts +0 -1379
- package/tests/torture-llm.test.ts +0 -808
- package/tests/torture-memory.test.ts +0 -1084
- package/tests/torture-plan.test.ts +0 -829
- package/tests/torture-run.test.ts +0 -626
- package/tests/torture-session.test.ts +0 -928
- package/tests/torture-swarm.test.ts +0 -1245
- package/tests/torture-tools.test.ts +0 -964
- package/tests/torture-workflow.test.ts +0 -2354
- package/tests/tourviews.test.ts +0 -52
- package/tests/transparency.test.ts +0 -147
- package/tests/trigger.test.ts +0 -941
- package/tests/v1sunset.test.ts +0 -428
- package/tests/virtlist.test.ts +0 -94
- package/tests/vision.test.ts +0 -540
- package/tests/vsixpack.test.ts +0 -145
- package/tests/webapi.test.ts +0 -523
- package/tests/workflow.test.ts +0 -1785
- package/tests/workfloweditor.test.ts +0 -759
- package/tests/xpipack.test.ts +0 -128
- package/tools.ts +0 -734
- package/types.ts +0 -9908
- package/umd.ts +0 -9
- package/utils.ts +0 -249
- package/version.ts +0 -8
- package/versionregistry.ts +0 -1310
- package/views.ts +0 -2153
- package/vitest.config.ts +0 -12
- package/web/App.tsx +0 -78
- package/web/README.md +0 -47
- package/web/capacitor.config.ts +0 -46
- package/web/const.ts +0 -19
- package/web/control.shell.tsx +0 -66
- package/web/dashboardpage.ts +0 -554
- package/web/db.ts +0 -493
- package/web/design.html +0 -541
- package/web/gatewayview/Gateway.tsx +0 -345
- package/web/gatewayview/config.ts +0 -1042
- package/web/gatewayview/definition.ts +0 -174
- package/web/icons.ts +0 -15
- package/web/optionspage.ts +0 -691
- package/web/popup.ts +0 -1651
- package/web/schema.prisma +0 -176
- package/web/sidepanel.ts +0 -7941
- package/web/sitemanifest.json +0 -13
- package/web/terminal-ui.tsx +0 -622
- package/web/transparencypage.ts +0 -541
- package/web/vite.config.ts +0 -39
- package/web/workspace.ts +0 -23
- package/workbenchmemory.ts +0 -104
- package/workbenchsession.ts +0 -304
- package/workflow.ts +0 -4495
- /package/{dist/bun.d.ts → bun.d.ts} +0 -0
- /package/{dist/cli.d.ts → cli.d.ts} +0 -0
- /package/{dist/companion.d.ts → companion.d.ts} +0 -0
- /package/{dist/crossbrowser.d.ts → crossbrowser.d.ts} +0 -0
- /package/{dist/dashdone.d.ts → dashdone.d.ts} +0 -0
- /package/{dist/deno.d.ts → deno.d.ts} +0 -0
- /package/{dist/hardening.d.ts → hardening.d.ts} +0 -0
- /package/{dist/headless.d.ts → headless.d.ts} +0 -0
- /package/{dist/http.d.ts → http.d.ts} +0 -0
- /package/{dist/index.d.ts → index.d.ts} +0 -0
- /package/{dist/mcp.d.ts → mcp.d.ts} +0 -0
- /package/{dist/memory.d.ts → memory.d.ts} +0 -0
- /package/{dist/node.d.ts → node.d.ts} +0 -0
- /package/{dist/pack.d.ts → pack.d.ts} +0 -0
- /package/{dist/policy.d.ts → policy.d.ts} +0 -0
- /package/{dist/progress.d.ts → progress.d.ts} +0 -0
- /package/{dist/protocol.d.ts → protocol.d.ts} +0 -0
- /package/{dist/server.d.ts → server.d.ts} +0 -0
- /package/{dist/umd.d.ts → umd.d.ts} +0 -0
package/web/db.ts
DELETED
|
@@ -1,493 +0,0 @@
|
|
|
1
|
-
/** DevThink v1.1.16: browser-local, non-secret workbench records. IndexedDB is an offline cache, never a credential vault or remote database. */
|
|
2
|
-
|
|
3
|
-
export type BrowserIdentity = { userId: string; deviceId: string; createdAt: string };
|
|
4
|
-
export type BrowserRoute = { workspaceId: string; sessionId: string; tabId: string; sectionId: string };
|
|
5
|
-
|
|
6
|
-
type LocalRecord = {
|
|
7
|
-
id: string;
|
|
8
|
-
ownerId: string;
|
|
9
|
-
updatedAt: string;
|
|
10
|
-
deviceId: string;
|
|
11
|
-
revision: number;
|
|
12
|
-
tombstone: boolean;
|
|
13
|
-
};
|
|
14
|
-
export type BrowserWorkspace = LocalRecord & { title: string; createdAt: string };
|
|
15
|
-
export type BrowserSession = LocalRecord & {
|
|
16
|
-
workspaceId: string;
|
|
17
|
-
title: string;
|
|
18
|
-
mode: string;
|
|
19
|
-
model?: string;
|
|
20
|
-
provider?: string;
|
|
21
|
-
activeTabId: string;
|
|
22
|
-
createdAt: string;
|
|
23
|
-
};
|
|
24
|
-
export type BrowserTab = LocalRecord & {
|
|
25
|
-
sessionId: string;
|
|
26
|
-
workspaceId: string;
|
|
27
|
-
label: string;
|
|
28
|
-
provider?: string;
|
|
29
|
-
sectionId: string;
|
|
30
|
-
createdAt: string;
|
|
31
|
-
};
|
|
32
|
-
export type BrowserMessage = LocalRecord & {
|
|
33
|
-
sessionId: string;
|
|
34
|
-
workspaceId: string;
|
|
35
|
-
tabId: string;
|
|
36
|
-
sectionId: string;
|
|
37
|
-
role: "user" | "assistant" | "system";
|
|
38
|
-
content: string;
|
|
39
|
-
createdAt: string;
|
|
40
|
-
};
|
|
41
|
-
export type BrowserPreference = LocalRecord & { key: string; value: string };
|
|
42
|
-
export type BrowserCredential = {
|
|
43
|
-
id: string;
|
|
44
|
-
ownerId: string;
|
|
45
|
-
providerId: string;
|
|
46
|
-
value: string;
|
|
47
|
-
deviceId: string;
|
|
48
|
-
createdAt: string;
|
|
49
|
-
updatedAt: string;
|
|
50
|
-
localOnly: true;
|
|
51
|
-
};
|
|
52
|
-
export type BrowserSessionSnapshot = {
|
|
53
|
-
identity: BrowserIdentity;
|
|
54
|
-
workspace?: BrowserWorkspace;
|
|
55
|
-
session?: BrowserSession;
|
|
56
|
-
tabs: BrowserTab[];
|
|
57
|
-
messages: BrowserMessage[];
|
|
58
|
-
preferences: Record<string, string>;
|
|
59
|
-
};
|
|
60
|
-
export type BrowserStoreSummary = {
|
|
61
|
-
available: boolean;
|
|
62
|
-
database: string;
|
|
63
|
-
ownerId?: string;
|
|
64
|
-
deviceId?: string;
|
|
65
|
-
workspaces: number;
|
|
66
|
-
sessions: number;
|
|
67
|
-
tabs: number;
|
|
68
|
-
messages: number;
|
|
69
|
-
preferences: number;
|
|
70
|
-
mode: "local-only" | "paired-gateway" | "remote-adapter";
|
|
71
|
-
};
|
|
72
|
-
|
|
73
|
-
type StoreName = "meta" | "workspaces" | "sessions" | "tabs" | "messages" | "preferences" | "credentials";
|
|
74
|
-
type MetaEntry = { key: string; value: BrowserIdentity };
|
|
75
|
-
|
|
76
|
-
const databaseName = "devthink.db";
|
|
77
|
-
const databaseVersion = 2;
|
|
78
|
-
const dataStores: Array<Exclude<StoreName, "meta">> = ["workspaces", "sessions", "tabs", "messages", "preferences"];
|
|
79
|
-
const ownerExpression = /^[a-z][a-z0-9]{9,14}$/;
|
|
80
|
-
|
|
81
|
-
function timestamp(): string {
|
|
82
|
-
return new Date().toISOString();
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
function compactToken(length = 10): string {
|
|
86
|
-
const alphabet = "0123456789abcdefghjkmnpqrstvwxyz";
|
|
87
|
-
const bytes = new Uint8Array(length);
|
|
88
|
-
globalThis.crypto?.getRandomValues?.(bytes);
|
|
89
|
-
return Array.from(bytes, (value, index) => alphabet[value ? value & 31 : (Date.now() + index) & 31]).join("");
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
function request<T>(source: IDBRequest<T>): Promise<T> {
|
|
93
|
-
return new Promise((resolve, reject) => {
|
|
94
|
-
source.onsuccess = () => resolve(source.result);
|
|
95
|
-
source.onerror = () => reject(source.error || new Error("DevThink browser store request failed."));
|
|
96
|
-
});
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
function finished(transaction: IDBTransaction): Promise<void> {
|
|
100
|
-
return new Promise((resolve, reject) => {
|
|
101
|
-
transaction.oncomplete = () => resolve();
|
|
102
|
-
transaction.onabort = () => reject(transaction.error || new Error("DevThink browser store transaction aborted."));
|
|
103
|
-
transaction.onerror = () => reject(transaction.error || new Error("DevThink browser store transaction failed."));
|
|
104
|
-
});
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
function openStore(): Promise<IDBDatabase> {
|
|
108
|
-
if (!("indexedDB" in globalThis)) return Promise.reject(new Error("IndexedDB is unavailable in this browser."));
|
|
109
|
-
return new Promise((resolve, reject) => {
|
|
110
|
-
const open = globalThis.indexedDB.open(databaseName, databaseVersion);
|
|
111
|
-
open.onupgradeneeded = () => {
|
|
112
|
-
const database = open.result;
|
|
113
|
-
if (!database.objectStoreNames.contains("meta")) database.createObjectStore("meta", { keyPath: "key" });
|
|
114
|
-
for (const storeName of dataStores) {
|
|
115
|
-
if (database.objectStoreNames.contains(storeName)) continue;
|
|
116
|
-
const store = database.createObjectStore(storeName, { keyPath: "id" });
|
|
117
|
-
store.createIndex("ownerId", "ownerId", { unique: false });
|
|
118
|
-
if (storeName === "sessions") store.createIndex("workspaceId", "workspaceId", { unique: false });
|
|
119
|
-
if (storeName === "tabs" || storeName === "messages")
|
|
120
|
-
store.createIndex("sessionId", "sessionId", { unique: false });
|
|
121
|
-
}
|
|
122
|
-
if (!database.objectStoreNames.contains("credentials")) {
|
|
123
|
-
const credentials = database.createObjectStore("credentials", { keyPath: "id" });
|
|
124
|
-
credentials.createIndex("ownerId", "ownerId", { unique: false });
|
|
125
|
-
credentials.createIndex("providerId", "providerId", { unique: false });
|
|
126
|
-
}
|
|
127
|
-
};
|
|
128
|
-
open.onsuccess = () => resolve(open.result);
|
|
129
|
-
open.onerror = () => reject(open.error || new Error("DevThink browser store could not open."));
|
|
130
|
-
});
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
async function withStore<T>(
|
|
134
|
-
storeName: StoreName,
|
|
135
|
-
mode: IDBTransactionMode,
|
|
136
|
-
operation: (store: IDBObjectStore) => Promise<T>,
|
|
137
|
-
): Promise<T> {
|
|
138
|
-
const database = await openStore();
|
|
139
|
-
try {
|
|
140
|
-
const transaction = database.transaction(storeName, mode);
|
|
141
|
-
const result = await operation(transaction.objectStore(storeName));
|
|
142
|
-
await finished(transaction);
|
|
143
|
-
return result;
|
|
144
|
-
} finally {
|
|
145
|
-
database.close();
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
async function record<T>(storeName: StoreName, id: string): Promise<T | undefined> {
|
|
150
|
-
return withStore(storeName, "readonly", async (store) => (await request(store.get(id))) as T | undefined);
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
async function allOwned<T extends LocalRecord>(storeName: Exclude<StoreName, "meta">, ownerId: string): Promise<T[]> {
|
|
154
|
-
return withStore(storeName, "readonly", async (store) =>
|
|
155
|
-
((await request(store.index("ownerId").getAll(IDBKeyRange.only(ownerId)))) as T[]).filter(
|
|
156
|
-
(item) => !item.tombstone,
|
|
157
|
-
),
|
|
158
|
-
);
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
async function put<T>(storeName: StoreName, value: T): Promise<T> {
|
|
162
|
-
return withStore(storeName, "readwrite", async (store) => {
|
|
163
|
-
await request(store.put(value));
|
|
164
|
-
return value;
|
|
165
|
-
});
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
async function updateRecord<T extends LocalRecord>(
|
|
169
|
-
storeName: Exclude<StoreName, "meta">,
|
|
170
|
-
identity: BrowserIdentity,
|
|
171
|
-
id: string,
|
|
172
|
-
value: Omit<T, keyof LocalRecord> & Partial<Pick<T, "updatedAt" | "tombstone">>,
|
|
173
|
-
): Promise<T> {
|
|
174
|
-
const current = await record<T>(storeName, id);
|
|
175
|
-
const next = {
|
|
176
|
-
...current,
|
|
177
|
-
...value,
|
|
178
|
-
id,
|
|
179
|
-
ownerId: identity.userId,
|
|
180
|
-
deviceId: identity.deviceId,
|
|
181
|
-
updatedAt: value.updatedAt || timestamp(),
|
|
182
|
-
revision: Math.max(0, current?.revision || 0) + 1,
|
|
183
|
-
tombstone: value.tombstone || false,
|
|
184
|
-
} as T;
|
|
185
|
-
return put(storeName, next);
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
/** Creates one browser-local identity only when a paired CLI identity is not available. */
|
|
189
|
-
export async function browserIdentity(): Promise<BrowserIdentity> {
|
|
190
|
-
const existing = await record<MetaEntry>("meta", "identity");
|
|
191
|
-
if (existing?.value && ownerExpression.test(existing.value.userId)) return existing.value;
|
|
192
|
-
const identity = { userId: `b${compactToken(11)}`, deviceId: `d_${compactToken(10)}`, createdAt: timestamp() };
|
|
193
|
-
await put("meta", { key: "identity", value: identity } satisfies MetaEntry);
|
|
194
|
-
return identity;
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
/** Caches the public identity supplied by a paired local gateway without importing any authentication material. */
|
|
198
|
-
export async function cacheBrowserIdentity(identity: BrowserIdentity): Promise<BrowserIdentity> {
|
|
199
|
-
if (!ownerExpression.test(identity.userId)) return browserIdentity();
|
|
200
|
-
const current = await browserIdentity();
|
|
201
|
-
if (current.userId !== identity.userId) {
|
|
202
|
-
for (const storeName of dataStores) {
|
|
203
|
-
const records = await allOwned<LocalRecord>(storeName, current.userId);
|
|
204
|
-
for (const item of records)
|
|
205
|
-
await put(storeName, {
|
|
206
|
-
...item,
|
|
207
|
-
ownerId: identity.userId,
|
|
208
|
-
deviceId: current.deviceId,
|
|
209
|
-
updatedAt: timestamp(),
|
|
210
|
-
revision: item.revision + 1,
|
|
211
|
-
});
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
const next = { ...current, userId: identity.userId };
|
|
215
|
-
await put("meta", { key: "identity", value: next } satisfies MetaEntry);
|
|
216
|
-
return next;
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
export async function readBrowserPreferences(): Promise<Record<string, string>> {
|
|
220
|
-
const identity = await browserIdentity();
|
|
221
|
-
const preferences = await allOwned<BrowserPreference>("preferences", identity.userId);
|
|
222
|
-
return Object.fromEntries(preferences.map((preference) => [preference.key, preference.value]));
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
/** Persists a non-secret interface preference or provider selection locally in this browser. */
|
|
226
|
-
export async function saveBrowserPreference(key: string, value: string): Promise<BrowserPreference> {
|
|
227
|
-
const identity = await browserIdentity();
|
|
228
|
-
return updateRecord<BrowserPreference>("preferences", identity, `${identity.userId}:${key}`, { key, value });
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
/** Stores a provider credential only after an explicit browser-local consent action. It is never included in summaries, gateway payloads or remote sync records. */
|
|
232
|
-
export async function saveBrowserCredential(providerId: string, value: string): Promise<void> {
|
|
233
|
-
const identity = await browserIdentity();
|
|
234
|
-
const now = timestamp();
|
|
235
|
-
const id = `${identity.userId}:${providerId}`;
|
|
236
|
-
const existing = await record<BrowserCredential>("credentials", id);
|
|
237
|
-
await put("credentials", {
|
|
238
|
-
id,
|
|
239
|
-
ownerId: identity.userId,
|
|
240
|
-
providerId,
|
|
241
|
-
value,
|
|
242
|
-
deviceId: identity.deviceId,
|
|
243
|
-
createdAt: existing?.createdAt || now,
|
|
244
|
-
updatedAt: now,
|
|
245
|
-
localOnly: true,
|
|
246
|
-
} satisfies BrowserCredential);
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
export async function removeBrowserCredential(providerId: string): Promise<void> {
|
|
250
|
-
const identity = await browserIdentity();
|
|
251
|
-
await withStore("credentials", "readwrite", async (store) => {
|
|
252
|
-
await request(store.delete(`${identity.userId}:${providerId}`));
|
|
253
|
-
});
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
export async function browserCredentialProviders(): Promise<string[]> {
|
|
257
|
-
const identity = await browserIdentity();
|
|
258
|
-
return withStore("credentials", "readonly", async (store) =>
|
|
259
|
-
((await request(store.index("ownerId").getAll(IDBKeyRange.only(identity.userId)))) as BrowserCredential[])
|
|
260
|
-
.filter((credential) => credential.localOnly)
|
|
261
|
-
.map((credential) => credential.providerId),
|
|
262
|
-
);
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
/** Ensures a URL-addressable workspace, session and tab exist in browser-local storage. */
|
|
266
|
-
export async function ensureBrowserSession(
|
|
267
|
-
route: BrowserRoute,
|
|
268
|
-
input: { provider?: string; model?: string; title?: string; mode?: string; tabLabel?: string } = {},
|
|
269
|
-
): Promise<BrowserSessionSnapshot> {
|
|
270
|
-
const identity = await browserIdentity();
|
|
271
|
-
const now = timestamp();
|
|
272
|
-
const workspace = await record<BrowserWorkspace>("workspaces", route.workspaceId);
|
|
273
|
-
if (!workspace || workspace.ownerId !== identity.userId)
|
|
274
|
-
await updateRecord<BrowserWorkspace>("workspaces", identity, route.workspaceId, {
|
|
275
|
-
title: input.title || "Local workspace",
|
|
276
|
-
createdAt: now,
|
|
277
|
-
});
|
|
278
|
-
const session = await record<BrowserSession>("sessions", route.sessionId);
|
|
279
|
-
if (!session || session.ownerId !== identity.userId)
|
|
280
|
-
await updateRecord<BrowserSession>("sessions", identity, route.sessionId, {
|
|
281
|
-
workspaceId: route.workspaceId,
|
|
282
|
-
title: input.title || "Local session",
|
|
283
|
-
mode: input.mode || "chat",
|
|
284
|
-
model: input.model,
|
|
285
|
-
provider: input.provider,
|
|
286
|
-
activeTabId: route.tabId,
|
|
287
|
-
createdAt: now,
|
|
288
|
-
});
|
|
289
|
-
const tab = await record<BrowserTab>("tabs", route.tabId);
|
|
290
|
-
if (!tab || tab.ownerId !== identity.userId)
|
|
291
|
-
await updateRecord<BrowserTab>("tabs", identity, route.tabId, {
|
|
292
|
-
sessionId: route.sessionId,
|
|
293
|
-
workspaceId: route.workspaceId,
|
|
294
|
-
label: input.tabLabel || "local session",
|
|
295
|
-
provider: input.provider,
|
|
296
|
-
sectionId: route.sectionId,
|
|
297
|
-
createdAt: now,
|
|
298
|
-
});
|
|
299
|
-
return loadBrowserSession(route.sessionId);
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
export async function loadBrowserSession(sessionId: string): Promise<BrowserSessionSnapshot> {
|
|
303
|
-
const identity = await browserIdentity();
|
|
304
|
-
const session = await record<BrowserSession>("sessions", sessionId);
|
|
305
|
-
const [tabs, messages, preferences] = await Promise.all([
|
|
306
|
-
allOwned<BrowserTab>("tabs", identity.userId),
|
|
307
|
-
allOwned<BrowserMessage>("messages", identity.userId),
|
|
308
|
-
readBrowserPreferences(),
|
|
309
|
-
]);
|
|
310
|
-
return {
|
|
311
|
-
identity,
|
|
312
|
-
workspace:
|
|
313
|
-
session?.ownerId === identity.userId
|
|
314
|
-
? await record<BrowserWorkspace>("workspaces", session.workspaceId)
|
|
315
|
-
: undefined,
|
|
316
|
-
session: session?.ownerId === identity.userId && !session.tombstone ? session : undefined,
|
|
317
|
-
tabs: tabs
|
|
318
|
-
.filter((tab) => tab.sessionId === sessionId)
|
|
319
|
-
.sort((left, right) => left.createdAt.localeCompare(right.createdAt)),
|
|
320
|
-
messages: messages
|
|
321
|
-
.filter((message) => message.sessionId === sessionId)
|
|
322
|
-
.sort((left, right) => left.createdAt.localeCompare(right.createdAt)),
|
|
323
|
-
preferences,
|
|
324
|
-
};
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
export async function saveBrowserTab(
|
|
328
|
-
route: BrowserRoute,
|
|
329
|
-
input: { label: string; provider?: string; sectionId?: string },
|
|
330
|
-
): Promise<BrowserTab> {
|
|
331
|
-
const identity = await browserIdentity();
|
|
332
|
-
await ensureBrowserSession(route, { provider: input.provider });
|
|
333
|
-
return updateRecord<BrowserTab>("tabs", identity, route.tabId, {
|
|
334
|
-
sessionId: route.sessionId,
|
|
335
|
-
workspaceId: route.workspaceId,
|
|
336
|
-
label: input.label,
|
|
337
|
-
provider: input.provider,
|
|
338
|
-
sectionId: input.sectionId || route.sectionId,
|
|
339
|
-
createdAt: (await record<BrowserTab>("tabs", route.tabId))?.createdAt || timestamp(),
|
|
340
|
-
});
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
export async function removeBrowserTab(id: string): Promise<void> {
|
|
344
|
-
const identity = await browserIdentity();
|
|
345
|
-
const tab = await record<BrowserTab>("tabs", id);
|
|
346
|
-
if (!tab || tab.ownerId !== identity.userId) return;
|
|
347
|
-
await updateRecord<BrowserTab>("tabs", identity, id, { ...tab, tombstone: true });
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
export async function removeBrowserMessage(id: string): Promise<void> {
|
|
351
|
-
const identity = await browserIdentity();
|
|
352
|
-
const message = await record<BrowserMessage>("messages", id);
|
|
353
|
-
if (!message || message.ownerId !== identity.userId) return;
|
|
354
|
-
await updateRecord<BrowserMessage>("messages", identity, id, { ...message, tombstone: true });
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
export async function saveBrowserMessages(
|
|
358
|
-
route: BrowserRoute,
|
|
359
|
-
messages: Array<Pick<BrowserMessage, "id" | "role" | "content"> & Partial<Pick<BrowserMessage, "createdAt">>>,
|
|
360
|
-
): Promise<void> {
|
|
361
|
-
const identity = await browserIdentity();
|
|
362
|
-
await ensureBrowserSession(route);
|
|
363
|
-
for (const message of messages)
|
|
364
|
-
await updateRecord<BrowserMessage>("messages", identity, message.id, {
|
|
365
|
-
sessionId: route.sessionId,
|
|
366
|
-
workspaceId: route.workspaceId,
|
|
367
|
-
tabId: route.tabId,
|
|
368
|
-
sectionId: route.sectionId,
|
|
369
|
-
role: message.role,
|
|
370
|
-
content: message.content,
|
|
371
|
-
createdAt: message.createdAt || (await record<BrowserMessage>("messages", message.id))?.createdAt || timestamp(),
|
|
372
|
-
});
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
/** Mirrors a safe gateway payload in the browser cache so it remains usable if the local gateway is later unavailable. */
|
|
376
|
-
export async function cacheGatewaySession(payload: {
|
|
377
|
-
id: string;
|
|
378
|
-
workspaceId: string;
|
|
379
|
-
title: string;
|
|
380
|
-
mode?: string;
|
|
381
|
-
model?: string;
|
|
382
|
-
provider?: string;
|
|
383
|
-
activeTabId: string;
|
|
384
|
-
createdAt?: string;
|
|
385
|
-
updatedAt?: string;
|
|
386
|
-
tabs: Array<{
|
|
387
|
-
id: string;
|
|
388
|
-
workspaceId: string;
|
|
389
|
-
sessionId: string;
|
|
390
|
-
label: string;
|
|
391
|
-
provider?: string;
|
|
392
|
-
sectionId: string;
|
|
393
|
-
createdAt: string;
|
|
394
|
-
updatedAt: string;
|
|
395
|
-
}>;
|
|
396
|
-
messages: Array<{
|
|
397
|
-
id: string;
|
|
398
|
-
workspaceId: string;
|
|
399
|
-
sessionId: string;
|
|
400
|
-
tabId: string;
|
|
401
|
-
sectionId: string;
|
|
402
|
-
role: "user" | "assistant" | "system";
|
|
403
|
-
content: string;
|
|
404
|
-
createdAt: string;
|
|
405
|
-
}>;
|
|
406
|
-
}): Promise<void> {
|
|
407
|
-
const identity = await browserIdentity();
|
|
408
|
-
const now = timestamp();
|
|
409
|
-
await updateRecord<BrowserWorkspace>("workspaces", identity, payload.workspaceId, {
|
|
410
|
-
title: payload.title || "Paired workspace",
|
|
411
|
-
createdAt: payload.createdAt || now,
|
|
412
|
-
updatedAt: payload.updatedAt || now,
|
|
413
|
-
});
|
|
414
|
-
await updateRecord<BrowserSession>("sessions", identity, payload.id, {
|
|
415
|
-
workspaceId: payload.workspaceId,
|
|
416
|
-
title: payload.title,
|
|
417
|
-
mode: payload.mode || "chat",
|
|
418
|
-
model: payload.model,
|
|
419
|
-
provider: payload.provider,
|
|
420
|
-
activeTabId: payload.activeTabId,
|
|
421
|
-
createdAt: payload.createdAt || now,
|
|
422
|
-
updatedAt: payload.updatedAt || now,
|
|
423
|
-
});
|
|
424
|
-
for (const tab of payload.tabs)
|
|
425
|
-
await updateRecord<BrowserTab>("tabs", identity, tab.id, {
|
|
426
|
-
sessionId: payload.id,
|
|
427
|
-
workspaceId: payload.workspaceId,
|
|
428
|
-
label: tab.label,
|
|
429
|
-
provider: tab.provider,
|
|
430
|
-
sectionId: tab.sectionId,
|
|
431
|
-
createdAt: tab.createdAt,
|
|
432
|
-
updatedAt: tab.updatedAt,
|
|
433
|
-
});
|
|
434
|
-
for (const message of payload.messages)
|
|
435
|
-
await updateRecord<BrowserMessage>("messages", identity, message.id, {
|
|
436
|
-
sessionId: payload.id,
|
|
437
|
-
workspaceId: payload.workspaceId,
|
|
438
|
-
tabId: message.tabId,
|
|
439
|
-
sectionId: message.sectionId,
|
|
440
|
-
role: message.role,
|
|
441
|
-
content: message.content,
|
|
442
|
-
createdAt: message.createdAt,
|
|
443
|
-
updatedAt: message.createdAt,
|
|
444
|
-
});
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
export async function browserStoreSummary(paired = false): Promise<BrowserStoreSummary> {
|
|
448
|
-
if (!("indexedDB" in globalThis))
|
|
449
|
-
return {
|
|
450
|
-
available: false,
|
|
451
|
-
database: databaseName,
|
|
452
|
-
workspaces: 0,
|
|
453
|
-
sessions: 0,
|
|
454
|
-
tabs: 0,
|
|
455
|
-
messages: 0,
|
|
456
|
-
preferences: 0,
|
|
457
|
-
mode: paired ? "paired-gateway" : "local-only",
|
|
458
|
-
};
|
|
459
|
-
const identity = await browserIdentity();
|
|
460
|
-
const [workspaces, sessions, tabs, messages, preferences] = await Promise.all(
|
|
461
|
-
dataStores.map((storeName) => allOwned<LocalRecord>(storeName, identity.userId)),
|
|
462
|
-
);
|
|
463
|
-
return {
|
|
464
|
-
available: true,
|
|
465
|
-
database: databaseName,
|
|
466
|
-
ownerId: identity.userId,
|
|
467
|
-
deviceId: identity.deviceId,
|
|
468
|
-
workspaces: workspaces.length,
|
|
469
|
-
sessions: sessions.length,
|
|
470
|
-
tabs: tabs.length,
|
|
471
|
-
messages: messages.length,
|
|
472
|
-
preferences: preferences.length,
|
|
473
|
-
mode: paired ? "paired-gateway" : "local-only",
|
|
474
|
-
};
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
export async function browserWorkspaces(): Promise<
|
|
478
|
-
Array<{ id: string; title: string; updatedAt: string; sessionCount: number }>
|
|
479
|
-
> {
|
|
480
|
-
const identity = await browserIdentity();
|
|
481
|
-
const [workspaces, sessions] = await Promise.all([
|
|
482
|
-
allOwned<BrowserWorkspace>("workspaces", identity.userId),
|
|
483
|
-
allOwned<BrowserSession>("sessions", identity.userId),
|
|
484
|
-
]);
|
|
485
|
-
return workspaces
|
|
486
|
-
.sort((left, right) => right.updatedAt.localeCompare(left.updatedAt))
|
|
487
|
-
.map((workspace) => ({
|
|
488
|
-
id: workspace.id,
|
|
489
|
-
title: workspace.title,
|
|
490
|
-
updatedAt: workspace.updatedAt,
|
|
491
|
-
sessionCount: sessions.filter((session) => session.workspaceId === workspace.id).length,
|
|
492
|
-
}));
|
|
493
|
-
}
|