@wipcomputer/wip-ldm-os 0.4.85-alpha.13 → 0.4.85-alpha.15

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
@@ -22,7 +22,7 @@
22
22
 
23
23
  import { existsSync, readFileSync, writeFileSync, mkdirSync, readdirSync, cpSync, chmodSync, unlinkSync, readlinkSync, renameSync, statSync, lstatSync, symlinkSync } from 'node:fs';
24
24
  import { join, basename, resolve, dirname } from 'node:path';
25
- import { execSync } from 'node:child_process';
25
+ import { execSync, spawnSync } from 'node:child_process';
26
26
  import { fileURLToPath } from 'node:url';
27
27
 
28
28
  const __filename = fileURLToPath(import.meta.url);
@@ -272,9 +272,13 @@ function maybeSelfUpdateLdmCliBeforeInstall() {
272
272
  execSync(`npm install -g @wipcomputer/wip-ldm-os@${latest}`, { stdio: 'inherit', timeout: 60000 });
273
273
  console.log(` CLI updated to v${latest}. Re-running with new code...`);
274
274
  console.log('');
275
- const reArgs = process.argv.slice(2).join(' ') || 'install';
276
- execSync(`LDM_SELF_UPDATED=1 ldm ${reArgs}`, { stdio: 'inherit' });
277
- process.exit(0);
275
+ const reArgs = process.argv.slice(2);
276
+ const child = spawnSync('ldm', reArgs.length > 0 ? reArgs : ['install'], {
277
+ stdio: 'inherit',
278
+ env: { ...process.env, LDM_SELF_UPDATED: '1' },
279
+ });
280
+ if (child.error) throw child.error;
281
+ process.exit(child.status ?? 1);
278
282
  } catch (e) {
279
283
  console.log(` ! Self-update failed: ${e.message}. Continuing with v${PKG_VERSION}.`);
280
284
  }
