@remnic/core 9.3.599 → 9.3.601
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/index.js +393 -106
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/spaces/index.test.ts +205 -18
- package/src/spaces/index.ts +453 -139
package/dist/index.js
CHANGED
|
@@ -3618,10 +3618,14 @@ function hashContent3(content) {
|
|
|
3618
3618
|
}
|
|
3619
3619
|
|
|
3620
3620
|
// src/spaces/index.ts
|
|
3621
|
+
import { spawnSync } from "child_process";
|
|
3622
|
+
import crypto7 from "crypto";
|
|
3621
3623
|
import fs7 from "fs";
|
|
3622
3624
|
import path11 from "path";
|
|
3623
|
-
import crypto7 from "crypto";
|
|
3624
3625
|
var MANIFEST_VERSION = 1;
|
|
3626
|
+
var MANIFEST_LOCK_STALE_MS = 3e4;
|
|
3627
|
+
var MANIFEST_LOCK_TIMEOUT_MS = MANIFEST_LOCK_STALE_MS + 1e4;
|
|
3628
|
+
var MANIFEST_LOCK_SLEEP_MS = 20;
|
|
3625
3629
|
function normalizeSpaceMemoryDir(memoryDir) {
|
|
3626
3630
|
return path11.resolve(memoryDir);
|
|
3627
3631
|
}
|
|
@@ -3633,24 +3637,283 @@ function getManifestPath(baseDir) {
|
|
|
3633
3637
|
return path11.join(getSpacesDir(baseDir), "manifest.json");
|
|
3634
3638
|
}
|
|
3635
3639
|
function loadManifest(baseDir, memoryDirOverride) {
|
|
3640
|
+
if (fs7.existsSync(getManifestPath(baseDir))) {
|
|
3641
|
+
try {
|
|
3642
|
+
return readManifestUnlocked(baseDir, memoryDirOverride, { bootstrapIfMissing: false });
|
|
3643
|
+
} catch (error) {
|
|
3644
|
+
if (error.code !== "ENOENT") {
|
|
3645
|
+
throw error;
|
|
3646
|
+
}
|
|
3647
|
+
}
|
|
3648
|
+
}
|
|
3649
|
+
return updateManifest(baseDir, (manifest) => manifest, memoryDirOverride);
|
|
3650
|
+
}
|
|
3651
|
+
function saveManifest(manifest, baseDir) {
|
|
3652
|
+
withManifestLock(baseDir, () => {
|
|
3653
|
+
saveManifestUnlocked(manifest, baseDir);
|
|
3654
|
+
});
|
|
3655
|
+
}
|
|
3656
|
+
function updateManifest(baseDir, updater, memoryDirOverride) {
|
|
3657
|
+
return withManifestLock(baseDir, () => {
|
|
3658
|
+
const manifest = readManifestUnlocked(baseDir, memoryDirOverride);
|
|
3659
|
+
const result = updater(manifest);
|
|
3660
|
+
saveManifestUnlocked(manifest, baseDir);
|
|
3661
|
+
return result;
|
|
3662
|
+
});
|
|
3663
|
+
}
|
|
3664
|
+
function readManifestUnlocked(baseDir, memoryDirOverride, options = {}) {
|
|
3636
3665
|
const manifestPath2 = getManifestPath(baseDir);
|
|
3637
3666
|
if (!fs7.existsSync(manifestPath2)) {
|
|
3667
|
+
if (options.bootstrapIfMissing === false) {
|
|
3668
|
+
const error = new Error(`Spaces manifest not found: ${manifestPath2}`);
|
|
3669
|
+
error.code = "ENOENT";
|
|
3670
|
+
throw error;
|
|
3671
|
+
}
|
|
3638
3672
|
const personalSpace = createPersonalSpace(baseDir, memoryDirOverride);
|
|
3639
|
-
|
|
3673
|
+
return {
|
|
3640
3674
|
activeSpaceId: personalSpace.id,
|
|
3641
3675
|
spaces: [personalSpace],
|
|
3642
3676
|
version: MANIFEST_VERSION
|
|
3643
3677
|
};
|
|
3644
|
-
saveManifest(manifest, baseDir);
|
|
3645
|
-
return manifest;
|
|
3646
3678
|
}
|
|
3647
3679
|
const raw = JSON.parse(fs7.readFileSync(manifestPath2, "utf8"));
|
|
3648
3680
|
return raw;
|
|
3649
3681
|
}
|
|
3650
|
-
function
|
|
3682
|
+
function saveManifestUnlocked(manifest, baseDir) {
|
|
3651
3683
|
const manifestPath2 = getManifestPath(baseDir);
|
|
3652
|
-
|
|
3653
|
-
fs7.
|
|
3684
|
+
const manifestDir2 = path11.dirname(manifestPath2);
|
|
3685
|
+
fs7.mkdirSync(manifestDir2, { recursive: true });
|
|
3686
|
+
const tempPath = path11.join(manifestDir2, `.manifest.${process.pid}.${Date.now()}.${crypto7.randomUUID()}.tmp`);
|
|
3687
|
+
try {
|
|
3688
|
+
fs7.writeFileSync(tempPath, `${JSON.stringify(manifest, null, 2)}
|
|
3689
|
+
`, { flag: "wx" });
|
|
3690
|
+
fs7.renameSync(tempPath, manifestPath2);
|
|
3691
|
+
} catch (error) {
|
|
3692
|
+
try {
|
|
3693
|
+
fs7.rmSync(tempPath, { force: true });
|
|
3694
|
+
} catch {
|
|
3695
|
+
}
|
|
3696
|
+
throw error;
|
|
3697
|
+
}
|
|
3698
|
+
}
|
|
3699
|
+
function withManifestLock(baseDir, operation) {
|
|
3700
|
+
const lockDir = `${getManifestPath(baseDir)}.lock`;
|
|
3701
|
+
fs7.mkdirSync(path11.dirname(lockDir), { recursive: true });
|
|
3702
|
+
const lockOwner = acquireManifestLock(lockDir);
|
|
3703
|
+
try {
|
|
3704
|
+
return operation();
|
|
3705
|
+
} finally {
|
|
3706
|
+
releaseManifestLock(lockDir, lockOwner);
|
|
3707
|
+
}
|
|
3708
|
+
}
|
|
3709
|
+
function acquireManifestLock(lockDir) {
|
|
3710
|
+
const deadline = Date.now() + MANIFEST_LOCK_TIMEOUT_MS;
|
|
3711
|
+
const owner = createManifestLockOwner();
|
|
3712
|
+
const reclaimDir = getManifestLockReclaimDir(lockDir);
|
|
3713
|
+
while (true) {
|
|
3714
|
+
if (fs7.existsSync(reclaimDir)) {
|
|
3715
|
+
removeStaleManifestReclaimLock(reclaimDir);
|
|
3716
|
+
}
|
|
3717
|
+
if (fs7.existsSync(reclaimDir)) {
|
|
3718
|
+
if (Date.now() >= deadline) {
|
|
3719
|
+
throw new Error(`Timed out waiting for spaces manifest reclaim lock: ${reclaimDir}`);
|
|
3720
|
+
}
|
|
3721
|
+
sleepSync(MANIFEST_LOCK_SLEEP_MS);
|
|
3722
|
+
continue;
|
|
3723
|
+
}
|
|
3724
|
+
try {
|
|
3725
|
+
fs7.mkdirSync(lockDir, { recursive: false });
|
|
3726
|
+
try {
|
|
3727
|
+
fs7.writeFileSync(path11.join(lockDir, "owner"), `${owner}
|
|
3728
|
+
`, { flag: "wx" });
|
|
3729
|
+
} catch (error) {
|
|
3730
|
+
fs7.rmSync(lockDir, { recursive: true, force: true });
|
|
3731
|
+
throw error;
|
|
3732
|
+
}
|
|
3733
|
+
if (fs7.existsSync(reclaimDir)) {
|
|
3734
|
+
releaseManifestLock(lockDir, owner);
|
|
3735
|
+
if (Date.now() >= deadline) {
|
|
3736
|
+
throw new Error(`Timed out waiting for spaces manifest reclaim lock: ${reclaimDir}`);
|
|
3737
|
+
}
|
|
3738
|
+
sleepSync(MANIFEST_LOCK_SLEEP_MS);
|
|
3739
|
+
continue;
|
|
3740
|
+
}
|
|
3741
|
+
return owner;
|
|
3742
|
+
} catch (error) {
|
|
3743
|
+
const code = error.code;
|
|
3744
|
+
if (code !== "EEXIST") {
|
|
3745
|
+
throw error;
|
|
3746
|
+
}
|
|
3747
|
+
removeStaleManifestLock(lockDir);
|
|
3748
|
+
if (Date.now() >= deadline) {
|
|
3749
|
+
throw new Error(`Timed out waiting for spaces manifest lock: ${lockDir}`);
|
|
3750
|
+
}
|
|
3751
|
+
sleepSync(MANIFEST_LOCK_SLEEP_MS);
|
|
3752
|
+
}
|
|
3753
|
+
}
|
|
3754
|
+
}
|
|
3755
|
+
function releaseManifestLock(lockDir, owner) {
|
|
3756
|
+
try {
|
|
3757
|
+
const ownerPath = path11.join(lockDir, "owner");
|
|
3758
|
+
if (fs7.readFileSync(ownerPath, "utf8").trim() === owner) {
|
|
3759
|
+
fs7.rmSync(lockDir, { recursive: true, force: true });
|
|
3760
|
+
}
|
|
3761
|
+
} catch (error) {
|
|
3762
|
+
if (error.code !== "ENOENT") {
|
|
3763
|
+
throw error;
|
|
3764
|
+
}
|
|
3765
|
+
}
|
|
3766
|
+
}
|
|
3767
|
+
function removeStaleManifestLock(lockDir) {
|
|
3768
|
+
const reclaimDir = getManifestLockReclaimDir(lockDir);
|
|
3769
|
+
const reclaimOwner = createManifestLockOwner();
|
|
3770
|
+
try {
|
|
3771
|
+
fs7.mkdirSync(reclaimDir, { recursive: false });
|
|
3772
|
+
try {
|
|
3773
|
+
fs7.writeFileSync(path11.join(reclaimDir, "owner"), `${reclaimOwner}
|
|
3774
|
+
`, { flag: "wx" });
|
|
3775
|
+
} catch (error) {
|
|
3776
|
+
fs7.rmSync(reclaimDir, { recursive: true, force: true });
|
|
3777
|
+
throw error;
|
|
3778
|
+
}
|
|
3779
|
+
} catch (error) {
|
|
3780
|
+
const code = error.code;
|
|
3781
|
+
if (code === "EEXIST") {
|
|
3782
|
+
return;
|
|
3783
|
+
}
|
|
3784
|
+
throw error;
|
|
3785
|
+
}
|
|
3786
|
+
try {
|
|
3787
|
+
const snapshot = readManifestLockSnapshot(lockDir);
|
|
3788
|
+
if (!snapshot || Date.now() - snapshot.mtimeMs <= MANIFEST_LOCK_STALE_MS) {
|
|
3789
|
+
return;
|
|
3790
|
+
}
|
|
3791
|
+
if (isManifestLockOwnerActive(snapshot.owner)) {
|
|
3792
|
+
return;
|
|
3793
|
+
}
|
|
3794
|
+
const tombstoneDir = `${lockDir}.stale.${process.pid}.${crypto7.randomUUID()}`;
|
|
3795
|
+
try {
|
|
3796
|
+
fs7.renameSync(lockDir, tombstoneDir);
|
|
3797
|
+
} catch (error) {
|
|
3798
|
+
if (error.code !== "ENOENT") {
|
|
3799
|
+
throw error;
|
|
3800
|
+
}
|
|
3801
|
+
return;
|
|
3802
|
+
}
|
|
3803
|
+
fs7.rmSync(tombstoneDir, { recursive: true, force: true });
|
|
3804
|
+
} finally {
|
|
3805
|
+
fs7.rmSync(reclaimDir, { recursive: true, force: true });
|
|
3806
|
+
}
|
|
3807
|
+
}
|
|
3808
|
+
function getManifestLockReclaimDir(lockDir) {
|
|
3809
|
+
return `${lockDir}.reclaim`;
|
|
3810
|
+
}
|
|
3811
|
+
function createManifestLockOwner() {
|
|
3812
|
+
return JSON.stringify({
|
|
3813
|
+
pid: process.pid,
|
|
3814
|
+
startKey: readProcessStartKey(process.pid),
|
|
3815
|
+
token: crypto7.randomUUID()
|
|
3816
|
+
});
|
|
3817
|
+
}
|
|
3818
|
+
function removeStaleManifestReclaimLock(reclaimDir) {
|
|
3819
|
+
const snapshot = readManifestLockSnapshot(reclaimDir);
|
|
3820
|
+
if (!snapshot || Date.now() - snapshot.mtimeMs <= MANIFEST_LOCK_STALE_MS) {
|
|
3821
|
+
return;
|
|
3822
|
+
}
|
|
3823
|
+
if (isManifestLockOwnerActive(snapshot.owner)) {
|
|
3824
|
+
return;
|
|
3825
|
+
}
|
|
3826
|
+
const tombstoneDir = `${reclaimDir}.stale.${process.pid}.${crypto7.randomUUID()}`;
|
|
3827
|
+
try {
|
|
3828
|
+
fs7.renameSync(reclaimDir, tombstoneDir);
|
|
3829
|
+
} catch (error) {
|
|
3830
|
+
if (error.code !== "ENOENT") {
|
|
3831
|
+
throw error;
|
|
3832
|
+
}
|
|
3833
|
+
return;
|
|
3834
|
+
}
|
|
3835
|
+
fs7.rmSync(tombstoneDir, { recursive: true, force: true });
|
|
3836
|
+
}
|
|
3837
|
+
function readManifestLockSnapshot(lockDir) {
|
|
3838
|
+
let stat;
|
|
3839
|
+
try {
|
|
3840
|
+
stat = fs7.statSync(lockDir);
|
|
3841
|
+
} catch (error) {
|
|
3842
|
+
if (error.code === "ENOENT") {
|
|
3843
|
+
return void 0;
|
|
3844
|
+
}
|
|
3845
|
+
throw error;
|
|
3846
|
+
}
|
|
3847
|
+
try {
|
|
3848
|
+
const owner = fs7.readFileSync(path11.join(lockDir, "owner"), "utf8").trim();
|
|
3849
|
+
return { mtimeMs: stat.mtimeMs, owner };
|
|
3850
|
+
} catch (error) {
|
|
3851
|
+
if (error.code === "ENOENT") {
|
|
3852
|
+
return { mtimeMs: stat.mtimeMs };
|
|
3853
|
+
}
|
|
3854
|
+
throw error;
|
|
3855
|
+
}
|
|
3856
|
+
}
|
|
3857
|
+
function isManifestLockOwnerActive(owner) {
|
|
3858
|
+
if (!owner) {
|
|
3859
|
+
return false;
|
|
3860
|
+
}
|
|
3861
|
+
const parsed = parseManifestLockOwner(owner);
|
|
3862
|
+
if (!parsed) {
|
|
3863
|
+
return false;
|
|
3864
|
+
}
|
|
3865
|
+
const currentStartKey = readProcessStartKey(parsed.pid);
|
|
3866
|
+
if (currentStartKey && parsed.startKey) {
|
|
3867
|
+
return currentStartKey === parsed.startKey;
|
|
3868
|
+
}
|
|
3869
|
+
return isProcessAlive(parsed.pid);
|
|
3870
|
+
}
|
|
3871
|
+
function parseManifestLockOwner(owner) {
|
|
3872
|
+
try {
|
|
3873
|
+
const parsed = JSON.parse(owner);
|
|
3874
|
+
const pid = typeof parsed.pid === "number" ? parsed.pid : Number.NaN;
|
|
3875
|
+
if (Number.isInteger(pid) && pid > 0) {
|
|
3876
|
+
return {
|
|
3877
|
+
pid,
|
|
3878
|
+
startKey: typeof parsed.startKey === "string" && parsed.startKey.length > 0 ? parsed.startKey : void 0
|
|
3879
|
+
};
|
|
3880
|
+
}
|
|
3881
|
+
} catch {
|
|
3882
|
+
}
|
|
3883
|
+
const legacyPid = Number(owner.split(":", 1)[0]);
|
|
3884
|
+
return Number.isInteger(legacyPid) && legacyPid > 0 ? { pid: legacyPid } : void 0;
|
|
3885
|
+
}
|
|
3886
|
+
function readProcessStartKey(pid) {
|
|
3887
|
+
if (!Number.isInteger(pid) || pid <= 0) {
|
|
3888
|
+
return void 0;
|
|
3889
|
+
}
|
|
3890
|
+
const result = spawnSync("ps", ["-p", String(pid), "-o", "lstart="], {
|
|
3891
|
+
encoding: "utf8",
|
|
3892
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
3893
|
+
});
|
|
3894
|
+
if (result.error || result.status !== 0 || typeof result.stdout !== "string") {
|
|
3895
|
+
return void 0;
|
|
3896
|
+
}
|
|
3897
|
+
const startKey = result.stdout.trim().replace(/\s+/g, " ");
|
|
3898
|
+
return startKey.length > 0 ? startKey : void 0;
|
|
3899
|
+
}
|
|
3900
|
+
function isProcessAlive(pid) {
|
|
3901
|
+
if (pid === process.pid) {
|
|
3902
|
+
return true;
|
|
3903
|
+
}
|
|
3904
|
+
try {
|
|
3905
|
+
process.kill(pid, 0);
|
|
3906
|
+
return true;
|
|
3907
|
+
} catch (error) {
|
|
3908
|
+
const code = error.code;
|
|
3909
|
+
if (code === "ESRCH") {
|
|
3910
|
+
return false;
|
|
3911
|
+
}
|
|
3912
|
+
return true;
|
|
3913
|
+
}
|
|
3914
|
+
}
|
|
3915
|
+
function sleepSync(ms) {
|
|
3916
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
3654
3917
|
}
|
|
3655
3918
|
function createPersonalSpace(baseDir, memoryDirOverride) {
|
|
3656
3919
|
const homeDir = baseDir ?? resolveHomeDir();
|
|
@@ -3681,84 +3944,91 @@ function getActiveSpace(baseDir) {
|
|
|
3681
3944
|
return space;
|
|
3682
3945
|
}
|
|
3683
3946
|
function createSpace(options) {
|
|
3684
|
-
const manifest = loadManifest(options.baseDir);
|
|
3685
3947
|
const id = options.name.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-");
|
|
3686
|
-
if (manifest.spaces.some((s) => s.id === id)) {
|
|
3687
|
-
throw new Error(`Space "${id}" already exists`);
|
|
3688
|
-
}
|
|
3689
|
-
if (options.parentSpaceId && !manifest.spaces.some((s) => s.id === options.parentSpaceId)) {
|
|
3690
|
-
throw new Error(`Parent space "${options.parentSpaceId}" not found`);
|
|
3691
|
-
}
|
|
3692
3948
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3693
3949
|
const memoryDir = normalizeSpaceMemoryDir(
|
|
3694
|
-
options.memoryDir ?? path11.join(
|
|
3695
|
-
|
|
3950
|
+
options.memoryDir ?? path11.join(getSpacesDir(options.baseDir), id, "memory")
|
|
3951
|
+
);
|
|
3952
|
+
const space = updateManifest(options.baseDir, (manifest) => {
|
|
3953
|
+
if (manifest.spaces.some((s) => s.id === id)) {
|
|
3954
|
+
throw new Error(`Space "${id}" already exists`);
|
|
3955
|
+
}
|
|
3956
|
+
if (options.parentSpaceId && !manifest.spaces.some((s) => s.id === options.parentSpaceId)) {
|
|
3957
|
+
throw new Error(`Parent space "${options.parentSpaceId}" not found`);
|
|
3958
|
+
}
|
|
3959
|
+
const created = {
|
|
3696
3960
|
id,
|
|
3697
|
-
|
|
3698
|
-
|
|
3961
|
+
name: options.name,
|
|
3962
|
+
kind: options.kind,
|
|
3963
|
+
description: options.description,
|
|
3964
|
+
memoryDir,
|
|
3965
|
+
createdAt: now,
|
|
3966
|
+
updatedAt: now,
|
|
3967
|
+
owner: readEnvVar("USER"),
|
|
3968
|
+
parentSpaceId: options.parentSpaceId
|
|
3969
|
+
};
|
|
3970
|
+
fs7.mkdirSync(memoryDir, { recursive: true });
|
|
3971
|
+
manifest.spaces.push(created);
|
|
3972
|
+
manifest.updatedAt = now;
|
|
3973
|
+
return created;
|
|
3974
|
+
});
|
|
3975
|
+
appendAudit(
|
|
3976
|
+
{
|
|
3977
|
+
action: "space.create",
|
|
3978
|
+
sourceSpaceId: id,
|
|
3979
|
+
details: `Created ${options.kind} space "${options.name}"`
|
|
3980
|
+
},
|
|
3981
|
+
options.baseDir
|
|
3699
3982
|
);
|
|
3700
|
-
const space = {
|
|
3701
|
-
id,
|
|
3702
|
-
name: options.name,
|
|
3703
|
-
kind: options.kind,
|
|
3704
|
-
description: options.description,
|
|
3705
|
-
memoryDir,
|
|
3706
|
-
createdAt: now,
|
|
3707
|
-
updatedAt: now,
|
|
3708
|
-
owner: readEnvVar("USER"),
|
|
3709
|
-
parentSpaceId: options.parentSpaceId
|
|
3710
|
-
};
|
|
3711
|
-
fs7.mkdirSync(memoryDir, { recursive: true });
|
|
3712
|
-
manifest.spaces.push(space);
|
|
3713
|
-
manifest.updatedAt = now;
|
|
3714
|
-
saveManifest(manifest, options.baseDir);
|
|
3715
|
-
appendAudit({
|
|
3716
|
-
action: "space.create",
|
|
3717
|
-
sourceSpaceId: id,
|
|
3718
|
-
details: `Created ${options.kind} space "${options.name}"`
|
|
3719
|
-
}, options.baseDir);
|
|
3720
3983
|
return space;
|
|
3721
3984
|
}
|
|
3722
3985
|
function deleteSpace(spaceId, baseDir) {
|
|
3723
|
-
const manifest = loadManifest(baseDir);
|
|
3724
3986
|
if (spaceId === "personal") {
|
|
3725
3987
|
throw new Error("Cannot delete the personal space");
|
|
3726
3988
|
}
|
|
3727
|
-
|
|
3728
|
-
|
|
3729
|
-
|
|
3730
|
-
manifest.activeSpaceId
|
|
3731
|
-
|
|
3732
|
-
for (const space of manifest.spaces) {
|
|
3733
|
-
if (space.parentSpaceId === spaceId) {
|
|
3734
|
-
space.parentSpaceId = void 0;
|
|
3989
|
+
updateManifest(baseDir, (manifest) => {
|
|
3990
|
+
const idx = manifest.spaces.findIndex((s) => s.id === spaceId);
|
|
3991
|
+
if (idx === -1) throw new Error(`Space "${spaceId}" not found`);
|
|
3992
|
+
if (manifest.activeSpaceId === spaceId) {
|
|
3993
|
+
manifest.activeSpaceId = "personal";
|
|
3735
3994
|
}
|
|
3736
|
-
|
|
3737
|
-
|
|
3738
|
-
|
|
3739
|
-
|
|
3740
|
-
|
|
3741
|
-
|
|
3742
|
-
|
|
3743
|
-
|
|
3995
|
+
for (const space of manifest.spaces) {
|
|
3996
|
+
if (space.parentSpaceId === spaceId) {
|
|
3997
|
+
space.parentSpaceId = void 0;
|
|
3998
|
+
}
|
|
3999
|
+
}
|
|
4000
|
+
manifest.spaces.splice(idx, 1);
|
|
4001
|
+
});
|
|
4002
|
+
appendAudit(
|
|
4003
|
+
{
|
|
4004
|
+
action: "space.delete",
|
|
4005
|
+
sourceSpaceId: spaceId,
|
|
4006
|
+
details: `Deleted space "${spaceId}"`
|
|
4007
|
+
},
|
|
4008
|
+
baseDir
|
|
4009
|
+
);
|
|
3744
4010
|
}
|
|
3745
4011
|
function switchSpace(spaceId, baseDir) {
|
|
3746
|
-
const
|
|
3747
|
-
|
|
3748
|
-
|
|
3749
|
-
|
|
3750
|
-
|
|
3751
|
-
|
|
3752
|
-
|
|
3753
|
-
|
|
3754
|
-
|
|
3755
|
-
|
|
3756
|
-
|
|
3757
|
-
|
|
4012
|
+
const { previousId, spaceName } = updateManifest(baseDir, (manifest) => {
|
|
4013
|
+
const space = manifest.spaces.find((s) => s.id === spaceId);
|
|
4014
|
+
if (!space) throw new Error(`Space "${spaceId}" not found`);
|
|
4015
|
+
const previousId2 = manifest.activeSpaceId;
|
|
4016
|
+
manifest.activeSpaceId = spaceId;
|
|
4017
|
+
return { previousId: previousId2, spaceName: space.name };
|
|
4018
|
+
});
|
|
4019
|
+
appendAudit(
|
|
4020
|
+
{
|
|
4021
|
+
action: "space.switch",
|
|
4022
|
+
sourceSpaceId: previousId,
|
|
4023
|
+
targetSpaceId: spaceId,
|
|
4024
|
+
details: `Switched from "${previousId}" to "${spaceId}"`
|
|
4025
|
+
},
|
|
4026
|
+
baseDir
|
|
4027
|
+
);
|
|
3758
4028
|
return {
|
|
3759
4029
|
previousSpaceId: previousId,
|
|
3760
4030
|
currentSpaceId: spaceId,
|
|
3761
|
-
message: `Switched to "${
|
|
4031
|
+
message: `Switched to "${spaceName}"`
|
|
3762
4032
|
};
|
|
3763
4033
|
}
|
|
3764
4034
|
function pushToSpace(sourceSpaceId, targetSpaceId, options) {
|
|
@@ -3772,12 +4042,15 @@ function pushToSpace(sourceSpaceId, targetSpaceId, options) {
|
|
|
3772
4042
|
filterIds: options?.memoryIds,
|
|
3773
4043
|
force: options?.force
|
|
3774
4044
|
});
|
|
3775
|
-
appendAudit(
|
|
3776
|
-
|
|
3777
|
-
|
|
3778
|
-
|
|
3779
|
-
|
|
3780
|
-
|
|
4045
|
+
appendAudit(
|
|
4046
|
+
{
|
|
4047
|
+
action: "space.push",
|
|
4048
|
+
sourceSpaceId,
|
|
4049
|
+
targetSpaceId,
|
|
4050
|
+
details: `Pushed ${result.merged} memories, ${result.conflicts.length} conflicts`
|
|
4051
|
+
},
|
|
4052
|
+
options?.baseDir
|
|
4053
|
+
);
|
|
3781
4054
|
return {
|
|
3782
4055
|
sourceSpaceId,
|
|
3783
4056
|
targetSpaceId,
|
|
@@ -3797,12 +4070,15 @@ function pullFromSpace(sourceSpaceId, targetSpaceId, options) {
|
|
|
3797
4070
|
filterIds: options?.memoryIds,
|
|
3798
4071
|
force: options?.force
|
|
3799
4072
|
});
|
|
3800
|
-
appendAudit(
|
|
3801
|
-
|
|
3802
|
-
|
|
3803
|
-
|
|
3804
|
-
|
|
3805
|
-
|
|
4073
|
+
appendAudit(
|
|
4074
|
+
{
|
|
4075
|
+
action: "space.pull",
|
|
4076
|
+
sourceSpaceId,
|
|
4077
|
+
targetSpaceId,
|
|
4078
|
+
details: `Pulled ${result.merged} memories, ${result.conflicts.length} conflicts`
|
|
4079
|
+
},
|
|
4080
|
+
options?.baseDir
|
|
4081
|
+
);
|
|
3806
4082
|
return {
|
|
3807
4083
|
sourceSpaceId,
|
|
3808
4084
|
targetSpaceId,
|
|
@@ -3812,22 +4088,26 @@ function pullFromSpace(sourceSpaceId, targetSpaceId, options) {
|
|
|
3812
4088
|
};
|
|
3813
4089
|
}
|
|
3814
4090
|
function shareSpace(spaceId, members, baseDir) {
|
|
3815
|
-
const
|
|
3816
|
-
|
|
3817
|
-
|
|
3818
|
-
|
|
3819
|
-
|
|
3820
|
-
|
|
3821
|
-
|
|
3822
|
-
|
|
3823
|
-
|
|
3824
|
-
|
|
3825
|
-
|
|
3826
|
-
|
|
4091
|
+
const spaceName = updateManifest(baseDir, (manifest) => {
|
|
4092
|
+
const space = manifest.spaces.find((s) => s.id === spaceId);
|
|
4093
|
+
if (!space) throw new Error(`Space "${spaceId}" not found`);
|
|
4094
|
+
if (space.kind === "personal") throw new Error("Cannot share personal space");
|
|
4095
|
+
space.members = [.../* @__PURE__ */ new Set([...space.members ?? [], ...members])];
|
|
4096
|
+
space.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
4097
|
+
return space.name;
|
|
4098
|
+
});
|
|
4099
|
+
appendAudit(
|
|
4100
|
+
{
|
|
4101
|
+
action: "space.share",
|
|
4102
|
+
sourceSpaceId: spaceId,
|
|
4103
|
+
details: `Shared with: ${members.join(", ")}`
|
|
4104
|
+
},
|
|
4105
|
+
baseDir
|
|
4106
|
+
);
|
|
3827
4107
|
return {
|
|
3828
4108
|
spaceId,
|
|
3829
4109
|
sharedWith: members,
|
|
3830
|
-
message: `Shared "${
|
|
4110
|
+
message: `Shared "${spaceName}" with ${members.length} member(s)`
|
|
3831
4111
|
};
|
|
3832
4112
|
}
|
|
3833
4113
|
function promoteSpace(sourceSpaceId, targetSpaceId, options) {
|
|
@@ -3846,12 +4126,15 @@ function promoteSpace(sourceSpaceId, targetSpaceId, options) {
|
|
|
3846
4126
|
filterIds: options?.memoryIds,
|
|
3847
4127
|
force: options?.forceOverwrite !== void 0 ? options.forceOverwrite : options?.force ?? false
|
|
3848
4128
|
});
|
|
3849
|
-
appendAudit(
|
|
3850
|
-
|
|
3851
|
-
|
|
3852
|
-
|
|
3853
|
-
|
|
3854
|
-
|
|
4129
|
+
appendAudit(
|
|
4130
|
+
{
|
|
4131
|
+
action: "space.promote",
|
|
4132
|
+
sourceSpaceId,
|
|
4133
|
+
targetSpaceId,
|
|
4134
|
+
details: `Promoted ${result.merged} memories from "${source.name}" to "${target.name}"`
|
|
4135
|
+
},
|
|
4136
|
+
options?.baseDir
|
|
4137
|
+
);
|
|
3855
4138
|
return {
|
|
3856
4139
|
sourceSpaceId,
|
|
3857
4140
|
targetSpaceId,
|
|
@@ -3870,12 +4153,15 @@ function mergeSpaces(sourceSpaceId, targetSpaceId, options) {
|
|
|
3870
4153
|
const result = copyMemories(source.memoryDir, target.memoryDir, {
|
|
3871
4154
|
force: options?.force
|
|
3872
4155
|
});
|
|
3873
|
-
appendAudit(
|
|
3874
|
-
|
|
3875
|
-
|
|
3876
|
-
|
|
3877
|
-
|
|
3878
|
-
|
|
4156
|
+
appendAudit(
|
|
4157
|
+
{
|
|
4158
|
+
action: "space.merge",
|
|
4159
|
+
sourceSpaceId,
|
|
4160
|
+
targetSpaceId,
|
|
4161
|
+
details: `Merged: ${result.merged} merged, ${result.conflicts.length} conflicts, ${result.skipped} skipped`
|
|
4162
|
+
},
|
|
4163
|
+
options?.baseDir
|
|
4164
|
+
);
|
|
3879
4165
|
return {
|
|
3880
4166
|
...result,
|
|
3881
4167
|
durationMs: Date.now() - startTime
|
|
@@ -3895,7 +4181,8 @@ function appendAudit(entry, baseDir) {
|
|
|
3895
4181
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3896
4182
|
...entry
|
|
3897
4183
|
};
|
|
3898
|
-
fs7.appendFileSync(auditPath, JSON.stringify(full)
|
|
4184
|
+
fs7.appendFileSync(auditPath, `${JSON.stringify(full)}
|
|
4185
|
+
`);
|
|
3899
4186
|
}
|
|
3900
4187
|
function copyMemories(sourceDir, targetDir, options) {
|
|
3901
4188
|
let merged = 0;
|