@muggleai/works 5.31.0-staging.277 → 5.31.0-staging.278

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.277"
12
+ "version": "5.31.0-staging.278"
13
13
  }
14
14
  ]
15
15
  }
@@ -2937,6 +2937,11 @@ var MUGGLE_MCP_SERVER_PATTERN = /@muggleai[\\/]works\b.*\bserve\b/;
2937
2937
  var PR_WATCH_LOOP_PATTERN = /pr-watch-loop\.sh\b.*?--slot\s+(?:"([^"]+)"|(\S+))/;
2938
2938
  var CLAUDE_SESSION_REGISTRY_DIR_NAME = "sessions";
2939
2939
  var CLAUDE_PROJECTS_DIR_NAME = "projects";
2940
+ var CLAUDE_JOBS_DIR_NAME = "jobs";
2941
+ var CLAUDE_JOB_STATE_FILE_NAME = "state.json";
2942
+ var SESSION_NAME_FLAG = "--name";
2943
+ var CLAUDE_PROMPT_NAME_MAX_LENGTH = 200;
2944
+ var CUSTOM_TITLE_ENTRY_TYPE = "custom-title";
2940
2945
  var CLAUDE_INSTALLED_PLUGINS_RELATIVE_PATH = ["plugins", "installed_plugins.json"];
2941
2946
  var CLAUDE_GLOBAL_CONFIG_FILE_NAME = ".claude.json";
2942
2947
  var CLAUDE_SETTINGS_FILE_NAME = "settings.json";
@@ -3004,6 +3009,7 @@ function resolveWatchPaths(env = process.env) {
3004
3009
  slotsDir: join(muggleDataDir, ...PR_WATCH_SLOTS_RELATIVE_PATH),
3005
3010
  claudeSessionRegistryDir: join(claudeConfigDir, CLAUDE_SESSION_REGISTRY_DIR_NAME),
3006
3011
  claudeProjectsDir: join(claudeConfigDir, CLAUDE_PROJECTS_DIR_NAME),
3012
+ claudeJobsDir: join(claudeConfigDir, CLAUDE_JOBS_DIR_NAME),
3007
3013
  claudeInstalledPluginsFile: join(claudeConfigDir, ...CLAUDE_INSTALLED_PLUGINS_RELATIVE_PATH),
3008
3014
  claudeGlobalConfigFile: join(env.CLAUDE_CONFIG_DIR || homedir(), CLAUDE_GLOBAL_CONFIG_FILE_NAME),
3009
3015
  claudeSettingsFile: join(claudeConfigDir, CLAUDE_SETTINGS_FILE_NAME),
@@ -5167,6 +5173,44 @@ function readCodexSessions(sessionsDir, nowMs) {
5167
5173
  }
5168
5174
  return sessions;
5169
5175
  }