@@ -1492,23 +1496,6 @@ async function showCatalogPicker() {
1492
1496
  // ── ldm install ──
1493
1497
 
1494
1498
  async function cmdInstall() {
1495
- if (!DRY_RUN && !acquireInstallLock()) return;
1496
-
1497
- // Ensure LDM is initialized
1498
- if (!existsSync(VERSION_PATH)) {
1499
- console.log(' LDM OS not initialized. Running init first...');
1500
- console.log('');
1501
- cmdInit();
1502
- }
1503
-
1504
- const { setFlags, installFromPath, installSingleTool, installToolbox, detectHarnesses } = await import('../lib/deploy.mjs');
1505
- const { detectInterfacesJSON } = await import('../lib/detect.mjs');
1506
-
1507
- // Refresh harness detection (catches newly installed harnesses)
1508
- detectHarnesses();
1509
-
1510
- setFlags({ dryRun: DRY_RUN, jsonOutput: JSON_OUTPUT, origin: 'manual' });
1511
-
1512
1499
  // --help flag (#81)
1513
1500
  if (args.includes('--help') || args.includes('-h')) {
1514
1501
  console.log(`
@@ -1530,6 +1517,23 @@ async function cmdInstall() {
1530
1517
 
1531
1518
  maybeSelfUpdateLdmCliBeforeInstall();
1532
1519
 
1520
+ if (!DRY_RUN && !acquireInstallLock()) return;
1521
+
1522
+ // Ensure LDM is initialized
1523
+ if (!existsSync(VERSION_PATH)) {
1524
+ console.log(' LDM OS not initialized. Running init first...');
1525
+ console.log('');
1526
+ cmdInit();
1527
+ }
1528
+
1529
+ const { setFlags, installFromPath, installSingleTool, installToolbox, detectHarnesses } = await import('../lib/deploy.mjs');
1530
+ const { detectInterfacesJSON } = await import('../lib/detect.mjs');
1531
+
1532
+ // Refresh harness detection (catches newly installed harnesses)
1533
+ detectHarnesses();
1534
+
1535
+ setFlags({ dryRun: DRY_RUN, jsonOutput: JSON_OUTPUT, origin: 'manual' });
1536
+
1533
1537
  // Find the target (skip flags)
1534
1538
  const target = args.slice(1).find(a => !a.startsWith('--'));
1535
1539
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wipcomputer/wip-ldm-os",
3
- "version": "0.4.85-alpha.13",
3
+ "version": "0.4.85-alpha.15",
4
4
  "type": "module",
5
5
  "description": "LDM OS: identity, memory, and sovereignty infrastructure for AI agents",
6
6
  "engines": {
@@ -30,11 +30,18 @@ assertContains("return openCodexWebClientsForKey(codexRelayKey(agentId, routeId)
30
30
  assertContains("const targets = resolveCodexWebClientsForDaemonFrame(identity.agentId, sessionId);", "daemon frames use route resolver");
31
31
  assertContains("for (const target of targets) {", "daemon frames send to every resolved target");
32
32
  assertContains("if (isCodexE2eeEnvelope(envelope) && envelope.session) {", "web e2ee messages are detected");
33
+ assertContains("envelope.route_thread_id = threadId;", "relay injects ticket-bound thread into e2ee hello");
34
+ assertContains("text = JSON.stringify(envelope);", "relay forwards the route-bound e2ee hello");
33
35
  assertContains("registerCodexE2eeSessionRoute(identity.agentId, envelope.session, threadId, ws);", "web e2ee session is registered");
34
36
  assertContains("const clientCount = addCodexWebClient(key, ws);", "new web connections are added without replacing existing clients");
35
37
  assertContains("removeCodexWebClient(key, ws);", "close cleanup removes only the closing socket");
36
38
  assertContains("removeCodexE2eeRoutesForWeb(identity.agentId, threadId, ws);", "close cleanup");
37
39
  assertContains("if (route.webKey === webKey && (!ws || route.ws === ws)) {", "cleanup only removes routes owned by the closing socket");
40
+ assertBefore(
41
+ "envelope.route_thread_id = threadId;",
42
+ "daemonWs.send(text);",
43
+ "web route thread is injected before forwarding to daemon",
44
+ );
38
45
  assertBefore(
39
46
  "registerCodexE2eeSessionRoute(identity.agentId, envelope.session, threadId, ws);",
40
47
  "daemonWs.send(text);",
@@ -1,6 +1,8 @@
1
1
  #!/usr/bin/env node
2
- import { readFileSync } from 'node:fs';
2
+ import { chmodSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
3
+ import { tmpdir } from 'node:os';
3
4
  import { dirname, join } from 'node:path';
5
+ import { spawnSync } from 'node:child_process';
4
6
  import { fileURLToPath } from 'node:url';
5
7
 
6
8
  const root = dirname(dirname(fileURLToPath(import.meta.url)));
@@ -21,17 +23,27 @@ if (!helperBlock.includes("execSync(`npm install -g @wipcomputer/wip-ldm-os@${la
21
23
  throw new Error('Self-update helper must update LDM OS before real installs');
22
24
  }
23
25
 
24
- if (!helperBlock.includes('execSync(`LDM_SELF_UPDATED=1 ldm ${reArgs}`')) {
25
- throw new Error('Self-update helper must re-run the original install command');
26
+ if (!helperBlock.includes("spawnSync('ldm'")) {
27
+ throw new Error('Self-update helper must re-run the original install command without shell joining args');
28
+ }
29
+
30
+ if (helperBlock.includes('process.argv.slice(2).join')) {
31
+ throw new Error('Self-update helper must preserve argv boundaries when re-running install');
26
32
  }
27
33
 
28
34
  const cmdInstallIdx = cli.indexOf('async function cmdInstall()');
35
+ const lockIdx = cli.indexOf('acquireInstallLock()', cmdInstallIdx);
36
+ const initIdx = cli.indexOf('LDM OS not initialized. Running init first', cmdInstallIdx);
29
37
  const targetIdx = cli.indexOf('// Find the target (skip flags)', cmdInstallIdx);
30
38
  const preflightCallIdx = cli.indexOf('maybeSelfUpdateLdmCliBeforeInstall();', cmdInstallIdx);
31
39
  if (cmdInstallIdx === -1 || targetIdx === -1 || preflightCallIdx === -1) {
32
40
  throw new Error('Could not find cmdInstall self-update placement');
33
41
  }
34
42
 
43
+ if (preflightCallIdx > lockIdx || preflightCallIdx > initIdx) {
44
+ throw new Error('Self-update preflight must run before lock acquisition and init work');
45
+ }
46
+
35
47
  if (preflightCallIdx > targetIdx) {
36
48
  throw new Error('Self-update preflight must run before target resolution so app installs are covered');
37
49
  }
@@ -43,4 +55,77 @@ if (oldCatalogBlock !== -1 && oldCatalogBlock < autoDetectIdx) {
43
55
  throw new Error('Catalog install should not own the only self-update block');
44
56
  }
45
57
 
58
+ const tempRoot = mkdtempSync(join(tmpdir(), 'ldm-target-self-update-'));
59
+ try {
60
+ const home = join(tempRoot, 'home');
61
+ const fakeBin = join(tempRoot, 'bin');
62
+ const target = join(tempRoot, 'target skill with spaces');
63
+
64
+ mkdirSync(join(home, '.ldm'), { recursive: true });
65
+ writeFileSync(join(home, '.ldm', 'version.json'), JSON.stringify({
66
+ version: '0.0.0',
67
+ installed: new Date().toISOString(),
68
+ updated: new Date().toISOString(),
69
+ }, null, 2) + '\n');
70
+
71
+ mkdirSync(fakeBin, { recursive: true });
72
+ const fakeNpm = join(fakeBin, 'npm');
73
+ writeFileSync(fakeNpm, `#!/usr/bin/env bash
74
+ if [ "$1" = "view" ] && [ "$2" = "@wipcomputer/wip-ldm-os" ] && [ "$3" = "dist-tags.alpha" ]; then
75
+ echo "99.0.0-alpha.1"
76
+ exit 0
77
+ fi
78
+ echo "unexpected npm command: $*" >&2
79
+ exit 64
80
+ `);
81
+ chmodSync(fakeNpm, 0o755);
82
+
83
+ mkdirSync(target, { recursive: true });
84
+ writeFileSync(join(target, 'SKILL.md'), `---
85
+ name: test-target-skill
86
+ description: Test target skill for installer self-update dry-run checks.
87
+ ---
88
+
89
+ # Test Target Skill
90
+ `);
91
+
92
+ const result = spawnSync(process.execPath, [
93
+ join(root, 'bin', 'ldm.js'),
94
+ 'install',
95
+ '--alpha',
96
+ '--dry-run',
97
+ target,
98
+ ], {
99
+ cwd: root,
100
+ encoding: 'utf8',
101
+ env: {
102
+ ...process.env,
103
+ HOME: home,
104
+ PATH: `${fakeBin}:${process.env.PATH || ''}`,
105
+ },
106
+ });
107
+
108
+ if (result.status !== 0) {
109
+ throw new Error(`Runtime dry-run exited ${result.status}\nstdout:\n${result.stdout}\nstderr:\n${result.stderr}`);
110
+ }
111
+
112
+ if (!result.stdout.includes('LDM OS CLI v')) {
113
+ throw new Error(`Runtime dry-run did not print the LDM OS skew warning\nstdout:\n${result.stdout}`);
114
+ }
115
+
116
+ if (!result.stdout.includes('-> v99.0.0-alpha.1 (alpha track) is available.')) {
117
+ throw new Error(`Runtime dry-run did not include the selected alpha track version\nstdout:\n${result.stdout}`);
118
+ }
119
+
120
+ if (!result.stdout.includes('Dry run only: continuing with v')) {
121
+ throw new Error(`Runtime dry-run did not say it would continue without updating\nstdout:\n${result.stdout}`);
122
+ }
123
+
124
+ if (!result.stdout.includes('Installing: target skill with spaces (dry run)')) {
125
+ throw new Error(`Runtime dry-run did not continue to the targeted install preview\nstdout:\n${result.stdout}`);
126
+ }
127
+ } finally {
128
+ rmSync(tempRoot, { recursive: true, force: true });
129
+ }
130
+
46
131
  console.log('targeted install self-update regression checks passed');
@@ -3129,10 +3129,16 @@ httpServer.on("upgrade", (req, socket, head) => {
3129
3129
  const clientCount = addCodexWebClient(key, ws);
3130
3130
  console.log("codex-relay: web online " + key + " clients=" + clientCount);
3131
3131
  ws.on("message", (data) => {
3132
- const text = data.toString();
3132
+ let text = data.toString();
3133
3133
  let envelope = null;
3134
3134
  try { envelope = JSON.parse(text); } catch {}
3135
3135
  if (isCodexE2eeEnvelope(envelope) && envelope.session) {
3136
+ // The browser cannot be allowed to choose this value. The relay
3137
+ // owns the route because it consumed the ticket for this URL
3138
+ // thread. The daemon uses this metadata to bind the encrypted
3139
+ // session before it decrypts any session.* command.
3140
+ envelope.route_thread_id = threadId;
3141
+ text = JSON.stringify(envelope);
3136
3142
  registerCodexE2eeSessionRoute(identity.agentId, envelope.session, threadId, ws);
3137
3143
  }
3138
3144
  const daemonWs = codexDaemons.get(identity.agentId);