@commonlyai/cli 0.1.77 → 0.1.78
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/package.json +1 -1
- package/src/lib/mcp-home.js +39 -8
package/package.json
CHANGED
package/src/lib/mcp-home.js
CHANGED
|
@@ -173,8 +173,10 @@ export const liveInUsePids = (home, version, {
|
|
|
173
173
|
*
|
|
174
174
|
* Called by `prepareMcpSpawn`, keyed by THIS process: the MCP server runs as a
|
|
175
175
|
* descendant of the process that materialised the command, so while that process
|
|
176
|
-
* lives its version dir must not be pruned.
|
|
177
|
-
*
|
|
176
|
+
* lives its version dir must not be pruned. A claim that cannot be written is
|
|
177
|
+
* reported, never swallowed: `prepareMcpSpawn` refuses to exec from a dir it
|
|
178
|
+
* could not claim (Vera 74903), because an unclaimed dir is one the pruner is
|
|
179
|
+
* free to `rm -rf` while this seat is running out of it.
|
|
178
180
|
*/
|
|
179
181
|
export const claimInUse = ({
|
|
180
182
|
home,
|
|
@@ -184,15 +186,30 @@ export const claimInUse = ({
|
|
|
184
186
|
writeFile = writeFileSync,
|
|
185
187
|
} = {}) => {
|
|
186
188
|
try {
|
|
187
|
-
if (!isUsableWarmHome(home) || !parseVersion(version) || parsePid(pid) === null)
|
|
189
|
+
if (!isUsableWarmHome(home) || !parseVersion(version) || parsePid(pid) === null) {
|
|
190
|
+
return { claimed: false, reason: 'not_claimable' };
|
|
191
|
+
}
|
|
188
192
|
mkdir(inUseDirFor(home, version), { recursive: true });
|
|
189
193
|
writeFile(inUseMarkerPath(home, version, pid), '');
|
|
190
|
-
return true;
|
|
191
|
-
} catch {
|
|
192
|
-
|
|
194
|
+
return { claimed: true, reason: null };
|
|
195
|
+
} catch (error) {
|
|
196
|
+
// The errno is the difference between a home nobody can write to and a
|
|
197
|
+
// `.inuse` path that is a file, and only one of those is worth a log line
|
|
198
|
+
// that says which.
|
|
199
|
+
return { claimed: false, reason: (error && error.code) || 'claim_failed' };
|
|
193
200
|
}
|
|
194
201
|
};
|
|
195
202
|
|
|
203
|
+
/**
|
|
204
|
+
* Say it out loud. A seat whose claim failed is still a seat, but it must not be
|
|
205
|
+
* a silent one: warn() is injectable so a test can hold the sentence.
|
|
206
|
+
*/
|
|
207
|
+
const defaultWarn = (line) => {
|
|
208
|
+
try {
|
|
209
|
+
process.stderr.write(`${line}\n`);
|
|
210
|
+
} catch { /* a seat that cannot warn still runs */ }
|
|
211
|
+
};
|
|
212
|
+
|
|
196
213
|
const defaultRead = (path) => {
|
|
197
214
|
try {
|
|
198
215
|
return existsSync(path) ? readFileSync(path, 'utf8') : null;
|
|
@@ -435,17 +452,31 @@ export const prepareMcpSpawn = (command, {
|
|
|
435
452
|
now = Date.now(),
|
|
436
453
|
readFile = defaultRead,
|
|
437
454
|
exists = defaultExists,
|
|
455
|
+
warn = defaultWarn,
|
|
438
456
|
} = {}) => {
|
|
439
457
|
const resolvedHome = resolveHomeDir(home);
|
|
440
458
|
try {
|
|
441
459
|
const plan = planMcpSpawn(command, { home: resolvedHome, readFile, exists });
|
|
442
460
|
// Claim the version this spawn is about to execute from, BEFORE returning the
|
|
443
461
|
// command, so a prune that runs while the seat is starting cannot take it.
|
|
444
|
-
|
|
462
|
+
let resolved = plan.command;
|
|
463
|
+
if (plan.source === 'home') {
|
|
464
|
+
const claim = claimInUse({ home: resolvedHome, version: plan.version });
|
|
465
|
+
// A claim that could not be written means the pruner may take this very
|
|
466
|
+
// directory out from under the running seat — the hazard the claim exists
|
|
467
|
+
// for. The declared command is what the guard approved and what no pruner
|
|
468
|
+
// owns, so the spawn falls back to it rather than executing unprotected,
|
|
469
|
+
// and says why. The warm still runs: with the seat off the dir, a prune or
|
|
470
|
+
// a repair is safe.
|
|
471
|
+
if (!claim.claimed) {
|
|
472
|
+
warn(`commonly: cannot claim ${versionDirFor(resolvedHome, plan.version)} for pid ${process.pid} (${claim.reason}) — running the declared command instead, because a prune could delete the directory this seat would run from`);
|
|
473
|
+
resolved = command;
|
|
474
|
+
}
|
|
475
|
+
}
|
|
445
476
|
const due = !lockPresent(resolvedHome, { exists })
|
|
446
477
|
&& warmLooksWanted(resolvedHome, plan.version, { now, readFile });
|
|
447
478
|
if (due) kickWarm({ home: resolvedHome, apiUrl, spawnImpl, childPath });
|
|
448
|
-
return
|
|
479
|
+
return resolved;
|
|
449
480
|
} catch {
|
|
450
481
|
// A rewrite that throws must not cost the seat its tools: the declaration
|
|
451
482
|
// is what the guard approved, and it still runs.
|