5176
+ function jobStatePath(jobsDir, shortId) {
5177
+ return join(jobsDir, shortId, CLAUDE_JOB_STATE_FILE_NAME);
5178
+ }
5179
+ function readJobState(statePath) {
5180
+ try {
5181
+ const parsed = JSON.parse(readFileSync(statePath, "utf-8"));
5182
+ return typeof parsed === "object" && parsed !== null && !Array.isArray(parsed) ? parsed : void 0;
5183
+ } catch {
5184
+ return void 0;
5185
+ }
5186
+ }
5187
+ function respawnFlagsOf(state) {
5188
+ return Array.isArray(state.respawnFlags) ? state.respawnFlags.filter((flag) => typeof flag === "string") : [];
5189
+ }
5190
+ function nameFlagOf(respawnFlags) {
5191
+ const flagIndex = respawnFlags.lastIndexOf(SESSION_NAME_FLAG);
5192
+ return flagIndex === -1 ? void 0 : optionalString(respawnFlags[flagIndex + 1]);
5193
+ }
5194
+ function readClaudeJob(jobsDir, shortId) {
5195
+ const state = readJobState(jobStatePath(jobsDir, shortId));
5196
+ if (state === void 0) return void 0;
5197
+ const intent = optionalString(state.intent);
5198
+ return { respawnFlags: respawnFlagsOf(state), ...intent === void 0 ? {} : { intent } };
5199
+ }
5200
+ function pinClaudeJobName(jobsDir, shortId, name) {
5201
+ const statePath = jobStatePath(jobsDir, shortId);
5202
+ const state = readJobState(statePath);
5203
+ if (state === void 0) return false;
5204
+ const respawnFlags = respawnFlagsOf(state);
5205
+ if (nameFlagOf(respawnFlags) === name) return true;
5206
+ const otherFlags = respawnFlags.filter((flag, index) => flag !== SESSION_NAME_FLAG && respawnFlags[index - 1] !== SESSION_NAME_FLAG);
5207
+ try {
5208
+ writeFileSync(statePath, JSON.stringify({ ...state, respawnFlags: [...otherFlags, SESSION_NAME_FLAG, name] }, null, 2));
5209
+ return true;
5210
+ } catch {
5211
+ return false;
5212
+ }
5213
+ }
5170
5214
  function findTranscriptPath(projectsDir, sessionId) {
5171
5215
  let projectDirNames;
5172
5216
  try {
@@ -5211,6 +5255,23 @@ function readLimitStop(projectsDir, sessionId) {
5211
5255
  }
5212
5256
  return void 0;
5213
5257
  }
5258
+ function readCustomTitle(projectsDir, sessionId) {
5259
+ const transcriptPath = findTranscriptPath(projectsDir, sessionId);
5260
+ if (transcriptPath === void 0) return void 0;
5261
+ let transcript;
5262
+ try {
5263
+ transcript = readFileSync(transcriptPath, "utf-8");
5264
+ } catch {
5265
+ return void 0;
5266
+ }
5267
+ let customTitle;
5268
+ for (const line of transcript.split("\n")) {
5269
+ if (!line.includes(CUSTOM_TITLE_ENTRY_TYPE)) continue;
5270
+ const entry = parseTranscriptLine(line);
5271
+ if (entry?.type === CUSTOM_TITLE_ENTRY_TYPE) customTitle = optionalString(entry.customTitle) ?? customTitle;
5272
+ }
5273
+ return customTitle;
5274
+ }
5214
5275
  function findClaudeSessionCwd(projectsDir, sessionId) {
5215
5276
  const transcriptPath = findTranscriptPath(projectsDir, sessionId);
5216
5277
  if (transcriptPath === void 0) return void 0;
@@ -5430,7 +5491,7 @@ function startServiceLoops(assessments, nowMs, isPollingDisabled, ownerSessionOf
5430
5491
  }
5431
5492
 
5432
5493
  // src/watch/session-restart.ts
5433
- function resumeSession(target, runCommand, waitForExit, isSessionRunning2) {
5494
+ function resumeSession(target, runCommand, waitForExit, isSessionRunning2, pinSessionName) {
5434
5495
  const runningSessionId = target.runningSessionId ?? target.sessionId;
5435
5496
  if (target.runningShortId !== void 0) {
5436
5497
  runCommand("claude", ["stop", target.runningShortId]);
@@ -5438,6 +5499,7 @@ function resumeSession(target, runCommand, waitForExit, isSessionRunning2) {
5438
5499
  } else if (isSessionRunning2(runningSessionId)) {
5439
5500
  return { outcome: "still-running" /* StillRunning */ };
5440
5501
  }
5502
+ pinSessionName(target.sessionId, target.runningShortId ?? shortSessionId(target.sessionId));
5441
5503
  const resumed = runCommand("claude", ["--resume", target.sessionId, "--bg", target.prompt], { cwd: target.cwd, isStderrCaptured: true });
5442
5504
  if (resumed?.exitCode !== 0) return { outcome: "failed" /* Failed */ };
5443
5505
  const backgroundedShortId = resumed.stdout.match(BACKGROUNDED_SESSION_ID_PATTERN)?.[1];
@@ -5530,7 +5592,8 @@ function executeWatcherRecoveries(planned, attemptsByOwner, deps) {
5530
5592
  { sessionId: recovery.ownerSessionId, cwd, prompt, runningShortId: recovery.ownerShortId, runningSessionId: recovery.ownerRunningSessionId },
5531
5593
  deps.runCommand,
5532
5594
  deps.waitForExit,
5533
- deps.isSessionRunning
5595
+ deps.isSessionRunning,
5596
+ deps.pinSessionName
5534
5597
  );
5535
5598
  if (resumed.outcome === "continued" /* Continued */) {
5536
5599
  markServiceLoopReportDelivered(recovery.slotDir);
@@ -5889,7 +5952,8 @@ function executeLimitResumes(planned, attemptsBySession, deps) {
5889
5952
  { sessionId: decision.sessionId, cwd, prompt: LIMIT_RESUME_PROMPT, runningShortId: decision.runningShortId },
5890
5953
  deps.runCommand,
5891
5954
  deps.waitForExit,
5892
- deps.isSessionRunning
5955
+ deps.isSessionRunning,
5956
+ deps.pinSessionName
5893
5957
  );
5894
5958
  outcome = continuedOutcomeOf(resumed.outcome);
5895
5959
  message = continueMessageOf(resumed, decision.reason);
@@ -6159,7 +6223,7 @@ function planReloadFanOut(sessions, pending, watchOwnerSessionIds, maxRestarts =
6159
6223
  }
6160
6224
  return decisions;
6161
6225
  }
6162
- function executeReloadFanOut(decisions, runCommand, canRestartIn) {
6226
+ function executeReloadFanOut(decisions, runCommand, canRestartIn, pinSessionName) {
6163
6227
  const restartableByCwd = /* @__PURE__ */ new Map();
6164
6228
  const isRestartable = (cwd) => {
6165
6229
  if (!restartableByCwd.has(cwd)) restartableByCwd.set(cwd, canRestartIn(cwd));
@@ -6172,6 +6236,7 @@ function executeReloadFanOut(decisions, runCommand, canRestartIn) {
6172
6236
  report.untrustedSessionIds.push(decision.sessionId);
6173
6237
  continue;
6174
6238
  }
6239
+ pinSessionName(decision.sessionId, decision.shortId);
6175
6240
  const isRestarted = runCommand("claude", ["respawn", decision.shortId])?.exitCode === 0;
6176
6241
  (isRestarted ? report.respawnedSessionIds : report.failedSessionIds).push(decision.sessionId);
6177
6242
  }
@@ -6372,6 +6437,16 @@ function stampSessionLoads(sessions, table, tracked, installs, nowMs) {
6372
6437
  });
6373
6438
  }
6374
6439
 
6440
+ // src/watch/session-names.ts
6441
+ function isPlaceholderSessionName(name, shortId) {
6442
+ return name === void 0 || name === shortId || name === LIMIT_RESUME_PROMPT || name.startsWith("/");
6443
+ }
6444
+ function resolveSessionName(sources) {
6445
+ const promptName = sources.job?.intent?.replace(/\s+/g, " ").trim().slice(0, CLAUDE_PROMPT_NAME_MAX_LENGTH);
6446
+ const candidates = [sources.customTitle, sources.job && nameFlagOf(sources.job.respawnFlags), sources.registeredName, promptName];
6447
+ return candidates.find((candidate) => !isPlaceholderSessionName(candidate, sources.shortId));
6448
+ }
6449
+
6375
6450
  // src/watch/terminal-pr-cache.ts
6376
6451
  function isRecheckDue(record, nowMs) {
6377
6452
  if (!record) return true;
@@ -6677,12 +6752,12 @@ function reloadRecordFor(state, targetAtMs) {
6677
6752
  }
6678
6753
  function runReloadPass(record, claudeSessions, watchOwnerSessionIds, deps, at) {
6679
6754
  const decisions = planReloadFanOut(claudeSessions, record, watchOwnerSessionIds);
6680
- const report = executeReloadFanOut(decisions, deps.runCommand, canRestartVia(deps));
6755
+ const report = executeReloadFanOut(decisions, deps.runCommand, canRestartVia(deps), pinSessionNameVia(deps, claudeSessions));
6681
6756
  return { report, pendingReload: advancePendingReload(record, report), events: reloadEvents(report, at) };
6682
6757
  }
6683
6758
  function runRemoteControlPass(login, claudeSessions, restartsBySession, maxRestarts, deps, at) {
6684
6759
  const plan = planRemoteControlRestarts(claudeSessions, login, restartsBySession, deps.nowMs, maxRestarts);
6685
- const executed = executeReloadFanOut(plan.decisions, deps.runCommand, canRestartVia(deps));
6760
+ const executed = executeReloadFanOut(plan.decisions, deps.runCommand, canRestartVia(deps), pinSessionNameVia(deps, claudeSessions));
6686
6761
  return {
6687
6762
  report: { ...executed, connectedSessionIds: plan.connectedSessionIds },
6688
6763
  restartsBySession: recordRemoteControlRestarts(restartsBySession, executed, deps.nowMs),
@@ -6698,14 +6773,26 @@ function waitForExitVia(deps) {
6698
6773
  function isSessionRunningVia(deps) {
6699
6774
  return (sessionId) => isSessionRunning(deps.runCommand, deps.paths.claudeSessionRegistryDir, sessionId) !== false;
6700
6775
  }
6701
- function resumeDepsOf(deps) {
6776
+ function pinSessionNameVia(deps, claudeSessions) {
6777
+ return (sessionId, shortId) => {
6778
+ const name = resolveSessionName({
6779
+ shortId,
6780
+ customTitle: readCustomTitle(deps.paths.claudeProjectsDir, sessionId),
6781
+ job: readClaudeJob(deps.paths.claudeJobsDir, shortId),
6782
+ registeredName: claudeSessions?.find((session) => session.sessionId === sessionId)?.name
6783
+ });
6784
+ if (name !== void 0) pinClaudeJobName(deps.paths.claudeJobsDir, shortId, name);
6785
+ };
6786
+ }
6787
+ function resumeDepsOf(deps, claudeSessions) {
6702
6788
  return {
6703
6789
  runCommand: deps.runCommand,
6704
6790
  claudeProjectsDir: deps.paths.claudeProjectsDir,
6705
6791
  nowMs: deps.nowMs,
6706
6792
  canRestartIn: canRestartVia(deps),
6707
6793
  waitForExit: waitForExitVia(deps),
6708
- isSessionRunning: isSessionRunningVia(deps)
6794
+ isSessionRunning: isSessionRunningVia(deps),
6795
+ pinSessionName: pinSessionNameVia(deps, claudeSessions)
6709
6796
  };
6710
6797
  }
6711
6798
  function runLimitResumePass(claudeSessions, attemptsBySession, maxContinues, deps) {
@@ -6722,7 +6809,7 @@ function runLimitResumePass(claudeSessions, attemptsBySession, maxContinues, dep
6722
6809
  isClaudeUsageLimitInForce(deps.paths.claudeGlobalConfigFile, deps.nowMs),
6723
6810
  maxContinues
6724
6811
  );
6725
- const executed = executeLimitResumes(planned, attemptsBySession, resumeDepsOf(deps));
6812
+ const executed = executeLimitResumes(planned, attemptsBySession, resumeDepsOf(deps, claudeSessions));
6726
6813
  return { report: { decisions: executed.decisions }, attemptsBySession: executed.attemptsBySession, events: executed.events };
6727
6814
  }
6728
6815
  function reloadTargetAtMs(deps, installs) {
@@ -6787,7 +6874,7 @@ function runTickUnlocked(deps) {
6787
6874
  sessionsById,
6788
6875
  isClaudeUsageLimitInForce(deps.paths.claudeGlobalConfigFile, deps.nowMs)
6789
6876
  );
6790
- const executed = executeWatcherRecoveries(planned, recoveryAttemptsByOwner, resumeDepsOf(deps));
6877
+ const executed = executeWatcherRecoveries(planned, recoveryAttemptsByOwner, resumeDepsOf(deps, claudeSessions));
6791
6878
  recoveries = executed.recoveries;
6792
6879
  recoveryAttemptsByOwner = executed.attemptsByOwner;
6793
6880
  events.push(...executed.events);
package/dist/cli.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { runCli } from './chunk-FVG7CYB7.js';
2
+ import { runCli } from './chunk-CGKARU5D.js';
3
3
  import './chunk-NTEBYUCA.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-FVG7CYB7.js';
1
+ export { src_exports as commands, createUnifiedMcpServer, server_exports as server } from './chunk-CGKARU5D.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-NTEBYUCA.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.277",
4
+ "version": "5.31.0-staging.278",
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.277",
7
+ "@muggleai/works@5.31.0-staging.278",
8
8
  "serve"
9
9
  ],
10
10
  "env": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "muggle",
3
- "version": "5.31.0-staging.277",
3
+ "version": "5.31.0-staging.278",
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.277",
5
+ "version": "5.31.0-staging.278",
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.277",
7
+ "@muggleai/works@5.31.0-staging.278",
8
8
  "serve"
9
9
  ]
10
10
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "release": "5.26.0",
3
- "buildId": "run-277-1",
4
- "commitSha": "fea69b3dd04919c685e56559e6ceba0b6d57129e",
5
- "buildTime": "2026-11-01T19:01:29Z",
3
+ "buildId": "run-278-1",
4
+ "commitSha": "eb4ff94a05a224818f6e1e2c273fbf00e6b9b76c",
5
+ "buildTime": "2026-11-01T21:49:32Z",
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.277",
4
+ "version": "5.31.0-staging.278",
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.277",
4
+ "version": "5.31.0-staging.278",
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.277",
7
+ "@muggleai/works@5.31.0-staging.278",
8
8
  "serve"
9
9
  ],
10
10
  "env": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "muggle",
3
- "version": "5.31.0-staging.277",
3
+ "version": "5.31.0-staging.278",
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.277",
5
+ "version": "5.31.0-staging.278",
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.277",
7
+ "@muggleai/works@5.31.0-staging.278",
8
8
  "serve"
9
9
  ]
10
10
  }