doer-agent 0.1.5 → 0.1.7

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/README.md CHANGED
@@ -11,7 +11,6 @@
11
11
  주요 엔트리 포인트:
12
12
 
13
13
  - `doer-agent`: 에이전트 본체 CLI
14
- - `playwright-mcp-call`: Playwright MCP 호출용 CLI
15
14
  - `codex`: Codex 래퍼 CLI
16
15
 
17
16
  ## 요구 사항
package/dist/agent.js CHANGED
@@ -1,14 +1,10 @@
1
1
  import { spawn, spawnSync } from "node:child_process";
2
2
  import { existsSync } from "node:fs";
3
- import { chmod, mkdir, readFile, rename, writeFile } from "node:fs/promises";
4
- import net from "node:net";
5
- import { arch, homedir } from "node:os";
3
+ import { chmod, mkdir, readFile, writeFile } from "node:fs/promises";
4
+ import { homedir } from "node:os";
6
5
  import path from "node:path";
7
6
  import { fileURLToPath } from "node:url";
8
7
  import { AckPolicy, connect, DeliverPolicy, JSONCodec, RetentionPolicy, StorageType } from "nats";
9
- const PLAYWRIGHT_SKIP_BROWSER_GC = "1";
10
- const PLAYWRIGHT_MCP_DAEMON_IDLE_TTL_SECONDS_DEFAULT = 10800;
11
- const PLAYWRIGHT_MCP_DAEMON_SIGNATURE_VERSION = "2026-03-15";
12
8
  const DEFAULT_SERVER_BASE_URL = "https://doer.cranix.net";
13
9
  const AGENT_MODULE_DIR = path.dirname(fileURLToPath(import.meta.url));
14
10
  const AGENT_PROJECT_DIR = path.join(AGENT_MODULE_DIR, "..");
