@vymalo/opencode-browser 0.12.0 → 0.14.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/agent-client.d.ts +36 -36
- package/dist/agent-client.js +201 -189
- package/dist/agent-client.js.map +1 -1
- package/dist/broker.d.ts +85 -87
- package/dist/broker.js +614 -599
- package/dist/broker.js.map +1 -1
- package/dist/catalog.d.ts +76 -76
- package/dist/catalog.js +826 -596
- package/dist/catalog.js.map +1 -1
- package/dist/endpoint.d.ts +42 -42
- package/dist/endpoint.js +132 -131
- package/dist/endpoint.js.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/lib.js +1 -0
- package/dist/lib.js.map +1 -1
- package/dist/logging.d.ts +6 -6
- package/dist/logging.js +60 -63
- package/dist/logging.js.map +1 -1
- package/dist/node-transport.d.ts +6 -6
- package/dist/node-transport.js +48 -44
- package/dist/node-transport.js.map +1 -1
- package/dist/opencode.d.ts +10 -10
- package/dist/opencode.js +143 -146
- package/dist/opencode.js.map +1 -1
- package/dist/protocol.d.ts +127 -127
- package/dist/protocol.js +148 -128
- package/dist/protocol.js.map +1 -1
- package/dist/schema.d.ts +32 -32
- package/dist/schema.js +39 -44
- package/dist/schema.js.map +1 -1
- package/dist/token-file.d.ts +4 -4
- package/dist/token-file.js +68 -60
- package/dist/token-file.js.map +1 -1
- package/dist/tools.d.ts +11 -11
- package/dist/tools.js +128 -123
- package/dist/tools.js.map +1 -1
- package/dist/transport.d.ts +17 -17
- package/dist/transport.js +3 -9
- package/dist/transport.js.map +1 -1
- package/dist/types.d.ts +61 -61
- package/dist/types.js +1 -0
- package/dist/types.js.map +1 -1
- package/package.json +3 -3
package/dist/logging.js
CHANGED
|
@@ -1,73 +1,70 @@
|
|
|
1
1
|
export const LOG_LEVEL_PRIORITY = {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
2
|
+
trace: 5,
|
|
3
|
+
debug: 10,
|
|
4
|
+
info: 20,
|
|
5
|
+
warn: 30,
|
|
6
|
+
error: 40
|
|
7
7
|
};
|
|
8
8
|
export const DEFAULT_LOG_LEVEL = "info";
|
|
9
9
|
function redactFields(fields) {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
10
|
+
if (!fields) {
|
|
11
|
+
return undefined;
|
|
12
|
+
}
|
|
13
|
+
const redacted = {};
|
|
14
|
+
for (const [key, value] of Object.entries(fields)) {
|
|
15
|
+
if (/token|secret|password|authorization/i.test(key)) {
|
|
16
|
+
redacted[key] = "[redacted]";
|
|
17
|
+
continue;
|
|
18
|
+
}
|
|
19
|
+
redacted[key] = value;
|
|
20
|
+
}
|
|
21
|
+
return redacted;
|
|
22
22
|
}
|
|
23
23
|
export function createJsonConsoleLogger(minLevel = DEFAULT_LOG_LEVEL) {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
24
|
+
const minPriority = LOG_LEVEL_PRIORITY[minLevel];
|
|
25
|
+
const write = (level, event, fields) => {
|
|
26
|
+
if (LOG_LEVEL_PRIORITY[level] < minPriority) {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
const payload = {
|
|
30
|
+
ts: new Date().toISOString(),
|
|
31
|
+
level,
|
|
32
|
+
event,
|
|
33
|
+
...redactFields(fields) ?? {}
|
|
34
|
+
};
|
|
35
|
+
const line = JSON.stringify(payload);
|
|
36
|
+
if (level === "error") {
|
|
37
|
+
console.error(line);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
if (level === "warn") {
|
|
41
|
+
console.warn(line);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
console.log(line);
|
|
45
|
+
};
|
|
46
|
+
return {
|
|
47
|
+
trace: (event, fields) => write("trace", event, fields),
|
|
48
|
+
debug: (event, fields) => write("debug", event, fields),
|
|
49
|
+
info: (event, fields) => write("info", event, fields),
|
|
50
|
+
warn: (event, fields) => write("warn", event, fields),
|
|
51
|
+
error: (event, fields) => write("error", event, fields)
|
|
52
|
+
};
|
|
53
53
|
}
|
|
54
54
|
export function fromOpenCodeLogLevel(value) {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
return "error";
|
|
69
|
-
default:
|
|
70
|
-
return undefined;
|
|
71
|
-
}
|
|
55
|
+
if (typeof value !== "string") {
|
|
56
|
+
return undefined;
|
|
57
|
+
}
|
|
58
|
+
switch (value.toUpperCase()) {
|
|
59
|
+
case "DEBUG":
|
|
60
|
+
// Host DEBUG unlocks our most-verbose internal tier (trace) — the browser
|
|
61
|
+
// bridge is the package users debug most, so DEBUG opts into full tracing.
|
|
62
|
+
return "trace";
|
|
63
|
+
case "INFO": return "info";
|
|
64
|
+
case "WARN": return "warn";
|
|
65
|
+
case "ERROR": return "error";
|
|
66
|
+
default: return undefined;
|
|
67
|
+
}
|
|
72
68
|
}
|
|
69
|
+
|
|
73
70
|
//# sourceMappingURL=logging.js.map
|
package/dist/logging.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"
|
|
1
|
+
{"mappings":"AAIA,OAAO,MAAM,qBAA+C;CAC1D,OAAO;CACP,OAAO;CACP,MAAM;CACN,MAAM;CACN,OAAO;AACT;AAEA,OAAO,MAAM,oBAA8B;AAc3C,SAAS,aAAa,QAA2C;CAC/D,IAAI,CAAC,QAAQ;EACX,OAAO;CACT;CACA,MAAM,WAAsB,CAAC;CAC7B,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,MAAM,GAAG;EACjD,IAAI,uCAAuC,KAAK,GAAG,GAAG;GACpD,SAAS,OAAO;GAChB;EACF;EACA,SAAS,OAAO;CAClB;CACA,OAAO;AACT;AAEA,OAAO,SAAS,wBAAwB,WAAqB,mBAA2B;CACtF,MAAM,cAAc,mBAAmB;CAEvC,MAAM,SAAS,OAAiB,OAAe,WAA6B;EAC1E,IAAI,mBAAmB,SAAS,aAAa;GAC3C;EACF;EACA,MAAM,UAAU;GACd,IAAI,IAAI,KAAK,CAAC,CAAC,YAAY;GAC3B;GACA;GACA,GAAI,aAAa,MAAM,KAAK,CAAC;EAC/B;EACA,MAAM,OAAO,KAAK,UAAU,OAAO;EACnC,IAAI,UAAU,SAAS;GACrB,QAAQ,MAAM,IAAI;GAClB;EACF;EACA,IAAI,UAAU,QAAQ;GACpB,QAAQ,KAAK,IAAI;GACjB;EACF;EACA,QAAQ,IAAI,IAAI;CAClB;CAEA,OAAO;EACL,QAAQ,OAAO,WAAW,MAAM,SAAS,OAAO,MAAM;EACtD,QAAQ,OAAO,WAAW,MAAM,SAAS,OAAO,MAAM;EACtD,OAAO,OAAO,WAAW,MAAM,QAAQ,OAAO,MAAM;EACpD,OAAO,OAAO,WAAW,MAAM,QAAQ,OAAO,MAAM;EACpD,QAAQ,OAAO,WAAW,MAAM,SAAS,OAAO,MAAM;CACxD;AACF;AAEA,OAAO,SAAS,qBAAqB,OAAsC;CACzE,IAAI,OAAO,UAAU,UAAU;EAC7B,OAAO;CACT;CACA,QAAQ,MAAM,YAAY,GAA1B;EACE,KAAK;;;EAGH,OAAO;EACT,KAAK,QACH,OAAO;EACT,KAAK,QACH,OAAO;EACT,KAAK,SACH,OAAO;EACT,SACE,OAAO;CACX;AACF","names":[],"sources":["../src/logging.ts"],"version":3,"file":"logging.js","sourceRoot":""}
|
package/dist/node-transport.d.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import type { AgentSocket, AgentSocketHandlers } from "./agent-client.js";
|
|
2
2
|
import type { BridgeTransport } from "./transport.js";
|
|
3
3
|
/**
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
4
|
+
* `BridgeTransport` backed by the Node `ws` package. This is the bridge's single
|
|
5
|
+
* host transport: `ws` runs under both Node *and* Bun, so it covers every
|
|
6
|
+
* runtime OpenCode loads the plugin in — the Bun CLI/web *and* the Node desktop
|
|
7
|
+
* app — without a Bun-specific code path. `listen` resolves once bound and
|
|
8
|
+
* rejects on `EADDRINUSE`, which drives the endpoint's host-vs-guest election.
|
|
9
|
+
*/
|
|
10
10
|
export declare function createNodeTransport(): BridgeTransport;
|
|
11
11
|
/** Agent-socket factory backed by the Node `ws` client (guest mode). */
|
|
12
12
|
export declare function createNodeAgentSocket(url: string, handlers: AgentSocketHandlers): AgentSocket;
|
package/dist/node-transport.js
CHANGED
|
@@ -1,52 +1,56 @@
|
|
|
1
1
|
import { WebSocket, WebSocketServer } from "ws";
|
|
2
2
|
/**
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
3
|
+
* `BridgeTransport` backed by the Node `ws` package. This is the bridge's single
|
|
4
|
+
* host transport: `ws` runs under both Node *and* Bun, so it covers every
|
|
5
|
+
* runtime OpenCode loads the plugin in — the Bun CLI/web *and* the Node desktop
|
|
6
|
+
* app — without a Bun-specific code path. `listen` resolves once bound and
|
|
7
|
+
* rejects on `EADDRINUSE`, which drives the endpoint's host-vs-guest election.
|
|
8
|
+
*/
|
|
9
9
|
export function createNodeTransport() {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
10
|
+
let server = null;
|
|
11
|
+
return {
|
|
12
|
+
listen({ host, port }, handlers) {
|
|
13
|
+
return new Promise((resolve, reject) => {
|
|
14
|
+
const wss = new WebSocketServer({
|
|
15
|
+
host,
|
|
16
|
+
port
|
|
17
|
+
});
|
|
18
|
+
server = wss;
|
|
19
|
+
wss.once("listening", () => resolve());
|
|
20
|
+
wss.once("error", (err) => reject(err));
|
|
21
|
+
wss.on("connection", (ws) => {
|
|
22
|
+
const conn = {
|
|
23
|
+
send: (data) => ws.send(data),
|
|
24
|
+
close: () => ws.close()
|
|
25
|
+
};
|
|
26
|
+
handlers.onOpen(conn);
|
|
27
|
+
ws.on("message", (data) => handlers.onMessage(conn, data.toString()));
|
|
28
|
+
ws.on("close", () => handlers.onClose(conn));
|
|
29
|
+
ws.on("error", () => {
|
|
30
|
+
/* close follows */
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
},
|
|
35
|
+
stop() {
|
|
36
|
+
server?.close();
|
|
37
|
+
server = null;
|
|
38
|
+
}
|
|
39
|
+
};
|
|
37
40
|
}
|
|
38
41
|
/** Agent-socket factory backed by the Node `ws` client (guest mode). */
|
|
39
42
|
export function createNodeAgentSocket(url, handlers) {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
43
|
+
const ws = new WebSocket(url);
|
|
44
|
+
ws.on("open", () => handlers.onOpen());
|
|
45
|
+
ws.on("message", (data) => handlers.onMessage(data.toString()));
|
|
46
|
+
ws.on("close", () => handlers.onClose());
|
|
47
|
+
ws.on("error", () => {
|
|
48
|
+
/* close follows */
|
|
49
|
+
});
|
|
50
|
+
return {
|
|
51
|
+
send: (data) => ws.send(data),
|
|
52
|
+
close: () => ws.close()
|
|
53
|
+
};
|
|
51
54
|
}
|
|
55
|
+
|
|
52
56
|
//# sourceMappingURL=node-transport.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"
|
|
1
|
+
{"mappings":"AAAA,SAAS,WAAW,uBAAuB;;;;;;;;AAY3C,OAAO,SAAS,sBAAuC;CACrD,IAAI,SAAiC;CAErC,OAAO;EACL,OAAO,EAAE,MAAM,QAAQ,UAAU;GAC/B,OAAO,IAAI,SAAe,SAAS,WAAW;IAC5C,MAAM,MAAM,IAAI,gBAAgB;KAAE;KAAM;IAAK,CAAC;IAC9C,SAAS;IACT,IAAI,KAAK,mBAAmB,QAAQ,CAAC;IACrC,IAAI,KAAK,UAAU,QAAQ,OAAO,GAAG,CAAC;IACtC,IAAI,GAAG,eAAe,OAAkB;KACtC,MAAM,OAAyB;MAC7B,OAAO,SAAS,GAAG,KAAK,IAAI;MAC5B,aAAa,GAAG,MAAM;KACxB;KACA,SAAS,OAAO,IAAI;KACpB,GAAG,GAAG,YAAY,SAAS,SAAS,UAAU,MAAM,KAAK,SAAS,CAAC,CAAC;KACpE,GAAG,GAAG,eAAe,SAAS,QAAQ,IAAI,CAAC;KAC3C,GAAG,GAAG,eAAe;;KAErB,CAAC;IACH,CAAC;GACH,CAAC;EACH;EACA,OAAO;GACL,QAAQ,MAAM;GACd,SAAS;EACX;CACF;AACF;;AAGA,OAAO,SAAS,sBAAsB,KAAa,UAA4C;CAC7F,MAAM,KAAK,IAAI,UAAU,GAAG;CAC5B,GAAG,GAAG,cAAc,SAAS,OAAO,CAAC;CACrC,GAAG,GAAG,YAAY,SAAS,SAAS,UAAU,KAAK,SAAS,CAAC,CAAC;CAC9D,GAAG,GAAG,eAAe,SAAS,QAAQ,CAAC;CACvC,GAAG,GAAG,eAAe;;CAErB,CAAC;CACD,OAAO;EACL,OAAO,SAAS,GAAG,KAAK,IAAI;EAC5B,aAAa,GAAG,MAAM;CACxB;AACF","names":[],"sources":["../src/node-transport.ts"],"version":3,"file":"node-transport.js","sourceRoot":""}
|
package/dist/opencode.d.ts
CHANGED
|
@@ -4,16 +4,16 @@ import { type Logger } from "./logging.js";
|
|
|
4
4
|
import { type SaveScreenshot } from "./tools.js";
|
|
5
5
|
import type { BridgeTransport } from "./transport.js";
|
|
6
6
|
export interface BrowserPluginFactoryOptions {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
7
|
+
/** Inject a logger (defaults to the OpenCode-piped logger). */
|
|
8
|
+
logger?: Logger;
|
|
9
|
+
/** Inject the server transport factory for host mode (defaults to the Node `ws` server). */
|
|
10
|
+
createServerTransport?: () => BridgeTransport;
|
|
11
|
+
/** Inject the agent-socket factory for guest mode (defaults to the Node `ws` client). */
|
|
12
|
+
createAgentSocket?: AgentSocketFactory;
|
|
13
|
+
/** Inject the screenshot disk-writer (tests). */
|
|
14
|
+
saveScreenshot?: SaveScreenshot;
|
|
15
|
+
/** Inject token generation (tests). */
|
|
16
|
+
generateToken?: () => string;
|
|
17
17
|
}
|
|
18
18
|
export declare function createBrowserPlugin(factoryOptions?: BrowserPluginFactoryOptions): Plugin;
|
|
19
19
|
export declare const OpencodeBrowserPlugin: Plugin;
|
package/dist/opencode.js
CHANGED
|
@@ -6,163 +6,160 @@ import { readBridgeFile, resolveSharedToken, writeBridgeFile } from "./token-fil
|
|
|
6
6
|
import { createNodeAgentSocket, createNodeTransport } from "./node-transport.js";
|
|
7
7
|
import { createBrowserTools } from "./tools.js";
|
|
8
8
|
function resolveGroups(raw) {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
9
|
+
if (!Array.isArray(raw)) {
|
|
10
|
+
return [...DEFAULT_GROUPS];
|
|
11
|
+
}
|
|
12
|
+
const valid = raw.filter((g) => TOOL_GROUPS.includes(g));
|
|
13
|
+
return valid.length > 0 ? valid : [...DEFAULT_GROUPS];
|
|
14
14
|
}
|
|
15
15
|
const PLUGIN_SERVICE_NAME = "opencode-browser-plugin";
|
|
16
16
|
const DEFAULTS = {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
17
|
+
host: "127.0.0.1",
|
|
18
|
+
port: 4517,
|
|
19
|
+
executor: "auto",
|
|
20
|
+
timeoutMs: 3e4,
|
|
21
|
+
screenshotDir: ".opencode/browser"
|
|
22
22
|
};
|
|
23
23
|
/**
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
24
|
+
* Pipe plugin logs through OpenCode's `client.app.log` so they show up in the
|
|
25
|
+
* host's structured log stream, with the JSON console as a reliable fallback.
|
|
26
|
+
* Mirrors the pattern used by `@vymalo/opencode-oauth2` / `-ratelimit`.
|
|
27
|
+
*/
|
|
28
28
|
function createOpenCodeLogger(client, getMinLevel) {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
29
|
+
const fallback = createJsonConsoleLogger("debug");
|
|
30
|
+
// OpenCode already captures plugin logs via client.app.log (and filters them
|
|
31
|
+
// by its own log level). Mirroring every event to stdout on top of that is
|
|
32
|
+
// what floods the terminal (e.g. `opencode web` re-loads the plugin per
|
|
33
|
+
// session). So only mirror warn/error to the JSON console by default; set
|
|
34
|
+
// VYMALO_PLUGIN_CONSOLE_LOG=1 to restore full console output for debugging.
|
|
35
|
+
const consoleAll = /^(1|true|yes|on)$/i.test(process.env.VYMALO_PLUGIN_CONSOLE_LOG ?? "");
|
|
36
|
+
const write = (level, event, fields) => {
|
|
37
|
+
if (LOG_LEVEL_PRIORITY[level] < LOG_LEVEL_PRIORITY[getMinLevel()]) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
if (consoleAll || level === "warn" || level === "error") {
|
|
41
|
+
fallback[level](event, fields);
|
|
42
|
+
}
|
|
43
|
+
// OpenCode's host log API has no `trace` tier — fold it into `debug` for the
|
|
44
|
+
// host call (our own level filter above already gates trace by DEBUG).
|
|
45
|
+
const hostLevel = level === "trace" ? "debug" : level;
|
|
46
|
+
void client.app.log({ body: {
|
|
47
|
+
service: PLUGIN_SERVICE_NAME,
|
|
48
|
+
level: hostLevel,
|
|
49
|
+
message: event,
|
|
50
|
+
extra: fields
|
|
51
|
+
} }).catch(() => {
|
|
52
|
+
/* best-effort */
|
|
53
|
+
});
|
|
54
|
+
};
|
|
55
|
+
return {
|
|
56
|
+
trace: (event, fields) => write("trace", event, fields),
|
|
57
|
+
debug: (event, fields) => write("debug", event, fields),
|
|
58
|
+
info: (event, fields) => write("info", event, fields),
|
|
59
|
+
warn: (event, fields) => write("warn", event, fields),
|
|
60
|
+
error: (event, fields) => write("error", event, fields)
|
|
61
|
+
};
|
|
61
62
|
}
|
|
62
63
|
function resolveOptions(raw, generateToken) {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
64
|
+
const opts = raw ?? {};
|
|
65
|
+
const token = typeof opts.token === "string" && opts.token.length > 0 ? opts.token : generateToken();
|
|
66
|
+
return {
|
|
67
|
+
enabled: opts.enabled !== false,
|
|
68
|
+
host: opts.host ?? DEFAULTS.host,
|
|
69
|
+
port: opts.port ?? DEFAULTS.port,
|
|
70
|
+
token,
|
|
71
|
+
executor: opts.executor ?? DEFAULTS.executor,
|
|
72
|
+
groups: resolveGroups(opts.groups),
|
|
73
|
+
timeoutMs: opts.timeoutMs ?? DEFAULTS.timeoutMs,
|
|
74
|
+
screenshotDir: opts.screenshotDir ?? DEFAULTS.screenshotDir
|
|
75
|
+
};
|
|
75
76
|
}
|
|
76
77
|
export function createBrowserPlugin(factoryOptions = {}) {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
config: async (config) => {
|
|
161
|
-
currentLogLevel = fromOpenCodeLogLevel(config.logLevel) ?? DEFAULT_LOG_LEVEL;
|
|
162
|
-
}
|
|
163
|
-
};
|
|
164
|
-
};
|
|
78
|
+
return async ({ client }, pluginOptions) => {
|
|
79
|
+
let currentLogLevel = DEFAULT_LOG_LEVEL;
|
|
80
|
+
const logger = factoryOptions.logger ?? createOpenCodeLogger(client, () => currentLogLevel);
|
|
81
|
+
const generateToken = factoryOptions.generateToken ?? (() => randomBytes(24).toString("hex"));
|
|
82
|
+
const options = resolveOptions(pluginOptions, generateToken);
|
|
83
|
+
const rawOptions = pluginOptions;
|
|
84
|
+
// Empty string is NOT an explicit token (resolveOptions generates one for
|
|
85
|
+
// it). Treating "" as provided would mark the shared token "explicit" and
|
|
86
|
+
// skip the paste_into_extension log, leaving the extension unable to learn
|
|
87
|
+
// the generated token.
|
|
88
|
+
const tokenProvided = typeof rawOptions?.token === "string" && rawOptions.token.length > 0;
|
|
89
|
+
// Only forward the executor preference when the operator set it explicitly;
|
|
90
|
+
// otherwise the extension keeps its own dashboard choice.
|
|
91
|
+
const executorProvided = typeof rawOptions?.executor === "string";
|
|
92
|
+
if (!options.enabled) {
|
|
93
|
+
logger.info("browser_plugin_disabled", {});
|
|
94
|
+
return { config: async (config) => {
|
|
95
|
+
currentLogLevel = fromOpenCodeLogLevel(config.logLevel) ?? DEFAULT_LOG_LEVEL;
|
|
96
|
+
} };
|
|
97
|
+
}
|
|
98
|
+
// Share the token across adapters via the per-user state file (explicit wins).
|
|
99
|
+
const { token, source } = resolveSharedToken(options.port, tokenProvided ? options.token : undefined, generateToken);
|
|
100
|
+
// Persist + advertise the token, **host-only** (fired via onHost so a host
|
|
101
|
+
// reached by failover re-election does it too). Writing only on the host —
|
|
102
|
+
// not unconditionally at load like before — means a guest that resolved a
|
|
103
|
+
// fresh token on a transient read miss can never clobber the live host's
|
|
104
|
+
// token in the shared file. We write whenever the file doesn't already match
|
|
105
|
+
// OUR (port, token): that keeps a port change in sync and lets an explicit
|
|
106
|
+
// token overwrite a stale generated one — but a no-op when the file already
|
|
107
|
+
// agrees, so a concurrent host doesn't thrash it. The `paste_into_extension`
|
|
108
|
+
// field name dodges the logger's redaction filter so the value stays copyable.
|
|
109
|
+
const advertiseToken = () => {
|
|
110
|
+
const existing = readBridgeFile();
|
|
111
|
+
if (!existing || existing.port !== options.port || existing.token !== token) {
|
|
112
|
+
writeBridgeFile(options.port, token);
|
|
113
|
+
}
|
|
114
|
+
if (source !== "explicit") {
|
|
115
|
+
logger.info("browser_bridge_token", {
|
|
116
|
+
paste_into_extension: token,
|
|
117
|
+
source,
|
|
118
|
+
mode: "host"
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
// Auto-elect: host the broker (win the bind) or join an existing one as a guest.
|
|
123
|
+
const endpoint = await createEndpoint({
|
|
124
|
+
host: options.host,
|
|
125
|
+
port: options.port,
|
|
126
|
+
token,
|
|
127
|
+
executor: executorProvided ? options.executor : undefined,
|
|
128
|
+
timeoutMs: options.timeoutMs,
|
|
129
|
+
label: "opencode-plugin",
|
|
130
|
+
onHost: advertiseToken,
|
|
131
|
+
// Lets a long-lived host pick up a rotated token from the shared file on
|
|
132
|
+
// a failed handshake, instead of rejecting forever until it's restarted.
|
|
133
|
+
// Disabled for an EXPLICIT token: the operator pinned it, so the file
|
|
134
|
+
// must never override their configured secret.
|
|
135
|
+
reloadToken: source === "explicit" ? undefined : () => readBridgeFile()?.token
|
|
136
|
+
}, {
|
|
137
|
+
logger,
|
|
138
|
+
createServerTransport: factoryOptions.createServerTransport ?? createNodeTransport,
|
|
139
|
+
createAgentSocket: factoryOptions.createAgentSocket ?? createNodeAgentSocket
|
|
140
|
+
});
|
|
141
|
+
// When OpenCode exits, hand the browser back automatically — the user
|
|
142
|
+
// shouldn't have to click Disconnect.
|
|
143
|
+
process.once("exit", () => {
|
|
144
|
+
try {
|
|
145
|
+
endpoint.shutdown();
|
|
146
|
+
} catch {}
|
|
147
|
+
});
|
|
148
|
+
const tools = createBrowserTools({
|
|
149
|
+
send: endpoint.send,
|
|
150
|
+
options,
|
|
151
|
+
logger,
|
|
152
|
+
saveScreenshot: factoryOptions.saveScreenshot
|
|
153
|
+
});
|
|
154
|
+
return {
|
|
155
|
+
tool: tools,
|
|
156
|
+
config: async (config) => {
|
|
157
|
+
currentLogLevel = fromOpenCodeLogLevel(config.logLevel) ?? DEFAULT_LOG_LEVEL;
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
};
|
|
165
161
|
}
|
|
166
162
|
export const OpencodeBrowserPlugin = createBrowserPlugin();
|
|
167
163
|
export default OpencodeBrowserPlugin;
|
|
164
|
+
|
|
168
165
|
//# sourceMappingURL=opencode.js.map
|