@muggleai/works 5.31.0-staging.269 → 5.31.0-staging.271
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/README.md +8 -1
- package/dist/{chunk-J2DEXYH7.js → chunk-RQ43IPVN.js} +212 -27
- package/dist/{chunk-YLQSNSSM.js → chunk-XUANXZ2O.js} +271 -6
- package/dist/cli.js +2 -2
- package/dist/index.js +2 -2
- package/dist/plugin/.claude-plugin/plugin.json +1 -1
- package/dist/plugin/.codex-mcp.json +1 -1
- package/dist/plugin/.codex-plugin/plugin.json +1 -1
- package/dist/plugin/.cursor-plugin/plugin.json +1 -1
- package/dist/plugin/.mcp.json +1 -1
- package/dist/release-manifest.json +3 -3
- package/dist/{src-7OY7H63R.js → src-J6AIYTLA.js} +1 -1
- package/package.json +1 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/.codex-mcp.json +1 -1
- package/plugin/.codex-plugin/plugin.json +1 -1
- package/plugin/.cursor-plugin/plugin.json +1 -1
- package/plugin/.mcp.json +1 -1
- package/scripts/muggle-watch/README.md +5 -3
- package/scripts/muggle-watch/install-muggle-watch.ps1 +5 -2
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"name": "muggleai",
|
|
10
10
|
"source": "./plugin",
|
|
11
11
|
"description": "Real-browser E2E acceptance for your coding agent: test the flows you changed and open the PR with screenshots. Built for flows other tools can't reach: managed test identities with live inboxes (magic links, email OTP and 2FA, verification mail, password resets), an encrypted secret store for login and test credentials, and a test graph that runs prerequisite flows first. Plain-English tests become replayable scripts on localhost or preview URLs.",
|
|
12
|
-
"version": "5.31.0-staging.
|
|
12
|
+
"version": "5.31.0-staging.271"
|
|
13
13
|
}
|
|
14
14
|
]
|
|
15
15
|
}
|
package/README.md
CHANGED
|
@@ -273,7 +273,7 @@ so the result carries a viewUrl straight to the dashboard
|
|
|
273
273
|
|
|
274
274
|
## MCP tool reference
|
|
275
275
|
|
|
276
|
-
|
|
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.
|
|
277
277
|
|
|
278
278
|
**Authentication** — `muggle-remote-auth-status`, `-login`, `-poll`, `-logout`, plus `-api-key-create`, `-list`, `-get`, `-revoke`.
|
|
279
279
|
|
|
@@ -301,6 +301,13 @@ so the result carries a viewUrl straight to the dashboard
|
|
|
301
301
|
| `muggle-remote-test-case-bulk-preview-submit` | Async batch preview (~50% cheaper) |
|
|
302
302
|
| `muggle-remote-test-plan-graph-rebuild` | Rebuild the prerequisite graph |
|
|
303
303
|
|
|
304
|
+
| Test states | Purpose |
|
|
305
|
+
| :---------- | :------ |
|
|
306
|
+
| `muggle-remote-test-chaining-catalog-get` / `-settings-update` | Read a project's states and roles; set its fresh account set state and parallel account set limit |
|
|
307
|
+
| `muggle-remote-test-state-create` / `-update` / `-delete` | Manage states: facts about one account set's data, plus a check |
|
|
308
|
+
| `muggle-remote-test-role-create` / `-update` / `-delete` | Manage roles: created or supplied accounts a test acts as |
|
|
309
|
+
| `muggle-remote-test-chaining-plan-get` | Preview the account set plan without running anything |
|
|
310
|
+
|
|
304
311
|
Bulk-preview submissions return a `jobId` immediately. Poll `muggle-remote-bulk-preview-job-get` until terminal, then persist with the matching `-create` tool. `-list` and `-cancel` round out the set.
|
|
305
312
|
|
|
306
313
|
| Scripts and workflows | Purpose |
|
|
@@ -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-
|
|
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';
|
|
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-
|
|
1168
|
+
const mcps = await import('./src-J6AIYTLA.js');
|
|
1169
1169
|
const credentials = await mcps.getCallerCredentialsAsync();
|
|
1170
1170
|
if (!credentials.bearerToken && !credentials.apiKey) {
|
|
1171
1171
|
stderrWrite(
|
|
@@ -2830,6 +2830,13 @@ var ClaudeTurnStatus = /* @__PURE__ */ ((ClaudeTurnStatus2) => {
|
|
|
2830
2830
|
ClaudeTurnStatus2["Waiting"] = "waiting";
|
|
2831
2831
|
return ClaudeTurnStatus2;
|
|
2832
2832
|
})(ClaudeTurnStatus || {});
|
|
2833
|
+
var ClaudeProcessStatus = /* @__PURE__ */ ((ClaudeProcessStatus2) => {
|
|
2834
|
+
ClaudeProcessStatus2["Idle"] = "idle";
|
|
2835
|
+
ClaudeProcessStatus2["Busy"] = "busy";
|
|
2836
|
+
ClaudeProcessStatus2["Shell"] = "shell";
|
|
2837
|
+
ClaudeProcessStatus2["Waiting"] = "waiting";
|
|
2838
|
+
return ClaudeProcessStatus2;
|
|
2839
|
+
})(ClaudeProcessStatus || {});
|
|
2833
2840
|
var ClaudeJobState = /* @__PURE__ */ ((ClaudeJobState2) => {
|
|
2834
2841
|
ClaudeJobState2["Working"] = "working";
|
|
2835
2842
|
ClaudeJobState2["Blocked"] = "blocked";
|
|
@@ -2932,6 +2939,12 @@ var CLAUDE_SESSION_REGISTRY_DIR_NAME = "sessions";
|
|
|
2932
2939
|
var CLAUDE_PROJECTS_DIR_NAME = "projects";
|
|
2933
2940
|
var CLAUDE_INSTALLED_PLUGINS_RELATIVE_PATH = ["plugins", "installed_plugins.json"];
|
|
2934
2941
|
var CLAUDE_GLOBAL_CONFIG_FILE_NAME = ".claude.json";
|
|
2942
|
+
var CLAUDE_SETTINGS_FILE_NAME = "settings.json";
|
|
2943
|
+
var CLAUDE_CREDENTIALS_FILE_NAME = ".credentials.json";
|
|
2944
|
+
var REMOTE_CONTROL_MAX_RESTARTS = 2;
|
|
2945
|
+
var REMOTE_CONTROL_RESTART_WINDOW_MS = 24 * 60 * 6e4;
|
|
2946
|
+
var REMOTE_CONTROL_RESTART_COOLDOWN_MS = 30 * 6e4;
|
|
2947
|
+
var REMOTE_CONTROL_NAME_COLUMN_WIDTH = 32;
|
|
2935
2948
|
var USAGE_WINDOW_EXHAUSTED_PERCENT = 100;
|
|
2936
2949
|
var RELOAD_PLUGIN_SCOPE = "user";
|
|
2937
2950
|
var MUGGLE_PLUGIN_ID = "muggleai@muggle-works";
|
|
@@ -2987,6 +3000,8 @@ function resolveWatchPaths(env = process.env) {
|
|
|
2987
3000
|
claudeProjectsDir: join(claudeConfigDir, CLAUDE_PROJECTS_DIR_NAME),
|
|
2988
3001
|
claudeInstalledPluginsFile: join(claudeConfigDir, ...CLAUDE_INSTALLED_PLUGINS_RELATIVE_PATH),
|
|
2989
3002
|
claudeGlobalConfigFile: join(env.CLAUDE_CONFIG_DIR || homedir(), CLAUDE_GLOBAL_CONFIG_FILE_NAME),
|
|
3003
|
+
claudeSettingsFile: join(claudeConfigDir, CLAUDE_SETTINGS_FILE_NAME),
|
|
3004
|
+
claudeCredentialsFile: join(claudeConfigDir, CLAUDE_CREDENTIALS_FILE_NAME),
|
|
2990
3005
|
codexSessionsDir: join(codexHome, "sessions"),
|
|
2991
3006
|
muggleInstallLogFile: join(muggleDataDir, MUGGLE_INSTALL_LOG_FILE)
|
|
2992
3007
|
};
|
|
@@ -4841,6 +4856,9 @@ function processSummary(snapshot) {
|
|
|
4841
4856
|
`${countOf("pr-watch-loop" /* PrWatchLoop */)} PR watch loop processes`
|
|
4842
4857
|
].join(", ");
|
|
4843
4858
|
}
|
|
4859
|
+
function countDecisions(report, action) {
|
|
4860
|
+
return report.decisions.filter((decision) => decision.action === action).length;
|
|
4861
|
+
}
|
|
4844
4862
|
function renderWatchStatus(snapshot, nowMs) {
|
|
4845
4863
|
const lines = [];
|
|
4846
4864
|
const snapshotAgeMs = nowMs - Date.parse(snapshot.takenAt);
|
|
@@ -4883,12 +4901,24 @@ function renderWatchStatus(snapshot, nowMs) {
|
|
|
4883
4901
|
}
|
|
4884
4902
|
}
|
|
4885
4903
|
if (snapshot.reload) {
|
|
4886
|
-
const
|
|
4904
|
+
const reload = snapshot.reload;
|
|
4887
4905
|
lines.push(
|
|
4888
4906
|
"",
|
|
4889
|
-
`Plugin reload: ${
|
|
4907
|
+
`Plugin reload: ${reload.respawnedSessionIds.length} restarted, ${reload.failedSessionIds.length} failed, ${countDecisions(reload, "defer" /* Defer */)} deferred, ${countDecisions(reload, "notify-manual" /* NotifyManual */) + (reload.untrustedSessionIds?.length ?? 0)} need /reload-plugins by hand`
|
|
4890
4908
|
);
|
|
4891
4909
|
}
|
|
4910
|
+
if (snapshot.remoteControl) {
|
|
4911
|
+
const remoteControl = snapshot.remoteControl;
|
|
4912
|
+
lines.push(
|
|
4913
|
+
"",
|
|
4914
|
+
`Remote Control: ${remoteControl.connectedSessionIds.length} connected, ${remoteControl.respawnedSessionIds.length} restarted, ${remoteControl.failedSessionIds.length} failed, ${remoteControl.untrustedSessionIds.length} refused, ${countDecisions(remoteControl, "defer" /* Defer */)} waiting, ${countDecisions(remoteControl, "skip" /* Skip */) + countDecisions(remoteControl, "notify-manual" /* NotifyManual */)} skipped`
|
|
4915
|
+
);
|
|
4916
|
+
for (const decision of remoteControl.decisions) {
|
|
4917
|
+
const outcome = remoteControl.untrustedSessionIds.includes(decision.sessionId) ? " \u2014 refused: its workspace takes no background restart, run /remote-control in it" : remoteControl.failedSessionIds.includes(decision.sessionId) ? " \u2014 restart failed" : "";
|
|
4918
|
+
const label = (decision.name ?? "").slice(0, REMOTE_CONTROL_NAME_COLUMN_WIDTH).padEnd(REMOTE_CONTROL_NAME_COLUMN_WIDTH);
|
|
4919
|
+
lines.push(` ${decision.action.padEnd(14)} ${shortSessionId(decision.sessionId)} ${label} ${decision.reason}${outcome}`);
|
|
4920
|
+
}
|
|
4921
|
+
}
|
|
4892
4922
|
return `${lines.join("\n")}
|
|
4893
4923
|
`;
|
|
4894
4924
|
}
|
|
@@ -4921,6 +4951,32 @@ function cwdFromSessionHead(head) {
|
|
|
4921
4951
|
}
|
|
4922
4952
|
}
|
|
4923
4953
|
|
|
4954
|
+
// src/watch/claude-account.ts
|
|
4955
|
+
function isRemoteControlAtStartup(claudeSettingsFile) {
|
|
4956
|
+
try {
|
|
4957
|
+
const settings = JSON.parse(readFileSync(claudeSettingsFile, "utf-8"));
|
|
4958
|
+
return settings.remoteControlAtStartup === true;
|
|
4959
|
+
} catch {
|
|
4960
|
+
return false;
|
|
4961
|
+
}
|
|
4962
|
+
}
|
|
4963
|
+
function readClaudeLogin(claudeGlobalConfigFile, claudeCredentialsFile) {
|
|
4964
|
+
let accountUuid;
|
|
4965
|
+
try {
|
|
4966
|
+
const config = JSON.parse(readFileSync(claudeGlobalConfigFile, "utf-8"));
|
|
4967
|
+
accountUuid = optionalString(config.oauthAccount?.accountUuid);
|
|
4968
|
+
} catch {
|
|
4969
|
+
accountUuid = void 0;
|
|
4970
|
+
}
|
|
4971
|
+
let credentialsWrittenAtMs;
|
|
4972
|
+
try {
|
|
4973
|
+
credentialsWrittenAtMs = statSync(claudeCredentialsFile).mtimeMs;
|
|
4974
|
+
} catch {
|
|
4975
|
+
credentialsWrittenAtMs = void 0;
|
|
4976
|
+
}
|
|
4977
|
+
return { accountUuid, credentialsWrittenAtMs };
|
|
4978
|
+
}
|
|
4979
|
+
|
|
4924
4980
|
// src/watch/session-liveness.ts
|
|
4925
4981
|
function hasLiveProcess(session) {
|
|
4926
4982
|
if (session.agent !== "claude" /* Claude */) return true;
|
|
@@ -4931,25 +4987,30 @@ function isIdleBackgroundSession(session) {
|
|
|
4931
4987
|
}
|
|
4932
4988
|
|
|
4933
4989
|
// src/watch/claude-sessions.ts
|
|
4934
|
-
function
|
|
4935
|
-
const
|
|
4990
|
+
function readRegistryBySessionId(registryDir) {
|
|
4991
|
+
const entriesBySessionId = /* @__PURE__ */ new Map();
|
|
4936
4992
|
let fileNames;
|
|
4937
4993
|
try {
|
|
4938
4994
|
fileNames = readdirSync(registryDir).filter((fileName) => fileName.endsWith(".json"));
|
|
4939
4995
|
} catch {
|
|
4940
|
-
return
|
|
4996
|
+
return entriesBySessionId;
|
|
4941
4997
|
}
|
|
4942
4998
|
for (const fileName of fileNames) {
|
|
4943
4999
|
try {
|
|
4944
5000
|
const entry = JSON.parse(readFileSync(join(registryDir, fileName), "utf-8"));
|
|
4945
5001
|
if (typeof entry.pid === "number" && typeof entry.sessionId === "string") {
|
|
4946
|
-
|
|
5002
|
+
entriesBySessionId.set(entry.sessionId, {
|
|
5003
|
+
pid: entry.pid,
|
|
5004
|
+
processStatus: enumValueOf(ClaudeProcessStatus, entry.status),
|
|
5005
|
+
processStatusSinceMs: typeof entry.statusUpdatedAt === "number" ? entry.statusUpdatedAt : void 0,
|
|
5006
|
+
remoteControlSessionId: optionalString(entry.bridgeSessionId)
|
|
5007
|
+
});
|
|
4947
5008
|
}
|
|
4948
5009
|
} catch {
|
|
4949
5010
|
continue;
|
|
4950
5011
|
}
|
|
4951
5012
|
}
|
|
4952
|
-
return
|
|
5013
|
+
return entriesBySessionId;
|
|
4953
5014
|
}
|
|
4954
5015
|
function waitForSessionExit(runCommand, registryDir, sessionId, sleep2 = blockFor) {
|
|
4955
5016
|
for (let waitedMs = 0; ; waitedMs += SESSION_EXIT_POLL_MS) {
|
|
@@ -4971,11 +5032,12 @@ function readClaudeSessions(runCommand, registryDir) {
|
|
|
4971
5032
|
} catch {
|
|
4972
5033
|
return null;
|
|
4973
5034
|
}
|
|
4974
|
-
const
|
|
5035
|
+
const registryBySessionId = readRegistryBySessionId(registryDir);
|
|
4975
5036
|
const sessions = [];
|
|
4976
5037
|
for (const listing of listings) {
|
|
4977
5038
|
const sessionId = optionalString(listing.sessionId);
|
|
4978
5039
|
if (!sessionId) continue;
|
|
5040
|
+
const registryEntry = registryBySessionId.get(sessionId);
|
|
4979
5041
|
sessions.push({
|
|
4980
5042
|
agent: "claude" /* Claude */,
|
|
4981
5043
|
sessionId,
|
|
@@ -4986,7 +5048,10 @@ function readClaudeSessions(runCommand, registryDir) {
|
|
|
4986
5048
|
turnStatus: enumValueOf(ClaudeTurnStatus, listing.status),
|
|
4987
5049
|
jobState: enumValueOf(ClaudeJobState, listing.state),
|
|
4988
5050
|
startedAtMs: typeof listing.startedAt === "number" ? listing.startedAt : void 0,
|
|
4989
|
-
pid: typeof listing.pid === "number" ? listing.pid :
|
|
5051
|
+
pid: typeof listing.pid === "number" ? listing.pid : registryEntry?.pid,
|
|
5052
|
+
processStatus: registryEntry?.processStatus,
|
|
5053
|
+
processStatusSinceMs: registryEntry?.processStatusSinceMs,
|
|
5054
|
+
remoteControlSessionId: registryEntry?.remoteControlSessionId
|
|
4990
5055
|
});
|
|
4991
5056
|
}
|
|
4992
5057
|
return sessions;
|
|
@@ -5381,26 +5446,29 @@ function executeWatcherRecoveries(planned, attemptsByOwner, deps) {
|
|
|
5381
5446
|
}
|
|
5382
5447
|
|
|
5383
5448
|
// src/watch/findings.ts
|
|
5384
|
-
function resolveWatcherLiveness(loop, heartbeatAgeMs, isOwnerSessionActive, isProcessTableKnown, isHeardByService = false) {
|
|
5449
|
+
function resolveWatcherLiveness(loop, heartbeatAgeMs, isOwnerSessionActive, isProcessTableKnown, isHeardByService = false, isLoopDroppedByOwner = false) {
|
|
5385
5450
|
const isHeartbeatWithin = (windowMs) => heartbeatAgeMs !== void 0 && heartbeatAgeMs <= windowMs;
|
|
5386
5451
|
if (!isProcessTableKnown) {
|
|
5387
5452
|
return isHeartbeatWithin(WATCH_HEARTBEAT_STALE_MS) ? "polling" /* Polling */ : "dead" /* Dead */;
|
|
5388
5453
|
}
|
|
5389
5454
|
if (loop) {
|
|
5390
|
-
|
|
5455
|
+
const isUnheard = !isHeardByService && (isOwnerSessionActive === false || isLoopDroppedByOwner);
|
|
5456
|
+
return isUnheard ? "deaf" /* Deaf */ : "polling" /* Polling */;
|
|
5391
5457
|
}
|
|
5392
5458
|
return isHeartbeatWithin(WATCHER_REARM_GRACE_MS) ? "rearming" /* Rearming */ : "dead" /* Dead */;
|
|
5393
5459
|
}
|
|
5394
5460
|
function oldestLoopFor(slug, processes) {
|
|
5395
5461
|
return processes.filter((tracked) => tracked.kind === "pr-watch-loop" /* PrWatchLoop */ && tracked.watchSlotSlug === slug).sort((left, right) => (right.ageMs ?? 0) - (left.ageMs ?? 0))[0];
|
|
5396
5462
|
}
|
|
5397
|
-
function assessWatchSlots(slots, processes, activeSessionIds, liveStatusesByUrl, nowMs) {
|
|
5463
|
+
function assessWatchSlots(slots, processes, activeSessionIds, liveStatusesByUrl, nowMs, idleSinceBySessionId2 = /* @__PURE__ */ new Map()) {
|
|
5398
5464
|
const assessments = [];
|
|
5399
5465
|
for (const slot of slots) {
|
|
5400
5466
|
const loop = processes ? oldestLoopFor(slot.slug, processes) : void 0;
|
|
5401
5467
|
const heartbeatAgeMs = slot.heartbeatMtimeMs === void 0 ? void 0 : Math.max(0, nowMs - slot.heartbeatMtimeMs);
|
|
5402
5468
|
const isOwnerSessionActive = slot.ownerSessionId === void 0 || activeSessionIds === null ? void 0 : activeSessionIds.has(slot.ownerSessionId);
|
|
5403
|
-
const
|
|
5469
|
+
const ownerIdleSinceMs = slot.ownerSessionId === void 0 ? void 0 : idleSinceBySessionId2.get(slot.ownerSessionId);
|
|
5470
|
+
const isLoopDroppedByOwner = ownerIdleSinceMs !== void 0 && loop?.ageMs !== void 0 && ownerIdleSinceMs >= nowMs - loop.ageMs;
|
|
5471
|
+
const watcher = resolveWatcherLiveness(loop, heartbeatAgeMs, isOwnerSessionActive, processes !== null, slot.isHeardByService, isLoopDroppedByOwner);
|
|
5404
5472
|
const reportAgeMs = slot.serviceLoopReport === void 0 ? void 0 : Math.max(0, nowMs - slot.serviceLoopReport.writtenAtMs);
|
|
5405
5473
|
for (const pr of slot.openPrs) {
|
|
5406
5474
|
const live = liveStatusesByUrl.get(pr.url);
|
|
@@ -5415,6 +5483,7 @@ function assessWatchSlots(slots, processes, activeSessionIds, liveStatusesByUrl,
|
|
|
5415
5483
|
heartbeatAgeMs,
|
|
5416
5484
|
ownerSessionId: slot.ownerSessionId,
|
|
5417
5485
|
isOwnerSessionActive,
|
|
5486
|
+
isLoopDroppedByOwner,
|
|
5418
5487
|
live,
|
|
5419
5488
|
ownerEscalation: resolveOwnerEscalation(slot.lastSeenJson, slot.followupLogLines, pr, live?.headSha),
|
|
5420
5489
|
isHeardByService: slot.isHeardByService,
|
|
@@ -5461,11 +5530,12 @@ function watcherFinding(assessment, subject) {
|
|
|
5461
5530
|
);
|
|
5462
5531
|
}
|
|
5463
5532
|
if (assessment.watcher === "deaf" /* Deaf */) {
|
|
5533
|
+
const ownerState = assessment.isLoopDroppedByOwner ? "is running but no longer holds it" : "has ended";
|
|
5464
5534
|
return finding(
|
|
5465
5535
|
"WATCHER_DEAF" /* WatcherDeaf */,
|
|
5466
5536
|
[assessment.pr.url, assessment.loopPid],
|
|
5467
5537
|
subject,
|
|
5468
|
-
`watch loop pid ${assessment.loopPid} reports to no session: its owner session ${shortSessionId(assessment.ownerSessionId ?? "")}
|
|
5538
|
+
`watch loop pid ${assessment.loopPid} reports to no session: its owner session ${shortSessionId(assessment.ownerSessionId ?? "")} ${ownerState}`
|
|
5469
5539
|
);
|
|
5470
5540
|
}
|
|
5471
5541
|
return void 0;
|
|
@@ -5906,6 +5976,75 @@ function advancePendingReload(pending, report) {
|
|
|
5906
5976
|
skippedSessionIds: [...pending.skippedSessionIds, ...report.untrustedSessionIds]
|
|
5907
5977
|
};
|
|
5908
5978
|
}
|
|
5979
|
+
|
|
5980
|
+
// src/watch/remote-control.ts
|
|
5981
|
+
function advanceClaudeLogin(previous, observed, nowMs) {
|
|
5982
|
+
if (observed.accountUuid === void 0) return previous;
|
|
5983
|
+
if (previous === void 0) return { accountUuid: observed.accountUuid, observedAtMs: nowMs };
|
|
5984
|
+
if (previous.accountUuid === observed.accountUuid) return { ...previous, observedAtMs: nowMs };
|
|
5985
|
+
const writtenAtMs = Math.min(observed.credentialsWrittenAtMs ?? nowMs, nowMs);
|
|
5986
|
+
return { accountUuid: observed.accountUuid, effectiveAtMs: Math.max(previous.observedAtMs, writtenAtMs), observedAtMs: nowMs };
|
|
5987
|
+
}
|
|
5988
|
+
function remoteControlGapOf(session, login) {
|
|
5989
|
+
if (session.remoteControlSessionId === void 0) return "no Remote Control link";
|
|
5990
|
+
const isStartedBeforeLogin = login.effectiveAtMs !== void 0 && session.startedAtMs !== void 0 && session.startedAtMs < login.effectiveAtMs;
|
|
5991
|
+
return isStartedBeforeLogin ? "linked under an earlier login" : void 0;
|
|
5992
|
+
}
|
|
5993
|
+
function isTurnOver(session) {
|
|
5994
|
+
if (session.processStatus !== void 0) {
|
|
5995
|
+
return session.processStatus === "idle" /* Idle */ || session.processStatus === "shell" /* Shell */;
|
|
5996
|
+
}
|
|
5997
|
+
return session.turnStatus === "idle" /* Idle */;
|
|
5998
|
+
}
|
|
5999
|
+
function recentRestartsMs(restartsBySession, sessionId, nowMs) {
|
|
6000
|
+
return (restartsBySession[sessionId] ?? []).map((restartedAt) => Date.parse(restartedAt)).filter((restartedAtMs) => !Number.isNaN(restartedAtMs) && nowMs - restartedAtMs < REMOTE_CONTROL_RESTART_WINDOW_MS);
|
|
6001
|
+
}
|
|
6002
|
+
function decide2(session, action, reason) {
|
|
6003
|
+
return { sessionId: session.sessionId, shortId: session.shortId, name: session.name, cwd: session.cwd, action, reason };
|
|
6004
|
+
}
|
|
6005
|
+
function planRemoteControlRestarts(sessions, login, restartsBySession, nowMs, maxRestarts) {
|
|
6006
|
+
let restartsPlanned = 0;
|
|
6007
|
+
const connectedSessionIds = [];
|
|
6008
|
+
const decisions = [];
|
|
6009
|
+
for (const session of sessions) {
|
|
6010
|
+
if (session.agent !== "claude" /* Claude */ || !hasLiveProcess(session)) continue;
|
|
6011
|
+
const gap = remoteControlGapOf(session, login);
|
|
6012
|
+
if (gap === void 0) {
|
|
6013
|
+
connectedSessionIds.push(session.sessionId);
|
|
6014
|
+
continue;
|
|
6015
|
+
}
|
|
6016
|
+
const restartsMs = recentRestartsMs(restartsBySession, session.sessionId, nowMs);
|
|
6017
|
+
if (session.kind === "interactive" /* Interactive */) {
|
|
6018
|
+
decisions.push(decide2(session, "notify-manual" /* NotifyManual */, `${gap} \u2014 terminal session, run /remote-control in it`));
|
|
6019
|
+
} else if (session.kind !== "background" /* Background */ || !session.shortId) {
|
|
6020
|
+
decisions.push(decide2(session, "skip" /* Skip */, `${gap} \u2014 not a background session the watch can restart`));
|
|
6021
|
+
} else if (restartsMs.length >= REMOTE_CONTROL_MAX_RESTARTS) {
|
|
6022
|
+
decisions.push(decide2(session, "skip" /* Skip */, `${gap} after ${restartsMs.length} restarts today`));
|
|
6023
|
+
} else if (nowMs - Math.max(-Infinity, ...restartsMs) < REMOTE_CONTROL_RESTART_COOLDOWN_MS) {
|
|
6024
|
+
decisions.push(decide2(session, "skip" /* Skip */, `${gap} \u2014 restarted recently, waiting for it to link`));
|
|
6025
|
+
} else if (!isTurnOver(session)) {
|
|
6026
|
+
decisions.push(decide2(session, "defer" /* Defer */, `${gap} \u2014 turn in flight (${session.processStatus ?? session.turnStatus ?? "unknown"})`));
|
|
6027
|
+
} else if (restartsPlanned >= maxRestarts) {
|
|
6028
|
+
decisions.push(decide2(session, "defer" /* Defer */, `${gap} \u2014 restart cap reached this pass`));
|
|
6029
|
+
} else {
|
|
6030
|
+
restartsPlanned++;
|
|
6031
|
+
decisions.push(decide2(session, "respawn" /* Respawn */, gap));
|
|
6032
|
+
}
|
|
6033
|
+
}
|
|
6034
|
+
return { connectedSessionIds, decisions };
|
|
6035
|
+
}
|
|
6036
|
+
function recordRemoteControlRestarts(restartsBySession, report, nowMs) {
|
|
6037
|
+
const nowIso = new Date(nowMs).toISOString();
|
|
6038
|
+
const nextRestartsBySession = {};
|
|
6039
|
+
for (const sessionId of Object.keys(restartsBySession)) {
|
|
6040
|
+
const recent = recentRestartsMs(restartsBySession, sessionId, nowMs).map((restartedAtMs) => new Date(restartedAtMs).toISOString());
|
|
6041
|
+
if (recent.length > 0) nextRestartsBySession[sessionId] = recent;
|
|
6042
|
+
}
|
|
6043
|
+
for (const sessionId of [...report.respawnedSessionIds, ...report.failedSessionIds, ...report.untrustedSessionIds]) {
|
|
6044
|
+
nextRestartsBySession[sessionId] = [...nextRestartsBySession[sessionId] ?? [], nowIso];
|
|
6045
|
+
}
|
|
6046
|
+
return nextRestartsBySession;
|
|
6047
|
+
}
|
|
5909
6048
|
function resolvePluginRoot(env = process.env) {
|
|
5910
6049
|
return env.CLAUDE_PLUGIN_ROOT || env.CURSOR_PLUGIN_ROOT || dirname(dirname(fileURLToPath(import.meta.url)));
|
|
5911
6050
|
}
|
|
@@ -6273,6 +6412,7 @@ function createDefaultWatchTickDeps(options) {
|
|
|
6273
6412
|
notify: createDesktopNotifier(process.platform, defaultCommandRunner),
|
|
6274
6413
|
isAutoReloadEnabled: options.isAutoReloadEnabled,
|
|
6275
6414
|
isRecoveryEnabled: options.isRecoveryEnabled,
|
|
6415
|
+
isRemoteControlKeptOn: options.isRemoteControlKeptOn,
|
|
6276
6416
|
launchServiceLoop: createServiceLoopLauncher(process.platform, defaultCommandRunner)
|
|
6277
6417
|
};
|
|
6278
6418
|
}
|
|
@@ -6292,18 +6432,26 @@ function observeSessions(deps, installs) {
|
|
|
6292
6432
|
const sessions = [...claudeSessions ?? [], ...readCodexSessions(deps.paths.codexSessionsDir, deps.nowMs)];
|
|
6293
6433
|
return { processes, claudeSessions, sessions };
|
|
6294
6434
|
}
|
|
6435
|
+
function idleSinceBySessionId(runningSessions) {
|
|
6436
|
+
return new Map(
|
|
6437
|
+
runningSessions.flatMap(
|
|
6438
|
+
(session) => session.processStatus === "idle" /* Idle */ && session.processStatusSinceMs !== void 0 ? [[session.sessionId, session.processStatusSinceMs]] : []
|
|
6439
|
+
)
|
|
6440
|
+
);
|
|
6441
|
+
}
|
|
6295
6442
|
function isRestartAttempted(recovery) {
|
|
6296
6443
|
return recovery.outcome === "resumed" /* Resumed */ || recovery.outcome === "resume-failed" /* ResumeFailed */;
|
|
6297
6444
|
}
|
|
6298
6445
|
function watchOwnersOf(slots) {
|
|
6299
6446
|
return new Set(slots.flatMap((slot) => !isTerminalPrState(slot.live?.state) && slot.ownerSessionId ? [slot.ownerSessionId] : []));
|
|
6300
6447
|
}
|
|
6301
|
-
function reloadEvents(report, at) {
|
|
6448
|
+
function reloadEvents(report, at, restartPurpose) {
|
|
6302
6449
|
const decisionById = new Map(report.decisions.map((decision) => [decision.sessionId, decision]));
|
|
6303
6450
|
const describe = (sessionId) => `session ${decisionById.get(sessionId)?.name ?? shortSessionId(sessionId)}`;
|
|
6451
|
+
const purposeOf = (sessionId) => restartPurpose === void 0 ? {} : { message: `${restartPurpose}: ${decisionById.get(sessionId)?.reason ?? ""}` };
|
|
6304
6452
|
return [
|
|
6305
|
-
...report.respawnedSessionIds.map((sessionId) => ({ at, kind: "respawned" /* Respawned */, subject: describe(sessionId) })),
|
|
6306
|
-
...report.failedSessionIds.map((sessionId) => ({ at, kind: "respawn-failed" /* RespawnFailed */, subject: describe(sessionId) })),
|
|
6453
|
+
...report.respawnedSessionIds.map((sessionId) => ({ at, kind: "respawned" /* Respawned */, subject: describe(sessionId), ...purposeOf(sessionId) })),
|
|
6454
|
+
...report.failedSessionIds.map((sessionId) => ({ at, kind: "respawn-failed" /* RespawnFailed */, subject: describe(sessionId), ...purposeOf(sessionId) })),
|
|
6307
6455
|
...report.untrustedSessionIds.map((sessionId) => ({
|
|
6308
6456
|
at,
|
|
6309
6457
|
kind: "restart-refused" /* RestartRefused */,
|
|
@@ -6321,6 +6469,15 @@ function runReloadPass(record, claudeSessions, watchOwnerSessionIds, deps, at) {
|
|
|
6321
6469
|
const report = executeReloadFanOut(decisions, deps.runCommand, canRestartVia(deps));
|
|
6322
6470
|
return { report, pendingReload: advancePendingReload(record, report), events: reloadEvents(report, at) };
|
|
6323
6471
|
}
|
|
6472
|
+
function runRemoteControlPass(login, claudeSessions, restartsBySession, maxRestarts, deps, at) {
|
|
6473
|
+
const plan = planRemoteControlRestarts(claudeSessions, login, restartsBySession, deps.nowMs, maxRestarts);
|
|
6474
|
+
const executed = executeReloadFanOut(plan.decisions, deps.runCommand, canRestartVia(deps));
|
|
6475
|
+
return {
|
|
6476
|
+
report: { ...executed, connectedSessionIds: plan.connectedSessionIds },
|
|
6477
|
+
restartsBySession: recordRemoteControlRestarts(restartsBySession, executed, deps.nowMs),
|
|
6478
|
+
events: reloadEvents(executed, at, "restarted to reconnect Remote Control")
|
|
6479
|
+
};
|
|
6480
|
+
}
|
|
6324
6481
|
function canRestartVia(deps) {
|
|
6325
6482
|
return (cwd) => canRestartSessionIn(cwd, deps.runCommand);
|
|
6326
6483
|
}
|
|
@@ -6366,7 +6523,7 @@ function runTickUnlocked(deps) {
|
|
|
6366
6523
|
at
|
|
6367
6524
|
);
|
|
6368
6525
|
for (const cachedStatus of cachedStatuses) liveStatusesByUrl.set(cachedStatus.url, cachedStatus);
|
|
6369
|
-
const allAssessments = assessWatchSlots(slots, processes, activeSessionIds, liveStatusesByUrl, deps.nowMs);
|
|
6526
|
+
const allAssessments = assessWatchSlots(slots, processes, activeSessionIds, liveStatusesByUrl, deps.nowMs, idleSinceBySessionId(runningSessions));
|
|
6370
6527
|
const closeOut = closeOutTerminalSlots(allAssessments, deps, at);
|
|
6371
6528
|
const assessments = allAssessments.filter((assessment) => !closeOut.closedOutSlugs.includes(assessment.slug));
|
|
6372
6529
|
const findings = deriveWatchFindings(assessments, processes ?? [], isPollingDisabled);
|
|
@@ -6409,16 +6566,34 @@ function runTickUnlocked(deps) {
|
|
|
6409
6566
|
serviceArms = started.serviceArms;
|
|
6410
6567
|
events.push(...started.events);
|
|
6411
6568
|
}
|
|
6569
|
+
const restartedSessionIds = new Set(recoveries.filter(isRestartAttempted).map((recovery) => recovery.ownerSessionId));
|
|
6412
6570
|
let pendingReload = state.pendingReload;
|
|
6413
6571
|
let reload;
|
|
6414
6572
|
const targetAtMs = reloadTargetAtMs(deps, installs);
|
|
6415
6573
|
if (deps.isAutoReloadEnabled && targetAtMs !== void 0 && claudeSessions !== null) {
|
|
6416
|
-
const
|
|
6417
|
-
const reloadableSessions = claudeSessions.filter((session) => !restartedOwnerIds.has(session.sessionId));
|
|
6574
|
+
const reloadableSessions = claudeSessions.filter((session) => !restartedSessionIds.has(session.sessionId));
|
|
6418
6575
|
const pass = runReloadPass(reloadRecordFor(state, targetAtMs), reloadableSessions, watchOwnersOf(assessments), deps, at);
|
|
6419
6576
|
pendingReload = pass.pendingReload;
|
|
6420
6577
|
reload = pass.report;
|
|
6421
6578
|
events.push(...pass.events);
|
|
6579
|
+
for (const sessionId of [...reload.respawnedSessionIds, ...reload.failedSessionIds]) restartedSessionIds.add(sessionId);
|
|
6580
|
+
}
|
|
6581
|
+
const observedLogin = readClaudeLogin(deps.paths.claudeGlobalConfigFile, deps.paths.claudeCredentialsFile);
|
|
6582
|
+
const claudeLogin = advanceClaudeLogin(state.claudeLogin, observedLogin, deps.nowMs);
|
|
6583
|
+
let remoteControlRestartsBySession = state.remoteControlRestartsBySession ?? {};
|
|
6584
|
+
let remoteControl;
|
|
6585
|
+
if (deps.isRemoteControlKeptOn && claudeSessions !== null && claudeLogin !== void 0 && observedLogin.accountUuid !== void 0 && isRemoteControlAtStartup(deps.paths.claudeSettingsFile)) {
|
|
6586
|
+
const pass = runRemoteControlPass(
|
|
6587
|
+
claudeLogin,
|
|
6588
|
+
claudeSessions.filter((session) => !restartedSessionIds.has(session.sessionId)),
|
|
6589
|
+
remoteControlRestartsBySession,
|
|
6590
|
+
Math.max(0, MAX_RESPAWNS_PER_TICK - (reload ? reload.respawnedSessionIds.length + reload.failedSessionIds.length : 0)),
|
|
6591
|
+
deps,
|
|
6592
|
+
at
|
|
6593
|
+
);
|
|
6594
|
+
remoteControl = pass.report;
|
|
6595
|
+
remoteControlRestartsBySession = pass.restartsBySession;
|
|
6596
|
+
events.push(...pass.events);
|
|
6422
6597
|
}
|
|
6423
6598
|
appendWatchEvents(deps.paths.watchDir, events);
|
|
6424
6599
|
notifyIfAny(deps, composeTickNotification(transitions.raised, recoveries, reload));
|
|
@@ -6426,7 +6601,9 @@ function runTickUnlocked(deps) {
|
|
|
6426
6601
|
activeFindings: transitions.activeFindings,
|
|
6427
6602
|
recoveryAttemptsByOwner,
|
|
6428
6603
|
terminalPrsByUrl,
|
|
6429
|
-
pendingReload
|
|
6604
|
+
pendingReload,
|
|
6605
|
+
claudeLogin,
|
|
6606
|
+
remoteControlRestartsBySession
|
|
6430
6607
|
});
|
|
6431
6608
|
const snapshot = {
|
|
6432
6609
|
takenAt: at,
|
|
@@ -6439,7 +6616,8 @@ function runTickUnlocked(deps) {
|
|
|
6439
6616
|
findings,
|
|
6440
6617
|
recoveries,
|
|
6441
6618
|
serviceArms,
|
|
6442
|
-
reload
|
|
6619
|
+
reload,
|
|
6620
|
+
remoteControl
|
|
6443
6621
|
};
|
|
6444
6622
|
writeWatchSnapshot(deps.paths.watchDir, snapshot);
|
|
6445
6623
|
return snapshot;
|
|
@@ -6493,7 +6671,11 @@ function failWith(message) {
|
|
|
6493
6671
|
function watchTickCommand(flags) {
|
|
6494
6672
|
try {
|
|
6495
6673
|
const snapshot = runWatchTick(
|
|
6496
|
-
createDefaultWatchTickDeps({
|
|
6674
|
+
createDefaultWatchTickDeps({
|
|
6675
|
+
isAutoReloadEnabled: flags.autoReload,
|
|
6676
|
+
isRecoveryEnabled: flags.recovery,
|
|
6677
|
+
isRemoteControlKeptOn: flags.remoteControl
|
|
6678
|
+
})
|
|
6497
6679
|
);
|
|
6498
6680
|
if (flags.json) {
|
|
6499
6681
|
process.stdout.write(`${JSON.stringify(snapshot, null, 2)}
|
|
@@ -6533,7 +6715,10 @@ function renderReloadReport(report, isDryRun) {
|
|
|
6533
6715
|
function watchReloadPluginsCommand(flags) {
|
|
6534
6716
|
try {
|
|
6535
6717
|
const isDryRun = flags.dryRun === true;
|
|
6536
|
-
const report = runReloadPluginsPass(
|
|
6718
|
+
const report = runReloadPluginsPass(
|
|
6719
|
+
createDefaultWatchTickDeps({ isAutoReloadEnabled: true, isRecoveryEnabled: false, isRemoteControlKeptOn: false }),
|
|
6720
|
+
isDryRun
|
|
6721
|
+
);
|
|
6537
6722
|
if (!report) {
|
|
6538
6723
|
failWith("could not list Claude sessions (`claude agents --json` failed)");
|
|
6539
6724
|
return;
|
|
@@ -6618,7 +6803,7 @@ function createProgram() {
|
|
|
6618
6803
|
stack.command("status").description("Show every lease, the line, and the services prepare tracks").option("--json", "Print the full report as JSON").action(stackStatusCommand);
|
|
6619
6804
|
program.command("ci-install").description("Add the Muggle walkthrough check to this repository's GitHub Actions").option("--force", "Replace an existing workflow file").action(ciInstallCommand);
|
|
6620
6805
|
const watch = program.command("watch").description("Standing watch over Claude/Codex sessions, their processes, and PR watchers");
|
|
6621
|
-
watch.command("tick").description("Run one watch pass (the scheduled task runs this)").option("--no-auto-reload", "Do not start a plugin reload fan-out when installed plugins change").option("--no-recovery", "Report dead watchers without resuming their owner sessions").option("--json", "Print the full snapshot as JSON").action(watchTickCommand);
|
|
6806
|
+
watch.command("tick").description("Run one watch pass (the scheduled task runs this)").option("--no-auto-reload", "Do not start a plugin reload fan-out when installed plugins change").option("--no-recovery", "Report dead watchers without resuming their owner sessions").option("--no-remote-control", "Report sessions off Remote Control without restarting them").option("--json", "Print the full snapshot as JSON").action(watchTickCommand);
|
|
6622
6807
|
watch.command("status").description("Show the last watch pass: sessions, processes, PR watches, findings").option("--json", "Print the snapshot as JSON").action(watchStatusCommand);
|
|
6623
6808
|
watch.command("reload-plugins").description("Reload plugins across active Claude sessions (respawns idle background ones)").option("--dry-run", "Show what each session would get without respawning anything").option("--json", "Print the report as JSON").action(watchReloadPluginsCommand);
|
|
6624
6809
|
watch.command("install").description("Schedule the watch with this OS's scheduler, or refresh the entry in place").option("--uninstall", "Remove the task; the ledger is kept").option("--no-wait", "Return once the task is registered, without waiting for its first pass").option("--from-preference", "Act on the autoInstallWatchService preference, as an install or upgrade does").action(watchInstallCommand);
|
|
@@ -6375,6 +6375,9 @@ var PinnedAccountSchema = z.object({
|
|
|
6375
6375
|
|
|
6376
6376
|
// packages/mcps/src/mcp/e2e/contracts/constants.ts
|
|
6377
6377
|
var WORKFLOW_RUNTIME_ID_NOT_RUN_ID_HINT = "Use the `workflowRuntimeId` returned when the workflow was started \u2014 not a run `id` or a test runs summary's `latestWorkflowRunId`, which are run ids and are not found here.";
|
|
6378
|
+
var TEST_STATE_PARALLEL_ACCOUNT_SET_LIMIT_MIN = 1;
|
|
6379
|
+
var TEST_STATE_PARALLEL_ACCOUNT_SET_LIMIT_MAX = 20;
|
|
6380
|
+
var TEST_STATE_PARALLEL_ACCOUNT_SET_LIMIT_DEFAULT = 5;
|
|
6378
6381
|
var FanOutChoice = /* @__PURE__ */ ((FanOutChoice2) => {
|
|
6379
6382
|
FanOutChoice2["FollowPolicy"] = "followPolicy";
|
|
6380
6383
|
FanOutChoice2["PrimaryOnly"] = "primaryOnly";
|
|
@@ -6401,6 +6404,85 @@ var SecretFieldEnum = /* @__PURE__ */ ((SecretFieldEnum2) => {
|
|
|
6401
6404
|
SecretFieldEnum2["BILLING_POSTAL_CODE"] = "billing_postal_code";
|
|
6402
6405
|
return SecretFieldEnum2;
|
|
6403
6406
|
})(SecretFieldEnum || {});
|
|
6407
|
+
|
|
6408
|
+
// packages/mcps/src/mcp/e2e/contracts/test-state-types.ts
|
|
6409
|
+
var TestStateFactScopeEnum = /* @__PURE__ */ ((TestStateFactScopeEnum2) => {
|
|
6410
|
+
TestStateFactScopeEnum2["ACCOUNT_SET"] = "ACCOUNT_SET";
|
|
6411
|
+
TestStateFactScopeEnum2["ACCOUNT"] = "ACCOUNT";
|
|
6412
|
+
return TestStateFactScopeEnum2;
|
|
6413
|
+
})(TestStateFactScopeEnum || {});
|
|
6414
|
+
var TestRoleSourceEnum = /* @__PURE__ */ ((TestRoleSourceEnum2) => {
|
|
6415
|
+
TestRoleSourceEnum2["CREATED"] = "CREATED";
|
|
6416
|
+
TestRoleSourceEnum2["SUPPLIED"] = "SUPPLIED";
|
|
6417
|
+
return TestRoleSourceEnum2;
|
|
6418
|
+
})(TestRoleSourceEnum || {});
|
|
6419
|
+
|
|
6420
|
+
// packages/mcps/src/mcp/e2e/contracts/test-state-schemas.ts
|
|
6421
|
+
var TestStateProjectIdSchema = MuggleEntityIdSchema.describe("Project ID (UUID) that owns the test states and roles");
|
|
6422
|
+
var TestStateIdSchema = MuggleEntityIdSchema.describe("Test state ID (UUID)");
|
|
6423
|
+
var TestRoleIdSchema = MuggleEntityIdSchema.describe("Test role ID (UUID)");
|
|
6424
|
+
var TestStateFactSchema = z.object({
|
|
6425
|
+
statement: z.string().min(1).describe("What holds, e.g. 'the thread shows one unread message'"),
|
|
6426
|
+
scope: z.enum(TestStateFactScopeEnum).describe(
|
|
6427
|
+
"ACCOUNT_SET: only this account set's data, e.g. the patient's thread with the doctor. ACCOUNT: one account as a whole, e.g. the doctor's unread count"
|
|
6428
|
+
),
|
|
6429
|
+
roleId: MuggleEntityIdSchema.nullable().describe("Test role ID (UUID) whose account an ACCOUNT fact is about; null for an ACCOUNT_SET fact")
|
|
6430
|
+
});
|
|
6431
|
+
var TestStateFactsSchema = z.array(TestStateFactSchema).describe("Every fact the state's tests rely on");
|
|
6432
|
+
var TestStateCheckSchema = z.string().describe("Quick read-only check that an account set is in this state, e.g. 'the thread shows one unread message'; may be empty");
|
|
6433
|
+
var TestRoleSourceSchema = z.enum(TestRoleSourceEnum).describe("CREATED: Muggle signs the account up itself. SUPPLIED: the customer provides the account");
|
|
6434
|
+
var TestRoleAllowsChangesSchema = z.boolean().describe("SUPPLIED only: the customer allows tests to change the account's account-wide facts when a proven way back restores them");
|
|
6435
|
+
var TestChainingCatalogGetInputSchema = z.object({
|
|
6436
|
+
projectId: TestStateProjectIdSchema
|
|
6437
|
+
});
|
|
6438
|
+
var TestChainingSettingsUpdateInputSchema = z.object({
|
|
6439
|
+
projectId: TestStateProjectIdSchema,
|
|
6440
|
+
freshAccountSetStateId: MuggleEntityIdSchema.nullable().optional().describe("Test state ID (UUID) a newly formed account set starts in, e.g. 'New patient'; null clears it"),
|
|
6441
|
+
parallelAccountSetLimit: z.number().int().min(TEST_STATE_PARALLEL_ACCOUNT_SET_LIMIT_MIN).max(TEST_STATE_PARALLEL_ACCOUNT_SET_LIMIT_MAX).optional().describe(`Most account sets a batch runs at once (default ${TEST_STATE_PARALLEL_ACCOUNT_SET_LIMIT_DEFAULT})`)
|
|
6442
|
+
});
|
|
6443
|
+
var TestStateCreateInputSchema = z.object({
|
|
6444
|
+
projectId: TestStateProjectIdSchema,
|
|
6445
|
+
name: z.string().min(1).describe("State name, unique per project ignoring case, e.g. 'Message unread'"),
|
|
6446
|
+
facts: TestStateFactsSchema.optional(),
|
|
6447
|
+
check: TestStateCheckSchema.optional()
|
|
6448
|
+
});
|
|
6449
|
+
var TestStateUpdateInputSchema = z.object({
|
|
6450
|
+
projectId: TestStateProjectIdSchema,
|
|
6451
|
+
stateId: TestStateIdSchema,
|
|
6452
|
+
name: z.string().min(1).optional().describe("New state name, unique per project ignoring case"),
|
|
6453
|
+
facts: TestStateFactsSchema.optional(),
|
|
6454
|
+
check: TestStateCheckSchema.optional()
|
|
6455
|
+
});
|
|
6456
|
+
var TestStateDeleteInputSchema = z.object({
|
|
6457
|
+
projectId: TestStateProjectIdSchema,
|
|
6458
|
+
stateId: TestStateIdSchema
|
|
6459
|
+
});
|
|
6460
|
+
var TestRoleCreateInputSchema = z.object({
|
|
6461
|
+
projectId: TestStateProjectIdSchema,
|
|
6462
|
+
name: z.string().min(1).describe("Role name, unique per project ignoring case, e.g. 'Patient'"),
|
|
6463
|
+
source: TestRoleSourceSchema,
|
|
6464
|
+
allowsChanges: TestRoleAllowsChangesSchema.optional()
|
|
6465
|
+
});
|
|
6466
|
+
var TestRoleUpdateInputSchema = z.object({
|
|
6467
|
+
projectId: TestStateProjectIdSchema,
|
|
6468
|
+
roleId: TestRoleIdSchema,
|
|
6469
|
+
name: z.string().min(1).optional().describe("New role name, unique per project ignoring case"),
|
|
6470
|
+
source: TestRoleSourceSchema.optional(),
|
|
6471
|
+
allowsChanges: TestRoleAllowsChangesSchema.optional()
|
|
6472
|
+
});
|
|
6473
|
+
var TestRoleDeleteInputSchema = z.object({
|
|
6474
|
+
projectId: TestStateProjectIdSchema,
|
|
6475
|
+
roleId: TestRoleIdSchema
|
|
6476
|
+
});
|
|
6477
|
+
var TestChainingPlanGetInputSchema = z.object({
|
|
6478
|
+
projectId: TestStateProjectIdSchema,
|
|
6479
|
+
isProduction: z.boolean().optional().describe("True to plan for production, which runs at the floor with no extra account sets for speed (default false)")
|
|
6480
|
+
});
|
|
6481
|
+
var TestCaseTestStateFieldsSchema = z.object({
|
|
6482
|
+
actsAsRoleIds: z.array(MuggleEntityIdSchema).nullable().optional().describe("Test role IDs (UUID) the test acts as, in order; null clears them"),
|
|
6483
|
+
prerequisiteStateId: MuggleEntityIdSchema.nullable().optional().describe("Test state ID (UUID) the test needs to start; null clears it"),
|
|
6484
|
+
resultStateId: MuggleEntityIdSchema.nullable().optional().describe("Test state ID (UUID) the test leaves when it passes; the prerequisite's ID for an observation; null clears it")
|
|
6485
|
+
});
|
|
6404
6486
|
var FanOutPlanInputSchema = z.object({
|
|
6405
6487
|
projectId: z.string().min(1).describe("Project ID"),
|
|
6406
6488
|
testCaseId: z.string().min(1).describe("Test case ID whose fan-out to plan"),
|
|
@@ -6720,7 +6802,8 @@ var TestCaseCreateInputSchema = z.object({
|
|
|
6720
6802
|
category: z.string().optional().describe("Test case category"),
|
|
6721
6803
|
automated: z.boolean().optional().describe("Whether this test case is automated (default: true)"),
|
|
6722
6804
|
pinnedAccount: PinnedAccountSchema.optional().describe("Project secrets the test case signs in as. Omit to leave it unpinned"),
|
|
6723
|
-
alwaysStartsFreshSession: z.boolean().optional().describe(ALWAYS_STARTS_FRESH_SESSION_DESCRIPTION)
|
|
6805
|
+
alwaysStartsFreshSession: z.boolean().optional().describe(ALWAYS_STARTS_FRESH_SESSION_DESCRIPTION),
|
|
6806
|
+
...TestCaseTestStateFieldsSchema.shape
|
|
6724
6807
|
});
|
|
6725
6808
|
var TestCaseUpdateInputSchema = z.object({
|
|
6726
6809
|
testCaseId: IdSchema.describe("Test case ID (UUID) to update"),
|
|
@@ -6736,7 +6819,8 @@ var TestCaseUpdateInputSchema = z.object({
|
|
|
6736
6819
|
category: z.string().optional().describe("Updated category"),
|
|
6737
6820
|
automated: z.boolean().optional().describe("Whether this test case is automated"),
|
|
6738
6821
|
pinnedAccount: PinnedAccountSchema.nullable().optional().describe("Project secrets the test case signs in as; null clears the pin"),
|
|
6739
|
-
alwaysStartsFreshSession: z.boolean().optional().describe(ALWAYS_STARTS_FRESH_SESSION_DESCRIPTION)
|
|
6822
|
+
alwaysStartsFreshSession: z.boolean().optional().describe(ALWAYS_STARTS_FRESH_SESSION_DESCRIPTION),
|
|
6823
|
+
...TestCaseTestStateFieldsSchema.shape
|
|
6740
6824
|
});
|
|
6741
6825
|
var TestScriptListInputSchema = z.object({
|
|
6742
6826
|
projectId: IdSchema.describe("Project ID (UUID) to list test scripts for"),
|
|
@@ -7611,6 +7695,32 @@ function mapTestScriptsSummary(response) {
|
|
|
7611
7695
|
};
|
|
7612
7696
|
}
|
|
7613
7697
|
|
|
7698
|
+
// packages/mcps/src/mcp/tools/e2e/test-state-upstream.ts
|
|
7699
|
+
function buildTestChainingCatalogPath(projectId) {
|
|
7700
|
+
return `${MUGGLE_TEST_PREFIX}/projects/${projectId}/test-chaining/catalog`;
|
|
7701
|
+
}
|
|
7702
|
+
function buildTestStatesPath(projectId) {
|
|
7703
|
+
return `${MUGGLE_TEST_PREFIX}/projects/${projectId}/test-chaining/states`;
|
|
7704
|
+
}
|
|
7705
|
+
function buildTestRolesPath(projectId) {
|
|
7706
|
+
return `${MUGGLE_TEST_PREFIX}/projects/${projectId}/test-chaining/roles`;
|
|
7707
|
+
}
|
|
7708
|
+
function buildTestChainingSettingsPath(projectId) {
|
|
7709
|
+
return `${MUGGLE_TEST_PREFIX}/projects/${projectId}/test-chaining/settings`;
|
|
7710
|
+
}
|
|
7711
|
+
function buildTestChainingPlanPath(projectId) {
|
|
7712
|
+
return `${MUGGLE_TEST_PREFIX}/projects/${projectId}/test-chaining/plan`;
|
|
7713
|
+
}
|
|
7714
|
+
function pickTestCaseTestStateFields(testStateFields) {
|
|
7715
|
+
const requestFields = {};
|
|
7716
|
+
if (testStateFields.actsAsRoleIds !== void 0) requestFields.actsAsRoleIds = testStateFields.actsAsRoleIds;
|
|
7717
|
+
if (testStateFields.prerequisiteStateId !== void 0) {
|
|
7718
|
+
requestFields.prerequisiteStateId = testStateFields.prerequisiteStateId;
|
|
7719
|
+
}
|
|
7720
|
+
if (testStateFields.resultStateId !== void 0) requestFields.resultStateId = testStateFields.resultStateId;
|
|
7721
|
+
return requestFields;
|
|
7722
|
+
}
|
|
7723
|
+
|
|
7614
7724
|
// packages/mcps/src/mcp/tools/e2e/tool-registry.ts
|
|
7615
7725
|
var getWorkflowTimeoutMs = () => getConfig().e2e.workflowTimeoutMs;
|
|
7616
7726
|
var projectTools = [
|
|
@@ -8000,7 +8110,7 @@ var testCaseTools = [
|
|
|
8000
8110
|
},
|
|
8001
8111
|
{
|
|
8002
8112
|
name: "muggle-remote-test-case-create",
|
|
8003
|
-
description: "Create a new test case for a use case.",
|
|
8113
|
+
description: "Create a new test case for a use case. Optionally place it on the project's test states: the roles it acts as (actsAsRoleIds), the state it needs to start (prerequisiteStateId) and the state it leaves when it passes (resultStateId), using ids from muggle-remote-test-chaining-catalog-get.",
|
|
8004
8114
|
inputSchema: TestCaseCreateInputSchema,
|
|
8005
8115
|
dashboardLink: linkToInputProjectPage("test-cases" /* TestCases */),
|
|
8006
8116
|
mapToUpstream: (input) => {
|
|
@@ -8025,18 +8135,19 @@ var testCaseTools = [
|
|
|
8025
8135
|
...toolInput.pinnedAccount !== void 0 && { pinnedAccount: toolInput.pinnedAccount },
|
|
8026
8136
|
...toolInput.alwaysStartsFreshSession !== void 0 && {
|
|
8027
8137
|
alwaysStartsFreshSession: toolInput.alwaysStartsFreshSession
|
|
8028
|
-
}
|
|
8138
|
+
},
|
|
8139
|
+
...pickTestCaseTestStateFields(toolInput)
|
|
8029
8140
|
}
|
|
8030
8141
|
};
|
|
8031
8142
|
}
|
|
8032
8143
|
},
|
|
8033
8144
|
{
|
|
8034
8145
|
name: "muggle-remote-test-case-update",
|
|
8035
|
-
description: "Directly update an existing test case's fields. Pass only the fields you want to change \u2014 others are left untouched. Use this for cheap, deterministic edits (rename, change status/priority, fix expected result, add tags, set or clear the pinned account with pinnedAccount, set whether every run starts from a fresh session with alwaysStartsFreshSession; pinned account and fresh session apply from the next script generation, not to existing scripts). Does not touch the associated test script \u2014 script regeneration is a separate workflow (muggle-remote-workflow-start-test-script-generation).",
|
|
8146
|
+
description: "Directly update an existing test case's fields. Pass only the fields you want to change \u2014 others are left untouched. Use this for cheap, deterministic edits (rename, change status/priority, fix expected result, add tags, set or clear the pinned account with pinnedAccount, set whether every run starts from a fresh session with alwaysStartsFreshSession; pinned account and fresh session apply from the next script generation, not to existing scripts; set the roles it acts as and its prerequisite and result test states with actsAsRoleIds, prerequisiteStateId and resultStateId, null clearing each). Does not touch the associated test script \u2014 script regeneration is a separate workflow (muggle-remote-workflow-start-test-script-generation).",
|
|
8036
8147
|
inputSchema: TestCaseUpdateInputSchema,
|
|
8037
8148
|
mapToUpstream: (input) => {
|
|
8038
8149
|
const toolInput = input;
|
|
8039
|
-
const body = { id: toolInput.testCaseId };
|
|
8150
|
+
const body = { id: toolInput.testCaseId, ...pickTestCaseTestStateFields(toolInput) };
|
|
8040
8151
|
if (toolInput.title !== void 0) body.title = toolInput.title;
|
|
8041
8152
|
if (toolInput.description !== void 0) body.description = toolInput.description;
|
|
8042
8153
|
if (toolInput.goal !== void 0) body.goal = toolInput.goal;
|
|
@@ -8085,6 +8196,146 @@ var testCaseTools = [
|
|
|
8085
8196
|
}
|
|
8086
8197
|
}
|
|
8087
8198
|
];
|
|
8199
|
+
var testStateTools = [
|
|
8200
|
+
{
|
|
8201
|
+
name: "muggle-remote-test-chaining-catalog-get",
|
|
8202
|
+
description: "Get a project's test states and roles. A state is a named set of facts about one account set's data that tests start from and leave, each fact scoped to the account set or account-wide, with a quick read-only check. A role is a kind of account a test acts as, CREATED by Muggle or SUPPLIED by the customer. Also returns freshAccountSetStateId (the state a newly formed account set starts in) and parallelAccountSetLimit; a project with none saved returns an empty catalog. Response shape: { projectId, states: [{ id, name, facts: [{ statement, scope, roleId }], check }], roles: [{ id, name, source, allowsChanges }], freshAccountSetStateId, parallelAccountSetLimit, updatedAt }.",
|
|
8203
|
+
inputSchema: TestChainingCatalogGetInputSchema,
|
|
8204
|
+
mapToUpstream: (input) => {
|
|
8205
|
+
const toolInput = input;
|
|
8206
|
+
return {
|
|
8207
|
+
method: "GET",
|
|
8208
|
+
path: buildTestChainingCatalogPath(toolInput.projectId)
|
|
8209
|
+
};
|
|
8210
|
+
}
|
|
8211
|
+
},
|
|
8212
|
+
{
|
|
8213
|
+
name: "muggle-remote-test-chaining-settings-update",
|
|
8214
|
+
description: "Set a project's freshAccountSetStateId (the state a newly formed account set starts in, such as 'New patient'; null clears it) and parallelAccountSetLimit (the most account sets a batch runs at once). Omitted fields stay unchanged. Returns the whole catalog, as muggle-remote-test-chaining-catalog-get does.",
|
|
8215
|
+
inputSchema: TestChainingSettingsUpdateInputSchema,
|
|
8216
|
+
mapToUpstream: (input) => {
|
|
8217
|
+
const toolInput = input;
|
|
8218
|
+
const body = {};
|
|
8219
|
+
if (toolInput.freshAccountSetStateId !== void 0) body.freshAccountSetStateId = toolInput.freshAccountSetStateId;
|
|
8220
|
+
if (toolInput.parallelAccountSetLimit !== void 0) body.parallelAccountSetLimit = toolInput.parallelAccountSetLimit;
|
|
8221
|
+
return {
|
|
8222
|
+
method: "PUT",
|
|
8223
|
+
path: buildTestChainingSettingsPath(toolInput.projectId),
|
|
8224
|
+
body
|
|
8225
|
+
};
|
|
8226
|
+
}
|
|
8227
|
+
},
|
|
8228
|
+
{
|
|
8229
|
+
name: "muggle-remote-test-state-create",
|
|
8230
|
+
description: "Create a test state: a named set of facts about one account set's data, such as 'Booked' or 'Message unread'. List every fact the state's tests rely on, each scoped to the account set (only this account set's data, e.g. the patient's thread with the doctor) or account-wide (one account as a whole, e.g. the doctor's unread count, naming that account's role). check is a quick read-only check that an account set is in the state. Reuse a matching state from muggle-remote-test-chaining-catalog-get before creating one. Returns the created state.",
|
|
8231
|
+
inputSchema: TestStateCreateInputSchema,
|
|
8232
|
+
mapToUpstream: (input) => {
|
|
8233
|
+
const toolInput = input;
|
|
8234
|
+
const body = { name: toolInput.name };
|
|
8235
|
+
if (toolInput.facts !== void 0) body.facts = toolInput.facts;
|
|
8236
|
+
if (toolInput.check !== void 0) body.check = toolInput.check;
|
|
8237
|
+
return {
|
|
8238
|
+
method: "POST",
|
|
8239
|
+
path: buildTestStatesPath(toolInput.projectId),
|
|
8240
|
+
body
|
|
8241
|
+
};
|
|
8242
|
+
}
|
|
8243
|
+
},
|
|
8244
|
+
{
|
|
8245
|
+
name: "muggle-remote-test-state-update",
|
|
8246
|
+
description: "Update a test state's name, facts or check. Omitted fields stay unchanged; facts replaces the state's whole fact list. Returns the updated state.",
|
|
8247
|
+
inputSchema: TestStateUpdateInputSchema,
|
|
8248
|
+
mapToUpstream: (input) => {
|
|
8249
|
+
const toolInput = input;
|
|
8250
|
+
const body = {};
|
|
8251
|
+
if (toolInput.name !== void 0) body.name = toolInput.name;
|
|
8252
|
+
if (toolInput.facts !== void 0) body.facts = toolInput.facts;
|
|
8253
|
+
if (toolInput.check !== void 0) body.check = toolInput.check;
|
|
8254
|
+
return {
|
|
8255
|
+
method: "PUT",
|
|
8256
|
+
path: `${buildTestStatesPath(toolInput.projectId)}/${toolInput.stateId}`,
|
|
8257
|
+
body
|
|
8258
|
+
};
|
|
8259
|
+
}
|
|
8260
|
+
},
|
|
8261
|
+
{
|
|
8262
|
+
name: "muggle-remote-test-state-delete",
|
|
8263
|
+
description: "Delete a test state. Rejected while a test case uses it as its prerequisite or result state, or while it is the project's freshAccountSetStateId.",
|
|
8264
|
+
inputSchema: TestStateDeleteInputSchema,
|
|
8265
|
+
mapToUpstream: (input) => {
|
|
8266
|
+
const toolInput = input;
|
|
8267
|
+
return {
|
|
8268
|
+
method: "DELETE",
|
|
8269
|
+
path: `${buildTestStatesPath(toolInput.projectId)}/${toolInput.stateId}`
|
|
8270
|
+
};
|
|
8271
|
+
},
|
|
8272
|
+
mapFromUpstream: (_response, input) => {
|
|
8273
|
+
const toolInput = input;
|
|
8274
|
+
return { stateId: toolInput.stateId, isDeleted: true };
|
|
8275
|
+
}
|
|
8276
|
+
},
|
|
8277
|
+
{
|
|
8278
|
+
name: "muggle-remote-test-role-create",
|
|
8279
|
+
description: "Create a test role: a kind of account a test acts as, such as Patient or Doctor; two accounts of one kind in one test are two roles. CREATED: Muggle signs each account set's account up itself, and it is disposable. SUPPLIED: the customer provides the account, which is never used up; allowsChanges lets tests change its account-wide facts when a proven way back restores them. Returns the created role.",
|
|
8280
|
+
inputSchema: TestRoleCreateInputSchema,
|
|
8281
|
+
mapToUpstream: (input) => {
|
|
8282
|
+
const toolInput = input;
|
|
8283
|
+
const body = { name: toolInput.name, source: toolInput.source };
|
|
8284
|
+
if (toolInput.allowsChanges !== void 0) body.allowsChanges = toolInput.allowsChanges;
|
|
8285
|
+
return {
|
|
8286
|
+
method: "POST",
|
|
8287
|
+
path: buildTestRolesPath(toolInput.projectId),
|
|
8288
|
+
body
|
|
8289
|
+
};
|
|
8290
|
+
}
|
|
8291
|
+
},
|
|
8292
|
+
{
|
|
8293
|
+
name: "muggle-remote-test-role-update",
|
|
8294
|
+
description: "Update a test role's name, source or allowsChanges. Omitted fields stay unchanged. Returns the updated role.",
|
|
8295
|
+
inputSchema: TestRoleUpdateInputSchema,
|
|
8296
|
+
mapToUpstream: (input) => {
|
|
8297
|
+
const toolInput = input;
|
|
8298
|
+
const body = {};
|
|
8299
|
+
if (toolInput.name !== void 0) body.name = toolInput.name;
|
|
8300
|
+
if (toolInput.source !== void 0) body.source = toolInput.source;
|
|
8301
|
+
if (toolInput.allowsChanges !== void 0) body.allowsChanges = toolInput.allowsChanges;
|
|
8302
|
+
return {
|
|
8303
|
+
method: "PUT",
|
|
8304
|
+
path: `${buildTestRolesPath(toolInput.projectId)}/${toolInput.roleId}`,
|
|
8305
|
+
body
|
|
8306
|
+
};
|
|
8307
|
+
}
|
|
8308
|
+
},
|
|
8309
|
+
{
|
|
8310
|
+
name: "muggle-remote-test-role-delete",
|
|
8311
|
+
description: "Delete a test role. Rejected while a test case acts as it or a state's account-wide fact names it.",
|
|
8312
|
+
inputSchema: TestRoleDeleteInputSchema,
|
|
8313
|
+
mapToUpstream: (input) => {
|
|
8314
|
+
const toolInput = input;
|
|
8315
|
+
return {
|
|
8316
|
+
method: "DELETE",
|
|
8317
|
+
path: `${buildTestRolesPath(toolInput.projectId)}/${toolInput.roleId}`
|
|
8318
|
+
};
|
|
8319
|
+
},
|
|
8320
|
+
mapFromUpstream: (_response, input) => {
|
|
8321
|
+
const toolInput = input;
|
|
8322
|
+
return { roleId: toolInput.roleId, isDeleted: true };
|
|
8323
|
+
}
|
|
8324
|
+
},
|
|
8325
|
+
{
|
|
8326
|
+
name: "muggle-remote-test-chaining-plan-get",
|
|
8327
|
+
description: "Preview how the project's test cases would run on account sets. Read-only: it plans from the saved states, roles and test cases and does not run anything. Returns the floor (fewest account sets that can run every placed test), each account set's route in order with setup runs (replays that only reach a state, with no verdict) and the one-way test that forced each account set beyond the first, the groups of states, Not allowed tests that would change a supplied account, tests whose prerequisite state no test reaches, and test cases with no states yet (they run as today). Response shape: { floor, batchLength, totalRuns, setupRuns, account sets: [{ accountSetNumber, slotNumber, steps: [{ testCaseId, prerequisiteStateId, resultStateId, isSetup, heldSuppliedRoleIds }], endStateId, isUsedUp, forcedByTestCaseId }], groups: [{ groupNumber, stateIds, isTerminal }], notAllowed: [{ testCaseId, reason, suppliedRoleIds }], missingState: [{ testCaseId, prerequisiteStateId }], unplacedTestCaseIds, unrestoredSuppliedRoleIds }.",
|
|
8328
|
+
inputSchema: TestChainingPlanGetInputSchema,
|
|
8329
|
+
mapToUpstream: (input) => {
|
|
8330
|
+
const toolInput = input;
|
|
8331
|
+
return {
|
|
8332
|
+
method: "GET",
|
|
8333
|
+
path: buildTestChainingPlanPath(toolInput.projectId),
|
|
8334
|
+
queryParams: { isProduction: toolInput.isProduction }
|
|
8335
|
+
};
|
|
8336
|
+
}
|
|
8337
|
+
}
|
|
8338
|
+
];
|
|
8088
8339
|
var bulkPreviewTools = [
|
|
8089
8340
|
{
|
|
8090
8341
|
name: "muggle-remote-bulk-preview-job-get",
|
|
@@ -9368,6 +9619,7 @@ var allQaToolDefinitions = [
|
|
|
9368
9619
|
...projectTools,
|
|
9369
9620
|
...useCaseTools,
|
|
9370
9621
|
...testCaseTools,
|
|
9622
|
+
...testStateTools,
|
|
9371
9623
|
...bulkPreviewTools,
|
|
9372
9624
|
...testScriptTools,
|
|
9373
9625
|
...actionScriptTools,
|
|
@@ -10708,11 +10960,24 @@ __export(e2e_exports2, {
|
|
|
10708
10960
|
TestCaseGetInputSchema: () => TestCaseGetInputSchema,
|
|
10709
10961
|
TestCaseListByUseCaseInputSchema: () => TestCaseListByUseCaseInputSchema,
|
|
10710
10962
|
TestCaseListInputSchema: () => TestCaseListInputSchema,
|
|
10963
|
+
TestCaseTestStateFieldsSchema: () => TestCaseTestStateFieldsSchema,
|
|
10711
10964
|
TestCaseUpdateInputSchema: () => TestCaseUpdateInputSchema,
|
|
10965
|
+
TestChainingCatalogGetInputSchema: () => TestChainingCatalogGetInputSchema,
|
|
10966
|
+
TestChainingPlanGetInputSchema: () => TestChainingPlanGetInputSchema,
|
|
10967
|
+
TestChainingSettingsUpdateInputSchema: () => TestChainingSettingsUpdateInputSchema,
|
|
10712
10968
|
TestPlanGraphRebuildInputSchema: () => TestPlanGraphRebuildInputSchema,
|
|
10969
|
+
TestRoleCreateInputSchema: () => TestRoleCreateInputSchema,
|
|
10970
|
+
TestRoleDeleteInputSchema: () => TestRoleDeleteInputSchema,
|
|
10971
|
+
TestRoleSourceEnum: () => TestRoleSourceEnum,
|
|
10972
|
+
TestRoleUpdateInputSchema: () => TestRoleUpdateInputSchema,
|
|
10713
10973
|
TestScriptDeleteInputSchema: () => TestScriptDeleteInputSchema,
|
|
10714
10974
|
TestScriptGetInputSchema: () => TestScriptGetInputSchema,
|
|
10715
10975
|
TestScriptListInputSchema: () => TestScriptListInputSchema,
|
|
10976
|
+
TestStateCreateInputSchema: () => TestStateCreateInputSchema,
|
|
10977
|
+
TestStateDeleteInputSchema: () => TestStateDeleteInputSchema,
|
|
10978
|
+
TestStateFactSchema: () => TestStateFactSchema,
|
|
10979
|
+
TestStateFactScopeEnum: () => TestStateFactScopeEnum,
|
|
10980
|
+
TestStateUpdateInputSchema: () => TestStateUpdateInputSchema,
|
|
10716
10981
|
TokenPackageIdSchema: () => TokenPackageIdSchema,
|
|
10717
10982
|
TokenUsageFilterTypeSchema: () => TokenUsageFilterTypeSchema,
|
|
10718
10983
|
UseCaseCandidatesApproveInputSchema: () => UseCaseCandidatesApproveInputSchema,
|
package/dist/cli.js
CHANGED
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { src_exports as commands, createUnifiedMcpServer, server_exports as server } from './chunk-
|
|
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-
|
|
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,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "muggle",
|
|
3
3
|
"description": "Real-browser E2E acceptance for your coding agent: test the flows you changed and open the PR with screenshots. Built for flows other tools can't reach: managed test identities with live inboxes (magic links, email OTP and 2FA, verification mail, password resets), an encrypted secret store for login and test credentials, and a test graph that runs prerequisite flows first. Plain-English tests become replayable scripts on localhost or preview URLs.",
|
|
4
|
-
"version": "5.31.0-staging.
|
|
4
|
+
"version": "5.31.0-staging.271",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Muggle AI",
|
|
7
7
|
"email": "support@muggle-ai.com"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "muggle",
|
|
3
|
-
"version": "5.31.0-staging.
|
|
3
|
+
"version": "5.31.0-staging.271",
|
|
4
4
|
"description": "Real-browser E2E acceptance for your coding agent: test the flows you changed and open the PR with screenshots. Built for flows other tools can't reach: managed test identities with live inboxes (magic links, email OTP and 2FA, verification mail, password resets), an encrypted secret store for login and test credentials, and a test graph that runs prerequisite flows first. Plain-English tests become replayable scripts on localhost or preview URLs.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Muggle AI",
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "muggle",
|
|
3
3
|
"displayName": "Muggle AI",
|
|
4
4
|
"description": "Real-browser E2E acceptance for your coding agent: test the flows you changed and open the PR with screenshots. Built for flows other tools can't reach: managed test identities with live inboxes (magic links, email OTP and 2FA, verification mail, password resets), an encrypted secret store for login and test credentials, and a test graph that runs prerequisite flows first. Plain-English tests become replayable scripts on localhost or preview URLs.",
|
|
5
|
-
"version": "5.31.0-staging.
|
|
5
|
+
"version": "5.31.0-staging.271",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Muggle AI",
|
|
8
8
|
"email": "support@muggle-ai.com"
|
package/dist/plugin/.mcp.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"release": "5.26.0",
|
|
3
|
-
"buildId": "run-
|
|
4
|
-
"commitSha": "
|
|
5
|
-
"buildTime": "2026-
|
|
3
|
+
"buildId": "run-271-1",
|
|
4
|
+
"commitSha": "5036fc9c7a6ea65a6269500436e8eb9b6c443413",
|
|
5
|
+
"buildTime": "2026-11-01T05:29:33Z",
|
|
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-
|
|
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';
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@muggleai/works",
|
|
3
3
|
"mcpName": "io.github.multiplex-ai/muggle",
|
|
4
|
-
"version": "5.31.0-staging.
|
|
4
|
+
"version": "5.31.0-staging.271",
|
|
5
5
|
"description": "13 years of Microsoft hyperscale engineering, packaged as a harness for your AI coding agent.",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "dist/index.js",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "muggle",
|
|
3
3
|
"description": "Real-browser E2E acceptance for your coding agent: test the flows you changed and open the PR with screenshots. Built for flows other tools can't reach: managed test identities with live inboxes (magic links, email OTP and 2FA, verification mail, password resets), an encrypted secret store for login and test credentials, and a test graph that runs prerequisite flows first. Plain-English tests become replayable scripts on localhost or preview URLs.",
|
|
4
|
-
"version": "5.31.0-staging.
|
|
4
|
+
"version": "5.31.0-staging.271",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Muggle AI",
|
|
7
7
|
"email": "support@muggle-ai.com"
|
package/plugin/.codex-mcp.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "muggle",
|
|
3
|
-
"version": "5.31.0-staging.
|
|
3
|
+
"version": "5.31.0-staging.271",
|
|
4
4
|
"description": "Real-browser E2E acceptance for your coding agent: test the flows you changed and open the PR with screenshots. Built for flows other tools can't reach: managed test identities with live inboxes (magic links, email OTP and 2FA, verification mail, password resets), an encrypted secret store for login and test credentials, and a test graph that runs prerequisite flows first. Plain-English tests become replayable scripts on localhost or preview URLs.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Muggle AI",
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "muggle",
|
|
3
3
|
"displayName": "Muggle AI",
|
|
4
4
|
"description": "Real-browser E2E acceptance for your coding agent: test the flows you changed and open the PR with screenshots. Built for flows other tools can't reach: managed test identities with live inboxes (magic links, email OTP and 2FA, verification mail, password resets), an encrypted secret store for login and test credentials, and a test graph that runs prerequisite flows first. Plain-English tests become replayable scripts on localhost or preview URLs.",
|
|
5
|
-
"version": "5.31.0-staging.
|
|
5
|
+
"version": "5.31.0-staging.271",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Muggle AI",
|
|
8
8
|
"email": "support@muggle-ai.com"
|
package/plugin/.mcp.json
CHANGED
|
@@ -4,10 +4,11 @@ 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)
|
|
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.
|
|
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
|
-
- **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
|
|
10
|
-
- **
|
|
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
|
+
- **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`.
|
|
11
|
+
- **Records and alerts.** Results go to `status.json` (last snapshot, with open-PR and Remote Control coverage), `state.json` (active findings, recovery attempts, reload progress, the login last seen, Remote Control restarts) and `events.jsonl` (raise/clear/resume/respawn/close-out history) under `~/.muggle-ai/watch/`. New alerts raise one desktop notification per tick.
|
|
11
12
|
|
|
12
13
|
## Install
|
|
13
14
|
|
|
@@ -43,6 +44,7 @@ On Windows the PowerShell installer also takes flags directly:
|
|
|
43
44
|
| `-IntervalMinutes <n>` | Tick interval, default 2 |
|
|
44
45
|
| `-NoRecovery` | Report dead watchers without resuming their owners |
|
|
45
46
|
| `-NoAutoReload` | Never restart sessions onto a new build |
|
|
47
|
+
| `-NoRemoteControl` | Report sessions off Remote Control without restarting them |
|
|
46
48
|
| `-NoWait` | Return once the task is registered, before its first tick |
|
|
47
49
|
| `-MuggleEntry <path>` | Run a specific `bin\muggle.js` instead of the global install |
|
|
48
50
|
| `-Uninstall` | Remove the task; the ledger is kept |
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
# Installs the MuggleWatch scheduled task for the current user (no elevation):
|
|
2
2
|
# every -IntervalMinutes it runs `muggle watch tick`, which snapshots Claude and
|
|
3
3
|
# Codex sessions, their processes and every PR-watch slot, alerts on dead or deaf
|
|
4
|
-
# watchers, red CI and stale branches,
|
|
5
|
-
#
|
|
4
|
+
# watchers, red CI and stale branches, restarts the loop of a registered slot
|
|
5
|
+
# whose watcher died, fans plugin reloads out across sessions, and restarts
|
|
6
|
+
# sessions that are off Remote Control under the current login.
|
|
6
7
|
#
|
|
7
8
|
# The task runs the global @muggleai/works install, whose path survives
|
|
8
9
|
# `muggle upgrade`, so the service always runs the current release. Only the
|
|
@@ -20,6 +21,7 @@ param(
|
|
|
20
21
|
[string]$MuggleEntry,
|
|
21
22
|
[switch]$NoAutoReload,
|
|
22
23
|
[switch]$NoRecovery,
|
|
24
|
+
[switch]$NoRemoteControl,
|
|
23
25
|
# Return once the task is registered; its trigger runs the first tick a minute later.
|
|
24
26
|
[switch]$NoWait,
|
|
25
27
|
[switch]$Uninstall
|
|
@@ -61,6 +63,7 @@ Copy-Item (Join-Path $PSScriptRoot "muggle-watch-hidden.vbs") $installedLauncher
|
|
|
61
63
|
$tickFlags = @()
|
|
62
64
|
if ($NoAutoReload) { $tickFlags += "--no-auto-reload" }
|
|
63
65
|
if ($NoRecovery) { $tickFlags += "--no-recovery" }
|
|
66
|
+
if ($NoRemoteControl) { $tickFlags += "--no-remote-control" }
|
|
64
67
|
$launcherArguments = @("`"$installedLauncher`"", "`"$($nodeCommand.Source)`"", "`"$MuggleEntry`"") + $tickFlags
|
|
65
68
|
|
|
66
69
|
# wscript.exe rather than node.exe is what keeps every run off the desktop; see
|