@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.
@@ -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.271"
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
- const sessions = readClaudeSessions(runCommand, registryDir);
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(target.runningSessionId ?? target.sessionId)) return false;
5347
- }
5348
- const resumed = runCommand("claude", ["--resume", target.sessionId, "--bg", target.prompt], { cwd: target.cwd });
5349
- return resumed?.exitCode === 0;
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 isResumed = resumeSession(
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 (isResumed) markServiceLoopReportDelivered(recovery.slotDir);
5436
- else countAttempt(recovery.ownerSessionId);
5437
- events.push({
5438
- at: nowIso,
5439
- kind: isResumed ? "resumed" /* Resumed */ : "resume-failed" /* ResumeFailed */,
5440
- subject,
5441
- message: `${isResumed ? "woke it" : "could not wake it"} with ${prompt}`
5442
- });
5443
- return { ...recovery, cwd, outcome: isResumed ? "resumed" /* Resumed */ : "resume-failed" /* ResumeFailed */ };
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
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { runCli } from './chunk-RQ43IPVN.js';
2
+ import { runCli } from './chunk-IZQGF3F2.js';
3
3
  import './chunk-XUANXZ2O.js';
4
4
 
5
5
  // src/cli/main.ts
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
- export { src_exports as commands, createUnifiedMcpServer, server_exports as server } from './chunk-RQ43IPVN.js';
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.271",
4
+ "version": "5.31.0-staging.272",
5
5
  "author": {
6
6
  "name": "Muggle AI",
7
7
  "email": "support@muggle-ai.com"
@@ -4,7 +4,7 @@
4
4
  "command": "npx",
5
5
  "args": [
6
6
  "-y",
7
- "@muggleai/works@5.31.0-staging.271",
7
+ "@muggleai/works@5.31.0-staging.272",
8
8
  "serve"
9
9
  ],
10
10
  "env": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "muggle",
3
- "version": "5.31.0-staging.271",
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.271",
5
+ "version": "5.31.0-staging.272",
6
6
  "author": {
7
7
  "name": "Muggle AI",
8
8
  "email": "support@muggle-ai.com"
@@ -4,7 +4,7 @@
4
4
  "command": "npx",
5
5
  "args": [
6
6
  "-y",
7
- "@muggleai/works@5.31.0-staging.271",
7
+ "@muggleai/works@5.31.0-staging.272",
8
8
  "serve"
9
9
  ]
10
10
  }
@@ -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-271-1",
4
- "commitSha": "5036fc9c7a6ea65a6269500436e8eb9b6c443413",
5
- "buildTime": "2026-11-01T05:29:33Z",
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.271",
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.271",
4
+ "version": "5.31.0-staging.272",
5
5
  "author": {
6
6
  "name": "Muggle AI",
7
7
  "email": "support@muggle-ai.com"
@@ -4,7 +4,7 @@
4
4
  "command": "npx",
5
5
  "args": [
6
6
  "-y",
7
- "@muggleai/works@5.31.0-staging.271",
7
+ "@muggleai/works@5.31.0-staging.272",
8
8
  "serve"
9
9
  ],
10
10
  "env": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "muggle",
3
- "version": "5.31.0-staging.271",
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.271",
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
@@ -4,7 +4,7 @@
4
4
  "command": "npx",
5
5
  "args": [
6
6
  "-y",
7
- "@muggleai/works@5.31.0-staging.271",
7
+ "@muggleai/works@5.31.0-staging.272",
8
8
  "serve"
9
9
  ]
10
10
  }
@@ -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`.