browser-debugging-daemon 1.0.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/package.json ADDED
@@ -0,0 +1,66 @@
1
+ {
2
+ "name": "browser-debugging-daemon",
3
+ "version": "1.0.0",
4
+ "description": "Playwright-based browser runtime for AI agents — SDK, HTTP daemon, and MCP server",
5
+ "type": "module",
6
+ "main": "./index.js",
7
+ "exports": {
8
+ ".": "./index.js",
9
+ "./runtime": "./runtime/BrowserRuntime.js",
10
+ "./subagent": "./subagent/BrowserSubagent.js",
11
+ "./orchestrator": "./orchestrator/TaskRunner.js",
12
+ "./shared": "./shared.js"
13
+ },
14
+ "bin": {
15
+ "browser-daemon": "./daemon.js",
16
+ "browser-mcp": "./mcp_server.js"
17
+ },
18
+ "files": [
19
+ "index.js",
20
+ "daemon.js",
21
+ "mcp_server.js",
22
+ "shared.js",
23
+ "runtime/",
24
+ "subagent/",
25
+ "orchestrator/",
26
+ "dashboard/"
27
+ ],
28
+ "keywords": [
29
+ "playwright",
30
+ "browser-automation",
31
+ "ai-agent",
32
+ "mcp",
33
+ "set-of-mark",
34
+ "subagent",
35
+ "browser-debugging",
36
+ "autonomous-agent"
37
+ ],
38
+ "license": "MIT",
39
+ "engines": {
40
+ "node": ">=18"
41
+ },
42
+ "repository": {
43
+ "type": "git",
44
+ "url": "https://github.com/JesstLe/browser-debugging-daemon"
45
+ },
46
+ "scripts": {
47
+ "check": "node --check index.js && node --check daemon.js && node --check mcp_server.js && node --check shared.js && node --check runtime/ArtifactStore.js && node --check runtime/BrowserRuntime.js && node --check subagent/OpenAIPlanner.js && node --check subagent/BrowserSubagent.js && node --check orchestrator/TaskRunStore.js && node --check orchestrator/RunTemplateStore.js && node --check orchestrator/TaskRunner.js && node --check dashboard/app.js && node --check examples/mock_delegate_demo.js && node --check examples/smoke_walkthrough_audit.js && node --check examples/smoke_run_recovery.js && node --check examples/smoke_run_templates.js && node --check examples/smoke_daemon_auth.js && node --check tooling/common.js && node --check tooling/generate_mcp_configs.js && node --check tooling/healthcheck.js && node --check tooling/start_ide_stack.js",
48
+ "smoke:mock": "node examples/mock_delegate_demo.js",
49
+ "smoke:walkthrough": "node examples/smoke_walkthrough_audit.js",
50
+ "smoke:recovery": "node examples/smoke_run_recovery.js",
51
+ "smoke:templates": "node examples/smoke_run_templates.js",
52
+ "smoke:auth": "node examples/smoke_daemon_auth.js",
53
+ "smoke:all": "npm run smoke:mock && npm run smoke:walkthrough && npm run smoke:recovery && npm run smoke:templates && npm run smoke:auth",
54
+ "ide:configs": "node tooling/generate_mcp_configs.js",
55
+ "ide:health": "node tooling/healthcheck.js",
56
+ "ide:bootstrap": "npm run ide:configs && npm run ide:health",
57
+ "ide:start": "node tooling/start_ide_stack.js",
58
+ "ide:vscode:check": "node --check ../integrations/vscode-extension/extension.cjs"
59
+ },
60
+ "dependencies": {
61
+ "@modelcontextprotocol/sdk": "^1.27.1",
62
+ "cors": "^2.8.5",
63
+ "express": "^4.18.2",
64
+ "playwright": "^1.42.1"
65
+ }
66
+ }
@@ -0,0 +1,202 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+
4
+ const ARTIFACT_MAX_SESSIONS = Number.parseInt(process.env.BROWSER_ARTIFACT_MAX_SESSIONS, 10);
5
+ const ARTIFACT_MAX_AGE_DAYS = Number.parseFloat(process.env.BROWSER_ARTIFACT_MAX_AGE_DAYS);
6
+
7
+ function sanitizeSegment(value) {
8
+ return String(value)
9
+ .trim()
10
+ .toLowerCase()
11
+ .replace(/[^a-z0-9-_]+/g, "-")
12
+ .replace(/^-+|-+$/g, "")
13
+ .slice(0, 60) || "artifact";
14
+ }
15
+
16
+ export class ArtifactStore {
17
+ constructor(baseDir) {
18
+ this.baseDir = baseDir;
19
+ this.rootDir = path.join(baseDir, "artifacts");
20
+ this.currentViewPath = path.join(baseDir, "current_view.png");
21
+ this.sessionId = null;
22
+ this.sessionDir = null;
23
+ this.screenshotsDir = null;
24
+ this.videosDir = null;
25
+ this.tracesDir = null;
26
+ this.eventsPath = null;
27
+ this.tracePath = null;
28
+ this.stepCounter = 0;
29
+ this.maxSessions = Number.isFinite(ARTIFACT_MAX_SESSIONS) && ARTIFACT_MAX_SESSIONS > 0
30
+ ? Math.floor(ARTIFACT_MAX_SESSIONS)
31
+ : null;
32
+ this.maxAgeMs = Number.isFinite(ARTIFACT_MAX_AGE_DAYS) && ARTIFACT_MAX_AGE_DAYS > 0
33
+ ? ARTIFACT_MAX_AGE_DAYS * 24 * 60 * 60 * 1000
34
+ : null;
35
+ }
36
+
37
+ startSession() {
38
+ this.cleanupRetainedSessions();
39
+ const sessionId = new Date().toISOString().replace(/[:.]/g, "-");
40
+ const sessionDir = path.join(this.rootDir, sessionId);
41
+
42
+ this.sessionId = sessionId;
43
+ this.sessionDir = sessionDir;
44
+ this.screenshotsDir = path.join(sessionDir, "screenshots");
45
+ this.videosDir = path.join(sessionDir, "videos");
46
+ this.tracesDir = path.join(sessionDir, "traces");
47
+ this.eventsPath = path.join(sessionDir, "events.jsonl");
48
+ this.tracePath = null;
49
+ this.stepCounter = 0;
50
+
51
+ fs.mkdirSync(this.screenshotsDir, { recursive: true });
52
+ fs.mkdirSync(this.videosDir, { recursive: true });
53
+ fs.mkdirSync(this.tracesDir, { recursive: true });
54
+ }
55
+
56
+ cleanupRetainedSessions() {
57
+ if (!fs.existsSync(this.rootDir)) {
58
+ return;
59
+ }
60
+
61
+ const entries = fs.readdirSync(this.rootDir, { withFileTypes: true })
62
+ .filter((entry) => entry.isDirectory())
63
+ .map((entry) => {
64
+ const sessionPath = path.join(this.rootDir, entry.name);
65
+ const stats = fs.statSync(sessionPath);
66
+ return {
67
+ path: sessionPath,
68
+ mtimeMs: stats.mtimeMs,
69
+ };
70
+ })
71
+ .sort((left, right) => right.mtimeMs - left.mtimeMs);
72
+
73
+ const now = Date.now();
74
+ const kept = [];
75
+ for (const entry of entries) {
76
+ const exceedsAge = this.maxAgeMs ? (now - entry.mtimeMs) > this.maxAgeMs : false;
77
+ const exceedsCount = this.maxSessions ? kept.length >= this.maxSessions : false;
78
+
79
+ if (exceedsAge || exceedsCount) {
80
+ fs.rmSync(entry.path, { recursive: true, force: true });
81
+ continue;
82
+ }
83
+
84
+ kept.push(entry);
85
+ }
86
+ }
87
+
88
+ ensureActiveSession() {
89
+ if (!this.sessionDir) {
90
+ throw new Error("Artifact session has not been initialized.");
91
+ }
92
+ }
93
+
94
+ getVideoDir() {
95
+ this.ensureActiveSession();
96
+ return this.videosDir;
97
+ }
98
+
99
+ getVideoPath(label = "video") {
100
+ this.ensureActiveSession();
101
+ return path.join(this.videosDir, `${sanitizeSegment(label)}_${Date.now()}.webm`);
102
+ }
103
+
104
+ getAttachedFramesDir(label = "attached-frames") {
105
+ this.ensureActiveSession();
106
+ const framesDir = path.join(this.videosDir, `${sanitizeSegment(label)}_${Date.now()}`);
107
+ fs.mkdirSync(framesDir, { recursive: true });
108
+ return framesDir;
109
+ }
110
+
111
+ getCurrentViewPath() {
112
+ return this.currentViewPath;
113
+ }
114
+
115
+ getStepScreenshotPath(label) {
116
+ this.ensureActiveSession();
117
+ this.stepCounter += 1;
118
+ const filename = `${String(this.stepCounter).padStart(3, "0")}_${sanitizeSegment(label)}.png`;
119
+ return path.join(this.screenshotsDir, filename);
120
+ }
121
+
122
+ getTracePath(label = "trace") {
123
+ this.ensureActiveSession();
124
+ this.tracePath = path.join(this.tracesDir, `${sanitizeSegment(label)}_${Date.now()}.zip`);
125
+ return this.tracePath;
126
+ }
127
+
128
+ getConsoleLogPath() {
129
+ this.ensureActiveSession();
130
+ return path.join(this.sessionDir, "console.json");
131
+ }
132
+
133
+ getNetworkLogPath() {
134
+ this.ensureActiveSession();
135
+ return path.join(this.sessionDir, "network.json");
136
+ }
137
+
138
+ getErrorLogPath() {
139
+ this.ensureActiveSession();
140
+ return path.join(this.sessionDir, "errors.json");
141
+ }
142
+
143
+ listTraceFiles() {
144
+ if (!this.tracesDir || !fs.existsSync(this.tracesDir)) {
145
+ return [];
146
+ }
147
+
148
+ return fs.readdirSync(this.tracesDir)
149
+ .filter((name) => name.endsWith(".zip"))
150
+ .map((name) => path.join(this.tracesDir, name))
151
+ .sort();
152
+ }
153
+
154
+ appendEvent(type, payload = {}) {
155
+ this.ensureActiveSession();
156
+ const entry = {
157
+ timestamp: new Date().toISOString(),
158
+ type,
159
+ payload,
160
+ };
161
+ fs.appendFileSync(this.eventsPath, `${JSON.stringify(entry)}\n`, "utf8");
162
+ }
163
+
164
+ writeJson(filename, data) {
165
+ this.ensureActiveSession();
166
+ const filePath = path.join(this.sessionDir, filename);
167
+ fs.writeFileSync(filePath, JSON.stringify(data, null, 2), "utf8");
168
+ return filePath;
169
+ }
170
+
171
+ writeText(filename, text) {
172
+ this.ensureActiveSession();
173
+ const filePath = path.join(this.sessionDir, filename);
174
+ fs.writeFileSync(filePath, text, "utf8");
175
+ return filePath;
176
+ }
177
+
178
+ listVideoFiles() {
179
+ if (!this.videosDir || !fs.existsSync(this.videosDir)) {
180
+ return [];
181
+ }
182
+
183
+ return fs.readdirSync(this.videosDir)
184
+ .filter((name) => name.endsWith(".webm"))
185
+ .map((name) => path.join(this.videosDir, name));
186
+ }
187
+
188
+ getSummary() {
189
+ return {
190
+ sessionId: this.sessionId,
191
+ sessionDir: this.sessionDir,
192
+ currentViewPath: this.currentViewPath,
193
+ screenshotsDir: this.screenshotsDir,
194
+ videosDir: this.videosDir,
195
+ videoFiles: this.listVideoFiles(),
196
+ tracesDir: this.tracesDir,
197
+ traceFiles: this.listTraceFiles(),
198
+ eventsPath: this.eventsPath,
199
+ tracePath: this.tracePath,
200
+ };
201
+ }
202
+ }