@ait-co/devtools 0.1.89 → 0.1.90
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/dist/mcp/cli.js +170 -44
- package/dist/mcp/cli.js.map +1 -1
- package/dist/mcp/server.js +1 -1
- package/dist/mcp/server.js.map +1 -1
- package/dist/panel/index.js +2 -2
- package/dist/unplugin/index.cjs.map +1 -1
- package/dist/unplugin/index.js.map +1 -1
- package/package.json +1 -1
package/dist/mcp/cli.js
CHANGED
|
@@ -84,6 +84,40 @@ function startParentWatcher(onOrphaned, opts) {
|
|
|
84
84
|
clearInterval(handle);
|
|
85
85
|
} };
|
|
86
86
|
}
|
|
87
|
+
/**
|
|
88
|
+
* Starts a periodic watchdog that calls `onExpired` once after `maxAgeMs`
|
|
89
|
+
* milliseconds have elapsed since the watchdog was created.
|
|
90
|
+
*
|
|
91
|
+
* Motivation (issue #571): cloudflared quick-tunnel lifetimes are finite (a
|
|
92
|
+
* few hours). A daemon that has been running for days will have outlived its
|
|
93
|
+
* tunnel regardless of whether the tunnel process exited cleanly. This watchdog
|
|
94
|
+
* caps the daemon's maximum age and forces a fresh start so the tunnel is
|
|
95
|
+
* replaced before it silently expires.
|
|
96
|
+
*
|
|
97
|
+
* @param onExpired - Called once when the maximum age is reached. The caller
|
|
98
|
+
* should call `shutdown()` then `process.exit(0)`.
|
|
99
|
+
* @param opts.maxAgeMs - Maximum daemon lifetime in ms. Default 6 h.
|
|
100
|
+
* @param opts.intervalMs - Check interval in ms. Default 60 000 (1 min).
|
|
101
|
+
* @param opts.now - Time source (injectable for tests). Default `Date.now`.
|
|
102
|
+
*
|
|
103
|
+
* @returns `stop` — call during shutdown to clear the interval.
|
|
104
|
+
*/
|
|
105
|
+
function startMaxAgeWatchdog(onExpired, opts = {}) {
|
|
106
|
+
const { maxAgeMs = 360 * 60 * 1e3, intervalMs = 6e4, now = () => Date.now() } = opts;
|
|
107
|
+
const startedAt = now();
|
|
108
|
+
let fired = false;
|
|
109
|
+
const handle = setInterval(() => {
|
|
110
|
+
if (fired) return;
|
|
111
|
+
if (now() - startedAt >= maxAgeMs) {
|
|
112
|
+
fired = true;
|
|
113
|
+
clearInterval(handle);
|
|
114
|
+
onExpired();
|
|
115
|
+
}
|
|
116
|
+
}, intervalMs);
|
|
117
|
+
return { stop() {
|
|
118
|
+
clearInterval(handle);
|
|
119
|
+
} };
|
|
120
|
+
}
|
|
87
121
|
//#endregion
|
|
88
122
|
//#region src/mcp/ait-chii-source.ts
|
|
89
123
|
function isObject$4(value) {
|
|
@@ -3491,10 +3525,12 @@ function readLock(lockPath) {
|
|
|
3491
3525
|
const parsed = JSON.parse(raw);
|
|
3492
3526
|
if (typeof parsed === "object" && parsed !== null && "pid" in parsed && typeof parsed.pid === "number" && "startedAt" in parsed && typeof parsed.startedAt === "string") {
|
|
3493
3527
|
const p = parsed;
|
|
3528
|
+
const tunnelChildPid = typeof p.tunnelChildPid === "number" ? p.tunnelChildPid : null;
|
|
3494
3529
|
return {
|
|
3495
3530
|
pid: p.pid,
|
|
3496
3531
|
wssUrl: typeof p.wssUrl === "string" ? p.wssUrl : null,
|
|
3497
|
-
startedAt: p.startedAt
|
|
3532
|
+
startedAt: p.startedAt,
|
|
3533
|
+
tunnelChildPid
|
|
3498
3534
|
};
|
|
3499
3535
|
}
|
|
3500
3536
|
return null;
|
|
@@ -3560,16 +3596,19 @@ function acquireLock(options = {}) {
|
|
|
3560
3596
|
const { force = false } = options;
|
|
3561
3597
|
const lockPath = lockFilePath();
|
|
3562
3598
|
const existing = readLock(lockPath);
|
|
3563
|
-
if (existing !== null) if (isPidAlive(existing.pid))
|
|
3564
|
-
|
|
3565
|
-
|
|
3566
|
-
|
|
3567
|
-
|
|
3568
|
-
|
|
3569
|
-
|
|
3570
|
-
|
|
3571
|
-
|
|
3572
|
-
|
|
3599
|
+
if (existing !== null) if (isPidAlive(existing.pid)) {
|
|
3600
|
+
const tunnelChildPid = existing.tunnelChildPid;
|
|
3601
|
+
if (typeof tunnelChildPid === "number" && !isPidAlive(tunnelChildPid)) process.stderr.write(`[ait-debug] stale lock: holder PID=${existing.pid} alive but tunnel child PID=${tunnelChildPid} is dead — reclaiming lock.\n`);
|
|
3602
|
+
else if (force) {
|
|
3603
|
+
process.stderr.write(`[ait-debug] --force: terminating existing session PID=${existing.pid} …\n`);
|
|
3604
|
+
killAndWait(existing.pid);
|
|
3605
|
+
process.stderr.write(`[ait-debug] --force: PID=${existing.pid} stopped, taking over.\n`);
|
|
3606
|
+
} else {
|
|
3607
|
+
const urlPart = existing.wssUrl != null ? `wssUrl=${existing.wssUrl}` : "wssUrl=(tunnel starting)";
|
|
3608
|
+
process.stderr.write(`[ait-debug] 기존 debug-mode 세션이 이미 실행 중 — PID=${existing.pid}, started ${existing.startedAt}, ${urlPart}\n[ait-debug] 회복: \`kill ${existing.pid}\` 또는 \`npx @ait-co/devtools devtools-mcp --force\`\n`);
|
|
3609
|
+
throw new ServerLockConflictError(existing.pid, existing.wssUrl, existing.startedAt);
|
|
3610
|
+
}
|
|
3611
|
+
} else process.stderr.write(`[ait-debug] stale lock from PID ${existing.pid} recovered — starting fresh.\n`);
|
|
3573
3612
|
const data = {
|
|
3574
3613
|
pid: process.pid,
|
|
3575
3614
|
wssUrl: null,
|
|
@@ -3583,6 +3622,11 @@ function acquireLock(options = {}) {
|
|
|
3583
3622
|
data.wssUrl = wssUrl;
|
|
3584
3623
|
writeLock(lockPath, data);
|
|
3585
3624
|
},
|
|
3625
|
+
updateTunnelChildPid(pid) {
|
|
3626
|
+
if (released) return;
|
|
3627
|
+
data.tunnelChildPid = pid;
|
|
3628
|
+
writeLock(lockPath, data);
|
|
3629
|
+
},
|
|
3586
3630
|
release() {
|
|
3587
3631
|
if (released) return;
|
|
3588
3632
|
released = true;
|
|
@@ -4800,7 +4844,7 @@ async function readMcpSdkVersion() {
|
|
|
4800
4844
|
* some test environments that skip the build step).
|
|
4801
4845
|
*/
|
|
4802
4846
|
function readDevtoolsVersion() {
|
|
4803
|
-
return "0.1.
|
|
4847
|
+
return "0.1.90";
|
|
4804
4848
|
}
|
|
4805
4849
|
/**
|
|
4806
4850
|
* Derives the next recommended action from a completed diagnostics snapshot.
|
|
@@ -4859,7 +4903,7 @@ function computeNextRecommendedAction(tunnel, pages, env, authRejects = null) {
|
|
|
4859
4903
|
* - Lock file data contains only pid + startedAt + wssUrl — no secrets.
|
|
4860
4904
|
*/
|
|
4861
4905
|
async function getDiagnostics(input) {
|
|
4862
|
-
const { tunnel, connection, env, envReason, collector, readLock: readLockFn, recentErrorsLimit = 10, getMcpVersion = readMcpSdkVersion, checkParentAlive = () => isPidAlive(process.ppid) } = input;
|
|
4906
|
+
const { tunnel, connection, env, envReason, collector, readLock: readLockFn, recentErrorsLimit = 10, getMcpVersion = readMcpSdkVersion, checkParentAlive = () => isPidAlive(process.ppid), tunnelChildPid } = input;
|
|
4863
4907
|
const [mcpVersion, devtoolsVersion] = await Promise.all([getMcpVersion(), Promise.resolve(readDevtoolsVersion())]);
|
|
4864
4908
|
const lockData = readLockFn();
|
|
4865
4909
|
const serverLockHolder = lockData ? {
|
|
@@ -4867,8 +4911,11 @@ async function getDiagnostics(input) {
|
|
|
4867
4911
|
startedAt: lockData.startedAt,
|
|
4868
4912
|
wssUrl: lockData.wssUrl
|
|
4869
4913
|
} : null;
|
|
4914
|
+
const effectiveTunnelChildPid = tunnelChildPid ?? lockData?.tunnelChildPid ?? null;
|
|
4915
|
+
let effectiveUp = tunnel.up;
|
|
4916
|
+
if (tunnel.up && typeof effectiveTunnelChildPid === "number" && effectiveTunnelChildPid !== null && !isPidAlive(effectiveTunnelChildPid)) effectiveUp = false;
|
|
4870
4917
|
const tunnelInfo = {
|
|
4871
|
-
up:
|
|
4918
|
+
up: effectiveUp,
|
|
4872
4919
|
wssUrl: tunnel.wssUrl,
|
|
4873
4920
|
pid: lockData?.pid ?? null,
|
|
4874
4921
|
startedAt: lockData?.startedAt ?? null,
|
|
@@ -4956,6 +5003,11 @@ async function ensureCloudflaredBin() {
|
|
|
4956
5003
|
/**
|
|
4957
5004
|
* Opens a cloudflared quick tunnel to the local relay port and resolves once
|
|
4958
5005
|
* the public URL is assigned.
|
|
5006
|
+
*
|
|
5007
|
+
* FIX 1 (issue #571): after URL resolution the returned `QuickTunnel` object
|
|
5008
|
+
* watches the cloudflared child process for unexpected exits and calls any
|
|
5009
|
+
* registered `onUnexpectedExit` callback so the health probe can immediately
|
|
5010
|
+
* trigger reissue instead of waiting for the next poll interval.
|
|
4959
5011
|
*/
|
|
4960
5012
|
async function startQuickTunnel(localPort) {
|
|
4961
5013
|
await ensureCloudflaredBin();
|
|
@@ -4982,10 +5034,20 @@ async function startQuickTunnel(localPort) {
|
|
|
4982
5034
|
tunnel.once("error", onError);
|
|
4983
5035
|
tunnel.once("exit", onExit);
|
|
4984
5036
|
});
|
|
5037
|
+
let intentionalStop = false;
|
|
5038
|
+
let unexpectedExitCb = null;
|
|
5039
|
+
tunnel.once("exit", (code) => {
|
|
5040
|
+
if (!intentionalStop && unexpectedExitCb !== null) unexpectedExitCb(code);
|
|
5041
|
+
});
|
|
4985
5042
|
return {
|
|
4986
5043
|
url,
|
|
4987
5044
|
wssUrl: url.replace(/^https/, "wss"),
|
|
4988
|
-
|
|
5045
|
+
childPid: tunnel.process?.pid,
|
|
5046
|
+
onUnexpectedExit(cb) {
|
|
5047
|
+
unexpectedExitCb = cb;
|
|
5048
|
+
},
|
|
5049
|
+
stop() {
|
|
5050
|
+
intentionalStop = true;
|
|
4989
5051
|
tunnel.stop();
|
|
4990
5052
|
}
|
|
4991
5053
|
};
|
|
@@ -5107,6 +5169,10 @@ async function probeTunnel(httpsUrl, timeoutMs = 1e4) {
|
|
|
5107
5169
|
* times). On success the caller is notified via `onReissue`; on permanent
|
|
5108
5170
|
* failure via `onPermanentDrop`.
|
|
5109
5171
|
*
|
|
5172
|
+
* FIX 1 (issue #571): the probe also subscribes to each tunnel's
|
|
5173
|
+
* `onUnexpectedExit` callback to detect child death *immediately* instead of
|
|
5174
|
+
* waiting for the next probe interval (which could be 60 s away).
|
|
5175
|
+
*
|
|
5110
5176
|
* @returns `stop` — call during server shutdown to clear the probe interval.
|
|
5111
5177
|
*/
|
|
5112
5178
|
function startTunnelHealthProbe(initialTunnel, localPort, options) {
|
|
@@ -5115,6 +5181,43 @@ function startTunnelHealthProbe(initialTunnel, localPort, options) {
|
|
|
5115
5181
|
let consecutiveFailures = 0;
|
|
5116
5182
|
let reissueAttempts = 0;
|
|
5117
5183
|
let stopped = false;
|
|
5184
|
+
const doReissueOrDrop = async () => {
|
|
5185
|
+
if (stopped) return;
|
|
5186
|
+
reissueAttempts += 1;
|
|
5187
|
+
if (reissueAttempts > 3) return;
|
|
5188
|
+
log(`[ait-debug] tunnel drop detected — reissuing (attempt ${reissueAttempts}/3)\n`);
|
|
5189
|
+
try {
|
|
5190
|
+
const newTunnel = await spawnTunnel(localPort);
|
|
5191
|
+
try {
|
|
5192
|
+
currentTunnel.stop();
|
|
5193
|
+
} catch {}
|
|
5194
|
+
currentTunnel = newTunnel;
|
|
5195
|
+
consecutiveFailures = 0;
|
|
5196
|
+
armChildExitWatch(newTunnel);
|
|
5197
|
+
log(`[ait-debug] tunnel reissued — new relay: ${newTunnel.wssUrl}\n`);
|
|
5198
|
+
onReissue(newTunnel);
|
|
5199
|
+
} catch (err) {
|
|
5200
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
5201
|
+
log(`[ait-debug] tunnel reissue attempt ${reissueAttempts} failed: ${message}\n`);
|
|
5202
|
+
if (reissueAttempts >= 3) {
|
|
5203
|
+
clearInterval(handle);
|
|
5204
|
+
stopped = true;
|
|
5205
|
+
const droppedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
5206
|
+
log(`[ait-debug] tunnel permanently dropped after 3 reissue attempts — restart the debug server to continue (npx @ait-co/devtools devtools-mcp).
|
|
5207
|
+
`);
|
|
5208
|
+
onPermanentDrop(droppedAt);
|
|
5209
|
+
}
|
|
5210
|
+
}
|
|
5211
|
+
};
|
|
5212
|
+
const armChildExitWatch = (t) => {
|
|
5213
|
+
t.onUnexpectedExit((code) => {
|
|
5214
|
+
if (stopped) return;
|
|
5215
|
+
log(`[ait-debug] cloudflared child exited unexpectedly (code=${code}) — triggering immediate reissue\n`);
|
|
5216
|
+
consecutiveFailures = failuresBeforeReissue;
|
|
5217
|
+
doReissueOrDrop();
|
|
5218
|
+
});
|
|
5219
|
+
};
|
|
5220
|
+
armChildExitWatch(initialTunnel);
|
|
5118
5221
|
const handle = setInterval(() => {
|
|
5119
5222
|
(async () => {
|
|
5120
5223
|
if (stopped) return;
|
|
@@ -5128,30 +5231,7 @@ function startTunnelHealthProbe(initialTunnel, localPort, options) {
|
|
|
5128
5231
|
consecutiveFailures += 1;
|
|
5129
5232
|
log(`[ait-debug] tunnel health probe: failure ${consecutiveFailures}/${failuresBeforeReissue} (url=${httpsUrl})\n`);
|
|
5130
5233
|
if (consecutiveFailures < failuresBeforeReissue) return;
|
|
5131
|
-
|
|
5132
|
-
if (reissueAttempts > 3) return;
|
|
5133
|
-
log(`[ait-debug] tunnel drop detected — reissuing (attempt ${reissueAttempts}/3)\n`);
|
|
5134
|
-
try {
|
|
5135
|
-
const newTunnel = await spawnTunnel(localPort);
|
|
5136
|
-
try {
|
|
5137
|
-
currentTunnel.stop();
|
|
5138
|
-
} catch {}
|
|
5139
|
-
currentTunnel = newTunnel;
|
|
5140
|
-
consecutiveFailures = 0;
|
|
5141
|
-
log(`[ait-debug] tunnel reissued — new relay: ${newTunnel.wssUrl}\n`);
|
|
5142
|
-
onReissue(newTunnel);
|
|
5143
|
-
} catch (err) {
|
|
5144
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
5145
|
-
log(`[ait-debug] tunnel reissue attempt ${reissueAttempts} failed: ${message}\n`);
|
|
5146
|
-
if (reissueAttempts >= 3) {
|
|
5147
|
-
clearInterval(handle);
|
|
5148
|
-
stopped = true;
|
|
5149
|
-
const droppedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
5150
|
-
log(`[ait-debug] tunnel permanently dropped after 3 reissue attempts — restart the debug server to continue (npx @ait-co/devtools devtools-mcp).
|
|
5151
|
-
`);
|
|
5152
|
-
onPermanentDrop(droppedAt);
|
|
5153
|
-
}
|
|
5154
|
-
}
|
|
5234
|
+
await doReissueOrDrop();
|
|
5155
5235
|
})();
|
|
5156
5236
|
}, probeIntervalMs);
|
|
5157
5237
|
return { stop() {
|
|
@@ -5301,15 +5381,16 @@ function waitForAttachWithEvents(connection, filterFn, timeoutMs, pollIntervalMs
|
|
|
5301
5381
|
* naturally via `enableDomains`). The tier only controls visibility.
|
|
5302
5382
|
*/
|
|
5303
5383
|
function createDebugServer(deps) {
|
|
5304
|
-
const { connection, router: routerDep, aitSource, getTunnelStatus, waitForAttachTimeoutMs = 6e4, qrHttpServer, getEnvironment: getEnvDep, getEnvironmentReason: getEnvReasonDep, diagnosticsCollector: collectorDep, totpSecret, onAttachUrlBuilt } = deps;
|
|
5384
|
+
const { connection, router: routerDep, aitSource, getTunnelStatus, waitForAttachTimeoutMs = 6e4, qrHttpServer, getEnvironment: getEnvDep, getEnvironmentReason: getEnvReasonDep, diagnosticsCollector: collectorDep, totpSecret, onAttachUrlBuilt, getTunnelChildPid, readLock: readLockDep } = deps;
|
|
5305
5385
|
const getTotpSecret = deps.getTotpSecret ?? (() => totpSecret);
|
|
5386
|
+
const readLockFn = readLockDep ?? readServerLock;
|
|
5306
5387
|
const router = routerDep ?? makeSingleConnectionRouter(connection);
|
|
5307
5388
|
const resolveEnvironment = getEnvDep ?? (() => deriveEnvironment(router.active.kind, getLiveIntent(), router.activeRelayOrigin));
|
|
5308
5389
|
const resolveEnvironmentReason = getEnvReasonDep ?? (() => `derived:kind=${router.active.kind},liveIntent=${getLiveIntent()}`);
|
|
5309
5390
|
const collector = collectorDep ?? new InMemoryDiagnosticsCollector();
|
|
5310
5391
|
const server = new Server({
|
|
5311
5392
|
name: "ait-debug",
|
|
5312
|
-
version: "0.1.
|
|
5393
|
+
version: "0.1.90"
|
|
5313
5394
|
}, { capabilities: { tools: { listChanged: true } } });
|
|
5314
5395
|
server.setRequestHandler(ListToolsRequestSchema, () => {
|
|
5315
5396
|
const conn = router.active;
|
|
@@ -5374,8 +5455,9 @@ function createDebugServer(deps) {
|
|
|
5374
5455
|
env,
|
|
5375
5456
|
envReason,
|
|
5376
5457
|
collector,
|
|
5377
|
-
readLock:
|
|
5378
|
-
recentErrorsLimit
|
|
5458
|
+
readLock: readLockFn,
|
|
5459
|
+
recentErrorsLimit,
|
|
5460
|
+
tunnelChildPid: getTunnelChildPid?.() ?? void 0
|
|
5379
5461
|
}), name, env, conn.listTargets().length > 0);
|
|
5380
5462
|
} catch (err) {
|
|
5381
5463
|
return errorResult(err, name);
|
|
@@ -6068,12 +6150,14 @@ async function bootRelayFamily(options = {}) {
|
|
|
6068
6150
|
tunnel = t;
|
|
6069
6151
|
tunnelStatus = makeTunnelStatus(true, t.wssUrl);
|
|
6070
6152
|
options.onWssUrl?.(t.wssUrl);
|
|
6153
|
+
if (t.childPid !== void 0) options.onTunnelChildPid?.(t.childPid);
|
|
6071
6154
|
logInfo("tunnel.up", { totpEnabled });
|
|
6072
6155
|
tunnelProbe = startTunnelHealthProbe(t, relay.port, {
|
|
6073
6156
|
onReissue: (newTunnel) => {
|
|
6074
6157
|
tunnel = newTunnel;
|
|
6075
6158
|
tunnelStatus = makeTunnelStatus(true, newTunnel.wssUrl, null, 0);
|
|
6076
6159
|
options.onWssUrl?.(newTunnel.wssUrl);
|
|
6160
|
+
if (newTunnel.childPid !== void 0) options.onTunnelChildPid?.(newTunnel.childPid);
|
|
6077
6161
|
printAttachBanner({
|
|
6078
6162
|
wssUrl: newTunnel.wssUrl,
|
|
6079
6163
|
totpEnabled
|
|
@@ -6406,6 +6490,7 @@ async function runDebugServer(options = {}) {
|
|
|
6406
6490
|
const lockHandle = acquireLock({ force: options.force ?? false });
|
|
6407
6491
|
const devtoolsOpener = new AutoDevtoolsOpener();
|
|
6408
6492
|
const diagnosticsCollector = new InMemoryDiagnosticsCollector();
|
|
6493
|
+
let activeTunnelChildPid = null;
|
|
6409
6494
|
const router = new DualConnectionRouter({
|
|
6410
6495
|
bootLazyFor: async (key, projectRoot) => key === "relay-sandbox" ? bootExternalRelayFamily(await readMobileRelayBaseUrl(process.env, projectRoot), await readRelayLocalUrl(process.env, projectRoot)) : key === "local-browser" ? bootLocalFamily() : bootRelayFamily({
|
|
6411
6496
|
relayPort: options.relayPort,
|
|
@@ -6414,6 +6499,10 @@ async function runDebugServer(options = {}) {
|
|
|
6414
6499
|
lockHandle.updateWssUrl(wssUrl);
|
|
6415
6500
|
qrServer?.notifyStateChange();
|
|
6416
6501
|
},
|
|
6502
|
+
onTunnelChildPid: (pid) => {
|
|
6503
|
+
activeTunnelChildPid = pid;
|
|
6504
|
+
lockHandle.updateTunnelChildPid(pid);
|
|
6505
|
+
},
|
|
6417
6506
|
onAuthReject: () => diagnosticsCollector.recordAuthReject()
|
|
6418
6507
|
}),
|
|
6419
6508
|
diagnosticsCollector,
|
|
@@ -6485,6 +6574,7 @@ async function runDebugServer(options = {}) {
|
|
|
6485
6574
|
router,
|
|
6486
6575
|
aitSource,
|
|
6487
6576
|
getTunnelStatus: () => router.relayTunnelStatus(),
|
|
6577
|
+
getTunnelChildPid: () => activeTunnelChildPid,
|
|
6488
6578
|
get qrHttpServer() {
|
|
6489
6579
|
return qrServer;
|
|
6490
6580
|
},
|
|
@@ -6498,10 +6588,12 @@ async function runDebugServer(options = {}) {
|
|
|
6498
6588
|
const transport = new StdioServerTransport();
|
|
6499
6589
|
let closed = false;
|
|
6500
6590
|
let parentWatcher = null;
|
|
6591
|
+
let maxAgeWatchdog = null;
|
|
6501
6592
|
const shutdown = () => {
|
|
6502
6593
|
if (closed) return;
|
|
6503
6594
|
closed = true;
|
|
6504
6595
|
parentWatcher?.stop();
|
|
6596
|
+
maxAgeWatchdog?.stop();
|
|
6505
6597
|
if (totpRefreshHandle) clearInterval(totpRefreshHandle);
|
|
6506
6598
|
router.stopWatcher();
|
|
6507
6599
|
for (const family of router.bootedFamilies()) family.stop();
|
|
@@ -6516,6 +6608,7 @@ async function runDebugServer(options = {}) {
|
|
|
6516
6608
|
if (!closed) {
|
|
6517
6609
|
closed = true;
|
|
6518
6610
|
parentWatcher?.stop();
|
|
6611
|
+
maxAgeWatchdog?.stop();
|
|
6519
6612
|
if (totpRefreshHandle) clearInterval(totpRefreshHandle);
|
|
6520
6613
|
router.stopWatcher();
|
|
6521
6614
|
for (const family of router.bootedFamilies()) family.stop();
|
|
@@ -6554,6 +6647,11 @@ async function runDebugServer(options = {}) {
|
|
|
6554
6647
|
process.exit(0);
|
|
6555
6648
|
});
|
|
6556
6649
|
}
|
|
6650
|
+
if (process.env.AIT_DEBUG_NO_MAX_AGE !== "1") maxAgeWatchdog = startMaxAgeWatchdog(() => {
|
|
6651
|
+
process.stderr.write("[ait-debug] max-age watchdog: daemon lifetime exceeded — shutting down for a fresh start.\n");
|
|
6652
|
+
shutdown();
|
|
6653
|
+
process.exit(0);
|
|
6654
|
+
}, { maxAgeMs: process.env.AIT_DEBUG_MAX_AGE_MS ? Number.parseInt(process.env.AIT_DEBUG_MAX_AGE_MS, 10) || void 0 : void 0 });
|
|
6557
6655
|
}
|
|
6558
6656
|
/**
|
|
6559
6657
|
* Serves the debug stack over stdio with the local browser as the default
|
|
@@ -6605,6 +6703,7 @@ async function runLocalDebugServer(options = {}) {
|
|
|
6605
6703
|
};
|
|
6606
6704
|
const devtoolsOpener = new AutoDevtoolsOpener();
|
|
6607
6705
|
const diagnosticsCollector = new InMemoryDiagnosticsCollector();
|
|
6706
|
+
let activeTunnelChildPid = null;
|
|
6608
6707
|
const router = new DualConnectionRouter({
|
|
6609
6708
|
bootLazyFor: async (key, projectRoot) => key === "relay-sandbox" ? bootExternalRelayFamily(await readMobileRelayBaseUrl(process.env, projectRoot), await readRelayLocalUrl(process.env, projectRoot)) : key === "local-browser" ? bootLocalFamilyForEntry() : bootRelayFamily({
|
|
6610
6709
|
verifyAuth: buildRelayVerifyAuth(),
|
|
@@ -6612,6 +6711,10 @@ async function runLocalDebugServer(options = {}) {
|
|
|
6612
6711
|
lockHandle.updateWssUrl(wssUrl);
|
|
6613
6712
|
qrServer?.notifyStateChange();
|
|
6614
6713
|
},
|
|
6714
|
+
onTunnelChildPid: (pid) => {
|
|
6715
|
+
activeTunnelChildPid = pid;
|
|
6716
|
+
lockHandle.updateTunnelChildPid(pid);
|
|
6717
|
+
},
|
|
6615
6718
|
onAuthReject: () => diagnosticsCollector.recordAuthReject()
|
|
6616
6719
|
}),
|
|
6617
6720
|
diagnosticsCollector,
|
|
@@ -6682,6 +6785,7 @@ async function runLocalDebugServer(options = {}) {
|
|
|
6682
6785
|
router,
|
|
6683
6786
|
aitSource,
|
|
6684
6787
|
getTunnelStatus: () => router.relayTunnelStatus(),
|
|
6788
|
+
getTunnelChildPid: () => activeTunnelChildPid,
|
|
6685
6789
|
get qrHttpServer() {
|
|
6686
6790
|
return qrServer;
|
|
6687
6791
|
},
|
|
@@ -6695,10 +6799,12 @@ async function runLocalDebugServer(options = {}) {
|
|
|
6695
6799
|
const transport = new StdioServerTransport();
|
|
6696
6800
|
let closed = false;
|
|
6697
6801
|
let parentWatcher = null;
|
|
6802
|
+
let maxAgeWatchdog = null;
|
|
6698
6803
|
const shutdown = () => {
|
|
6699
6804
|
if (closed) return;
|
|
6700
6805
|
closed = true;
|
|
6701
6806
|
parentWatcher?.stop();
|
|
6807
|
+
maxAgeWatchdog?.stop();
|
|
6702
6808
|
if (totpRefreshHandle) clearInterval(totpRefreshHandle);
|
|
6703
6809
|
router.stopWatcher();
|
|
6704
6810
|
for (const family of router.bootedFamilies()) family.stop();
|
|
@@ -6713,6 +6819,7 @@ async function runLocalDebugServer(options = {}) {
|
|
|
6713
6819
|
if (!closed) {
|
|
6714
6820
|
closed = true;
|
|
6715
6821
|
parentWatcher?.stop();
|
|
6822
|
+
maxAgeWatchdog?.stop();
|
|
6716
6823
|
if (totpRefreshHandle) clearInterval(totpRefreshHandle);
|
|
6717
6824
|
router.stopWatcher();
|
|
6718
6825
|
for (const family of router.bootedFamilies()) family.stop();
|
|
@@ -6753,6 +6860,11 @@ async function runLocalDebugServer(options = {}) {
|
|
|
6753
6860
|
process.exit(0);
|
|
6754
6861
|
});
|
|
6755
6862
|
}
|
|
6863
|
+
if (process.env.AIT_DEBUG_NO_MAX_AGE !== "1") maxAgeWatchdog = startMaxAgeWatchdog(() => {
|
|
6864
|
+
process.stderr.write("[ait-debug] max-age watchdog: daemon lifetime exceeded — shutting down for a fresh start.\n");
|
|
6865
|
+
shutdown();
|
|
6866
|
+
process.exit(0);
|
|
6867
|
+
}, { maxAgeMs: process.env.AIT_DEBUG_MAX_AGE_MS ? Number.parseInt(process.env.AIT_DEBUG_MAX_AGE_MS, 10) || void 0 : void 0 });
|
|
6756
6868
|
}
|
|
6757
6869
|
/**
|
|
6758
6870
|
* Serves the env-2 (real-device PWA) debug stack over stdio with the external
|
|
@@ -6786,6 +6898,7 @@ async function runMobileDebugServer(options = {}) {
|
|
|
6786
6898
|
const lockHandle = acquireLock({ force: options.force ?? false });
|
|
6787
6899
|
const devtoolsOpener = new AutoDevtoolsOpener();
|
|
6788
6900
|
const diagnosticsCollector = new InMemoryDiagnosticsCollector();
|
|
6901
|
+
let activeTunnelChildPid = null;
|
|
6789
6902
|
const router = new DualConnectionRouter({
|
|
6790
6903
|
bootLazyFor: async (key) => key === "relay-sandbox" ? bootExternalRelayFamily(relayBaseUrl, await readRelayLocalUrl(process.env, options.projectRoot ?? process.cwd())) : key === "local-browser" ? bootLocalFamily() : bootRelayFamily({
|
|
6791
6904
|
verifyAuth: buildRelayVerifyAuth(),
|
|
@@ -6793,6 +6906,10 @@ async function runMobileDebugServer(options = {}) {
|
|
|
6793
6906
|
lockHandle.updateWssUrl(wssUrl);
|
|
6794
6907
|
qrServer?.notifyStateChange();
|
|
6795
6908
|
},
|
|
6909
|
+
onTunnelChildPid: (pid) => {
|
|
6910
|
+
activeTunnelChildPid = pid;
|
|
6911
|
+
lockHandle.updateTunnelChildPid(pid);
|
|
6912
|
+
},
|
|
6796
6913
|
onAuthReject: () => diagnosticsCollector.recordAuthReject()
|
|
6797
6914
|
}),
|
|
6798
6915
|
diagnosticsCollector,
|
|
@@ -6863,6 +6980,7 @@ async function runMobileDebugServer(options = {}) {
|
|
|
6863
6980
|
router,
|
|
6864
6981
|
aitSource,
|
|
6865
6982
|
getTunnelStatus: () => router.relayTunnelStatus(),
|
|
6983
|
+
getTunnelChildPid: () => activeTunnelChildPid,
|
|
6866
6984
|
get qrHttpServer() {
|
|
6867
6985
|
return qrServer;
|
|
6868
6986
|
},
|
|
@@ -6876,10 +6994,12 @@ async function runMobileDebugServer(options = {}) {
|
|
|
6876
6994
|
const transport = new StdioServerTransport();
|
|
6877
6995
|
let closed = false;
|
|
6878
6996
|
let parentWatcher = null;
|
|
6997
|
+
let maxAgeWatchdog = null;
|
|
6879
6998
|
const shutdown = () => {
|
|
6880
6999
|
if (closed) return;
|
|
6881
7000
|
closed = true;
|
|
6882
7001
|
parentWatcher?.stop();
|
|
7002
|
+
maxAgeWatchdog?.stop();
|
|
6883
7003
|
if (totpRefreshHandle) clearInterval(totpRefreshHandle);
|
|
6884
7004
|
router.stopWatcher();
|
|
6885
7005
|
for (const family of router.bootedFamilies()) family.stop();
|
|
@@ -6894,6 +7014,7 @@ async function runMobileDebugServer(options = {}) {
|
|
|
6894
7014
|
if (!closed) {
|
|
6895
7015
|
closed = true;
|
|
6896
7016
|
parentWatcher?.stop();
|
|
7017
|
+
maxAgeWatchdog?.stop();
|
|
6897
7018
|
if (totpRefreshHandle) clearInterval(totpRefreshHandle);
|
|
6898
7019
|
router.stopWatcher();
|
|
6899
7020
|
for (const family of router.bootedFamilies()) family.stop();
|
|
@@ -6934,6 +7055,11 @@ async function runMobileDebugServer(options = {}) {
|
|
|
6934
7055
|
process.exit(0);
|
|
6935
7056
|
});
|
|
6936
7057
|
}
|
|
7058
|
+
if (process.env.AIT_DEBUG_NO_MAX_AGE !== "1") maxAgeWatchdog = startMaxAgeWatchdog(() => {
|
|
7059
|
+
process.stderr.write("[ait-debug] max-age watchdog: daemon lifetime exceeded — shutting down for a fresh start.\n");
|
|
7060
|
+
shutdown();
|
|
7061
|
+
process.exit(0);
|
|
7062
|
+
}, { maxAgeMs: process.env.AIT_DEBUG_MAX_AGE_MS ? Number.parseInt(process.env.AIT_DEBUG_MAX_AGE_MS, 10) || void 0 : void 0 });
|
|
6937
7063
|
}
|
|
6938
7064
|
//#endregion
|
|
6939
7065
|
//#region src/mcp/ait-http-source.ts
|
|
@@ -7363,7 +7489,7 @@ function createDevServer(deps = {}) {
|
|
|
7363
7489
|
const aitSource = deps.aitSource ?? new HttpAitSource({ stateEndpoint });
|
|
7364
7490
|
const server = new Server({
|
|
7365
7491
|
name: "ait-devtools",
|
|
7366
|
-
version: "0.1.
|
|
7492
|
+
version: "0.1.90"
|
|
7367
7493
|
}, { capabilities: { tools: {} } });
|
|
7368
7494
|
server.setRequestHandler(ListToolsRequestSchema, () => ({ tools: DEV_TOOL_DEFINITIONS.map((tool) => ({ ...tool })) }));
|
|
7369
7495
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|