@wipcomputer/wip-ldm-os 0.4.85-alpha.25 → 0.4.85-alpha.26
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 {
|
|
25
|
+
import { execFile, execSync, spawnSync } from 'node:child_process';
|
|
26
26
|
import { fileURLToPath } from 'node:url';
|
|
27
27
|
|
|
28
28
|
const __filename = fileURLToPath(import.meta.url);
|
|
@@ -3284,19 +3284,34 @@ 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);
|
|
3287
3288
|
|
|
3288
3289
|
function npmViewVersionForStatus(pkg, timeoutMs) {
|
|
3289
|
-
return
|
|
3290
|
-
|
|
3291
|
-
|
|
3292
|
-
|
|
3293
|
-
|
|
3290
|
+
return new Promise((resolvePromise, rejectPromise) => {
|
|
3291
|
+
execFile('npm', ['view', pkg, 'version'], {
|
|
3292
|
+
encoding: 'utf8',
|
|
3293
|
+
timeout: timeoutMs,
|
|
3294
|
+
maxBuffer: 1024 * 1024,
|
|
3295
|
+
}, (error, stdout) => {
|
|
3296
|
+
if (error) {
|
|
3297
|
+
rejectPromise(error);
|
|
3298
|
+
return;
|
|
3299
|
+
}
|
|
3300
|
+
resolvePromise(String(stdout || '').trim());
|
|
3301
|
+
});
|
|
3302
|
+
});
|
|
3294
3303
|
}
|
|
3295
3304
|
|
|
3296
3305
|
function remainingStatusBudgetMs(startedAt) {
|
|
3297
3306
|
return Math.max(0, STATUS_TOTAL_BUDGET_MS - (Date.now() - startedAt));
|
|
3298
3307
|
}
|
|
3299
3308
|
|
|
3309
|
+
function formatStatusElapsed(ms) {
|
|
3310
|
+
if (!Number.isFinite(ms) || ms <= 0) return '0ms';
|
|
3311
|
+
if (ms < 1000) return `${Math.round(ms)}ms`;
|
|
3312
|
+
return `${(ms / 1000).toFixed(1)}s`;
|
|
3313
|
+
}
|
|
3314
|
+
|
|
3300
3315
|
function classifyStatusCheckError(error) {
|
|
3301
3316
|
if (error?.signal === 'SIGTERM' || error?.code === 'ETIMEDOUT' || String(error?.message || '').includes('ETIMEDOUT')) {
|
|
3302
3317
|
return 'timeout';
|
|
@@ -3304,7 +3319,48 @@ function classifyStatusCheckError(error) {
|
|
|
3304
3319
|
return 'unavailable';
|
|
3305
3320
|
}
|
|
3306
3321
|
|
|
3307
|
-
function
|
|
3322
|
+
async function runStatusProbesWithConcurrency(items, concurrency, statusStartedAt) {
|
|
3323
|
+
if (items.length === 0) return [];
|
|
3324
|
+
|
|
3325
|
+
const results = new Array(items.length);
|
|
3326
|
+
const workerCount = Math.max(1, Math.min(concurrency, items.length));
|
|
3327
|
+
let nextIndex = 0;
|
|
3328
|
+
|
|
3329
|
+
async function worker() {
|
|
3330
|
+
while (nextIndex < items.length) {
|
|
3331
|
+
const index = nextIndex;
|
|
3332
|
+
nextIndex += 1;
|
|
3333
|
+
const item = items[index];
|
|
3334
|
+
const remaining = remainingStatusBudgetMs(statusStartedAt);
|
|
3335
|
+
|
|
3336
|
+
if (remaining <= 0) {
|
|
3337
|
+
results[index] = { ...item, status: 'skipped', reason: 'budget', elapsedMs: 0 };
|
|
3338
|
+
continue;
|
|
3339
|
+
}
|
|
3340
|
+
|
|
3341
|
+
const timeout = Math.min(STATUS_NPM_TIMEOUT_MS, remaining);
|
|
3342
|
+
const probeStartedAt = Date.now();
|
|
3343
|
+
console.log(` ${item.name}: checking npm`);
|
|
3344
|
+
|
|
3345
|
+
try {
|
|
3346
|
+
const latest = await npmViewVersionForStatus(item.npm, timeout);
|
|
3347
|
+
results[index] = { ...item, status: 'ok', latest, elapsedMs: Date.now() - probeStartedAt };
|
|
3348
|
+
} catch (error) {
|
|
3349
|
+
results[index] = {
|
|
3350
|
+
...item,
|
|
3351
|
+
status: 'skipped',
|
|
3352
|
+
reason: classifyStatusCheckError(error),
|
|
3353
|
+
elapsedMs: Date.now() - probeStartedAt,
|
|
3354
|
+
};
|
|
3355
|
+
}
|
|
3356
|
+
}
|
|
3357
|
+
}
|
|
3358
|
+
|
|
3359
|
+
await Promise.all(Array.from({ length: workerCount }, () => worker()));
|
|
3360
|
+
return results;
|
|
3361
|
+
}
|
|
3362
|
+
|
|
3363
|
+
async function cmdStatus() {
|
|
3308
3364
|
const version = readJSON(VERSION_PATH);
|
|
3309
3365
|
const registry = readJSON(REGISTRY_PATH);
|
|
3310
3366
|
const extCount = Object.keys(registry?.extensions || {}).length;
|
|
@@ -3338,18 +3394,14 @@ function cmdStatus() {
|
|
|
3338
3394
|
console.log('');
|
|
3339
3395
|
console.log(' Checking updates:');
|
|
3340
3396
|
|
|
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
3397
|
// Check extensions against npm using registry source info (#262)
|
|
3398
|
+
const probeItems = [{
|
|
3399
|
+
kind: 'cli',
|
|
3400
|
+
name: 'ldm cli',
|
|
3401
|
+
npm: '@wipcomputer/wip-ldm-os',
|
|
3402
|
+
current: PKG_VERSION,
|
|
3403
|
+
}];
|
|
3404
|
+
|
|
3353
3405
|
const updates = [];
|
|
3354
3406
|
for (const [name, info] of Object.entries(registry?.extensions || {})) {
|
|
3355
3407
|
// Use registry source.npm (v2) or fall back to extension's package.json
|
|
@@ -3362,19 +3414,37 @@ function cmdStatus() {
|
|
|
3362
3414
|
if (!npmPkg) continue;
|
|
3363
3415
|
const currentVersion = info?.installed?.version || info.version;
|
|
3364
3416
|
if (!currentVersion) continue;
|
|
3365
|
-
|
|
3366
|
-
|
|
3367
|
-
|
|
3368
|
-
|
|
3369
|
-
|
|
3370
|
-
|
|
3371
|
-
|
|
3372
|
-
|
|
3373
|
-
|
|
3374
|
-
|
|
3375
|
-
|
|
3376
|
-
|
|
3377
|
-
|
|
3417
|
+
probeItems.push({
|
|
3418
|
+
kind: 'extension',
|
|
3419
|
+
name,
|
|
3420
|
+
npm: npmPkg,
|
|
3421
|
+
current: currentVersion,
|
|
3422
|
+
});
|
|
3423
|
+
}
|
|
3424
|
+
|
|
3425
|
+
let cliUpdate = null;
|
|
3426
|
+
const probeResults = await runStatusProbesWithConcurrency(probeItems, STATUS_NPM_CONCURRENCY, statusStartedAt);
|
|
3427
|
+
for (const result of probeResults) {
|
|
3428
|
+
if (!result) continue;
|
|
3429
|
+
if (result.status === 'skipped') {
|
|
3430
|
+
skipped.push({
|
|
3431
|
+
name: result.name,
|
|
3432
|
+
npm: result.npm,
|
|
3433
|
+
reason: result.reason,
|
|
3434
|
+
elapsedMs: result.elapsedMs,
|
|
3435
|
+
});
|
|
3436
|
+
continue;
|
|
3437
|
+
}
|
|
3438
|
+
|
|
3439
|
+
if (result.kind === 'cli') {
|
|
3440
|
+
if (result.latest && semverNewer(result.latest, PKG_VERSION)) cliUpdate = result.latest;
|
|
3441
|
+
} else if (result.latest && semverNewer(result.latest, result.current)) {
|
|
3442
|
+
updates.push({
|
|
3443
|
+
name: result.name,
|
|
3444
|
+
current: result.current,
|
|
3445
|
+
latest: result.latest,
|
|
3446
|
+
npm: result.npm,
|
|
3447
|
+
});
|
|
3378
3448
|
}
|
|
3379
3449
|
}
|
|
3380
3450
|
|
|
@@ -3407,7 +3477,7 @@ function cmdStatus() {
|
|
|
3407
3477
|
console.log('');
|
|
3408
3478
|
console.log(' Update checks skipped:');
|
|
3409
3479
|
for (const item of skipped) {
|
|
3410
|
-
console.log(` ${item.name}: [${item.reason}] ${item.npm}`);
|
|
3480
|
+
console.log(` ${item.name}: [${item.reason} ${formatStatusElapsed(item.elapsedMs)}] ${item.npm}`);
|
|
3411
3481
|
}
|
|
3412
3482
|
}
|
|
3413
3483
|
|
|
@@ -4866,7 +4936,7 @@ async function main() {
|
|
|
4866
4936
|
await cmdDoctor();
|
|
4867
4937
|
break;
|
|
4868
4938
|
case 'status':
|
|
4869
|
-
cmdStatus();
|
|
4939
|
+
await cmdStatus();
|
|
4870
4940
|
break;
|
|
4871
4941
|
case 'sessions':
|
|
4872
4942
|
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.
|
|
3
|
+
"version": "0.4.85-alpha.26",
|
|
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,88 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { chmodSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { tmpdir } from 'node:os';
|
|
4
|
+
import { dirname, join } from 'node:path';
|
|
5
|
+
import { spawnSync } from 'node:child_process';
|
|
6
|
+
import { fileURLToPath } from 'node:url';
|
|
7
|
+
|
|
8
|
+
const root = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
9
|
+
const tempRoot = mkdtempSync(join(tmpdir(), 'ldm-status-concurrency-'));
|
|
10
|
+
const sourceVersion = JSON.parse(readFileSync(join(root, 'package.json'), 'utf8')).version;
|
|
11
|
+
|
|
12
|
+
function assert(condition, message) {
|
|
13
|
+
if (!condition) throw new Error(message);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function writeFixture(home, fakeBin) {
|
|
17
|
+
const extensions = join(home, '.ldm', 'extensions');
|
|
18
|
+
mkdirSync(extensions, { recursive: true });
|
|
19
|
+
writeFileSync(join(home, '.ldm', 'version.json'), JSON.stringify({
|
|
20
|
+
version: '0.0.0-test',
|
|
21
|
+
installed: '2026-05-12T00:00:00.000Z',
|
|
22
|
+
updated: '2026-05-12T00:00:00.000Z',
|
|
23
|
+
}, null, 2) + '\n');
|
|
24
|
+
|
|
25
|
+
const registry = { extensions: {} };
|
|
26
|
+
for (let i = 1; i <= 8; i += 1) {
|
|
27
|
+
registry.extensions[`ext-${i}`] = {
|
|
28
|
+
source: { npm: `ext-${i}` },
|
|
29
|
+
installed: { version: '1.0.0' },
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
writeFileSync(join(extensions, 'registry.json'), JSON.stringify(registry, null, 2) + '\n');
|
|
33
|
+
|
|
34
|
+
mkdirSync(fakeBin, { recursive: true });
|
|
35
|
+
const fakeNpm = join(fakeBin, 'npm');
|
|
36
|
+
writeFileSync(fakeNpm, `#!/usr/bin/env bash
|
|
37
|
+
if [ "$1" = "view" ]; then
|
|
38
|
+
sleep "\${FAKE_NPM_SLEEP:-1}"
|
|
39
|
+
echo "1.0.0"
|
|
40
|
+
exit 0
|
|
41
|
+
fi
|
|
42
|
+
echo "unexpected npm command: $*" >&2
|
|
43
|
+
exit 64
|
|
44
|
+
`);
|
|
45
|
+
chmodSync(fakeNpm, 0o755);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function runStatus({ concurrency, sleepSeconds, timeoutMs }) {
|
|
49
|
+
const home = join(tempRoot, `home-${concurrency}-${sleepSeconds}`);
|
|
50
|
+
const fakeBin = join(tempRoot, `bin-${concurrency}-${sleepSeconds}`);
|
|
51
|
+
writeFixture(home, fakeBin);
|
|
52
|
+
|
|
53
|
+
const startedAt = Date.now();
|
|
54
|
+
const result = spawnSync(process.execPath, [join(root, 'bin', 'ldm.js'), 'status'], {
|
|
55
|
+
cwd: root,
|
|
56
|
+
encoding: 'utf8',
|
|
57
|
+
timeout: timeoutMs,
|
|
58
|
+
env: {
|
|
59
|
+
...process.env,
|
|
60
|
+
HOME: home,
|
|
61
|
+
PATH: `${fakeBin}:${process.env.PATH || ''}`,
|
|
62
|
+
FAKE_NPM_SLEEP: String(sleepSeconds),
|
|
63
|
+
LDM_STATUS_NPM_CONCURRENCY: String(concurrency),
|
|
64
|
+
LDM_STATUS_NPM_TIMEOUT_MS: '2000',
|
|
65
|
+
LDM_STATUS_TOTAL_BUDGET_MS: '10000',
|
|
66
|
+
},
|
|
67
|
+
});
|
|
68
|
+
return { result, elapsedMs: Date.now() - startedAt };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
const concurrent = runStatus({ concurrency: 4, sleepSeconds: 1, timeoutMs: 7000 });
|
|
73
|
+
assert(concurrent.result.status === 0, `concurrent ldm status exited ${concurrent.result.status}\nstdout:\n${concurrent.result.stdout}\nstderr:\n${concurrent.result.stderr}`);
|
|
74
|
+
assert(concurrent.elapsedMs < 6500, `concurrent ldm status should finish well before serial runtime; elapsed ${concurrent.elapsedMs}ms`);
|
|
75
|
+
assert(concurrent.result.stdout.includes(`LDM OS v${sourceVersion}`), `status should print installed LDM OS version\n${concurrent.result.stdout}`);
|
|
76
|
+
assert(concurrent.result.stdout.includes('Extensions: 8'), `status should print extension count\n${concurrent.result.stdout}`);
|
|
77
|
+
assert(concurrent.result.stdout.includes('ext-8: checking npm'), `status should check every staged extension\n${concurrent.result.stdout}`);
|
|
78
|
+
assert(!concurrent.result.stdout.includes('Update checks skipped:'), `concurrent status should not skip checks in this fixture\n${concurrent.result.stdout}`);
|
|
79
|
+
|
|
80
|
+
const serialFallback = runStatus({ concurrency: 1, sleepSeconds: 0.05, timeoutMs: 5000 });
|
|
81
|
+
assert(serialFallback.result.status === 0, `serial fallback ldm status exited ${serialFallback.result.status}\nstdout:\n${serialFallback.result.stdout}\nstderr:\n${serialFallback.result.stderr}`);
|
|
82
|
+
assert(serialFallback.result.stdout.includes('ext-8: checking npm'), `serial fallback should still check every staged extension\n${serialFallback.result.stdout}`);
|
|
83
|
+
assert(!serialFallback.result.stdout.includes('Update checks skipped:'), `serial fallback should not skip checks in this fixture\n${serialFallback.result.stdout}`);
|
|
84
|
+
} finally {
|
|
85
|
+
rmSync(tempRoot, { recursive: true, force: true });
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
console.log('ldm status concurrency regression passed');
|
|
@@ -72,7 +72,7 @@ exit 64
|
|
|
72
72
|
assert(result.stdout.includes('Checking updates:'), `status should show progress before update checks\n${result.stdout}`);
|
|
73
73
|
assert(result.stdout.includes('hung-extension: checking npm'), `status should print the extension name before probing it\n${result.stdout}`);
|
|
74
74
|
assert(result.stdout.includes('Update checks skipped:'), `status should report skipped checks instead of hanging\n${result.stdout}`);
|
|
75
|
-
assert(
|
|
75
|
+
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
76
|
} finally {
|
|
77
77
|
rmSync(tempRoot, { recursive: true, force: true });
|
|
78
78
|
}
|