@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 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
- // Check if files differ (compare against the LDM target, or first available)
1006
- const checkDest = join(targets[0].dir, 'dist');
1007
- let changed = false;
1008
- try {
1009
- const srcFiles = readdirSync(bridgeSrc).filter(f => f.endsWith('.js') || f.endsWith('.d.ts'));
1010
- for (const file of srcFiles) {
1011
- const srcPath = join(bridgeSrc, file);
1012
- const destPath = join(checkDest, file);
1013
- if (!existsSync(destPath)) {
1014
- changed = true;
1015
- break;
1016
- }
1017
- const srcContent = readFileSync(srcPath);
1018
- const destContent = readFileSync(destPath);
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
- if (!changed) return 0;
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 ${targets.map(t => t.label).join(' + ')}`);
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 targets) {
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
- console.log(` + bridge deployed to ${target.label} (${srcFiles.length} files)`);
1069
- installLog(`Bridge deployed: ${srcFiles.length} files to ${target.label}`);
1070
- totalDeployed += srcFiles.length;
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 (totalDeployed > 0 && existsSync(join(ldmBridgeDir, 'dist', 'mcp-server.js'))) {
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wipcomputer/wip-ldm-os",
3
- "version": "0.4.87-alpha.2",
3
+ "version": "0.4.87-alpha.3",
4
4
  "type": "module",
5
5
  "description": "LDM OS: identity, memory, and sovereignty infrastructure for AI agents",
6
6
  "engines": {
@@ -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
+ }
@@ -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",