@almadar/workspace 0.1.2 → 0.1.4
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/dist/__tests__/concurrent-resolve.test.d.ts +14 -0
- package/dist/__tests__/mint-collision.test.d.ts +17 -0
- package/dist/__tests__/workspace-index-cosine.test.d.ts +1 -0
- package/dist/__tests__/workspace-index-fingerprint.test.d.ts +1 -0
- package/dist/__tests__/workspace-index.test.d.ts +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +335 -3
- package/dist/index.js.map +1 -1
- package/dist/internal/workspace-resolver.d.ts +12 -1
- package/dist/service.d.ts +3 -0
- package/dist/types.d.ts +9 -0
- package/dist/workspace-index/cosine.d.ts +8 -0
- package/dist/workspace-index/embedder.d.ts +24 -0
- package/dist/workspace-index/fingerprint.d.ts +49 -0
- package/dist/workspace-index/index-impl.d.ts +39 -0
- package/dist/workspace-index/index.d.ts +11 -0
- package/dist/workspace-index/sidecar.d.ts +22 -0
- package/dist/workspace-index/types.d.ts +132 -0
- package/package.json +2 -1
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Regression test for the per-(userId, appId) `openWorkspace` dedupe.
|
|
3
|
+
*
|
|
4
|
+
* Two concurrent calls with the same appId both run
|
|
5
|
+
* `findLocalWorkspaceDir` before either has written `app-marker.json`;
|
|
6
|
+
* without the in-flight Promise cache they both fall through to
|
|
7
|
+
* `mintSessionDir` and produce two distinct workspaces for the same
|
|
8
|
+
* logical app. The cache makes them share the first call's resolved
|
|
9
|
+
* service.
|
|
10
|
+
*
|
|
11
|
+
* Anonymous calls (no appId) must NOT dedupe — each anonymous mint is
|
|
12
|
+
* an independent session.
|
|
13
|
+
*/
|
|
14
|
+
export {};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Regression test for the `mintSessionDir` same-millisecond collision.
|
|
3
|
+
*
|
|
4
|
+
* Before the fix: `mintSessionDir` used `Date.now().toString(36)` alone
|
|
5
|
+
* as the dir suffix. Two concurrent mints in the same millisecond
|
|
6
|
+
* produced the same path; the subsequent `backend.mkdir(..., { recursive:
|
|
7
|
+
* true })` silently succeeded for both, and two `WorkspaceService`
|
|
8
|
+
* instances ended up pointing at the same directory.
|
|
9
|
+
*
|
|
10
|
+
* After the fix: the suffix appends 4 hex chars of `randomBytes(2)` so
|
|
11
|
+
* concurrent mints produce distinct paths even within the same ms.
|
|
12
|
+
*
|
|
13
|
+
* The test fires 50 anonymous (no appId) mints concurrently and asserts
|
|
14
|
+
* every `workDir` is unique. Without the random tail the assertion fails
|
|
15
|
+
* deterministically on a multi-core box; with the fix it passes.
|
|
16
|
+
*/
|
|
17
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/index.d.ts
CHANGED
|
@@ -8,3 +8,5 @@
|
|
|
8
8
|
*/
|
|
9
9
|
export { openWorkspace } from './open-workspace.js';
|
|
10
10
|
export type { WorkspaceService, WorkspaceObserver, WorkspaceWriteEvent, OpenWorkspaceOptions, RestoreBackend, GitHubConfig, GitStatusInfo, FileTreeNode, } from './types.js';
|
|
11
|
+
export type { WorkspaceIndex, EmbedderPort, ResolveResult, ResolveOptions, TraitRefEmit, WorkspaceIndexStats, OrbitalIndexEntry, ExtraTraitIdentity, } from './workspace-index/types.js';
|
|
12
|
+
export { DEFAULT_COERCION_THRESHOLD, WORKSPACE_INDEX_SCHEMA_VERSION, } from './workspace-index/types.js';
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import path2 from 'path';
|
|
2
2
|
import fs from 'fs';
|
|
3
3
|
import { execFile } from 'child_process';
|
|
4
|
+
import { randomBytes, createHash } from 'crypto';
|
|
5
|
+
import { EmbeddingClient } from '@almadar/llm';
|
|
4
6
|
|
|
5
7
|
// src/open-workspace.ts
|
|
6
8
|
var LocalBackend = class {
|
|
@@ -502,7 +504,7 @@ async function findLocalWorkspaceDir(backend, workspacesRoot, userId, appId) {
|
|
|
502
504
|
}
|
|
503
505
|
function mintSessionDir(workspacesRoot, userId) {
|
|
504
506
|
const ts = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
505
|
-
const suffix = Date.now().toString(36)
|
|
507
|
+
const suffix = `${Date.now().toString(36)}${randomBytes(2).toString("hex")}`;
|
|
506
508
|
return path2.join(workspacesRoot, userId, `${ts}_${suffix}`);
|
|
507
509
|
}
|
|
508
510
|
async function restoreWorkspace(backend, rootDir, restore) {
|
|
@@ -528,6 +530,15 @@ async function restoreWorkspace(backend, rootDir, restore) {
|
|
|
528
530
|
}
|
|
529
531
|
|
|
530
532
|
// src/internal/memory-files.ts
|
|
533
|
+
async function readJsonFile(backend, absPath) {
|
|
534
|
+
if (!backend.exists(absPath)) return null;
|
|
535
|
+
try {
|
|
536
|
+
const raw = await backend.readFile(absPath);
|
|
537
|
+
return JSON.parse(raw);
|
|
538
|
+
} catch {
|
|
539
|
+
return null;
|
|
540
|
+
}
|
|
541
|
+
}
|
|
531
542
|
async function writeJsonFile(backend, absPath, value) {
|
|
532
543
|
await backend.writeFile(absPath, JSON.stringify(value, null, 2));
|
|
533
544
|
}
|
|
@@ -537,6 +548,278 @@ async function appendJsonLine(backend, absPath, value) {
|
|
|
537
548
|
await backend.writeFile(absPath, existing + ending + JSON.stringify(value) + "\n");
|
|
538
549
|
}
|
|
539
550
|
|
|
551
|
+
// src/workspace-index/types.ts
|
|
552
|
+
var WORKSPACE_INDEX_SCHEMA_VERSION = 1;
|
|
553
|
+
var DEFAULT_COERCION_THRESHOLD = 0.85;
|
|
554
|
+
|
|
555
|
+
// src/workspace-index/cosine.ts
|
|
556
|
+
function cosineSimilarity(a, b) {
|
|
557
|
+
if (a.length !== b.length || a.length === 0) return 0;
|
|
558
|
+
let dot = 0;
|
|
559
|
+
let magA = 0;
|
|
560
|
+
let magB = 0;
|
|
561
|
+
for (let i = 0; i < a.length; i++) {
|
|
562
|
+
const av = a[i] ?? 0;
|
|
563
|
+
const bv = b[i] ?? 0;
|
|
564
|
+
dot += av * bv;
|
|
565
|
+
magA += av * av;
|
|
566
|
+
magB += bv * bv;
|
|
567
|
+
}
|
|
568
|
+
if (magA === 0 || magB === 0) return 0;
|
|
569
|
+
return dot / (Math.sqrt(magA) * Math.sqrt(magB));
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
// src/workspace-index/fingerprint.ts
|
|
573
|
+
function composeOrbitalIdentityFingerprint(spec) {
|
|
574
|
+
const segments = [];
|
|
575
|
+
pushString(segments, spec["orbitalName"]);
|
|
576
|
+
const params = asJsonObject(spec["params"]);
|
|
577
|
+
if (params) {
|
|
578
|
+
pushString(segments, params["entityName"]);
|
|
579
|
+
}
|
|
580
|
+
pushString(segments, spec["organism"]);
|
|
581
|
+
const traitNames = extractTraitNamesFromSpec(spec);
|
|
582
|
+
if (traitNames.length > 0) {
|
|
583
|
+
segments.push(traitNames.join(", "));
|
|
584
|
+
}
|
|
585
|
+
return segments.join(" \xB7 ");
|
|
586
|
+
}
|
|
587
|
+
function composeExtraTraitIdentityFingerprint(emit) {
|
|
588
|
+
const segments = [];
|
|
589
|
+
pushString(segments, emit.ref);
|
|
590
|
+
const alias = emit.name && emit.name.length > 0 ? emit.name : tailOfRef(emit.ref);
|
|
591
|
+
if (alias && alias !== emit.ref) {
|
|
592
|
+
pushString(segments, alias);
|
|
593
|
+
}
|
|
594
|
+
pushString(segments, emit.linkedEntity);
|
|
595
|
+
return segments.join(" \xB7 ");
|
|
596
|
+
}
|
|
597
|
+
function deriveExtraTraitAlias(emit) {
|
|
598
|
+
if (emit.name && emit.name.length > 0) return emit.name;
|
|
599
|
+
return tailOfRef(emit.ref);
|
|
600
|
+
}
|
|
601
|
+
function pushString(out, value) {
|
|
602
|
+
if (typeof value === "string" && value.length > 0) out.push(value);
|
|
603
|
+
}
|
|
604
|
+
function asJsonObject(value) {
|
|
605
|
+
if (value === void 0 || value === null) return null;
|
|
606
|
+
if (typeof value !== "object" || Array.isArray(value)) return null;
|
|
607
|
+
return value;
|
|
608
|
+
}
|
|
609
|
+
function tailOfRef(ref) {
|
|
610
|
+
const match = /\.traits\.([A-Za-z0-9_]+)$/.exec(ref);
|
|
611
|
+
return match ? match[1] : ref;
|
|
612
|
+
}
|
|
613
|
+
function extractTraitNamesFromSpec(spec) {
|
|
614
|
+
const out = [];
|
|
615
|
+
const params = asJsonObject(spec["params"]);
|
|
616
|
+
if (!params) return out;
|
|
617
|
+
const traitOverrides = asJsonObject(params["traitOverrides"]);
|
|
618
|
+
if (traitOverrides) {
|
|
619
|
+
for (const key of Object.keys(traitOverrides).sort()) out.push(key);
|
|
620
|
+
}
|
|
621
|
+
const extras = params["extraTraits"];
|
|
622
|
+
if (Array.isArray(extras)) {
|
|
623
|
+
for (const entry of extras) {
|
|
624
|
+
const obj = asJsonObject(entry);
|
|
625
|
+
if (!obj) continue;
|
|
626
|
+
const alias = typeof obj["name"] === "string" && obj["name"].length > 0 ? obj["name"] : typeof obj["ref"] === "string" ? tailOfRef(obj["ref"]) : null;
|
|
627
|
+
if (alias) out.push(alias);
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
return out;
|
|
631
|
+
}
|
|
632
|
+
var SIDECAR_BASENAME = "index.json";
|
|
633
|
+
function sidecarPath(workDir, orbital) {
|
|
634
|
+
return orbitalSessionFile(workDir, orbital, SIDECAR_BASENAME);
|
|
635
|
+
}
|
|
636
|
+
async function readSidecar(backend, workDir, orbital) {
|
|
637
|
+
const raw = await readJsonFile(backend, sidecarPath(workDir, orbital));
|
|
638
|
+
if (raw === null) return null;
|
|
639
|
+
if (raw.schemaVersion !== WORKSPACE_INDEX_SCHEMA_VERSION) return null;
|
|
640
|
+
return raw;
|
|
641
|
+
}
|
|
642
|
+
async function writeSidecar(backend, workDir, orbital, entry) {
|
|
643
|
+
await ensureOrbitalSessionDir(backend, workDir, orbital);
|
|
644
|
+
await writeJsonFile(
|
|
645
|
+
backend,
|
|
646
|
+
sidecarPath(workDir, orbital),
|
|
647
|
+
entry
|
|
648
|
+
);
|
|
649
|
+
}
|
|
650
|
+
function checksumSpec(spec) {
|
|
651
|
+
return createHash("sha256").update(JSON.stringify(spec)).digest("hex");
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
// src/workspace-index/index-impl.ts
|
|
655
|
+
var WorkspaceIndexImpl = class {
|
|
656
|
+
constructor(deps) {
|
|
657
|
+
this.deps = deps;
|
|
658
|
+
this.entries = /* @__PURE__ */ new Map();
|
|
659
|
+
/** Serialized re-bake per orbital so concurrent writeSpec events don't race. */
|
|
660
|
+
this.bakeQueue = /* @__PURE__ */ new Map();
|
|
661
|
+
/** Names whose checksum doesn't match the current spec — surfaced via stats. */
|
|
662
|
+
this.stale = /* @__PURE__ */ new Set();
|
|
663
|
+
this.deps.sinks.subscribe({
|
|
664
|
+
onWrite: async (event) => {
|
|
665
|
+
if (event.kind !== "spec") return;
|
|
666
|
+
await this.rebakeOrbital(event.orbital, event.content);
|
|
667
|
+
}
|
|
668
|
+
});
|
|
669
|
+
}
|
|
670
|
+
async warm() {
|
|
671
|
+
const orbitals = this.deps.listOrbitals();
|
|
672
|
+
const work = [];
|
|
673
|
+
for (const orbital of orbitals) {
|
|
674
|
+
const spec = this.deps.readSpec(orbital);
|
|
675
|
+
if (spec === null) {
|
|
676
|
+
continue;
|
|
677
|
+
}
|
|
678
|
+
const checksum = checksumSpec(spec);
|
|
679
|
+
const existing = await readSidecar(this.deps.backend, this.deps.workDir, orbital);
|
|
680
|
+
if (existing !== null && existing.specChecksum === checksum) {
|
|
681
|
+
this.entries.set(orbital, existing);
|
|
682
|
+
this.stale.delete(orbital);
|
|
683
|
+
continue;
|
|
684
|
+
}
|
|
685
|
+
work.push(this.bakeAndPersist(orbital, spec, checksum));
|
|
686
|
+
}
|
|
687
|
+
await Promise.all(work);
|
|
688
|
+
}
|
|
689
|
+
async resolveOrbitalName(name, opts) {
|
|
690
|
+
const threshold = opts?.threshold ?? DEFAULT_COERCION_THRESHOLD;
|
|
691
|
+
if (this.entries.size === 0) {
|
|
692
|
+
return { coercedTo: null, similarity: 0, method: "identity-vector" };
|
|
693
|
+
}
|
|
694
|
+
const [vector] = await this.embedOne(name);
|
|
695
|
+
let bestOrbital = null;
|
|
696
|
+
let bestSim = 0;
|
|
697
|
+
for (const [orbital, entry] of this.entries) {
|
|
698
|
+
const sim = cosineSimilarity(vector, entry.identityVector);
|
|
699
|
+
if (sim > bestSim) {
|
|
700
|
+
bestSim = sim;
|
|
701
|
+
bestOrbital = orbital;
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
if (bestOrbital !== null && bestSim >= threshold) {
|
|
705
|
+
return { coercedTo: bestOrbital, similarity: bestSim, method: "identity-vector" };
|
|
706
|
+
}
|
|
707
|
+
return { coercedTo: null, similarity: bestSim, method: "identity-vector" };
|
|
708
|
+
}
|
|
709
|
+
async resolveTraitRef(emit, orbitalContext, opts) {
|
|
710
|
+
const threshold = opts?.threshold ?? DEFAULT_COERCION_THRESHOLD;
|
|
711
|
+
const entry = this.entries.get(orbitalContext.orbitalName);
|
|
712
|
+
if (!entry || entry.extraTraitIdentities.length === 0) {
|
|
713
|
+
return { coercedTo: null, similarity: 0, method: "identity-vector" };
|
|
714
|
+
}
|
|
715
|
+
const fingerprint = composeExtraTraitIdentityFingerprint(emit);
|
|
716
|
+
const [vector] = await this.embedOne(fingerprint);
|
|
717
|
+
let bestAlias = null;
|
|
718
|
+
let bestSim = 0;
|
|
719
|
+
for (const candidate of entry.extraTraitIdentities) {
|
|
720
|
+
const sim = cosineSimilarity(vector, candidate.vector);
|
|
721
|
+
if (sim > bestSim) {
|
|
722
|
+
bestSim = sim;
|
|
723
|
+
bestAlias = candidate.alias;
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
if (bestAlias !== null && bestSim >= threshold) {
|
|
727
|
+
return { coercedTo: bestAlias, similarity: bestSim, method: "identity-vector" };
|
|
728
|
+
}
|
|
729
|
+
return { coercedTo: null, similarity: bestSim, method: "identity-vector" };
|
|
730
|
+
}
|
|
731
|
+
stats() {
|
|
732
|
+
let extraTraitIdentityCount = 0;
|
|
733
|
+
for (const entry of this.entries.values()) {
|
|
734
|
+
extraTraitIdentityCount += entry.extraTraitIdentities.length;
|
|
735
|
+
}
|
|
736
|
+
return {
|
|
737
|
+
orbitalCount: this.entries.size,
|
|
738
|
+
extraTraitIdentityCount,
|
|
739
|
+
staleOrbitals: Array.from(this.stale).sort()
|
|
740
|
+
};
|
|
741
|
+
}
|
|
742
|
+
async rebakeOrbital(orbital, spec) {
|
|
743
|
+
const checksum = checksumSpec(spec);
|
|
744
|
+
const existing = this.entries.get(orbital);
|
|
745
|
+
if (existing && existing.specChecksum === checksum) return;
|
|
746
|
+
await this.enqueueBake(orbital, () => this.bakeAndPersist(orbital, spec, checksum));
|
|
747
|
+
}
|
|
748
|
+
enqueueBake(orbital, op) {
|
|
749
|
+
const prev = this.bakeQueue.get(orbital) ?? Promise.resolve();
|
|
750
|
+
const next = prev.then(op);
|
|
751
|
+
const swallowed = next.catch(() => void 0);
|
|
752
|
+
this.bakeQueue.set(orbital, swallowed);
|
|
753
|
+
return next;
|
|
754
|
+
}
|
|
755
|
+
async bakeAndPersist(orbital, spec, checksum) {
|
|
756
|
+
this.stale.add(orbital);
|
|
757
|
+
const identityFingerprint = composeOrbitalIdentityFingerprint(spec);
|
|
758
|
+
const extraInputs = extractExtraTraitInputs(spec);
|
|
759
|
+
const allTexts = [identityFingerprint, ...extraInputs.map((e) => e.fingerprint)];
|
|
760
|
+
const { embeddings } = await this.deps.embedder.embedBatch(allTexts);
|
|
761
|
+
if (embeddings.length !== allTexts.length) {
|
|
762
|
+
throw new Error(
|
|
763
|
+
`[workspace-index] embedder returned ${embeddings.length} vectors for ${allTexts.length} inputs`
|
|
764
|
+
);
|
|
765
|
+
}
|
|
766
|
+
const [identityVector, ...extraVectors] = embeddings;
|
|
767
|
+
const extraTraitIdentities = extraInputs.map((input, idx) => ({
|
|
768
|
+
ref: input.ref,
|
|
769
|
+
alias: input.alias,
|
|
770
|
+
fingerprint: input.fingerprint,
|
|
771
|
+
vector: extraVectors[idx]
|
|
772
|
+
}));
|
|
773
|
+
const entry = {
|
|
774
|
+
schemaVersion: WORKSPACE_INDEX_SCHEMA_VERSION,
|
|
775
|
+
specChecksum: checksum,
|
|
776
|
+
identityVector,
|
|
777
|
+
identityFingerprint,
|
|
778
|
+
extraTraitIdentities,
|
|
779
|
+
bakedAt: Date.now()
|
|
780
|
+
};
|
|
781
|
+
await writeSidecar(this.deps.backend, this.deps.workDir, orbital, entry);
|
|
782
|
+
this.entries.set(orbital, entry);
|
|
783
|
+
this.stale.delete(orbital);
|
|
784
|
+
}
|
|
785
|
+
async embedOne(text) {
|
|
786
|
+
const { embeddings } = await this.deps.embedder.embedBatch([text]);
|
|
787
|
+
if (embeddings.length !== 1) {
|
|
788
|
+
throw new Error(
|
|
789
|
+
`[workspace-index] embedder returned ${embeddings.length} vectors for a single text`
|
|
790
|
+
);
|
|
791
|
+
}
|
|
792
|
+
return embeddings;
|
|
793
|
+
}
|
|
794
|
+
};
|
|
795
|
+
function extractExtraTraitInputs(spec) {
|
|
796
|
+
const params = asJsonObject2(spec["params"]);
|
|
797
|
+
if (!params) return [];
|
|
798
|
+
const extras = params["extraTraits"];
|
|
799
|
+
if (!Array.isArray(extras)) return [];
|
|
800
|
+
const out = [];
|
|
801
|
+
for (const entry of extras) {
|
|
802
|
+
const obj = asJsonObject2(entry);
|
|
803
|
+
if (!obj) continue;
|
|
804
|
+
const ref = typeof obj["ref"] === "string" ? obj["ref"] : null;
|
|
805
|
+
if (ref === null) continue;
|
|
806
|
+
const name = typeof obj["name"] === "string" ? obj["name"] : void 0;
|
|
807
|
+
const linkedEntity = typeof obj["linkedEntity"] === "string" ? obj["linkedEntity"] : void 0;
|
|
808
|
+
const alias = deriveExtraTraitAlias({ ref, name });
|
|
809
|
+
out.push({
|
|
810
|
+
ref,
|
|
811
|
+
alias,
|
|
812
|
+
fingerprint: composeExtraTraitIdentityFingerprint({ ref, name, linkedEntity })
|
|
813
|
+
});
|
|
814
|
+
}
|
|
815
|
+
return out;
|
|
816
|
+
}
|
|
817
|
+
function asJsonObject2(value) {
|
|
818
|
+
if (value === void 0 || value === null) return null;
|
|
819
|
+
if (typeof value !== "object" || Array.isArray(value)) return null;
|
|
820
|
+
return value;
|
|
821
|
+
}
|
|
822
|
+
|
|
540
823
|
// src/service.ts
|
|
541
824
|
var COORD_FILES = {
|
|
542
825
|
analysis: "analysis.json",
|
|
@@ -562,6 +845,14 @@ var WorkspaceServiceImpl = class {
|
|
|
562
845
|
this._appId = args.appId;
|
|
563
846
|
this.git = args.git;
|
|
564
847
|
this.github = args.github;
|
|
848
|
+
this.index = new WorkspaceIndexImpl({
|
|
849
|
+
workDir: this.workDir,
|
|
850
|
+
backend: this.backend,
|
|
851
|
+
sinks: this.sinks,
|
|
852
|
+
embedder: args.embedder,
|
|
853
|
+
listOrbitals: () => this.listOrbitals(),
|
|
854
|
+
readSpec: (orbital) => this.readSpec(orbital)
|
|
855
|
+
});
|
|
565
856
|
}
|
|
566
857
|
// === Identity ===
|
|
567
858
|
get appId() {
|
|
@@ -1008,9 +1299,47 @@ async function removeTree(backend, dir) {
|
|
|
1008
1299
|
}
|
|
1009
1300
|
}
|
|
1010
1301
|
}
|
|
1302
|
+
function createDefaultEmbedder() {
|
|
1303
|
+
let client = null;
|
|
1304
|
+
function get() {
|
|
1305
|
+
if (client === null) client = new EmbeddingClient({ provider: "openrouter" });
|
|
1306
|
+
return client;
|
|
1307
|
+
}
|
|
1308
|
+
return {
|
|
1309
|
+
async embedBatch(texts) {
|
|
1310
|
+
if (texts.length === 0) return { embeddings: [] };
|
|
1311
|
+
const result = await get().embedBatch(texts);
|
|
1312
|
+
return { embeddings: result.embeddings };
|
|
1313
|
+
}
|
|
1314
|
+
};
|
|
1315
|
+
}
|
|
1011
1316
|
|
|
1012
1317
|
// src/open-workspace.ts
|
|
1318
|
+
var openWorkspaceInFlight = /* @__PURE__ */ new Map();
|
|
1319
|
+
function openWorkspaceCacheKey(opts) {
|
|
1320
|
+
if (opts.appId === void 0) return null;
|
|
1321
|
+
const backendTag = opts.backend === "memory" ? "memory" : "local";
|
|
1322
|
+
return `${backendTag}:${opts.userId}:${opts.appId}`;
|
|
1323
|
+
}
|
|
1013
1324
|
async function openWorkspace(opts) {
|
|
1325
|
+
const key = openWorkspaceCacheKey(opts);
|
|
1326
|
+
if (key !== null) {
|
|
1327
|
+
const cached = openWorkspaceInFlight.get(key);
|
|
1328
|
+
if (cached) return cached;
|
|
1329
|
+
const pending = (async () => {
|
|
1330
|
+
try {
|
|
1331
|
+
return await openWorkspaceInternal(opts);
|
|
1332
|
+
} catch (err) {
|
|
1333
|
+
openWorkspaceInFlight.delete(key);
|
|
1334
|
+
throw err;
|
|
1335
|
+
}
|
|
1336
|
+
})();
|
|
1337
|
+
openWorkspaceInFlight.set(key, pending);
|
|
1338
|
+
return pending;
|
|
1339
|
+
}
|
|
1340
|
+
return openWorkspaceInternal(opts);
|
|
1341
|
+
}
|
|
1342
|
+
async function openWorkspaceInternal(opts) {
|
|
1014
1343
|
const backend = opts.backend === "memory" ? new MemoryBackend() : new LocalBackend();
|
|
1015
1344
|
const sinks = new SinkManager();
|
|
1016
1345
|
const resolved = await resolveLifecycle(backend, opts);
|
|
@@ -1038,14 +1367,17 @@ async function openWorkspace(opts) {
|
|
|
1038
1367
|
if (opts.github && opts.backend !== "memory") {
|
|
1039
1368
|
git = await ensureGitInit(backend, resolved.workDir);
|
|
1040
1369
|
}
|
|
1041
|
-
|
|
1370
|
+
const service = new WorkspaceServiceImpl({
|
|
1042
1371
|
workDir: resolved.workDir,
|
|
1043
1372
|
backend,
|
|
1044
1373
|
sinks,
|
|
1374
|
+
embedder: opts.embedder ?? createDefaultEmbedder(),
|
|
1045
1375
|
appId: resolved.appId,
|
|
1046
1376
|
git,
|
|
1047
1377
|
github: opts.github
|
|
1048
1378
|
});
|
|
1379
|
+
await service.index.warm();
|
|
1380
|
+
return service;
|
|
1049
1381
|
}
|
|
1050
1382
|
async function resolveLifecycle(backend, opts) {
|
|
1051
1383
|
if (opts.adopt) {
|
|
@@ -1077,6 +1409,6 @@ async function resolveLifecycle(backend, opts) {
|
|
|
1077
1409
|
return { workDir, appId: opts.appId };
|
|
1078
1410
|
}
|
|
1079
1411
|
|
|
1080
|
-
export { openWorkspace };
|
|
1412
|
+
export { DEFAULT_COERCION_THRESHOLD, WORKSPACE_INDEX_SCHEMA_VERSION, openWorkspace };
|
|
1081
1413
|
//# sourceMappingURL=index.js.map
|
|
1082
1414
|
//# sourceMappingURL=index.js.map
|