@muggleai/works 5.28.0-staging.249 → 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.
@@ -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.28.0-staging.249"
12
+ "version": "5.28.0-staging.250"
13
13
  }
14
14
  ]
15
15
  }
@@ -5,7 +5,7 @@ import { v4 } from 'uuid';
5
5
  import { z, ZodError } from 'zod';
6
6
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
7
7
  import * as fs from 'fs';
8
- import { readFileSync, existsSync, rmSync, mkdirSync, readdirSync, createWriteStream, writeFileSync, statSync, readlinkSync, realpathSync, openSync, writeSync, closeSync, unlinkSync, copyFileSync, renameSync, appendFileSync, readSync, fstatSync } from 'fs';
8
+ import { readFileSync, existsSync, rmSync, mkdirSync, readdirSync, createWriteStream, writeFileSync, statSync, readlinkSync, realpathSync, openSync, writeSync, closeSync, unlinkSync, copyFileSync, renameSync, appendFileSync, readSync } from 'fs';
9
9
  import * as path from 'path';
10
10
  import { dirname, resolve, join, basename } from 'path';
11
11
  import { fileURLToPath, pathToFileURL } from 'url';
@@ -2400,8 +2400,6 @@ var TERMINAL_PR_RECHECK_MS = 6 * 60 * 6e4;
2400
2400
  var WATCH_COMMAND_TIMEOUT_MS = 3e4;
2401
2401
  var WATCH_COMMAND_MAX_BUFFER_BYTES = 64 * 1024 * 1024;
2402
2402
  var SESSION_FILE_HEAD_BYTES = 64 * 1024;
2403
- var SESSION_FILE_TAIL_BYTES = 64 * 1024;
2404
- var USAGE_LIMIT_NOTICE_PATTERN = /\bhit your (?:session|usage|weekly|daily) limit\b|\busage limit reached\b/i;
2405
2403
  var WATCH_LOCK_FILE = "watch.lock";
2406
2404
  var WATCH_LOCK_WAIT_MS = 6e4;
2407
2405
  var PR_WATCH_SLOTS_RELATIVE_PATH = ["muggle-do", "sessions"];
@@ -2434,6 +2432,8 @@ var PR_WATCH_LOOP_PATTERN = /pr-watch-loop\.sh\b.*?--slot\s+(?:"([^"]+)"|(\S+))/
2434
2432
  var CLAUDE_SESSION_REGISTRY_DIR_NAME = "sessions";
2435
2433
  var CLAUDE_PROJECTS_DIR_NAME = "projects";
2436
2434
  var CLAUDE_INSTALLED_PLUGINS_RELATIVE_PATH = ["plugins", "installed_plugins.json"];
2435
+ var CLAUDE_GLOBAL_CONFIG_FILE_NAME = ".claude.json";
2436
+ var USAGE_WINDOW_EXHAUSTED_PERCENT = 100;
2437
2437
  var RELOAD_PLUGIN_SCOPE = "user";
2438
2438
  var MUGGLE_PLUGIN_ID = "muggleai@muggle-works";
2439
2439
  var CODEX_ROLLOUT_PREFIX = "rollout-";
@@ -2487,6 +2487,7 @@ function resolveWatchPaths(env = process.env) {
2487
2487
  claudeSessionRegistryDir: join(claudeConfigDir, CLAUDE_SESSION_REGISTRY_DIR_NAME),
2488
2488
  claudeProjectsDir: join(claudeConfigDir, CLAUDE_PROJECTS_DIR_NAME),
2489
2489
  claudeInstalledPluginsFile: join(claudeConfigDir, ...CLAUDE_INSTALLED_PLUGINS_RELATIVE_PATH),
2490
+ claudeGlobalConfigFile: join(env.CLAUDE_CONFIG_DIR || homedir(), CLAUDE_GLOBAL_CONFIG_FILE_NAME),
2490
2491
  codexSessionsDir: join(codexHome, "sessions"),
2491
2492
  muggleInstallLogFile: join(muggleDataDir, MUGGLE_INSTALL_LOG_FILE)
2492
2493
  };
@@ -4638,21 +4639,6 @@ function readFileHead(filePath) {
4638
4639
  if (fileDescriptor !== void 0) closeSync(fileDescriptor);
4639
4640
  }
4640
4641
  }
