doer-agent 0.1.7 → 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
- import { existsSync } from "node:fs";
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";
@@ -509,6 +510,30 @@ function resolveShellPath() {
509
510
  }
510
511
  throw new Error("No shell executable found. Set SHELL env or install /bin/sh (or bash).");
511
512
  }
513
+ function resolveTaskWorkspace(rawCwd) {
514
+ const workspaceRoot = workspaceRootOverride ?? (process.env.WORKSPACE?.trim() || process.cwd());
515
+ const requestedCwd = rawCwd?.trim() || "";
516
+ const resolvedCwd = requestedCwd
517
+ ? path.isAbsolute(requestedCwd)
518
+ ? path.resolve(requestedCwd)
519
+ : path.resolve(workspaceRoot, requestedCwd)
520
+ : workspaceRoot;
521
+ if (!existsSync(resolvedCwd)) {
522
+ throw new Error(`Invalid cwd: ${requestedCwd || "(empty)"} resolved to ${resolvedCwd} (path does not exist)`);
523
+ }
524
+ let stats;
525
+ try {
526
+ stats = statSync(resolvedCwd);
527
+ }
528
+ catch (error) {
529
+ const message = error instanceof Error ? error.message : String(error);
530
+ throw new Error(`Invalid cwd: ${requestedCwd || "(empty)"} resolved to ${resolvedCwd} (${message})`);
531
+ }
532
+ if (!stats.isDirectory()) {
533
+ throw new Error(`Invalid cwd: ${requestedCwd || "(empty)"} resolved to ${resolvedCwd} (not a directory)`);
534
+ }
535
+ return resolvedCwd;
536
+ }
512
537
  async function postJson(url, body) {
513
538
  const res = await fetch(url, {
514
539
  method: "POST",
@@ -714,6 +739,7 @@ async function runTask(args) {
714
739
  userId: args.userId,
715
740
  };
716
741
  const shellPath = resolveShellPath();
742
+ const taskWorkspace = resolveTaskWorkspace(args.cwd);
717
743
  const runtimeConfig = await prepareTaskRuntimeConfig({
718
744
  serverBaseUrl: args.serverBaseUrl,
719
745
  taskId: args.taskId,
@@ -726,7 +752,6 @@ async function runTask(args) {
726
752
  userId: args.userId,
727
753
  agentToken: args.agentToken,
728
754
  });
729
- const taskWorkspace = args.cwd || process.env.WORKSPACE?.trim() || process.cwd();
730
755
  const baseTaskEnvPatch = {
731
756
  ...(runtimeConfig?.envPatch ?? {}),
732
757
  ...(codexAuth?.envPatch ?? {}),
@@ -747,7 +772,8 @@ async function runTask(args) {
747
772
  pid: process.pid,
748
773
  startedAt: formatLocalTimestamp(),
749
774
  command: args.command,
750
- cwd: args.cwd,
775
+ cwd: taskWorkspace,
776
+ requestedCwd: args.cwd,
751
777
  shell: shellPath,
752
778
  ...(runtimeConfig?.meta ?? { runtimeConfigSynced: false }),
753
779
  ...(codexAuth?.meta ?? { codexAuthSynced: false }),
@@ -763,7 +789,7 @@ async function runTask(args) {
763
789
  const runtimeBinPath = path.join(AGENT_PROJECT_DIR, "runtime/bin");
764
790
  const taskPath = [runtimeBinPath, process.env.PATH || ""].filter(Boolean).join(path.delimiter);
765
791
  const child = spawn(args.command, {
766
- cwd: args.cwd || process.cwd(),
792
+ cwd: taskWorkspace,
767
793
  shell: shellPath,
768
794
  detached: process.platform !== "win32",
769
795
  env: {
@@ -934,9 +960,9 @@ async function connectBootstrapWithRetry(args) {
934
960
  async function main() {
935
961
  const args = parseArgs(process.argv.slice(2));
936
962
  const workspaceDir = resolveArgOrEnv(args, ["workspace-dir", "workspaceDir"], ["WORKSPACE"]);
937
- if (workspaceDir) {
938
- process.chdir(path.resolve(workspaceDir));
939
- }
963
+ const startupWorkspaceRoot = path.resolve(workspaceDir || process.cwd());
964
+ workspaceRootOverride = startupWorkspaceRoot;
965
+ process.chdir(startupWorkspaceRoot);
940
966
  const serverBaseUrlRaw = resolveArgOrEnv(args, ["server", "url"], ["DOER_AGENT_SERVER"], DEFAULT_SERVER_BASE_URL);
941
967
  const requestedServerBaseUrl = serverBaseUrlRaw.replace(/\/$/, "");
942
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.7",
3
+ "version": "0.1.9",
4
4
  "description": "Reverse-polling agent runtime for doer",
5
5
  "type": "module",
6
6
  "main": "dist/agent.js",