@muggleai/works 5.27.0 → 5.28.0-staging.250
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/.claude-plugin/marketplace.json +1 -1
- package/dist/{chunk-6BAB7IVG.js → chunk-HBCASFGN.js} +300 -99
- package/dist/{chunk-B5BWBZZ5.js → chunk-KGNRBL52.js} +46 -58
- package/dist/cli.js +2 -2
- package/dist/index.js +2 -2
- package/dist/plugin/.claude-plugin/plugin.json +1 -1
- package/dist/plugin/.codex-mcp.json +1 -1
- package/dist/plugin/.codex-plugin/plugin.json +1 -1
- package/dist/plugin/.cursor-plugin/plugin.json +1 -1
- package/dist/plugin/.mcp.json +1 -1
- package/dist/plugin/skills/_shared/dev-loop/run.md +1 -1
- package/dist/plugin/skills/do/pre-flight.md +1 -1
- package/dist/plugin/skills/muggle-test/execute-local.md +8 -6
- package/dist/plugin/skills/muggle-test-feature-local/SKILL.md +4 -2
- package/dist/plugin/skills/muggle-test-prepare/SKILL.md +7 -4
- package/dist/plugin/skills/muggle-test-prepare/steps/claim-stack.md +10 -4
- package/dist/release-manifest.json +3 -3
- package/dist/src-ZOCXV6PT.js +1 -0
- package/package.json +2 -2
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/.codex-mcp.json +1 -1
- package/plugin/.codex-plugin/plugin.json +1 -1
- package/plugin/.cursor-plugin/plugin.json +1 -1
- package/plugin/.mcp.json +1 -1
- package/plugin/skills/_shared/dev-loop/run.md +1 -1
- package/plugin/skills/do/pre-flight.md +1 -1
- package/plugin/skills/muggle-test/execute-local.md +8 -6
- package/plugin/skills/muggle-test-feature-local/SKILL.md +4 -2
- package/plugin/skills/muggle-test-prepare/SKILL.md +7 -4
- package/plugin/skills/muggle-test-prepare/steps/claim-stack.md +10 -4
- package/dist/src-MCJFH7AB.js +0 -1
|
@@ -85,6 +85,7 @@ __export(src_exports, {
|
|
|
85
85
|
SCREENSHOT_FILE_EXTENSION: () => SCREENSHOT_FILE_EXTENSION,
|
|
86
86
|
StackCheckVerdict: () => StackCheckVerdict,
|
|
87
87
|
StackClaimVerdict: () => StackClaimVerdict,
|
|
88
|
+
StackLeaseEndReason: () => StackLeaseEndReason,
|
|
88
89
|
StackMismatchKind: () => StackMismatchKind,
|
|
89
90
|
StackPortAction: () => StackPortAction,
|
|
90
91
|
WATCHER_LIFETIME_SECONDS: () => WATCHER_LIFETIME_SECONDS,
|
|
@@ -102,6 +103,7 @@ __export(src_exports, {
|
|
|
102
103
|
calculateFileChecksum: () => calculateFileChecksum,
|
|
103
104
|
checkServiceStack: () => checkServiceStack,
|
|
104
105
|
claimServiceStack: () => claimServiceStack,
|
|
106
|
+
compareQueuePlaces: () => compareQueuePlaces,
|
|
105
107
|
completeOnboarding: () => completeOnboarding,
|
|
106
108
|
createApiKeyWithToken: () => createApiKeyWithToken,
|
|
107
109
|
createChildLogger: () => createChildLogger,
|
|
@@ -157,6 +159,7 @@ __export(src_exports, {
|
|
|
157
159
|
loadApiKeyData: () => loadApiKeyData,
|
|
158
160
|
loadCredentials: () => loadCredentials,
|
|
159
161
|
localQa: () => local_exports2,
|
|
162
|
+
lookUpCheckoutPullRequest: () => lookUpCheckoutPullRequest,
|
|
160
163
|
markOnboardingCompleted: () => markOnboardingCompleted,
|
|
161
164
|
maskLlmApiKey: () => maskLlmApiKey,
|
|
162
165
|
mcp: () => mcp_exports,
|
|
@@ -3326,6 +3329,57 @@ function describeStoppedRun(params) {
|
|
|
3326
3329
|
return `Run stopped: it exceeded its ${params.timeoutMs}ms time limit.`;
|
|
3327
3330
|
}
|
|
3328
3331
|
|
|
3332
|
+
// packages/mcps/src/mcp/local/services/execution-timeout-constants.ts
|
|
3333
|
+
var DEFAULT_GENERATION_TIMEOUT_MS = 3e5;
|
|
3334
|
+
var DEFAULT_REPLAY_TIMEOUT_MS = 18e4;
|
|
3335
|
+
|
|
3336
|
+
// packages/mcps/src/shared/queue-priority/queue-priority-constants.ts
|
|
3337
|
+
var PR_LOOKUP_TIMEOUT_MS = 1e4;
|
|
3338
|
+
var PR_LOOKUP_CACHE_MS = 2 * 6e4;
|
|
3339
|
+
var PR_LOOKUP_ENV = "MUGGLE_PR_LOOKUP";
|
|
3340
|
+
|
|
3341
|
+
// packages/mcps/src/shared/queue-priority/pull-request-lookup.ts
|
|
3342
|
+
var lookupsByCwd = /* @__PURE__ */ new Map();
|
|
3343
|
+
var lookUpCheckoutPullRequest = async (cwd) => {
|
|
3344
|
+
if (process.env[PR_LOOKUP_ENV] === "off") return null;
|
|
3345
|
+
const cached = lookupsByCwd.get(cwd);
|
|
3346
|
+
if (cached !== void 0 && Date.now() - cached.lookedUpAt < PR_LOOKUP_CACHE_MS) return cached.pullRequest;
|
|
3347
|
+
const pullRequest = parseOpenPullRequest(await runGhPrView(cwd));
|
|
3348
|
+
lookupsByCwd.set(cwd, { pullRequest, lookedUpAt: Date.now() });
|
|
3349
|
+
return pullRequest;
|
|
3350
|
+
};
|
|
3351
|
+
function runGhPrView(cwd) {
|
|
3352
|
+
return new Promise((resolve7) => {
|
|
3353
|
+
execFile(
|
|
3354
|
+
"gh",
|
|
3355
|
+
["pr", "view", "--json", "number,url,createdAt,state"],
|
|
3356
|
+
{ cwd, timeout: PR_LOOKUP_TIMEOUT_MS, windowsHide: true, encoding: "utf-8" },
|
|
3357
|
+
(error, stdout) => {
|
|
3358
|
+
resolve7(error ? null : stdout);
|
|
3359
|
+
}
|
|
3360
|
+
);
|
|
3361
|
+
});
|
|
3362
|
+
}
|
|
3363
|
+
function parseOpenPullRequest(output) {
|
|
3364
|
+
if (output === null) return null;
|
|
3365
|
+
try {
|
|
3366
|
+
const parsed = JSON.parse(output);
|
|
3367
|
+
const createdAt = typeof parsed.createdAt === "string" ? Date.parse(parsed.createdAt) : Number.NaN;
|
|
3368
|
+
if (parsed.state !== "OPEN" || typeof parsed.number !== "number" || Number.isNaN(createdAt)) return null;
|
|
3369
|
+
return { number: parsed.number, url: typeof parsed.url === "string" ? parsed.url : "", createdAt };
|
|
3370
|
+
} catch {
|
|
3371
|
+
return null;
|
|
3372
|
+
}
|
|
3373
|
+
}
|
|
3374
|
+
|
|
3375
|
+
// packages/mcps/src/shared/queue-priority/queue-priority.ts
|
|
3376
|
+
function compareQueuePlaces(left, right) {
|
|
3377
|
+
const leftOpenedAt = left.pullRequest?.createdAt ?? Number.POSITIVE_INFINITY;
|
|
3378
|
+
const rightOpenedAt = right.pullRequest?.createdAt ?? Number.POSITIVE_INFINITY;
|
|
3379
|
+
if (leftOpenedAt !== rightOpenedAt) return leftOpenedAt < rightOpenedAt ? -1 : 1;
|
|
3380
|
+
return left.enqueuedAt - right.enqueuedAt;
|
|
3381
|
+
}
|
|
3382
|
+
|
|
3329
3383
|
// packages/mcps/src/mcp/local/services/local-execution-lock-constants.ts
|
|
3330
3384
|
var LOCK_FILE_NAME = "local-execution.lock";
|
|
3331
3385
|
var MODIFY_LOCK_SUFFIX = ".modify";
|
|
@@ -3333,6 +3387,8 @@ var POLL_INTERVAL_MS = 2e3;
|
|
|
3333
3387
|
var WAIT_LOG_INTERVAL_MS = 1e4;
|
|
3334
3388
|
var MODIFY_LOCK_RETRY_MS = 50;
|
|
3335
3389
|
var MODIFY_LOCK_STALE_MS = 5e3;
|
|
3390
|
+
var WAITER_ABANDON_MS = 5 * POLL_INTERVAL_MS;
|
|
3391
|
+
var HOLD_OVERRUN_GRACE_MS = STOP_REQUEST_GRACE_MS + 3 * 6e4;
|
|
3336
3392
|
|
|
3337
3393
|
// packages/mcps/src/mcp/local/services/local-execution-lock.ts
|
|
3338
3394
|
function lockFilePath() {
|
|
@@ -3385,8 +3441,8 @@ async function withModifyLock(fn) {
|
|
|
3385
3441
|
} catch (error) {
|
|
3386
3442
|
if (error.code !== "EEXIST") throw error;
|
|
3387
3443
|
try {
|
|
3388
|
-
const
|
|
3389
|
-
if (Date.now() -
|
|
3444
|
+
const stat4 = await fs14.stat(modifyPath);
|
|
3445
|
+
if (Date.now() - stat4.mtimeMs > MODIFY_LOCK_STALE_MS) {
|
|
3390
3446
|
await fs14.unlink(modifyPath).catch(() => void 0);
|
|
3391
3447
|
continue;
|
|
3392
3448
|
}
|
|
@@ -3405,74 +3461,95 @@ async function withModifyLock(fn) {
|
|
|
3405
3461
|
}
|
|
3406
3462
|
async function tryAcquireOnce(params) {
|
|
3407
3463
|
return await withModifyLock(async () => {
|
|
3464
|
+
const now = Date.now();
|
|
3408
3465
|
const existing = await readState();
|
|
3409
|
-
|
|
3410
|
-
|
|
3411
|
-
|
|
3412
|
-
|
|
3413
|
-
|
|
3414
|
-
|
|
3415
|
-
|
|
3416
|
-
|
|
3417
|
-
|
|
3418
|
-
|
|
3419
|
-
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
|
|
3466
|
+
const isCaller = (waiter) => waiter.cwd === params.caller.cwd && waiter.pid === params.caller.pid;
|
|
3467
|
+
const previousPlace = existing?.waiters?.find(isCaller);
|
|
3468
|
+
const caller = {
|
|
3469
|
+
...params.caller,
|
|
3470
|
+
enqueuedAt: Math.min(params.caller.enqueuedAt, previousPlace?.enqueuedAt ?? Number.POSITIVE_INFINITY),
|
|
3471
|
+
lastPolledAt: now
|
|
3472
|
+
};
|
|
3473
|
+
const otherWaiters = (existing?.waiters ?? []).filter(
|
|
3474
|
+
(waiter) => !isCaller(waiter) && pidIsAlive(waiter.pid) && now - waiter.lastPolledAt <= WAITER_ABANDON_MS
|
|
3475
|
+
);
|
|
3476
|
+
const holderPids = existing === null ? [] : findResponsiveHolderPids({ state: existing, now });
|
|
3477
|
+
const isHeldByPeer = holderPids.length > 0 && existing?.holderCwd !== caller.cwd;
|
|
3478
|
+
const isPeerAhead = otherWaiters.some((waiter) => waiter.cwd !== caller.cwd && compareQueuePlaces(waiter, caller) < 0);
|
|
3479
|
+
if (!isHeldByPeer && !isPeerAhead) {
|
|
3480
|
+
const isJoining = existing !== null && holderPids.length > 0;
|
|
3423
3481
|
await writeState({
|
|
3424
|
-
holderCwd:
|
|
3425
|
-
holderPids: [
|
|
3426
|
-
acquiredAt:
|
|
3482
|
+
holderCwd: caller.cwd,
|
|
3483
|
+
holderPids: [...holderPids, caller.pid],
|
|
3484
|
+
acquiredAt: isJoining ? existing.acquiredAt : now,
|
|
3485
|
+
holdDeadlineAt: Math.max(isJoining ? existing.holdDeadlineAt ?? 0 : 0, now + params.holdLimitMs),
|
|
3486
|
+
waiters: otherWaiters
|
|
3427
3487
|
});
|
|
3428
3488
|
return { acquired: true };
|
|
3429
3489
|
}
|
|
3430
|
-
|
|
3431
|
-
|
|
3432
|
-
|
|
3433
|
-
|
|
3434
|
-
acquiredAt: existing.acquiredAt
|
|
3435
|
-
};
|
|
3436
|
-
await writeState(updated);
|
|
3437
|
-
return { acquired: true };
|
|
3438
|
-
}
|
|
3439
|
-
return {
|
|
3440
|
-
acquired: false,
|
|
3441
|
-
blockingState: { ...existing, holderPids: liveHolderPids }
|
|
3442
|
-
};
|
|
3490
|
+
const line = [...otherWaiters, caller].sort(compareQueuePlaces);
|
|
3491
|
+
const blockingState = existing === null || holderPids.length === 0 ? { holderCwd: "", holderPids: [], acquiredAt: 0, waiters: line } : { ...existing, holderPids, waiters: line };
|
|
3492
|
+
await writeState(blockingState);
|
|
3493
|
+
return { acquired: false, blockingState, position: line.findIndex(isCaller) + 1 };
|
|
3443
3494
|
});
|
|
3444
3495
|
}
|
|
3496
|
+
function findResponsiveHolderPids(params) {
|
|
3497
|
+
const liveHolderPids = params.state.holderPids.filter((pid) => pidIsAlive(pid));
|
|
3498
|
+
if (liveHolderPids.length < params.state.holderPids.length) {
|
|
3499
|
+
getLogger().warn("Dropping exited local-execution lock holders", {
|
|
3500
|
+
holderCwd: params.state.holderCwd,
|
|
3501
|
+
exitedPids: params.state.holderPids.filter((pid) => !liveHolderPids.includes(pid))
|
|
3502
|
+
});
|
|
3503
|
+
}
|
|
3504
|
+
const isStuck = params.state.holdDeadlineAt !== void 0 && params.now > params.state.holdDeadlineAt;
|
|
3505
|
+
if (liveHolderPids.length > 0 && isStuck) {
|
|
3506
|
+
getLogger().warn("Breaking stuck local-execution lock \u2014 its holder outran its run timeout", {
|
|
3507
|
+
stuckCwd: params.state.holderCwd,
|
|
3508
|
+
stuckPids: liveHolderPids,
|
|
3509
|
+
overrunSec: Math.round((params.now - params.state.holdDeadlineAt) / 1e3)
|
|
3510
|
+
});
|
|
3511
|
+
return [];
|
|
3512
|
+
}
|
|
3513
|
+
return liveHolderPids;
|
|
3514
|
+
}
|
|
3445
3515
|
async function decrementHolder(params) {
|
|
3446
3516
|
await withModifyLock(async () => {
|
|
3447
3517
|
const existing = await readState();
|
|
3448
3518
|
if (existing === null) return;
|
|
3449
|
-
|
|
3450
|
-
|
|
3519
|
+
const removalIndex = existing.holderPids.indexOf(params.pid);
|
|
3520
|
+
if (existing.holderCwd !== params.normalizedCwd || removalIndex < 0) {
|
|
3521
|
+
getLogger().warn("Release skipped \u2014 no longer a holder; the lock passed on", {
|
|
3451
3522
|
ourCwd: params.normalizedCwd,
|
|
3452
3523
|
holderCwd: existing.holderCwd
|
|
3453
3524
|
});
|
|
3454
3525
|
return;
|
|
3455
3526
|
}
|
|
3456
|
-
const
|
|
3457
|
-
|
|
3458
|
-
|
|
3527
|
+
const remainingPids = [...existing.holderPids.slice(0, removalIndex), ...existing.holderPids.slice(removalIndex + 1)];
|
|
3528
|
+
if (remainingPids.length > 0) {
|
|
3529
|
+
await writeState({ ...existing, holderPids: remainingPids });
|
|
3530
|
+
return;
|
|
3531
|
+
}
|
|
3532
|
+
if ((existing.waiters ?? []).length === 0) {
|
|
3459
3533
|
await deleteStateFile();
|
|
3460
3534
|
return;
|
|
3461
3535
|
}
|
|
3462
|
-
await writeState({
|
|
3463
|
-
holderCwd: existing.holderCwd,
|
|
3464
|
-
holderPids: remainingPids,
|
|
3465
|
-
acquiredAt: existing.acquiredAt
|
|
3466
|
-
});
|
|
3536
|
+
await writeState({ holderCwd: "", holderPids: [], acquiredAt: 0, waiters: existing.waiters });
|
|
3467
3537
|
});
|
|
3468
3538
|
}
|
|
3469
3539
|
async function acquireLocalExecutionLock(params) {
|
|
3470
3540
|
const normalizedCwd = normalizeCwd(params.cwd);
|
|
3471
3541
|
const pid = process.pid;
|
|
3472
3542
|
const startWaitAt = Date.now();
|
|
3543
|
+
const caller = {
|
|
3544
|
+
cwd: normalizedCwd,
|
|
3545
|
+
pid,
|
|
3546
|
+
pullRequest: await (params.lookUpPullRequest ?? lookUpCheckoutPullRequest)(normalizedCwd),
|
|
3547
|
+
enqueuedAt: startWaitAt,
|
|
3548
|
+
lastPolledAt: startWaitAt
|
|
3549
|
+
};
|
|
3473
3550
|
let lastWaitLogAt = 0;
|
|
3474
3551
|
while (true) {
|
|
3475
|
-
const outcome = await tryAcquireOnce({
|
|
3552
|
+
const outcome = await tryAcquireOnce({ caller, holdLimitMs: params.runTimeoutMs + HOLD_OVERRUN_GRACE_MS });
|
|
3476
3553
|
if (outcome.acquired) {
|
|
3477
3554
|
return {
|
|
3478
3555
|
release: () => decrementHolder({ normalizedCwd, pid })
|
|
@@ -3485,6 +3562,8 @@ async function acquireLocalExecutionLock(params) {
|
|
|
3485
3562
|
ourCwd: normalizedCwd,
|
|
3486
3563
|
heldBy: outcome.blockingState?.holderCwd,
|
|
3487
3564
|
heldByPids: outcome.blockingState?.holderPids,
|
|
3565
|
+
position: outcome.position,
|
|
3566
|
+
lineLength: outcome.blockingState?.waiters?.length,
|
|
3488
3567
|
waitedSec
|
|
3489
3568
|
});
|
|
3490
3569
|
lastWaitLogAt = now;
|
|
@@ -3733,10 +3812,10 @@ async function scanForRuntimeDirByMtime(params) {
|
|
|
3733
3812
|
for (const entry of entries) {
|
|
3734
3813
|
if (!entry.isDirectory()) continue;
|
|
3735
3814
|
const fullPath = path16.join(runtimeRoot, entry.name);
|
|
3736
|
-
const
|
|
3737
|
-
if (
|
|
3738
|
-
if (bestCandidate === null ||
|
|
3739
|
-
bestCandidate = { path: fullPath, mtimeMs:
|
|
3815
|
+
const stat4 = await fs14.stat(fullPath);
|
|
3816
|
+
if (stat4.mtimeMs < params.spawnAtMs) continue;
|
|
3817
|
+
if (bestCandidate === null || stat4.mtimeMs > bestCandidate.mtimeMs) {
|
|
3818
|
+
bestCandidate = { path: fullPath, mtimeMs: stat4.mtimeMs };
|
|
3740
3819
|
}
|
|
3741
3820
|
}
|
|
3742
3821
|
return bestCandidate?.path ?? null;
|
|
@@ -3923,7 +4002,10 @@ ${processInfo.capturedStderr}`
|
|
|
3923
4002
|
});
|
|
3924
4003
|
}
|
|
3925
4004
|
async function executeTestGeneration(params) {
|
|
3926
|
-
const lockHandle = await acquireLocalExecutionLock({
|
|
4005
|
+
const lockHandle = await acquireLocalExecutionLock({
|
|
4006
|
+
cwd: params.cwd,
|
|
4007
|
+
runTimeoutMs: params.timeoutMs ?? DEFAULT_GENERATION_TIMEOUT_MS
|
|
4008
|
+
});
|
|
3927
4009
|
try {
|
|
3928
4010
|
return await runTestGenerationLocked(params);
|
|
3929
4011
|
} finally {
|
|
@@ -3932,7 +4014,7 @@ async function executeTestGeneration(params) {
|
|
|
3932
4014
|
}
|
|
3933
4015
|
async function runTestGenerationLocked(params) {
|
|
3934
4016
|
const { testCase, localUrl } = params;
|
|
3935
|
-
const timeoutMs = params.timeoutMs ??
|
|
4017
|
+
const timeoutMs = params.timeoutMs ?? DEFAULT_GENERATION_TIMEOUT_MS;
|
|
3936
4018
|
const userId = getAuthenticatedUserId();
|
|
3937
4019
|
const authContent = buildStudioAuthContent();
|
|
3938
4020
|
if (!testCase.url) {
|
|
@@ -4119,7 +4201,10 @@ async function runTestGenerationLocked(params) {
|
|
|
4119
4201
|
}
|
|
4120
4202
|
}
|
|
4121
4203
|
async function executeReplay(params) {
|
|
4122
|
-
const lockHandle = await acquireLocalExecutionLock({
|
|
4204
|
+
const lockHandle = await acquireLocalExecutionLock({
|
|
4205
|
+
cwd: params.cwd,
|
|
4206
|
+
runTimeoutMs: params.timeoutMs ?? DEFAULT_REPLAY_TIMEOUT_MS
|
|
4207
|
+
});
|
|
4123
4208
|
try {
|
|
4124
4209
|
return await runReplayLocked(params);
|
|
4125
4210
|
} finally {
|
|
@@ -4128,7 +4213,7 @@ async function executeReplay(params) {
|
|
|
4128
4213
|
}
|
|
4129
4214
|
async function runReplayLocked(params) {
|
|
4130
4215
|
const { testScript, actionScript, localUrl } = params;
|
|
4131
|
-
const timeoutMs = params.timeoutMs ??
|
|
4216
|
+
const timeoutMs = params.timeoutMs ?? DEFAULT_REPLAY_TIMEOUT_MS;
|
|
4132
4217
|
const userId = getAuthenticatedUserId();
|
|
4133
4218
|
const authContent = buildStudioAuthContent();
|
|
4134
4219
|
if (!testScript.url) {
|
|
@@ -5188,6 +5273,13 @@ function toolRequiresAuth(toolName) {
|
|
|
5188
5273
|
}
|
|
5189
5274
|
|
|
5190
5275
|
// packages/mcps/src/shared/service-stack/service-stack-types.ts
|
|
5276
|
+
var StackLeaseEndReason = /* @__PURE__ */ ((StackLeaseEndReason2) => {
|
|
5277
|
+
StackLeaseEndReason2["NeverServed"] = "NEVER_SERVED";
|
|
5278
|
+
StackLeaseEndReason2["NoRunAfterServed"] = "NO_RUN_AFTER_SERVED";
|
|
5279
|
+
StackLeaseEndReason2["Idle"] = "IDLE";
|
|
5280
|
+
StackLeaseEndReason2["HolderExited"] = "HOLDER_EXITED";
|
|
5281
|
+
return StackLeaseEndReason2;
|
|
5282
|
+
})(StackLeaseEndReason || {});
|
|
5191
5283
|
var StackClaimVerdict = /* @__PURE__ */ ((StackClaimVerdict2) => {
|
|
5192
5284
|
StackClaimVerdict2["Granted"] = "GRANTED";
|
|
5193
5285
|
StackClaimVerdict2["Busy"] = "BUSY";
|
|
@@ -5204,6 +5296,7 @@ var StackCheckVerdict = /* @__PURE__ */ ((StackCheckVerdict2) => {
|
|
|
5204
5296
|
StackCheckVerdict2["NoLease"] = "NO_LEASE";
|
|
5205
5297
|
StackCheckVerdict2["Stale"] = "STALE";
|
|
5206
5298
|
StackCheckVerdict2["PeerLease"] = "PEER_LEASE";
|
|
5299
|
+
StackCheckVerdict2["Ended"] = "ENDED";
|
|
5207
5300
|
return StackCheckVerdict2;
|
|
5208
5301
|
})(StackCheckVerdict || {});
|
|
5209
5302
|
var StackMismatchKind = /* @__PURE__ */ ((StackMismatchKind2) => {
|
|
@@ -5216,7 +5309,9 @@ var StackMismatchKind = /* @__PURE__ */ ((StackMismatchKind2) => {
|
|
|
5216
5309
|
// packages/mcps/src/shared/service-stack/service-stack-constants.ts
|
|
5217
5310
|
var STACK_LEDGER_DIR_NAME = "service-stack";
|
|
5218
5311
|
var PREPARE_TRACKING_DIR_SEGMENTS = ["prepare", "running"];
|
|
5219
|
-
var
|
|
5312
|
+
var SERVE_WINDOW_MS = 8 * 6e4;
|
|
5313
|
+
var FIRST_RUN_WINDOW_MS = 4 * 6e4;
|
|
5314
|
+
var ENDED_LEASE_MEMORY_MS = 30 * 6e4;
|
|
5220
5315
|
var IDLE_RELEASE_MS = 2 * 6e4;
|
|
5221
5316
|
var MAX_RUN_MS = 2 * 60 * 6e4;
|
|
5222
5317
|
var DEAD_HOLDER_GRACE_MS = 6e4;
|
|
@@ -5230,6 +5325,12 @@ var OWED_RETRY_AFTER_MS = 5e3;
|
|
|
5230
5325
|
var CLAIM_POLL_INTERVAL_MS = 1e3;
|
|
5231
5326
|
var BUSY_EXIT_CODE = 75;
|
|
5232
5327
|
var LISTENER_COMMAND_MAX_CHARS = 120;
|
|
5328
|
+
var LEASE_END_REASON_TEXT = {
|
|
5329
|
+
["NEVER_SERVED" /* NeverServed */]: `its ports did not serve the claimed code within ${SERVE_WINDOW_MS / 6e4} minutes of the grant`,
|
|
5330
|
+
["NO_RUN_AFTER_SERVED" /* NoRunAfterServed */]: `no run started within ${FIRST_RUN_WINDOW_MS / 6e4} minutes of its services coming up`,
|
|
5331
|
+
["IDLE" /* Idle */]: `no run started within ${IDLE_RELEASE_MS / 6e4} minutes of the last one`,
|
|
5332
|
+
["HOLDER_EXITED" /* HolderExited */]: "every MCP server that ran under it exited"
|
|
5333
|
+
};
|
|
5233
5334
|
var DEFAULT_PORT_BY_PROTOCOL = { "http:": 80, "https:": 443 };
|
|
5234
5335
|
|
|
5235
5336
|
// packages/mcps/src/shared/service-stack/generation-store-constants.ts
|
|
@@ -5461,22 +5562,34 @@ function needsConflict(left, right) {
|
|
|
5461
5562
|
}
|
|
5462
5563
|
function sweepStackRecord(params) {
|
|
5463
5564
|
const { record, now } = params;
|
|
5464
|
-
const
|
|
5465
|
-
const
|
|
5466
|
-
|
|
5565
|
+
const servedSince = params.servedSince ?? isServedByNothing;
|
|
5566
|
+
const endings = record.leases.map((lease) => ({
|
|
5567
|
+
lease,
|
|
5568
|
+
reason: findLeaseEndReason({ lease, now, isAlive: params.isAlive, servedSince })
|
|
5569
|
+
}));
|
|
5570
|
+
const liveLeases = endings.filter((ending) => ending.reason === null).map((ending) => ending.lease);
|
|
5571
|
+
const endedNow = endings.flatMap(
|
|
5572
|
+
({ lease, reason }) => reason === null ? [] : [{ cwd: lease.cwd, label: lease.label, endedAt: now, reason }]
|
|
5573
|
+
);
|
|
5467
5574
|
return {
|
|
5468
5575
|
leases: liveLeases,
|
|
5469
|
-
queue: record.queue.filter((ticket) => now - ticket.lastSeenAt <= TICKET_ABANDON_MS),
|
|
5576
|
+
queue: record.queue.filter((ticket) => now - ticket.lastSeenAt <= TICKET_ABANDON_MS).sort(compareQueuePlaces),
|
|
5470
5577
|
portFreedAt: markFreedPorts({
|
|
5471
5578
|
portFreedAt: forgetSettledPorts({ portFreedAt: record.portFreedAt, now }),
|
|
5472
|
-
releasedNeeds:
|
|
5579
|
+
releasedNeeds: endings.filter((ending) => ending.reason !== null).flatMap((ending) => ending.lease.needs),
|
|
5473
5580
|
remainingLeases: liveLeases,
|
|
5474
5581
|
now
|
|
5582
|
+
}),
|
|
5583
|
+
endedLeases: rememberEndedLeases({
|
|
5584
|
+
endedLeases: record.endedLeases ?? [],
|
|
5585
|
+
endedNow,
|
|
5586
|
+
liveLeases,
|
|
5587
|
+
now
|
|
5475
5588
|
})
|
|
5476
5589
|
};
|
|
5477
5590
|
}
|
|
5478
5591
|
function decideStackClaim(params) {
|
|
5479
|
-
const swept = sweepStackRecord(
|
|
5592
|
+
const swept = sweepStackRecord(params);
|
|
5480
5593
|
const peerLeases = swept.leases.filter((lease) => lease.cwd !== params.cwd);
|
|
5481
5594
|
const leaseBlockers = peerLeases.flatMap((lease) => leaseBlockersFor({ lease, needs: params.needs, now: params.now }));
|
|
5482
5595
|
const queueWithCaller = upsertTicket({
|
|
@@ -5484,6 +5597,7 @@ function decideStackClaim(params) {
|
|
|
5484
5597
|
cwd: params.cwd,
|
|
5485
5598
|
label: params.label,
|
|
5486
5599
|
needs: params.needs,
|
|
5600
|
+
pullRequest: params.pullRequest,
|
|
5487
5601
|
now: params.now
|
|
5488
5602
|
});
|
|
5489
5603
|
const position = queueWithCaller.findIndex((ticket) => ticket.cwd === params.cwd) + 1;
|
|
@@ -5512,7 +5626,8 @@ function decideStackClaim(params) {
|
|
|
5512
5626
|
releasedNeeds: ownLease?.needs ?? [],
|
|
5513
5627
|
remainingLeases: leases,
|
|
5514
5628
|
now: params.now
|
|
5515
|
-
})
|
|
5629
|
+
}),
|
|
5630
|
+
endedLeases: (swept.endedLeases ?? []).filter((ended) => ended.cwd !== params.cwd)
|
|
5516
5631
|
},
|
|
5517
5632
|
blockers: [],
|
|
5518
5633
|
sharedWith: peerLeases.filter((lease) => lease.needs.some((need) => params.needs.some((own) => own.port === need.port))).map((lease) => lease.cwd),
|
|
@@ -5523,7 +5638,7 @@ function decideStackClaim(params) {
|
|
|
5523
5638
|
}
|
|
5524
5639
|
function decideStackRelease(params) {
|
|
5525
5640
|
const isReleased = params.record.leases.some((lease) => lease.cwd === params.cwd) || params.record.queue.some((ticket) => ticket.cwd === params.cwd);
|
|
5526
|
-
const swept = sweepStackRecord(
|
|
5641
|
+
const swept = sweepStackRecord(params);
|
|
5527
5642
|
if (!isReleased) return { next: hasSweepDropped({ record: params.record, swept }) ? swept : null, isReleased: false };
|
|
5528
5643
|
const ownLease = swept.leases.find((lease) => lease.cwd === params.cwd);
|
|
5529
5644
|
const leases = swept.leases.filter((lease) => lease.cwd !== params.cwd);
|
|
@@ -5535,7 +5650,8 @@ function decideStackRelease(params) {
|
|
|
5535
5650
|
releasedNeeds: ownLease?.needs ?? [],
|
|
5536
5651
|
remainingLeases: leases,
|
|
5537
5652
|
now: params.now
|
|
5538
|
-
})
|
|
5653
|
+
}),
|
|
5654
|
+
endedLeases: (swept.endedLeases ?? []).filter((ended) => ended.cwd !== params.cwd)
|
|
5539
5655
|
};
|
|
5540
5656
|
return { next, isReleased: true };
|
|
5541
5657
|
}
|
|
@@ -5544,7 +5660,7 @@ function decideStackReap(params) {
|
|
|
5544
5660
|
return hasSweepDropped({ record: params.record, swept }) ? swept : null;
|
|
5545
5661
|
}
|
|
5546
5662
|
function decideStackRunStart(params) {
|
|
5547
|
-
const swept = sweepStackRecord(
|
|
5663
|
+
const swept = sweepStackRecord(params);
|
|
5548
5664
|
if (!swept.leases.some((lease) => lease.cwd === params.cwd)) return decideStackReap(params);
|
|
5549
5665
|
const start = (lease) => {
|
|
5550
5666
|
const liveHolders = (lease.holderPids ?? []).filter((pid) => params.isAlive(pid));
|
|
@@ -5560,7 +5676,7 @@ function decideStackRunStart(params) {
|
|
|
5560
5676
|
return { ...swept, leases: swept.leases.map((lease) => lease.cwd === params.cwd ? start(lease) : lease) };
|
|
5561
5677
|
}
|
|
5562
5678
|
function decideStackRunEnd(params) {
|
|
5563
|
-
const swept = sweepStackRecord(
|
|
5679
|
+
const swept = sweepStackRecord(params);
|
|
5564
5680
|
if (!swept.leases.some((lease) => lease.cwd === params.cwd)) return decideStackReap(params);
|
|
5565
5681
|
const finish = (lease) => {
|
|
5566
5682
|
const activeRuns = liveRuns({ lease, now: params.now, isAlive: params.isAlive }).filter(
|
|
@@ -5585,13 +5701,31 @@ function findVacatedNeeds(params) {
|
|
|
5585
5701
|
}
|
|
5586
5702
|
return vacated;
|
|
5587
5703
|
}
|
|
5588
|
-
function
|
|
5589
|
-
if (liveRuns(params).length > 0) return
|
|
5704
|
+
function findLeaseEndReason(params) {
|
|
5705
|
+
if (liveRuns(params).length > 0) return null;
|
|
5590
5706
|
const holderPids = params.lease.holderPids ?? [];
|
|
5591
5707
|
const isHolderGone = holderPids.length > 0 && !holderPids.some((pid) => params.isAlive(pid));
|
|
5592
|
-
if (isHolderGone && params.now - params.lease.lastSeenAt > DEAD_HOLDER_GRACE_MS) return
|
|
5708
|
+
if (isHolderGone && params.now - params.lease.lastSeenAt > DEAD_HOLDER_GRACE_MS) return "HOLDER_EXITED" /* HolderExited */;
|
|
5593
5709
|
const lastRunEndedAt = params.lease.lastRunEndedAt;
|
|
5594
|
-
|
|
5710
|
+
if (lastRunEndedAt !== void 0) return params.now - lastRunEndedAt > IDLE_RELEASE_MS ? "IDLE" /* Idle */ : null;
|
|
5711
|
+
const servedAt = findLeaseServedAt({ lease: params.lease, servedSince: params.servedSince });
|
|
5712
|
+
if (servedAt === null) return params.now - params.lease.lastSeenAt > SERVE_WINDOW_MS ? "NEVER_SERVED" /* NeverServed */ : null;
|
|
5713
|
+
return params.now - Math.max(servedAt, params.lease.lastSeenAt) > FIRST_RUN_WINDOW_MS ? "NO_RUN_AFTER_SERVED" /* NoRunAfterServed */ : null;
|
|
5714
|
+
}
|
|
5715
|
+
function findLeaseServedAt(params) {
|
|
5716
|
+
const servedTimes = params.lease.needs.map((need) => params.servedSince(need));
|
|
5717
|
+
if (servedTimes.some((servedAt) => servedAt === null)) return null;
|
|
5718
|
+
return Math.max(params.lease.grantedAt, ...servedTimes);
|
|
5719
|
+
}
|
|
5720
|
+
function isServedByNothing() {
|
|
5721
|
+
return null;
|
|
5722
|
+
}
|
|
5723
|
+
function rememberEndedLeases(params) {
|
|
5724
|
+
const isSuperseded = (ended) => params.endedNow.some((latest) => latest.cwd === ended.cwd) || params.liveLeases.some((lease) => lease.cwd === ended.cwd);
|
|
5725
|
+
const remembered = params.endedLeases.filter(
|
|
5726
|
+
(ended) => params.now - ended.endedAt <= ENDED_LEASE_MEMORY_MS && !isSuperseded(ended)
|
|
5727
|
+
);
|
|
5728
|
+
return [...remembered, ...params.endedNow];
|
|
5595
5729
|
}
|
|
5596
5730
|
function isSameNeed(left, right) {
|
|
5597
5731
|
return left.port === right.port && left.runDir === right.runDir && left.headSha === right.headSha;
|
|
@@ -5627,16 +5761,23 @@ function ticketBlockersFor(params) {
|
|
|
5627
5761
|
}));
|
|
5628
5762
|
}
|
|
5629
5763
|
function upsertTicket(params) {
|
|
5630
|
-
const existing = params.queue.find((
|
|
5631
|
-
|
|
5632
|
-
|
|
5633
|
-
|
|
5634
|
-
|
|
5635
|
-
|
|
5636
|
-
|
|
5637
|
-
|
|
5638
|
-
|
|
5639
|
-
|
|
5764
|
+
const existing = params.queue.find((ticket2) => ticket2.cwd === params.cwd);
|
|
5765
|
+
const ticket = existing === void 0 ? {
|
|
5766
|
+
cwd: params.cwd,
|
|
5767
|
+
label: params.label,
|
|
5768
|
+
needs: params.needs,
|
|
5769
|
+
pullRequest: params.pullRequest ?? void 0,
|
|
5770
|
+
enqueuedAt: params.now,
|
|
5771
|
+
lastSeenAt: params.now
|
|
5772
|
+
} : {
|
|
5773
|
+
...existing,
|
|
5774
|
+
label: params.label ?? existing.label,
|
|
5775
|
+
needs: params.needs,
|
|
5776
|
+
pullRequest: params.pullRequest ?? existing.pullRequest,
|
|
5777
|
+
lastSeenAt: params.now
|
|
5778
|
+
};
|
|
5779
|
+
const queue = existing === void 0 ? [...params.queue, ticket] : params.queue.map((queued) => queued.cwd === params.cwd ? ticket : queued);
|
|
5780
|
+
return queue.sort(compareQueuePlaces);
|
|
5640
5781
|
}
|
|
5641
5782
|
function isOwedTurn(params) {
|
|
5642
5783
|
if (params.now - params.ticket.lastSeenAt < CLAIM_GRACE_MS) return true;
|
|
@@ -5692,9 +5833,10 @@ async function claimServiceStack(params) {
|
|
|
5692
5833
|
const runGit = params.runGit ?? runGitCommand;
|
|
5693
5834
|
const cwd = await resolveCheckoutRoot({ cwd: params.cwd, runGit });
|
|
5694
5835
|
const needs = await resolveNeeds({ needs: params.needs, runGit });
|
|
5836
|
+
const pullRequest = await (params.lookUpPullRequest ?? lookUpCheckoutPullRequest)(cwd);
|
|
5695
5837
|
const startedAt = Date.now();
|
|
5696
5838
|
for (; ; ) {
|
|
5697
|
-
const report = await claimOnce({ cwd, label: params.label, needs });
|
|
5839
|
+
const report = await claimOnce({ cwd, label: params.label, needs, pullRequest });
|
|
5698
5840
|
const hasWaitLeft = params.waitMs !== void 0 && Date.now() - startedAt < params.waitMs;
|
|
5699
5841
|
if (report.verdict === "GRANTED" /* Granted */ || !hasWaitLeft) return report;
|
|
5700
5842
|
await sleep3(CLAIM_POLL_INTERVAL_MS);
|
|
@@ -5703,7 +5845,13 @@ async function claimServiceStack(params) {
|
|
|
5703
5845
|
async function checkServiceStack(params) {
|
|
5704
5846
|
const cwd = await resolveCheckoutRoot({ cwd: params.cwd, runGit: params.runGit ?? runGitCommand });
|
|
5705
5847
|
const snapshot = await readGenerationStore({ directory: ledgerDir(), emptyRecord: EMPTY_STACK_RECORD });
|
|
5706
|
-
const
|
|
5848
|
+
const services = await readTrackedServices();
|
|
5849
|
+
const swept = sweepStackRecord({
|
|
5850
|
+
record: snapshot.record,
|
|
5851
|
+
now: Date.now(),
|
|
5852
|
+
isAlive: isProcessAlive,
|
|
5853
|
+
servedSince: servedSinceFrom(services)
|
|
5854
|
+
});
|
|
5707
5855
|
const ownLease = swept.leases.find((lease) => lease.cwd === cwd) ?? null;
|
|
5708
5856
|
const base = { cwd, generation: snapshot.generation, mismatches: [], peerLease: null, servedNeeds: [] };
|
|
5709
5857
|
const isClaimedByCaller = ownLease?.needs.some((need) => need.port === params.port) ?? false;
|
|
@@ -5717,10 +5865,17 @@ async function checkServiceStack(params) {
|
|
|
5717
5865
|
peerLease
|
|
5718
5866
|
};
|
|
5719
5867
|
}
|
|
5868
|
+
const endedLease = ownLease === null ? swept.endedLeases?.find((ended) => ended.cwd === cwd) : void 0;
|
|
5869
|
+
if (endedLease !== void 0) {
|
|
5870
|
+
return {
|
|
5871
|
+
...base,
|
|
5872
|
+
verdict: "ENDED" /* Ended */,
|
|
5873
|
+
reason: `this checkout's lease ended ${Math.round((Date.now() - endedLease.endedAt) / 1e3)}s ago: ${LEASE_END_REASON_TEXT[endedLease.reason]}`
|
|
5874
|
+
};
|
|
5875
|
+
}
|
|
5720
5876
|
if (ownLease === null) {
|
|
5721
5877
|
return { ...base, verdict: "NO_LEASE" /* NoLease */, reason: "this checkout holds no stack lease" };
|
|
5722
5878
|
}
|
|
5723
|
-
const services = await readTrackedServices();
|
|
5724
5879
|
const readListener = params.readListener ?? readPortListener;
|
|
5725
5880
|
const mismatches = (await Promise.all(
|
|
5726
5881
|
ownLease.needs.map((need) => findPortMismatch({ need, services, readListener }))
|
|
@@ -5743,48 +5898,68 @@ async function checkServiceStack(params) {
|
|
|
5743
5898
|
}
|
|
5744
5899
|
async function startStackRun(params) {
|
|
5745
5900
|
const cwd = await resolveCheckoutRoot({ cwd: params.cwd, runGit: params.runGit ?? runGitCommand });
|
|
5746
|
-
await updateLedger((record) => ({
|
|
5901
|
+
await updateLedger((record, servedSince) => ({
|
|
5747
5902
|
next: decideStackRunStart({
|
|
5748
5903
|
record,
|
|
5749
5904
|
cwd,
|
|
5750
5905
|
runId: params.runId,
|
|
5751
5906
|
holderPid: params.holderPid,
|
|
5752
5907
|
now: Date.now(),
|
|
5753
|
-
isAlive: isProcessAlive
|
|
5908
|
+
isAlive: isProcessAlive,
|
|
5909
|
+
servedSince
|
|
5754
5910
|
}),
|
|
5755
5911
|
result: null
|
|
5756
5912
|
}));
|
|
5757
5913
|
}
|
|
5758
5914
|
async function finishStackRun(params) {
|
|
5759
5915
|
const cwd = await resolveCheckoutRoot({ cwd: params.cwd, runGit: params.runGit ?? runGitCommand });
|
|
5760
|
-
await updateLedger((record) => ({
|
|
5761
|
-
next: decideStackRunEnd({
|
|
5916
|
+
await updateLedger((record, servedSince) => ({
|
|
5917
|
+
next: decideStackRunEnd({
|
|
5918
|
+
record,
|
|
5919
|
+
cwd,
|
|
5920
|
+
runId: params.runId,
|
|
5921
|
+
now: Date.now(),
|
|
5922
|
+
isAlive: isProcessAlive,
|
|
5923
|
+
servedSince
|
|
5924
|
+
}),
|
|
5762
5925
|
result: null
|
|
5763
5926
|
}));
|
|
5764
5927
|
}
|
|
5765
5928
|
async function reapServiceStack() {
|
|
5766
|
-
const update = await updateLedger((record) => ({
|
|
5767
|
-
next: decideStackReap({ record, now: Date.now(), isAlive: isProcessAlive }),
|
|
5929
|
+
const update = await updateLedger((record, servedSince) => ({
|
|
5930
|
+
next: decideStackReap({ record, now: Date.now(), isAlive: isProcessAlive, servedSince }),
|
|
5768
5931
|
result: null
|
|
5769
5932
|
}));
|
|
5770
5933
|
return update.stoppedServices;
|
|
5771
5934
|
}
|
|
5772
5935
|
async function releaseServiceStack(params) {
|
|
5773
5936
|
const cwd = await resolveCheckoutRoot({ cwd: params.cwd, runGit: params.runGit ?? runGitCommand });
|
|
5774
|
-
const update = await updateLedger((record) => {
|
|
5775
|
-
const decision = decideStackRelease({
|
|
5937
|
+
const update = await updateLedger((record, servedSince) => {
|
|
5938
|
+
const decision = decideStackRelease({
|
|
5939
|
+
record,
|
|
5940
|
+
cwd,
|
|
5941
|
+
now: Date.now(),
|
|
5942
|
+
isAlive: isProcessAlive,
|
|
5943
|
+
servedSince
|
|
5944
|
+
});
|
|
5776
5945
|
return { next: decision.next, result: decision.isReleased };
|
|
5777
5946
|
});
|
|
5778
5947
|
return { isReleased: update.result, stoppedServices: update.stoppedServices };
|
|
5779
5948
|
}
|
|
5780
5949
|
async function readServiceStackStatus() {
|
|
5781
5950
|
const snapshot = await readGenerationStore({ directory: ledgerDir(), emptyRecord: EMPTY_STACK_RECORD });
|
|
5782
|
-
const
|
|
5951
|
+
const services = await readTrackedServices();
|
|
5952
|
+
const swept = sweepStackRecord({
|
|
5953
|
+
record: snapshot.record,
|
|
5954
|
+
now: Date.now(),
|
|
5955
|
+
isAlive: isProcessAlive,
|
|
5956
|
+
servedSince: servedSinceFrom(services)
|
|
5957
|
+
});
|
|
5783
5958
|
return {
|
|
5784
5959
|
generation: snapshot.generation,
|
|
5785
5960
|
leases: swept.leases,
|
|
5786
5961
|
queue: swept.queue,
|
|
5787
|
-
services
|
|
5962
|
+
services
|
|
5788
5963
|
};
|
|
5789
5964
|
}
|
|
5790
5965
|
function portOfUrl(url) {
|
|
@@ -5797,14 +5972,16 @@ function portOfUrl(url) {
|
|
|
5797
5972
|
}
|
|
5798
5973
|
}
|
|
5799
5974
|
async function claimOnce(params) {
|
|
5800
|
-
const update = await updateLedger((record) => {
|
|
5975
|
+
const update = await updateLedger((record, servedSince) => {
|
|
5801
5976
|
const decision2 = decideStackClaim({
|
|
5802
5977
|
record,
|
|
5803
5978
|
cwd: params.cwd,
|
|
5804
5979
|
label: params.label,
|
|
5805
5980
|
needs: params.needs,
|
|
5981
|
+
pullRequest: params.pullRequest,
|
|
5806
5982
|
now: Date.now(),
|
|
5807
|
-
isAlive: isProcessAlive
|
|
5983
|
+
isAlive: isProcessAlive,
|
|
5984
|
+
servedSince
|
|
5808
5985
|
});
|
|
5809
5986
|
return { next: decision2.next, result: decision2 };
|
|
5810
5987
|
});
|
|
@@ -5825,11 +6002,12 @@ async function claimOnce(params) {
|
|
|
5825
6002
|
};
|
|
5826
6003
|
}
|
|
5827
6004
|
async function updateLedger(decide) {
|
|
6005
|
+
const servedSince = servedSinceFrom(await readTrackedServices());
|
|
5828
6006
|
const outcome = await updateGenerationStore({
|
|
5829
6007
|
directory: ledgerDir(),
|
|
5830
6008
|
emptyRecord: EMPTY_STACK_RECORD,
|
|
5831
6009
|
decide: (snapshot) => {
|
|
5832
|
-
const update = decide(snapshot.record);
|
|
6010
|
+
const update = decide(snapshot.record, servedSince);
|
|
5833
6011
|
const vacatedNeeds = update.next === null ? [] : findVacatedNeeds({ before: snapshot.record, after: update.next });
|
|
5834
6012
|
return {
|
|
5835
6013
|
next: update.next,
|
|
@@ -5874,6 +6052,12 @@ async function findPortMismatch(params) {
|
|
|
5874
6052
|
if (isTrackedListener) return null;
|
|
5875
6053
|
return { need: params.need, kind: "UNTRACKED_LISTENER" /* UntrackedListener */, actual: tracked, listener: reading.listener };
|
|
5876
6054
|
}
|
|
6055
|
+
function servedSinceFrom(services) {
|
|
6056
|
+
return (need) => {
|
|
6057
|
+
const service = trackedServiceOnPort({ services, port: need.port });
|
|
6058
|
+
return service !== null && servesNeed({ service, need }) ? service.startedAt : null;
|
|
6059
|
+
};
|
|
6060
|
+
}
|
|
5877
6061
|
function servesNeed(params) {
|
|
5878
6062
|
return params.service.dir === params.need.runDir && params.service.headSha === params.need.headSha;
|
|
5879
6063
|
}
|
|
@@ -5886,7 +6070,9 @@ async function readTrackedServices() {
|
|
|
5886
6070
|
const perFile = await Promise.all(
|
|
5887
6071
|
fileNames.filter((fileName) => fileName.endsWith(".json")).map(async (fileName) => {
|
|
5888
6072
|
try {
|
|
5889
|
-
const
|
|
6073
|
+
const filePath = path16.join(directory, fileName);
|
|
6074
|
+
const [content, stat4] = await Promise.all([fs14.readFile(filePath, "utf-8"), fs14.stat(filePath)]);
|
|
6075
|
+
const parsed = JSON.parse(content);
|
|
5890
6076
|
return (parsed.services ?? []).filter((service) => typeof service.port === "number" && typeof service.pid === "number").filter((service) => isProcessAlive(service.pid)).map(
|
|
5891
6077
|
(service) => ({
|
|
5892
6078
|
name: service.name ?? "",
|
|
@@ -5895,7 +6081,8 @@ async function readTrackedServices() {
|
|
|
5895
6081
|
pid: service.pid,
|
|
5896
6082
|
headSha: service.headSha ?? null,
|
|
5897
6083
|
source: normalizePath(parsed.source ?? ""),
|
|
5898
|
-
isExternal: service.external === true
|
|
6084
|
+
isExternal: service.external === true,
|
|
6085
|
+
startedAt: parseStartedAt(service.startedAt) ?? stat4.mtimeMs
|
|
5899
6086
|
})
|
|
5900
6087
|
);
|
|
5901
6088
|
} catch {
|
|
@@ -5905,6 +6092,10 @@ async function readTrackedServices() {
|
|
|
5905
6092
|
);
|
|
5906
6093
|
return perFile.flat();
|
|
5907
6094
|
}
|
|
6095
|
+
function parseStartedAt(startedAt) {
|
|
6096
|
+
const parsed = startedAt === void 0 ? Number.NaN : Date.parse(startedAt);
|
|
6097
|
+
return Number.isNaN(parsed) ? null : parsed;
|
|
6098
|
+
}
|
|
5908
6099
|
async function resolveNeeds(params) {
|
|
5909
6100
|
return await Promise.all(
|
|
5910
6101
|
params.needs.map(async (need) => {
|
|
@@ -9588,7 +9779,11 @@ function writeLastHost(cwd, host) {
|
|
|
9588
9779
|
function clearLastHost(cwd) {
|
|
9589
9780
|
clearCwdEntry(LAST_HOST_CACHE, cwd);
|
|
9590
9781
|
}
|
|
9591
|
-
var REFUSING_VERDICTS = /* @__PURE__ */ new Set([
|
|
9782
|
+
var REFUSING_VERDICTS = /* @__PURE__ */ new Set([
|
|
9783
|
+
"PEER_LEASE" /* PeerLease */,
|
|
9784
|
+
"STALE" /* Stale */,
|
|
9785
|
+
"ENDED" /* Ended */
|
|
9786
|
+
]);
|
|
9592
9787
|
var idleReapTimer = null;
|
|
9593
9788
|
async function findRunStackRefusal(params) {
|
|
9594
9789
|
if (!isLoopbackUrl(params.localUrl)) return null;
|
|
@@ -9709,11 +9904,17 @@ function formatRunCheckoutRefusal(report, cwd) {
|
|
|
9709
9904
|
}
|
|
9710
9905
|
|
|
9711
9906
|
// packages/mcps/src/mcp/tools/local/run-stack-refusal.ts
|
|
9907
|
+
var RE_PREPARE_REMEDY = "Re-run muggle-test-prepare from this checkout: it re-claims the stack and restarts what moved.";
|
|
9908
|
+
var REFUSAL_HEADING_BY_VERDICT = {
|
|
9909
|
+
["PEER_LEASE" /* PeerLease */]: "## Run refused \u2014 the target port is leased to another checkout",
|
|
9910
|
+
["STALE" /* Stale */]: "## Run refused \u2014 the service stack changed since this checkout claimed it",
|
|
9911
|
+
["ENDED" /* Ended */]: "## Run refused \u2014 this checkout's stack lease ended and its services were stopped"
|
|
9912
|
+
};
|
|
9712
9913
|
function formatRunStackRefusal(report, cwd) {
|
|
9713
9914
|
const mismatchLines = report.mismatches.map((mismatch) => `- ${describeStackMismatch(mismatch)}`);
|
|
9714
|
-
const remedy = report.verdict === "PEER_LEASE" /* PeerLease */ ? `Claim the stack for this checkout \u2014 \`muggle stack claim --cwd "${cwd}" --need <port>=<run-dir>\` \u2014 then wait your turn or re-run prepare once it is granted.` :
|
|
9915
|
+
const remedy = report.verdict === "PEER_LEASE" /* PeerLease */ ? `Claim the stack for this checkout \u2014 \`muggle stack claim --cwd "${cwd}" --need <port>=<run-dir>\` \u2014 then wait your turn or re-run prepare once it is granted.` : report.verdict === "ENDED" /* Ended */ ? `${RE_PREPARE_REMEDY} Ask any question before invoking it: a lease ends when its first run does not follow soon after its services come up.` : RE_PREPARE_REMEDY;
|
|
9715
9916
|
return [
|
|
9716
|
-
report.verdict
|
|
9917
|
+
REFUSAL_HEADING_BY_VERDICT[report.verdict] ?? `## Run refused \u2014 ${report.verdict}`,
|
|
9717
9918
|
"",
|
|
9718
9919
|
`**Verdict:** ${report.verdict}`,
|
|
9719
9920
|
`**Checkout:** ${report.cwd}`,
|
|
@@ -11069,4 +11270,4 @@ function applyLlmSourceSelection(selection, dataDirOverride2) {
|
|
|
11069
11270
|
return resolveLlmSourceStatus(dataDirOverride2);
|
|
11070
11271
|
}
|
|
11071
11272
|
|
|
11072
|
-
export { ACTION_SCRIPT_FILE_NAME, BASE_REMOTE_NAME, BLOCKING_FRESHNESS_VERDICTS, BRANCH_CREATION_REFLOG_PREFIX, BUSY_EXIT_CODE, CLAUDE_CODE_AUTH_STATUS_ARGS, CLAUDE_CODE_CHECK_TIMEOUT_MS, CLAUDE_CODE_EXECUTABLE, CLAUDE_CODE_EXECUTABLE_NOT_FOUND_CODE, CLAUDE_CODE_SESSION_LINK_ENV_VARS, CLAUDE_CODE_VERSION_ARGS, CheckoutFreshnessVerdict, ClaudeCodeReadiness, DEFAULT_LOCAL_LLM_BASE_URL, DEFAULT_PREFERENCES, ELECTRON_APP_EXECUTABLE_PATH_SEGMENTS, ElectronAppPlatform, ElectronAppReleaseStream, EventName, HTML_VIEW_OFFER_LINE, LLM_CREDENTIALS_FILE_MODE, LLM_CREDENTIALS_FILE_NAME, LLM_SOURCE_HEADER, LLM_SOURCE_OPTIONS, LLM_SOURCE_OWNED_ENV_NAMES, LLM_SOURCE_PROMPT, LLM_SOURCE_SCOPE_NOTE, LlmSource, MASKED_API_KEY_PREFIX, MASKED_API_KEY_VISIBLE_CHARS, MAX_LISTED_INHERITED_COMMITS, ONBOARDING_BLANKET_OPTIONS, ONBOARDING_CHOICE_GROUPS, ONBOARDING_MAX_OFFERS, ONBOARDING_PRIMER_BULLETS, ONBOARDING_PRIMER_HEADLINE, ONBOARDING_TOGGLE_GROUPS, ONBOARDING_TOGGLE_PROMPT, ONBOARDING_UNSELECTED_TOGGLE_VALUE, OnboardingBlanketChoice, OnboardingGroupKind, PREFERENCES_FILE_NAME, PREFERENCES_PROJECT_DIR_NAME, PREFERENCES_SCHEMA, PREFERENCES_VERSION, PREFERENCE_ALLOWED_VALUES, PreferenceKey, PreferenceValue, ProjectPreferencesReconcileOutcome, RUN_SESSIONS_DIR_NAME, RUN_VIEW_DASHBOARD_LINK_LABEL, RunViewError, RuntimeTarget, SCREENSHOT_DIR_RELATIVE_PATH, SCREENSHOT_FILE_EXTENSION, ServiceName, StackCheckVerdict, StackClaimVerdict, StackMismatchKind, StackPortAction, Surface, WATCHER_LIFETIME_SECONDS, WATCHER_LIFETIME_UNBOUNDED_SECONDS, __export, __require, applyLlmSourceSelection, applyOnboardingAnswers, assertDeviceCodeClientProvisioned, buildElectronAppChecksumsUrl, buildElectronAppDirName, buildElectronAppReleaseAssetUrl, buildElectronAppReleaseTag, buildLlmSourcePlan, buildOnboardingPlan, buildToolInputJsonSchema, calculateFileChecksum, checkServiceStack, claimServiceStack, completeOnboarding, createApiKeyWithToken, createChildLogger, decideStackClaim, decideStackReap, decideStackRelease, decideStackRunEnd, decideStackRunStart, deleteApiKeyData, deleteCredentials, describeLlmSourceStatus, describeStackMismatch, e2e_exports2 as e2e_exports, evaluateCheckoutFreshness, findVacatedNeeds, finishStackRun, formatPreferencesOneLiner, getActiveElectronAppReleaseStream, getActiveRuntimeTarget, getApiKey, getApiKeyFilePath, getAuthService, getBundledElectronAppVersion, getCallerCredentials, getCallerCredentialsAsync, getChecksumForPlatform, getConfig, getCredentialsFilePath, getDashboardBaseUrl, getDataDir, getDataDir2, getDisclosureCopy, getDownloadBaseUrl, getElectronAppChecksums, getElectronAppDir, getElectronAppReleaseTagPrefix, getElectronAppSignedFromVersion, getElectronAppVersion, getElectronAppVersionSource, getLlmSourceOption, getLocalQaTools, getLogger, getOnboardingToggleKeys, getPlatformKey, getQaTools, getReleaseSignerIdentityUri, getRunSessionsDir, getValidApiKeyData, getValidCredentials, hasApiKey, hasShownDisclosure, initTelemetry, isElectronAppInstalled, isElectronAppPlatform, isFirstRun, isValidLocalBaseUrl, loadApiKeyData, loadCredentials, local_exports2 as local_exports, markDisclosureShown, markOnboardingCompleted, maskLlmApiKey, mcp_exports, needsConflict, needsOnboarding, openBrowserUrl, parseElectronAppDirName, performLogin, performLogout, pollDeviceCode, portOfUrl, readPreferencesMetadata, readRunView, readServiceStackStatus, reapServiceStack, reconcileProjectPreferences, recordOnboardingSkip, releaseServiceStack, renderRunViewHtml, renderRunViewText, resetConfig, resetLogger, resetPreference, resolveActiveProfile, resolveActiveReleaseStream, resolveActiveReleaseTagPrefix, resolveBlanketPreferences, resolveElectronAppExecutablePath, resolveElectronAppPathOrNull, resolveLlmSourceStatus, resolveOnboardingAnswers, resolvePreferences, resolveRunSessionPath, resolveRuntimeTarget, resolveTogglePreferences, runGitCommand, saveApiKey, saveApiKeyData, saveCredentials, src_exports, startDeviceCodeFlow, startStackRun, stopVacatedServices, toDisplayRef, toolRequiresAuth, track, validatePreference, verifyFileChecksum, writeLlmEnvBlock, writePreferences, writePreferencesWithMetadata, writeRunViewHtml };
|
|
11273
|
+
export { ACTION_SCRIPT_FILE_NAME, BASE_REMOTE_NAME, BLOCKING_FRESHNESS_VERDICTS, BRANCH_CREATION_REFLOG_PREFIX, BUSY_EXIT_CODE, CLAUDE_CODE_AUTH_STATUS_ARGS, CLAUDE_CODE_CHECK_TIMEOUT_MS, CLAUDE_CODE_EXECUTABLE, CLAUDE_CODE_EXECUTABLE_NOT_FOUND_CODE, CLAUDE_CODE_SESSION_LINK_ENV_VARS, CLAUDE_CODE_VERSION_ARGS, CheckoutFreshnessVerdict, ClaudeCodeReadiness, DEFAULT_LOCAL_LLM_BASE_URL, DEFAULT_PREFERENCES, ELECTRON_APP_EXECUTABLE_PATH_SEGMENTS, ElectronAppPlatform, ElectronAppReleaseStream, EventName, HTML_VIEW_OFFER_LINE, LLM_CREDENTIALS_FILE_MODE, LLM_CREDENTIALS_FILE_NAME, LLM_SOURCE_HEADER, LLM_SOURCE_OPTIONS, LLM_SOURCE_OWNED_ENV_NAMES, LLM_SOURCE_PROMPT, LLM_SOURCE_SCOPE_NOTE, LlmSource, MASKED_API_KEY_PREFIX, MASKED_API_KEY_VISIBLE_CHARS, MAX_LISTED_INHERITED_COMMITS, ONBOARDING_BLANKET_OPTIONS, ONBOARDING_CHOICE_GROUPS, ONBOARDING_MAX_OFFERS, ONBOARDING_PRIMER_BULLETS, ONBOARDING_PRIMER_HEADLINE, ONBOARDING_TOGGLE_GROUPS, ONBOARDING_TOGGLE_PROMPT, ONBOARDING_UNSELECTED_TOGGLE_VALUE, OnboardingBlanketChoice, OnboardingGroupKind, PREFERENCES_FILE_NAME, PREFERENCES_PROJECT_DIR_NAME, PREFERENCES_SCHEMA, PREFERENCES_VERSION, PREFERENCE_ALLOWED_VALUES, PreferenceKey, PreferenceValue, ProjectPreferencesReconcileOutcome, RUN_SESSIONS_DIR_NAME, RUN_VIEW_DASHBOARD_LINK_LABEL, RunViewError, RuntimeTarget, SCREENSHOT_DIR_RELATIVE_PATH, SCREENSHOT_FILE_EXTENSION, ServiceName, StackCheckVerdict, StackClaimVerdict, StackLeaseEndReason, StackMismatchKind, StackPortAction, Surface, WATCHER_LIFETIME_SECONDS, WATCHER_LIFETIME_UNBOUNDED_SECONDS, __export, __require, applyLlmSourceSelection, applyOnboardingAnswers, assertDeviceCodeClientProvisioned, buildElectronAppChecksumsUrl, buildElectronAppDirName, buildElectronAppReleaseAssetUrl, buildElectronAppReleaseTag, buildLlmSourcePlan, buildOnboardingPlan, buildToolInputJsonSchema, calculateFileChecksum, checkServiceStack, claimServiceStack, compareQueuePlaces, completeOnboarding, createApiKeyWithToken, createChildLogger, decideStackClaim, decideStackReap, decideStackRelease, decideStackRunEnd, decideStackRunStart, deleteApiKeyData, deleteCredentials, describeLlmSourceStatus, describeStackMismatch, e2e_exports2 as e2e_exports, evaluateCheckoutFreshness, findVacatedNeeds, finishStackRun, formatPreferencesOneLiner, getActiveElectronAppReleaseStream, getActiveRuntimeTarget, getApiKey, getApiKeyFilePath, getAuthService, getBundledElectronAppVersion, getCallerCredentials, getCallerCredentialsAsync, getChecksumForPlatform, getConfig, getCredentialsFilePath, getDashboardBaseUrl, getDataDir, getDataDir2, getDisclosureCopy, getDownloadBaseUrl, getElectronAppChecksums, getElectronAppDir, getElectronAppReleaseTagPrefix, getElectronAppSignedFromVersion, getElectronAppVersion, getElectronAppVersionSource, getLlmSourceOption, getLocalQaTools, getLogger, getOnboardingToggleKeys, getPlatformKey, getQaTools, getReleaseSignerIdentityUri, getRunSessionsDir, getValidApiKeyData, getValidCredentials, hasApiKey, hasShownDisclosure, initTelemetry, isElectronAppInstalled, isElectronAppPlatform, isFirstRun, isValidLocalBaseUrl, loadApiKeyData, loadCredentials, local_exports2 as local_exports, lookUpCheckoutPullRequest, markDisclosureShown, markOnboardingCompleted, maskLlmApiKey, mcp_exports, needsConflict, needsOnboarding, openBrowserUrl, parseElectronAppDirName, performLogin, performLogout, pollDeviceCode, portOfUrl, readPreferencesMetadata, readRunView, readServiceStackStatus, reapServiceStack, reconcileProjectPreferences, recordOnboardingSkip, releaseServiceStack, renderRunViewHtml, renderRunViewText, resetConfig, resetLogger, resetPreference, resolveActiveProfile, resolveActiveReleaseStream, resolveActiveReleaseTagPrefix, resolveBlanketPreferences, resolveElectronAppExecutablePath, resolveElectronAppPathOrNull, resolveLlmSourceStatus, resolveOnboardingAnswers, resolvePreferences, resolveRunSessionPath, resolveRuntimeTarget, resolveTogglePreferences, runGitCommand, saveApiKey, saveApiKeyData, saveCredentials, src_exports, startDeviceCodeFlow, startStackRun, stopVacatedServices, toDisplayRef, toolRequiresAuth, track, validatePreference, verifyFileChecksum, writeLlmEnvBlock, writePreferences, writePreferencesWithMetadata, writeRunViewHtml };
|