@ait-co/devtools 0.1.88 → 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/{chii-relay-BzUf0LH3.cjs → chii-relay-BVVTS3tE.cjs} +23 -9
- package/dist/{chii-relay-BzUf0LH3.cjs.map → chii-relay-BVVTS3tE.cjs.map} +1 -1
- package/dist/{chii-relay-BASitNMw.js → chii-relay-CUS9FJKB.js} +23 -9
- package/dist/{chii-relay-BASitNMw.js.map → chii-relay-CUS9FJKB.js.map} +1 -1
- package/dist/mcp/cli.js +198 -53
- 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 +1 -1
- package/dist/unplugin/index.cjs.map +1 -1
- package/dist/unplugin/index.js +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) {
|
|
@@ -369,7 +403,12 @@ var ChiiCdpConnection = class {
|
|
|
369
403
|
}
|
|
370
404
|
/** Refresh the attached-target list from the relay's `GET /targets`. */
|
|
371
405
|
async refreshTargets() {
|
|
372
|
-
|
|
406
|
+
let targetsUrl = `${this.relayBaseUrl}/targets`;
|
|
407
|
+
if (this.totpSecret) {
|
|
408
|
+
const code = generateTotp(this.totpSecret);
|
|
409
|
+
targetsUrl += `?at=${encodeURIComponent(code)}`;
|
|
410
|
+
}
|
|
411
|
+
const res = await fetch(targetsUrl);
|
|
373
412
|
if (!res.ok) throw new Error(`Chii relay /targets returned HTTP ${res.status} ${res.statusText}`);
|
|
374
413
|
const body = await res.json();
|
|
375
414
|
const list = isObject$3(body) && Array.isArray(body.targets) ? body.targets : [];
|
|
@@ -934,14 +973,28 @@ async function startChiiRelay(options = {}) {
|
|
|
934
973
|
};
|
|
935
974
|
if (verifyAuth) httpServer.on("request", (req, res) => {
|
|
936
975
|
const rewritten = rewriteAtPathPrefix(req.url ?? "");
|
|
937
|
-
if (rewritten
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
976
|
+
if (rewritten !== null) {
|
|
977
|
+
req.url = rewritten;
|
|
978
|
+
if (!verifyAuth(req)) {
|
|
979
|
+
res.statusCode = 401;
|
|
980
|
+
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
981
|
+
res.setHeader("Content-Type", "application/json");
|
|
982
|
+
res.end(JSON.stringify({ error: RELAY_AUTH_REJECT_REASON }));
|
|
983
|
+
notifyAuthReject("http-request");
|
|
984
|
+
}
|
|
985
|
+
return;
|
|
986
|
+
}
|
|
987
|
+
const pathname = (req.url ?? "").split("?")[0];
|
|
988
|
+
if (pathname === "/targets" || pathname === "/targets/") {
|
|
989
|
+
if (!verifyAuth(req)) {
|
|
990
|
+
res.statusCode = 401;
|
|
991
|
+
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
992
|
+
res.setHeader("Content-Type", "application/json");
|
|
993
|
+
res.end(JSON.stringify({ error: RELAY_AUTH_REJECT_REASON }));
|
|
994
|
+
notifyAuthReject("http-request");
|
|
995
|
+
return;
|
|
996
|
+
}
|
|
997
|
+
return;
|
|
945
998
|
}
|
|
946
999
|
});
|
|
947
1000
|
const chiiWssClass = keepaliveIntervalMs > 0 ? tryLoadChiiWssClass() : null;
|
|
@@ -3472,10 +3525,12 @@ function readLock(lockPath) {
|
|
|
3472
3525
|
const parsed = JSON.parse(raw);
|
|
3473
3526
|
if (typeof parsed === "object" && parsed !== null && "pid" in parsed && typeof parsed.pid === "number" && "startedAt" in parsed && typeof parsed.startedAt === "string") {
|
|
3474
3527
|
const p = parsed;
|
|
3528
|
+
const tunnelChildPid = typeof p.tunnelChildPid === "number" ? p.tunnelChildPid : null;
|
|
3475
3529
|
return {
|
|
3476
3530
|
pid: p.pid,
|
|
3477
3531
|
wssUrl: typeof p.wssUrl === "string" ? p.wssUrl : null,
|
|
3478
|
-
startedAt: p.startedAt
|
|
3532
|
+
startedAt: p.startedAt,
|
|
3533
|
+
tunnelChildPid
|
|
3479
3534
|
};
|
|
3480
3535
|
}
|
|
3481
3536
|
return null;
|
|
@@ -3541,16 +3596,19 @@ function acquireLock(options = {}) {
|
|
|
3541
3596
|
const { force = false } = options;
|
|
3542
3597
|
const lockPath = lockFilePath();
|
|
3543
3598
|
const existing = readLock(lockPath);
|
|
3544
|
-
if (existing !== null) if (isPidAlive(existing.pid))
|
|
3545
|
-
|
|
3546
|
-
|
|
3547
|
-
|
|
3548
|
-
|
|
3549
|
-
|
|
3550
|
-
|
|
3551
|
-
|
|
3552
|
-
|
|
3553
|
-
|
|
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`);
|
|
3554
3612
|
const data = {
|
|
3555
3613
|
pid: process.pid,
|
|
3556
3614
|
wssUrl: null,
|
|
@@ -3564,6 +3622,11 @@ function acquireLock(options = {}) {
|
|
|
3564
3622
|
data.wssUrl = wssUrl;
|
|
3565
3623
|
writeLock(lockPath, data);
|
|
3566
3624
|
},
|
|
3625
|
+
updateTunnelChildPid(pid) {
|
|
3626
|
+
if (released) return;
|
|
3627
|
+
data.tunnelChildPid = pid;
|
|
3628
|
+
writeLock(lockPath, data);
|
|
3629
|
+
},
|
|
3567
3630
|
release() {
|
|
3568
3631
|
if (released) return;
|
|
3569
3632
|
released = true;
|
|
@@ -4781,7 +4844,7 @@ async function readMcpSdkVersion() {
|
|
|
4781
4844
|
* some test environments that skip the build step).
|
|
4782
4845
|
*/
|
|
4783
4846
|
function readDevtoolsVersion() {
|
|
4784
|
-
return "0.1.
|
|
4847
|
+
return "0.1.90";
|
|
4785
4848
|
}
|
|
4786
4849
|
/**
|
|
4787
4850
|
* Derives the next recommended action from a completed diagnostics snapshot.
|
|
@@ -4840,7 +4903,7 @@ function computeNextRecommendedAction(tunnel, pages, env, authRejects = null) {
|
|
|
4840
4903
|
* - Lock file data contains only pid + startedAt + wssUrl — no secrets.
|
|
4841
4904
|
*/
|
|
4842
4905
|
async function getDiagnostics(input) {
|
|
4843
|
-
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;
|
|
4844
4907
|
const [mcpVersion, devtoolsVersion] = await Promise.all([getMcpVersion(), Promise.resolve(readDevtoolsVersion())]);
|
|
4845
4908
|
const lockData = readLockFn();
|
|
4846
4909
|
const serverLockHolder = lockData ? {
|
|
@@ -4848,8 +4911,11 @@ async function getDiagnostics(input) {
|
|
|
4848
4911
|
startedAt: lockData.startedAt,
|
|
4849
4912
|
wssUrl: lockData.wssUrl
|
|
4850
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;
|
|
4851
4917
|
const tunnelInfo = {
|
|
4852
|
-
up:
|
|
4918
|
+
up: effectiveUp,
|
|
4853
4919
|
wssUrl: tunnel.wssUrl,
|
|
4854
4920
|
pid: lockData?.pid ?? null,
|
|
4855
4921
|
startedAt: lockData?.startedAt ?? null,
|
|
@@ -4937,6 +5003,11 @@ async function ensureCloudflaredBin() {
|
|
|
4937
5003
|
/**
|
|
4938
5004
|
* Opens a cloudflared quick tunnel to the local relay port and resolves once
|
|
4939
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.
|
|
4940
5011
|
*/
|
|
4941
5012
|
async function startQuickTunnel(localPort) {
|
|
4942
5013
|
await ensureCloudflaredBin();
|
|
@@ -4963,10 +5034,20 @@ async function startQuickTunnel(localPort) {
|
|
|
4963
5034
|
tunnel.once("error", onError);
|
|
4964
5035
|
tunnel.once("exit", onExit);
|
|
4965
5036
|
});
|
|
5037
|
+
let intentionalStop = false;
|
|
5038
|
+
let unexpectedExitCb = null;
|
|
5039
|
+
tunnel.once("exit", (code) => {
|
|
5040
|
+
if (!intentionalStop && unexpectedExitCb !== null) unexpectedExitCb(code);
|
|
5041
|
+
});
|
|
4966
5042
|
return {
|
|
4967
5043
|
url,
|
|
4968
5044
|
wssUrl: url.replace(/^https/, "wss"),
|
|
4969
|
-
|
|
5045
|
+
childPid: tunnel.process?.pid,
|
|
5046
|
+
onUnexpectedExit(cb) {
|
|
5047
|
+
unexpectedExitCb = cb;
|
|
5048
|
+
},
|
|
5049
|
+
stop() {
|
|
5050
|
+
intentionalStop = true;
|
|
4970
5051
|
tunnel.stop();
|
|
4971
5052
|
}
|
|
4972
5053
|
};
|
|
@@ -5088,6 +5169,10 @@ async function probeTunnel(httpsUrl, timeoutMs = 1e4) {
|
|
|
5088
5169
|
* times). On success the caller is notified via `onReissue`; on permanent
|
|
5089
5170
|
* failure via `onPermanentDrop`.
|
|
5090
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
|
+
*
|
|
5091
5176
|
* @returns `stop` — call during server shutdown to clear the probe interval.
|
|
5092
5177
|
*/
|
|
5093
5178
|
function startTunnelHealthProbe(initialTunnel, localPort, options) {
|
|
@@ -5096,6 +5181,43 @@ function startTunnelHealthProbe(initialTunnel, localPort, options) {
|
|
|
5096
5181
|
let consecutiveFailures = 0;
|
|
5097
5182
|
let reissueAttempts = 0;
|
|
5098
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);
|
|
5099
5221
|
const handle = setInterval(() => {
|
|
5100
5222
|
(async () => {
|
|
5101
5223
|
if (stopped) return;
|
|
@@ -5109,30 +5231,7 @@ function startTunnelHealthProbe(initialTunnel, localPort, options) {
|
|
|
5109
5231
|
consecutiveFailures += 1;
|
|
5110
5232
|
log(`[ait-debug] tunnel health probe: failure ${consecutiveFailures}/${failuresBeforeReissue} (url=${httpsUrl})\n`);
|
|
5111
5233
|
if (consecutiveFailures < failuresBeforeReissue) return;
|
|
5112
|
-
|
|
5113
|
-
if (reissueAttempts > 3) return;
|
|
5114
|
-
log(`[ait-debug] tunnel drop detected — reissuing (attempt ${reissueAttempts}/3)\n`);
|
|
5115
|
-
try {
|
|
5116
|
-
const newTunnel = await spawnTunnel(localPort);
|
|
5117
|
-
try {
|
|
5118
|
-
currentTunnel.stop();
|
|
5119
|
-
} catch {}
|
|
5120
|
-
currentTunnel = newTunnel;
|
|
5121
|
-
consecutiveFailures = 0;
|
|
5122
|
-
log(`[ait-debug] tunnel reissued — new relay: ${newTunnel.wssUrl}\n`);
|
|
5123
|
-
onReissue(newTunnel);
|
|
5124
|
-
} catch (err) {
|
|
5125
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
5126
|
-
log(`[ait-debug] tunnel reissue attempt ${reissueAttempts} failed: ${message}\n`);
|
|
5127
|
-
if (reissueAttempts >= 3) {
|
|
5128
|
-
clearInterval(handle);
|
|
5129
|
-
stopped = true;
|
|
5130
|
-
const droppedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
5131
|
-
log(`[ait-debug] tunnel permanently dropped after 3 reissue attempts — restart the debug server to continue (npx @ait-co/devtools devtools-mcp).
|
|
5132
|
-
`);
|
|
5133
|
-
onPermanentDrop(droppedAt);
|
|
5134
|
-
}
|
|
5135
|
-
}
|
|
5234
|
+
await doReissueOrDrop();
|
|
5136
5235
|
})();
|
|
5137
5236
|
}, probeIntervalMs);
|
|
5138
5237
|
return { stop() {
|
|
@@ -5282,15 +5381,16 @@ function waitForAttachWithEvents(connection, filterFn, timeoutMs, pollIntervalMs
|
|
|
5282
5381
|
* naturally via `enableDomains`). The tier only controls visibility.
|
|
5283
5382
|
*/
|
|
5284
5383
|
function createDebugServer(deps) {
|
|
5285
|
-
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;
|
|
5286
5385
|
const getTotpSecret = deps.getTotpSecret ?? (() => totpSecret);
|
|
5386
|
+
const readLockFn = readLockDep ?? readServerLock;
|
|
5287
5387
|
const router = routerDep ?? makeSingleConnectionRouter(connection);
|
|
5288
5388
|
const resolveEnvironment = getEnvDep ?? (() => deriveEnvironment(router.active.kind, getLiveIntent(), router.activeRelayOrigin));
|
|
5289
5389
|
const resolveEnvironmentReason = getEnvReasonDep ?? (() => `derived:kind=${router.active.kind},liveIntent=${getLiveIntent()}`);
|
|
5290
5390
|
const collector = collectorDep ?? new InMemoryDiagnosticsCollector();
|
|
5291
5391
|
const server = new Server({
|
|
5292
5392
|
name: "ait-debug",
|
|
5293
|
-
version: "0.1.
|
|
5393
|
+
version: "0.1.90"
|
|
5294
5394
|
}, { capabilities: { tools: { listChanged: true } } });
|
|
5295
5395
|
server.setRequestHandler(ListToolsRequestSchema, () => {
|
|
5296
5396
|
const conn = router.active;
|
|
@@ -5355,8 +5455,9 @@ function createDebugServer(deps) {
|
|
|
5355
5455
|
env,
|
|
5356
5456
|
envReason,
|
|
5357
5457
|
collector,
|
|
5358
|
-
readLock:
|
|
5359
|
-
recentErrorsLimit
|
|
5458
|
+
readLock: readLockFn,
|
|
5459
|
+
recentErrorsLimit,
|
|
5460
|
+
tunnelChildPid: getTunnelChildPid?.() ?? void 0
|
|
5360
5461
|
}), name, env, conn.listTargets().length > 0);
|
|
5361
5462
|
} catch (err) {
|
|
5362
5463
|
return errorResult(err, name);
|
|
@@ -6049,12 +6150,14 @@ async function bootRelayFamily(options = {}) {
|
|
|
6049
6150
|
tunnel = t;
|
|
6050
6151
|
tunnelStatus = makeTunnelStatus(true, t.wssUrl);
|
|
6051
6152
|
options.onWssUrl?.(t.wssUrl);
|
|
6153
|
+
if (t.childPid !== void 0) options.onTunnelChildPid?.(t.childPid);
|
|
6052
6154
|
logInfo("tunnel.up", { totpEnabled });
|
|
6053
6155
|
tunnelProbe = startTunnelHealthProbe(t, relay.port, {
|
|
6054
6156
|
onReissue: (newTunnel) => {
|
|
6055
6157
|
tunnel = newTunnel;
|
|
6056
6158
|
tunnelStatus = makeTunnelStatus(true, newTunnel.wssUrl, null, 0);
|
|
6057
6159
|
options.onWssUrl?.(newTunnel.wssUrl);
|
|
6160
|
+
if (newTunnel.childPid !== void 0) options.onTunnelChildPid?.(newTunnel.childPid);
|
|
6058
6161
|
printAttachBanner({
|
|
6059
6162
|
wssUrl: newTunnel.wssUrl,
|
|
6060
6163
|
totpEnabled
|
|
@@ -6387,6 +6490,7 @@ async function runDebugServer(options = {}) {
|
|
|
6387
6490
|
const lockHandle = acquireLock({ force: options.force ?? false });
|
|
6388
6491
|
const devtoolsOpener = new AutoDevtoolsOpener();
|
|
6389
6492
|
const diagnosticsCollector = new InMemoryDiagnosticsCollector();
|
|
6493
|
+
let activeTunnelChildPid = null;
|
|
6390
6494
|
const router = new DualConnectionRouter({
|
|
6391
6495
|
bootLazyFor: async (key, projectRoot) => key === "relay-sandbox" ? bootExternalRelayFamily(await readMobileRelayBaseUrl(process.env, projectRoot), await readRelayLocalUrl(process.env, projectRoot)) : key === "local-browser" ? bootLocalFamily() : bootRelayFamily({
|
|
6392
6496
|
relayPort: options.relayPort,
|
|
@@ -6395,6 +6499,10 @@ async function runDebugServer(options = {}) {
|
|
|
6395
6499
|
lockHandle.updateWssUrl(wssUrl);
|
|
6396
6500
|
qrServer?.notifyStateChange();
|
|
6397
6501
|
},
|
|
6502
|
+
onTunnelChildPid: (pid) => {
|
|
6503
|
+
activeTunnelChildPid = pid;
|
|
6504
|
+
lockHandle.updateTunnelChildPid(pid);
|
|
6505
|
+
},
|
|
6398
6506
|
onAuthReject: () => diagnosticsCollector.recordAuthReject()
|
|
6399
6507
|
}),
|
|
6400
6508
|
diagnosticsCollector,
|
|
@@ -6466,6 +6574,7 @@ async function runDebugServer(options = {}) {
|
|
|
6466
6574
|
router,
|
|
6467
6575
|
aitSource,
|
|
6468
6576
|
getTunnelStatus: () => router.relayTunnelStatus(),
|
|
6577
|
+
getTunnelChildPid: () => activeTunnelChildPid,
|
|
6469
6578
|
get qrHttpServer() {
|
|
6470
6579
|
return qrServer;
|
|
6471
6580
|
},
|
|
@@ -6479,10 +6588,12 @@ async function runDebugServer(options = {}) {
|
|
|
6479
6588
|
const transport = new StdioServerTransport();
|
|
6480
6589
|
let closed = false;
|
|
6481
6590
|
let parentWatcher = null;
|
|
6591
|
+
let maxAgeWatchdog = null;
|
|
6482
6592
|
const shutdown = () => {
|
|
6483
6593
|
if (closed) return;
|
|
6484
6594
|
closed = true;
|
|
6485
6595
|
parentWatcher?.stop();
|
|
6596
|
+
maxAgeWatchdog?.stop();
|
|
6486
6597
|
if (totpRefreshHandle) clearInterval(totpRefreshHandle);
|
|
6487
6598
|
router.stopWatcher();
|
|
6488
6599
|
for (const family of router.bootedFamilies()) family.stop();
|
|
@@ -6497,6 +6608,7 @@ async function runDebugServer(options = {}) {
|
|
|
6497
6608
|
if (!closed) {
|
|
6498
6609
|
closed = true;
|
|
6499
6610
|
parentWatcher?.stop();
|
|
6611
|
+
maxAgeWatchdog?.stop();
|
|
6500
6612
|
if (totpRefreshHandle) clearInterval(totpRefreshHandle);
|
|
6501
6613
|
router.stopWatcher();
|
|
6502
6614
|
for (const family of router.bootedFamilies()) family.stop();
|
|
@@ -6535,6 +6647,11 @@ async function runDebugServer(options = {}) {
|
|
|
6535
6647
|
process.exit(0);
|
|
6536
6648
|
});
|
|
6537
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 });
|
|
6538
6655
|
}
|
|
6539
6656
|
/**
|
|
6540
6657
|
* Serves the debug stack over stdio with the local browser as the default
|
|
@@ -6586,6 +6703,7 @@ async function runLocalDebugServer(options = {}) {
|
|
|
6586
6703
|
};
|
|
6587
6704
|
const devtoolsOpener = new AutoDevtoolsOpener();
|
|
6588
6705
|
const diagnosticsCollector = new InMemoryDiagnosticsCollector();
|
|
6706
|
+
let activeTunnelChildPid = null;
|
|
6589
6707
|
const router = new DualConnectionRouter({
|
|
6590
6708
|
bootLazyFor: async (key, projectRoot) => key === "relay-sandbox" ? bootExternalRelayFamily(await readMobileRelayBaseUrl(process.env, projectRoot), await readRelayLocalUrl(process.env, projectRoot)) : key === "local-browser" ? bootLocalFamilyForEntry() : bootRelayFamily({
|
|
6591
6709
|
verifyAuth: buildRelayVerifyAuth(),
|
|
@@ -6593,6 +6711,10 @@ async function runLocalDebugServer(options = {}) {
|
|
|
6593
6711
|
lockHandle.updateWssUrl(wssUrl);
|
|
6594
6712
|
qrServer?.notifyStateChange();
|
|
6595
6713
|
},
|
|
6714
|
+
onTunnelChildPid: (pid) => {
|
|
6715
|
+
activeTunnelChildPid = pid;
|
|
6716
|
+
lockHandle.updateTunnelChildPid(pid);
|
|
6717
|
+
},
|
|
6596
6718
|
onAuthReject: () => diagnosticsCollector.recordAuthReject()
|
|
6597
6719
|
}),
|
|
6598
6720
|
diagnosticsCollector,
|
|
@@ -6663,6 +6785,7 @@ async function runLocalDebugServer(options = {}) {
|
|
|
6663
6785
|
router,
|
|
6664
6786
|
aitSource,
|
|
6665
6787
|
getTunnelStatus: () => router.relayTunnelStatus(),
|
|
6788
|
+
getTunnelChildPid: () => activeTunnelChildPid,
|
|
6666
6789
|
get qrHttpServer() {
|
|
6667
6790
|
return qrServer;
|
|
6668
6791
|
},
|
|
@@ -6676,10 +6799,12 @@ async function runLocalDebugServer(options = {}) {
|
|
|
6676
6799
|
const transport = new StdioServerTransport();
|
|
6677
6800
|
let closed = false;
|
|
6678
6801
|
let parentWatcher = null;
|
|
6802
|
+
let maxAgeWatchdog = null;
|
|
6679
6803
|
const shutdown = () => {
|
|
6680
6804
|
if (closed) return;
|
|
6681
6805
|
closed = true;
|
|
6682
6806
|
parentWatcher?.stop();
|
|
6807
|
+
maxAgeWatchdog?.stop();
|
|
6683
6808
|
if (totpRefreshHandle) clearInterval(totpRefreshHandle);
|
|
6684
6809
|
router.stopWatcher();
|
|
6685
6810
|
for (const family of router.bootedFamilies()) family.stop();
|
|
@@ -6694,6 +6819,7 @@ async function runLocalDebugServer(options = {}) {
|
|
|
6694
6819
|
if (!closed) {
|
|
6695
6820
|
closed = true;
|
|
6696
6821
|
parentWatcher?.stop();
|
|
6822
|
+
maxAgeWatchdog?.stop();
|
|
6697
6823
|
if (totpRefreshHandle) clearInterval(totpRefreshHandle);
|
|
6698
6824
|
router.stopWatcher();
|
|
6699
6825
|
for (const family of router.bootedFamilies()) family.stop();
|
|
@@ -6734,6 +6860,11 @@ async function runLocalDebugServer(options = {}) {
|
|
|
6734
6860
|
process.exit(0);
|
|
6735
6861
|
});
|
|
6736
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 });
|
|
6737
6868
|
}
|
|
6738
6869
|
/**
|
|
6739
6870
|
* Serves the env-2 (real-device PWA) debug stack over stdio with the external
|
|
@@ -6767,6 +6898,7 @@ async function runMobileDebugServer(options = {}) {
|
|
|
6767
6898
|
const lockHandle = acquireLock({ force: options.force ?? false });
|
|
6768
6899
|
const devtoolsOpener = new AutoDevtoolsOpener();
|
|
6769
6900
|
const diagnosticsCollector = new InMemoryDiagnosticsCollector();
|
|
6901
|
+
let activeTunnelChildPid = null;
|
|
6770
6902
|
const router = new DualConnectionRouter({
|
|
6771
6903
|
bootLazyFor: async (key) => key === "relay-sandbox" ? bootExternalRelayFamily(relayBaseUrl, await readRelayLocalUrl(process.env, options.projectRoot ?? process.cwd())) : key === "local-browser" ? bootLocalFamily() : bootRelayFamily({
|
|
6772
6904
|
verifyAuth: buildRelayVerifyAuth(),
|
|
@@ -6774,6 +6906,10 @@ async function runMobileDebugServer(options = {}) {
|
|
|
6774
6906
|
lockHandle.updateWssUrl(wssUrl);
|
|
6775
6907
|
qrServer?.notifyStateChange();
|
|
6776
6908
|
},
|
|
6909
|
+
onTunnelChildPid: (pid) => {
|
|
6910
|
+
activeTunnelChildPid = pid;
|
|
6911
|
+
lockHandle.updateTunnelChildPid(pid);
|
|
6912
|
+
},
|
|
6777
6913
|
onAuthReject: () => diagnosticsCollector.recordAuthReject()
|
|
6778
6914
|
}),
|
|
6779
6915
|
diagnosticsCollector,
|
|
@@ -6844,6 +6980,7 @@ async function runMobileDebugServer(options = {}) {
|
|
|
6844
6980
|
router,
|
|
6845
6981
|
aitSource,
|
|
6846
6982
|
getTunnelStatus: () => router.relayTunnelStatus(),
|
|
6983
|
+
getTunnelChildPid: () => activeTunnelChildPid,
|
|
6847
6984
|
get qrHttpServer() {
|
|
6848
6985
|
return qrServer;
|
|
6849
6986
|
},
|
|
@@ -6857,10 +6994,12 @@ async function runMobileDebugServer(options = {}) {
|
|
|
6857
6994
|
const transport = new StdioServerTransport();
|
|
6858
6995
|
let closed = false;
|
|
6859
6996
|
let parentWatcher = null;
|
|
6997
|
+
let maxAgeWatchdog = null;
|
|
6860
6998
|
const shutdown = () => {
|
|
6861
6999
|
if (closed) return;
|
|
6862
7000
|
closed = true;
|
|
6863
7001
|
parentWatcher?.stop();
|
|
7002
|
+
maxAgeWatchdog?.stop();
|
|
6864
7003
|
if (totpRefreshHandle) clearInterval(totpRefreshHandle);
|
|
6865
7004
|
router.stopWatcher();
|
|
6866
7005
|
for (const family of router.bootedFamilies()) family.stop();
|
|
@@ -6875,6 +7014,7 @@ async function runMobileDebugServer(options = {}) {
|
|
|
6875
7014
|
if (!closed) {
|
|
6876
7015
|
closed = true;
|
|
6877
7016
|
parentWatcher?.stop();
|
|
7017
|
+
maxAgeWatchdog?.stop();
|
|
6878
7018
|
if (totpRefreshHandle) clearInterval(totpRefreshHandle);
|
|
6879
7019
|
router.stopWatcher();
|
|
6880
7020
|
for (const family of router.bootedFamilies()) family.stop();
|
|
@@ -6915,6 +7055,11 @@ async function runMobileDebugServer(options = {}) {
|
|
|
6915
7055
|
process.exit(0);
|
|
6916
7056
|
});
|
|
6917
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 });
|
|
6918
7063
|
}
|
|
6919
7064
|
//#endregion
|
|
6920
7065
|
//#region src/mcp/ait-http-source.ts
|
|
@@ -7344,7 +7489,7 @@ function createDevServer(deps = {}) {
|
|
|
7344
7489
|
const aitSource = deps.aitSource ?? new HttpAitSource({ stateEndpoint });
|
|
7345
7490
|
const server = new Server({
|
|
7346
7491
|
name: "ait-devtools",
|
|
7347
|
-
version: "0.1.
|
|
7492
|
+
version: "0.1.90"
|
|
7348
7493
|
}, { capabilities: { tools: {} } });
|
|
7349
7494
|
server.setRequestHandler(ListToolsRequestSchema, () => ({ tools: DEV_TOOL_DEFINITIONS.map((tool) => ({ ...tool })) }));
|
|
7350
7495
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|