@nudge-ui/mcp 0.2.1 → 0.2.2
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/README.md +91 -24
- package/dist/adapterRouting.d.ts +16 -0
- package/dist/adapterRouting.js +59 -0
- package/dist/bridge.js +18 -1
- package/dist/cli.d.ts +2 -2
- package/dist/cli.js +14 -6
- package/dist/cli.mjs +236 -55
- package/dist/discovery.d.ts +19 -2
- package/dist/discovery.js +55 -7
- package/dist/projectSessionClient.d.ts +5 -0
- package/dist/projectSessionClient.js +12 -2
- package/dist/protocol.d.ts +1 -0
- package/dist/protocol.js +1 -0
- package/dist/server.d.ts +3 -2
- package/dist/server.js +69 -29
- package/dist/sessionRegistry.d.ts +10 -1
- package/dist/sessionRegistry.js +48 -16
- package/package.json +1 -1
package/dist/cli.mjs
CHANGED
|
@@ -7479,7 +7479,7 @@ function isCanvasCommand(value) {
|
|
|
7479
7479
|
// src/discovery.ts
|
|
7480
7480
|
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
7481
7481
|
import { realpath as realpath3 } from "node:fs/promises";
|
|
7482
|
-
import { resolve as resolve3 } from "node:path";
|
|
7482
|
+
import { isAbsolute as isAbsolute2, resolve as resolve3 } from "node:path";
|
|
7483
7483
|
|
|
7484
7484
|
// src/projectSessionClient.ts
|
|
7485
7485
|
import { randomUUID } from "node:crypto";
|
|
@@ -7488,6 +7488,7 @@ import { randomUUID } from "node:crypto";
|
|
|
7488
7488
|
var BRIDGE_ENDPOINTS = {
|
|
7489
7489
|
health: "/health",
|
|
7490
7490
|
pair: "/pair",
|
|
7491
|
+
takeover: "/takeover",
|
|
7491
7492
|
events: "/events",
|
|
7492
7493
|
prompt: "/prompt",
|
|
7493
7494
|
status: "/status",
|
|
@@ -7508,6 +7509,14 @@ var AGENT_CONTROL_ENDPOINTS = {
|
|
|
7508
7509
|
// src/projectSessionClient.ts
|
|
7509
7510
|
var CONTROL_TIMEOUT_MS = 5e3;
|
|
7510
7511
|
var CANVAS_TIMEOUT_MS = 65e3;
|
|
7512
|
+
var ProjectSessionResponseError = class extends Error {
|
|
7513
|
+
status;
|
|
7514
|
+
constructor(status, message) {
|
|
7515
|
+
super(message);
|
|
7516
|
+
this.name = "ProjectSessionResponseError";
|
|
7517
|
+
this.status = status;
|
|
7518
|
+
}
|
|
7519
|
+
};
|
|
7511
7520
|
async function requestJson(session, path, init = {}, { signal, timeoutMs = CONTROL_TIMEOUT_MS } = {}) {
|
|
7512
7521
|
const response = await fetch(`${session.endpoint}${path}`, {
|
|
7513
7522
|
...init,
|
|
@@ -7520,7 +7529,12 @@ async function requestJson(session, path, init = {}, { signal, timeoutMs = CONTR
|
|
|
7520
7529
|
}
|
|
7521
7530
|
});
|
|
7522
7531
|
const body = await response.json().catch(() => void 0);
|
|
7523
|
-
if (!response.ok)
|
|
7532
|
+
if (!response.ok) {
|
|
7533
|
+
throw new ProjectSessionResponseError(
|
|
7534
|
+
response.status,
|
|
7535
|
+
body?.error?.message ?? `Nudge project bridge returned ${response.status}.`
|
|
7536
|
+
);
|
|
7537
|
+
}
|
|
7524
7538
|
return body;
|
|
7525
7539
|
}
|
|
7526
7540
|
async function readSessionHealth(session, timeoutMs = CONTROL_TIMEOUT_MS) {
|
|
@@ -7595,13 +7609,21 @@ import { lstat, readFile, readdir, realpath } from "node:fs/promises";
|
|
|
7595
7609
|
import { resolve } from "node:path";
|
|
7596
7610
|
var PROBE_TIMEOUT_MS = 750;
|
|
7597
7611
|
var MAX_DESCRIPTOR_BYTES = 32 * 1024;
|
|
7612
|
+
async function inspectProjectSessions(registryRoot) {
|
|
7613
|
+
const names = (await readRegistryDirectory(registryRoot)).filter((name) => name.endsWith(".json"));
|
|
7614
|
+
const records = await Promise.all(names.map((name) => readDescriptor(resolve(registryRoot, name))));
|
|
7615
|
+
const descriptors = records.filter((record2) => typeof record2 === "object");
|
|
7616
|
+
const probes = await Promise.all(descriptors.map(probeSession));
|
|
7617
|
+
return {
|
|
7618
|
+
descriptorCount: names.length,
|
|
7619
|
+
invalidCount: records.filter((record2) => record2 === void 0).length + probes.filter((probe) => probe.kind === "invalid").length,
|
|
7620
|
+
incompatibleCount: records.filter((record2) => record2 === "incompatible").length,
|
|
7621
|
+
unreachable: descriptors.filter((_, index) => probes[index]?.kind === "unreachable").map(({ workspaceRoot, appRoot, sessionId }) => ({ workspaceRoot, appRoot, sessionId })),
|
|
7622
|
+
live: probes.flatMap((probe) => probe.kind === "live" ? [probe.session] : [])
|
|
7623
|
+
};
|
|
7624
|
+
}
|
|
7598
7625
|
async function readLiveProjectSessions(registryRoot) {
|
|
7599
|
-
|
|
7600
|
-
const descriptors = await Promise.all(
|
|
7601
|
-
names.filter((name) => name.endsWith(".json")).map((name) => readDescriptor(resolve(registryRoot, name)))
|
|
7602
|
-
);
|
|
7603
|
-
const live = await Promise.all(descriptors.filter(isPresent).map(probeSession));
|
|
7604
|
-
return live.filter(isPresent);
|
|
7626
|
+
return (await inspectProjectSessions(registryRoot)).live;
|
|
7605
7627
|
}
|
|
7606
7628
|
async function readRegistryDirectory(registryRoot) {
|
|
7607
7629
|
try {
|
|
@@ -7619,8 +7641,9 @@ async function readDescriptor(path) {
|
|
|
7619
7641
|
if ((metadata.mode & 63) !== 0) return void 0;
|
|
7620
7642
|
const parsed = JSON.parse(await readFile(path, "utf8"));
|
|
7621
7643
|
if (!isStoredSession(parsed)) return void 0;
|
|
7644
|
+
if (parsed.protocolVersion !== AGENT_PROTOCOL_VERSION) return "incompatible";
|
|
7622
7645
|
const [workspaceRoot, appRoot] = await Promise.all([realpath(parsed.workspaceRoot), realpath(parsed.appRoot)]);
|
|
7623
|
-
return { ...parsed, workspaceRoot, appRoot };
|
|
7646
|
+
return { ...parsed, protocolVersion: AGENT_PROTOCOL_VERSION, workspaceRoot, appRoot };
|
|
7624
7647
|
} catch {
|
|
7625
7648
|
return void 0;
|
|
7626
7649
|
}
|
|
@@ -7628,14 +7651,21 @@ async function readDescriptor(path) {
|
|
|
7628
7651
|
async function probeSession(descriptor) {
|
|
7629
7652
|
try {
|
|
7630
7653
|
const health = await readSessionHealth(descriptor, PROBE_TIMEOUT_MS);
|
|
7631
|
-
if (health.projectId !== descriptor.projectId
|
|
7632
|
-
return { descriptor, health };
|
|
7633
|
-
} catch {
|
|
7634
|
-
return
|
|
7654
|
+
if (!isSessionHealth(health) || health.projectId !== descriptor.projectId) return { kind: "invalid" };
|
|
7655
|
+
return { kind: "live", session: { descriptor, health } };
|
|
7656
|
+
} catch (error2) {
|
|
7657
|
+
return { kind: error2 instanceof ProjectSessionResponseError ? "invalid" : "unreachable" };
|
|
7635
7658
|
}
|
|
7636
7659
|
}
|
|
7637
|
-
function
|
|
7638
|
-
|
|
7660
|
+
function isSessionHealth(value) {
|
|
7661
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
7662
|
+
const health = value;
|
|
7663
|
+
return typeof health.projectId === "string" && health.available === true && typeof health.claimed === "boolean" && isAgentStatusSnapshot(health.status);
|
|
7664
|
+
}
|
|
7665
|
+
function isAgentStatusSnapshot(value) {
|
|
7666
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
7667
|
+
const status = value;
|
|
7668
|
+
return status.protocolVersion === AGENT_PROTOCOL_VERSION && typeof status.projectId === "string" && (status.connection === "offline" || status.connection === "listening" || status.connection === "paired" || status.connection === "working") && typeof status.listenerActive === "boolean" && typeof status.paired === "boolean" && (status.request === null || typeof status.request === "object" && !Array.isArray(status.request));
|
|
7639
7669
|
}
|
|
7640
7670
|
function isLoopbackEndpoint(value) {
|
|
7641
7671
|
if (typeof value !== "string") return false;
|
|
@@ -7649,7 +7679,7 @@ function isLoopbackEndpoint(value) {
|
|
|
7649
7679
|
function isStoredSession(value) {
|
|
7650
7680
|
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
7651
7681
|
const session = value;
|
|
7652
|
-
return session.schemaVersion === 1 && session.protocolVersion ===
|
|
7682
|
+
return session.schemaVersion === 1 && typeof session.protocolVersion === "number" && Number.isSafeInteger(session.protocolVersion) && typeof session.sessionId === "string" && session.sessionId.length > 0 && typeof session.projectId === "string" && session.projectId.length > 0 && typeof session.workspaceRoot === "string" && session.workspaceRoot.length > 0 && typeof session.appRoot === "string" && session.appRoot.length > 0 && (session.gitCommonDir === void 0 || typeof session.gitCommonDir === "string") && (session.branch === void 0 || typeof session.branch === "string") && typeof session.origin === "string" && isLoopbackEndpoint(session.endpoint) && typeof session.startedAt === "string" && typeof session.pid === "number" && Number.isSafeInteger(session.pid) && typeof session.controlToken === "string" && session.controlToken.length >= 32 && session.controlToken.length <= 256;
|
|
7653
7683
|
}
|
|
7654
7684
|
|
|
7655
7685
|
// src/sessionSelection.ts
|
|
@@ -7985,7 +8015,7 @@ function createLoopbackBridge(options) {
|
|
|
7985
8015
|
pageUrl: lastPageUrl
|
|
7986
8016
|
}, incomingOrigin);
|
|
7987
8017
|
};
|
|
7988
|
-
const
|
|
8018
|
+
const readPairRequest = async (request) => {
|
|
7989
8019
|
if (!loopbackAddress(request.socket.remoteAddress)) {
|
|
7990
8020
|
throw new BridgeRequestError(403, "loopback_only", "The browser bridge accepts loopback connections only.");
|
|
7991
8021
|
}
|
|
@@ -8006,6 +8036,10 @@ function createLoopbackBridge(options) {
|
|
|
8006
8036
|
}
|
|
8007
8037
|
requestedPageUrl = new URL(body.pageUrl, requestedOrigin).href;
|
|
8008
8038
|
}
|
|
8039
|
+
return { body, incomingOrigin, requestedPageUrl };
|
|
8040
|
+
};
|
|
8041
|
+
const handlePair = async (request, response) => {
|
|
8042
|
+
const { body, incomingOrigin, requestedPageUrl } = await readPairRequest(request);
|
|
8009
8043
|
if (sessionToken) {
|
|
8010
8044
|
if (body.sessionToken !== sessionToken) {
|
|
8011
8045
|
throw new BridgeRequestError(409, "already_paired", "A browser is already paired with this project.");
|
|
@@ -8041,6 +8075,12 @@ function createLoopbackBridge(options) {
|
|
|
8041
8075
|
sendJson(response, 200, result, incomingOrigin);
|
|
8042
8076
|
broadcastStatus();
|
|
8043
8077
|
};
|
|
8078
|
+
const handleTakeover = async (request, response) => {
|
|
8079
|
+
const { incomingOrigin, requestedPageUrl } = await readPairRequest(request);
|
|
8080
|
+
if (sessionToken) disconnectBrowser(sessionToken);
|
|
8081
|
+
const pairing = pairBrowser(options.projectId, incomingOrigin, requestedPageUrl);
|
|
8082
|
+
sendJson(response, 200, pairing, incomingOrigin);
|
|
8083
|
+
};
|
|
8044
8084
|
const handleEvents = (request, response, url) => {
|
|
8045
8085
|
parseProjectId(url.searchParams.get("projectId"), options.projectId);
|
|
8046
8086
|
const incomingOrigin = requireBrowserRequest(request, url);
|
|
@@ -8322,6 +8362,10 @@ function createLoopbackBridge(options) {
|
|
|
8322
8362
|
await handlePair(request, response);
|
|
8323
8363
|
return;
|
|
8324
8364
|
}
|
|
8365
|
+
if (request.method === "POST" && url.pathname === BRIDGE_ENDPOINTS.takeover) {
|
|
8366
|
+
await handleTakeover(request, response);
|
|
8367
|
+
return;
|
|
8368
|
+
}
|
|
8325
8369
|
if (request.method === "GET" && url.pathname === BRIDGE_ENDPOINTS.events) {
|
|
8326
8370
|
handleEvents(request, response, url);
|
|
8327
8371
|
return;
|
|
@@ -8620,15 +8664,15 @@ function publicSession(session) {
|
|
|
8620
8664
|
return visible;
|
|
8621
8665
|
}
|
|
8622
8666
|
async function discoverProjectSessions(applicationRoot, registryRoot = defaultSessionRegistryRoot()) {
|
|
8623
|
-
const canonicalApplication = await realpath3(
|
|
8624
|
-
const canonicalWorkspace = await resolveWorkspaceRoot(canonicalApplication);
|
|
8667
|
+
const canonicalApplication = applicationRoot === void 0 ? void 0 : await realpath3(requireAbsoluteWorkspaceRoot(applicationRoot));
|
|
8668
|
+
const canonicalWorkspace = canonicalApplication === void 0 ? void 0 : await resolveWorkspaceRoot(canonicalApplication);
|
|
8625
8669
|
const live = await readLiveProjectSessions(resolve3(registryRoot));
|
|
8626
8670
|
return live.map(({ descriptor, health }) => {
|
|
8627
8671
|
const matchesWorkspace = descriptor.workspaceRoot === canonicalWorkspace;
|
|
8628
8672
|
return {
|
|
8629
8673
|
...publicSession(descriptor),
|
|
8630
8674
|
matchesWorkspace,
|
|
8631
|
-
matchesApplication: matchesApplication(descriptor, {
|
|
8675
|
+
matchesApplication: canonicalWorkspace !== void 0 && canonicalApplication !== void 0 && matchesApplication(descriptor, {
|
|
8632
8676
|
workspaceRoot: canonicalWorkspace,
|
|
8633
8677
|
applicationRoot: canonicalApplication
|
|
8634
8678
|
}),
|
|
@@ -8648,14 +8692,23 @@ var DiscoveredProjectRouter = class _DiscoveredProjectRouter {
|
|
|
8648
8692
|
agentId;
|
|
8649
8693
|
client = null;
|
|
8650
8694
|
releaseGeneration = 0;
|
|
8695
|
+
connectionQueue = Promise.resolve();
|
|
8651
8696
|
constructor(workspaceRoot, applicationRoot, registryRoot, agentId) {
|
|
8652
8697
|
this.workspaceRoot = workspaceRoot;
|
|
8653
8698
|
this.applicationRoot = applicationRoot;
|
|
8654
8699
|
this.registryRoot = registryRoot;
|
|
8655
8700
|
this.agentId = agentId;
|
|
8656
8701
|
}
|
|
8702
|
+
get selectedSessionId() {
|
|
8703
|
+
return this.client?.descriptor.sessionId;
|
|
8704
|
+
}
|
|
8705
|
+
static forSession(session, registryRoot) {
|
|
8706
|
+
const router = new _DiscoveredProjectRouter(session.workspaceRoot, session.appRoot, registryRoot, randomUUID3());
|
|
8707
|
+
router.client = new ProjectSessionClient(session, router.agentId);
|
|
8708
|
+
return router;
|
|
8709
|
+
}
|
|
8657
8710
|
static async create(workspaceRoot, registryRoot = defaultSessionRegistryRoot()) {
|
|
8658
|
-
const applicationRoot = await realpath3(
|
|
8711
|
+
const applicationRoot = await realpath3(requireAbsoluteWorkspaceRoot(workspaceRoot));
|
|
8659
8712
|
return new _DiscoveredProjectRouter(
|
|
8660
8713
|
await resolveWorkspaceRoot(applicationRoot),
|
|
8661
8714
|
applicationRoot,
|
|
@@ -8666,7 +8719,12 @@ var DiscoveredProjectRouter = class _DiscoveredProjectRouter {
|
|
|
8666
8719
|
async listSessions() {
|
|
8667
8720
|
return await discoverProjectSessions(this.applicationRoot, this.registryRoot);
|
|
8668
8721
|
}
|
|
8669
|
-
|
|
8722
|
+
connectToSession(sessionId) {
|
|
8723
|
+
const connection = this.connectionQueue.then(() => this.selectSession(sessionId));
|
|
8724
|
+
this.connectionQueue = connection.then(() => void 0, () => void 0);
|
|
8725
|
+
return connection;
|
|
8726
|
+
}
|
|
8727
|
+
async selectSession(sessionId) {
|
|
8670
8728
|
const live = await readLiveProjectSessions(this.registryRoot);
|
|
8671
8729
|
const selected = selectProjectSession(live.map(({ descriptor }) => descriptor), {
|
|
8672
8730
|
workspaceRoot: this.workspaceRoot,
|
|
@@ -8712,6 +8770,7 @@ var DiscoveredProjectRouter = class _DiscoveredProjectRouter {
|
|
|
8712
8770
|
}
|
|
8713
8771
|
async release() {
|
|
8714
8772
|
this.releaseGeneration += 1;
|
|
8773
|
+
await this.connectionQueue;
|
|
8715
8774
|
const client = this.client;
|
|
8716
8775
|
this.client = null;
|
|
8717
8776
|
await client?.close();
|
|
@@ -8720,6 +8779,33 @@ var DiscoveredProjectRouter = class _DiscoveredProjectRouter {
|
|
|
8720
8779
|
await this.release();
|
|
8721
8780
|
}
|
|
8722
8781
|
};
|
|
8782
|
+
async function diagnoseProjectSessions(applicationRoot, registryRoot = defaultSessionRegistryRoot()) {
|
|
8783
|
+
const resolvedRegistryRoot = resolve3(registryRoot);
|
|
8784
|
+
const app = applicationRoot === void 0 ? void 0 : await realpath3(requireAbsoluteWorkspaceRoot(applicationRoot));
|
|
8785
|
+
const workspace = app === void 0 ? void 0 : await resolveWorkspaceRoot(app);
|
|
8786
|
+
const inspection = await inspectProjectSessions(resolvedRegistryRoot);
|
|
8787
|
+
const matching = (session) => app === void 0 || session.workspaceRoot === workspace && (app === workspace || session.appRoot === app);
|
|
8788
|
+
return {
|
|
8789
|
+
registryRoot: resolvedRegistryRoot,
|
|
8790
|
+
workspaceRoot: workspace,
|
|
8791
|
+
applicationRoot: app,
|
|
8792
|
+
descriptorCount: inspection.descriptorCount,
|
|
8793
|
+
invalidCount: inspection.invalidCount,
|
|
8794
|
+
incompatibleCount: inspection.incompatibleCount,
|
|
8795
|
+
reachableCount: inspection.live.length,
|
|
8796
|
+
matchingReachableCount: inspection.live.filter(({ descriptor }) => matching(descriptor)).length,
|
|
8797
|
+
matchingUnreachableCount: inspection.unreachable.filter(matching).length,
|
|
8798
|
+
guidance: [
|
|
8799
|
+
...inspection.invalidCount > 0 ? ["Some session descriptors or authenticated health responses are invalid. Check the adapter and project bridge versions, descriptor permissions, and bridge logs."] : [],
|
|
8800
|
+
...inspection.unreachable.some(matching) ? ["Matching descriptors exist but their loopback endpoints are unreachable. The server may have stopped or local networking may be blocked. Retry outside the agent command sandbox; do not treat this as proof that no bridge registered."] : [],
|
|
8801
|
+
...inspection.incompatibleCount > 0 ? ["Some registrations use an incompatible protocol. Upgrade the reusable adapter and the project's @nudge-ui/mcp dependency, then restart the development server and agent host."] : []
|
|
8802
|
+
]
|
|
8803
|
+
};
|
|
8804
|
+
}
|
|
8805
|
+
function requireAbsoluteWorkspaceRoot(workspaceRoot) {
|
|
8806
|
+
if (!isAbsolute2(workspaceRoot)) throw new TypeError("workspaceRoot must be an absolute path.");
|
|
8807
|
+
return workspaceRoot;
|
|
8808
|
+
}
|
|
8723
8809
|
|
|
8724
8810
|
// ../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/external.js
|
|
8725
8811
|
var external_exports = {};
|
|
@@ -22955,7 +23041,64 @@ var StdioServerTransport = class {
|
|
|
22955
23041
|
};
|
|
22956
23042
|
|
|
22957
23043
|
// src/server.ts
|
|
23044
|
+
import { realpath as realpath5 } from "node:fs/promises";
|
|
22958
23045
|
import { createRequire } from "node:module";
|
|
23046
|
+
import { isAbsolute as isAbsolute4 } from "node:path";
|
|
23047
|
+
|
|
23048
|
+
// src/adapterRouting.ts
|
|
23049
|
+
import { realpath as realpath4 } from "node:fs/promises";
|
|
23050
|
+
import { isAbsolute as isAbsolute3 } from "node:path";
|
|
23051
|
+
var AdapterRouting = class {
|
|
23052
|
+
constructor(registryRoot = defaultSessionRegistryRoot()) {
|
|
23053
|
+
this.registryRoot = registryRoot;
|
|
23054
|
+
}
|
|
23055
|
+
closing = false;
|
|
23056
|
+
selections = /* @__PURE__ */ new Map();
|
|
23057
|
+
applications = /* @__PURE__ */ new Map();
|
|
23058
|
+
async target({ workspaceRoot, sessionId }) {
|
|
23059
|
+
if (!workspaceRoot || !isAbsolute3(workspaceRoot)) {
|
|
23060
|
+
throw new Error("Supply workspaceRoot as the absolute path of the checkout or application you are editing.");
|
|
23061
|
+
}
|
|
23062
|
+
const applicationRoot = await realpath4(workspaceRoot);
|
|
23063
|
+
if (this.closing) throw new Error("The MCP adapter is closing.");
|
|
23064
|
+
const key = JSON.stringify([applicationRoot, sessionId]);
|
|
23065
|
+
let selection = this.selections.get(key);
|
|
23066
|
+
if (!selection) {
|
|
23067
|
+
selection = this.select(applicationRoot, sessionId);
|
|
23068
|
+
this.selections.set(key, selection);
|
|
23069
|
+
void selection.catch(() => {
|
|
23070
|
+
if (this.selections.get(key) === selection) this.selections.delete(key);
|
|
23071
|
+
});
|
|
23072
|
+
}
|
|
23073
|
+
return await selection;
|
|
23074
|
+
}
|
|
23075
|
+
async select(applicationRoot, sessionId) {
|
|
23076
|
+
const workspaceRoot = await resolveWorkspaceRoot(applicationRoot);
|
|
23077
|
+
const live = await readLiveProjectSessions(this.registryRoot);
|
|
23078
|
+
if (this.closing) throw new Error("The MCP adapter is closing.");
|
|
23079
|
+
const selected = selectProjectSession(live.map(({ descriptor }) => descriptor), {
|
|
23080
|
+
workspaceRoot,
|
|
23081
|
+
applicationRoot,
|
|
23082
|
+
requestedSessionId: sessionId
|
|
23083
|
+
});
|
|
23084
|
+
const key = selected.sessionId;
|
|
23085
|
+
let router = this.applications.get(key) ?? [...this.applications.values()].find((candidate) => candidate.selectedSessionId === selected.sessionId);
|
|
23086
|
+
if (!router) {
|
|
23087
|
+
router = DiscoveredProjectRouter.forSession(selected, this.registryRoot);
|
|
23088
|
+
this.applications.set(key, router);
|
|
23089
|
+
}
|
|
23090
|
+
return router;
|
|
23091
|
+
}
|
|
23092
|
+
async close() {
|
|
23093
|
+
this.closing = true;
|
|
23094
|
+
await Promise.allSettled(this.selections.values());
|
|
23095
|
+
await Promise.all([...this.applications.values()].map((router) => router.close()));
|
|
23096
|
+
this.selections.clear();
|
|
23097
|
+
this.applications.clear();
|
|
23098
|
+
}
|
|
23099
|
+
};
|
|
23100
|
+
|
|
23101
|
+
// src/server.ts
|
|
22959
23102
|
var MCP_SERVER_NAME = "nudge-ui";
|
|
22960
23103
|
var MCP_SERVER_VERSION = readPackageVersion();
|
|
22961
23104
|
function readPackageVersion() {
|
|
@@ -22967,13 +23110,14 @@ function readPackageVersion() {
|
|
|
22967
23110
|
}
|
|
22968
23111
|
var MCP_SERVER_INSTRUCTIONS = [
|
|
22969
23112
|
"Nudge UI is a local browser companion for one exact project workspace or Git worktree.",
|
|
22970
|
-
"
|
|
23113
|
+
"Supply workspaceRoot with the absolute checkout or application path you are editing on every tool call. Listing and selecting sessions requires this explicit scope; repeat the same workspaceRoot and sessionId for status, Canvas, and release calls. Never use the MCP process working directory to infer the task workspace.",
|
|
22971
23114
|
"When the user asks to listen to Nudge, call nudge_list_sessions when that tool is available; if several apps are running, select only a session marked matchesApplication.",
|
|
22972
23115
|
"Call nudge_listen (or nudge_connect) and keep the call open while waiting for a browser request; the bridge may be running before a listener exists.",
|
|
22973
23116
|
"After a prompt is delivered, edit the project source using the host's normal approval flow.",
|
|
22974
23117
|
"Report completed, failed, or interrupted status with nudge_report_status before listening again.",
|
|
22975
23118
|
"When the user asks to stop listening, call nudge_release when that tool is available so another agent can claim the project session.",
|
|
22976
23119
|
"Canvas tools operate only on same-origin routes from the paired project.",
|
|
23120
|
+
"Automated browsers such as Playwright and headless Chrome get the plain app without the Nudge editor; add ?nudge-ui=on to a URL only when testing Nudge itself.",
|
|
22977
23121
|
"No request, prompt, or browser state is persisted by this companion."
|
|
22978
23122
|
].join(" ");
|
|
22979
23123
|
function textResult(value) {
|
|
@@ -23019,17 +23163,29 @@ function boundedWorkspaceRoot(value) {
|
|
|
23019
23163
|
if (value.length === 0 || value.length > 4096) throw new TypeError("workspaceRoot must be a bounded non-empty string");
|
|
23020
23164
|
return value;
|
|
23021
23165
|
}
|
|
23022
|
-
function installTools(mcpServer, bridge) {
|
|
23166
|
+
function installTools(mcpServer, bridge, routing) {
|
|
23167
|
+
const target = async (args) => {
|
|
23168
|
+
if (routing) return await routing.target(args);
|
|
23169
|
+
if (bridge instanceof DiscoveredProjectRouter && args.workspaceRoot !== void 0 && await realpath5(args.workspaceRoot) !== bridge.applicationRoot) {
|
|
23170
|
+
throw new Error("This legacy MCP configuration is restricted to another application. Rerun agent setup to migrate it to the reusable adapter.");
|
|
23171
|
+
}
|
|
23172
|
+
return bridge;
|
|
23173
|
+
};
|
|
23174
|
+
const workspaceInput = external_exports.string().min(1).max(4096).refine(isAbsolute4, "workspaceRoot must be an absolute path.").describe("Absolute path of the checkout or application being edited.");
|
|
23175
|
+
const scopeInput = {
|
|
23176
|
+
workspaceRoot: routing ? workspaceInput : workspaceInput.optional(),
|
|
23177
|
+
sessionId: external_exports.string().uuid().optional()
|
|
23178
|
+
};
|
|
23023
23179
|
const listen = async (args, extra) => {
|
|
23024
23180
|
try {
|
|
23025
|
-
return promptResult(await
|
|
23181
|
+
return promptResult(await (await target(args)).waitForPrompt(extra.signal, args.sessionId));
|
|
23026
23182
|
} catch (error2) {
|
|
23027
23183
|
return errorResult(error2);
|
|
23028
23184
|
}
|
|
23029
23185
|
};
|
|
23030
23186
|
const listenDescription = "When the user asks to listen to Nudge, wait for one browser prompt. Keep this standard MCP call open, then call nudge_report_status and listen again after the request ends.";
|
|
23031
|
-
const emptyInput =
|
|
23032
|
-
const listenInput =
|
|
23187
|
+
const emptyInput = scopeInput;
|
|
23188
|
+
const listenInput = scopeInput;
|
|
23033
23189
|
mcpServer.registerTool("nudge_listen", {
|
|
23034
23190
|
title: "Listen for Nudge requests",
|
|
23035
23191
|
description: listenDescription,
|
|
@@ -23047,24 +23203,38 @@ function installTools(mcpServer, bridge) {
|
|
|
23047
23203
|
description: "Read the in-memory browser pairing and request status for this project.",
|
|
23048
23204
|
inputSchema: emptyInput,
|
|
23049
23205
|
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false }
|
|
23050
|
-
}, async () =>
|
|
23051
|
-
|
|
23206
|
+
}, async (args) => {
|
|
23207
|
+
try {
|
|
23208
|
+
return textResult(await (await target(args)).getStatus());
|
|
23209
|
+
} catch (error2) {
|
|
23210
|
+
return errorResult(error2);
|
|
23211
|
+
}
|
|
23212
|
+
});
|
|
23213
|
+
if (routing || bridge?.listSessions) {
|
|
23052
23214
|
mcpServer.registerTool("nudge_list_sessions", {
|
|
23053
23215
|
title: "List Nudge project sessions",
|
|
23054
|
-
description: "List live local Nudge sessions.
|
|
23216
|
+
description: "List live local Nudge sessions for the supplied checkout or application path. The workspaceRoot value is required so the adapter can select only sessions in that explicit scope.",
|
|
23055
23217
|
inputSchema: emptyInput,
|
|
23056
23218
|
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false }
|
|
23057
|
-
}, async () => textResult(await bridge.listSessions()));
|
|
23219
|
+
}, async (args) => textResult(routing ? await discoverProjectSessions(args.workspaceRoot, routing.registryRoot) : await bridge.listSessions()));
|
|
23058
23220
|
}
|
|
23059
|
-
if (
|
|
23221
|
+
if (routing) {
|
|
23222
|
+
mcpServer.registerTool("nudge_diagnose", {
|
|
23223
|
+
title: "Diagnose Nudge discovery",
|
|
23224
|
+
description: "Diagnose Nudge discovery for the supplied checkout or application path. The result includes the resolved registry, workspace, and application scope plus counts for missing registrations, invalid descriptors or health responses, unreachable loopback endpoints, and incompatible bridge protocols; credentials are never exposed.",
|
|
23225
|
+
inputSchema: scopeInput,
|
|
23226
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false }
|
|
23227
|
+
}, async (args) => textResult(await diagnoseProjectSessions(args.workspaceRoot, routing.registryRoot)));
|
|
23228
|
+
}
|
|
23229
|
+
if (routing || bridge?.release) {
|
|
23060
23230
|
mcpServer.registerTool("nudge_release", {
|
|
23061
23231
|
title: "Release Nudge session",
|
|
23062
23232
|
description: "Stop listening and release this adapter's project-session claim so another agent can connect.",
|
|
23063
23233
|
inputSchema: emptyInput,
|
|
23064
23234
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false }
|
|
23065
|
-
}, async () => {
|
|
23235
|
+
}, async (args) => {
|
|
23066
23236
|
try {
|
|
23067
|
-
await
|
|
23237
|
+
await (await target(args)).release();
|
|
23068
23238
|
return textResult({ released: true });
|
|
23069
23239
|
} catch (error2) {
|
|
23070
23240
|
return errorResult(error2);
|
|
@@ -23073,8 +23243,9 @@ function installTools(mcpServer, bridge) {
|
|
|
23073
23243
|
}
|
|
23074
23244
|
mcpServer.registerTool("nudge_report_status", {
|
|
23075
23245
|
title: "Report Nudge request status",
|
|
23076
|
-
description: "Report working, completed, failed, or interrupted status for the prompt currently being handled.",
|
|
23246
|
+
description: "Report working, completed, failed, or interrupted status for the prompt currently being handled. Report completed only after implementing the request; the browser removes its submitted sketches if their revisions are unchanged.",
|
|
23077
23247
|
inputSchema: {
|
|
23248
|
+
...scopeInput,
|
|
23078
23249
|
requestId: external_exports.string().min(1).max(256),
|
|
23079
23250
|
status: external_exports.enum(["working", "completed", "failed", "interrupted"]),
|
|
23080
23251
|
summary: external_exports.string().max(32e3).optional(),
|
|
@@ -23089,8 +23260,8 @@ function installTools(mcpServer, bridge) {
|
|
|
23089
23260
|
...args.error === void 0 ? {} : { error: args.error }
|
|
23090
23261
|
};
|
|
23091
23262
|
try {
|
|
23092
|
-
const updated = await
|
|
23093
|
-
return textResult(updated ?? await
|
|
23263
|
+
const updated = await (await target(args)).updateRequestStatus(update);
|
|
23264
|
+
return textResult(updated ?? await (await target(args)).getStatus());
|
|
23094
23265
|
} catch (error2) {
|
|
23095
23266
|
return errorResult(error2);
|
|
23096
23267
|
}
|
|
@@ -23100,9 +23271,9 @@ function installTools(mcpServer, bridge) {
|
|
|
23100
23271
|
description: "Read the paired browser's current Canvas groups and focus.",
|
|
23101
23272
|
inputSchema: emptyInput,
|
|
23102
23273
|
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false }
|
|
23103
|
-
}, async () => {
|
|
23274
|
+
}, async (args) => {
|
|
23104
23275
|
try {
|
|
23105
|
-
return commandResult(await
|
|
23276
|
+
return commandResult(await (await target(args)).dispatchCanvasCommand({ type: "read-state" }));
|
|
23106
23277
|
} catch (error2) {
|
|
23107
23278
|
return errorResult(error2);
|
|
23108
23279
|
}
|
|
@@ -23111,6 +23282,7 @@ function installTools(mcpServer, bridge) {
|
|
|
23111
23282
|
title: "Present routes in Canvas",
|
|
23112
23283
|
description: "Append or focus an agent-owned, labeled comparison group of same-origin project routes in Canvas.",
|
|
23113
23284
|
inputSchema: {
|
|
23285
|
+
...scopeInput,
|
|
23114
23286
|
label: external_exports.string().min(1).max(160),
|
|
23115
23287
|
groupId: external_exports.string().min(1).max(256).regex(/^agent-[A-Za-z0-9._:-]+$/).optional(),
|
|
23116
23288
|
routes: external_exports.array(external_exports.object({
|
|
@@ -23122,7 +23294,7 @@ function installTools(mcpServer, bridge) {
|
|
|
23122
23294
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false }
|
|
23123
23295
|
}, async (args) => {
|
|
23124
23296
|
try {
|
|
23125
|
-
return commandResult(await
|
|
23297
|
+
return commandResult(await (await target(args)).dispatchCanvasCommand({
|
|
23126
23298
|
type: "present-routes",
|
|
23127
23299
|
groupId: args.groupId ?? `agent-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`,
|
|
23128
23300
|
label: args.label,
|
|
@@ -23135,11 +23307,11 @@ function installTools(mcpServer, bridge) {
|
|
|
23135
23307
|
mcpServer.registerTool("nudge_focus_canvas_group", {
|
|
23136
23308
|
title: "Focus Canvas group",
|
|
23137
23309
|
description: "Focus an existing Canvas comparison group by its stable group ID.",
|
|
23138
|
-
inputSchema: { groupId: external_exports.string().min(1).max(256) },
|
|
23310
|
+
inputSchema: { ...scopeInput, groupId: external_exports.string().min(1).max(256) },
|
|
23139
23311
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false }
|
|
23140
23312
|
}, async (args) => {
|
|
23141
23313
|
try {
|
|
23142
|
-
return commandResult(await
|
|
23314
|
+
return commandResult(await (await target(args)).dispatchCanvasCommand({ type: "focus-group", groupId: args.groupId }));
|
|
23143
23315
|
} catch (error2) {
|
|
23144
23316
|
return errorResult(error2);
|
|
23145
23317
|
}
|
|
@@ -23149,9 +23321,9 @@ function installTools(mcpServer, bridge) {
|
|
|
23149
23321
|
description: "Fit all current Canvas cards into the browser board.",
|
|
23150
23322
|
inputSchema: emptyInput,
|
|
23151
23323
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false }
|
|
23152
|
-
}, async () => {
|
|
23324
|
+
}, async (args) => {
|
|
23153
23325
|
try {
|
|
23154
|
-
return commandResult(await
|
|
23326
|
+
return commandResult(await (await target(args)).dispatchCanvasCommand({ type: "fit-all" }));
|
|
23155
23327
|
} catch (error2) {
|
|
23156
23328
|
return errorResult(error2);
|
|
23157
23329
|
}
|
|
@@ -23159,11 +23331,11 @@ function installTools(mcpServer, bridge) {
|
|
|
23159
23331
|
mcpServer.registerTool("nudge_remove_canvas_group", {
|
|
23160
23332
|
title: "Remove agent Canvas group",
|
|
23161
23333
|
description: "Remove an agent-owned Canvas comparison group. The browser refuses removal of user-owned groups.",
|
|
23162
|
-
inputSchema: { groupId: external_exports.string().min(1).max(256).regex(/^agent-[A-Za-z0-9._:-]+$/) },
|
|
23334
|
+
inputSchema: { ...scopeInput, groupId: external_exports.string().min(1).max(256).regex(/^agent-[A-Za-z0-9._:-]+$/) },
|
|
23163
23335
|
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false }
|
|
23164
23336
|
}, async (args) => {
|
|
23165
23337
|
try {
|
|
23166
|
-
return commandResult(await
|
|
23338
|
+
return commandResult(await (await target(args)).dispatchCanvasCommand({ type: "remove-group", groupId: args.groupId }));
|
|
23167
23339
|
} catch (error2) {
|
|
23168
23340
|
return errorResult(error2);
|
|
23169
23341
|
}
|
|
@@ -23221,7 +23393,8 @@ function createAgentCompanion(options) {
|
|
|
23221
23393
|
};
|
|
23222
23394
|
}
|
|
23223
23395
|
async function createDiscoveredAgentAdapter(options) {
|
|
23224
|
-
const
|
|
23396
|
+
const routing = options.workspaceRoot === void 0 ? new AdapterRouting(options.registryRoot) : void 0;
|
|
23397
|
+
const router = options.workspaceRoot === void 0 ? void 0 : await DiscoveredProjectRouter.create(options.workspaceRoot, options.registryRoot);
|
|
23225
23398
|
const mcpServer = new McpServer(
|
|
23226
23399
|
{
|
|
23227
23400
|
name: options.serverName ?? MCP_SERVER_NAME,
|
|
@@ -23229,13 +23402,13 @@ async function createDiscoveredAgentAdapter(options) {
|
|
|
23229
23402
|
},
|
|
23230
23403
|
{ instructions: MCP_SERVER_INSTRUCTIONS }
|
|
23231
23404
|
);
|
|
23232
|
-
installTools(mcpServer, router);
|
|
23405
|
+
installTools(mcpServer, router, routing);
|
|
23233
23406
|
let transport = null;
|
|
23234
23407
|
let started = false;
|
|
23235
23408
|
let closePromise = null;
|
|
23236
23409
|
return {
|
|
23237
23410
|
mcpServer,
|
|
23238
|
-
router,
|
|
23411
|
+
router: router ?? routing,
|
|
23239
23412
|
async start(providedTransport) {
|
|
23240
23413
|
if (started) return;
|
|
23241
23414
|
if (closePromise) throw new Error("The Nudge MCP adapter is closing.");
|
|
@@ -23246,7 +23419,7 @@ async function createDiscoveredAgentAdapter(options) {
|
|
|
23246
23419
|
close() {
|
|
23247
23420
|
if (closePromise) return closePromise;
|
|
23248
23421
|
closePromise = (async () => {
|
|
23249
|
-
await router.close();
|
|
23422
|
+
await (router ?? routing).close();
|
|
23250
23423
|
if (transport) await mcpServer.close().catch(() => void 0);
|
|
23251
23424
|
})();
|
|
23252
23425
|
return closePromise;
|
|
@@ -23309,7 +23482,7 @@ function parseCliArguments(argv = process.argv.slice(2), environment = process.e
|
|
|
23309
23482
|
let host = environment.NUDGE_UI_BRIDGE_HOST ?? "127.0.0.1";
|
|
23310
23483
|
let port = environment.NUDGE_UI_BRIDGE_PORT === void 0 ? DEFAULT_PORT : parseInteger(environment.NUDGE_UI_BRIDGE_PORT, "NUDGE_UI_BRIDGE_PORT", 65535);
|
|
23311
23484
|
let commandTimeoutMs = environment.NUDGE_UI_COMMAND_TIMEOUT_MS === void 0 ? DEFAULT_COMMAND_TIMEOUT_MS2 : parseInteger(environment.NUDGE_UI_COMMAND_TIMEOUT_MS, "NUDGE_UI_COMMAND_TIMEOUT_MS", 6e4);
|
|
23312
|
-
let workspaceRoot = root;
|
|
23485
|
+
let workspaceRoot = environment.NUDGE_UI_WORKSPACE_ROOT === void 0 ? void 0 : root;
|
|
23313
23486
|
let help = false;
|
|
23314
23487
|
let explicitPort = environment.NUDGE_UI_BRIDGE_PORT !== void 0;
|
|
23315
23488
|
for (let index = 0; index < argumentsToParse.length; index += 1) {
|
|
@@ -23366,7 +23539,7 @@ running project unless --origin selects the legacy coupled mode.
|
|
|
23366
23539
|
Options:
|
|
23367
23540
|
--project-id <id> Stable project pairing identity
|
|
23368
23541
|
--origin <origin> Exact app origin required for browser pairing
|
|
23369
|
-
--workspace-root <path>
|
|
23542
|
+
--workspace-root <path> Optional legacy project restriction; omitted for reusable adapters
|
|
23370
23543
|
--host <loopback> 127.0.0.1, ::1, or localhost
|
|
23371
23544
|
--port <port> Bridge port; default is deterministic per project, 0 is ephemeral
|
|
23372
23545
|
--command-timeout-ms <ms> Canvas acknowledgement timeout
|
|
@@ -23380,12 +23553,18 @@ async function runCli(argv = process.argv.slice(2), environment = process.env) {
|
|
|
23380
23553
|
}
|
|
23381
23554
|
if (options.command === "doctor") {
|
|
23382
23555
|
const sessions = await discoverProjectSessions(options.workspaceRoot);
|
|
23383
|
-
const matches = sessions.filter((session) => session.matchesApplication);
|
|
23556
|
+
const matches = sessions.filter((session) => options.workspaceRoot === void 0 || session.matchesApplication);
|
|
23557
|
+
const diagnostics = await diagnoseProjectSessions(options.workspaceRoot);
|
|
23384
23558
|
process.stdout.write([
|
|
23385
|
-
`[nudge-ui]
|
|
23559
|
+
`[nudge-ui] registry: ${diagnostics.registryRoot}`,
|
|
23560
|
+
`[nudge-ui] workspace: ${diagnostics.workspaceRoot ?? "all projects"}`,
|
|
23561
|
+
`[nudge-ui] application: ${diagnostics.applicationRoot ?? "all applications"}`,
|
|
23562
|
+
`[nudge-ui] descriptors: ${diagnostics.descriptorCount}; invalid: ${diagnostics.invalidCount}; incompatible: ${diagnostics.incompatibleCount}`,
|
|
23563
|
+
`[nudge-ui] matching unreachable descriptors: ${diagnostics.matchingUnreachableCount}`,
|
|
23564
|
+
...diagnostics.guidance.map((message) => `[nudge-ui] ${message}`),
|
|
23386
23565
|
`[nudge-ui] live sessions: ${sessions.length}`,
|
|
23387
23566
|
`[nudge-ui] matching sessions: ${matches.length}`,
|
|
23388
|
-
...matches.length === 0 ? ["[nudge-ui] no
|
|
23567
|
+
...matches.length === 0 ? ["[nudge-ui] no reachable compatible bridge matches; inspect the diagnostics above or start the development server"] : matches.map((session) => {
|
|
23389
23568
|
const state = session.status.connection === "working" ? "working" : session.status.listenerActive ? "listening" : "available";
|
|
23390
23569
|
return `[nudge-ui] ${state}: ${session.projectId} (${session.origin})${session.claimed ? " [claimed]" : ""}`;
|
|
23391
23570
|
})
|
|
@@ -23419,8 +23598,10 @@ async function runCli(argv = process.argv.slice(2), environment = process.env) {
|
|
|
23419
23598
|
}
|
|
23420
23599
|
return;
|
|
23421
23600
|
}
|
|
23601
|
+
process.stderr.write("[nudge-ui] --origin uses deprecated coupled mode. Rerun agent setup to use project-owned bridges and avoid port collisions.\n");
|
|
23422
23602
|
const companion = createAgentCompanion({
|
|
23423
23603
|
...options,
|
|
23604
|
+
workspaceRoot: options.workspaceRoot ?? resolve4(environment.INIT_CWD ?? process.cwd()),
|
|
23424
23605
|
onControllerUnavailable: (pageUrl) => openPairedPage(pageUrl).then(() => void 0)
|
|
23425
23606
|
});
|
|
23426
23607
|
let stopping = false;
|
package/dist/discovery.d.ts
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import type { AgentPromptRequest, AgentStatusSnapshot, AgentStatusUpdate, CanvasCommandResult } from "./internal/agent-protocol.js";
|
|
2
2
|
import type { CanvasCommandInput } from "./bridge.js";
|
|
3
|
-
import { type ProjectSession } from "./project.js";
|
|
3
|
+
import { type ProjectSession, type StoredProjectSession } from "./project.js";
|
|
4
4
|
export interface DiscoveredProjectSession extends ProjectSession {
|
|
5
5
|
readonly matchesWorkspace: boolean;
|
|
6
6
|
readonly matchesApplication: boolean;
|
|
7
7
|
readonly claimed: boolean;
|
|
8
8
|
readonly status: Pick<AgentStatusSnapshot, "connection" | "listenerActive" | "paired">;
|
|
9
9
|
}
|
|
10
|
-
export declare function discoverProjectSessions(applicationRoot: string, registryRoot?: string): Promise<DiscoveredProjectSession[]>;
|
|
10
|
+
export declare function discoverProjectSessions(applicationRoot: string | undefined, registryRoot?: string): Promise<DiscoveredProjectSession[]>;
|
|
11
11
|
/** Routes MCP tools to a live project in the adapter's exact worktree. */
|
|
12
12
|
export declare class DiscoveredProjectRouter {
|
|
13
13
|
readonly workspaceRoot: string;
|
|
@@ -16,10 +16,14 @@ export declare class DiscoveredProjectRouter {
|
|
|
16
16
|
readonly agentId: string;
|
|
17
17
|
private client;
|
|
18
18
|
private releaseGeneration;
|
|
19
|
+
private connectionQueue;
|
|
19
20
|
private constructor();
|
|
21
|
+
get selectedSessionId(): string | undefined;
|
|
22
|
+
static forSession(session: StoredProjectSession, registryRoot: string): DiscoveredProjectRouter;
|
|
20
23
|
static create(workspaceRoot: string, registryRoot?: string): Promise<DiscoveredProjectRouter>;
|
|
21
24
|
listSessions(): Promise<DiscoveredProjectSession[]>;
|
|
22
25
|
private connectToSession;
|
|
26
|
+
private selectSession;
|
|
23
27
|
waitForPrompt(signal?: AbortSignal, sessionId?: string): Promise<AgentPromptRequest>;
|
|
24
28
|
getStatus(): Promise<AgentStatusSnapshot>;
|
|
25
29
|
updateRequestStatus(update: AgentStatusUpdate): Promise<AgentStatusSnapshot>;
|
|
@@ -27,3 +31,16 @@ export declare class DiscoveredProjectRouter {
|
|
|
27
31
|
release(): Promise<void>;
|
|
28
32
|
close(): Promise<void>;
|
|
29
33
|
}
|
|
34
|
+
/** Returns diagnostic counts, scope, and public paths without exposing registry credentials. */
|
|
35
|
+
export declare function diagnoseProjectSessions(applicationRoot?: string, registryRoot?: string): Promise<{
|
|
36
|
+
registryRoot: string;
|
|
37
|
+
workspaceRoot: string | undefined;
|
|
38
|
+
applicationRoot: string | undefined;
|
|
39
|
+
descriptorCount: number;
|
|
40
|
+
invalidCount: number;
|
|
41
|
+
incompatibleCount: number;
|
|
42
|
+
reachableCount: number;
|
|
43
|
+
matchingReachableCount: number;
|
|
44
|
+
matchingUnreachableCount: number;
|
|
45
|
+
guidance: string[];
|
|
46
|
+
}>;
|