@wipcomputer/wip-ldm-os 0.4.85-alpha.25 → 0.4.85-alpha.27

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 { execFileSync, execSync, spawnSync } 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);
@@ -3284,27 +3284,89 @@ async function cmdDoctor() {
3284
3284
 
3285
3285
  const STATUS_NPM_TIMEOUT_MS = parsePositiveInt(process.env.LDM_STATUS_NPM_TIMEOUT_MS, 5000);
3286
3286
  const STATUS_TOTAL_BUDGET_MS = parsePositiveInt(process.env.LDM_STATUS_TOTAL_BUDGET_MS, 60000);
3287
+ const STATUS_NPM_CONCURRENCY = parsePositiveInt(process.env.LDM_STATUS_NPM_CONCURRENCY, 8);
3288
+ const STATUS_NPM_REGISTRY_URL = process.env.LDM_STATUS_NPM_REGISTRY_URL || 'https://registry.npmjs.org';
3287
3289
 
3288
- function npmViewVersionForStatus(pkg, timeoutMs) {
3289
- return execFileSync('npm', ['view', pkg, 'version'], {
3290
- encoding: 'utf8',
3291
- timeout: timeoutMs,
3292
- stdio: ['ignore', 'pipe', 'ignore'],
3293
- }).trim();
3290
+ async function npmViewVersionForStatus(pkg, timeoutMs) {
3291
+ const controller = new AbortController();
3292
+ const timeout = setTimeout(() => controller.abort(), timeoutMs);
3293
+ try {
3294
+ const registry = STATUS_NPM_REGISTRY_URL.replace(/\/+$/, '');
3295
+ const response = await fetch(`${registry}/${encodeURIComponent(pkg)}`, {
3296
+ signal: controller.signal,
3297
+ headers: { accept: 'application/vnd.npm.install-v1+json, application/json' },
3298
+ });
3299
+ if (!response.ok) {
3300
+ const error = new Error(`npm registry returned ${response.status}`);
3301
+ error.statusCode = response.status;
3302
+ throw error;
3303
+ }
3304
+ const metadata = await response.json();
3305
+ return metadata?.['dist-tags']?.latest || '';
3306
+ } finally {
3307
+ clearTimeout(timeout);
3308
+ }
3294
3309
  }
3295
3310
 
3296
3311
  function remainingStatusBudgetMs(startedAt) {
3297
3312
  return Math.max(0, STATUS_TOTAL_BUDGET_MS - (Date.now() - startedAt));
3298
3313
  }
3299
3314
 
3315
+ function formatStatusElapsed(ms) {
3316
+ if (!Number.isFinite(ms) || ms <= 0) return '0ms';
3317
+ if (ms < 1000) return `${Math.round(ms)}ms`;
3318
+ return `${(ms / 1000).toFixed(1)}s`;
3319
+ }
3320
+
3300
3321
  function classifyStatusCheckError(error) {
3301
- if (error?.signal === 'SIGTERM' || error?.code === 'ETIMEDOUT' || String(error?.message || '').includes('ETIMEDOUT')) {
3322
+ if (error?.name === 'AbortError' || error?.signal === 'SIGTERM' || error?.code === 'ETIMEDOUT' || String(error?.message || '').includes('ETIMEDOUT')) {
3302
3323
  return 'timeout';
3303
3324
  }
3304
3325
  return 'unavailable';
3305
3326
  }
3306
3327
 
3307
- function cmdStatus() {
3328
+ async function runStatusProbesWithConcurrency(items, concurrency, statusStartedAt) {
3329
+ if (items.length === 0) return [];
3330
+
3331
+ const results = new Array(items.length);
3332
+ const workerCount = Math.max(1, Math.min(concurrency, items.length));
3333
+ let nextIndex = 0;
3334
+
3335
+ async function worker() {
3336
+ while (nextIndex < items.length) {
3337
+ const index = nextIndex;
3338
+ nextIndex += 1;
3339
+ const item = items[index];
3340
+ const remaining = remainingStatusBudgetMs(statusStartedAt);
3341
+
3342
+ if (remaining <= 0) {
3343
+ results[index] = { ...item, status: 'skipped', reason: 'budget', elapsedMs: 0 };
3344
+ continue;
3345
+ }
3346
+
3347
+ const timeout = Math.min(STATUS_NPM_TIMEOUT_MS, remaining);
3348
+ const probeStartedAt = Date.now();
3349
+ console.log(` ${item.name}: checking npm`);
3350
+
3351
+ try {
3352
+ const latest = await npmViewVersionForStatus(item.npm, timeout);
3353
+ results[index] = { ...item, status: 'ok', latest, elapsedMs: Date.now() - probeStartedAt };
3354
+ } catch (error) {
3355
+ results[index] = {
3356
+ ...item,
3357
+ status: 'skipped',
3358
+ reason: classifyStatusCheckError(error),
3359
+ elapsedMs: Date.now() - probeStartedAt,
3360
+ };
3361
+ }
3362
+ }
3363
+ }
3364
+
3365
+ await Promise.all(Array.from({ length: workerCount }, () => worker()));
3366
+ return results;
3367
+ }
3368
+
3369
+ async function cmdStatus() {
3308
3370
  const version = readJSON(VERSION_PATH);
3309
3371
  const registry = readJSON(REGISTRY_PATH);
3310
3372
  const extCount = Object.keys(registry?.extensions || {}).length;
@@ -3338,18 +3400,14 @@ function cmdStatus() {
3338
3400
  console.log('');
3339
3401
  console.log(' Checking updates:');
3340
3402
 
3341
- let cliUpdate = null;
3342
- try {
3343
- const timeout = Math.min(STATUS_NPM_TIMEOUT_MS, remainingStatusBudgetMs(statusStartedAt));
3344
- if (timeout <= 0) throw new Error('status update-check budget exhausted');
3345
- console.log(' ldm cli: checking npm');
3346
- const latest = npmViewVersionForStatus('@wipcomputer/wip-ldm-os', timeout);
3347
- if (latest && semverNewer(latest, PKG_VERSION)) cliUpdate = latest;
3348
- } catch (error) {
3349
- skipped.push({ name: 'ldm cli', npm: '@wipcomputer/wip-ldm-os', reason: classifyStatusCheckError(error) });
3350
- }
3351
-
3352
3403
  // Check extensions against npm using registry source info (#262)
3404
+ const probeItems = [{
3405
+ kind: 'cli',
3406
+ name: 'ldm cli',
3407
+ npm: '@wipcomputer/wip-ldm-os',
3408
+ current: PKG_VERSION,
3409
+ }];
3410
+
3353
3411
  const updates = [];
3354
3412
  for (const [name, info] of Object.entries(registry?.extensions || {})) {
3355
3413
  // Use registry source.npm (v2) or fall back to extension's package.json
@@ -3362,19 +3420,37 @@ function cmdStatus() {
3362
3420
  if (!npmPkg) continue;
3363
3421
  const currentVersion = info?.installed?.version || info.version;
3364
3422
  if (!currentVersion) continue;
3365
- try {
3366
- const timeout = Math.min(STATUS_NPM_TIMEOUT_MS, remainingStatusBudgetMs(statusStartedAt));
3367
- if (timeout <= 0) {
3368
- skipped.push({ name, npm: npmPkg, reason: 'budget' });
3369
- continue;
3370
- }
3371
- console.log(` ${name}: checking npm`);
3372
- const latest = npmViewVersionForStatus(npmPkg, timeout);
3373
- if (latest && semverNewer(latest, currentVersion)) {
3374
- updates.push({ name, current: currentVersion, latest, npm: npmPkg });
3375
- }
3376
- } catch (error) {
3377
- skipped.push({ name, npm: npmPkg, reason: classifyStatusCheckError(error) });
3423
+ probeItems.push({
3424
+ kind: 'extension',
3425
+ name,
3426
+ npm: npmPkg,
3427
+ current: currentVersion,
3428
+ });
3429
+ }
3430
+
3431
+ let cliUpdate = null;
3432
+ const probeResults = await runStatusProbesWithConcurrency(probeItems, STATUS_NPM_CONCURRENCY, statusStartedAt);
3433
+ for (const result of probeResults) {
3434
+ if (!result) continue;
3435
+ if (result.status === 'skipped') {
3436
+ skipped.push({
3437
+ name: result.name,
3438
+ npm: result.npm,
3439
+ reason: result.reason,
3440
+ elapsedMs: result.elapsedMs,
3441
+ });
3442
+ continue;
3443
+ }
3444
+
3445
+ if (result.kind === 'cli') {
3446
+ if (result.latest && semverNewer(result.latest, PKG_VERSION)) cliUpdate = result.latest;
3447
+ } else if (result.latest && semverNewer(result.latest, result.current)) {
3448
+ updates.push({
3449
+ name: result.name,
3450
+ current: result.current,
3451
+ latest: result.latest,
3452
+ npm: result.npm,
3453
+ });
3378
3454
  }
3379
3455
  }
3380
3456
 
@@ -3407,7 +3483,7 @@ function cmdStatus() {
3407
3483
  console.log('');
3408
3484
  console.log(' Update checks skipped:');
3409
3485
  for (const item of skipped) {
3410
- console.log(` ${item.name}: [${item.reason}] ${item.npm}`);
3486
+ console.log(` ${item.name}: [${item.reason} ${formatStatusElapsed(item.elapsedMs)}] ${item.npm}`);
3411
3487
  }
3412
3488
  }
3413
3489
 
@@ -4866,7 +4942,7 @@ async function main() {
4866
4942
  await cmdDoctor();
4867
4943
  break;
4868
4944
  case 'status':
4869
- cmdStatus();
4945
+ await cmdStatus();
4870
4946
  break;
4871
4947
  case 'sessions':
4872
4948
  await cmdSessions();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wipcomputer/wip-ldm-os",
3
- "version": "0.4.85-alpha.25",
3
+ "version": "0.4.85-alpha.27",
4
4
  "type": "module",
5
5
  "description": "LDM OS: identity, memory, and sovereignty infrastructure for AI agents",
6
6
  "engines": {
@@ -18,11 +18,12 @@
18
18
  "scripts": {
19
19
  "build:bridge": "cd src/bridge && npm install && npx tsup core.ts mcp-server.ts cli.ts openclaw.ts --format esm --dts --clean --outDir ../../dist/bridge",
20
20
  "build": "npm run build:bridge",
21
- "prepublishOnly": "npm run build:bridge && npm run validate:bin-manifest && npm run test:install-prompt-policy && npm run test:ldm-status-timeout",
21
+ "prepublishOnly": "npm run build:bridge && npm run validate:bin-manifest && npm run test:install-prompt-policy && npm run test:ldm-status-timeout && npm run test:ldm-status-concurrency",
22
22
  "validate:bin-manifest": "node scripts/validate-bin-manifest.mjs",
23
23
  "test:skill-frontmatter": "node scripts/test-skill-frontmatter.mjs",
24
24
  "test:install-prompt-policy": "node scripts/test-install-prompt-policy.mjs",
25
25
  "test:ldm-status-timeout": "node scripts/test-ldm-status-timeout.mjs",
26
+ "test:ldm-status-concurrency": "node scripts/test-ldm-status-concurrency.mjs",
26
27
  "test:installer-update-tracks": "node scripts/test-installer-update-tracks.mjs",
27
28
  "test:installer-hook-toolname": "node scripts/test-installer-hook-toolname.mjs",
28
29
  "test:installer-target-self-update": "node scripts/test-installer-target-self-update.mjs",
@@ -0,0 +1,118 @@
1
+ #!/usr/bin/env node
2
+ import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
3
+ import { createServer } from 'node:http';
4
+ import { tmpdir } from 'node:os';
5
+ import { dirname, join } from 'node:path';
6
+ import { spawn } from 'node:child_process';
7
+ import { fileURLToPath } from 'node:url';
8
+
9
+ const root = dirname(dirname(fileURLToPath(import.meta.url)));
10
+ const tempRoot = mkdtempSync(join(tmpdir(), 'ldm-status-concurrency-'));
11
+ const sourceVersion = JSON.parse(readFileSync(join(root, 'package.json'), 'utf8')).version;
12
+
13
+ function assert(condition, message) {
14
+ if (!condition) throw new Error(message);
15
+ }
16
+
17
+ function listen(server) {
18
+ return new Promise((resolve) => {
19
+ server.listen(0, '127.0.0.1', () => resolve(server.address()));
20
+ });
21
+ }
22
+
23
+ function createRegistryServer(delayMs) {
24
+ let active = 0;
25
+ let maxActive = 0;
26
+ const server = createServer((_req, res) => {
27
+ active += 1;
28
+ maxActive = Math.max(maxActive, active);
29
+ setTimeout(() => {
30
+ res.setHeader('content-type', 'application/json');
31
+ res.end(JSON.stringify({ 'dist-tags': { latest: '1.0.0' } }));
32
+ active -= 1;
33
+ }, delayMs);
34
+ });
35
+ return { server, getMaxActive: () => maxActive };
36
+ }
37
+
38
+ function writeFixture(home) {
39
+ const extensions = join(home, '.ldm', 'extensions');
40
+ mkdirSync(extensions, { recursive: true });
41
+ writeFileSync(join(home, '.ldm', 'version.json'), JSON.stringify({
42
+ version: '0.0.0-test',
43
+ installed: '2026-05-12T00:00:00.000Z',
44
+ updated: '2026-05-12T00:00:00.000Z',
45
+ }, null, 2) + '\n');
46
+
47
+ const registry = { extensions: {} };
48
+ for (let i = 1; i <= 8; i += 1) {
49
+ registry.extensions[`ext-${i}`] = {
50
+ source: { npm: `ext-${i}` },
51
+ installed: { version: '1.0.0' },
52
+ };
53
+ }
54
+ writeFileSync(join(extensions, 'registry.json'), JSON.stringify(registry, null, 2) + '\n');
55
+ }
56
+
57
+ function runStatus({ concurrency, registryUrl, home }) {
58
+ return new Promise((resolve) => {
59
+ const child = spawn(process.execPath, [join(root, 'bin', 'ldm.js'), 'status'], {
60
+ cwd: root,
61
+ env: {
62
+ ...process.env,
63
+ HOME: home,
64
+ LDM_STATUS_NPM_REGISTRY_URL: registryUrl,
65
+ LDM_STATUS_NPM_CONCURRENCY: String(concurrency),
66
+ LDM_STATUS_NPM_TIMEOUT_MS: '2000',
67
+ LDM_STATUS_TOTAL_BUDGET_MS: '10000',
68
+ },
69
+ stdio: ['ignore', 'pipe', 'pipe'],
70
+ });
71
+
72
+ let stdout = '';
73
+ let stderr = '';
74
+ child.stdout.setEncoding('utf8');
75
+ child.stderr.setEncoding('utf8');
76
+ child.stdout.on('data', chunk => { stdout += chunk; });
77
+ child.stderr.on('data', chunk => { stderr += chunk; });
78
+ child.on('close', status => resolve({ status, stdout, stderr }));
79
+ });
80
+ }
81
+
82
+ async function runFixture({ concurrency, delayMs }) {
83
+ const home = join(tempRoot, `home-${concurrency}-${delayMs}`);
84
+ writeFixture(home);
85
+ const registry = createRegistryServer(delayMs);
86
+ const address = await listen(registry.server);
87
+ const startedAt = Date.now();
88
+ const result = await runStatus({
89
+ concurrency,
90
+ home,
91
+ registryUrl: `http://${address.address}:${address.port}`,
92
+ });
93
+ const elapsedMs = Date.now() - startedAt;
94
+ registry.server.closeAllConnections();
95
+ registry.server.close();
96
+ return { result, elapsedMs, maxActive: registry.getMaxActive() };
97
+ }
98
+
99
+ try {
100
+ const concurrent = await runFixture({ concurrency: 4, delayMs: 500 });
101
+ assert(concurrent.result.status === 0, `concurrent ldm status exited ${concurrent.result.status}\nstdout:\n${concurrent.result.stdout}\nstderr:\n${concurrent.result.stderr}`);
102
+ assert(concurrent.elapsedMs < 3000, `concurrent ldm status should finish well before serial runtime; elapsed ${concurrent.elapsedMs}ms`);
103
+ assert(concurrent.maxActive >= 4, `registry server should see concurrent probes; max active ${concurrent.maxActive}`);
104
+ assert(concurrent.result.stdout.includes(`LDM OS v${sourceVersion}`), `status should print installed LDM OS version\n${concurrent.result.stdout}`);
105
+ assert(concurrent.result.stdout.includes('Extensions: 8'), `status should print extension count\n${concurrent.result.stdout}`);
106
+ assert(concurrent.result.stdout.includes('ext-8: checking npm'), `status should check every staged extension\n${concurrent.result.stdout}`);
107
+ assert(!concurrent.result.stdout.includes('Update checks skipped:'), `concurrent status should not skip checks in this fixture\n${concurrent.result.stdout}`);
108
+
109
+ const serialFallback = await runFixture({ concurrency: 1, delayMs: 10 });
110
+ assert(serialFallback.result.status === 0, `serial fallback ldm status exited ${serialFallback.result.status}\nstdout:\n${serialFallback.result.stdout}\nstderr:\n${serialFallback.result.stderr}`);
111
+ assert(serialFallback.maxActive === 1, `serial fallback should only run one probe at a time; max active ${serialFallback.maxActive}`);
112
+ assert(serialFallback.result.stdout.includes('ext-8: checking npm'), `serial fallback should still check every staged extension\n${serialFallback.result.stdout}`);
113
+ assert(!serialFallback.result.stdout.includes('Update checks skipped:'), `serial fallback should not skip checks in this fixture\n${serialFallback.result.stdout}`);
114
+ } finally {
115
+ rmSync(tempRoot, { recursive: true, force: true });
116
+ }
117
+
118
+ console.log('ldm status concurrency regression passed');
@@ -1,8 +1,9 @@
1
1
  #!/usr/bin/env node
2
- import { chmodSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
2
+ import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
3
+ import { createServer } from 'node:http';
3
4
  import { tmpdir } from 'node:os';
4
5
  import { dirname, join } from 'node:path';
5
- import { spawnSync } from 'node:child_process';
6
+ import { spawn } from 'node:child_process';
6
7
  import { fileURLToPath } from 'node:url';
7
8
 
8
9
  const root = dirname(dirname(fileURLToPath(import.meta.url)));
@@ -13,9 +14,38 @@ function assert(condition, message) {
13
14
  if (!condition) throw new Error(message);
14
15
  }
15
16
 
17
+ function runStatus({ home, registryUrl }) {
18
+ return new Promise((resolve) => {
19
+ const child = spawn(process.execPath, [join(root, 'bin', 'ldm.js'), 'status'], {
20
+ cwd: root,
21
+ env: {
22
+ ...process.env,
23
+ HOME: home,
24
+ LDM_STATUS_NPM_REGISTRY_URL: registryUrl,
25
+ LDM_STATUS_NPM_TIMEOUT_MS: '75',
26
+ LDM_STATUS_TOTAL_BUDGET_MS: '250',
27
+ },
28
+ stdio: ['ignore', 'pipe', 'pipe'],
29
+ });
30
+
31
+ let stdout = '';
32
+ let stderr = '';
33
+ child.stdout.setEncoding('utf8');
34
+ child.stderr.setEncoding('utf8');
35
+ child.stdout.on('data', chunk => { stdout += chunk; });
36
+ child.stderr.on('data', chunk => { stderr += chunk; });
37
+ child.on('close', status => resolve({ status, stdout, stderr }));
38
+ });
39
+ }
40
+
41
+ function listen(server) {
42
+ return new Promise((resolve) => {
43
+ server.listen(0, '127.0.0.1', () => resolve(server.address()));
44
+ });
45
+ }
46
+
16
47
  try {
17
48
  const home = join(tempRoot, 'home');
18
- const fakeBin = join(tempRoot, 'bin');
19
49
  const extensions = join(home, '.ldm', 'extensions');
20
50
 
21
51
  mkdirSync(extensions, { recursive: true });
@@ -37,33 +67,19 @@ try {
37
67
  },
38
68
  }, null, 2) + '\n');
39
69
 
40
- mkdirSync(fakeBin, { recursive: true });
41
- const fakeNpm = join(fakeBin, 'npm');
42
- writeFileSync(fakeNpm, `#!/usr/bin/env bash
43
- if [ "$1" = "view" ]; then
44
- sleep 2
45
- echo "9.9.9"
46
- exit 0
47
- fi
48
- echo "unexpected npm command: $*" >&2
49
- exit 64
50
- `);
51
- chmodSync(fakeNpm, 0o755);
70
+ const server = createServer((_req, res) => {
71
+ setTimeout(() => {
72
+ res.setHeader('content-type', 'application/json');
73
+ res.end(JSON.stringify({ 'dist-tags': { latest: '9.9.9' } }));
74
+ }, 2000);
75
+ });
76
+ const address = await listen(server);
52
77
 
53
78
  const startedAt = Date.now();
54
- const result = spawnSync(process.execPath, [join(root, 'bin', 'ldm.js'), 'status'], {
55
- cwd: root,
56
- encoding: 'utf8',
57
- timeout: 3000,
58
- env: {
59
- ...process.env,
60
- HOME: home,
61
- PATH: `${fakeBin}:${process.env.PATH || ''}`,
62
- LDM_STATUS_NPM_TIMEOUT_MS: '75',
63
- LDM_STATUS_TOTAL_BUDGET_MS: '250',
64
- },
65
- });
79
+ const result = await runStatus({ home, registryUrl: `http://${address.address}:${address.port}` });
66
80
  const elapsedMs = Date.now() - startedAt;
81
+ server.closeAllConnections();
82
+ server.close();
67
83
 
68
84
  assert(result.status === 0, `ldm status exited ${result.status}\nstdout:\n${result.stdout}\nstderr:\n${result.stderr}`);
69
85
  assert(elapsedMs < 2500, `ldm status should return before the process timeout; elapsed ${elapsedMs}ms`);
@@ -72,7 +88,7 @@ exit 64
72
88
  assert(result.stdout.includes('Checking updates:'), `status should show progress before update checks\n${result.stdout}`);
73
89
  assert(result.stdout.includes('hung-extension: checking npm'), `status should print the extension name before probing it\n${result.stdout}`);
74
90
  assert(result.stdout.includes('Update checks skipped:'), `status should report skipped checks instead of hanging\n${result.stdout}`);
75
- assert(result.stdout.includes('hung-extension: [timeout] hung-extension'), `hung extension should be reported as a timeout\n${result.stdout}`);
91
+ assert(/hung-extension: \[timeout \d+(ms|\.\d+s)\] hung-extension/.test(result.stdout), `hung extension should be reported as a timeout with elapsed time\n${result.stdout}`);
76
92
  } finally {
77
93
  rmSync(tempRoot, { recursive: true, force: true });
78
94
  }