@almadar/workspace 0.11.4 → 0.11.6
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__/append-json-line.test.d.ts +1 -0
- package/dist/__tests__/workspace-ledger.test.d.ts +1 -0
- package/dist/index.d.ts +4 -3
- package/dist/index.js +263 -68
- package/dist/index.js.map +1 -1
- package/dist/internal/backends/local.d.ts +1 -0
- package/dist/internal/backends/memory.d.ts +1 -0
- package/dist/internal/memory-files.d.ts +6 -0
- package/dist/internal/path-layout.d.ts +7 -1
- package/dist/internal/types.d.ts +6 -0
- package/dist/service.d.ts +12 -1
- package/dist/types.d.ts +27 -0
- package/dist/workspace-index/fingerprint.d.ts +1 -2
- package/dist/workspace-index/index-impl.d.ts +1 -5
- package/dist/workspace-index/index.d.ts +2 -2
- package/dist/workspace-index/types.d.ts +0 -49
- package/dist/workspace-ledger/index.d.ts +9 -0
- package/dist/workspace-ledger/ledger-impl.d.ts +42 -0
- package/dist/workspace-ledger/persistence.d.ts +18 -0
- package/dist/workspace-ledger/types.d.ts +38 -0
- package/package.json +4 -4
- package/dist/__tests__/workspace-index-real-embedder.test.d.ts +0 -14
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/index.d.ts
CHANGED
|
@@ -10,6 +10,7 @@ export { openWorkspace } from './open-workspace.js';
|
|
|
10
10
|
export { listWorkspaces } from './list-workspaces.js';
|
|
11
11
|
export { deleteWorkspace } from './delete-workspace.js';
|
|
12
12
|
export { openAccount } from './account.js';
|
|
13
|
-
export type { WorkspaceService, InstantiateWorkspaceSeed, WorkspaceObserver, WorkspaceWriteEvent, WorkspaceWatchEvent, OpenWorkspaceOptions, ListWorkspacesOptions, WorkspaceSummary, RestoreBackend, GitHubConfig, GitStatusInfo, SnapshotMeta, WorkspaceArchiveBackend, FileTreeNode, AccountService, AccountConfig, ProviderCredential, AuthToken, AccountIdentity, OpenAccountOptions, } from './types.js';
|
|
14
|
-
export type { WorkspaceIndex, EmbedderPort,
|
|
15
|
-
export {
|
|
13
|
+
export type { WorkspaceService, InstantiateWorkspaceSeed, WorkspaceObserver, WorkspaceWriteEvent, WorkspaceWatchEvent, OpenWorkspaceOptions, MountEntry, ListWorkspacesOptions, WorkspaceSummary, RestoreBackend, GitHubConfig, GitStatusInfo, SnapshotMeta, WorkspaceArchiveBackend, FileTreeNode, AccountService, AccountConfig, ProviderCredential, AuthToken, AccountIdentity, OpenAccountOptions, } from './types.js';
|
|
14
|
+
export type { WorkspaceIndex, EmbedderPort, WorkspaceIndexStats, OrbitalIndexEntry, ExtraTraitIdentity, RetrievalResult, RetrievalOptions, RecentlyEditedOptions, EventEdge, EntityBinding, RuleBinding, RecencyEntry, IntentMaps, ComposedMaps, BM25Document, BM25Table, BM25Options, WorkspaceIndexManifest, } from './workspace-index/types.js';
|
|
15
|
+
export { DEFAULT_RETRIEVAL_TOP_K, RRF_K, WORKSPACE_INDEX_SCHEMA_VERSION, } from './workspace-index/types.js';
|
|
16
|
+
export type { WorkspaceLedger } from './workspace-ledger/types.js';
|
package/dist/index.js
CHANGED
|
@@ -3,6 +3,7 @@ import fs, { existsSync } from 'fs';
|
|
|
3
3
|
import { execFile } from 'child_process';
|
|
4
4
|
import dugite from 'dugite';
|
|
5
5
|
import { randomBytes, createHash } from 'crypto';
|
|
6
|
+
import { ledgerResolveName, mintId, ledgerRename, ledgerCurName, IdentityLedgerSchema } from '@almadar/core';
|
|
6
7
|
import { EmbeddingClient } from '@almadar/llm';
|
|
7
8
|
import os from 'os';
|
|
8
9
|
|
|
@@ -22,6 +23,10 @@ var LocalBackend = class {
|
|
|
22
23
|
fs.mkdirSync(path2.dirname(absPath), { recursive: true });
|
|
23
24
|
fs.writeFileSync(absPath, content, "utf-8");
|
|
24
25
|
}
|
|
26
|
+
async appendFile(absPath, content) {
|
|
27
|
+
await fs.promises.mkdir(path2.dirname(absPath), { recursive: true });
|
|
28
|
+
await fs.promises.appendFile(absPath, content, "utf-8");
|
|
29
|
+
}
|
|
25
30
|
async readFileBytes(absPath) {
|
|
26
31
|
return fs.promises.readFile(absPath);
|
|
27
32
|
}
|
|
@@ -102,6 +107,9 @@ var MemoryBackend = class {
|
|
|
102
107
|
writeFileSync(absPath, content) {
|
|
103
108
|
this.files.set(absPath, content);
|
|
104
109
|
}
|
|
110
|
+
async appendFile(absPath, content) {
|
|
111
|
+
this.files.set(absPath, (this.files.get(absPath) ?? "") + content);
|
|
112
|
+
}
|
|
105
113
|
async readFileBytes(absPath) {
|
|
106
114
|
const b = this.bytes.get(absPath);
|
|
107
115
|
if (b === void 0) throw new Error(`ENOENT: ${absPath}`);
|
|
@@ -293,7 +301,10 @@ function appMarkerFile(workDir) {
|
|
|
293
301
|
function workspaceIndexManifestFile(workDir) {
|
|
294
302
|
return path2.join(workDir, WORKSPACE_LAYOUT.ALMADAR_DIR, "index.json");
|
|
295
303
|
}
|
|
296
|
-
function
|
|
304
|
+
function workspaceLedgerFile(workDir) {
|
|
305
|
+
return path2.join(workDir, WORKSPACE_LAYOUT.ALMADAR_DIR, "ledger.json");
|
|
306
|
+
}
|
|
307
|
+
function sandboxedPath(workDir, relPath, mounts) {
|
|
297
308
|
if (typeof relPath !== "string" || relPath.length === 0) {
|
|
298
309
|
throw new Error("sandbox: empty relative path");
|
|
299
310
|
}
|
|
@@ -303,6 +314,20 @@ function sandboxedPath(workDir, relPath) {
|
|
|
303
314
|
if (/^[a-zA-Z]:[\\/]/.test(relPath)) {
|
|
304
315
|
throw new Error(`sandbox: drive-letter paths are not allowed (${relPath})`);
|
|
305
316
|
}
|
|
317
|
+
if (mounts) {
|
|
318
|
+
for (const [prefix, mountRoot] of mounts) {
|
|
319
|
+
const prefixSep = prefix + "/";
|
|
320
|
+
if (relPath === prefix || relPath.startsWith(prefixSep)) {
|
|
321
|
+
const mountAbs = path2.resolve(mountRoot);
|
|
322
|
+
const rest = relPath === prefix ? "." : relPath.slice(prefixSep.length);
|
|
323
|
+
const target2 = path2.resolve(mountAbs, rest);
|
|
324
|
+
if (target2 !== mountAbs && !target2.startsWith(mountAbs + path2.sep)) {
|
|
325
|
+
throw new Error(`sandbox: mount path escapes mount root (${relPath})`);
|
|
326
|
+
}
|
|
327
|
+
return target2;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
}
|
|
306
331
|
const rootAbs = path2.resolve(workDir);
|
|
307
332
|
const target = path2.resolve(rootAbs, relPath);
|
|
308
333
|
if (target !== rootAbs && !target.startsWith(rootAbs + path2.sep)) {
|
|
@@ -662,14 +687,11 @@ async function writeJsonFile(backend, absPath, value) {
|
|
|
662
687
|
await backend.writeFile(absPath, JSON.stringify(value, null, 2));
|
|
663
688
|
}
|
|
664
689
|
async function appendJsonLine(backend, absPath, value) {
|
|
665
|
-
|
|
666
|
-
const ending = existing.length > 0 && !existing.endsWith("\n") ? "\n" : "";
|
|
667
|
-
await backend.writeFile(absPath, existing + ending + JSON.stringify(value) + "\n");
|
|
690
|
+
await backend.appendFile(absPath, JSON.stringify(value) + "\n");
|
|
668
691
|
}
|
|
669
692
|
|
|
670
693
|
// src/workspace-index/types.ts
|
|
671
694
|
var WORKSPACE_INDEX_SCHEMA_VERSION = 2;
|
|
672
|
-
var DEFAULT_COERCION_THRESHOLD = 0.85;
|
|
673
695
|
var RRF_K = 60;
|
|
674
696
|
var DEFAULT_RETRIEVAL_TOP_K = 3;
|
|
675
697
|
|
|
@@ -1325,62 +1347,6 @@ var WorkspaceIndexImpl = class {
|
|
|
1325
1347
|
}
|
|
1326
1348
|
await this.rebuildManifest();
|
|
1327
1349
|
}
|
|
1328
|
-
async resolveOrbitalName(name, opts) {
|
|
1329
|
-
const threshold = opts?.threshold ?? DEFAULT_COERCION_THRESHOLD;
|
|
1330
|
-
if (this.entries.size === 0) {
|
|
1331
|
-
return { coercedTo: null, similarity: 0, method: "identity-vector" };
|
|
1332
|
-
}
|
|
1333
|
-
let vector;
|
|
1334
|
-
try {
|
|
1335
|
-
[vector] = await this.embedOne(name);
|
|
1336
|
-
} catch (err) {
|
|
1337
|
-
const reason = err instanceof Error ? err.message : String(err);
|
|
1338
|
-
console.warn(`[workspace-index] resolveOrbitalName: embedder failed, returning null (${reason})`);
|
|
1339
|
-
return { coercedTo: null, similarity: 0, method: "identity-vector" };
|
|
1340
|
-
}
|
|
1341
|
-
let bestOrbital = null;
|
|
1342
|
-
let bestSim = 0;
|
|
1343
|
-
for (const [orbital, entry] of this.entries) {
|
|
1344
|
-
const sim = cosineSimilarity(vector, entry.identityVector);
|
|
1345
|
-
if (sim > bestSim) {
|
|
1346
|
-
bestSim = sim;
|
|
1347
|
-
bestOrbital = orbital;
|
|
1348
|
-
}
|
|
1349
|
-
}
|
|
1350
|
-
if (bestOrbital !== null && bestSim >= threshold) {
|
|
1351
|
-
return { coercedTo: bestOrbital, similarity: bestSim, method: "identity-vector" };
|
|
1352
|
-
}
|
|
1353
|
-
return { coercedTo: null, similarity: bestSim, method: "identity-vector" };
|
|
1354
|
-
}
|
|
1355
|
-
async resolveTraitRef(emit, orbitalContext, opts) {
|
|
1356
|
-
const threshold = opts?.threshold ?? DEFAULT_COERCION_THRESHOLD;
|
|
1357
|
-
const entry = this.entries.get(orbitalContext.orbitalName);
|
|
1358
|
-
if (!entry || entry.extraTraitIdentities.length === 0) {
|
|
1359
|
-
return { coercedTo: null, similarity: 0, method: "identity-vector" };
|
|
1360
|
-
}
|
|
1361
|
-
const fingerprint = composeExtraTraitIdentityFingerprint(emit);
|
|
1362
|
-
let vector;
|
|
1363
|
-
try {
|
|
1364
|
-
[vector] = await this.embedOne(fingerprint);
|
|
1365
|
-
} catch (err) {
|
|
1366
|
-
const reason = err instanceof Error ? err.message : String(err);
|
|
1367
|
-
console.warn(`[workspace-index] resolveTraitRef: embedder failed, returning null (${reason})`);
|
|
1368
|
-
return { coercedTo: null, similarity: 0, method: "identity-vector" };
|
|
1369
|
-
}
|
|
1370
|
-
let bestAlias = null;
|
|
1371
|
-
let bestSim = 0;
|
|
1372
|
-
for (const candidate of entry.extraTraitIdentities) {
|
|
1373
|
-
const sim = cosineSimilarity(vector, candidate.vector);
|
|
1374
|
-
if (sim > bestSim) {
|
|
1375
|
-
bestSim = sim;
|
|
1376
|
-
bestAlias = candidate.alias;
|
|
1377
|
-
}
|
|
1378
|
-
}
|
|
1379
|
-
if (bestAlias !== null && bestSim >= threshold) {
|
|
1380
|
-
return { coercedTo: bestAlias, similarity: bestSim, method: "identity-vector" };
|
|
1381
|
-
}
|
|
1382
|
-
return { coercedTo: null, similarity: bestSim, method: "identity-vector" };
|
|
1383
|
-
}
|
|
1384
1350
|
stats() {
|
|
1385
1351
|
let extraTraitIdentityCount = 0;
|
|
1386
1352
|
for (const entry of this.entries.values()) {
|
|
@@ -1750,6 +1716,172 @@ function serializeMapOfArrays(map, projectOne) {
|
|
|
1750
1716
|
}
|
|
1751
1717
|
return out;
|
|
1752
1718
|
}
|
|
1719
|
+
function ledgerPath(workDir) {
|
|
1720
|
+
return workspaceLedgerFile(workDir);
|
|
1721
|
+
}
|
|
1722
|
+
async function readLedger(backend, workDir) {
|
|
1723
|
+
const p = ledgerPath(workDir);
|
|
1724
|
+
if (!backend.exists(p)) return null;
|
|
1725
|
+
try {
|
|
1726
|
+
const raw = await backend.readFile(p);
|
|
1727
|
+
const result = IdentityLedgerSchema.safeParse(JSON.parse(raw));
|
|
1728
|
+
return result.success ? result.data : null;
|
|
1729
|
+
} catch {
|
|
1730
|
+
return null;
|
|
1731
|
+
}
|
|
1732
|
+
}
|
|
1733
|
+
async function writeLedger(backend, workDir, ledger) {
|
|
1734
|
+
await backend.writeFile(ledgerPath(workDir), JSON.stringify(serializeLedger(ledger), null, 2));
|
|
1735
|
+
}
|
|
1736
|
+
function serializeLedger(ledger) {
|
|
1737
|
+
const entries = {};
|
|
1738
|
+
for (const [id, entry] of Object.entries(ledger.entries)) {
|
|
1739
|
+
entries[id] = serializeEntry(entry);
|
|
1740
|
+
}
|
|
1741
|
+
return { schemaVersion: ledger.schemaVersion, entries };
|
|
1742
|
+
}
|
|
1743
|
+
function serializeEntry(entry) {
|
|
1744
|
+
const row = {
|
|
1745
|
+
id: entry.id,
|
|
1746
|
+
kind: entry.kind,
|
|
1747
|
+
bakedName: entry.bakedName,
|
|
1748
|
+
curName: entry.curName,
|
|
1749
|
+
renames: entry.renames.map((r) => ({ from: r.from, to: r.to, at: r.at })),
|
|
1750
|
+
owner: entry.owner
|
|
1751
|
+
};
|
|
1752
|
+
if (entry.parent !== void 0) row.parent = entry.parent;
|
|
1753
|
+
return row;
|
|
1754
|
+
}
|
|
1755
|
+
|
|
1756
|
+
// src/workspace-ledger/ledger-impl.ts
|
|
1757
|
+
var WorkspaceLedgerImpl = class {
|
|
1758
|
+
constructor(deps) {
|
|
1759
|
+
this.deps = deps;
|
|
1760
|
+
this.ledger = emptyLedger();
|
|
1761
|
+
/** Serialized persists so concurrent writes don't race the file. */
|
|
1762
|
+
this.persistQueue = Promise.resolve();
|
|
1763
|
+
this.deps.sinks.subscribe({
|
|
1764
|
+
onWrite: (event) => this.onWorkspaceWrite(event)
|
|
1765
|
+
});
|
|
1766
|
+
}
|
|
1767
|
+
async onWorkspaceWrite(event) {
|
|
1768
|
+
if (event.kind !== "orbital") return;
|
|
1769
|
+
const slice = extractLedgerSlice(event.content);
|
|
1770
|
+
if (slice !== null) {
|
|
1771
|
+
this.ingestOrbSlice(slice);
|
|
1772
|
+
} else if (isJsonObject(event.content) && this.resolve("orbital", event.name) === null) {
|
|
1773
|
+
this.mint("orbital", event.name);
|
|
1774
|
+
}
|
|
1775
|
+
await this.persistQueue;
|
|
1776
|
+
}
|
|
1777
|
+
resolve(kind, name) {
|
|
1778
|
+
return ledgerResolveName(this.ledger, kind, name);
|
|
1779
|
+
}
|
|
1780
|
+
mint(kind, name) {
|
|
1781
|
+
if (kind === "palette") {
|
|
1782
|
+
throw new Error("WorkspaceLedger.mint: palette ids are manifest-owned, not name-ledger rows");
|
|
1783
|
+
}
|
|
1784
|
+
if (this.resolve(kind, name) !== null) {
|
|
1785
|
+
throw new Error(`WorkspaceLedger.mint: duplicate curName "${name}" within kind "${kind}"`);
|
|
1786
|
+
}
|
|
1787
|
+
const id = mintId(kind);
|
|
1788
|
+
const entry = {
|
|
1789
|
+
id,
|
|
1790
|
+
kind,
|
|
1791
|
+
bakedName: name,
|
|
1792
|
+
curName: name,
|
|
1793
|
+
renames: [],
|
|
1794
|
+
owner: "workspace"
|
|
1795
|
+
};
|
|
1796
|
+
this.ledger = { ...this.ledger, entries: { ...this.ledger.entries, [id]: entry } };
|
|
1797
|
+
void this.persist();
|
|
1798
|
+
return id;
|
|
1799
|
+
}
|
|
1800
|
+
rename(id, to, at) {
|
|
1801
|
+
if (this.ledger.entries[id] === void 0) {
|
|
1802
|
+
throw new Error(`WorkspaceLedger.rename: unknown id "${id}"`);
|
|
1803
|
+
}
|
|
1804
|
+
this.ledger = ledgerRename(this.ledger, id, to, String(at));
|
|
1805
|
+
void this.persist();
|
|
1806
|
+
}
|
|
1807
|
+
curName(id) {
|
|
1808
|
+
return ledgerCurName(this.ledger, id);
|
|
1809
|
+
}
|
|
1810
|
+
entry(id) {
|
|
1811
|
+
return this.ledger.entries[id] ?? null;
|
|
1812
|
+
}
|
|
1813
|
+
snapshot() {
|
|
1814
|
+
return cloneLedger(this.ledger);
|
|
1815
|
+
}
|
|
1816
|
+
ingestOrbSlice(slice) {
|
|
1817
|
+
const entries = { ...this.ledger.entries };
|
|
1818
|
+
let changed = false;
|
|
1819
|
+
for (const [id, entry] of Object.entries(slice.entries)) {
|
|
1820
|
+
if (entries[id] === void 0) {
|
|
1821
|
+
entries[id] = entry;
|
|
1822
|
+
changed = true;
|
|
1823
|
+
}
|
|
1824
|
+
}
|
|
1825
|
+
if (!changed) return;
|
|
1826
|
+
this.ledger = { ...this.ledger, entries };
|
|
1827
|
+
void this.persist();
|
|
1828
|
+
}
|
|
1829
|
+
async warm() {
|
|
1830
|
+
const loaded = await readLedger(this.deps.backend, this.deps.workDir);
|
|
1831
|
+
this.ledger = loaded ?? emptyLedger();
|
|
1832
|
+
const names = this.deps.listOrbitals();
|
|
1833
|
+
for (const name of names) {
|
|
1834
|
+
const content = this.deps.readOrbital(name);
|
|
1835
|
+
if (content === null) continue;
|
|
1836
|
+
const slice = extractLedgerSlice(content);
|
|
1837
|
+
if (slice !== null) this.ingestOrbSlice(slice);
|
|
1838
|
+
}
|
|
1839
|
+
for (const name of names) {
|
|
1840
|
+
if (this.resolve("orbital", name) === null) this.mint("orbital", name);
|
|
1841
|
+
}
|
|
1842
|
+
await this.persistQueue;
|
|
1843
|
+
}
|
|
1844
|
+
persist() {
|
|
1845
|
+
const snapshot = this.ledger;
|
|
1846
|
+
const next = this.persistQueue.then(async () => {
|
|
1847
|
+
await writeLedger(this.deps.backend, this.deps.workDir, snapshot);
|
|
1848
|
+
await this.deps.sinks.notifyAll({
|
|
1849
|
+
kind: "workspace-ledger",
|
|
1850
|
+
content: serializeLedger(snapshot)
|
|
1851
|
+
});
|
|
1852
|
+
});
|
|
1853
|
+
this.persistQueue = next.catch(() => void 0);
|
|
1854
|
+
return next;
|
|
1855
|
+
}
|
|
1856
|
+
};
|
|
1857
|
+
function emptyLedger() {
|
|
1858
|
+
return { schemaVersion: 1, entries: {} };
|
|
1859
|
+
}
|
|
1860
|
+
function cloneLedger(ledger) {
|
|
1861
|
+
const entries = {};
|
|
1862
|
+
for (const [id, entry] of Object.entries(ledger.entries)) {
|
|
1863
|
+
entries[id] = { ...entry, renames: entry.renames.map((r) => ({ ...r })) };
|
|
1864
|
+
}
|
|
1865
|
+
return { schemaVersion: ledger.schemaVersion, entries };
|
|
1866
|
+
}
|
|
1867
|
+
function isJsonObject(content) {
|
|
1868
|
+
try {
|
|
1869
|
+
const root = JSON.parse(content);
|
|
1870
|
+
return root !== null && typeof root === "object" && !Array.isArray(root);
|
|
1871
|
+
} catch {
|
|
1872
|
+
return false;
|
|
1873
|
+
}
|
|
1874
|
+
}
|
|
1875
|
+
function extractLedgerSlice(content) {
|
|
1876
|
+
try {
|
|
1877
|
+
const root = JSON.parse(content);
|
|
1878
|
+
if (root === null || typeof root !== "object" || Array.isArray(root)) return null;
|
|
1879
|
+
const result = IdentityLedgerSchema.safeParse(root.ledger);
|
|
1880
|
+
return result.success ? result.data : null;
|
|
1881
|
+
} catch {
|
|
1882
|
+
return null;
|
|
1883
|
+
}
|
|
1884
|
+
}
|
|
1753
1885
|
|
|
1754
1886
|
// src/service.ts
|
|
1755
1887
|
var COORD_FILES = {
|
|
@@ -1770,6 +1902,8 @@ var WorkspaceServiceImpl = class {
|
|
|
1770
1902
|
constructor(args) {
|
|
1771
1903
|
/** Per-absolute-path serial queue. */
|
|
1772
1904
|
this.writeQueue = /* @__PURE__ */ new Map();
|
|
1905
|
+
/** Mount prefix → resolved absolute directory. Built from constructor `mounts` + runtime `mountReadonly`. */
|
|
1906
|
+
this.mountMap = /* @__PURE__ */ new Map();
|
|
1773
1907
|
this.workDir = args.workDir;
|
|
1774
1908
|
this.backend = args.backend;
|
|
1775
1909
|
this.sinks = args.sinks;
|
|
@@ -1787,6 +1921,16 @@ var WorkspaceServiceImpl = class {
|
|
|
1787
1921
|
readSchema: () => this.readSchema(),
|
|
1788
1922
|
readHistory: (orbital) => this.readHistory(orbital)
|
|
1789
1923
|
});
|
|
1924
|
+
this.ledger = new WorkspaceLedgerImpl({
|
|
1925
|
+
workDir: this.workDir,
|
|
1926
|
+
backend: this.backend,
|
|
1927
|
+
sinks: this.sinks,
|
|
1928
|
+
listOrbitals: () => this.listOrbitals(),
|
|
1929
|
+
readOrbital: (name) => this.readOrbital(name)
|
|
1930
|
+
});
|
|
1931
|
+
if (args.mounts) {
|
|
1932
|
+
for (const m of args.mounts) this.mountMap.set(m.prefix, path2.resolve(m.absPath));
|
|
1933
|
+
}
|
|
1790
1934
|
}
|
|
1791
1935
|
// === Identity ===
|
|
1792
1936
|
get appId() {
|
|
@@ -2109,7 +2253,7 @@ var WorkspaceServiceImpl = class {
|
|
|
2109
2253
|
}
|
|
2110
2254
|
// === Sandboxed generic file I/O ===
|
|
2111
2255
|
async readFile(relPath) {
|
|
2112
|
-
const abs =
|
|
2256
|
+
const abs = this.sandbox(relPath);
|
|
2113
2257
|
if (!this.backend.exists(abs)) return null;
|
|
2114
2258
|
try {
|
|
2115
2259
|
return await this.backend.readFile(abs);
|
|
@@ -2118,7 +2262,8 @@ var WorkspaceServiceImpl = class {
|
|
|
2118
2262
|
}
|
|
2119
2263
|
}
|
|
2120
2264
|
async writeFile(relPath, content) {
|
|
2121
|
-
|
|
2265
|
+
this.assertNotMounted(relPath);
|
|
2266
|
+
const abs = this.sandbox(relPath);
|
|
2122
2267
|
await this.withLock(abs, async () => {
|
|
2123
2268
|
await this.ensureParent(abs);
|
|
2124
2269
|
await this.backend.writeFile(abs, content);
|
|
@@ -2126,7 +2271,8 @@ var WorkspaceServiceImpl = class {
|
|
|
2126
2271
|
await this.emit({ kind: "file", relPath, content });
|
|
2127
2272
|
}
|
|
2128
2273
|
async removeFile(relPath) {
|
|
2129
|
-
|
|
2274
|
+
this.assertNotMounted(relPath);
|
|
2275
|
+
const abs = this.sandbox(relPath);
|
|
2130
2276
|
let removed = false;
|
|
2131
2277
|
await this.withLock(abs, async () => {
|
|
2132
2278
|
if (!this.backend.exists(abs)) return;
|
|
@@ -2136,7 +2282,7 @@ var WorkspaceServiceImpl = class {
|
|
|
2136
2282
|
if (removed) await this.emit({ kind: "file-removed", relPath });
|
|
2137
2283
|
}
|
|
2138
2284
|
async listTree(relPath) {
|
|
2139
|
-
const base = relPath === void 0 ? this.workDir :
|
|
2285
|
+
const base = relPath === void 0 ? this.workDir : this.sandbox(relPath);
|
|
2140
2286
|
if (!this.backend.exists(base)) return [];
|
|
2141
2287
|
const out = [];
|
|
2142
2288
|
await walk(this.backend, base, this.workDir, out);
|
|
@@ -2145,12 +2291,39 @@ var WorkspaceServiceImpl = class {
|
|
|
2145
2291
|
async exists(relPath) {
|
|
2146
2292
|
let abs;
|
|
2147
2293
|
try {
|
|
2148
|
-
abs =
|
|
2294
|
+
abs = this.sandbox(relPath);
|
|
2149
2295
|
} catch {
|
|
2150
2296
|
return false;
|
|
2151
2297
|
}
|
|
2152
2298
|
return this.backend.exists(abs);
|
|
2153
2299
|
}
|
|
2300
|
+
// === Read-only mounts ===
|
|
2301
|
+
mountReadonly(prefix, absPath) {
|
|
2302
|
+
if (this.mountMap.has(prefix)) {
|
|
2303
|
+
throw new Error(`mount: prefix "${prefix}" already registered`);
|
|
2304
|
+
}
|
|
2305
|
+
this.mountMap.set(prefix, path2.resolve(absPath));
|
|
2306
|
+
}
|
|
2307
|
+
isMounted(relPath) {
|
|
2308
|
+
try {
|
|
2309
|
+
for (const [prefix] of this.mountMap) {
|
|
2310
|
+
if (relPath === prefix || relPath.startsWith(prefix + "/")) return true;
|
|
2311
|
+
}
|
|
2312
|
+
return false;
|
|
2313
|
+
} catch {
|
|
2314
|
+
return false;
|
|
2315
|
+
}
|
|
2316
|
+
}
|
|
2317
|
+
/** Resolve a sandboxed path through mounts. */
|
|
2318
|
+
sandbox(relPath) {
|
|
2319
|
+
return sandboxedPath(this.workDir, relPath, this.mountMap.size > 0 ? this.mountMap : void 0);
|
|
2320
|
+
}
|
|
2321
|
+
/** Throw if relPath resolves to a mount (write/remove are blocked). */
|
|
2322
|
+
assertNotMounted(relPath) {
|
|
2323
|
+
if (this.isMounted(relPath)) {
|
|
2324
|
+
throw new Error(`sandbox: cannot write to read-only mount (${relPath})`);
|
|
2325
|
+
}
|
|
2326
|
+
}
|
|
2154
2327
|
// === Git ===
|
|
2155
2328
|
async commitAndPush(opts) {
|
|
2156
2329
|
if (!this.git) return null;
|
|
@@ -2361,6 +2534,25 @@ async function openWorkspace(opts) {
|
|
|
2361
2534
|
}
|
|
2362
2535
|
return openWorkspaceInternal(opts);
|
|
2363
2536
|
}
|
|
2537
|
+
var WORKSPACE_OWNED_ENTRIES = /* @__PURE__ */ new Set([".almadar", "orbitals", "schema.orb", ".git", ".gitignore"]);
|
|
2538
|
+
async function guardForeignMintTarget(backend, workDir) {
|
|
2539
|
+
if (readAppMarker(backend, workDir) !== null) return;
|
|
2540
|
+
if (backend.exists(path2.join(workDir, "schema.orb"))) return;
|
|
2541
|
+
if (backend.exists(path2.join(workDir, ".almadar", "sessions"))) return;
|
|
2542
|
+
if (!backend.exists(workDir)) return;
|
|
2543
|
+
if (process.env["ALMADAR_WORKSPACE_ADOPT_NONEMPTY"] === "1") return;
|
|
2544
|
+
let entries;
|
|
2545
|
+
try {
|
|
2546
|
+
entries = await backend.readdir(workDir);
|
|
2547
|
+
} catch {
|
|
2548
|
+
return;
|
|
2549
|
+
}
|
|
2550
|
+
const foreign = entries.filter((e) => !WORKSPACE_OWNED_ENTRIES.has(e));
|
|
2551
|
+
if (foreign.length === 0) return;
|
|
2552
|
+
throw new Error(
|
|
2553
|
+
`refusing to mint a workspace into ${workDir} \u2014 it already contains non-workspace content (${foreign.slice(0, 5).join(", ")}${foreign.length > 5 ? ", \u2026" : ""}). Pass an empty/dedicated workDir, or set ALMADAR_WORKSPACE_ADOPT_NONEMPTY=1 to adopt this directory anyway.`
|
|
2554
|
+
);
|
|
2555
|
+
}
|
|
2364
2556
|
async function openWorkspaceInternal(opts) {
|
|
2365
2557
|
const backend = opts.backend === "memory" ? new MemoryBackend() : new LocalBackend();
|
|
2366
2558
|
const sinks = new SinkManager();
|
|
@@ -2368,6 +2560,7 @@ async function openWorkspaceInternal(opts) {
|
|
|
2368
2560
|
if (resolved === null) return null;
|
|
2369
2561
|
const bare = opts.bare === true && opts.adopt !== void 0;
|
|
2370
2562
|
if (!bare) {
|
|
2563
|
+
await guardForeignMintTarget(backend, resolved.workDir);
|
|
2371
2564
|
await ensureSkeleton(backend, resolved.workDir);
|
|
2372
2565
|
await writeMintTemplatesIfMissing(
|
|
2373
2566
|
backend,
|
|
@@ -2405,10 +2598,12 @@ async function openWorkspaceInternal(opts) {
|
|
|
2405
2598
|
appId: resolved.appId,
|
|
2406
2599
|
git,
|
|
2407
2600
|
github: opts.github,
|
|
2408
|
-
archiveBackend: opts.archiveBackend
|
|
2601
|
+
archiveBackend: opts.archiveBackend,
|
|
2602
|
+
mounts: opts.mounts
|
|
2409
2603
|
});
|
|
2410
2604
|
if (!bare) {
|
|
2411
2605
|
await service.index.warm();
|
|
2606
|
+
await service.ledger.warm();
|
|
2412
2607
|
}
|
|
2413
2608
|
return service;
|
|
2414
2609
|
}
|
|
@@ -2598,6 +2793,6 @@ async function openAccount(opts = {}) {
|
|
|
2598
2793
|
};
|
|
2599
2794
|
}
|
|
2600
2795
|
|
|
2601
|
-
export {
|
|
2796
|
+
export { DEFAULT_RETRIEVAL_TOP_K, RRF_K, WORKSPACE_INDEX_SCHEMA_VERSION, deleteWorkspace, listWorkspaces, openAccount, openWorkspace };
|
|
2602
2797
|
//# sourceMappingURL=index.js.map
|
|
2603
2798
|
//# sourceMappingURL=index.js.map
|