@snutils/snu 0.2.2 → 0.2.5
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/cli/attachment.d.ts +60 -0
- package/dist/cli/attachment.d.ts.map +1 -0
- package/dist/cli/attachment.js +52 -0
- package/dist/cli/attachment.js.map +1 -0
- package/dist/cli/daemon.d.ts +63 -0
- package/dist/cli/daemon.d.ts.map +1 -1
- package/dist/cli/daemon.js +163 -0
- package/dist/cli/daemon.js.map +1 -1
- package/dist/cli/doctor.d.ts +155 -0
- package/dist/cli/doctor.d.ts.map +1 -0
- package/dist/cli/doctor.js +394 -0
- package/dist/cli/doctor.js.map +1 -0
- package/dist/cli/index.d.ts +23 -0
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +200 -18
- package/dist/cli/index.js.map +1 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +44 -5
- package/dist/client.js.map +1 -1
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js +44 -25
- package/dist/mcp/index.js.map +1 -1
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +28 -5
- package/dist/registry.js.map +1 -1
- package/dist/server/dispatcher.d.ts +9 -0
- package/dist/server/dispatcher.d.ts.map +1 -1
- package/dist/server/dispatcher.js +136 -3
- package/dist/server/dispatcher.js.map +1 -1
- package/dist/server/httpBridge.d.ts.map +1 -1
- package/dist/server/httpBridge.js +2 -1
- package/dist/server/httpBridge.js.map +1 -1
- package/dist/server/scopeResolver.d.ts +85 -0
- package/dist/server/scopeResolver.d.ts.map +1 -0
- package/dist/server/scopeResolver.js +116 -0
- package/dist/server/scopeResolver.js.map +1 -0
- package/dist/server/scopeVectors.d.ts +30 -0
- package/dist/server/scopeVectors.d.ts.map +1 -0
- package/dist/server/scopeVectors.js +90 -0
- package/dist/server/scopeVectors.js.map +1 -0
- package/dist/server/standalone.d.ts +13 -0
- package/dist/server/standalone.d.ts.map +1 -1
- package/dist/server/standalone.js +70 -3
- package/dist/server/standalone.js.map +1 -1
- package/dist/types.d.ts +12 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +8 -0
- package/dist/types.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { HealthResponse } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* How an MCP server (or any client) should obtain a bridge.
|
|
4
|
+
*
|
|
5
|
+
* The old rule was "if discovery fails, take port 1978 and start our own". That
|
|
6
|
+
* conflates two very different failures. Discovery fails when the *descriptor*
|
|
7
|
+
* is missing, stale, or clobbered — which says nothing about whether a bridge
|
|
8
|
+
* is running. A healthy standalone bridge started by another MCP client whose
|
|
9
|
+
* port file had been removed was therefore stopped and replaced, which is the
|
|
10
|
+
* "multiple MCP clients reclaim each other's ports" failure exactly.
|
|
11
|
+
*
|
|
12
|
+
* A running bridge is proven by its health endpoint, not by a file. So an
|
|
13
|
+
* undiscoverable-but-serving bridge is attached to, never displaced; only a
|
|
14
|
+
* port held by something that answers nothing may be reclaimed.
|
|
15
|
+
*
|
|
16
|
+
* Pure decision logic with injected probes: no sockets, no process control.
|
|
17
|
+
*/
|
|
18
|
+
export type AttachmentMode =
|
|
19
|
+
/** Found through the normal port-descriptor path. */
|
|
20
|
+
'attached-discovered'
|
|
21
|
+
/** Serving, but its descriptor was missing or stale. Attached anyway. */
|
|
22
|
+
| 'attached-undiscoverable'
|
|
23
|
+
/** No bridge was serving; this process must start one. */
|
|
24
|
+
| 'create-standalone';
|
|
25
|
+
export interface BridgeAttachment {
|
|
26
|
+
mode: AttachmentMode;
|
|
27
|
+
/** HTTP port of the bridge to talk to, when one was found. */
|
|
28
|
+
port?: number;
|
|
29
|
+
pid?: number;
|
|
30
|
+
hostKind?: string;
|
|
31
|
+
/** Human-readable explanation, surfaced in MCP startup logs and diagnostics. */
|
|
32
|
+
reason: string;
|
|
33
|
+
/**
|
|
34
|
+
* True only when the caller is allowed to reclaim a held port before
|
|
35
|
+
* binding. Never true while any bridge is answering.
|
|
36
|
+
*/
|
|
37
|
+
mayReclaimPorts: boolean;
|
|
38
|
+
}
|
|
39
|
+
export interface AttachmentProbes {
|
|
40
|
+
/** Normal discovery. Should reject the way discoverBridge does. */
|
|
41
|
+
discover: () => Promise<{
|
|
42
|
+
port: number;
|
|
43
|
+
pid: number;
|
|
44
|
+
} | undefined>;
|
|
45
|
+
/** Probe a port's health endpoint; undefined when nothing healthy answers. */
|
|
46
|
+
probeHealth: (port: number) => Promise<HealthResponse | undefined>;
|
|
47
|
+
/** Ports to check when discovery fails, in order. */
|
|
48
|
+
candidatePorts?: number[];
|
|
49
|
+
}
|
|
50
|
+
export declare const DEFAULT_HTTP_PORT = 1977;
|
|
51
|
+
/**
|
|
52
|
+
* Decide how to obtain a bridge, without changing anything.
|
|
53
|
+
*
|
|
54
|
+
* Discovery failures that mean "no bridge" and failures that mean "a bridge is
|
|
55
|
+
* running but is not registered" are deliberately handled the same way here:
|
|
56
|
+
* both fall through to a health probe, because only the probe can tell them
|
|
57
|
+
* apart.
|
|
58
|
+
*/
|
|
59
|
+
export declare function resolveBridgeAttachment(probes: AttachmentProbes): Promise<BridgeAttachment>;
|
|
60
|
+
//# sourceMappingURL=attachment.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"attachment.d.ts","sourceRoot":"","sources":["../../src/cli/attachment.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAE7C;;;;;;;;;;;;;;;GAeG;AAEH,MAAM,MAAM,cAAc;AACzB,qDAAqD;AACnD,qBAAqB;AACvB,yEAAyE;GACvE,yBAAyB;AAC3B,0DAA0D;GACxD,mBAAmB,CAAC;AAEvB,MAAM,WAAW,gBAAgB;IAChC,IAAI,EAAE,cAAc,CAAC;IACrB,8DAA8D;IAC9D,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,gFAAgF;IAChF,MAAM,EAAE,MAAM,CAAC;IACf;;;OAGG;IACH,eAAe,EAAE,OAAO,CAAC;CACzB;AAED,MAAM,WAAW,gBAAgB;IAChC,mEAAmE;IACnE,QAAQ,EAAE,MAAM,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,GAAG,SAAS,CAAC,CAAC;IACnE,8EAA8E;IAC9E,WAAW,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,cAAc,GAAG,SAAS,CAAC,CAAC;IACnE,qDAAqD;IACrD,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;CAC1B;AAED,eAAO,MAAM,iBAAiB,OAAO,CAAC;AAEtC;;;;;;;GAOG;AACH,wBAAsB,uBAAuB,CAAC,MAAM,EAAE,gBAAgB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAuCjG"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DEFAULT_HTTP_PORT = void 0;
|
|
4
|
+
exports.resolveBridgeAttachment = resolveBridgeAttachment;
|
|
5
|
+
exports.DEFAULT_HTTP_PORT = 1977;
|
|
6
|
+
/**
|
|
7
|
+
* Decide how to obtain a bridge, without changing anything.
|
|
8
|
+
*
|
|
9
|
+
* Discovery failures that mean "no bridge" and failures that mean "a bridge is
|
|
10
|
+
* running but is not registered" are deliberately handled the same way here:
|
|
11
|
+
* both fall through to a health probe, because only the probe can tell them
|
|
12
|
+
* apart.
|
|
13
|
+
*/
|
|
14
|
+
async function resolveBridgeAttachment(probes) {
|
|
15
|
+
try {
|
|
16
|
+
const found = await probes.discover();
|
|
17
|
+
if (found) {
|
|
18
|
+
return {
|
|
19
|
+
mode: 'attached-discovered',
|
|
20
|
+
port: found.port,
|
|
21
|
+
pid: found.pid,
|
|
22
|
+
reason: `Attached to the bridge registered on port ${found.port} (PID ${found.pid}).`,
|
|
23
|
+
mayReclaimPorts: false,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
// Discovery says nothing about whether a bridge is running — only that
|
|
29
|
+
// no usable descriptor was found. Fall through and ask the ports.
|
|
30
|
+
}
|
|
31
|
+
const candidates = probes.candidatePorts ?? [exports.DEFAULT_HTTP_PORT];
|
|
32
|
+
for (const port of candidates) {
|
|
33
|
+
const health = await probes.probeHealth(port);
|
|
34
|
+
if (health) {
|
|
35
|
+
return {
|
|
36
|
+
mode: 'attached-undiscoverable',
|
|
37
|
+
port,
|
|
38
|
+
pid: health.pid,
|
|
39
|
+
hostKind: health.hostKind,
|
|
40
|
+
reason: `Attached to a healthy ${health.hostKind || 'unknown'} bridge on port ${port} (PID ${health.pid}) ` +
|
|
41
|
+
`whose port descriptor was missing or stale. It was not replaced.`,
|
|
42
|
+
mayReclaimPorts: false,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return {
|
|
47
|
+
mode: 'create-standalone',
|
|
48
|
+
reason: 'No bridge is answering on the known ports; starting an in-process standalone bridge.',
|
|
49
|
+
mayReclaimPorts: true,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=attachment.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"attachment.js","sourceRoot":"","sources":["../../src/cli/attachment.ts"],"names":[],"mappings":";;;AA6DA,0DAuCC;AAjDY,QAAA,iBAAiB,GAAG,IAAI,CAAC;AAEtC;;;;;;;GAOG;AACI,KAAK,UAAU,uBAAuB,CAAC,MAAwB;IACrE,IAAI,CAAC;QACJ,MAAM,KAAK,GAAG,MAAM,MAAM,CAAC,QAAQ,EAAE,CAAC;QACtC,IAAI,KAAK,EAAE,CAAC;YACX,OAAO;gBACN,IAAI,EAAE,qBAAqB;gBAC3B,IAAI,EAAE,KAAK,CAAC,IAAI;gBAChB,GAAG,EAAE,KAAK,CAAC,GAAG;gBACd,MAAM,EAAE,6CAA6C,KAAK,CAAC,IAAI,SAAS,KAAK,CAAC,GAAG,IAAI;gBACrF,eAAe,EAAE,KAAK;aACtB,CAAC;QACH,CAAC;IACF,CAAC;IAAC,MAAM,CAAC;QACR,uEAAuE;QACvE,kEAAkE;IACnE,CAAC;IAED,MAAM,UAAU,GAAG,MAAM,CAAC,cAAc,IAAI,CAAC,yBAAiB,CAAC,CAAC;IAChE,KAAK,MAAM,IAAI,IAAI,UAAU,EAAE,CAAC;QAC/B,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC;QAC9C,IAAI,MAAM,EAAE,CAAC;YACZ,OAAO;gBACN,IAAI,EAAE,yBAAyB;gBAC/B,IAAI;gBACJ,GAAG,EAAE,MAAM,CAAC,GAAG;gBACf,QAAQ,EAAE,MAAM,CAAC,QAAQ;gBACzB,MAAM,EACL,yBAAyB,MAAM,CAAC,QAAQ,IAAI,SAAS,mBAAmB,IAAI,SAAS,MAAM,CAAC,GAAG,IAAI;oBACnG,kEAAkE;gBACnE,eAAe,EAAE,KAAK;aACtB,CAAC;QACH,CAAC;IACF,CAAC;IAED,OAAO;QACN,IAAI,EAAE,mBAAmB;QACzB,MAAM,EAAE,sFAAsF;QAC9F,eAAe,EAAE,IAAI;KACrB,CAAC;AACH,CAAC"}
|
package/dist/cli/daemon.d.ts
CHANGED
|
@@ -26,5 +26,68 @@ export declare function inspectBridge(options?: DiscoveryOptions): Promise<Bridg
|
|
|
26
26
|
export declare function assertStandaloneBridge(status: BridgeStatus): asserts status is ActiveBridgeStatus;
|
|
27
27
|
export declare function requestStandaloneYield(status: BridgeStatus, fetchImpl?: YieldFetch): Promise<void>;
|
|
28
28
|
export declare function waitForBridgeExit(pid: number, timeoutMs?: number, aliveCheck?: (pid: number) => boolean): Promise<void>;
|
|
29
|
+
/**
|
|
30
|
+
* Send a lifecycle command to an editor-hosted bridge.
|
|
31
|
+
*
|
|
32
|
+
* The standalone bridge exposes /api/yield; the editor host routes everything
|
|
33
|
+
* through POST /api. Recovering an editor-hosted bridge used to be impossible
|
|
34
|
+
* from here — the CLI refused, and the user had to find and kill an extension
|
|
35
|
+
* host PID by hand. Asking the owning window to stand down is the safe
|
|
36
|
+
* equivalent: no signals are ever sent to an editor process.
|
|
37
|
+
*/
|
|
38
|
+
export declare function requestEditorBridgeLifecycle(status: ActiveBridgeStatus, command: 'yield' | 'restart', fetchImpl?: YieldFetch): Promise<void>;
|
|
39
|
+
/**
|
|
40
|
+
* Wait for an editor-hosted bridge to go quiet.
|
|
41
|
+
*
|
|
42
|
+
* The extension-host process itself stays alive, so PID liveness proves
|
|
43
|
+
* nothing here — the health endpoint is the only honest signal.
|
|
44
|
+
*/
|
|
45
|
+
export declare function waitForBridgeUnreachable(port: number, timeoutMs?: number): Promise<void>;
|
|
46
|
+
/** Wait for a bridge to answer again after a restart. */
|
|
47
|
+
export declare function waitForBridgeReachable(port: number, timeoutMs?: number): Promise<HealthResponse>;
|
|
48
|
+
/**
|
|
49
|
+
* Wait for a bridge to come back up after a restart.
|
|
50
|
+
*
|
|
51
|
+
* Two assumptions had to go.
|
|
52
|
+
*
|
|
53
|
+
* Waiting for it to go *unreachable* first looks natural and is wrong: the
|
|
54
|
+
* down-state is transient, and a stop/start that completes between two polls is
|
|
55
|
+
* never observed, so `snu restart --json` reported E_STOP_TIMEOUT for restarts
|
|
56
|
+
* that had already succeeded.
|
|
57
|
+
*
|
|
58
|
+
* Assuming the port is stable is also wrong. The bridge prefers 1977 and falls
|
|
59
|
+
* back to an ephemeral port when it is taken — which is exactly what happens
|
|
60
|
+
* right after a force takeover, while the displaced process still holds 1977.
|
|
61
|
+
* The restart then moves it back, and polling only the pre-restart port waited
|
|
62
|
+
* out the timeout against an address nothing was serving any more.
|
|
63
|
+
*
|
|
64
|
+
* So: poll every plausible port, and re-read discovery each round to pick up a
|
|
65
|
+
* port that only the descriptor knows about.
|
|
66
|
+
*/
|
|
67
|
+
export declare function waitForBridgeRestarted(ports: number | number[], previous: {
|
|
68
|
+
startedAt?: number;
|
|
69
|
+
pid?: number;
|
|
70
|
+
}, options?: {
|
|
71
|
+
timeoutMs?: number;
|
|
72
|
+
/** Re-read the port descriptor, to catch a bridge that moved. */
|
|
73
|
+
rediscover?: () => Promise<number | undefined>;
|
|
74
|
+
}): Promise<HealthResponse>;
|
|
75
|
+
/**
|
|
76
|
+
* Wait, briefly and optimistically, for a restarted bridge to become
|
|
77
|
+
* discoverable again.
|
|
78
|
+
*
|
|
79
|
+
* Serving and discoverable are not the same state. The global descriptor
|
|
80
|
+
* (~/.sn-scriptsync/agent-port.json) is removed on every start and only
|
|
81
|
+
* re-written once the browser helper reconnects and reports a Pro licence, so
|
|
82
|
+
* for roughly a second after a restart the bridge answers health while no
|
|
83
|
+
* descriptor names it. `snu restart` reported success in that window and an
|
|
84
|
+
* immediate `snu status` then described a healthy orphan.
|
|
85
|
+
*
|
|
86
|
+
* This never fails the restart. A workspace-external caller without a Pro
|
|
87
|
+
* licence has no global descriptor at all, and blocking on one that is never
|
|
88
|
+
* coming would turn a working restart into a hang. The caller reports what is
|
|
89
|
+
* true instead: restarted yes, discoverable not yet.
|
|
90
|
+
*/
|
|
91
|
+
export declare function waitForDiscovery(options: DiscoveryOptions, timeoutMs?: number): Promise<boolean>;
|
|
29
92
|
export {};
|
|
30
93
|
//# sourceMappingURL=daemon.d.ts.map
|
package/dist/cli/daemon.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"daemon.d.ts","sourceRoot":"","sources":["../../src/cli/daemon.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,gBAAgB,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAEhF,UAAU,aAAa;IACrB,EAAE,EAAE,OAAO,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,IAAI,OAAO,CAAC;QAAE,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACtD;AAED,KAAK,UAAU,GAAG,CAChB,GAAG,EAAE,MAAM,EACX,IAAI,EAAE;IACJ,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,WAAW,CAAC;CACrB,KACE,OAAO,CAAC,aAAa,CAAC,CAAC;AAE5B,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,IAAI,CAAC;IACd,SAAS,EAAE,eAAe,CAAC;IAC3B,MAAM,EAAE,cAAc,CAAC;CACxB;AAED,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,KAAK,CAAC;CAChB;AAED,MAAM,MAAM,YAAY,GAAG,kBAAkB,GAAG,oBAAoB,CAAC;AAErE,wBAAsB,aAAa,CAAC,OAAO,GAAE,gBAAqB,GAAG,OAAO,CAAC,YAAY,CAAC,CAezF;AAED,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,MAAM,IAAI,kBAAkB,CAajG;AAED,wBAAsB,sBAAsB,CAC1C,MAAM,EAAE,YAAY,EACpB,SAAS,GAAE,UAA2C,GACrD,OAAO,CAAC,IAAI,CAAC,CAgCf;AAWD,wBAAsB,iBAAiB,CACrC,GAAG,EAAE,MAAM,EACX,SAAS,SAAQ,EACjB,UAAU,GAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAoB,GAChD,OAAO,CAAC,IAAI,CAAC,CAUf"}
|
|
1
|
+
{"version":3,"file":"daemon.d.ts","sourceRoot":"","sources":["../../src/cli/daemon.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,gBAAgB,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAEhF,UAAU,aAAa;IACrB,EAAE,EAAE,OAAO,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,IAAI,OAAO,CAAC;QAAE,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACtD;AAED,KAAK,UAAU,GAAG,CAChB,GAAG,EAAE,MAAM,EACX,IAAI,EAAE;IACJ,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,WAAW,CAAC;CACrB,KACE,OAAO,CAAC,aAAa,CAAC,CAAC;AAE5B,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,IAAI,CAAC;IACd,SAAS,EAAE,eAAe,CAAC;IAC3B,MAAM,EAAE,cAAc,CAAC;CACxB;AAED,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,KAAK,CAAC;CAChB;AAED,MAAM,MAAM,YAAY,GAAG,kBAAkB,GAAG,oBAAoB,CAAC;AAErE,wBAAsB,aAAa,CAAC,OAAO,GAAE,gBAAqB,GAAG,OAAO,CAAC,YAAY,CAAC,CAezF;AAED,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,MAAM,IAAI,kBAAkB,CAajG;AAED,wBAAsB,sBAAsB,CAC1C,MAAM,EAAE,YAAY,EACpB,SAAS,GAAE,UAA2C,GACrD,OAAO,CAAC,IAAI,CAAC,CAgCf;AAWD,wBAAsB,iBAAiB,CACrC,GAAG,EAAE,MAAM,EACX,SAAS,SAAQ,EACjB,UAAU,GAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAoB,GAChD,OAAO,CAAC,IAAI,CAAC,CAUf;AAED;;;;;;;;GAQG;AACH,wBAAsB,4BAA4B,CAChD,MAAM,EAAE,kBAAkB,EAC1B,OAAO,EAAE,OAAO,GAAG,SAAS,EAC5B,SAAS,GAAE,UAA2C,GACrD,OAAO,CAAC,IAAI,CAAC,CA8Bf;AAED;;;;;GAKG;AACH,wBAAsB,wBAAwB,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,SAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAc7F;AAED,yDAAyD;AACzD,wBAAsB,sBAAsB,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,SAAS,GAAG,OAAO,CAAC,cAAc,CAAC,CAiBtG;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAsB,sBAAsB,CAC1C,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,EACxB,QAAQ,EAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,EAC9C,OAAO,GAAE;IACP,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,iEAAiE;IACjE,UAAU,CAAC,EAAE,MAAM,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;CAC3C,GACL,OAAO,CAAC,cAAc,CAAC,CA4CzB;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,gBAAgB,CACpC,OAAO,EAAE,gBAAgB,EACzB,SAAS,SAAQ,GAChB,OAAO,CAAC,OAAO,CAAC,CAYlB"}
|
package/dist/cli/daemon.js
CHANGED
|
@@ -4,6 +4,11 @@ exports.inspectBridge = inspectBridge;
|
|
|
4
4
|
exports.assertStandaloneBridge = assertStandaloneBridge;
|
|
5
5
|
exports.requestStandaloneYield = requestStandaloneYield;
|
|
6
6
|
exports.waitForBridgeExit = waitForBridgeExit;
|
|
7
|
+
exports.requestEditorBridgeLifecycle = requestEditorBridgeLifecycle;
|
|
8
|
+
exports.waitForBridgeUnreachable = waitForBridgeUnreachable;
|
|
9
|
+
exports.waitForBridgeReachable = waitForBridgeReachable;
|
|
10
|
+
exports.waitForBridgeRestarted = waitForBridgeRestarted;
|
|
11
|
+
exports.waitForDiscovery = waitForDiscovery;
|
|
7
12
|
const client_js_1 = require("../client.js");
|
|
8
13
|
async function inspectBridge(options = {}) {
|
|
9
14
|
try {
|
|
@@ -77,4 +82,162 @@ async function waitForBridgeExit(pid, timeoutMs = 5_000, aliveCheck = isPidAlive
|
|
|
77
82
|
}
|
|
78
83
|
throw new client_js_1.ScriptSyncClientError(`Standalone bridge PID ${pid} did not stop within ${timeoutMs / 1000}s.`, 'E_STOP_TIMEOUT');
|
|
79
84
|
}
|
|
85
|
+
/**
|
|
86
|
+
* Send a lifecycle command to an editor-hosted bridge.
|
|
87
|
+
*
|
|
88
|
+
* The standalone bridge exposes /api/yield; the editor host routes everything
|
|
89
|
+
* through POST /api. Recovering an editor-hosted bridge used to be impossible
|
|
90
|
+
* from here — the CLI refused, and the user had to find and kill an extension
|
|
91
|
+
* host PID by hand. Asking the owning window to stand down is the safe
|
|
92
|
+
* equivalent: no signals are ever sent to an editor process.
|
|
93
|
+
*/
|
|
94
|
+
async function requestEditorBridgeLifecycle(status, command, fetchImpl = fetch) {
|
|
95
|
+
const controller = new AbortController();
|
|
96
|
+
const timer = setTimeout(() => controller.abort(), 5_000);
|
|
97
|
+
try {
|
|
98
|
+
const response = await fetchImpl(`http://127.0.0.1:${status.discovery.port}/api`, {
|
|
99
|
+
method: 'POST',
|
|
100
|
+
headers: {
|
|
101
|
+
'Content-Type': 'application/json',
|
|
102
|
+
'X-Agent-Token': status.discovery.token,
|
|
103
|
+
},
|
|
104
|
+
body: JSON.stringify({ id: `snu_lifecycle_${command}`, command, params: {} }),
|
|
105
|
+
signal: controller.signal,
|
|
106
|
+
});
|
|
107
|
+
const payload = (await response.json().catch(() => ({})));
|
|
108
|
+
if (!response.ok || payload.status !== 'success') {
|
|
109
|
+
throw new client_js_1.ScriptSyncClientError(payload.error || `The editor-hosted bridge refused to ${command} (HTTP ${response.status}).`, command === 'yield' ? 'E_STOP_FAILED' : 'E_RESTART_FAILED', response.status);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
catch (err) {
|
|
113
|
+
if (err instanceof client_js_1.ScriptSyncClientError)
|
|
114
|
+
throw err;
|
|
115
|
+
throw new client_js_1.ScriptSyncClientError(`Could not ${command} the editor-hosted bridge: ${err?.message || err}`, command === 'yield' ? 'E_STOP_FAILED' : 'E_RESTART_FAILED');
|
|
116
|
+
}
|
|
117
|
+
finally {
|
|
118
|
+
clearTimeout(timer);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Wait for an editor-hosted bridge to go quiet.
|
|
123
|
+
*
|
|
124
|
+
* The extension-host process itself stays alive, so PID liveness proves
|
|
125
|
+
* nothing here — the health endpoint is the only honest signal.
|
|
126
|
+
*/
|
|
127
|
+
async function waitForBridgeUnreachable(port, timeoutMs = 8_000) {
|
|
128
|
+
const deadline = Date.now() + timeoutMs;
|
|
129
|
+
while (Date.now() < deadline) {
|
|
130
|
+
try {
|
|
131
|
+
await (0, client_js_1.checkHealth)(port);
|
|
132
|
+
}
|
|
133
|
+
catch {
|
|
134
|
+
return; // no longer answering: the transports are down
|
|
135
|
+
}
|
|
136
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
137
|
+
}
|
|
138
|
+
throw new client_js_1.ScriptSyncClientError(`The bridge on port ${port} was still answering after ${timeoutMs / 1000}s.`, 'E_STOP_TIMEOUT');
|
|
139
|
+
}
|
|
140
|
+
/** Wait for a bridge to answer again after a restart. */
|
|
141
|
+
async function waitForBridgeReachable(port, timeoutMs = 20_000) {
|
|
142
|
+
const deadline = Date.now() + timeoutMs;
|
|
143
|
+
let lastError;
|
|
144
|
+
while (Date.now() < deadline) {
|
|
145
|
+
try {
|
|
146
|
+
return await (0, client_js_1.checkHealth)(port);
|
|
147
|
+
}
|
|
148
|
+
catch (err) {
|
|
149
|
+
lastError = err;
|
|
150
|
+
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
throw new client_js_1.ScriptSyncClientError(`The bridge did not come back on port ${port} within ${timeoutMs / 1000}s: ${lastError?.message || lastError}`, 'E_RESTART_FAILED');
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Wait for a bridge to come back up after a restart.
|
|
157
|
+
*
|
|
158
|
+
* Two assumptions had to go.
|
|
159
|
+
*
|
|
160
|
+
* Waiting for it to go *unreachable* first looks natural and is wrong: the
|
|
161
|
+
* down-state is transient, and a stop/start that completes between two polls is
|
|
162
|
+
* never observed, so `snu restart --json` reported E_STOP_TIMEOUT for restarts
|
|
163
|
+
* that had already succeeded.
|
|
164
|
+
*
|
|
165
|
+
* Assuming the port is stable is also wrong. The bridge prefers 1977 and falls
|
|
166
|
+
* back to an ephemeral port when it is taken — which is exactly what happens
|
|
167
|
+
* right after a force takeover, while the displaced process still holds 1977.
|
|
168
|
+
* The restart then moves it back, and polling only the pre-restart port waited
|
|
169
|
+
* out the timeout against an address nothing was serving any more.
|
|
170
|
+
*
|
|
171
|
+
* So: poll every plausible port, and re-read discovery each round to pick up a
|
|
172
|
+
* port that only the descriptor knows about.
|
|
173
|
+
*/
|
|
174
|
+
async function waitForBridgeRestarted(ports, previous, options = {}) {
|
|
175
|
+
const timeoutMs = options.timeoutMs ?? 25_000;
|
|
176
|
+
// Only the ports the caller nominated, plus whatever rediscovery turns up.
|
|
177
|
+
// Adding the fixed port here unconditionally would be wrong: an unrelated
|
|
178
|
+
// bridge on 1977 would satisfy the "different pid" test and be reported as
|
|
179
|
+
// this bridge's restart. Which ports are plausible is the caller's knowledge.
|
|
180
|
+
const candidates = new Set(Array.isArray(ports) ? ports : [ports]);
|
|
181
|
+
const isRestarted = (health) => (typeof health.startedAt === 'number' &&
|
|
182
|
+
typeof previous.startedAt === 'number' &&
|
|
183
|
+
health.startedAt !== previous.startedAt) ||
|
|
184
|
+
(typeof health.pid === 'number' && typeof previous.pid === 'number' && health.pid !== previous.pid);
|
|
185
|
+
const deadline = Date.now() + timeoutMs;
|
|
186
|
+
let lastError;
|
|
187
|
+
while (Date.now() < deadline) {
|
|
188
|
+
for (const port of Array.from(candidates)) {
|
|
189
|
+
try {
|
|
190
|
+
const health = await (0, client_js_1.checkHealth)(port);
|
|
191
|
+
if (isRestarted(health))
|
|
192
|
+
return health;
|
|
193
|
+
}
|
|
194
|
+
catch (err) {
|
|
195
|
+
lastError = err; // mid-cycle, or nothing on this port
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
if (options.rediscover) {
|
|
199
|
+
try {
|
|
200
|
+
const moved = await options.rediscover();
|
|
201
|
+
if (typeof moved === 'number')
|
|
202
|
+
candidates.add(moved);
|
|
203
|
+
}
|
|
204
|
+
catch {
|
|
205
|
+
/* the descriptor may be mid-rewrite */
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
await new Promise((resolve) => setTimeout(resolve, 150));
|
|
209
|
+
}
|
|
210
|
+
throw new client_js_1.ScriptSyncClientError(`The bridge did not report a restart within ${timeoutMs / 1000}s on ports ${Array.from(candidates).join(', ')}${lastError ? `: ${lastError?.message || lastError}` : ''}.`, 'E_RESTART_FAILED');
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Wait, briefly and optimistically, for a restarted bridge to become
|
|
214
|
+
* discoverable again.
|
|
215
|
+
*
|
|
216
|
+
* Serving and discoverable are not the same state. The global descriptor
|
|
217
|
+
* (~/.sn-scriptsync/agent-port.json) is removed on every start and only
|
|
218
|
+
* re-written once the browser helper reconnects and reports a Pro licence, so
|
|
219
|
+
* for roughly a second after a restart the bridge answers health while no
|
|
220
|
+
* descriptor names it. `snu restart` reported success in that window and an
|
|
221
|
+
* immediate `snu status` then described a healthy orphan.
|
|
222
|
+
*
|
|
223
|
+
* This never fails the restart. A workspace-external caller without a Pro
|
|
224
|
+
* licence has no global descriptor at all, and blocking on one that is never
|
|
225
|
+
* coming would turn a working restart into a hang. The caller reports what is
|
|
226
|
+
* true instead: restarted yes, discoverable not yet.
|
|
227
|
+
*/
|
|
228
|
+
async function waitForDiscovery(options, timeoutMs = 5_000) {
|
|
229
|
+
const deadline = Date.now() + timeoutMs;
|
|
230
|
+
while (Date.now() < deadline) {
|
|
231
|
+
try {
|
|
232
|
+
const status = await inspectBridge(options);
|
|
233
|
+
if (status.running)
|
|
234
|
+
return true;
|
|
235
|
+
}
|
|
236
|
+
catch {
|
|
237
|
+
/* discovery is allowed to fail while the descriptor is being rewritten */
|
|
238
|
+
}
|
|
239
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
240
|
+
}
|
|
241
|
+
return false;
|
|
242
|
+
}
|
|
80
243
|
//# sourceMappingURL=daemon.js.map
|
package/dist/cli/daemon.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"daemon.js","sourceRoot":"","sources":["../../src/cli/daemon.ts"],"names":[],"mappings":";;AA+BA,sCAeC;AAED,wDAaC;AAED,wDAmCC;AAWD,8CAcC;
|
|
1
|
+
{"version":3,"file":"daemon.js","sourceRoot":"","sources":["../../src/cli/daemon.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `snu doctor` — one bundle that explains why the bridge is not behaving,
|
|
3
|
+
* without leaking anything.
|
|
4
|
+
*
|
|
5
|
+
* Two rules shape this file.
|
|
6
|
+
*
|
|
7
|
+
* Redaction is an ALLOWLIST. Every field in the report is named here
|
|
8
|
+
* explicitly; anything not named is dropped. A blocklist would be the obvious
|
|
9
|
+
* design and the wrong one: the port descriptor carries an auth token, health
|
|
10
|
+
* responses may grow fields, and instance settings hold session material — with
|
|
11
|
+
* a blocklist, every future field leaks by default and the mistake is invisible
|
|
12
|
+
* until someone pastes a diagnostic into an issue. Here the failure mode is a
|
|
13
|
+
* missing field, which is loud and harmless.
|
|
14
|
+
*
|
|
15
|
+
* Collection is INJECTED. The report shape and the redaction are pure, so they
|
|
16
|
+
* can be tested against token-shaped fixtures without a bridge, processes or
|
|
17
|
+
* a network.
|
|
18
|
+
*/
|
|
19
|
+
export interface DoctorSources {
|
|
20
|
+
cliVersion: string;
|
|
21
|
+
/** Raw descriptor contents, keyed by the path they came from. */
|
|
22
|
+
descriptors: Array<{
|
|
23
|
+
path: string;
|
|
24
|
+
data: any | null;
|
|
25
|
+
error?: string;
|
|
26
|
+
}>;
|
|
27
|
+
/** Raw /api/health response, if any bridge answered. */
|
|
28
|
+
health?: any;
|
|
29
|
+
/** Processes listening on the bridge ports. */
|
|
30
|
+
listeners: Array<{
|
|
31
|
+
port: number;
|
|
32
|
+
pid: number;
|
|
33
|
+
command: string;
|
|
34
|
+
kind: string;
|
|
35
|
+
} | null>;
|
|
36
|
+
/** Owner lease contents, if present. */
|
|
37
|
+
lease?: any;
|
|
38
|
+
/** Instance folders discovered in the workspace. */
|
|
39
|
+
instances: Array<{
|
|
40
|
+
name: string;
|
|
41
|
+
url?: string | null;
|
|
42
|
+
hasSettings: boolean;
|
|
43
|
+
}>;
|
|
44
|
+
/** auth_status results per instance, if the bridge could be asked. */
|
|
45
|
+
auth?: Array<{
|
|
46
|
+
instance: string;
|
|
47
|
+
state?: string;
|
|
48
|
+
ok?: boolean;
|
|
49
|
+
lastValidatedAt?: number;
|
|
50
|
+
}>;
|
|
51
|
+
/** get_capabilities result, if reachable. */
|
|
52
|
+
capabilities?: any;
|
|
53
|
+
/** Recent lifecycle/bridge errors, newest last. */
|
|
54
|
+
recentErrors?: string[];
|
|
55
|
+
platform: string;
|
|
56
|
+
nodeVersion: string;
|
|
57
|
+
cwd: string;
|
|
58
|
+
}
|
|
59
|
+
export interface DoctorReport {
|
|
60
|
+
generatedAt: number | null;
|
|
61
|
+
versions: {
|
|
62
|
+
cli: string;
|
|
63
|
+
extension: string | null;
|
|
64
|
+
bridgeApi: number | null;
|
|
65
|
+
node: string;
|
|
66
|
+
platform: string;
|
|
67
|
+
};
|
|
68
|
+
bridge: {
|
|
69
|
+
reachable: boolean;
|
|
70
|
+
hostKind: string | null;
|
|
71
|
+
pid: number | null;
|
|
72
|
+
workspaceRoot: string | null;
|
|
73
|
+
startedAt: number | null;
|
|
74
|
+
commandCount: number | null;
|
|
75
|
+
};
|
|
76
|
+
ownership: {
|
|
77
|
+
leasePresent: boolean;
|
|
78
|
+
leasePid: number | null;
|
|
79
|
+
leaseEditorKind: string | null;
|
|
80
|
+
leaseWorkspaceRoot: string | null;
|
|
81
|
+
leaseHeartbeatAt: number | null;
|
|
82
|
+
};
|
|
83
|
+
descriptors: Array<{
|
|
84
|
+
path: string;
|
|
85
|
+
present: boolean;
|
|
86
|
+
pid: number | null;
|
|
87
|
+
port: number | null;
|
|
88
|
+
hostKind: string | null;
|
|
89
|
+
workspaceRoot: string | null;
|
|
90
|
+
/** Whether a credential field was present — never its value. */
|
|
91
|
+
carriesToken: boolean;
|
|
92
|
+
error?: string;
|
|
93
|
+
}>;
|
|
94
|
+
listeners: Array<{
|
|
95
|
+
port: number;
|
|
96
|
+
pid: number;
|
|
97
|
+
kind: string;
|
|
98
|
+
command: string;
|
|
99
|
+
}>;
|
|
100
|
+
instances: Array<{
|
|
101
|
+
name: string;
|
|
102
|
+
origin: string | null;
|
|
103
|
+
hasSettings: boolean;
|
|
104
|
+
}>;
|
|
105
|
+
auth: Array<{
|
|
106
|
+
instance: string;
|
|
107
|
+
state: string;
|
|
108
|
+
ok: boolean;
|
|
109
|
+
lastValidatedAt: number | null;
|
|
110
|
+
}>;
|
|
111
|
+
capabilities: {
|
|
112
|
+
tier: string | null;
|
|
113
|
+
proFeatures: boolean | null;
|
|
114
|
+
commandReview: boolean | null;
|
|
115
|
+
} | null;
|
|
116
|
+
findings: string[];
|
|
117
|
+
recentErrors: string[];
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Scrub free text — command lines and error messages.
|
|
121
|
+
*
|
|
122
|
+
* This is the one place an allowlist cannot be used: the content is arbitrary
|
|
123
|
+
* strings produced elsewhere, so there are no fields to enumerate. Treat it as
|
|
124
|
+
* defence in depth rather than a guarantee, and keep the surface small by
|
|
125
|
+
* truncating hard.
|
|
126
|
+
*
|
|
127
|
+
* Three shapes are removed: credential-ish key/value pairs with or without
|
|
128
|
+
* leading dashes, bearer tokens, and any long hex or base64-looking run, which
|
|
129
|
+
* is what this codebase's 32-character tokens actually look like. The first
|
|
130
|
+
* version of this only matched dashed flags, and the redaction test caught a
|
|
131
|
+
* token in `token=<value>` sailing straight through.
|
|
132
|
+
*/
|
|
133
|
+
export declare function scrubFreeText(value: unknown, max?: number): string;
|
|
134
|
+
/** Back-compat alias: command lines are just one kind of free text. */
|
|
135
|
+
export declare const safeCommand: typeof scrubFreeText;
|
|
136
|
+
/**
|
|
137
|
+
* Build the report. Every output field is constructed explicitly from a named
|
|
138
|
+
* input; nothing is spread, copied wholesale, or passed through.
|
|
139
|
+
*/
|
|
140
|
+
export declare function buildDoctorReport(sources: DoctorSources, now?: number | null): DoctorReport;
|
|
141
|
+
/**
|
|
142
|
+
* Turn the collected facts into the sentences a reader actually needs. A dump
|
|
143
|
+
* that leaves the diagnosis to the reader is not a diagnostic.
|
|
144
|
+
*/
|
|
145
|
+
export declare function deriveFindings(report: DoctorReport): string[];
|
|
146
|
+
/** Human-readable summary. The JSON form carries everything; this carries the point. */
|
|
147
|
+
export declare function formatDoctorReport(report: DoctorReport): string;
|
|
148
|
+
/** Gather everything a diagnosis needs, tolerating every part being absent. */
|
|
149
|
+
export declare function collectDoctorSources(options: {
|
|
150
|
+
cliVersion: string;
|
|
151
|
+
cwd: string;
|
|
152
|
+
httpPort?: number;
|
|
153
|
+
wsPort?: number;
|
|
154
|
+
}): Promise<DoctorSources>;
|
|
155
|
+
//# sourceMappingURL=doctor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../../src/cli/doctor.ts"],"names":[],"mappings":"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"}
|