doer-agent 0.1.8 → 0.1.9

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.js CHANGED
@@ -1,7 +1,6 @@
1
1
  import { spawn, spawnSync } from "node:child_process";
2
2
  import { existsSync, statSync } from "node:fs";
3
3
  import { chmod, mkdir, readFile, writeFile } from "node:fs/promises";
4
- import { homedir } from "node:os";
5
4
  import path from "node:path";
6
5
  import { fileURLToPath } from "node:url";
7
6
  import { AckPolicy, connect, DeliverPolicy, JSONCodec, RetentionPolicy, StorageType } from "nats";
@@ -11,6 +10,7 @@ const AGENT_PROJECT_DIR = path.join(AGENT_MODULE_DIR, "..");
11
10
  const AGENT_PACKAGE_JSON_PATH = path.join(AGENT_PROJECT_DIR, "package.json");
12
11
  let activeTaskLogContext = null;
13
12
  const activeTaskCancelRequests = new Map();
13
+ let workspaceRootOverride = null;
14
14
  function sanitizeUserId(userId) {
15
15
  const normalized = userId.trim().replace(/[^a-zA-Z0-9_-]/g, "_");
16
16
  return normalized.length > 0 ? normalized : "anonymous";
@@ -135,7 +135,8 @@ async function initJetStreamContext(args) {
135
135
  };
136
136
  }
137
137
  function resolveCodexHomePath() {
138
- return path.join(homedir(), ".codex");
138
+ const workspaceRoot = workspaceRootOverride ?? (process.env.WORKSPACE?.trim() || process.cwd());
139
+ return path.join(workspaceRoot, ".codex");
139
140
  }
