@muggleai/works 5.31.0-staging.271 → 5.31.0-staging.273

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.273"
13
13
  }
14
14
  ]
15
15
  }
package/README.md CHANGED
@@ -273,11 +273,11 @@ so the result carries a viewUrl straight to the dashboard
273
273
 
274
274
  ## MCP tool reference
275
275
 
276
- 125 tools across authentication, projects, use cases, test cases, test states, scripts, workflows, local execution, reporting, secrets, billing, and administration. Call them directly from any MCP client to build your own pipeline.
276
+ 126 tools across authentication, projects, use cases, test cases, test states, scripts, workflows, local execution, reporting, secrets, billing, and administration. Call them directly from any MCP client to build your own pipeline.
277
277
 
278
278
  **Authentication** — `muggle-remote-auth-status`, `-login`, `-poll`, `-logout`, plus `-api-key-create`, `-list`, `-get`, `-revoke`.
279
279
 
280
- **Projects** — `muggle-remote-project-create`, `-list`, `-get`, `-update`, `-delete`, plus per-project rollups: `-test-results-summary-get`, `-test-runs-summary-get`, `-test-scripts-summary-get`.
280
+ **Projects** — `muggle-remote-project-create`, `-list`, `-get`, `-update`, `-delete`, plus per-project rollups: `-test-results-summary-get`, `-test-runs-summary-get`, `-test-scripts-summary-get`. Pass `primaryResolution`, `compatibleResolutions` and `runStrategy` (`primaryOnly`, the default, `primaryFirst` or `allAtOnce`) to `-create` or `-update` to verify every test case at more than one viewport; `muggle-remote-project-display-resolutions-get` reads a lane's primary, compatible and enabled resolutions and its run strategy.
281
281
 
282
282
  | Use cases | Purpose |
283
283
  | :-------- | :------ |
@@ -4703,7 +4703,7 @@ function resolveConnectionString(explicit) {
4703
4703
  return explicit;
4704
4704
  return process.env[ENV_CONNECTION_STRING] ?? process.env[ENV_VITE_CONNECTION_STRING] ?? void 0;
4705
4705
  }
