@aexol/spectral 0.8.7 → 0.8.10

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.
Files changed (78) hide show
  1. package/dist/auth-helper.d.ts +49 -0
  2. package/dist/auth-helper.d.ts.map +1 -0
  3. package/dist/auth-helper.js +191 -0
  4. package/dist/commands/login-oauth.d.ts +2 -9
  5. package/dist/commands/login-oauth.d.ts.map +1 -1
  6. package/dist/commands/login-oauth.js +10 -105
  7. package/dist/commands/serve.d.ts.map +1 -1
  8. package/dist/commands/serve.js +68 -1
  9. package/dist/designer/systems.d.ts.map +1 -1
  10. package/dist/designer/systems.js +5 -0
  11. package/dist/extensions/aexol-mcp.d.ts.map +1 -1
  12. package/dist/extensions/aexol-mcp.js +33 -2
  13. package/dist/extensions/browser/browser-service.d.ts +104 -0
  14. package/dist/extensions/browser/browser-service.d.ts.map +1 -0
  15. package/dist/extensions/browser/browser-service.js +333 -0
  16. package/dist/extensions/browser/config.d.ts +24 -0
  17. package/dist/extensions/browser/config.d.ts.map +1 -0
  18. package/dist/extensions/browser/config.js +17 -0
  19. package/dist/extensions/browser/index.d.ts +35 -0
  20. package/dist/extensions/browser/index.d.ts.map +1 -0
  21. package/dist/extensions/browser/index.js +126 -0
  22. package/dist/extensions/browser/stream/signaling-client.d.ts +28 -0
  23. package/dist/extensions/browser/stream/signaling-client.d.ts.map +1 -0
  24. package/dist/extensions/browser/stream/signaling-client.js +69 -0
  25. package/dist/extensions/browser/stream/stream-injector.d.ts +30 -0
  26. package/dist/extensions/browser/stream/stream-injector.d.ts.map +1 -0
  27. package/dist/extensions/browser/stream/stream-injector.js +161 -0
  28. package/dist/extensions/browser/stream/stream-tool.d.ts +10 -0
  29. package/dist/extensions/browser/stream/stream-tool.d.ts.map +1 -0
  30. package/dist/extensions/browser/stream/stream-tool.js +182 -0
  31. package/dist/extensions/browser/tools/console.d.ts +7 -0
  32. package/dist/extensions/browser/tools/console.d.ts.map +1 -0
  33. package/dist/extensions/browser/tools/console.js +54 -0
  34. package/dist/extensions/browser/tools/core.d.ts +7 -0
  35. package/dist/extensions/browser/tools/core.d.ts.map +1 -0
  36. package/dist/extensions/browser/tools/core.js +115 -0
  37. package/dist/extensions/browser/tools/dialog.d.ts +11 -0
  38. package/dist/extensions/browser/tools/dialog.d.ts.map +1 -0
  39. package/dist/extensions/browser/tools/dialog.js +54 -0
  40. package/dist/extensions/browser/tools/drop.d.ts +7 -0
  41. package/dist/extensions/browser/tools/drop.d.ts.map +1 -0
  42. package/dist/extensions/browser/tools/drop.js +93 -0
  43. package/dist/extensions/browser/tools/evaluate.d.ts +7 -0
  44. package/dist/extensions/browser/tools/evaluate.d.ts.map +1 -0
  45. package/dist/extensions/browser/tools/evaluate.js +105 -0
  46. package/dist/extensions/browser/tools/input.d.ts +7 -0
  47. package/dist/extensions/browser/tools/input.d.ts.map +1 -0
  48. package/dist/extensions/browser/tools/input.js +290 -0
  49. package/dist/extensions/browser/tools/network.d.ts +7 -0
  50. package/dist/extensions/browser/tools/network.d.ts.map +1 -0
  51. package/dist/extensions/browser/tools/network.js +116 -0
  52. package/dist/extensions/browser/tools/screenshot.d.ts +7 -0
  53. package/dist/extensions/browser/tools/screenshot.d.ts.map +1 -0
  54. package/dist/extensions/browser/tools/screenshot.js +153 -0
  55. package/dist/extensions/browser/tools/tabs.d.ts +7 -0
  56. package/dist/extensions/browser/tools/tabs.d.ts.map +1 -0
  57. package/dist/extensions/browser/tools/tabs.js +72 -0
  58. package/dist/extensions/browser/tools/wait.d.ts +7 -0
  59. package/dist/extensions/browser/tools/wait.d.ts.map +1 -0
  60. package/dist/extensions/browser/tools/wait.js +64 -0
  61. package/dist/extensions/kanban-bridge.d.ts.map +1 -1
  62. package/dist/extensions/kanban-bridge.js +70 -3
  63. package/dist/mcp/ui-server.d.ts.map +1 -1
  64. package/dist/mcp/ui-server.js +23 -10
  65. package/dist/sdk/coding-agent/core/extensions/native-extensions.d.ts +60 -0
  66. package/dist/sdk/coding-agent/core/extensions/native-extensions.d.ts.map +1 -0
  67. package/dist/sdk/coding-agent/core/extensions/native-extensions.js +108 -0
  68. package/dist/sdk/coding-agent/core/resource-loader.d.ts.map +1 -1
  69. package/dist/sdk/coding-agent/core/resource-loader.js +23 -1
  70. package/dist/sdk/coding-agent/core/settings-manager.d.ts +5 -0
  71. package/dist/sdk/coding-agent/core/settings-manager.d.ts.map +1 -1
  72. package/dist/sdk/coding-agent/core/settings-manager.js +12 -0
  73. package/dist/server/sqlite-adapter.d.ts +41 -0
  74. package/dist/server/sqlite-adapter.d.ts.map +1 -0
  75. package/dist/server/sqlite-adapter.js +128 -0
  76. package/dist/server/storage.d.ts.map +1 -1
  77. package/dist/server/storage.js +2 -2
  78. package/package.json +5 -2