140
141
  function parseEnvBoolean(value) {
141
142
  return value?.trim().toLowerCase() === "true";
@@ -510,7 +511,7 @@ function resolveShellPath() {
510
511
  throw new Error("No shell executable found. Set SHELL env or install /bin/sh (or bash).");
511
512
  }
512
513
  function resolveTaskWorkspace(rawCwd) {
513
- const workspaceRoot = process.env.WORKSPACE?.trim() || process.cwd();
514
+ const workspaceRoot = workspaceRootOverride ?? (process.env.WORKSPACE?.trim() || process.cwd());
514
515
  const requestedCwd = rawCwd?.trim() || "";
515
516
  const resolvedCwd = requestedCwd
516
517
  ? path.isAbsolute(requestedCwd)
@@ -959,9 +960,9 @@ async function connectBootstrapWithRetry(args) {
959
960
  async function main() {
960
961
  const args = parseArgs(process.argv.slice(2));
961
962
  const workspaceDir = resolveArgOrEnv(args, ["workspace-dir", "workspaceDir"], ["WORKSPACE"]);
962
- if (workspaceDir) {
963
- process.chdir(path.resolve(workspaceDir));
964
- }
963
+ const startupWorkspaceRoot = path.resolve(workspaceDir || process.cwd());
964
+ workspaceRootOverride = startupWorkspaceRoot;
965
+ process.chdir(startupWorkspaceRoot);
965
966
  const serverBaseUrlRaw = resolveArgOrEnv(args, ["server", "url"], ["DOER_AGENT_SERVER"], DEFAULT_SERVER_BASE_URL);
966
967
  const requestedServerBaseUrl = serverBaseUrlRaw.replace(/\/$/, "");
967
968
  const serverBaseUrl = resolveContainerReachableServerBaseUrl(requestedServerBaseUrl);
@@ -0,0 +1,199 @@
1
+ #!/usr/bin/env node
2
+ import { Buffer } from "node:buffer";
3
+ import { mkdir } from "node:fs/promises";
4
+ import path from "node:path";
5
+ import { chromium } from "playwright-core";
6
+ function printHelp() {
7
+ console.log(`playwright-cdp
8
+
9
+ Usage:
10
+ PLAYWRIGHT_CDP_ENDPOINT=http://127.0.0.1:9222 \
11
+ playwright-cdp --tool <tool> --args-base64 <base64>
12
+
13
+ playwright-cdp \
14
+ --endpoint http://127.0.0.1:9222 \
15
+ --tool browser_navigate \
16
+ --args '{"url":"https://example.com"}'
17
+
18
+ Tools:
19
+ browser_list_tabs
20
+ browser_navigate
21
+ browser_click
22
+ browser_type
23
+ browser_screenshot
24
+ browser_get_html
25
+ browser_get_text
26
+ browser_eval
27
+ `);
28
+ }
29
+ function parseArgs(argv) {
30
+ const options = {};
31
+ for (let i = 0; i < argv.length; i += 1) {
32
+ const token = argv[i];
33
+ if (!token.startsWith("--"))
34
+ continue;
35
+ const key = token.slice(2);
36
+ const next = argv[i + 1];
37
+ if (!next || next.startsWith("--")) {
38
+ options[key] = true;
39
+ continue;
40
+ }
41
+ options[key] = next;
42
+ i += 1;
43
+ }
44
+ return options;
45
+ }
46
+ function requiredOption(options, key) {
47
+ const value = options[key];
48
+ if (typeof value !== "string" || !value.trim()) {
49
+ throw new Error(`--${key} is required`);
50
+ }
51
+ return value.trim();
52
+ }
53
+ function optionalOption(options, key) {
54
+ const value = options[key];
55
+ return typeof value === "string" && value.trim() ? value.trim() : null;
56
+ }
57
+ function readEndpoint(options) {
58
+ return optionalOption(options, "endpoint")
59
+ ?? process.env.PLAYWRIGHT_CDP_ENDPOINT
60
+ ?? process.env.CDP_ENDPOINT
61
+ ?? "";
62
+ }
63
+ function readTimeout(options) {
64
+ const raw = optionalOption(options, "timeout");
65
+ if (!raw)
66
+ return 10000;
67
+ const value = Number.parseInt(raw, 10);
68
+ if (Number.isNaN(value) || value < 0) {
69
+ throw new Error("--timeout must be a non-negative integer");
70
+ }
71
+ return value;
72
+ }
73
+ function readPayload(options) {
74
+ const argsJson = optionalOption(options, "args");
75
+ const argsBase64 = optionalOption(options, "args-base64");
76
+ if (!argsJson && !argsBase64) {
77
+ throw new Error("--args or --args-base64 is required");
78
+ }
79
+ const raw = argsJson ?? Buffer.from(argsBase64, "base64").toString("utf8");
80
+ const parsed = JSON.parse(raw);
81
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
82
+ throw new Error("tool args must decode to a JSON object");
83
+ }
84
+ return parsed;
85
+ }
86
+ function asString(value) {
87
+ return typeof value === "string" && value.trim() ? value.trim() : null;
88
+ }
89
+ function asBoolean(value) {
90
+ return value === true || value === "true";
91
+ }
92
+ function selectorFrom(args) {
93
+ return asString(args.selector)
94
+ ?? asString(args.locator)
95
+ ?? asString(args.query)
96
+ ?? (() => { throw new Error("selector is required in args"); })();
97
+ }
98
+ async function resolvePage(browser, args) {
99
+ const contexts = browser.contexts();
100
+ const context = contexts[0] ?? (await browser.newContext());
101
+ if (asBoolean(args.newPage)) {
102
+ return context.newPage();
103
+ }
104
+ const pageIndexRaw = args.pageIndex;
105
+ const pageIndex = typeof pageIndexRaw === "number" ? pageIndexRaw : 0;
106
+ const pages = context.pages();
107
+ return pages[pageIndex] ?? pages[0] ?? context.newPage();
108
+ }
109
+ async function main() {
110
+ const options = parseArgs(process.argv.slice(2));
111
+ if (options.help === true || options.h === true) {
112
+ printHelp();
113
+ return;
114
+ }
115
+ const endpoint = readEndpoint(options);
116
+ if (!endpoint) {
117
+ throw new Error("CDP endpoint is required via --endpoint or PLAYWRIGHT_CDP_ENDPOINT");
118
+ }
119
+ const tool = requiredOption(options, "tool");
120
+ const args = readPayload(options);
121
+ const timeout = readTimeout(options);
122
+ const browser = await chromium.connectOverCDP(endpoint, { timeout });
123
+ try {
124
+ if (tool === "browser_list_tabs") {
125
+ const payload = browser.contexts().map((context, contextIndex) => ({
126
+ contextIndex,
127
+ pages: context.pages().map((page, pageIndex) => ({
128
+ pageIndex,
129
+ url: page.url(),
130
+ })),
131
+ }));
132
+ console.log(JSON.stringify({ tool, result: payload }, null, 2));
133
+ return;
134
+ }
135
+ const page = await resolvePage(browser, args);
136
+ switch (tool) {
137
+ case "browser_navigate": {
138
+ const url = asString(args.url);
139
+ if (!url)
140
+ throw new Error("url is required in args");
141
+ await page.goto(url, { waitUntil: "domcontentloaded", timeout });
142
+ console.log(JSON.stringify({ tool, url: page.url(), title: await page.title() }, null, 2));
143
+ return;
144
+ }
145
+ case "browser_click": {
146
+ const selector = selectorFrom(args);
147
+ await page.locator(selector).click({ timeout });
148
+ console.log(JSON.stringify({ tool, selector, url: page.url() }, null, 2));
149
+ return;
150
+ }
151
+ case "browser_type": {
152
+ const selector = selectorFrom(args);
153
+ const text = asString(args.text) ?? asString(args.value);
154
+ if (!text)
155
+ throw new Error("text is required in args");
156
+ await page.locator(selector).fill(text, { timeout });
157
+ console.log(JSON.stringify({ tool, selector, length: text.length }, null, 2));
158
+ return;
159
+ }
160
+ case "browser_screenshot": {
161
+ const out = asString(args.path) ?? asString(args.out) ?? path.join("artifacts", `playwright-cdp-${Date.now()}.png`);
162
+ await mkdir(path.dirname(out), { recursive: true });
163
+ await page.screenshot({ path: out, fullPage: true });
164
+ console.log(JSON.stringify({ tool, path: path.resolve(out), url: page.url() }, null, 2));
165
+ return;
166
+ }
167
+ case "browser_get_html": {
168
+ const selector = asString(args.selector) ?? asString(args.locator);
169
+ const html = selector ? await page.locator(selector).innerHTML({ timeout }) : await page.content();
170
+ console.log(html);
171
+ return;
172
+ }
173
+ case "browser_get_text": {
174
+ const selector = asString(args.selector) ?? asString(args.locator) ?? "body";
175
+ const text = await page.locator(selector).innerText({ timeout });
176
+ console.log(text);
177
+ return;
178
+ }
179
+ case "browser_eval": {
180
+ const expression = asString(args.expression) ?? asString(args.expr);
181
+ if (!expression)
182
+ throw new Error("expression is required in args");
183
+ const value = await page.evaluate((source) => globalThis.eval(source), expression);
184
+ console.log(JSON.stringify({ tool, value }, null, 2));
185
+ return;
186
+ }
187
+ default:
188
+ throw new Error(`unsupported tool: ${tool}`);
189
+ }
190
+ }
191
+ finally {
192
+ await browser.close();
193
+ }
194
+ }
195
+ main().catch((error) => {
196
+ const message = error instanceof Error ? error.message : String(error);
197
+ console.error(message);
198
+ process.exit(1);
199
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "doer-agent",
3
- "version": "0.1.8",
3
+ "version": "0.1.9",
4
4
  "description": "Reverse-polling agent runtime for doer",
5
5
  "type": "module",
6
6
  "main": "dist/agent.js",