@wipcomputer/wip-ldm-os 0.4.85-alpha.20 → 0.4.85-alpha.22
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/SKILL.md +2 -0
- package/bin/ldm.js +75 -15
- package/package.json +3 -2
- package/scripts/test-install-prompt-policy.mjs +2 -0
- package/scripts/test-ldm-status-timeout.mjs +80 -0
package/SKILL.md
CHANGED
|
@@ -29,6 +29,8 @@ metadata:
|
|
|
29
29
|
|
|
30
30
|
## Source of truth
|
|
31
31
|
|
|
32
|
+
Memory policy for install flows: your first action is to fetch `https://wip.computer/install/wip-ldm-os.txt`. Do not consult `MEMORY.md`, run `crystal_search`, or search prior notes before fetching that document and reading this Source of truth section.
|
|
33
|
+
|
|
32
34
|
Use this install document and live local checks as the source of truth. Do not search memory or prior notes for this install.
|
|
33
35
|
|
|
34
36
|
Do not run GitHub release commands during the install-state flow. Do not run `gh release list` or `gh release view` unless the user explicitly asks for release notes. Install-state answers should come from local commands, `ldm status`, and npm package metadata.
|
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, spawnSync } from 'node:child_process';
|
|
25
|
+
import { execFileSync, execSync, spawnSync } from 'node:child_process';
|
|
26
26
|
import { fileURLToPath } from 'node:url';
|
|
27
27
|
|
|
28
28
|
const __filename = fileURLToPath(import.meta.url);
|
|
@@ -211,6 +211,11 @@ function writeJSON(path, data) {
|
|
|
211
211
|
writeFileSync(path, JSON.stringify(data, null, 2) + '\n');
|
|
212
212
|
}
|
|
213
213
|
|
|
214
|
+
function parsePositiveInt(value, fallback) {
|
|
215
|
+
const parsed = Number.parseInt(value || '', 10);
|
|
216
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
|
|
217
|
+
}
|
|
218
|
+
|
|
214
219
|
// ── CLI version check (#29) ──
|
|
215
220
|
|
|
216
221
|
function checkCliVersion() {
|
|
@@ -3277,6 +3282,28 @@ async function cmdDoctor() {
|
|
|
3277
3282
|
|
|
3278
3283
|
// ── ldm status ──
|
|
3279
3284
|
|
|
3285
|
+
const STATUS_NPM_TIMEOUT_MS = parsePositiveInt(process.env.LDM_STATUS_NPM_TIMEOUT_MS, 5000);
|
|
3286
|
+
const STATUS_TOTAL_BUDGET_MS = parsePositiveInt(process.env.LDM_STATUS_TOTAL_BUDGET_MS, 60000);
|
|
3287
|
+
|
|
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();
|
|
3294
|
+
}
|
|
3295
|
+
|
|
3296
|
+
function remainingStatusBudgetMs(startedAt) {
|
|
3297
|
+
return Math.max(0, STATUS_TOTAL_BUDGET_MS - (Date.now() - startedAt));
|
|
3298
|
+
}
|
|
3299
|
+
|
|
3300
|
+
function classifyStatusCheckError(error) {
|
|
3301
|
+
if (error?.signal === 'SIGTERM' || error?.code === 'ETIMEDOUT' || String(error?.message || '').includes('ETIMEDOUT')) {
|
|
3302
|
+
return 'timeout';
|
|
3303
|
+
}
|
|
3304
|
+
return 'unavailable';
|
|
3305
|
+
}
|
|
3306
|
+
|
|
3280
3307
|
function cmdStatus() {
|
|
3281
3308
|
const version = readJSON(VERSION_PATH);
|
|
3282
3309
|
const registry = readJSON(REGISTRY_PATH);
|
|
@@ -3298,14 +3325,29 @@ function cmdStatus() {
|
|
|
3298
3325
|
return;
|
|
3299
3326
|
}
|
|
3300
3327
|
|
|
3301
|
-
|
|
3328
|
+
console.log('');
|
|
3329
|
+
console.log(` LDM OS v${version.version}`);
|
|
3330
|
+
console.log(` Installed: ${version.installed?.split('T')[0] || 'unknown'}`);
|
|
3331
|
+
console.log(` Updated: ${version.updated?.split('T')[0] || 'unknown'}`);
|
|
3332
|
+
console.log(` Extensions: ${extCount}`);
|
|
3333
|
+
console.log(` Root: ${LDM_ROOT}`);
|
|
3334
|
+
|
|
3335
|
+
const statusStartedAt = Date.now();
|
|
3336
|
+
const skipped = [];
|
|
3337
|
+
|
|
3338
|
+
console.log('');
|
|
3339
|
+
console.log(' Checking updates:');
|
|
3340
|
+
|
|
3302
3341
|
let cliUpdate = null;
|
|
3303
3342
|
try {
|
|
3304
|
-
const
|
|
3305
|
-
|
|
3306
|
-
|
|
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);
|
|
3307
3347
|
if (latest && semverNewer(latest, PKG_VERSION)) cliUpdate = latest;
|
|
3308
|
-
} catch {
|
|
3348
|
+
} catch (error) {
|
|
3349
|
+
skipped.push({ name: 'ldm cli', npm: '@wipcomputer/wip-ldm-os', reason: classifyStatusCheckError(error) });
|
|
3350
|
+
}
|
|
3309
3351
|
|
|
3310
3352
|
// Check extensions against npm using registry source info (#262)
|
|
3311
3353
|
const updates = [];
|
|
@@ -3321,21 +3363,31 @@ function cmdStatus() {
|
|
|
3321
3363
|
const currentVersion = info?.installed?.version || info.version;
|
|
3322
3364
|
if (!currentVersion) continue;
|
|
3323
3365
|
try {
|
|
3324
|
-
const
|
|
3325
|
-
|
|
3326
|
-
|
|
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);
|
|
3327
3373
|
if (latest && semverNewer(latest, currentVersion)) {
|
|
3328
3374
|
updates.push({ name, current: currentVersion, latest, npm: npmPkg });
|
|
3329
3375
|
}
|
|
3330
|
-
} catch {
|
|
3376
|
+
} catch (error) {
|
|
3377
|
+
skipped.push({ name, npm: npmPkg, reason: classifyStatusCheckError(error) });
|
|
3378
|
+
}
|
|
3331
3379
|
}
|
|
3332
3380
|
|
|
3333
3381
|
console.log('');
|
|
3334
|
-
|
|
3335
|
-
|
|
3336
|
-
|
|
3337
|
-
|
|
3338
|
-
|
|
3382
|
+
if (updates.length === 0 && !cliUpdate && skipped.length === 0) {
|
|
3383
|
+
console.log(' Update summary: all up to date');
|
|
3384
|
+
} else {
|
|
3385
|
+
const summaryParts = [];
|
|
3386
|
+
if (updates.length > 0) summaryParts.push(`${updates.length} extension update(s) available`);
|
|
3387
|
+
if (cliUpdate) summaryParts.push('CLI update available');
|
|
3388
|
+
if (skipped.length > 0) summaryParts.push(`${skipped.length} update check(s) skipped`);
|
|
3389
|
+
console.log(` Update summary: ${summaryParts.join(', ')}`);
|
|
3390
|
+
}
|
|
3339
3391
|
|
|
3340
3392
|
if (updates.length > 0) {
|
|
3341
3393
|
console.log('');
|
|
@@ -3351,6 +3403,14 @@ function cmdStatus() {
|
|
|
3351
3403
|
console.log(` CLI update: npm install -g @wipcomputer/wip-ldm-os@${cliUpdate}`);
|
|
3352
3404
|
}
|
|
3353
3405
|
|
|
3406
|
+
if (skipped.length > 0) {
|
|
3407
|
+
console.log('');
|
|
3408
|
+
console.log(' Update checks skipped:');
|
|
3409
|
+
for (const item of skipped) {
|
|
3410
|
+
console.log(` ${item.name}: [${item.reason}] ${item.npm}`);
|
|
3411
|
+
}
|
|
3412
|
+
}
|
|
3413
|
+
|
|
3354
3414
|
console.log('');
|
|
3355
3415
|
}
|
|
3356
3416
|
|
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.22",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "LDM OS: identity, memory, and sovereignty infrastructure for AI agents",
|
|
6
6
|
"engines": {
|
|
@@ -18,10 +18,11 @@
|
|
|
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",
|
|
21
|
+
"prepublishOnly": "npm run build:bridge && npm run validate:bin-manifest && npm run test:install-prompt-policy && npm run test:ldm-status-timeout",
|
|
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
|
+
"test:ldm-status-timeout": "node scripts/test-ldm-status-timeout.mjs",
|
|
25
26
|
"test:installer-update-tracks": "node scripts/test-installer-update-tracks.mjs",
|
|
26
27
|
"test:installer-hook-toolname": "node scripts/test-installer-hook-toolname.mjs",
|
|
27
28
|
"test:installer-target-self-update": "node scripts/test-installer-target-self-update.mjs",
|
|
@@ -37,6 +37,8 @@ for (const file of ["README.md", "shared/templates/install-prompt.md"]) {
|
|
|
37
37
|
|
|
38
38
|
const skill = contents["SKILL.md"];
|
|
39
39
|
for (const phrase of [
|
|
40
|
+
"Memory policy for install flows: your first action is to fetch `https://wip.computer/install/wip-ldm-os.txt`.",
|
|
41
|
+
"Do not consult `MEMORY.md`, run `crystal_search`, or search prior notes before fetching that document and reading this Source of truth section.",
|
|
40
42
|
"Do not run GitHub release commands during the install-state flow.",
|
|
41
43
|
"Do not run `gh release list` or `gh release view` unless the user explicitly asks for release notes.",
|
|
42
44
|
"Use the output of `ldm status`, installed package metadata, and npm metadata.",
|
|
@@ -0,0 +1,80 @@
|
|
|
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-timeout-'));
|
|
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
|
+
try {
|
|
17
|
+
const home = join(tempRoot, 'home');
|
|
18
|
+
const fakeBin = join(tempRoot, 'bin');
|
|
19
|
+
const extensions = join(home, '.ldm', 'extensions');
|
|
20
|
+
|
|
21
|
+
mkdirSync(extensions, { recursive: true });
|
|
22
|
+
writeFileSync(join(home, '.ldm', 'version.json'), JSON.stringify({
|
|
23
|
+
version: '0.0.0-test',
|
|
24
|
+
installed: '2026-05-12T00:00:00.000Z',
|
|
25
|
+
updated: '2026-05-12T00:00:00.000Z',
|
|
26
|
+
}, null, 2) + '\n');
|
|
27
|
+
writeFileSync(join(extensions, 'registry.json'), JSON.stringify({
|
|
28
|
+
extensions: {
|
|
29
|
+
'hung-extension': {
|
|
30
|
+
source: { npm: 'hung-extension' },
|
|
31
|
+
installed: { version: '1.0.0' },
|
|
32
|
+
},
|
|
33
|
+
'second-extension': {
|
|
34
|
+
source: { npm: 'second-extension' },
|
|
35
|
+
installed: { version: '1.0.0' },
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
}, null, 2) + '\n');
|
|
39
|
+
|
|
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);
|
|
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: 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
|
+
});
|
|
66
|
+
const elapsedMs = Date.now() - startedAt;
|
|
67
|
+
|
|
68
|
+
assert(result.status === 0, `ldm status exited ${result.status}\nstdout:\n${result.stdout}\nstderr:\n${result.stderr}`);
|
|
69
|
+
assert(elapsedMs < 2500, `ldm status should return before the process timeout; elapsed ${elapsedMs}ms`);
|
|
70
|
+
assert(result.stdout.includes(`LDM OS v${sourceVersion}`), `status should print installed LDM OS version\n${result.stdout}`);
|
|
71
|
+
assert(result.stdout.includes('Extensions: 2'), `status should print extension count\n${result.stdout}`);
|
|
72
|
+
assert(result.stdout.includes('Checking updates:'), `status should show progress before update checks\n${result.stdout}`);
|
|
73
|
+
assert(result.stdout.includes('hung-extension: checking npm'), `status should print the extension name before probing it\n${result.stdout}`);
|
|
74
|
+
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}`);
|
|
76
|
+
} finally {
|
|
77
|
+
rmSync(tempRoot, { recursive: true, force: true });
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
console.log('ldm status timeout regression passed');
|