4641
- function readFileTail(filePath) {
4642
- let fileDescriptor;
4643
- try {
4644
- fileDescriptor = openSync(filePath, "r");
4645
- const fileSize = fstatSync(fileDescriptor).size;
4646
- const length = Math.min(fileSize, SESSION_FILE_TAIL_BYTES);
4647
- const buffer = Buffer.alloc(length);
4648
- const bytesRead = readSync(fileDescriptor, buffer, 0, length, fileSize - length);
4649
- return buffer.subarray(0, bytesRead).toString("utf-8");
4650
- } catch {
4651
- return void 0;
4652
- } finally {
4653
- if (fileDescriptor !== void 0) closeSync(fileDescriptor);
4654
- }
4655
- }
4656
4642
  function cwdFromSessionHead(head) {
4657
4643
  const parts = head.match(/"cwd"\s*:\s*("(?:[^"\\]|\\.)*")/);
4658
4644
  if (!parts) return void 0;
@@ -4747,36 +4733,12 @@ function findTranscriptPath(projectsDir, sessionId) {
4747
4733
  }
4748
4734
  return void 0;
4749
4735
  }
4750
- function assistantText(entry) {
4751
- if (entry.type !== "assistant") return void 0;
4752
- const content = entry.message?.content;
4753
- if (typeof content === "string") return content;
4754
- if (!Array.isArray(content)) return "";
4755
- return content.map((part) => typeof part?.text === "string" ? part.text : "").join(" ");
4756
- }
4757
4736
  function findClaudeSessionCwd(projectsDir, sessionId) {
4758
4737
  const transcriptPath = findTranscriptPath(projectsDir, sessionId);
4759
4738
  if (transcriptPath === void 0) return void 0;
4760
4739
  const head = readFileHead(transcriptPath);
4761
4740
  return head === void 0 ? void 0 : cwdFromSessionHead(head);
4762
4741
  }