@@ -144,18 +140,6 @@ function resolveCodexHomePath() {
144
140
  function parseEnvBoolean(value) {
145
141
  return value?.trim().toLowerCase() === "true";
146
142
  }
147
- function parseEnvStringArray(value) {
148
- if (!value?.trim()) {
149
- return [];
150
- }
151
- try {
152
- const parsed = JSON.parse(value);
153
- return Array.isArray(parsed) && parsed.every((item) => typeof item === "string") ? parsed : [];
154
- }
155
- catch {
156
- return [];
157
- }
158
- }
159
143
  function parseEnvInteger(value, fallback) {
160
144
  const normalized = value?.trim();
161
145
  if (!normalized) {
@@ -164,223 +148,6 @@ function parseEnvInteger(value, fallback) {
164
148
  const parsed = Number.parseInt(normalized, 10);
165
149
  return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
166
150
  }
167
- function resolvePlaywrightMcpProxyPath() {
168
- const candidates = [
169
- path.join(AGENT_PROJECT_DIR, "runtime/bin/doer-mcp-proxy"),
170
- path.join(process.cwd(), "agent/runtime/bin/doer-mcp-proxy"),
171
- path.join(process.cwd(), "runtime/bin/doer-mcp-proxy"),
172
- ];
173
- for (const candidate of candidates) {
174
- if (existsSync(candidate)) {
175
- return candidate;
176
- }
177
- }
178
- return "";
179
- }
180
- const PLAYWRIGHT_MCP_PROXY_LAUNCHER_PATH = path.join(AGENT_PROJECT_DIR, "runtime/bin/playwright-mcp-proxy-launcher.sh");
181
- function resolvePlaywrightMcpDaemonSocketPath(stateDir) {
182
- if (process.platform === "win32") {
183
- const suffix = stateDir
184
- .toLowerCase()
185
- .replace(/[^a-z0-9]+/g, "-")
186
- .replace(/^-+|-+$/g, "")
187
- .slice(-48) || "default";
188
- return `\\\\.\\pipe\\doer-playwright-mcp-${suffix}`;
189
- }
190
- return path.join(stateDir, "playwright-mcp-daemon", "playwright-mcp.sock");
191
- }
192
- function resolvePlaywrightMcpDaemonStatePaths() {
193
- const stateDir = resolveAgentStateDir();
194
- const daemonDir = path.join(stateDir, "playwright-mcp-daemon");
195
- return {
196
- daemonDir,
197
- socketPath: resolvePlaywrightMcpDaemonSocketPath(stateDir),
198
- pidPath: path.join(daemonDir, "daemon.pid"),
199
- metaPath: path.join(daemonDir, "daemon-meta.json"),
200
- };
201
- }
202
- function parseEnvAssignmentArgs(values) {
203
- const envPatch = {};
204
- for (const value of values) {
205
- const separatorIndex = value.indexOf("=");
206
- if (separatorIndex <= 0) {
207
- continue;
208
- }
209
- const key = value.slice(0, separatorIndex).trim();
210
- const envValue = value.slice(separatorIndex + 1);
211
- if (!key) {
212
- continue;
213
- }
214
- envPatch[key] = envValue;
215
- }
216
- return envPatch;
217
- }
218
- function escapeShellArg(value) {
219
- return `'${value.replace(/'/g, `'\"'\"'`)}'`;
220
- }
221
- async function readPidFile(pidPath) {
222
- const raw = await readFile(pidPath, "utf8").catch(() => "");
223
- const parsed = Number.parseInt(raw.trim(), 10);
224
- return Number.isInteger(parsed) && parsed > 1 ? parsed : null;
225
- }
226
- function isProcessAlive(pid) {
227
- try {
228
- process.kill(pid, 0);
229
- return true;
230
- }
231
- catch {
232
- return false;
233
- }
234
- }
235
- async function waitForPlaywrightMcpSocketReady(socketPath, timeoutMs) {
236
- const startedAt = Date.now();
237
- while (Date.now() - startedAt <= timeoutMs) {
238
- const isReady = await new Promise((resolve) => {
239
- const socket = net.createConnection({ path: socketPath });
240
- let settled = false;
241
- const finish = (value) => {
242
- if (settled) {
243
- return;
244
- }
245
- settled = true;
246
- resolve(value);
247
- };
248
- socket.once("connect", () => {
249
- socket.end();
250
- finish(true);
251
- });
252
- socket.once("error", () => {
253
- finish(false);
254
- });
255
- setTimeout(() => {
256
- socket.destroy();
257
- finish(false);
258
- }, 250).unref?.();
259
- });
260
- if (isReady) {
261
- return true;
262
- }
263
- await sleep(120);
264
- }
265
- return false;
266
- }
267
- async function stopPlaywrightMcpDaemon(paths) {
268
- const pid = await readPidFile(paths.pidPath);
269
- if (pid && isProcessAlive(pid)) {
270
- try {
271
- process.kill(pid, "SIGTERM");
272
- }
273
- catch {
274
- // ignore
275
- }
276
- const waitStartedAt = Date.now();
277
- while (Date.now() - waitStartedAt < 1800 && isProcessAlive(pid)) {
278
- await sleep(120);
279
- }
280
- if (isProcessAlive(pid)) {
281
- try {
282
- process.kill(pid, "SIGKILL");
283
- }
284
- catch {
285
- // ignore
286
- }
287
- }
288
- }
289
- await Promise.all([
290
- rename(paths.socketPath, `${paths.socketPath}.stale.${Date.now()}`).catch(() => undefined),
291
- rename(paths.pidPath, `${paths.pidPath}.stale.${Date.now()}`).catch(() => undefined),
292
- rename(paths.metaPath, `${paths.metaPath}.stale.${Date.now()}`).catch(() => undefined),
293
- ]);
294
- }
295
- async function ensureManagedPlaywrightMcpDaemon(args) {
296
- const paths = resolvePlaywrightMcpDaemonStatePaths();
297
- await mkdir(paths.daemonDir, { recursive: true });
298
- const daemonCommand = args.command;
299
- const targetEnvPatch = parseEnvAssignmentArgs(args.browserEnvArgs);
300
- const daemonCommandArgs = args.daemonArgs;
301
- const signature = JSON.stringify({
302
- version: PLAYWRIGHT_MCP_DAEMON_SIGNATURE_VERSION,
303
- daemonCommand,
304
- daemonCommandArgs,
305
- targetEnvPatch,
306
- idleTtlSeconds: parseEnvInteger(process.env.DOER_PLAYWRIGHT_MCP_DAEMON_IDLE_TTL_SECONDS, PLAYWRIGHT_MCP_DAEMON_IDLE_TTL_SECONDS_DEFAULT),
307
- });
308
- const existingMetaRaw = await readFile(paths.metaPath, "utf8").catch(() => "");
309
- let existingMeta = null;
310
- if (existingMetaRaw) {
311
- try {
312
- existingMeta = JSON.parse(existingMetaRaw);
313
- }
314
- catch {
315
- existingMeta = null;
316
- }
317
- }
318
- const existingPid = await readPidFile(paths.pidPath);
319
- if (existingMeta?.signature === signature
320
- && existingPid
321
- && isProcessAlive(existingPid)
322
- && await waitForPlaywrightMcpSocketReady(paths.socketPath, 350)) {
323
- return paths.socketPath;
324
- }
325
- await stopPlaywrightMcpDaemon(paths);
326
- const daemonScriptPath = path.join(AGENT_MODULE_DIR, "playwright-mcp-daemon.ts");
327
- const idleTtlSeconds = String(parseEnvInteger(process.env.DOER_PLAYWRIGHT_MCP_DAEMON_IDLE_TTL_SECONDS, PLAYWRIGHT_MCP_DAEMON_IDLE_TTL_SECONDS_DEFAULT));
328
- const child = spawn(process.execPath, ["--import", "tsx", daemonScriptPath], {
329
- cwd: AGENT_PROJECT_DIR,
330
- detached: true,
331
- stdio: "ignore",
332
- env: {
333
- ...process.env,
334
- DOER_PLAYWRIGHT_MCP_DAEMON_SOCKET: paths.socketPath,
335
- DOER_PLAYWRIGHT_MCP_DAEMON_IDLE_TTL_SECONDS: idleTtlSeconds,
336
- DOER_PLAYWRIGHT_MCP_TARGET_COMMAND: daemonCommand,
337
- DOER_PLAYWRIGHT_MCP_TARGET_ARGS_JSON: JSON.stringify(daemonCommandArgs),
338
- DOER_PLAYWRIGHT_MCP_TARGET_ENV_JSON: JSON.stringify(targetEnvPatch),
339
- },
340
- });
341
- child.unref();
342
- if (!child.pid) {
343
- throw new Error("failed to start playwright mcp daemon: missing pid");
344
- }
345
- await writeFile(paths.pidPath, `${child.pid}\n`, "utf8");
346
- await writeFile(paths.metaPath, `${JSON.stringify({ signature, pid: child.pid, socketPath: paths.socketPath, updatedAt: new Date().toISOString() }, null, 2)}\n`, "utf8");
347
- const ready = await waitForPlaywrightMcpSocketReady(paths.socketPath, 6000);
348
- if (!ready) {
349
- throw new Error(`playwright mcp daemon socket not ready: ${paths.socketPath}`);
350
- }
351
- return paths.socketPath;
352
- }
353
- async function ensureCodexPlaywrightMcpLauncher() {
354
- const browserEnvArgs = [
355
- `PLAYWRIGHT_SKIP_BROWSER_GC=${PLAYWRIGHT_SKIP_BROWSER_GC}`,
356
- ];
357
- const daemonArgsFromEnv = parseEnvStringArray(process.env.DOER_PLAYWRIGHT_MCP_DAEMON_ARGS_JSON);
358
- const [daemonCommandFromArgs, ...daemonArgsRest] = daemonArgsFromEnv;
359
- const daemonCommand = daemonCommandFromArgs || "npx";
360
- let daemonArgs = daemonCommandFromArgs && daemonArgsFromEnv.length > 0
361
- ? daemonArgsRest
362
- : ["-y", "@playwright/mcp"];
363
- const hasBrowserOption = daemonArgs.some((arg) => arg === "--browser" || arg.startsWith("--browser="));
364
- if (arch() === "arm64" && !hasBrowserOption) {
365
- daemonArgs = [...daemonArgs, "--browser", "chromium"];
366
- }
367
- const hasNoSandboxOption = daemonArgs.some((arg) => arg === "--no-sandbox");
368
- if (typeof process.getuid === "function" && process.getuid() === 0 && !hasNoSandboxOption) {
369
- daemonArgs = [...daemonArgs, "--no-sandbox"];
370
- }
371
- const socketPath = await ensureManagedPlaywrightMcpDaemon({
372
- command: daemonCommand,
373
- daemonArgs,
374
- browserEnvArgs,
375
- });
376
- if (!existsSync(PLAYWRIGHT_MCP_PROXY_LAUNCHER_PATH)) {
377
- throw new Error(`playwright mcp proxy launcher script not found: ${PLAYWRIGHT_MCP_PROXY_LAUNCHER_PATH}`);
378
- }
379
- return PLAYWRIGHT_MCP_PROXY_LAUNCHER_PATH;
380
- }
381
- function resolveAgentStateDir() {
382
- return process.env.DOER_AGENT_STATE_DIR?.trim() || path.join(homedir(), ".doer-agent");
383
- }
384
151
  function resolveContainerReachableServerBaseUrl(serverBaseUrl) {
385
152
  return serverBaseUrl;
386
153
  }
@@ -969,11 +736,6 @@ async function runTask(args) {
969
736
  cwd: taskWorkspace,
970
737
  baseEnvPatch: baseTaskEnvPatch,
971
738
  });
972
- const codexMcpEnvPatch = {};
973
- if (process.platform !== "win32") {
974
- await ensureCodexPlaywrightMcpLauncher();
975
- codexMcpEnvPatch.PLAYWRIGHT_SKIP_BROWSER_GC = PLAYWRIGHT_SKIP_BROWSER_GC;
976
- }
977
739
  await recordAgentEvent({ jetstream: args.jetstream,
978
740
  serverBaseUrl: args.serverBaseUrl,
979
741
  taskId: args.taskId,
@@ -1008,7 +770,6 @@ async function runTask(args) {
1008
770
  ...process.env,
1009
771
  ...baseTaskEnvPatch,
1010
772
  ...taskGitEnv.envPatch,
1011
- ...codexMcpEnvPatch,
1012
773
  PATH: taskPath,
1013
774
  DOER_AGENT_TOKEN: args.agentToken,
1014
775
  },
package/package.json CHANGED
@@ -1,12 +1,11 @@
1
1
  {
2
2
  "name": "doer-agent",
3
- "version": "0.1.5",
3
+ "version": "0.1.7",
4
4
  "description": "Reverse-polling agent runtime for doer",
5
5
  "type": "module",
6
6
  "main": "dist/agent.js",
7
7
  "bin": {
8
8
  "doer-agent": "dist/cli.js",
9
- "playwright-mcp-call": "dist/playwright-mcp-call-cli.js",
10
9
  "codex": "dist/codex-cli.js"
11
10
  },
12
11
  "files": [
@@ -18,17 +17,15 @@
18
17
  "access": "public"
19
18
  },
20
19
  "scripts": {
21
- "build": "tsc -p tsconfig.build.json && chmod +x dist/cli.js dist/playwright-mcp-call-cli.js dist/codex-cli.js",
20
+ "build": "tsc -p tsconfig.build.json && chmod +x dist/cli.js dist/codex-cli.js",
22
21
  "start": "node --import tsx src/agent.ts",
23
22
  "start:dist": "node dist/agent.js",
24
23
  "codex": "codex",
25
- "playwright-mcp-call": "node --import tsx src/playwright-mcp-call.ts",
26
24
  "prepublishOnly": "npm run build"
27
25
  },
28
26
  "dependencies": {
29
27
  "@modelcontextprotocol/sdk": "^1.27.1",
30
28
  "@openai/codex-sdk": "^0.115.0",
31
- "@playwright/mcp": "0.0.68",
32
29
  "nats": "^2.29.3"
33
30
  },
34
31
  "devDependencies": {
@@ -1,2 +0,0 @@
1
- #!/usr/bin/env node
2
- import "./playwright-mcp-call.js";
@@ -1,111 +0,0 @@
1
- import { Buffer } from "node:buffer";
2
- import { existsSync } from "node:fs";
3
- import { homedir } from "node:os";
4
- import path from "node:path";
5
- import { Client } from "@modelcontextprotocol/sdk/client/index.js";
6
- import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
7
- function parseArgs(argv) {
8
- const parsed = { tool: "", argsBase64: "" };
9
- for (let i = 0; i < argv.length; i += 1) {
10
- const token = argv[i];
11
- if (token === "--tool") {
12
- parsed.tool = (argv[i + 1] || "").trim();
13
- i += 1;
14
- continue;
15
- }
16
- if (token === "--args-base64") {
17
- parsed.argsBase64 = (argv[i + 1] || "").trim();
18
- i += 1;
19
- }
20
- }
21
- return parsed;
22
- }
23
- function resolveProxyPath() {
24
- const candidates = [
25
- path.join(process.cwd(), "agent/runtime/bin/doer-mcp-proxy"),
26
- path.join(process.cwd(), "runtime/bin/doer-mcp-proxy"),
27
- ];
28
- for (const candidate of candidates) {
29
- if (existsSync(candidate)) {
30
- return candidate;
31
- }
32
- }
33
- return "";
34
- }
35
- function resolveSocketPath() {
36
- const explicit = (process.env.DOER_MCP_SOCKET || "").trim();
37
- if (explicit) {
38
- return explicit;
39
- }
40
- const stateDir = (process.env.DOER_AGENT_STATE_DIR || "").trim() || path.join(homedir(), ".doer-agent");
41
- if (process.platform === "win32") {
42
- const suffix = stateDir
43
- .toLowerCase()
44
- .replace(/[^a-z0-9]+/g, "-")
45
- .replace(/^-+|-+$/g, "")
46
- .slice(-48) || "default";
47
- return `\\\\.\\pipe\\doer-playwright-mcp-${suffix}`;
48
- }
49
- return path.join(stateDir, "playwright-mcp-daemon", "playwright-mcp.sock");
50
- }
51
- function decodeToolArgs(argsBase64) {
52
- if (!argsBase64) {
53
- return {};
54
- }
55
- const raw = Buffer.from(argsBase64, "base64").toString("utf8");
56
- const parsed = JSON.parse(raw);
57
- if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
58
- throw new Error("decoded tool arguments must be a JSON object");
59
- }
60
- return parsed;
61
- }
62
- async function main() {
63
- const { tool, argsBase64 } = parseArgs(process.argv.slice(2));
64
- if (!tool) {
65
- throw new Error("--tool is required");
66
- }
67
- const proxyPath = resolveProxyPath();
68
- if (!proxyPath) {
69
- throw new Error("doer-mcp-proxy binary not found");
70
- }
71
- const socketPath = resolveSocketPath();
72
- const toolArgs = decodeToolArgs(argsBase64);
73
- const transport = new StdioClientTransport({
74
- command: proxyPath,
75
- args: [],
76
- env: {
77
- ...process.env,
78
- DOER_MCP_SOCKET: socketPath,
79
- },
80
- stderr: "pipe",
81
- });
82
- const client = new Client({
83
- name: "doer-agent-playwright-mcp-runner",
84
- version: "0.1.0",
85
- }, {
86
- capabilities: {},
87
- });
88
- try {
89
- await client.connect(transport);
90
- const result = await client.callTool({
91
- name: tool,
92
- arguments: toolArgs,
93
- });
94
- process.stdout.write(`${JSON.stringify({
95
- ok: true,
96
- tool,
97
- content: result.content ?? null,
98
- isError: result.isError === true,
99
- structuredContent: result.structuredContent ?? null,
100
- result,
101
- })}\n`);
102
- }
103
- finally {
104
- await client.close().catch(() => undefined);
105
- }
106
- }
107
- main().catch((error) => {
108
- const message = error instanceof Error ? error.message : String(error);
109
- process.stderr.write(`${JSON.stringify({ ok: false, error: message })}\n`);
110
- process.exit(1);
111
- });
@@ -1,180 +0,0 @@
1
- import { spawn } from "node:child_process";
2
- import { existsSync } from "node:fs";
3
- import { mkdir, rm } from "node:fs/promises";
4
- import net from "node:net";
5
- import path from "node:path";
6
- function parseInteger(value, fallback) {
7
- const normalized = value?.trim();
8
- if (!normalized) {
9
- return fallback;
10
- }
11
- const parsed = Number.parseInt(normalized, 10);
12
- return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
13
- }
14
- const socketPath = process.env.DOER_PLAYWRIGHT_MCP_DAEMON_SOCKET?.trim() || "";
15
- const targetCommand = process.env.DOER_PLAYWRIGHT_MCP_TARGET_COMMAND?.trim() || "";
16
- const idleTtlSeconds = parseInteger(process.env.DOER_PLAYWRIGHT_MCP_DAEMON_IDLE_TTL_SECONDS, 10800);
17
- function isWindowsNamedPipePath(value) {
18
- return process.platform === "win32" && value.startsWith("\\\\.\\pipe\\");
19
- }
20
- let targetArgs = [];
21
- try {
22
- const parsed = JSON.parse(process.env.DOER_PLAYWRIGHT_MCP_TARGET_ARGS_JSON || "[]");
23
- if (Array.isArray(parsed) && parsed.every((item) => typeof item === "string")) {
24
- targetArgs = parsed;
25
- }
26
- }
27
- catch {
28
- targetArgs = [];
29
- }
30
- let targetEnvPatch = {};
31
- try {
32
- const parsed = JSON.parse(process.env.DOER_PLAYWRIGHT_MCP_TARGET_ENV_JSON || "{}");
33
- if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
34
- targetEnvPatch = Object.fromEntries(Object.entries(parsed).flatMap(([key, value]) => (typeof value === "string" ? [[key, value]] : [])));
35
- }
36
- }
37
- catch {
38
- targetEnvPatch = {};
39
- }
40
- if (!socketPath) {
41
- process.stderr.write("playwright-mcp-daemon error: DOER_PLAYWRIGHT_MCP_DAEMON_SOCKET is required\n");
42
- process.exit(1);
43
- }
44
- if (!targetCommand) {
45
- process.stderr.write("playwright-mcp-daemon error: DOER_PLAYWRIGHT_MCP_TARGET_COMMAND is required\n");
46
- process.exit(1);
47
- }
48
- let child = null;
49
- let activeClient = null;
50
- let shuttingDown = false;
51
- let lastActivityAt = Date.now();
52
- const idleTtlMs = idleTtlSeconds * 1000;
53
- function markActivity() {
54
- lastActivityAt = Date.now();
55
- }
56
- function spawnTargetIfNeeded() {
57
- if (child) {
58
- return;
59
- }
60
- child = spawn(targetCommand, targetArgs, {
61
- stdio: ["pipe", "pipe", "pipe"],
62
- env: { ...process.env, ...targetEnvPatch },
63
- });
64
- child.stdout.on("data", (chunk) => {
65
- markActivity();
66
- if (activeClient && !activeClient.destroyed) {
67
- activeClient.write(chunk);
68
- }
69
- });
70
- child.stderr.on("data", (chunk) => {
71
- process.stderr.write(chunk);
72
- });
73
- child.on("exit", () => {
74
- child = null;
75
- if (activeClient && !activeClient.destroyed) {
76
- activeClient.end();
77
- activeClient = null;
78
- }
79
- markActivity();
80
- });
81
- }
82
- async function shutdown(server) {
83
- if (shuttingDown) {
84
- return;
85
- }
86
- shuttingDown = true;
87
- if (activeClient && !activeClient.destroyed) {
88
- activeClient.destroy();
89
- activeClient = null;
90
- }
91
- if (child) {
92
- child.kill("SIGTERM");
93
- await new Promise((resolve) => {
94
- const timeout = setTimeout(() => {
95
- if (child) {
96
- child.kill("SIGKILL");
97
- }
98
- resolve();
99
- }, 2000);
100
- timeout.unref?.();
101
- child?.once("exit", () => {
102
- clearTimeout(timeout);
103
- resolve();
104
- });
105
- });
106
- child = null;
107
- }
108
- await new Promise((resolve) => server.close(() => resolve()));
109
- if (!isWindowsNamedPipePath(socketPath) && existsSync(socketPath)) {
110
- await rm(socketPath, { force: true });
111
- }
112
- process.exit(0);
113
- }
114
- async function main() {
115
- if (!isWindowsNamedPipePath(socketPath)) {
116
- await mkdir(path.dirname(socketPath), { recursive: true });
117
- if (existsSync(socketPath)) {
118
- await rm(socketPath, { force: true });
119
- }
120
- }
121
- const server = net.createServer((socket) => {
122
- if (activeClient && !activeClient.destroyed) {
123
- socket.end();
124
- return;
125
- }
126
- activeClient = socket;
127
- markActivity();
128
- spawnTargetIfNeeded();
129
- socket.on("data", (chunk) => {
130
- markActivity();
131
- spawnTargetIfNeeded();
132
- if (child && !child.killed) {
133
- child.stdin.write(chunk);
134
- }
135
- });
136
- socket.on("close", () => {
137
- if (activeClient === socket) {
138
- activeClient = null;
139
- }
140
- markActivity();
141
- });
142
- socket.on("error", () => {
143
- if (activeClient === socket) {
144
- activeClient = null;
145
- }
146
- markActivity();
147
- });
148
- });
149
- server.on("error", (error) => {
150
- process.stderr.write(`playwright-mcp-daemon error: ${error instanceof Error ? error.message : String(error)}\n`);
151
- process.exit(1);
152
- });
153
- await new Promise((resolve, reject) => {
154
- server.once("error", reject);
155
- server.listen(socketPath, () => {
156
- server.off("error", reject);
157
- resolve();
158
- });
159
- });
160
- const interval = setInterval(() => {
161
- if (shuttingDown) {
162
- return;
163
- }
164
- if (activeClient && !activeClient.destroyed) {
165
- return;
166
- }
167
- if (Date.now() - lastActivityAt < idleTtlMs) {
168
- return;
169
- }
170
- void shutdown(server);
171
- }, 30000);
172
- interval.unref?.();
173
- process.on("SIGTERM", () => {
174
- void shutdown(server);
175
- });
176
- process.on("SIGINT", () => {
177
- void shutdown(server);
178
- });
179
- }
180
- void main();
@@ -1,15 +0,0 @@
1
- #!/bin/sh
2
- set -eu
3
- SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
4
- if [ -z "${DOER_MCP_SOCKET:-}" ]; then
5
- if [ -n "${DOER_AGENT_STATE_DIR:-}" ]; then
6
- DOER_MCP_SOCKET="$DOER_AGENT_STATE_DIR/playwright-mcp-daemon/playwright-mcp.sock"
7
- elif [ -n "${HOME:-}" ]; then
8
- DOER_MCP_SOCKET="$HOME/.doer-agent/playwright-mcp-daemon/playwright-mcp.sock"
9
- else
10
- echo "playwright-mcp-proxy-launcher.sh: DOER_MCP_SOCKET is not set and neither DOER_AGENT_STATE_DIR nor HOME is available" >&2
11
- exit 1
12
- fi
13
- fi
14
- export DOER_MCP_SOCKET
15
- exec "$SCRIPT_DIR/doer-mcp-proxy" "$@"