@muggleai/works 5.31.0-staging.271 → 5.31.0-staging.272
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-RQ43IPVN.js → chunk-IZQGF3F2.js} +59 -21
- package/dist/cli.js +1 -1
- package/dist/index.js +1 -1
- 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/muggle-pr-followup/state-schemas.md +1 -1
- package/dist/release-manifest.json +3 -3
- package/package.json +1 -1
- 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/muggle-pr-followup/state-schemas.md +1 -1
- package/scripts/muggle-watch/README.md +1 -1
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"name": "muggleai",
|
|
10
10
|
"source": "./plugin",
|
|
11
11
|
"description": "Real-browser E2E acceptance for your coding agent: test the flows you changed and open the PR with screenshots. Built for flows other tools can't reach: managed test identities with live inboxes (magic links, email OTP and 2FA, verification mail, password resets), an encrypted secret store for login and test credentials, and a test graph that runs prerequisite flows first. Plain-English tests become replayable scripts on localhost or preview URLs.",
|
|
12
|
-
"version": "5.31.0-staging.
|
|
12
|
+
"version": "5.31.0-staging.272"
|
|
13
13
|
}
|
|
14
14
|
]
|
|
15
15
|
}
|
|
@@ -5012,11 +5012,25 @@ function readRegistryBySessionId(registryDir) {
|
|
|
5012
5012
|
}
|
|
5013
5013
|
return entriesBySessionId;
|
|
5014
5014
|
}
|
|
5015
|
+
function isPidAlive(pid) {
|
|
5016
|
+
try {
|
|
5017
|
+
process.kill(pid, 0);
|
|
5018
|
+
return true;
|
|
5019
|
+
} catch (error) {
|
|
5020
|
+
return error.code === "EPERM";
|
|
5021
|
+
}
|
|
5022
|
+
}
|
|
5023
|
+
function isSessionRunning(runCommand, registryDir, sessionId) {
|
|
5024
|
+
const registryEntry = readRegistryBySessionId(registryDir).get(sessionId);
|
|
5025
|
+
if (registryEntry !== void 0 && isPidAlive(registryEntry.pid)) return true;
|
|
5026
|
+
const sessions = readClaudeSessions(runCommand, registryDir);
|
|
5027
|
+
if (sessions === null) return null;
|
|
5028
|
+
const session = sessions.find((listed) => listed.sessionId === sessionId);
|
|
5029
|
+
return session !== void 0 && hasLiveProcess(session);
|
|
5030
|
+
}
|
|
5015
5031
|
function waitForSessionExit(runCommand, registryDir, sessionId, sleep2 = blockFor) {
|
|
5016
5032
|
for (let waitedMs = 0; ; waitedMs += SESSION_EXIT_POLL_MS) {
|
|
5017
|
-
|
|
5018
|
-
const session = sessions?.find((listed) => listed.sessionId === sessionId);
|
|
5019
|
-
if (sessions !== null && (session === void 0 || !hasLiveProcess(session))) return true;
|
|
5033
|
+
if (isSessionRunning(runCommand, registryDir, sessionId) === false) return true;
|
|
5020
5034
|
if (waitedMs >= SESSION_EXIT_WAIT_MS) return false;
|
|
5021
5035
|
sleep2(SESSION_EXIT_POLL_MS);
|
|
5022
5036
|
}
|
|
@@ -5340,13 +5354,22 @@ function startServiceLoops(assessments, nowMs, isPollingDisabled, ownerSessionOf
|
|
|
5340
5354
|
}
|
|
5341
5355
|
|
|
5342
5356
|
// src/watch/session-restart.ts
|
|
5343
|
-
function resumeSession(target, runCommand, waitForExit) {
|
|
5357
|
+
function resumeSession(target, runCommand, waitForExit, isSessionRunning2) {
|
|
5358
|
+
const runningSessionId = target.runningSessionId ?? target.sessionId;
|
|
5344
5359
|
if (target.runningShortId !== void 0) {
|
|
5345
5360
|
runCommand("claude", ["stop", target.runningShortId]);
|
|
5346
|
-
if (!waitForExit(
|
|
5347
|
-
}
|
|
5348
|
-
|
|
5349
|
-
|
|
5361
|
+
if (!waitForExit(runningSessionId)) return { outcome: "still-running" /* StillRunning */ };
|
|
5362
|
+
} else if (isSessionRunning2(runningSessionId)) {
|
|
5363
|
+
return { outcome: "still-running" /* StillRunning */ };
|
|
5364
|
+
}
|
|
5365
|
+
const resumed = runCommand("claude", ["--resume", target.sessionId, "--bg", target.prompt], { cwd: target.cwd, isStderrCaptured: true });
|
|
5366
|
+
if (resumed?.exitCode !== 0) return { outcome: "failed" /* Failed */ };
|
|
5367
|
+
const backgroundedShortId = resumed.stdout.match(BACKGROUNDED_SESSION_ID_PATTERN)?.[1];
|
|
5368
|
+
const ownShortIds = [shortSessionId(target.sessionId), shortSessionId(runningSessionId), target.runningShortId];
|
|
5369
|
+
if (backgroundedShortId === void 0 || ownShortIds.includes(backgroundedShortId)) return { outcome: "continued" /* Continued */ };
|
|
5370
|
+
const isCopyStopped = runCommand("claude", ["stop", backgroundedShortId])?.exitCode === 0;
|
|
5371
|
+
const isCopyRemoved = isCopyStopped && runCommand("claude", ["rm", backgroundedShortId])?.exitCode === 0;
|
|
5372
|
+
return { outcome: "copied" /* Copied */, copyShortId: backgroundedShortId, isCopyRemoved };
|
|
5350
5373
|
}
|
|
5351
5374
|
|
|
5352
5375
|
// src/watch/recovery.ts
|
|
@@ -5427,20 +5450,31 @@ function executeWatcherRecoveries(planned, attemptsByOwner, deps) {
|
|
|
5427
5450
|
});
|
|
5428
5451
|
return { ...recovery, cwd, outcome: "restart-refused" /* RestartRefused */ };
|
|
5429
5452
|
}
|
|
5430
|
-
const
|
|
5453
|
+
const resumed = resumeSession(
|
|
5431
5454
|
{ sessionId: recovery.ownerSessionId, cwd, prompt, runningShortId: recovery.ownerShortId, runningSessionId: recovery.ownerRunningSessionId },
|
|
5432
5455
|
deps.runCommand,
|
|
5433
|
-
deps.waitForExit
|
|
5456
|
+
deps.waitForExit,
|
|
5457
|
+
deps.isSessionRunning
|
|
5434
5458
|
);
|
|
5435
|
-
if (
|
|
5436
|
-
|
|
5437
|
-
|
|
5438
|
-
|
|
5439
|
-
|
|
5440
|
-
|
|
5441
|
-
|
|
5442
|
-
|
|
5443
|
-
|
|
5459
|
+
if (resumed.outcome === "continued" /* Continued */) {
|
|
5460
|
+
markServiceLoopReportDelivered(recovery.slotDir);
|
|
5461
|
+
events.push({ at: nowIso, kind: "resumed" /* Resumed */, subject, message: `woke it with ${prompt}` });
|
|
5462
|
+
return { ...recovery, cwd, outcome: "resumed" /* Resumed */ };
|
|
5463
|
+
}
|
|
5464
|
+
countAttempt(recovery.ownerSessionId);
|
|
5465
|
+
if (resumed.outcome === "copied" /* Copied */) {
|
|
5466
|
+
const copyNote = resumed.isCopyRemoved ? "stopped and removed the copy" : `could not remove the copy, run claude rm ${resumed.copyShortId}`;
|
|
5467
|
+
events.push({
|
|
5468
|
+
at: nowIso,
|
|
5469
|
+
kind: "resume-forked" /* ResumeForked */,
|
|
5470
|
+
subject,
|
|
5471
|
+
message: `could not wake it: Claude still ran it and started copy ${resumed.copyShortId} instead; ${copyNote}`
|
|
5472
|
+
});
|
|
5473
|
+
return { ...recovery, cwd, outcome: "resume-forked" /* ResumeForked */ };
|
|
5474
|
+
}
|
|
5475
|
+
const reason = resumed.outcome === "still-running" /* StillRunning */ ? "it is still running, so a resume would start a copy" : `${prompt} did not start`;
|
|
5476
|
+
events.push({ at: nowIso, kind: "resume-failed" /* ResumeFailed */, subject, message: `could not wake it: ${reason}` });
|
|
5477
|
+
return { ...recovery, cwd, outcome: "resume-failed" /* ResumeFailed */ };
|
|
5444
5478
|
});
|
|
5445
5479
|
return { recoveries, attemptsByOwner: nextAttemptsByOwner, events };
|
|
5446
5480
|
}
|
|
@@ -6440,7 +6474,7 @@ function idleSinceBySessionId(runningSessions) {
|
|
|
6440
6474
|
);
|
|
6441
6475
|
}
|
|
6442
6476
|
function isRestartAttempted(recovery) {
|
|
6443
|
-
return recovery.outcome === "resumed" /* Resumed */ || recovery.outcome === "resume-failed" /* ResumeFailed */;
|
|
6477
|
+
return recovery.outcome === "resumed" /* Resumed */ || recovery.outcome === "resume-failed" /* ResumeFailed */ || recovery.outcome === "resume-forked" /* ResumeForked */;
|
|
6444
6478
|
}
|
|
6445
6479
|
function watchOwnersOf(slots) {
|
|
6446
6480
|
return new Set(slots.flatMap((slot) => !isTerminalPrState(slot.live?.state) && slot.ownerSessionId ? [slot.ownerSessionId] : []));
|
|
@@ -6484,6 +6518,9 @@ function canRestartVia(deps) {
|
|
|
6484
6518
|
function waitForExitVia(deps) {
|
|
6485
6519
|
return (sessionId) => waitForSessionExit(deps.runCommand, deps.paths.claudeSessionRegistryDir, sessionId, deps.sleep);
|
|
6486
6520
|
}
|
|
6521
|
+
function isSessionRunningVia(deps) {
|
|
6522
|
+
return (sessionId) => isSessionRunning(deps.runCommand, deps.paths.claudeSessionRegistryDir, sessionId) !== false;
|
|
6523
|
+
}
|
|
6487
6524
|
function reloadTargetAtMs(deps, installs) {
|
|
6488
6525
|
return resolveReloadTargetAtMs(installs, readMugglePluginUpdatedAtMs(deps.paths.claudeInstalledPluginsFile));
|
|
6489
6526
|
}
|
|
@@ -6551,7 +6588,8 @@ function runTickUnlocked(deps) {
|
|
|
6551
6588
|
claudeProjectsDir: deps.paths.claudeProjectsDir,
|
|
6552
6589
|
nowMs: deps.nowMs,
|
|
6553
6590
|
canRestartIn: canRestartVia(deps),
|
|
6554
|
-
waitForExit: waitForExitVia(deps)
|
|
6591
|
+
waitForExit: waitForExitVia(deps),
|
|
6592
|
+
isSessionRunning: isSessionRunningVia(deps)
|
|
6555
6593
|
});
|
|
6556
6594
|
recoveries = executed.recoveries;
|
|
6557
6595
|
recoveryAttemptsByOwner = executed.attemptsByOwner;
|
package/dist/cli.js
CHANGED
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { src_exports as commands, createUnifiedMcpServer, server_exports as server } from './chunk-
|
|
1
|
+
export { src_exports as commands, createUnifiedMcpServer, server_exports as server } from './chunk-IZQGF3F2.js';
|
|
2
2
|
export { createChildLogger, e2e_exports as e2e, getConfig, getLocalQaTools, getLogger, getQaTools, local_exports as localQa, mcp_exports as mcp, e2e_exports as qa, src_exports as shared } from './chunk-XUANXZ2O.js';
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "muggle",
|
|
3
3
|
"description": "Real-browser E2E acceptance for your coding agent: test the flows you changed and open the PR with screenshots. Built for flows other tools can't reach: managed test identities with live inboxes (magic links, email OTP and 2FA, verification mail, password resets), an encrypted secret store for login and test credentials, and a test graph that runs prerequisite flows first. Plain-English tests become replayable scripts on localhost or preview URLs.",
|
|
4
|
-
"version": "5.31.0-staging.
|
|
4
|
+
"version": "5.31.0-staging.272",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Muggle AI",
|
|
7
7
|
"email": "support@muggle-ai.com"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "muggle",
|
|
3
|
-
"version": "5.31.0-staging.
|
|
3
|
+
"version": "5.31.0-staging.272",
|
|
4
4
|
"description": "Real-browser E2E acceptance for your coding agent: test the flows you changed and open the PR with screenshots. Built for flows other tools can't reach: managed test identities with live inboxes (magic links, email OTP and 2FA, verification mail, password resets), an encrypted secret store for login and test credentials, and a test graph that runs prerequisite flows first. Plain-English tests become replayable scripts on localhost or preview URLs.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Muggle AI",
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "muggle",
|
|
3
3
|
"displayName": "Muggle AI",
|
|
4
4
|
"description": "Real-browser E2E acceptance for your coding agent: test the flows you changed and open the PR with screenshots. Built for flows other tools can't reach: managed test identities with live inboxes (magic links, email OTP and 2FA, verification mail, password resets), an encrypted secret store for login and test credentials, and a test graph that runs prerequisite flows first. Plain-English tests become replayable scripts on localhost or preview URLs.",
|
|
5
|
-
"version": "5.31.0-staging.
|
|
5
|
+
"version": "5.31.0-staging.272",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Muggle AI",
|
|
8
8
|
"email": "support@muggle-ai.com"
|
package/dist/plugin/.mcp.json
CHANGED
|
@@ -61,7 +61,7 @@ The slot's opt-in to recovery by the muggle watch service (`muggle watch`, a per
|
|
|
61
61
|
|
|
62
62
|
Recovery requires all of: the slot is registered, the PR is open on GitHub, no loop is heard (no loop process carries the slot's `--slot` path, or the one that does prints into a session that has ended), the heartbeat is older than the re-arm hand-off, `polling.disabled` is absent, `owner.json` names an owner, and that owner is not mid-turn. The service then runs `pr-watch-arm.sh` itself, detached, with `MUGGLE_PR_WATCH_LISTENER=muggle-watch`. The loop records [`watch-listener`](#watch-listener) and writes its output to [`watch-service.out`](#watch-serviceout), and no session spends a turn on it.
|
|
63
63
|
|
|
64
|
-
When that loop exits on an `EVENT` or `TERMINAL` line, the service wakes the owner with `/muggle:muggle-pr-followup <slug> <n> --wake=<event|terminal>`, naming that slot and PR only. It resumes the owner in the background from the project dir its transcript records. A running owner is woken only as a background session idle between turns: it is stopped first, and only after a throwaway background launch confirms Claude will resume it in that workspace. An owner that is mid-turn, waiting on its user, or open in a terminal keeps the report, and the service alerts once the report is overdue. The service never wakes the ended owner of a merged or closed PR; it closes that slot out instead. An `EXPIRED` loop is restarted without waking anyone. The service caps loop starts and wakes per pass, caps failed wakes per owner, and holds every wake while the account sits at a usage limit.
|
|
64
|
+
When that loop exits on an `EVENT` or `TERMINAL` line, the service wakes the owner with `/muggle:muggle-pr-followup <slug> <n> --wake=<event|terminal>`, naming that slot and PR only. It resumes the owner in the background from the project dir its transcript records. A running owner is woken only as a background session idle between turns: it is stopped first, and only after a throwaway background launch confirms Claude will resume it in that workspace. The resume runs only once neither `claude agents` nor Claude's own session registry shows a process for the owner. If Claude still answers with a copy under a new id, the service stops and removes that copy and counts the wake as failed, so the agent view never gains a duplicate. An owner that is mid-turn, waiting on its user, or open in a terminal keeps the report, and the service alerts once the report is overdue. The service never wakes the ended owner of a merged or closed PR; it closes that slot out instead. An `EXPIRED` loop is restarted without waking anyone. The service caps loop starts and wakes per pass, caps failed wakes per owner, and holds every wake while the account sits at a usage limit.
|
|
65
65
|
|
|
66
66
|
**Absent ⇒ observed only.** The service still reports on the slot's watcher, CI and branch standing, but never starts its loop or wakes its owner. Slots armed before this file existed stay that way until their next arm. The file lives in the slot, so a `.stopped` rename or a `result.md` retires it with the slot.
|
|
67
67
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"release": "5.26.0",
|
|
3
|
-
"buildId": "run-
|
|
4
|
-
"commitSha": "
|
|
5
|
-
"buildTime": "2026-11-
|
|
3
|
+
"buildId": "run-272-1",
|
|
4
|
+
"commitSha": "76a7b1d3658b798a4b809e758e42e7128e23aba1",
|
|
5
|
+
"buildTime": "2026-11-01T06:59:53Z",
|
|
6
6
|
"serviceName": "muggle-ai-works-mcp"
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@muggleai/works",
|
|
3
3
|
"mcpName": "io.github.multiplex-ai/muggle",
|
|
4
|
-
"version": "5.31.0-staging.
|
|
4
|
+
"version": "5.31.0-staging.272",
|
|
5
5
|
"description": "13 years of Microsoft hyperscale engineering, packaged as a harness for your AI coding agent.",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "dist/index.js",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "muggle",
|
|
3
3
|
"description": "Real-browser E2E acceptance for your coding agent: test the flows you changed and open the PR with screenshots. Built for flows other tools can't reach: managed test identities with live inboxes (magic links, email OTP and 2FA, verification mail, password resets), an encrypted secret store for login and test credentials, and a test graph that runs prerequisite flows first. Plain-English tests become replayable scripts on localhost or preview URLs.",
|
|
4
|
-
"version": "5.31.0-staging.
|
|
4
|
+
"version": "5.31.0-staging.272",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Muggle AI",
|
|
7
7
|
"email": "support@muggle-ai.com"
|
package/plugin/.codex-mcp.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "muggle",
|
|
3
|
-
"version": "5.31.0-staging.
|
|
3
|
+
"version": "5.31.0-staging.272",
|
|
4
4
|
"description": "Real-browser E2E acceptance for your coding agent: test the flows you changed and open the PR with screenshots. Built for flows other tools can't reach: managed test identities with live inboxes (magic links, email OTP and 2FA, verification mail, password resets), an encrypted secret store for login and test credentials, and a test graph that runs prerequisite flows first. Plain-English tests become replayable scripts on localhost or preview URLs.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Muggle AI",
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "muggle",
|
|
3
3
|
"displayName": "Muggle AI",
|
|
4
4
|
"description": "Real-browser E2E acceptance for your coding agent: test the flows you changed and open the PR with screenshots. Built for flows other tools can't reach: managed test identities with live inboxes (magic links, email OTP and 2FA, verification mail, password resets), an encrypted secret store for login and test credentials, and a test graph that runs prerequisite flows first. Plain-English tests become replayable scripts on localhost or preview URLs.",
|
|
5
|
-
"version": "5.31.0-staging.
|
|
5
|
+
"version": "5.31.0-staging.272",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Muggle AI",
|
|
8
8
|
"email": "support@muggle-ai.com"
|
package/plugin/.mcp.json
CHANGED
|
@@ -61,7 +61,7 @@ The slot's opt-in to recovery by the muggle watch service (`muggle watch`, a per
|
|
|
61
61
|
|
|
62
62
|
Recovery requires all of: the slot is registered, the PR is open on GitHub, no loop is heard (no loop process carries the slot's `--slot` path, or the one that does prints into a session that has ended), the heartbeat is older than the re-arm hand-off, `polling.disabled` is absent, `owner.json` names an owner, and that owner is not mid-turn. The service then runs `pr-watch-arm.sh` itself, detached, with `MUGGLE_PR_WATCH_LISTENER=muggle-watch`. The loop records [`watch-listener`](#watch-listener) and writes its output to [`watch-service.out`](#watch-serviceout), and no session spends a turn on it.
|
|
63
63
|
|
|
64
|
-
When that loop exits on an `EVENT` or `TERMINAL` line, the service wakes the owner with `/muggle:muggle-pr-followup <slug> <n> --wake=<event|terminal>`, naming that slot and PR only. It resumes the owner in the background from the project dir its transcript records. A running owner is woken only as a background session idle between turns: it is stopped first, and only after a throwaway background launch confirms Claude will resume it in that workspace. An owner that is mid-turn, waiting on its user, or open in a terminal keeps the report, and the service alerts once the report is overdue. The service never wakes the ended owner of a merged or closed PR; it closes that slot out instead. An `EXPIRED` loop is restarted without waking anyone. The service caps loop starts and wakes per pass, caps failed wakes per owner, and holds every wake while the account sits at a usage limit.
|
|
64
|
+
When that loop exits on an `EVENT` or `TERMINAL` line, the service wakes the owner with `/muggle:muggle-pr-followup <slug> <n> --wake=<event|terminal>`, naming that slot and PR only. It resumes the owner in the background from the project dir its transcript records. A running owner is woken only as a background session idle between turns: it is stopped first, and only after a throwaway background launch confirms Claude will resume it in that workspace. The resume runs only once neither `claude agents` nor Claude's own session registry shows a process for the owner. If Claude still answers with a copy under a new id, the service stops and removes that copy and counts the wake as failed, so the agent view never gains a duplicate. An owner that is mid-turn, waiting on its user, or open in a terminal keeps the report, and the service alerts once the report is overdue. The service never wakes the ended owner of a merged or closed PR; it closes that slot out instead. An `EXPIRED` loop is restarted without waking anyone. The service caps loop starts and wakes per pass, caps failed wakes per owner, and holds every wake while the account sits at a usage limit.
|
|
65
65
|
|
|
66
66
|
**Absent ⇒ observed only.** The service still reports on the slot's watcher, CI and branch standing, but never starts its loop or wakes its owner. Slots armed before this file existed stay that way until their next arm. The file lives in the slot, so a `.stopped` rename or a `result.md` retires it with the slot.
|
|
67
67
|
|
|
@@ -4,7 +4,7 @@ A standing per-user scheduled job that runs `muggle watch tick` every few minute
|
|
|
4
4
|
|
|
5
5
|
- **Tracks** active Claude Code sessions (`claude agents --json`) and Codex sessions (recent `~/.codex/sessions` rollouts). It also tracks their processes: claude, codex, muggle MCP servers, and PR watch loops. Each live Claude session is stamped with when it last loaded its plugins (its process start, or its newest muggle MCP server start after a `/reload-plugins`) and the muggle works build installed then, read off `~/.muggle-ai/postinstall.log`.
|
|
6
6
|
- **Checks every open PR-watch slot** under `~/.muggle-ai/muggle-do/sessions/`. Is its loop running, and heard by a live session? Is CI red? Is the branch behind or conflicting? Are review threads unresolved?
|
|
7
|
-
- **Keeps registered watchers running.** A slot carrying `watch-registration.json` (written by `pr-watch-arm.sh` on every arm) whose loop is dead or unheard gets a loop the service starts itself, as a program outside every session. When that loop reports new activity or the PR ending, the owner session is woken with `/muggle:muggle-pr-followup <slug> <n> --wake=<event|terminal>` for that one PR, but only between its turns. The service never addresses a review, pushes, or pushes anything else into a session.
|
|
7
|
+
- **Keeps registered watchers running.** A slot carrying `watch-registration.json` (written by `pr-watch-arm.sh` on every arm) whose loop is dead or unheard gets a loop the service starts itself, as a program outside every session. When that loop reports new activity or the PR ending, the owner session is woken with `/muggle:muggle-pr-followup <slug> <n> --wake=<event|terminal>` for that one PR, but only between its turns. A wake that Claude answers with a copy of the session under a new id counts as failed, and the copy is stopped and removed. The service never addresses a review, pushes, or pushes anything else into a session.
|
|
8
8
|
- **Closes out finished slots.** A slot whose PR GitHub reports merged or closed, with no loop and no running owner, is finalized the way a watcher's terminal tick would: `prs.json` takes the state, `followup.log` the terminal line, and `result.md` is written. Only local files change; post-merge cleanup is left to a session and recorded as not run.
|
|
9
9
|
- **Keeps sessions on the installed build.** A session loaded before the newest global install (or a later user-scope update of the muggle plugin) is behind; other plugins updating restart nothing. Idle background sessions are respawned onto the new build, up to 3 per tick, keeping their own id and name. A session that owns open PR watches is left running. Before a restart, a throwaway background launch in the session's workspace confirms Claude will bring it back; a workspace that refuses is left running and named once. Busy or waiting sessions wait for a later tick, and terminal sessions get one notification to run `/reload-plugins`. No interface runs a slash command inside another session.
|
|
10
10
|
- **Keeps sessions on Remote Control.** With `remoteControlAtStartup` on in Claude's settings, a live background session is off when its `~/.claude/sessions/<pid>.json` entry has no Remote Control link, or its process started before the current claude.ai login took effect, so its link serves another account. Such a session is respawned once its turn is over (registry status `idle` or `shell`), sharing the 3-per-tick cap with the reload. That keeps the same session id, so there is no duplicate in the agent view, and nothing is typed into it. Background commands carry over to the restarted process, PR watch loops included, so they keep reporting to that session. Each restart opens a new Remote Control session, so a session already on is never restarted. A restarted session gets 30 minutes to link, and one still off after 2 restarts in a day is left alone. Terminal sessions that are off are listed for `/remote-control`.
|