4763
- function isStoppedAtUsageLimit(projectsDir, sessionId) {
4764
- const transcriptPath = findTranscriptPath(projectsDir, sessionId);
4765
- const tail = transcriptPath === void 0 ? void 0 : readFileTail(transcriptPath);
4766
- if (tail === void 0) return false;
4767
- const lines = tail.split("\n");
4768
- for (let index = lines.length - 1; index >= 0; index--) {
4769
- let entry;
4770
- try {
4771
- entry = JSON.parse(lines[index]);
4772
- } catch {
4773
- continue;
4774
- }
4775
- const text = assistantText(entry);
4776
- if (text !== void 0) return USAGE_LIMIT_NOTICE_PATTERN.test(text);
4777
- }
4778
- return false;
4779
- }
4780
4742
  var ROLLOUT_SESSION_ID = /([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\.jsonl$/i;
4781
4743
  function sortedChildDirsDescending(dir) {
4782
4744
  try {
@@ -5516,7 +5478,7 @@ function findRunningOwner(sessionsById, ownerSessionId) {
5516
5478
  const ownerShortId = shortSessionId(ownerSessionId);
5517
5479
  return sessionsById.get(ownerSessionId) ?? [...sessionsById.values()].find((session) => session.shortId === ownerShortId);
5518
5480
  }
5519
- function planWatcherRecoveries(assessments, attemptsByOwner, nowMs, isPollingDisabled, sessionsById = /* @__PURE__ */ new Map(), isStoppedAtUsageLimit2 = () => false) {
5481
+ function planWatcherRecoveries(assessments, attemptsByOwner, nowMs, isPollingDisabled, sessionsById = /* @__PURE__ */ new Map(), isUsageLimitInForce = false) {
5520
5482
  if (isPollingDisabled) return [];
5521
5483
  const slugsByOwner = /* @__PURE__ */ new Map();
5522
5484
  for (const assessment of assessments) {
@@ -5533,9 +5495,8 @@ function planWatcherRecoveries(assessments, attemptsByOwner, nowMs, isPollingDis
5533
5495
  const recentAttemptsMs = (attemptsByOwner[ownerSessionId] ?? []).map((attemptedAt) => Date.parse(attemptedAt)).filter((attemptedAtMs) => !Number.isNaN(attemptedAtMs) && nowMs - attemptedAtMs < RECOVERY_ATTEMPT_WINDOW_MS);
5534
5496
  const lastAttemptMs = Math.max(-Infinity, ...recentAttemptsMs);
5535
5497
  const runningOwner = findRunningOwner(sessionsById, ownerSessionId);
5536
- const isRunningAtUsageLimit = runningOwner !== void 0 && [ownerSessionId, runningOwner.sessionId].some((sessionId) => isStoppedAtUsageLimit2(sessionId));
5537
5498
  let outcome = "resume-due" /* ResumeDue */;
5538
- if (isRunningAtUsageLimit) outcome = "awaiting-usage-limit-reset" /* AwaitingUsageLimitReset */;
5499
+ if (isUsageLimitInForce) outcome = "awaiting-usage-limit-reset" /* AwaitingUsageLimitReset */;
5539
5500
  else if (recentAttemptsMs.length >= RECOVERY_MAX_ATTEMPTS) outcome = "attempts-exhausted" /* AttemptsExhausted */;
5540
5501
  else if (nowMs - lastAttemptMs < RECOVERY_COOLDOWN_MS) outcome = "cooling-down" /* CoolingDown */;
5541
5502
  else if (resumesPlanned >= MAX_RESUMES_PER_TICK) outcome = "deferred-by-tick-cap" /* DeferredByTickCap */;
@@ -5795,6 +5756,24 @@ function classifyTrackedProcesses(table, platform4) {
5795
5756
  }
5796
5757
  return tracked;
5797
5758
  }
5759
+ function isUsageWindowExhausted(window, nowMs) {
5760
+ if (typeof window !== "object" || window === null) return false;
5761
+ const { utilization, resets_at: resetsAt, locked_reason: lockedReason } = window;
5762
+ const resetsAtMs = typeof resetsAt === "string" ? Date.parse(resetsAt) : Number.NaN;
5763
+ if (Number.isNaN(resetsAtMs) || resetsAtMs <= nowMs) return false;
5764
+ const isUtilizationExhausted = typeof utilization === "number" && utilization >= USAGE_WINDOW_EXHAUSTED_PERCENT;
5765
+ return isUtilizationExhausted || lockedReason !== null && lockedReason !== void 0;
5766
+ }
5767
+ function isClaudeUsageLimitInForce(claudeGlobalConfigFile, nowMs) {
5768
+ let usageWindows;
5769
+ try {
5770
+ const config = JSON.parse(readFileSync(claudeGlobalConfigFile, "utf-8"));
5771
+ usageWindows = config.cachedUsageUtilization?.utilization ?? {};
5772
+ } catch {
5773
+ return false;
5774
+ }
5775
+ return Object.values(usageWindows).some((usageWindow) => isUsageWindowExhausted(usageWindow, nowMs));
5776
+ }
5798
5777
  function readJsonFile(filePath) {
5799
5778
  try {
5800
5779
  return JSON.parse(readFileSync(filePath, "utf-8"));
@@ -6055,7 +6034,7 @@ function runTickUnlocked(deps) {
6055
6034
  deps.nowMs,
6056
6035
  isPollingDisabled,
6057
6036
  new Map(runningSessions.map((session) => [session.sessionId, session])),
6058
- (sessionId) => isStoppedAtUsageLimit(deps.paths.claudeProjectsDir, sessionId)
6037
+ isClaudeUsageLimitInForce(deps.paths.claudeGlobalConfigFile, deps.nowMs)
6059
6038
  );
6060
6039
  const executed = executeWatcherRecoveries(planned, recoveryAttemptsByOwner, {
6061
6040
  runCommand: deps.runCommand,
package/dist/cli.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { runCli } from './chunk-USALT4LJ.js';
2
+ import { runCli } from './chunk-KGNRBL52.js';
3
3
  import './chunk-HBCASFGN.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-USALT4LJ.js';
1
+ export { src_exports as commands, createUnifiedMcpServer, server_exports as server } from './chunk-KGNRBL52.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-HBCASFGN.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.28.0-staging.249",
4
+ "version": "5.28.0-staging.250",
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.28.0-staging.249",
7
+ "@muggleai/works@5.28.0-staging.250",
8
8
  "serve"
9
9
  ],
10
10
  "env": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "muggle",
3
- "version": "5.28.0-staging.249",
3
+ "version": "5.28.0-staging.250",
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.28.0-staging.249",
5
+ "version": "5.28.0-staging.250",
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.28.0-staging.249",
7
+ "@muggleai/works@5.28.0-staging.250",
8
8
  "serve"
9
9
  ]
10
10
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "release": "5.26.0",
3
- "buildId": "run-249-1",
4
- "commitSha": "82c1359671b126a641e46f27fc99a83313d48826",
5
- "buildTime": "2026-10-29T23:11:54Z",
3
+ "buildId": "run-250-1",
4
+ "commitSha": "ba9d56238c918b107aabc3037ceab011d08e3e85",
5
+ "buildTime": "2026-10-29T23:38:01Z",
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.28.0-staging.249",
4
+ "version": "5.28.0-staging.250",
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.28.0-staging.249",
4
+ "version": "5.28.0-staging.250",
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.28.0-staging.249",
7
+ "@muggleai/works@5.28.0-staging.250",
8
8
  "serve"
9
9
  ],
10
10
  "env": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "muggle",
3
- "version": "5.28.0-staging.249",
3
+ "version": "5.28.0-staging.250",
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.28.0-staging.249",
5
+ "version": "5.28.0-staging.250",
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.28.0-staging.249",
7
+ "@muggleai/works@5.28.0-staging.250",
8
8
  "serve"
9
9
  ]
10
10
  }