@@ -0,0 +1 @@
1
+ {"version":3,"file":"signaling-client.d.ts","sourceRoot":"","sources":["../../../../src/extensions/browser/stream/signaling-client.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,MAAM,WAAW,qBAAqB;IACpC,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,KAAK,CAAC;QAChB,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;QACxB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,UAAU,CAAC,EAAE,MAAM,CAAC;KACrB,CAAC,CAAC;CACJ;AAED,qBAAa,eAAe;IAKxB,OAAO,CAAC,QAAQ,CAAC,MAAM;IAJzB,OAAO,CAAC,EAAE,CAA0B;IACpC,OAAO,CAAC,SAAS,CAAS;gBAGP,MAAM,EAAE,qBAAqB;IAG1C,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAgC9B,OAAO,CAAC,aAAa;IAWrB,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI;IAMzC,KAAK,IAAI,IAAI;IAQb,WAAW,IAAI,OAAO;CAGvB"}
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Stream signaling client — connects to the backend WebSocket endpoint
3
+ * for WebRTC signaling relay.
4
+ *
5
+ * The backend only relays SDP offers/answers and ICE candidates between
6
+ * the streamer (this extension) and viewers. No media passes through.
7
+ */
8
+ import WebSocket from "ws";
9
+ export class SignalingClient {
10
+ config;
11
+ ws = null;
12
+ connected = false;
13
+ constructor(config) {
14
+ this.config = config;
15
+ }
16
+ async connect() {
17
+ return new Promise((resolve, reject) => {
18
+ const timeout = setTimeout(() => reject(new Error("Signaling connection timeout")), 10_000);
19
+ this.ws = new WebSocket(this.config.signalingUrl);
20
+ this.ws.on("open", () => {
21
+ clearTimeout(timeout);
22
+ this.connected = true;
23
+ this.ws.send(JSON.stringify({
24
+ type: "create-session",
25
+ sessionId: this.config.sessionId,
26
+ }));
27
+ resolve();
28
+ });
29
+ this.ws.on("error", (err) => {
30
+ clearTimeout(timeout);
31
+ reject(err);
32
+ });
33
+ this.ws.on("message", (data) => {
34
+ try {
35
+ const msg = JSON.parse(data.toString());
36
+ this.handleMessage(msg);
37
+ }
38
+ catch {
39
+ // Ignore malformed messages
40
+ }
41
+ });
42
+ });
43
+ }
44
+ handleMessage(msg) {
45
+ // Messages are primarily forwarded to the in-browser WebRTC client
46
+ // via page.evaluate(). This client just logs them.
47
+ if (msg.type === "viewer-joined") {
48
+ process.stderr.write(`[browser-stream] Viewer joined: ${msg.viewerId}\n`);
49
+ }
50
+ if (msg.type === "viewer-left") {
51
+ process.stderr.write(`[browser-stream] Viewer left: ${msg.viewerId}\n`);
52
+ }
53
+ }
54
+ send(data) {
55
+ if (this.ws && this.connected) {
56
+ this.ws.send(JSON.stringify(data));
57
+ }
58
+ }
59
+ close() {
60
+ if (this.ws) {
61
+ this.ws.close();
62
+ this.ws = null;
63
+ this.connected = false;
64
+ }
65
+ }
66
+ isConnected() {
67
+ return this.connected;
68
+ }
69
+ }
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Stream injector — WebRTC code injected into the browser page.
3
+ *
4
+ * When browser_stream_start is called, this code is injected via
5
+ * page.addInitScript() and page.evaluate(). It captures the page
6
+ * visually and streams it via WebRTC through the signaling server.
7
+ *
8
+ * Approach: canvas.captureStream() at 15 FPS.
9
+ * The canvas mirrors the visible viewport content.
10
+ */
11
+ import type { Page } from "playwright";
12
+ export interface StreamInjectConfig {
13
+ signalingUrl: string;
14
+ sessionId: string;
15
+ iceServers: Array<{
16
+ urls: string | string[];
17
+ username?: string;
18
+ credential?: string;
19
+ }>;
20
+ }
21
+ /**
22
+ * Inject WebRTC streaming code into a Playwright page.
23
+ * Uses addInitScript so it's present on every navigation.
24
+ */
25
+ export declare function injectStreamingCode(page: Page, config: StreamInjectConfig): Promise<void>;
26
+ /**
27
+ * Stop the WebRTC stream and clean up injected code.
28
+ */
29
+ export declare function stopStreaming(page: Page): Promise<void>;
30
+ //# sourceMappingURL=stream-injector.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stream-injector.d.ts","sourceRoot":"","sources":["../../../../src/extensions/browser/stream/stream-injector.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAEvC,MAAM,WAAW,kBAAkB;IACjC,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,KAAK,CAAC;QAChB,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;QACxB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,UAAU,CAAC,EAAE,MAAM,CAAC;KACrB,CAAC,CAAC;CACJ;AAED;;;GAGG;AACH,wBAAsB,mBAAmB,CACvC,IAAI,EAAE,IAAI,EACV,MAAM,EAAE,kBAAkB,GACzB,OAAO,CAAC,IAAI,CAAC,CA0If;AAED;;GAEG;AACH,wBAAsB,aAAa,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAM7D"}
@@ -0,0 +1,161 @@
1
+ /**
2
+ * Stream injector — WebRTC code injected into the browser page.
3
+ *
4
+ * When browser_stream_start is called, this code is injected via
5
+ * page.addInitScript() and page.evaluate(). It captures the page
6
+ * visually and streams it via WebRTC through the signaling server.
7
+ *
8
+ * Approach: canvas.captureStream() at 15 FPS.
9
+ * The canvas mirrors the visible viewport content.
10
+ */
11
+ /**
12
+ * Inject WebRTC streaming code into a Playwright page.
13
+ * Uses addInitScript so it's present on every navigation.
14
+ */
15
+ export async function injectStreamingCode(page, config) {
16
+ const iceServersJson = JSON.stringify(config.iceServers);
17
+ await page.addInitScript(`
18
+ (() => {
19
+ if (window.__spectralStreamInstalled) return;
20
+ window.__spectralStreamInstalled = true;
21
+
22
+ const SIGNALING_URL = ${JSON.stringify(config.signalingUrl)};
23
+ const SESSION_ID = ${JSON.stringify(config.sessionId)};
24
+ const ICE_SERVERS = ${iceServersJson};
25
+
26
+ let pc = null;
27
+ let localStream = null;
28
+ let ws = null;
29
+ let canvas = null;
30
+ let animationId = null;
31
+
32
+ window.__spectralStreamStart = async function() {
33
+ try {
34
+ // Connect signaling WebSocket
35
+ ws = new WebSocket(SIGNALING_URL);
36
+
37
+ await new Promise((resolve, reject) => {
38
+ ws.onopen = () => {
39
+ ws.send(JSON.stringify({ type: "streamer-join", sessionId: SESSION_ID }));
40
+ resolve();
41
+ };
42
+ ws.onerror = reject;
43
+ });
44
+
45
+ // Capture viewport via canvas
46
+ canvas = document.createElement("canvas");
47
+ canvas.width = window.innerWidth;
48
+ canvas.height = window.innerHeight;
49
+ const ctx = canvas.getContext("2d");
50
+
51
+ localStream = canvas.captureStream(15); // 15 FPS
52
+
53
+ // Draw a simple visualization frame
54
+ function drawFrame() {
55
+ if (!ctx) return;
56
+ // Background
57
+ ctx.fillStyle = "#1a1a2e";
58
+ ctx.fillRect(0, 0, canvas.width, canvas.height);
59
+ // Status text
60
+ ctx.fillStyle = "#ffffff";
61
+ ctx.font = "20px monospace";
62
+ ctx.fillText("Spectral Browser Stream", 20, 40);
63
+ ctx.fillStyle = "#a0a0c0";
64
+ ctx.font = "14px monospace";
65
+ ctx.fillText("URL: " + window.location.href, 20, 70);
66
+ ctx.fillText("Time: " + new Date().toISOString(), 20, 90);
67
+ animationId = requestAnimationFrame(drawFrame);
68
+ }
69
+ drawFrame();
70
+
71
+ // Listen for viewer join messages
72
+ ws.onmessage = async (event) => {
73
+ const msg = JSON.parse(event.data);
74
+
75
+ if (msg.type === "viewer-joined") {
76
+ await createOfferForViewer(msg.viewerId);
77
+ }
78
+
79
+ if (msg.type === "answer") {
80
+ const desc = new RTCSessionDescription(msg.sdp);
81
+ await pc.setRemoteDescription(desc);
82
+ }
83
+
84
+ if (msg.type === "ice-candidate") {
85
+ try {
86
+ const candidate = new RTCIceCandidate(msg.candidate);
87
+ await pc.addIceCandidate(candidate);
88
+ } catch (e) {
89
+ console.warn("[spectral-stream] ICE candidate error:", e);
90
+ }
91
+ }
92
+ };
93
+
94
+ async function createOfferForViewer(viewerId) {
95
+ pc = new RTCPeerConnection({ iceServers: ICE_SERVERS });
96
+
97
+ for (const track of localStream.getTracks()) {
98
+ pc.addTrack(track, localStream);
99
+ }
100
+
101
+ pc.onicecandidate = (e) => {
102
+ if (e.candidate && ws?.readyState === WebSocket.OPEN) {
103
+ ws.send(JSON.stringify({
104
+ type: "ice-candidate",
105
+ sessionId: SESSION_ID,
106
+ receiverId: viewerId,
107
+ candidate: e.candidate.toJSON(),
108
+ }));
109
+ }
110
+ };
111
+
112
+ const offer = await pc.createOffer();
113
+ await pc.setLocalDescription(offer);
114
+
115
+ if (ws?.readyState === WebSocket.OPEN) {
116
+ ws.send(JSON.stringify({
117
+ type: "offer",
118
+ sessionId: SESSION_ID,
119
+ receiverId: viewerId,
120
+ sdp: pc.localDescription.toJSON(),
121
+ }));
122
+ }
123
+ }
124
+
125
+ console.log("[spectral-stream] Streaming started — canvas capture at 15 FPS");
126
+ } catch (err) {
127
+ console.error("[spectral-stream] Failed to start:", err);
128
+ }
129
+ };
130
+
131
+ window.__spectralStreamStop = function() {
132
+ if (animationId) cancelAnimationFrame(animationId);
133
+ if (pc) { pc.close(); pc = null; }
134
+ if (localStream) {
135
+ localStream.getTracks().forEach(t => t.stop());
136
+ localStream = null;
137
+ }
138
+ if (ws) { ws.close(); ws = null; }
139
+ if (canvas) { canvas.remove(); canvas = null; }
140
+ window.__spectralStreamInstalled = false;
141
+ console.log("[spectral-stream] Streaming stopped");
142
+ };
143
+ })();
144
+ `);
145
+ // Trigger if page already loaded
146
+ await page.evaluate(() => {
147
+ if (window.__spectralStreamStart) {
148
+ window.__spectralStreamStart();
149
+ }
150
+ });
151
+ }
152
+ /**
153
+ * Stop the WebRTC stream and clean up injected code.
154
+ */
155
+ export async function stopStreaming(page) {
156
+ await page.evaluate(() => {
157
+ if (window.__spectralStreamStop) {
158
+ window.__spectralStreamStop();
159
+ }
160
+ });
161
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Stream tools: browser_stream_start, browser_stream_stop, browser_stream_status.
3
+ *
4
+ * These extend the browser extension with WebRTC streaming capability
5
+ * so that remote viewers can watch the browser content in real time.
6
+ */
7
+ import type { ExtensionAPI } from "../../../sdk/coding-agent/index.js";
8
+ import { BrowserService } from "../browser-service.js";
9
+ export declare function registerStreamTools(ext: ExtensionAPI, bs: BrowserService): void;
10
+ //# sourceMappingURL=stream-tool.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stream-tool.d.ts","sourceRoot":"","sources":["../../../../src/extensions/browser/stream/stream-tool.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAkB,MAAM,oCAAoC,CAAC;AACvF,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAYvD,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,YAAY,EAAE,EAAE,EAAE,cAAc,GAAG,IAAI,CA2L/E"}
@@ -0,0 +1,182 @@
1
+ /**
2
+ * Stream tools: browser_stream_start, browser_stream_stop, browser_stream_status.
3
+ *
4
+ * These extend the browser extension with WebRTC streaming capability
5
+ * so that remote viewers can watch the browser content in real time.
6
+ */
7
+ import { SignalingClient } from "./signaling-client.js";
8
+ import { injectStreamingCode, stopStreaming } from "./stream-injector.js";
9
+ let activeStream = null;
10
+ export function registerStreamTools(ext, bs) {
11
+ const startTool = {
12
+ name: "browser_stream_start",
13
+ label: "Start Browser Stream",
14
+ description: "Start streaming the browser content via WebRTC. " +
15
+ "Returns a sessionId that viewers can use to connect through the backend signaling server. " +
16
+ "The stream uses canvas capture at 15 FPS — not a full screen mirror, but a live visualization. " +
17
+ "Use browser_stream_stop when done.",
18
+ promptSnippet: "`browser_stream_start { url }` — start streaming the browser to viewers",
19
+ parameters: {
20
+ type: "object",
21
+ properties: {
22
+ url: {
23
+ type: "string",
24
+ description: "Optional initial URL to navigate to before starting the stream.",
25
+ },
26
+ },
27
+ },
28
+ async execute(_toolCallId, params) {
29
+ const p = params;
30
+ if (activeStream) {
31
+ return {
32
+ content: [{
33
+ type: "text",
34
+ text: `Stream already active (session: ${activeStream.sessionId}). Use browser_stream_stop first.`,
35
+ }],
36
+ details: { isError: true },
37
+ };
38
+ }
39
+ try {
40
+ const { page } = await bs.ensureBrowser();
41
+ if (p.url) {
42
+ await bs.navigate(p.url);
43
+ }
44
+ const signalingUrl = process.env.SPECTRAL_STREAM_SIGNALING_URL ?? "ws://localhost:4008/browser-stream";
45
+ const sessionId = crypto.randomUUID();
46
+ const iceServers = [
47
+ { urls: "stun:stun.l.google.com:19302" },
48
+ { urls: "stun:stun1.l.google.com:19302" },
49
+ ...(process.env.SPECTRAL_TURN_URL
50
+ ? [{
51
+ urls: process.env.SPECTRAL_TURN_URL,
52
+ username: process.env.SPECTRAL_TURN_USERNAME ?? "",
53
+ credential: process.env.SPECTRAL_TURN_CREDENTIAL ?? "",
54
+ }]
55
+ : []),
56
+ ];
57
+ const signalingClient = new SignalingClient({
58
+ signalingUrl,
59
+ sessionId,
60
+ iceServers,
61
+ });
62
+ await signalingClient.connect();
63
+ await injectStreamingCode(page, {
64
+ signalingUrl,
65
+ sessionId,
66
+ iceServers: [
67
+ { urls: "stun:stun.l.google.com:19302" },
68
+ ],
69
+ });
70
+ activeStream = {
71
+ sessionId,
72
+ signalingClient,
73
+ startedAt: Date.now(),
74
+ };
75
+ return {
76
+ content: [{
77
+ type: "text",
78
+ text: [
79
+ `✓ Browser stream started`,
80
+ `Session ID: ${sessionId}`,
81
+ `Signaling URL: ${signalingUrl}`,
82
+ ``,
83
+ `Viewers connect via: ${signalingUrl}?session=${sessionId}`,
84
+ ``,
85
+ `Use these tools to control the stream:`,
86
+ `- browser_stream_stop: stop streaming`,
87
+ `- browser_stream_status: check stream status`,
88
+ `- browser_navigate: navigate to a different URL`,
89
+ `- browser_evaluate: execute JS in the streamed page`,
90
+ ].join("\n"),
91
+ }],
92
+ details: { sessionId },
93
+ };
94
+ }
95
+ catch (err) {
96
+ const msg = err instanceof Error ? err.message : String(err);
97
+ return {
98
+ content: [{ type: "text", text: `Failed to start browser stream: ${msg}` }],
99
+ details: { isError: true },
100
+ };
101
+ }
102
+ },
103
+ };
104
+ const stopTool = {
105
+ name: "browser_stream_stop",
106
+ label: "Stop Browser Stream",
107
+ description: "Stop the active browser stream and clean up WebRTC connections.",
108
+ promptSnippet: "`browser_stream_stop` — stop the active browser stream",
109
+ parameters: {
110
+ type: "object",
111
+ properties: {},
112
+ },
113
+ async execute() {
114
+ if (!activeStream) {
115
+ return {
116
+ content: [{ type: "text", text: "No active stream." }],
117
+ details: {},
118
+ };
119
+ }
120
+ try {
121
+ const page = bs.getActivePage();
122
+ await stopStreaming(page);
123
+ }
124
+ catch { /* ignore page-level errors */ }
125
+ try {
126
+ activeStream.signalingClient.close();
127
+ }
128
+ catch { /* ignore */ }
129
+ const duration = Math.round((Date.now() - activeStream.startedAt) / 1000);
130
+ const sessionId = activeStream.sessionId;
131
+ activeStream = null;
132
+ return {
133
+ content: [{
134
+ type: "text",
135
+ text: `✓ Stream stopped.\nSession: ${sessionId}\nDuration: ${duration}s`,
136
+ }],
137
+ details: {},
138
+ };
139
+ },
140
+ };
141
+ const statusTool = {
142
+ name: "browser_stream_status",
143
+ label: "Browser Stream Status",
144
+ description: "Check the status of the current browser stream.",
145
+ promptSnippet: "`browser_stream_status` — check stream status",
146
+ parameters: {
147
+ type: "object",
148
+ properties: {},
149
+ },
150
+ async execute() {
151
+ if (!activeStream) {
152
+ return {
153
+ content: [{ type: "text", text: "No active stream. Use browser_stream_start to begin." }],
154
+ details: {},
155
+ };
156
+ }
157
+ const duration = Math.round((Date.now() - activeStream.startedAt) / 1000);
158
+ return {
159
+ content: [{
160
+ type: "text",
161
+ text: [
162
+ `Stream active`,
163
+ `Session ID: ${activeStream.sessionId}`,
164
+ `Started: ${new Date(activeStream.startedAt).toISOString()}`,
165
+ `Duration: ${duration}s`,
166
+ `Signaling: ${activeStream.signalingClient.isConnected() ? "connected" : "disconnected"}`,
167
+ ].join("\n"),
168
+ }],
169
+ details: {},
170
+ };
171
+ },
172
+ };
173
+ for (const tool of [startTool, stopTool, statusTool]) {
174
+ try {
175
+ ext.registerTool(tool);
176
+ }
177
+ catch (err) {
178
+ const msg = err instanceof Error ? err.message : String(err);
179
+ process.stderr.write(`[browser-stream] Failed to register ${tool.name}: ${msg}\n`);
180
+ }
181
+ }
182
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Console messages tool: browser_console_messages.
3
+ */
4
+ import type { ExtensionAPI } from "../../../sdk/coding-agent/index.js";
5
+ import { BrowserService } from "../browser-service.js";
6
+ export declare function registerConsoleTools(ext: ExtensionAPI, bs: BrowserService): void;
7
+ //# sourceMappingURL=console.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"console.d.ts","sourceRoot":"","sources":["../../../../src/extensions/browser/tools/console.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAkB,MAAM,oCAAoC,CAAC;AACvF,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAEvD,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,YAAY,EAAE,EAAE,EAAE,cAAc,GAAG,IAAI,CAyDhF"}
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Console messages tool: browser_console_messages.
3
+ */
4
+ export function registerConsoleTools(ext, bs) {
5
+ const consoleTool = {
6
+ name: "browser_console_messages",
7
+ label: "Browser Console Messages",
8
+ description: "Returns all console messages from the browser. Each level includes messages of more severe levels.",
9
+ promptSnippet: "`browser_console_messages` — get browser console output",
10
+ parameters: {
11
+ type: "object",
12
+ properties: {
13
+ level: {
14
+ type: "string",
15
+ enum: ["error", "warning", "info", "debug"],
16
+ description: "Minimum severity level to return. Default: info (returns info, warning, and error).",
17
+ },
18
+ all: {
19
+ type: "boolean",
20
+ description: "Return all messages since the start of the session, not just the current page. Default: false.",
21
+ },
22
+ },
23
+ },
24
+ async execute(_toolCallId, params) {
25
+ const p = params;
26
+ try {
27
+ const messages = bs.getConsoleMessages(p.level, p.all ?? false);
28
+ if (messages.length === 0) {
29
+ return { content: [{ type: "text", text: "No console messages." }], details: {} };
30
+ }
31
+ const lines = messages.map((m) => `[${m.type.padEnd(7)}] ${m.text.slice(0, 300)}`);
32
+ const text = lines.join("\n");
33
+ return {
34
+ content: [{
35
+ type: "text",
36
+ text: text.length > 20_000 ? text.slice(0, 20_000) + "\n\n[truncated]" : text,
37
+ }],
38
+ details: {},
39
+ };
40
+ }
41
+ catch (err) {
42
+ const msg = err instanceof Error ? err.message : String(err);
43
+ return { content: [{ type: "text", text: `Console messages failed: ${msg}` }], details: { isError: true } };
44
+ }
45
+ },
46
+ };
47
+ try {
48
+ ext.registerTool(consoleTool);
49
+ }
50
+ catch (err) {
51
+ const msg = err instanceof Error ? err.message : String(err);
52
+ process.stderr.write(`[browser] Failed to register browser_console_messages: ${msg}\n`);
53
+ }
54
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Core browser tools: navigate, navigate_back, close, resize.
3
+ */
4
+ import type { ExtensionAPI } from "../../../sdk/coding-agent/index.js";
5
+ import { BrowserService } from "../browser-service.js";
6
+ export declare function registerCoreTools(ext: ExtensionAPI, bs: BrowserService): void;
7
+ //# sourceMappingURL=core.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"core.d.ts","sourceRoot":"","sources":["../../../../src/extensions/browser/tools/core.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAkB,MAAM,oCAAoC,CAAC;AACvF,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAEvD,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,YAAY,EAAE,EAAE,EAAE,cAAc,GAAG,IAAI,CA8G7E"}
@@ -0,0 +1,115 @@
1
+ /**
2
+ * Core browser tools: navigate, navigate_back, close, resize.
3
+ */
4
+ export function registerCoreTools(ext, bs) {
5
+ const navigateTool = {
6
+ name: "browser_navigate",
7
+ label: "Browser Navigate",
8
+ description: "Navigate to a URL in the browser. Auto-prepends https:// if no protocol is given.",
9
+ promptSnippet: "`browser_navigate { url }` — navigate the browser to a URL",
10
+ parameters: {
11
+ type: "object",
12
+ properties: {
13
+ url: {
14
+ type: "string",
15
+ description: "The URL to navigate to. https:// is prepended automatically if missing.",
16
+ },
17
+ },
18
+ required: ["url"],
19
+ },
20
+ async execute(_toolCallId, params) {
21
+ const { url } = params;
22
+ if (!url || typeof url !== "string") {
23
+ return { content: [{ type: "text", text: "Error: url (string) is required." }], details: {} };
24
+ }
25
+ try {
26
+ const result = await bs.navigate(url);
27
+ return {
28
+ content: [{ type: "text", text: `✓ Navigated to: ${result.url}\nTitle: ${result.title}` }],
29
+ details: {},
30
+ };
31
+ }
32
+ catch (err) {
33
+ const msg = err instanceof Error ? err.message : String(err);
34
+ return { content: [{ type: "text", text: `Navigation failed: ${msg}` }], details: { isError: true } };
35
+ }
36
+ },
37
+ };
38
+ const navigateBackTool = {
39
+ name: "browser_navigate_back",
40
+ label: "Browser Navigate Back",
41
+ description: "Go back to the previous page in browser history.",
42
+ promptSnippet: "`browser_navigate_back` — go back one page",
43
+ parameters: {
44
+ type: "object",
45
+ properties: {},
46
+ },
47
+ async execute() {
48
+ try {
49
+ const result = await bs.navigateBack();
50
+ return {
51
+ content: [{ type: "text", text: `✓ Went back to: ${result.url}\nTitle: ${result.title}` }],
52
+ details: {},
53
+ };
54
+ }
55
+ catch (err) {
56
+ const msg = err instanceof Error ? err.message : String(err);
57
+ return { content: [{ type: "text", text: `Navigate back failed: ${msg}` }], details: { isError: true } };
58
+ }
59
+ },
60
+ };
61
+ const closeTool = {
62
+ name: "browser_close",
63
+ label: "Browser Close Page",
64
+ description: "Close the current page. A blank page is opened if it was the last one.",
65
+ promptSnippet: "`browser_close` — close the current browser page",
66
+ parameters: {
67
+ type: "object",
68
+ properties: {},
69
+ },
70
+ async execute() {
71
+ try {
72
+ await bs.closePage();
73
+ return { content: [{ type: "text", text: "✓ Page closed." }], details: {} };
74
+ }
75
+ catch (err) {
76
+ const msg = err instanceof Error ? err.message : String(err);
77
+ return { content: [{ type: "text", text: `Close failed: ${msg}` }], details: { isError: true } };
78
+ }
79
+ },
80
+ };
81
+ const resizeTool = {
82
+ name: "browser_resize",
83
+ label: "Browser Resize",
84
+ description: "Resize the browser viewport.",
85
+ promptSnippet: "`browser_resize { width, height }` — resize the browser viewport",
86
+ parameters: {
87
+ type: "object",
88
+ properties: {
89
+ width: { type: "number", description: "Width in pixels" },
90
+ height: { type: "number", description: "Height in pixels" },
91
+ },
92
+ required: ["width", "height"],
93
+ },
94
+ async execute(_toolCallId, params) {
95
+ const { width, height } = params;
96
+ try {
97
+ await bs.resize(width, height);
98
+ return { content: [{ type: "text", text: `✓ Viewport resized to ${width}x${height}.` }], details: {} };
99
+ }
100
+ catch (err) {
101
+ const msg = err instanceof Error ? err.message : String(err);
102
+ return { content: [{ type: "text", text: `Resize failed: ${msg}` }], details: { isError: true } };
103
+ }
104
+ },
105
+ };
106
+ for (const tool of [navigateTool, navigateBackTool, closeTool, resizeTool]) {
107
+ try {
108
+ ext.registerTool(tool);
109
+ }
110
+ catch (err) {
111
+ const msg = err instanceof Error ? err.message : String(err);
112
+ process.stderr.write(`[browser] Failed to register ${tool.name}: ${msg}\n`);
113
+ }
114
+ }
115
+ }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Dialog tool: browser_handle_dialog.
3
+ *
4
+ * NOTE: BrowserService intercepts all dialogs globally and holds them
5
+ * until the agent handles them via this tool. This avoids Playwright's
6
+ * default auto-dismiss behavior.
7
+ */
8
+ import type { ExtensionAPI } from "../../../sdk/coding-agent/index.js";
9
+ import { BrowserService } from "../browser-service.js";
10
+ export declare function registerDialogTools(ext: ExtensionAPI, bs: BrowserService): void;
11
+ //# sourceMappingURL=dialog.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dialog.d.ts","sourceRoot":"","sources":["../../../../src/extensions/browser/tools/dialog.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAkB,MAAM,oCAAoC,CAAC;AACvF,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAEvD,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,YAAY,EAAE,EAAE,EAAE,cAAc,GAAG,IAAI,CA8C/E"}