@unbound-app/debugger-protocol 0.8.0 → 0.9.0
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/controller.d.mts
CHANGED
|
@@ -1,16 +1,17 @@
|
|
|
1
|
-
import { l as
|
|
1
|
+
import { l as EvalResult, o as DeviceInfo, p as PluginPushResult } from "./index-Ctl4b3sx.mjs";
|
|
2
2
|
import { AddonManifest } from "@unbound-app/types";
|
|
3
|
-
//#region src/controller.d.ts
|
|
4
3
|
type PluginPush = {
|
|
5
4
|
addonId: string;
|
|
6
5
|
bundle: string;
|
|
7
6
|
manifest: AddonManifest;
|
|
7
|
+
bytecode?: Record<string, string>;
|
|
8
8
|
};
|
|
9
|
-
declare class ControllerClient {
|
|
9
|
+
export declare class ControllerClient {
|
|
10
10
|
private ws;
|
|
11
11
|
private readonly pending;
|
|
12
12
|
private readonly pendingPushes;
|
|
13
13
|
private deviceConnected;
|
|
14
|
+
private deviceInfo;
|
|
14
15
|
private closed;
|
|
15
16
|
private readonly url;
|
|
16
17
|
constructor(url: string);
|
|
@@ -20,13 +21,16 @@ declare class ControllerClient {
|
|
|
20
21
|
close(): void;
|
|
21
22
|
/** @description Whether a device is currently connected to the bridge. */
|
|
22
23
|
get isDeviceConnected(): boolean;
|
|
24
|
+
/** @description What the connected device reported about itself in its hello, once it has said hello. */
|
|
25
|
+
get device(): DeviceInfo | undefined;
|
|
23
26
|
/**
|
|
24
27
|
* @description Sends code to the device via the bridge and resolves with its result. Fails fast
|
|
25
28
|
* if the socket isn't open or the bridge doesn't answer in time.
|
|
26
29
|
* @param code The JavaScript to evaluate on the device.
|
|
30
|
+
* @param timeout How long the bridge waits for the device, in milliseconds; the bridge's default when omitted.
|
|
27
31
|
* @returns The device's {@link EvalResult}.
|
|
28
32
|
*/
|
|
29
|
-
evaluate(code: string): Promise<EvalResult>;
|
|
33
|
+
evaluate(code: string, timeout?: number): Promise<EvalResult>;
|
|
30
34
|
/**
|
|
31
35
|
* @description Pushes a freshly built plugin to the device via the bridge and resolves with the
|
|
32
36
|
* device's reload outcome. Fails fast if the socket isn't open or the bridge doesn't answer in time.
|
|
@@ -37,5 +41,4 @@ declare class ControllerClient {
|
|
|
37
41
|
private onMessage;
|
|
38
42
|
private onClose;
|
|
39
43
|
}
|
|
40
|
-
|
|
41
|
-
export { ControllerClient };
|
|
44
|
+
export {};
|
package/dist/controller.mjs
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import { parseMessage, serializeMessage } from "./index.mjs";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
const EVAL_TIMEOUT_MS = 15e3;
|
|
1
|
+
import { MAX_EVAL_TIMEOUT_MS, parseMessage, serializeMessage } from "./index.mjs";
|
|
2
|
+
// src/controller.ts
|
|
3
|
+
const EVAL_RESULT_GRACE_MS = 5e3;
|
|
5
4
|
const PLUGIN_PUSH_TIMEOUT_MS = 75e3;
|
|
6
5
|
/** How long to wait between reconnection attempts when the bridge is unreachable. */
|
|
7
6
|
const RECONNECT_DELAY_MS = 1e3;
|
|
@@ -10,6 +9,7 @@ var ControllerClient = class {
|
|
|
10
9
|
pending = /* @__PURE__ */ new Map();
|
|
11
10
|
pendingPushes = /* @__PURE__ */ new Map();
|
|
12
11
|
deviceConnected = false;
|
|
12
|
+
deviceInfo;
|
|
13
13
|
closed = false;
|
|
14
14
|
url;
|
|
15
15
|
constructor(url) {
|
|
@@ -53,13 +53,18 @@ var ControllerClient = class {
|
|
|
53
53
|
get isDeviceConnected() {
|
|
54
54
|
return this.deviceConnected;
|
|
55
55
|
}
|
|
56
|
+
/** @description What the connected device reported about itself in its hello, once it has said hello. */
|
|
57
|
+
get device() {
|
|
58
|
+
return this.deviceInfo;
|
|
59
|
+
}
|
|
56
60
|
/**
|
|
57
61
|
* @description Sends code to the device via the bridge and resolves with its result. Fails fast
|
|
58
62
|
* if the socket isn't open or the bridge doesn't answer in time.
|
|
59
63
|
* @param code The JavaScript to evaluate on the device.
|
|
64
|
+
* @param timeout How long the bridge waits for the device, in milliseconds; the bridge's default when omitted.
|
|
60
65
|
* @returns The device's {@link EvalResult}.
|
|
61
66
|
*/
|
|
62
|
-
evaluate(code) {
|
|
67
|
+
evaluate(code, timeout) {
|
|
63
68
|
const id = crypto.randomUUID();
|
|
64
69
|
return new Promise((resolve) => {
|
|
65
70
|
if (this.ws?.readyState !== WebSocket.OPEN) {
|
|
@@ -72,25 +77,31 @@ var ControllerClient = class {
|
|
|
72
77
|
return;
|
|
73
78
|
}
|
|
74
79
|
const logs = [];
|
|
80
|
+
const limit = Math.min(timeout ?? 1e4, MAX_EVAL_TIMEOUT_MS);
|
|
75
81
|
const timer = setTimeout(() => {
|
|
76
82
|
this.pending.delete(id);
|
|
77
83
|
resolve({
|
|
78
84
|
type: "eval-result",
|
|
79
85
|
id,
|
|
80
86
|
ok: false,
|
|
81
|
-
error: `Evaluation timed out after ${
|
|
87
|
+
error: `Evaluation timed out after ${limit}ms.`,
|
|
82
88
|
logs
|
|
83
89
|
});
|
|
84
|
-
},
|
|
90
|
+
}, limit + EVAL_RESULT_GRACE_MS);
|
|
85
91
|
this.pending.set(id, {
|
|
86
92
|
resolve,
|
|
87
93
|
timer,
|
|
88
94
|
logs
|
|
89
95
|
});
|
|
90
|
-
this.ws.send(serializeMessage({
|
|
96
|
+
this.ws.send(serializeMessage(timeout === void 0 ? {
|
|
91
97
|
type: "eval",
|
|
92
98
|
id,
|
|
93
99
|
code
|
|
100
|
+
} : {
|
|
101
|
+
type: "eval",
|
|
102
|
+
id,
|
|
103
|
+
code,
|
|
104
|
+
timeout
|
|
94
105
|
}));
|
|
95
106
|
});
|
|
96
107
|
}
|
|
@@ -137,6 +148,7 @@ var ControllerClient = class {
|
|
|
137
148
|
if (!message) return;
|
|
138
149
|
if (message.type === "device-status") {
|
|
139
150
|
this.deviceConnected = message.connected;
|
|
151
|
+
this.deviceInfo = message.connected ? message.device : void 0;
|
|
140
152
|
return;
|
|
141
153
|
}
|
|
142
154
|
if (message.type === "log") {
|
|
@@ -168,5 +180,4 @@ var ControllerClient = class {
|
|
|
168
180
|
setTimeout(() => this.connect(), RECONNECT_DELAY_MS);
|
|
169
181
|
}
|
|
170
182
|
};
|
|
171
|
-
//#endregion
|
|
172
183
|
export { ControllerClient };
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { AddonManifest } from "@unbound-app/types";
|
|
2
|
-
//#region src/index.d.ts
|
|
3
2
|
/**
|
|
4
3
|
* The wire protocol shared by the bridge, the device and controllers: every frame is a JSON-encoded
|
|
5
4
|
* {@link BridgeMessage}. Dependency-free at runtime so the React Native client can bundle it.
|
|
@@ -10,6 +9,7 @@ type EvalRequest = {
|
|
|
10
9
|
/** Correlation id echoed back on the matching {@link EvalResult}. */
|
|
11
10
|
id: string;
|
|
12
11
|
code: string;
|
|
12
|
+
timeout?: number;
|
|
13
13
|
};
|
|
14
14
|
/** The outcome of an {@link EvalRequest}, sent device → bridge → the one waiting controller. */
|
|
15
15
|
type EvalResult = {
|
|
@@ -38,6 +38,8 @@ type PluginPushRequest = {
|
|
|
38
38
|
addonId: string;
|
|
39
39
|
bundle: string;
|
|
40
40
|
manifest: AddonManifest;
|
|
41
|
+
/** The bundle compiled to Hermes bytecode, base64-encoded, by bytecode version; its digests are in `manifest.bytecode`. */
|
|
42
|
+
bytecode?: Record<string, string>;
|
|
41
43
|
};
|
|
42
44
|
/** The outcome of a {@link PluginPushRequest}, sent device → bridge → the one waiting controller. */
|
|
43
45
|
type PluginPushResult = {
|
|
@@ -55,15 +57,31 @@ type LogMessage = {
|
|
|
55
57
|
level: 1 | 2 | 3;
|
|
56
58
|
message: string;
|
|
57
59
|
};
|
|
58
|
-
/**
|
|
60
|
+
/** What a device reports about itself when it connects. */
|
|
61
|
+
type DeviceInfo = {
|
|
62
|
+
platform: string;
|
|
63
|
+
discordVersion: string;
|
|
64
|
+
loaderVersion: string;
|
|
65
|
+
clientVersion: string;
|
|
66
|
+
};
|
|
67
|
+
/** Device → bridge, once its socket opens, so the bridge can say which device is attached. */
|
|
68
|
+
type DeviceHello = {
|
|
69
|
+
type: 'hello';
|
|
70
|
+
device: DeviceInfo;
|
|
71
|
+
};
|
|
72
|
+
/** Bridge → controller, once on attach and whenever the device connects, drops or says hello. */
|
|
59
73
|
type DeviceStatus = {
|
|
60
74
|
type: 'device-status';
|
|
61
75
|
connected: boolean;
|
|
76
|
+
/** Present once the connected device has said hello. */
|
|
77
|
+
device?: DeviceInfo;
|
|
62
78
|
};
|
|
63
79
|
/** Every framed message on the wire. */
|
|
64
|
-
type BridgeMessage = EvalRequest | EvalResult | PluginPushRequest | PluginPushResult | LogMessage | DeviceStatus;
|
|
80
|
+
type BridgeMessage = EvalRequest | EvalResult | PluginPushRequest | PluginPushResult | LogMessage | DeviceHello | DeviceStatus;
|
|
65
81
|
/** The default port the bridge listens on and controllers dial. */
|
|
66
82
|
declare const DEFAULT_BRIDGE_PORT = 9090;
|
|
83
|
+
declare const DEFAULT_EVAL_TIMEOUT_MS = 10000;
|
|
84
|
+
declare const MAX_EVAL_TIMEOUT_MS = 86400000;
|
|
67
85
|
/** The query marker a controller appends to its URL so the bridge routes it as a controller, not a device. */
|
|
68
86
|
declare const CONTROLLER_ENDPOINT_MARKER = "mcp";
|
|
69
87
|
/**
|
|
@@ -81,9 +99,10 @@ declare function parseMessage(raw: string): BridgeMessage | undefined;
|
|
|
81
99
|
declare function serializeMessage(message: BridgeMessage): string;
|
|
82
100
|
declare const _default: {
|
|
83
101
|
DEFAULT_BRIDGE_PORT: number;
|
|
102
|
+
DEFAULT_EVAL_TIMEOUT_MS: number;
|
|
103
|
+
MAX_EVAL_TIMEOUT_MS: number;
|
|
84
104
|
CONTROLLER_ENDPOINT_MARKER: string;
|
|
85
105
|
parseMessage: typeof parseMessage;
|
|
86
106
|
serializeMessage: typeof serializeMessage;
|
|
87
107
|
};
|
|
88
|
-
|
|
89
|
-
export { EvalRequest as a, PluginPushRequest as c, parseMessage as d, serializeMessage as f, DeviceStatus as i, PluginPushResult as l, CONTROLLER_ENDPOINT_MARKER as n, EvalResult as o, DEFAULT_BRIDGE_PORT as r, LogMessage as s, BridgeMessage as t, _default as u };
|
|
108
|
+
export { DeviceHello as a, EvalRequest as c, MAX_EVAL_TIMEOUT_MS as d, PluginPushRequest as f, serializeMessage as g, parseMessage as h, DEFAULT_EVAL_TIMEOUT_MS as i, EvalResult as l, _default as m, CONTROLLER_ENDPOINT_MARKER as n, DeviceInfo as o, PluginPushResult as p, DEFAULT_BRIDGE_PORT as r, DeviceStatus as s, BridgeMessage as t, LogMessage as u };
|
package/dist/index.d.mts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as
|
|
2
|
-
export { BridgeMessage, CONTROLLER_ENDPOINT_MARKER, DEFAULT_BRIDGE_PORT, DeviceStatus, EvalRequest, EvalResult, LogMessage, PluginPushRequest, PluginPushResult, _default as default, parseMessage, serializeMessage };
|
|
1
|
+
import { a as DeviceHello, c as EvalRequest, d as MAX_EVAL_TIMEOUT_MS, f as PluginPushRequest, g as serializeMessage, h as parseMessage, i as DEFAULT_EVAL_TIMEOUT_MS, l as EvalResult, m as _default, n as CONTROLLER_ENDPOINT_MARKER, o as DeviceInfo, p as PluginPushResult, r as DEFAULT_BRIDGE_PORT, s as DeviceStatus, t as BridgeMessage, u as LogMessage } from "./index-Ctl4b3sx.mjs";
|
|
2
|
+
export { BridgeMessage, CONTROLLER_ENDPOINT_MARKER, DEFAULT_BRIDGE_PORT, DEFAULT_EVAL_TIMEOUT_MS, DeviceHello, DeviceInfo, DeviceStatus, EvalRequest, EvalResult, LogMessage, MAX_EVAL_TIMEOUT_MS, PluginPushRequest, PluginPushResult, _default as default, parseMessage, serializeMessage };
|
package/dist/index.mjs
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
|
|
1
|
+
// src/index.ts
|
|
2
2
|
/** The default port the bridge listens on and controllers dial. */
|
|
3
3
|
const DEFAULT_BRIDGE_PORT = 9090;
|
|
4
|
+
const DEFAULT_EVAL_TIMEOUT_MS = 1e4;
|
|
5
|
+
const MAX_EVAL_TIMEOUT_MS = 864e5;
|
|
4
6
|
/** The query marker a controller appends to its URL so the bridge routes it as a controller, not a device. */
|
|
5
7
|
const CONTROLLER_ENDPOINT_MARKER = "mcp";
|
|
6
8
|
const MESSAGE_TYPES = /* @__PURE__ */ new Set([
|
|
@@ -9,6 +11,7 @@ const MESSAGE_TYPES = /* @__PURE__ */ new Set([
|
|
|
9
11
|
"plugin-push",
|
|
10
12
|
"plugin-push-result",
|
|
11
13
|
"log",
|
|
14
|
+
"hello",
|
|
12
15
|
"device-status"
|
|
13
16
|
]);
|
|
14
17
|
/**
|
|
@@ -26,8 +29,27 @@ function parseMessage(raw) {
|
|
|
26
29
|
}
|
|
27
30
|
return isBridgeMessage(value) ? value : void 0;
|
|
28
31
|
}
|
|
32
|
+
function hasRequiredFields(frame) {
|
|
33
|
+
if (typeof frame !== "object" || frame === null || !("type" in frame)) return false;
|
|
34
|
+
switch (frame.type) {
|
|
35
|
+
case "log": return "message" in frame && typeof frame.message === "string" && "level" in frame && (frame.level === 1 || frame.level === 2 || frame.level === 3);
|
|
36
|
+
case "eval": return "id" in frame && typeof frame.id === "string" && "code" in frame && typeof frame.code === "string";
|
|
37
|
+
case "eval-result":
|
|
38
|
+
case "plugin-push-result": return "id" in frame && typeof frame.id === "string" && "ok" in frame && typeof frame.ok === "boolean";
|
|
39
|
+
case "plugin-push": return "id" in frame && typeof frame.id === "string" && "addonId" in frame && typeof frame.addonId === "string" && "bundle" in frame && typeof frame.bundle === "string" && "manifest" in frame && typeof frame.manifest === "object" && frame.manifest !== null && (!("bytecode" in frame) || frame.bytecode === void 0 || isStringRecord(frame.bytecode));
|
|
40
|
+
case "hello": return "device" in frame && isDeviceInfo(frame.device);
|
|
41
|
+
case "device-status": return "connected" in frame && typeof frame.connected === "boolean" && (!("device" in frame) || frame.device === void 0 || isDeviceInfo(frame.device));
|
|
42
|
+
default: return false;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
function isDeviceInfo(value) {
|
|
46
|
+
return typeof value === "object" && value !== null && "platform" in value && typeof value.platform === "string" && "discordVersion" in value && typeof value.discordVersion === "string" && "loaderVersion" in value && typeof value.loaderVersion === "string" && "clientVersion" in value && typeof value.clientVersion === "string";
|
|
47
|
+
}
|
|
48
|
+
function isStringRecord(value) {
|
|
49
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) && Object.values(value).every((entry) => typeof entry === "string");
|
|
50
|
+
}
|
|
29
51
|
function isBridgeMessage(value) {
|
|
30
|
-
return typeof value === "object" && value !== null && "type" in value && typeof value.type === "string" && MESSAGE_TYPES.has(value.type);
|
|
52
|
+
return typeof value === "object" && value !== null && "type" in value && typeof value.type === "string" && MESSAGE_TYPES.has(value.type) && hasRequiredFields(value);
|
|
31
53
|
}
|
|
32
54
|
/**
|
|
33
55
|
* @description Serialises a {@link BridgeMessage} into its wire form. Inverse of {@link parseMessage}.
|
|
@@ -39,9 +61,10 @@ function serializeMessage(message) {
|
|
|
39
61
|
}
|
|
40
62
|
var src_default = {
|
|
41
63
|
DEFAULT_BRIDGE_PORT,
|
|
64
|
+
DEFAULT_EVAL_TIMEOUT_MS,
|
|
65
|
+
MAX_EVAL_TIMEOUT_MS,
|
|
42
66
|
CONTROLLER_ENDPOINT_MARKER: "mcp",
|
|
43
67
|
parseMessage,
|
|
44
68
|
serializeMessage
|
|
45
69
|
};
|
|
46
|
-
|
|
47
|
-
export { CONTROLLER_ENDPOINT_MARKER, DEFAULT_BRIDGE_PORT, src_default as default, parseMessage, serializeMessage };
|
|
70
|
+
export { CONTROLLER_ENDPOINT_MARKER, DEFAULT_BRIDGE_PORT, DEFAULT_EVAL_TIMEOUT_MS, MAX_EVAL_TIMEOUT_MS, src_default as default, parseMessage, serializeMessage };
|
package/dist/registry.d.mts
CHANGED
|
@@ -1,16 +1,15 @@
|
|
|
1
1
|
import { ControllerClient } from "./controller.mjs";
|
|
2
2
|
import { z } from "zod";
|
|
3
|
-
//#region src/registry.d.ts
|
|
4
3
|
/**
|
|
5
4
|
* One command definition feeds both surfaces: the CLI builds flags and positionals from its schema,
|
|
6
5
|
* and the MCP server uses the schema as the tool's input schema.
|
|
7
6
|
*/
|
|
8
7
|
/** The shared runtime a command handler runs against: the bridge controller it evaluates through. */
|
|
9
|
-
type CommandContext = {
|
|
8
|
+
export type CommandContext = {
|
|
10
9
|
client: ControllerClient;
|
|
11
10
|
};
|
|
12
11
|
/** What a command handler returns: a success flag, human-readable text, and optional structured data. */
|
|
13
|
-
type CommandOutput = {
|
|
12
|
+
export type CommandOutput = {
|
|
14
13
|
ok: boolean;
|
|
15
14
|
text: string;
|
|
16
15
|
data?: unknown;
|
|
@@ -19,7 +18,7 @@ type CommandOutput = {
|
|
|
19
18
|
* Every command is on the CLI: at the root (`ubd eval`), or under `ubd mcp` (`ubd mcp open`) for
|
|
20
19
|
* agent-facing tools, which must then be MCP tools too.
|
|
21
20
|
*/
|
|
22
|
-
type CommandSurfaces = {
|
|
21
|
+
export type CommandSurfaces = {
|
|
23
22
|
cli: 'root';
|
|
24
23
|
mcp: boolean;
|
|
25
24
|
} | {
|
|
@@ -27,7 +26,7 @@ type CommandSurfaces = {
|
|
|
27
26
|
mcp: true;
|
|
28
27
|
};
|
|
29
28
|
/** A command usable from both the CLI and (when opted in) the MCP server. */
|
|
30
|
-
type CommandDefinition<S extends z.ZodObject<z.ZodRawShape>> = {
|
|
29
|
+
export type CommandDefinition<S extends z.ZodObject<z.ZodRawShape>> = {
|
|
31
30
|
name: string;
|
|
32
31
|
description: string;
|
|
33
32
|
surfaces: CommandSurfaces;
|
|
@@ -38,6 +37,4 @@ type CommandDefinition<S extends z.ZodObject<z.ZodRawShape>> = {
|
|
|
38
37
|
positionals?: (keyof z.infer<S>)[];
|
|
39
38
|
};
|
|
40
39
|
handler: (input: z.infer<S>, context: CommandContext) => Promise<CommandOutput>;
|
|
41
|
-
};
|
|
42
|
-
//#endregion
|
|
43
|
-
export { CommandContext, CommandDefinition, CommandOutput, CommandSurfaces };
|
|
40
|
+
};
|
package/package.json
CHANGED