@wipcomputer/wip-ldm-os 0.4.87-alpha.2 → 0.4.87-alpha.3
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/bin/ldm.js +43 -35
- package/package.json +1 -1
- package/scripts/test-bin-manifest.mjs +43 -0
- package/src/bridge/openclaw.plugin.json +13 -0
- package/src/bridge/package.json +3 -0
package/bin/ldm.js
CHANGED
|
@@ -966,6 +966,8 @@ function deployRules() {
|
|
|
966
966
|
function deployBridge() {
|
|
967
967
|
const ldmBridgeDir = join(LDM_EXTENSIONS, 'lesa-bridge');
|
|
968
968
|
const ocBridgeDir = join(HOME, '.openclaw', 'extensions', 'lesa-bridge');
|
|
969
|
+
const bridgeMetadataDir = join(__dirname, '..', 'src', 'bridge');
|
|
970
|
+
const bridgeMetadataFiles = ['openclaw.plugin.json', 'package.json'];
|
|
969
971
|
|
|
970
972
|
// Deploy targets: LDM path (canonical) and OpenClaw path (where the plugin loads)
|
|
971
973
|
// Create dirs if missing so first-time deploy works (don't skip with filter)
|
|
@@ -1002,51 +1004,52 @@ function deployBridge() {
|
|
|
1002
1004
|
|
|
1003
1005
|
if (!bridgeSrc || !existsSync(bridgeSrc)) return 0;
|
|
1004
1006
|
|
|
1005
|
-
|
|
1006
|
-
const
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
const
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
const
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
if (!srcContent.equals(destContent)) {
|
|
1020
|
-
changed = true;
|
|
1021
|
-
break;
|
|
1007
|
+
const srcFiles = readdirSync(bridgeSrc).filter(f => f.endsWith('.js') || f.endsWith('.d.ts'));
|
|
1008
|
+
const srcFileSet = new Set(srcFiles);
|
|
1009
|
+
|
|
1010
|
+
function targetNeedsDeploy(target) {
|
|
1011
|
+
const checkDest = join(target.dir, 'dist');
|
|
1012
|
+
try {
|
|
1013
|
+
if (!existsSync(checkDest)) return true;
|
|
1014
|
+
for (const file of srcFiles) {
|
|
1015
|
+
const srcPath = join(bridgeSrc, file);
|
|
1016
|
+
const destPath = join(checkDest, file);
|
|
1017
|
+
if (!existsSync(destPath)) return true;
|
|
1018
|
+
const srcContent = readFileSync(srcPath);
|
|
1019
|
+
const destContent = readFileSync(destPath);
|
|
1020
|
+
if (!srcContent.equals(destContent)) return true;
|
|
1022
1021
|
}
|
|
1023
|
-
|
|
1024
|
-
// Also check if there are stale files in the target that aren't in the source
|
|
1025
|
-
if (!changed) {
|
|
1022
|
+
|
|
1026
1023
|
const destFiles = readdirSync(checkDest).filter(f => f.endsWith('.js'));
|
|
1027
|
-
const srcFileSet = new Set(srcFiles);
|
|
1028
1024
|
for (const file of destFiles) {
|
|
1029
|
-
if (!srcFileSet.has(file))
|
|
1030
|
-
changed = true; // stale chunk file found
|
|
1031
|
-
break;
|
|
1032
|
-
}
|
|
1025
|
+
if (!srcFileSet.has(file)) return true;
|
|
1033
1026
|
}
|
|
1027
|
+
|
|
1028
|
+
for (const file of bridgeMetadataFiles) {
|
|
1029
|
+
const srcPath = join(bridgeMetadataDir, file);
|
|
1030
|
+
const destPath = join(target.dir, file);
|
|
1031
|
+
if (!existsSync(srcPath) || !existsSync(destPath)) return true;
|
|
1032
|
+
if (!readFileSync(srcPath).equals(readFileSync(destPath))) return true;
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
return false;
|
|
1036
|
+
} catch {
|
|
1037
|
+
return true;
|
|
1034
1038
|
}
|
|
1035
|
-
} catch {
|
|
1036
|
-
changed = true; // if comparison fails, copy anyway
|
|
1037
1039
|
}
|
|
1038
1040
|
|
|
1039
|
-
|
|
1041
|
+
const staleTargets = targets.filter(targetNeedsDeploy);
|
|
1042
|
+
if (staleTargets.length === 0) return 0;
|
|
1040
1043
|
|
|
1041
1044
|
if (DRY_RUN) {
|
|
1042
|
-
console.log(` + would deploy bridge files to ${
|
|
1045
|
+
console.log(` + would deploy bridge files to ${staleTargets.map(t => t.label).join(' + ')}`);
|
|
1043
1046
|
return 0;
|
|
1044
1047
|
}
|
|
1045
1048
|
|
|
1046
|
-
const srcFiles = readdirSync(bridgeSrc).filter(f => f.endsWith('.js') || f.endsWith('.d.ts'));
|
|
1047
1049
|
let totalDeployed = 0;
|
|
1050
|
+
let ldmTargetDeployed = false;
|
|
1048
1051
|
|
|
1049
|
-
for (const target of
|
|
1052
|
+
for (const target of staleTargets) {
|
|
1050
1053
|
const dest = join(target.dir, 'dist');
|
|
1051
1054
|
try {
|
|
1052
1055
|
mkdirSync(dest, { recursive: true });
|
|
@@ -1065,16 +1068,21 @@ function deployBridge() {
|
|
|
1065
1068
|
for (const file of srcFiles) {
|
|
1066
1069
|
cpSync(join(bridgeSrc, file), join(dest, file));
|
|
1067
1070
|
}
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
+
for (const file of bridgeMetadataFiles) {
|
|
1072
|
+
cpSync(join(bridgeMetadataDir, file), join(target.dir, file));
|
|
1073
|
+
}
|
|
1074
|
+
const deployedCount = srcFiles.length + bridgeMetadataFiles.length;
|
|
1075
|
+
console.log(` + bridge deployed to ${target.label} (${deployedCount} files)`);
|
|
1076
|
+
installLog(`Bridge deployed: ${deployedCount} files to ${target.label}`);
|
|
1077
|
+
totalDeployed += deployedCount;
|
|
1078
|
+
if (target.dir === ldmBridgeDir) ldmTargetDeployed = true;
|
|
1071
1079
|
} catch (e) {
|
|
1072
1080
|
console.log(` ! bridge deploy to ${target.label} failed: ${e.message}`);
|
|
1073
1081
|
}
|
|
1074
1082
|
}
|
|
1075
1083
|
|
|
1076
1084
|
// Re-register MCP server to point to the canonical LDM path
|
|
1077
|
-
if (
|
|
1085
|
+
if (ldmTargetDeployed && existsSync(join(ldmBridgeDir, 'dist', 'mcp-server.js'))) {
|
|
1078
1086
|
try {
|
|
1079
1087
|
const mcpPath = join(ldmBridgeDir, 'dist', 'mcp-server.js');
|
|
1080
1088
|
execSync(`claude mcp add lesa-bridge --scope user -- node ${mcpPath}`, {
|
package/package.json
CHANGED
|
@@ -249,6 +249,49 @@ function runInstall({ home, fakeBin }) {
|
|
|
249
249
|
rmSync(w.home, { recursive: true, force: true });
|
|
250
250
|
}
|
|
251
251
|
|
|
252
|
+
// Test A2: bridge deployment forms a complete OpenClaw plugin package.
|
|
253
|
+
console.log('Integration: ldm install deploys bridge manifest and package metadata');
|
|
254
|
+
{
|
|
255
|
+
const w = makeIntegrationHome();
|
|
256
|
+
const out = runInstall(w);
|
|
257
|
+
const text = typeof out === 'string' ? out : out.output;
|
|
258
|
+
const bridgeRoot = join(w.home, '.openclaw', 'extensions', 'lesa-bridge');
|
|
259
|
+
const manifest = JSON.parse(readFileSync(join(bridgeRoot, 'openclaw.plugin.json'), 'utf8'));
|
|
260
|
+
const bridgePackage = JSON.parse(readFileSync(join(bridgeRoot, 'package.json'), 'utf8'));
|
|
261
|
+
assert(manifest.id === 'lesa-bridge', 'bridge manifest id is deployed', text);
|
|
262
|
+
assert(manifest.activation?.onStartup === true, 'bridge startup activation is deployed', text);
|
|
263
|
+
assert(bridgePackage.openclaw?.extensions?.includes('./dist/openclaw.js'), 'bridge runtime entry is deployed', text);
|
|
264
|
+
assert(existsSync(join(bridgeRoot, 'dist', 'openclaw.js')), 'bridge runtime is deployed', text);
|
|
265
|
+
rmSync(w.home, { recursive: true, force: true });
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Test A3: a partial prior install must not make an OpenClaw retry a no-op.
|
|
269
|
+
console.log('Integration: ldm install repairs only a stale OpenClaw Bridge target');
|
|
270
|
+
{
|
|
271
|
+
const w = makeIntegrationHome();
|
|
272
|
+
const first = runInstall(w);
|
|
273
|
+
const firstText = typeof first === 'string' ? first : first.output;
|
|
274
|
+
const ldmRoot = join(w.home, '.ldm', 'extensions', 'lesa-bridge');
|
|
275
|
+
const bridgeRoot = join(w.home, '.openclaw', 'extensions', 'lesa-bridge');
|
|
276
|
+
const ldmManifestBefore = readFileSync(join(ldmRoot, 'openclaw.plugin.json'));
|
|
277
|
+
|
|
278
|
+
rmSync(join(bridgeRoot, 'openclaw.plugin.json'));
|
|
279
|
+
writeFileSync(join(bridgeRoot, 'package.json'), '{"stale":true}\n');
|
|
280
|
+
|
|
281
|
+
const retry = runInstall(w);
|
|
282
|
+
const retryText = typeof retry === 'string' ? retry : retry.output;
|
|
283
|
+
const repairedManifest = JSON.parse(readFileSync(join(bridgeRoot, 'openclaw.plugin.json'), 'utf8'));
|
|
284
|
+
const repairedPackage = JSON.parse(readFileSync(join(bridgeRoot, 'package.json'), 'utf8'));
|
|
285
|
+
|
|
286
|
+
assert(!first.error, 'first Bridge install succeeds', firstText);
|
|
287
|
+
assert(repairedManifest.id === 'lesa-bridge', 'missing OpenClaw manifest is repaired', retryText);
|
|
288
|
+
assert(repairedPackage.openclaw?.extensions?.includes('./dist/openclaw.js'), 'stale OpenClaw package metadata is repaired', retryText);
|
|
289
|
+
assert(/bridge deployed to ~\/\.openclaw\/extensions\/lesa-bridge/.test(retryText), 'retry deploys the stale OpenClaw target', retryText);
|
|
290
|
+
assert(!/bridge deployed to ~\/\.ldm\/extensions\/lesa-bridge/.test(retryText), 'retry leaves the current LDM target untouched', retryText);
|
|
291
|
+
assert(readFileSync(join(ldmRoot, 'openclaw.plugin.json')).equals(ldmManifestBefore), 'LDM manifest remains byte-identical', retryText);
|
|
292
|
+
rmSync(w.home, { recursive: true, force: true });
|
|
293
|
+
}
|
|
294
|
+
|
|
252
295
|
// Test B: conflict between LDM CLI and a fake plugin BOTH claiming the same name.
|
|
253
296
|
// Install must abort BEFORE writing fake-shim.sh.
|
|
254
297
|
console.log('Integration: ldm install aborts on conflict before writing');
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "lesa-bridge",
|
|
3
|
+
"name": "LDM Bridge",
|
|
4
|
+
"description": "Agent-to-agent communication and shared-memory access through LDM OS.",
|
|
5
|
+
"activation": {
|
|
6
|
+
"onStartup": true
|
|
7
|
+
},
|
|
8
|
+
"configSchema": {
|
|
9
|
+
"type": "object",
|
|
10
|
+
"additionalProperties": false,
|
|
11
|
+
"properties": {}
|
|
12
|
+
}
|
|
13
|
+
}
|
package/src/bridge/package.json
CHANGED
|
@@ -4,6 +4,9 @@
|
|
|
4
4
|
"private": true,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "WIP Bridge ... agent-to-agent communication, memory search, skill bridging. Core module of LDM OS.",
|
|
7
|
+
"openclaw": {
|
|
8
|
+
"extensions": ["./dist/openclaw.js"]
|
|
9
|
+
},
|
|
7
10
|
"dependencies": {
|
|
8
11
|
"@modelcontextprotocol/sdk": "^1.12.1",
|
|
9
12
|
"better-sqlite3": "^11.8.1",
|