4706
- function buildCommonProperties(serviceName, surface) {
4706
+ function buildCommonProperties(serviceName, surface, environment) {
4707
4707
  const manifest = readReleaseManifest2();
4708
4708
  const props = {
4709
4709
  source: "client",
@@ -4720,6 +4720,8 @@ function buildCommonProperties(serviceName, surface) {
4720
4720
  const userId = readUserId();
4721
4721
  if (userId !== void 0)
4722
4722
  props.userId = userId;
4723
+ if (environment !== void 0)
4724
+ props.environment = environment;
4723
4725
  return props;
4724
4726
  }
4725
4727
  function initTelemetry(options) {
@@ -4743,7 +4745,7 @@ function initTelemetry(options) {
4743
4745
  client.context.tags[client.context.keys.applicationVersion] = manifest.release;
4744
4746
  client.commonProperties = {
4745
4747
  ...client.commonProperties,
4746
- ...buildCommonProperties(options.serviceName, options.surface)
4748
+ ...buildCommonProperties(options.serviceName, options.surface, options.environment)
4747
4749
  };
4748
4750
  AppInsights.start();
4749
4751
  state = {
@@ -4862,6 +4864,12 @@ var Surface;
4862
4864
  Surface2["Skill"] = "skill";
4863
4865
  Surface2["Electron"] = "electron";
4864
4866
  })(Surface || (Surface = {}));
4867
+ var TelemetryEnvironment;
4868
+ (function(TelemetryEnvironment2) {
4869
+ TelemetryEnvironment2["Production"] = "production";
4870
+ TelemetryEnvironment2["Staging"] = "staging";
4871
+ TelemetryEnvironment2["Development"] = "development";
4872
+ })(TelemetryEnvironment || (TelemetryEnvironment = {}));
4865
4873
  var ServiceName;
4866
4874
  (function(ServiceName2) {
4867
4875
  ServiceName2["MuggleMcp"] = "muggle-mcp";
@@ -6388,6 +6396,55 @@ var FanOutChoiceInputSchema = z.enum(["followPolicy" /* FollowPolicy */, "primar
6388
6396
  "Optional: followPolicy (default) runs each test case's runSettings records per its lane's fan-out policy; primaryOnly runs only the primary record; allNow runs every record at once"
6389
6397
  );
6390
6398
 
6399
+ // packages/mcps/src/mcp/e2e/contracts/run-strategy-types.ts
6400
+ var RunStrategy = /* @__PURE__ */ ((RunStrategy2) => {
6401
+ RunStrategy2["PrimaryOnly"] = "primaryOnly";
6402
+ RunStrategy2["PrimaryFirst"] = "primaryFirst";
6403
+ RunStrategy2["AllAtOnce"] = "allAtOnce";
6404
+ return RunStrategy2;
6405
+ })(RunStrategy || {});
6406
+
6407
+ // packages/mcps/src/mcp/e2e/contracts/project-display-resolution-schemas.ts
6408
+ var ProjectResolutionSettingsInputSchema = z.object({
6409
+ primaryResolution: z.nativeEnum(DisplayResolution).optional().describe(
6410
+ 'Primary resolution: the viewport every test case in the project is generated and run at first, e.g. "R_1920x1080". Passed without compatibleResolutions, it also clears the compatible resolutions. Omit both to keep the current resolutions; a new project defaults to R_1920x1080 alone.'
6411
+ ),
6412
+ compatibleResolutions: z.array(z.nativeEnum(DisplayResolution)).optional().describe(
6413
+ 'Compatible resolutions: further viewports every test case also runs at, one run each, as runStrategy says, e.g. ["R_0390x0844"]. Replaces the whole list; [] clears it. Requires primaryResolution (read the current one with muggle-remote-project-display-resolutions-get) and must not include it; duplicates are dropped.'
6414
+ ),
6415
+ runStrategy: z.nativeEnum(RunStrategy).optional().describe(
6416
+ "Run strategy across the resolutions: primaryOnly runs the primary alone and reports the compatible resolutions as not run; primaryFirst runs the compatible resolutions only after the primary passes; allAtOnce runs every resolution at once. Omit to keep the current one; a project that never set one runs primaryOnly."
6417
+ )
6418
+ });
6419
+ var ProjectDisplayResolutionsGetInputSchema = z.object({
6420
+ projectId: MuggleEntityIdSchema.describe("Project ID (UUID)"),
6421
+ runEnvironmentType: RunEnvironmentSchema.optional().describe(
6422
+ "Lane to read: 'remote' (cloud runs, the default) or 'local' (developer localhost)."
6423
+ )
6424
+ });
6425
+
6426
+ // packages/mcps/src/mcp/e2e/contracts/project-resolution-settings-validation.ts
6427
+ function addProjectResolutionSettingsIssues(settings, refinementContext) {
6428
+ if (settings.compatibleResolutions === void 0) {
6429
+ return;
6430
+ }
6431
+ if (settings.primaryResolution === void 0) {
6432
+ refinementContext.addIssue({
6433
+ code: "custom",
6434
+ path: ["compatibleResolutions"],
6435
+ message: "compatibleResolutions requires primaryResolution: the project's resolutions are saved as one list, primary first. Pass the current primary (read it with muggle-remote-project-display-resolutions-get) or a new one."
6436
+ });
6437
+ return;
6438
+ }
6439
+ if (settings.compatibleResolutions.includes(settings.primaryResolution)) {
6440
+ refinementContext.addIssue({
6441
+ code: "custom",
6442
+ path: ["compatibleResolutions"],
6443
+ message: `compatibleResolutions must not include the primary resolution ${settings.primaryResolution}; it already runs as the primary.`
6444
+ });
6445
+ }
6446
+ }
6447
+
6391
6448
  // packages/mcps/src/mcp/e2e/contracts/secret-group-types.ts
6392
6449
  var SecretTypeEnum = /* @__PURE__ */ ((SecretTypeEnum2) => {
6393
6450
  SecretTypeEnum2["LOGIN_CREDENTIAL"] = "login_credential";
@@ -6574,7 +6631,7 @@ var ProjectCreateInputSchema = z.object({
6574
6631
  organizationId: IdSchema.optional().describe(
6575
6632
  "Organization (UUID) that owns the project; its runs are billed to that organization. Omit for a personal project. The caller must be an active member: list memberships with muggle-remote-organization-list."
6576
6633
  )
6577
- });
6634
+ }).extend(ProjectResolutionSettingsInputSchema.shape).superRefine(addProjectResolutionSettingsIssues);
6578
6635
  var ProjectGetInputSchema = z.object({
6579
6636
  projectId: IdSchema.describe("Project ID (UUID) to retrieve")
6580
6637
  });
@@ -6589,7 +6646,7 @@ var ProjectUpdateInputSchema = z.object({
6589
6646
  organizationId: IdSchema.optional().describe(
6590
6647
  "Move a personal project into this organization (UUID). One-way: an organization's project cannot move to another organization or back to personal. The caller must be an active member."
6591
6648
  )
6592
- });
6649
+ }).extend(ProjectResolutionSettingsInputSchema.shape).superRefine(addProjectResolutionSettingsIssues);
6593
6650
  var ProjectListInputSchema = PaginationInputSchema.extend({});
6594
6651
  var PrdFileUploadInputSchema = z.object({
6595
6652
  projectId: IdSchema.describe("Project ID (UUID) to associate the PRD file with"),
@@ -7517,6 +7574,57 @@ function mapOrganizationList(response) {
7517
7574
  };
7518
7575
  }
7519
7576
 
7577
+ // packages/mcps/src/mcp/tools/e2e/project-display-resolutions-constants.ts
7578
+ var KNOWN_DISPLAY_RESOLUTIONS = new Set(Object.values(DisplayResolution));
7579
+ var KNOWN_RUN_STRATEGIES = new Set(Object.values(RunStrategy));
7580
+ var DEFAULT_RUN_STRATEGY = "primaryOnly" /* PrimaryOnly */;
7581
+
7582
+ // packages/mcps/src/mcp/tools/e2e/project-display-resolutions-transform.ts
7583
+ function toProjectResolutionUpstreamFields(settings) {
7584
+ const upstreamFields = {};
7585
+ if (settings.primaryResolution !== void 0) {
7586
+ upstreamFields.displayOptions = [
7587
+ .../* @__PURE__ */ new Set([settings.primaryResolution, ...settings.compatibleResolutions ?? []])
7588
+ ];
7589
+ }
7590
+ if (settings.runStrategy !== void 0) {
7591
+ upstreamFields.fanOutPolicy = settings.runStrategy;
7592
+ }
7593
+ return upstreamFields;
7594
+ }
7595
+ function mapProjectDisplayResolutions(response, input) {
7596
+ const toolInput = input;
7597
+ const envelope = response.data;
7598
+ const displayResolutionAxis = envelope?.effectivePerAxis?.displayResolution ?? envelope?.projectTestSpec?.perAxis?.displayResolution;
7599
+ if (!displayResolutionAxis) {
7600
+ throw new GatewayError({
7601
+ code: "UPSTREAM_ERROR" /* UPSTREAM_ERROR */,
7602
+ message: "project test spec response carried no display resolutions: neither effectivePerAxis.displayResolution nor projectTestSpec.perAxis.displayResolution.",
7603
+ statusCode: response.statusCode
7604
+ });
7605
+ }
7606
+ const runEnvironmentType = toolInput.runEnvironmentType ?? "remote" /* Remote */;
7607
+ const runStrategy = toRunStrategy(envelope?.fanOutPolicy ?? envelope?.projectTestSpec?.fanOutPolicy);
7608
+ const [primaryOptionId, ...compatibleOptionIds] = displayResolutionAxis.default ?? [];
7609
+ return {
7610
+ projectId: toolInput.projectId,
7611
+ runEnvironmentType,
7612
+ primaryResolution: isKnownDisplayResolution(primaryOptionId) ? primaryOptionId : null,
7613
+ compatibleResolutions: toKnownDisplayResolutions(compatibleOptionIds),
7614
+ enabledResolutions: toKnownDisplayResolutions(displayResolutionAxis.enabledValues),
7615
+ runStrategy
7616
+ };
7617
+ }
7618
+ function isKnownDisplayResolution(optionId) {
7619
+ return optionId !== void 0 && KNOWN_DISPLAY_RESOLUTIONS.has(optionId);
7620
+ }
7621
+ function toKnownDisplayResolutions(optionIds) {
7622
+ return (optionIds ?? []).filter(isKnownDisplayResolution);
7623
+ }
7624
+ function toRunStrategy(fanOutPolicy) {
7625
+ return fanOutPolicy !== void 0 && KNOWN_RUN_STRATEGIES.has(fanOutPolicy) ? fanOutPolicy : DEFAULT_RUN_STRATEGY;
7626
+ }
7627
+
7520
7628
  // packages/mcps/src/mcp/tools/e2e/run-settings-list-transform.ts
7521
7629
  function mapRunSettingsList(response) {
7522
7630
  const envelope = response.data;
@@ -7726,7 +7834,7 @@ var getWorkflowTimeoutMs = () => getConfig().e2e.workflowTimeoutMs;
7726
7834
  var projectTools = [
7727
7835
  {
7728
7836
  name: "muggle-remote-project-create",
7729
- description: "Create an E2E acceptance testing project to organize browser tests for a web app. A project groups test scenarios (use cases), specific test steps (test cases), and replayable browser scripts (test scripts) for one application. Create a project first before generating or running any E2E tests. Pass organizationId to create it under an organization the caller belongs to; omit it for a personal project.",
7837
+ description: "Create an E2E acceptance testing project to organize browser tests for a web app. A project groups test scenarios (use cases), specific test steps (test cases), and replayable browser scripts (test scripts) for one application. Create a project first before generating or running any E2E tests. Pass organizationId to create it under an organization the caller belongs to; omit it for a personal project. Pass primaryResolution, compatibleResolutions and runStrategy to verify every test case at more than one viewport (e.g. desktop and phone); omit them for R_1920x1080 alone under primaryOnly.",
7730
7838
  inputSchema: ProjectCreateInputSchema,
7731
7839
  dashboardLink: linkToCreatedProject,
7732
7840
  mapToUpstream: (input) => {
@@ -7734,7 +7842,8 @@ var projectTools = [
7734
7842
  const body = {
7735
7843
  name: toolInput.projectName,
7736
7844
  description: toolInput.description,
7737
- url: toolInput.url
7845
+ url: toolInput.url,
7846
+ ...toProjectResolutionUpstreamFields(toolInput)
7738
7847
  };
7739
7848
  if (toolInput.organizationId !== void 0) body.organizationId = toolInput.organizationId;
7740
7849
  return {
@@ -7746,7 +7855,7 @@ var projectTools = [
7746
7855
  },
7747
7856
  {
7748
7857
  name: "muggle-remote-project-get",
7749
- description: "Get details of a specific project by ID.",
7858
+ description: "Get details of a specific project by ID. Its displayOptions is the resolution list last saved on the project, primary first; read the primary and compatible resolutions in effect, and the run strategy, with muggle-remote-project-display-resolutions-get.",
7750
7859
  inputSchema: ProjectGetInputSchema,
7751
7860
  dashboardLink: linkToInputProjectPage("project" /* Project */),
7752
7861
  mapToUpstream: (input) => {
@@ -7759,11 +7868,14 @@ var projectTools = [
7759
7868
  },
7760
7869
  {
7761
7870
  name: "muggle-remote-project-update",
7762
- description: "Update an existing project's details, or move a personal project into an organization the caller belongs to.",
7871
+ description: "Update an existing project's details, or move a personal project into an organization the caller belongs to. primaryResolution, compatibleResolutions and runStrategy change, on both lanes, the resolutions every test case is verified at and how its runs across them start. The resolutions save as one list: compatibleResolutions requires primaryResolution and replaces the whole compatible list, and primaryResolution alone clears it, so read the current ones with muggle-remote-project-display-resolutions-get and pass back those to keep. runStrategy alone leaves the resolutions unchanged.",
7763
7872
  inputSchema: ProjectUpdateInputSchema,
7764
7873
  mapToUpstream: (input) => {
7765
7874
  const toolInput = input;
7766
- const body = { id: toolInput.projectId };
7875
+ const body = {
7876
+ id: toolInput.projectId,
7877
+ ...toProjectResolutionUpstreamFields(toolInput)
7878
+ };
7767
7879
  if (toolInput.projectName !== void 0) body.name = toolInput.projectName;
7768
7880
  if (toolInput.description !== void 0) body.description = toolInput.description;
7769
7881
  if (toolInput.url !== void 0) body.url = toolInput.url;
@@ -7775,6 +7887,20 @@ var projectTools = [
7775
7887
  };
7776
7888
  }
7777
7889
  },
7890
+ {
7891
+ name: "muggle-remote-project-display-resolutions-get",
7892
+ description: "Get a project lane's resolutions and run strategy: the resolutions every test case that picks none of its own is verified at, one run each, and how those runs start. Response shape: { projectId, runEnvironmentType, primaryResolution, compatibleResolutions, enabledResolutions, runStrategy }. runStrategy is primaryOnly (the primary runs alone; compatible resolutions are reported as not run), primaryFirst (compatible resolutions run once the primary passes) or allAtOnce (every resolution runs at once); a lane that never set one reports primaryOnly. enabledResolutions are those a test case on the lane may pick. Change them with primaryResolution, compatibleResolutions and runStrategy on muggle-remote-project-update.",
7893
+ inputSchema: ProjectDisplayResolutionsGetInputSchema,
7894
+ mapToUpstream: (input) => {
7895
+ const toolInput = input;
7896
+ return {
7897
+ method: "GET",
7898
+ path: `${MUGGLE_TEST_PREFIX}/projects/${toolInput.projectId}/project-test-spec`,
7899
+ queryParams: { type: toolInput.runEnvironmentType ?? "remote" /* Remote */ }
7900
+ };
7901
+ },
7902
+ mapFromUpstream: mapProjectDisplayResolutions
7903
+ },
7778
7904
  {
7779
7905
  name: "muggle-remote-project-list",
7780
7906
  description: "List projects accessible to the authenticated user: the projects they own plus the projects shared with them. Returns up to 10 items per page by default (max 100). Response includes pagination metadata (totalCount, totalPages, hasMore) \u2014 check `hasMore` to decide whether to fetch additional pages.",
@@ -8058,7 +8184,7 @@ var testCaseTools = [
8058
8184
  },
8059
8185
  {
8060
8186
  name: "muggle-remote-fan-out-plan",
8061
- description: "Plan a test case's fan-out across its runSettings records in one lane, for a caller that runs the records itself, such as a local run; it starts nothing and writes nothing. Response shape: { testCaseId, laneType, fanOutPolicy, fanOutChoice, isPrimaryKnownGood, isSecondaryGatedOnPrimary, records: [{ runSettingsId, resolutionLabel, displayResolution, isPrimary, testScriptId }], notRunRunSettingsIds }. `records` is in start order, primary first: run records[0] first. When isSecondaryGatedOnPrimary is true, run the rest only if that run passed, and otherwise report each as skipped because the primary failed; when false, run them all. A record's testScriptId is its script on the lane, absent when it has none: replay that script, or generate with the record's runSettingsId and displayResolution. Empty records means the lane has no records for the test case. notRunRunSettingsIds lists the records primaryOnly leaves unrun. Omit fanOutChoice to follow the lane's policy.",
8187
+ description: "Plan a test case's fan-out across its runSettings records in one lane, for a caller that runs the records itself, such as a local run; it starts nothing and writes nothing. Response shape: { testCaseId, laneType, fanOutPolicy, fanOutChoice, isPrimaryKnownGood, isSecondaryGatedOnPrimary, records: [{ runSettingsId, resolutionLabel, displayResolution, isPrimary, testScriptId }], notRunRunSettingsIds }. `records` is in start order, primary first: run records[0] first. When isSecondaryGatedOnPrimary is true, run the rest only if that run passed, and otherwise report each as skipped because the primary failed; when false, run them all. A record's testScriptId is its script on the lane, absent when it has none: replay that script, or generate with the record's runSettingsId and displayResolution. Empty records means the lane has no records for the test case. notRunRunSettingsIds lists the records left unrun because fanOutChoice or the lane's fanOutPolicy is primaryOnly. Omit fanOutChoice to follow the lane's policy.",
8062
8188
  inputSchema: FanOutPlanInputSchema,
8063
8189
  mapToUpstream: (input) => {
8064
8190
  const toolInput = input;
@@ -10922,8 +11048,10 @@ __export(e2e_exports2, {
10922
11048
  PrdFileUploadInputSchema: () => PrdFileUploadInputSchema,
10923
11049
  ProjectCreateInputSchema: () => ProjectCreateInputSchema,
10924
11050
  ProjectDeleteInputSchema: () => ProjectDeleteInputSchema,
11051
+ ProjectDisplayResolutionsGetInputSchema: () => ProjectDisplayResolutionsGetInputSchema,
10925
11052
  ProjectGetInputSchema: () => ProjectGetInputSchema,
10926
11053
  ProjectListInputSchema: () => ProjectListInputSchema,
11054
+ ProjectResolutionSettingsInputSchema: () => ProjectResolutionSettingsInputSchema,
10927
11055
  ProjectTestResultsSummaryInputSchema: () => ProjectTestResultsSummaryInputSchema,
10928
11056
  ProjectTestRunsSummaryInputSchema: () => ProjectTestRunsSummaryInputSchema,
10929
11057
  ProjectTestScriptsSummaryInputSchema: () => ProjectTestScriptsSummaryInputSchema,
@@ -10940,6 +11068,7 @@ __export(e2e_exports2, {
10940
11068
  RunEnvironmentInputSchema: () => RunEnvironmentInputSchema,
10941
11069
  RunEnvironmentSchema: () => RunEnvironmentSchema,
10942
11070
  RunSettingsListInputSchema: () => RunSettingsListInputSchema,
11071
+ RunStrategy: () => RunStrategy,
10943
11072
  SecretCreateInputSchema: () => SecretCreateInputSchema,
10944
11073
  SecretDeleteInputSchema: () => SecretDeleteInputSchema,
10945
11074
  SecretFieldEnum: () => SecretFieldEnum,
@@ -11586,4 +11715,4 @@ function applyLlmSourceSelection(selection, dataDirOverride2) {
11586
11715
  return resolveLlmSourceStatus(dataDirOverride2);
11587
11716
  }
11588
11717
 
11589
- 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 };
11718
+ 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, TelemetryEnvironment, 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 };
@@ -1,4 +1,4 @@
1
- import { __export, getLogger, getConfig, createChildLogger, buildElectronAppReleaseAssetUrl, getActiveRuntimeTarget, getAuthService, hasApiKey, getElectronAppVersion, getElectronAppDir, getPlatformKey, reconcileProjectPreferences, getDataDir2, PREFERENCES_FILE_NAME, isFirstRun, writePreferences, DEFAULT_PREFERENCES, isElectronAppInstalled, getElectronAppChecksums, getChecksumForPlatform, getElectronAppSignedFromVersion, getReleaseSignerIdentityUri, verifyFileChecksum, resolveElectronAppExecutablePath, calculateFileChecksum, initTelemetry, Surface, ServiceName, track, EventName, getQaTools, getLocalQaTools, performLogout, assertDeviceCodeClientProvisioned, performLogin, getActiveElectronAppReleaseStream, buildToolInputJsonSchema, toolRequiresAuth, getCallerCredentials, parseElectronAppDirName, getElectronAppReleaseTagPrefix, hasShownDisclosure, markDisclosureShown, getBundledElectronAppVersion, getElectronAppVersionSource, getCredentialsFilePath, ElectronAppReleaseStream, buildElectronAppChecksumsUrl, __require, getDisclosureCopy, buildOnboardingPlan, applyOnboardingAnswers, needsOnboarding, buildLlmSourcePlan, describeLlmSourceStatus, resolveLlmSourceStatus, readRunView, RunViewError, renderRunViewText, writeRunViewHtml, openBrowserUrl, evaluateCheckoutFreshness, runGitCommand, BLOCKING_FRESHNESS_VERDICTS, claimServiceStack, BUSY_EXIT_CODE, checkServiceStack, releaseServiceStack, readServiceStackStatus, resolvePreferences, OnboardingBlanketChoice, PreferenceKey, LlmSource, applyLlmSourceSelection, describeStackMismatch, getDataDir, DEFAULT_LOCAL_LLM_BASE_URL, isValidLocalBaseUrl, local_exports } from './chunk-XUANXZ2O.js';
1
+ import { __export, getLogger, TelemetryEnvironment, getConfig, createChildLogger, buildElectronAppReleaseAssetUrl, getActiveRuntimeTarget, getAuthService, hasApiKey, getElectronAppVersion, getElectronAppDir, getPlatformKey, reconcileProjectPreferences, getDataDir2, PREFERENCES_FILE_NAME, isFirstRun, writePreferences, DEFAULT_PREFERENCES, isElectronAppInstalled, getElectronAppChecksums, getChecksumForPlatform, getElectronAppSignedFromVersion, getReleaseSignerIdentityUri, verifyFileChecksum, resolveElectronAppExecutablePath, calculateFileChecksum, initTelemetry, Surface, ServiceName, track, EventName, getQaTools, getLocalQaTools, performLogout, assertDeviceCodeClientProvisioned, performLogin, getActiveElectronAppReleaseStream, buildToolInputJsonSchema, toolRequiresAuth, getCallerCredentials, parseElectronAppDirName, getElectronAppReleaseTagPrefix, hasShownDisclosure, markDisclosureShown, getBundledElectronAppVersion, getElectronAppVersionSource, getCredentialsFilePath, ElectronAppReleaseStream, buildElectronAppChecksumsUrl, __require, getDisclosureCopy, buildOnboardingPlan, applyOnboardingAnswers, needsOnboarding, buildLlmSourcePlan, describeLlmSourceStatus, resolveLlmSourceStatus, readRunView, RunViewError, renderRunViewText, writeRunViewHtml, openBrowserUrl, evaluateCheckoutFreshness, runGitCommand, BLOCKING_FRESHNESS_VERDICTS, claimServiceStack, BUSY_EXIT_CODE, checkServiceStack, releaseServiceStack, readServiceStackStatus, resolvePreferences, OnboardingBlanketChoice, PreferenceKey, LlmSource, applyLlmSourceSelection, describeStackMismatch, getDataDir, DEFAULT_LOCAL_LLM_BASE_URL, isValidLocalBaseUrl, local_exports } from './chunk-7J7DS4SA.js';
2
2
  import { Server } from '@modelcontextprotocol/sdk/server/index.js';
3
3
  import { ListToolsRequestSchema, CallToolRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema } from '@modelcontextprotocol/sdk/types.js';
4
4
  import { v4 } from 'uuid';
@@ -1165,7 +1165,7 @@ async function resolveGsScreenshotUrls(report, opts) {
1165
1165
  if (gsUrls.length === 0) {
1166
1166
  return report;
1167
1167
  }
1168
- const mcps = await import('./src-J6AIYTLA.js');
1168
+ const mcps = await import('./src-QA7TWQ2M.js');
1169
1169
  const credentials = await mcps.getCallerCredentialsAsync();
1170
1170
  if (!credentials.bearerToken && !credentials.apiKey) {
1171
1171
  stderrWrite(
@@ -3979,6 +3979,13 @@ function resolveDisclosureCopy() {
3979
3979
  return `${upstream.slice(0, markerIndex)}${DISCLOSURE_OPT_OUT_COPY}`;
3980
3980
  }
3981
3981
 
3982
+ // src/cli/telemetry-environment-constants.ts
3983
+ var TELEMETRY_ENVIRONMENT_BY_RUNTIME_TARGET = {
3984
+ ["production" /* Production */]: TelemetryEnvironment.Production,
3985
+ ["staging" /* Staging */]: TelemetryEnvironment.Staging,
3986
+ ["dev" /* Dev */]: TelemetryEnvironment.Development
3987
+ };
3988
+
3982
3989
  // src/cli/serve.ts
3983
3990
  var APPLICATIONINSIGHTS_CONNECTION_STRING = "InstrumentationKey=0e28df42-b98e-4089-b3b5-6decefb99447;IngestionEndpoint=https://westus3-1.in.applicationinsights.azure.com/;LiveEndpoint=https://westus3.livediagnostics.monitor.azure.com/;ApplicationId=b64a932f-85ec-40cd-a74b-e181a3660f6c";
3984
3991
  function showDisclosureIfNeeded() {
@@ -4005,7 +4012,8 @@ async function serveCommand(options) {
4005
4012
  initTelemetry({
4006
4013
  serviceName: ServiceName.MuggleMcp,
4007
4014
  surface: Surface.McpLocal,
4008
- connectionString: APPLICATIONINSIGHTS_CONNECTION_STRING
4015
+ connectionString: APPLICATIONINSIGHTS_CONNECTION_STRING,
4016
+ environment: TELEMETRY_ENVIRONMENT_BY_RUNTIME_TARGET[getActiveRuntimeTarget()]
4009
4017
  });
4010
4018
  track({ name: EventName.SystemStartup, props: { serviceName: ServiceName.MuggleMcp } });
4011
4019
  } catch (err) {
@@ -5012,11 +5020,25 @@ function readRegistryBySessionId(registryDir) {
5012
5020
  }
5013
5021
  return entriesBySessionId;
5014
5022
  }
5023
+ function isPidAlive(pid) {
5024
+ try {
5025
+ process.kill(pid, 0);
5026
+ return true;
5027
+ } catch (error) {
5028
+ return error.code === "EPERM";
5029
+ }
5030
+ }
5031
+ function isSessionRunning(runCommand, registryDir, sessionId) {
5032
+ const registryEntry = readRegistryBySessionId(registryDir).get(sessionId);
5033
+ if (registryEntry !== void 0 && isPidAlive(registryEntry.pid)) return true;
5034
+ const sessions = readClaudeSessions(runCommand, registryDir);
5035
+ if (sessions === null) return null;
5036
+ const session = sessions.find((listed) => listed.sessionId === sessionId);
5037
+ return session !== void 0 && hasLiveProcess(session);
5038
+ }
5015
5039
  function waitForSessionExit(runCommand, registryDir, sessionId, sleep2 = blockFor) {
5016
5040
  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;
5041
+ if (isSessionRunning(runCommand, registryDir, sessionId) === false) return true;
5020
5042
  if (waitedMs >= SESSION_EXIT_WAIT_MS) return false;
5021
5043
  sleep2(SESSION_EXIT_POLL_MS);
5022
5044
  }
@@ -5340,13 +5362,22 @@ function startServiceLoops(assessments, nowMs, isPollingDisabled, ownerSessionOf
5340
5362
  }
5341
5363
 
5342
5364
  // src/watch/session-restart.ts
5343
- function resumeSession(target, runCommand, waitForExit) {
5365
+ function resumeSession(target, runCommand, waitForExit, isSessionRunning2) {
5366
+ const runningSessionId = target.runningSessionId ?? target.sessionId;
5344
5367
  if (target.runningShortId !== void 0) {
5345
5368
  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;
5369
+ if (!waitForExit(runningSessionId)) return { outcome: "still-running" /* StillRunning */ };
5370
+ } else if (isSessionRunning2(runningSessionId)) {
5371
+ return { outcome: "still-running" /* StillRunning */ };
5372
+ }
5373
+ const resumed = runCommand("claude", ["--resume", target.sessionId, "--bg", target.prompt], { cwd: target.cwd, isStderrCaptured: true });
5374
+ if (resumed?.exitCode !== 0) return { outcome: "failed" /* Failed */ };
5375
+ const backgroundedShortId = resumed.stdout.match(BACKGROUNDED_SESSION_ID_PATTERN)?.[1];
5376
+ const ownShortIds = [shortSessionId(target.sessionId), shortSessionId(runningSessionId), target.runningShortId];
5377
+ if (backgroundedShortId === void 0 || ownShortIds.includes(backgroundedShortId)) return { outcome: "continued" /* Continued */ };
5378
+ const isCopyStopped = runCommand("claude", ["stop", backgroundedShortId])?.exitCode === 0;
5379
+ const isCopyRemoved = isCopyStopped && runCommand("claude", ["rm", backgroundedShortId])?.exitCode === 0;
5380
+ return { outcome: "copied" /* Copied */, copyShortId: backgroundedShortId, isCopyRemoved };
5350
5381
  }
5351
5382
 
5352
5383
  // src/watch/recovery.ts
@@ -5427,20 +5458,31 @@ function executeWatcherRecoveries(planned, attemptsByOwner, deps) {
5427
5458
  });
5428
5459
  return { ...recovery, cwd, outcome: "restart-refused" /* RestartRefused */ };
5429
5460
  }
5430
- const isResumed = resumeSession(
5461
+ const resumed = resumeSession(
5431
5462
  { sessionId: recovery.ownerSessionId, cwd, prompt, runningShortId: recovery.ownerShortId, runningSessionId: recovery.ownerRunningSessionId },
5432
5463
  deps.runCommand,
5433
- deps.waitForExit
5464
+ deps.waitForExit,
5465
+ deps.isSessionRunning
5434
5466
  );
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 */ };
5467
+ if (resumed.outcome === "continued" /* Continued */) {
5468
+ markServiceLoopReportDelivered(recovery.slotDir);
5469
+ events.push({ at: nowIso, kind: "resumed" /* Resumed */, subject, message: `woke it with ${prompt}` });
5470
+ return { ...recovery, cwd, outcome: "resumed" /* Resumed */ };
5471
+ }
5472
+ countAttempt(recovery.ownerSessionId);
5473
+ if (resumed.outcome === "copied" /* Copied */) {
5474
+ const copyNote = resumed.isCopyRemoved ? "stopped and removed the copy" : `could not remove the copy, run claude rm ${resumed.copyShortId}`;
5475
+ events.push({
5476
+ at: nowIso,
5477
+ kind: "resume-forked" /* ResumeForked */,
5478
+ subject,
5479
+ message: `could not wake it: Claude still ran it and started copy ${resumed.copyShortId} instead; ${copyNote}`
5480
+ });
5481
+ return { ...recovery, cwd, outcome: "resume-forked" /* ResumeForked */ };
5482
+ }
5483
+ const reason = resumed.outcome === "still-running" /* StillRunning */ ? "it is still running, so a resume would start a copy" : `${prompt} did not start`;
5484
+ events.push({ at: nowIso, kind: "resume-failed" /* ResumeFailed */, subject, message: `could not wake it: ${reason}` });
5485
+ return { ...recovery, cwd, outcome: "resume-failed" /* ResumeFailed */ };
5444
5486
  });
5445
5487
  return { recoveries, attemptsByOwner: nextAttemptsByOwner, events };
5446
5488
  }
@@ -6440,7 +6482,7 @@ function idleSinceBySessionId(runningSessions) {
6440
6482
  );
6441
6483
  }
6442
6484
  function isRestartAttempted(recovery) {
6443
- return recovery.outcome === "resumed" /* Resumed */ || recovery.outcome === "resume-failed" /* ResumeFailed */;
6485
+ return recovery.outcome === "resumed" /* Resumed */ || recovery.outcome === "resume-failed" /* ResumeFailed */ || recovery.outcome === "resume-forked" /* ResumeForked */;
6444
6486
  }
6445
6487
  function watchOwnersOf(slots) {
6446
6488
  return new Set(slots.flatMap((slot) => !isTerminalPrState(slot.live?.state) && slot.ownerSessionId ? [slot.ownerSessionId] : []));
@@ -6484,6 +6526,9 @@ function canRestartVia(deps) {
6484
6526
  function waitForExitVia(deps) {
6485
6527
  return (sessionId) => waitForSessionExit(deps.runCommand, deps.paths.claudeSessionRegistryDir, sessionId, deps.sleep);
6486
6528
  }
6529
+ function isSessionRunningVia(deps) {
6530
+ return (sessionId) => isSessionRunning(deps.runCommand, deps.paths.claudeSessionRegistryDir, sessionId) !== false;
6531
+ }
6487
6532
  function reloadTargetAtMs(deps, installs) {
6488
6533
  return resolveReloadTargetAtMs(installs, readMugglePluginUpdatedAtMs(deps.paths.claudeInstalledPluginsFile));
6489
6534
  }
@@ -6551,7 +6596,8 @@ function runTickUnlocked(deps) {
6551
6596
  claudeProjectsDir: deps.paths.claudeProjectsDir,
6552
6597
  nowMs: deps.nowMs,
6553
6598
  canRestartIn: canRestartVia(deps),
6554
- waitForExit: waitForExitVia(deps)
6599
+ waitForExit: waitForExitVia(deps),
6600
+ isSessionRunning: isSessionRunningVia(deps)
6555
6601
  });
6556
6602
  recoveries = executed.recoveries;
6557
6603
  recoveryAttemptsByOwner = executed.attemptsByOwner;
package/dist/cli.js CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
- import { runCli } from './chunk-RQ43IPVN.js';
3
- import './chunk-XUANXZ2O.js';
2
+ import { runCli } from './chunk-7JQWRPFI.js';
3
+ import './chunk-7J7DS4SA.js';
4
4
 
5
5
  // src/cli/main.ts
6
6
  runCli().catch((error) => {
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
- export { src_exports as commands, createUnifiedMcpServer, server_exports as server } from './chunk-RQ43IPVN.js';
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
+ export { src_exports as commands, createUnifiedMcpServer, server_exports as server } from './chunk-7JQWRPFI.js';
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-7J7DS4SA.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.273",
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.273",
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.273",
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.273",
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.273",
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-273-1",
4
+ "commitSha": "79c091d63782313be3672ba64db5ae65c44e2c2b",
5
+ "buildTime": "2026-11-01T07:21:17Z",
6
6
  "serviceName": "muggle-ai-works-mcp"
7
7
  }
@@ -1 +1 @@
1
- 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, 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, StackCheckVerdict, StackClaimVerdict, StackLeaseEndReason, StackMismatchKind, StackPortAction, WATCHER_LIFETIME_SECONDS, WATCHER_LIFETIME_UNBOUNDED_SECONDS, 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_exports as e2e, evaluateCheckoutFreshness, findVacatedNeeds, finishStackRun, formatPreferencesOneLiner, getActiveElectronAppReleaseStream, getActiveRuntimeTarget, getApiKey, getApiKeyFilePath, getAuthService, getBundledElectronAppVersion, getCallerCredentials, getCallerCredentialsAsync, getChecksumForPlatform, getConfig, getCredentialsFilePath, getDashboardBaseUrl, getDataDir2 as getDataDir, getDownloadBaseUrl, getElectronAppChecksums, getElectronAppDir, getElectronAppReleaseTagPrefix, getElectronAppSignedFromVersion, getElectronAppVersion, getElectronAppVersionSource, getLlmSourceOption, getLocalQaTools, getLogger, getOnboardingToggleKeys, getPlatformKey, getQaTools, getReleaseSignerIdentityUri, getRunSessionsDir, getValidApiKeyData, getValidCredentials, hasApiKey, isElectronAppInstalled, isElectronAppPlatform, isFirstRun, isValidLocalBaseUrl, loadApiKeyData, loadCredentials, local_exports as localQa, lookUpCheckoutPullRequest, markOnboardingCompleted, maskLlmApiKey, mcp_exports as mcp, needsConflict, needsOnboarding, openBrowserUrl, parseElectronAppDirName, performLogin, performLogout, pollDeviceCode, portOfUrl, e2e_exports as qa, 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, startDeviceCodeFlow, startStackRun, stopVacatedServices, toDisplayRef, toolRequiresAuth, validatePreference, verifyFileChecksum, writeLlmEnvBlock, writePreferences, writePreferencesWithMetadata, writeRunViewHtml } from './chunk-XUANXZ2O.js';
1
+ 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, 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, StackCheckVerdict, StackClaimVerdict, StackLeaseEndReason, StackMismatchKind, StackPortAction, WATCHER_LIFETIME_SECONDS, WATCHER_LIFETIME_UNBOUNDED_SECONDS, 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_exports as e2e, evaluateCheckoutFreshness, findVacatedNeeds, finishStackRun, formatPreferencesOneLiner, getActiveElectronAppReleaseStream, getActiveRuntimeTarget, getApiKey, getApiKeyFilePath, getAuthService, getBundledElectronAppVersion, getCallerCredentials, getCallerCredentialsAsync, getChecksumForPlatform, getConfig, getCredentialsFilePath, getDashboardBaseUrl, getDataDir2 as getDataDir, getDownloadBaseUrl, getElectronAppChecksums, getElectronAppDir, getElectronAppReleaseTagPrefix, getElectronAppSignedFromVersion, getElectronAppVersion, getElectronAppVersionSource, getLlmSourceOption, getLocalQaTools, getLogger, getOnboardingToggleKeys, getPlatformKey, getQaTools, getReleaseSignerIdentityUri, getRunSessionsDir, getValidApiKeyData, getValidCredentials, hasApiKey, isElectronAppInstalled, isElectronAppPlatform, isFirstRun, isValidLocalBaseUrl, loadApiKeyData, loadCredentials, local_exports as localQa, lookUpCheckoutPullRequest, markOnboardingCompleted, maskLlmApiKey, mcp_exports as mcp, needsConflict, needsOnboarding, openBrowserUrl, parseElectronAppDirName, performLogin, performLogout, pollDeviceCode, portOfUrl, e2e_exports as qa, 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, startDeviceCodeFlow, startStackRun, stopVacatedServices, toDisplayRef, toolRequiresAuth, validatePreference, verifyFileChecksum, writeLlmEnvBlock, writePreferences, writePreferencesWithMetadata, writeRunViewHtml } from './chunk-7J7DS4SA.js';
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.273",
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.273",
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.273",
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.273",
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.273",
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.273",
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`.