@duckmind/dm-windows-x64 0.61.9 → 0.62.3
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/extensions/.dm-extensions.json +5 -5
- package/extensions/dm-cua/bin/browser-cua.mjs +6 -1
- package/extensions/dm-cua/index.js +3 -2
- package/extensions/dm-cua/src/browser-cua-lib.mjs +69 -13
- package/extensions/greedysearch-dm/bin/cdp.mjs +28 -3
- package/extensions/greedysearch-dm/bin/launch.mjs +2 -3
- package/extensions/greedysearch-dm/src/search/constants.mjs +5 -2
- package/extensions/greedysearch-dm/src/search/research.mjs +1 -1
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"status": "ok",
|
|
3
|
-
"prepared_at": "2026-08-
|
|
3
|
+
"prepared_at": "2026-08-30T20:04:04.470932+00:00",
|
|
4
4
|
"managed_entries": [
|
|
5
5
|
{
|
|
6
6
|
"id": "dm-context",
|
|
@@ -518,8 +518,8 @@
|
|
|
518
518
|
"src/utils/system-cmds.mjs"
|
|
519
519
|
],
|
|
520
520
|
"file_count": 62,
|
|
521
|
-
"before_bytes":
|
|
522
|
-
"after_bytes":
|
|
521
|
+
"before_bytes": 613697,
|
|
522
|
+
"after_bytes": 496359,
|
|
523
523
|
"runtime_specifier_rewrites": 0,
|
|
524
524
|
"runtime_filename_rewrites": 8,
|
|
525
525
|
"declaration_files_removed": 0
|
|
@@ -764,8 +764,8 @@
|
|
|
764
764
|
"src/browser-install.mjs"
|
|
765
765
|
],
|
|
766
766
|
"file_count": 4,
|
|
767
|
-
"before_bytes":
|
|
768
|
-
"after_bytes":
|
|
767
|
+
"before_bytes": 38392,
|
|
768
|
+
"after_bytes": 38745,
|
|
769
769
|
"runtime_specifier_rewrites": 0,
|
|
770
770
|
"runtime_filename_rewrites": 0,
|
|
771
771
|
"declaration_files_removed": 0
|
|
@@ -11,11 +11,16 @@ Usage:
|
|
|
11
11
|
node browser-cua.mjs click_xy --x <num> --y <num> [--tab <prefix>] [--json]
|
|
12
12
|
node browser-cua.mjs type --text <text> [--tab <prefix>] [--json]
|
|
13
13
|
node browser-cua.mjs evaluate --expression <js> [--tab <prefix>] [--json]
|
|
14
|
+
node browser-cua.mjs handoff [--tab <prefix>] [--json]
|
|
14
15
|
node browser-cua.mjs stop [--json]
|
|
15
16
|
|
|
16
17
|
Notes:
|
|
17
18
|
- This helper reuses DM's bundled greedysearch-dm CDP sidecar.
|
|
18
|
-
|
|
19
|
+
- Fresh dedicated browser profiles auto-bootstrap a blank tab.
|
|
20
|
+
- handoff opens the dedicated browser visibly for user-authorized login or
|
|
21
|
+
verification. DM does not solve challenges or enter credentials for you.
|
|
22
|
+
- Profiles persist by project under ~/.dm/agent/cua/profiles by default;
|
|
23
|
+
set DM_CUA_EPHEMERAL=1 for a disposable profile.
|
|
19
24
|
- Default browser is Chrome/Chromium. If absent, DM attempts a non-interactive
|
|
20
25
|
package-manager install; set DM_CUA_AUTO_INSTALL=0 to opt out.
|
|
21
26
|
- To try CloakBrowser, set DM_CUA_BROWSER=cloak
|
|
@@ -15,9 +15,10 @@ const browserActionSchema = Type.Object({
|
|
|
15
15
|
Type.Literal("click_xy"),
|
|
16
16
|
Type.Literal("type"),
|
|
17
17
|
Type.Literal("evaluate"),
|
|
18
|
+
Type.Literal("handoff"),
|
|
18
19
|
Type.Literal("stop")
|
|
19
20
|
], {
|
|
20
|
-
description: "Browser action. Start with list, snapshot, or screenshot before mutating actions on unfamiliar pages."
|
|
21
|
+
description: "Browser action. Start with list, snapshot, or screenshot before mutating actions on unfamiliar pages. Use handoff to show the retained DM browser window for user-authorized login or verification."
|
|
21
22
|
}),
|
|
22
23
|
tab: Type.Optional(Type.String({
|
|
23
24
|
description: "Optional target tab prefix from browser_cua(list). If omitted, the first live page in the dedicated profile is used."
|
|
@@ -41,7 +42,7 @@ export default function registerDmCua(pi) {
|
|
|
41
42
|
pi.registerTool({
|
|
42
43
|
name: "browser_cua",
|
|
43
44
|
label: "Browser CUA",
|
|
44
|
-
description: "Drive a dedicated Chrome browser profile through DM's bundled browser CUA lane. " + "If Chrome/Chromium is absent, DM attempts a non-interactive OS package-manager install before launch. " + "Inspect first with list, snapshot, or screenshot. Fresh empty profiles auto-bootstrap a blank tab. " + "Use navigate for deterministic URLs, click with a standard CSS selector/click_xy/type for page interaction, evaluate for focused DOM reads or text matching, and stop to close the dedicated browser lane.",
|
|
45
|
+
description: "Drive a dedicated Chrome browser profile through DM's bundled browser CUA lane. " + "If Chrome/Chromium is absent, DM attempts a non-interactive OS package-manager install before launch. " + "Inspect first with list, snapshot, or screenshot. Fresh empty profiles auto-bootstrap a blank tab. " + "Use handoff when a site needs you to complete a login or verification in the visible browser; DM never solves challenges or enters credentials. " + "Use navigate for deterministic URLs, click with a standard CSS selector/click_xy/type for page interaction, evaluate for focused DOM reads or text matching, and stop to close the dedicated browser lane.",
|
|
45
46
|
promptSnippet: "First-class browser CUA for Chrome with automatic browser bootstrap. Inspect before acting. Screenshot complex pages before click/scroll loops.",
|
|
46
47
|
parameters: browserActionSchema,
|
|
47
48
|
async execute(_toolCallId, params) {
|
|
@@ -44,7 +44,8 @@ export function resolveBrowserRuntimeConfig(env = process.env, scope = process.c
|
|
|
44
44
|
const browserExecutablePath = resolveBrowserExecutablePath(env, browserKind);
|
|
45
45
|
const port = parsePort(env.DM_CUA_PORT) ?? parsePort(env.GREEDY_SEARCH_PORT) ?? DEFAULT_CUA_PORT_BASE + Number.parseInt(scopeHash.slice(0, 8), 16) % DEFAULT_CUA_PORT_RANGE;
|
|
46
46
|
const profileSuffix = browserKind === "chrome" ? "chrome" : browserKind;
|
|
47
|
-
const
|
|
47
|
+
const defaultProfileDir = env.DM_CUA_EPHEMERAL === "1" ? join(tmpdir(), `dm-cua-${profileSuffix}-profile-${instanceId}`) : join(resolveAgentDir(env), "cua", "profiles", `${profileSuffix}-${scopeHash}`);
|
|
48
|
+
const profileDir = resolve(env.DM_CUA_PROFILE_DIR || defaultProfileDir);
|
|
48
49
|
const socketBaseDir = platform() === "win32" ? tmpdir() : "/tmp";
|
|
49
50
|
return {
|
|
50
51
|
instanceId,
|
|
@@ -133,8 +134,8 @@ export function resolveBrowserHelperPaths() {
|
|
|
133
134
|
}
|
|
134
135
|
return candidates[0];
|
|
135
136
|
}
|
|
136
|
-
export function resolveBrowserProcessEnv(config = BROWSER_CONFIG, env = process.env) {
|
|
137
|
-
const
|
|
137
|
+
export function resolveBrowserProcessEnv(config = BROWSER_CONFIG, env = process.env, { visible } = {}) {
|
|
138
|
+
const visibleMode = visible === true ? "1" : visible === false ? "0" : env.GREEDY_SEARCH_VISIBLE ?? env.DM_CUA_VISIBLE ?? env.DM_BROWSER_CUA_VISIBLE;
|
|
138
139
|
return {
|
|
139
140
|
...env,
|
|
140
141
|
...config.browserExecutablePath ? { CHROME_PATH: config.browserExecutablePath } : {},
|
|
@@ -147,11 +148,11 @@ export function resolveBrowserProcessEnv(config = BROWSER_CONFIG, env = process.
|
|
|
147
148
|
GREEDY_SEARCH_PID_FILE: config.pidFile.replace(/\\/g, "/"),
|
|
148
149
|
GREEDY_SEARCH_MODE_FILE: config.modeFile.replace(/\\/g, "/"),
|
|
149
150
|
GREEDY_SEARCH_ALLOW_PORT_CLEANUP: env.GREEDY_SEARCH_ALLOW_PORT_CLEANUP ?? "0",
|
|
150
|
-
...
|
|
151
|
+
...visibleMode === undefined ? {} : { GREEDY_SEARCH_VISIBLE: visibleMode }
|
|
151
152
|
};
|
|
152
153
|
}
|
|
153
|
-
function browserEnv() {
|
|
154
|
-
return resolveBrowserProcessEnv(BROWSER_CONFIG, process.env);
|
|
154
|
+
function browserEnv(options) {
|
|
155
|
+
return resolveBrowserProcessEnv(BROWSER_CONFIG, process.env, options);
|
|
155
156
|
}
|
|
156
157
|
function looksLikeUrl(value) {
|
|
157
158
|
return URLISH_PATTERN.test(value);
|
|
@@ -184,8 +185,8 @@ export function validateBrowserSelector(selector) {
|
|
|
184
185
|
}
|
|
185
186
|
return value;
|
|
186
187
|
}
|
|
187
|
-
function resolveAgentDir() {
|
|
188
|
-
const explicit =
|
|
188
|
+
function resolveAgentDir(env = process.env) {
|
|
189
|
+
const explicit = env.DM_CODING_AGENT_DIR || env.PI_CODING_AGENT_DIR;
|
|
189
190
|
return explicit ? resolve(explicit) : join(homedir(), ".dm", "agent");
|
|
190
191
|
}
|
|
191
192
|
function defaultScreenshotPath() {
|
|
@@ -269,7 +270,10 @@ async function createBlankTarget(url = "about:blank") {
|
|
|
269
270
|
}
|
|
270
271
|
async function runCdp(args, options = {}) {
|
|
271
272
|
const { cdpScript } = resolveBrowserHelperPaths();
|
|
272
|
-
return runNodeScript(cdpScript, args, {
|
|
273
|
+
return runNodeScript(cdpScript, args, {
|
|
274
|
+
env: browserEnv({ visible: options.visible }),
|
|
275
|
+
timeoutMs: options.timeoutMs ?? DEFAULT_TIMEOUT_MS
|
|
276
|
+
});
|
|
273
277
|
}
|
|
274
278
|
async function listPages() {
|
|
275
279
|
const result = await runCdp(["list"]);
|
|
@@ -288,10 +292,13 @@ async function ensureBrowserHelpersExist() {
|
|
|
288
292
|
}
|
|
289
293
|
return { launchScript, cdpScript };
|
|
290
294
|
}
|
|
291
|
-
export async function ensureBrowserReady() {
|
|
295
|
+
export async function ensureBrowserReady({ visible = false } = {}) {
|
|
292
296
|
const { launchScript } = await ensureBrowserHelpersExist();
|
|
293
297
|
await ensureConfiguredBrowserSelectionUsable();
|
|
294
|
-
await runNodeScript(launchScript, [], {
|
|
298
|
+
await runNodeScript(launchScript, [], {
|
|
299
|
+
env: browserEnv({ visible }),
|
|
300
|
+
timeoutMs: DEFAULT_TIMEOUT_MS
|
|
301
|
+
});
|
|
295
302
|
let pages = await listPages();
|
|
296
303
|
if (pages.length === 0) {
|
|
297
304
|
await createBlankTarget("about:blank");
|
|
@@ -310,6 +317,17 @@ async function resolveTabContext(tabPrefix) {
|
|
|
310
317
|
}
|
|
311
318
|
return { tab: page.target, page, pages };
|
|
312
319
|
}
|
|
320
|
+
export function classifyHumanInteraction(value) {
|
|
321
|
+
const text = String(value ?? "");
|
|
322
|
+
const match = text.match(/cloudflare|turnstile|recaptcha|hcaptcha|aws waf|geetest|verify (?:you are )?human|just a moment|checking your browser|login required|sign[ -]?in required|authentication required/i);
|
|
323
|
+
return match ? {
|
|
324
|
+
needed: true,
|
|
325
|
+
reason: match[0]
|
|
326
|
+
} : {
|
|
327
|
+
needed: false,
|
|
328
|
+
reason: null
|
|
329
|
+
};
|
|
330
|
+
}
|
|
313
331
|
export async function runBrowserAction({
|
|
314
332
|
action,
|
|
315
333
|
tab,
|
|
@@ -347,6 +365,7 @@ export async function runBrowserAction({
|
|
|
347
365
|
case "snapshot": {
|
|
348
366
|
const context = await resolveTabContext(tab);
|
|
349
367
|
const result = await runCdp(["snap", context.tab]);
|
|
368
|
+
const snapshot = result.stdout.trim();
|
|
350
369
|
return {
|
|
351
370
|
status: "ok",
|
|
352
371
|
action,
|
|
@@ -354,7 +373,33 @@ export async function runBrowserAction({
|
|
|
354
373
|
tab: context.tab,
|
|
355
374
|
title: context.page?.title || "",
|
|
356
375
|
url: context.page?.url || "",
|
|
357
|
-
snapshot
|
|
376
|
+
snapshot,
|
|
377
|
+
humanInteraction: classifyHumanInteraction(`${context.page?.title || ""}
|
|
378
|
+
${context.page?.url || ""}
|
|
379
|
+
${snapshot}`)
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
case "handoff": {
|
|
383
|
+
try {
|
|
384
|
+
await runCdp(["stop"]);
|
|
385
|
+
} catch {}
|
|
386
|
+
const pages = await ensureBrowserReady({ visible: true });
|
|
387
|
+
const page = findPage(pages, tab);
|
|
388
|
+
if (!page) {
|
|
389
|
+
throw new Error(tab ? `No browser tab matching prefix "${tab}". Run browser_cua(list) first.` : "No browser tab available after browser bootstrap.");
|
|
390
|
+
}
|
|
391
|
+
const snapshot = await runCdp(["snap", page.target], { visible: true }).then((result) => result.stdout.trim());
|
|
392
|
+
return {
|
|
393
|
+
status: "ok",
|
|
394
|
+
action,
|
|
395
|
+
browser: browserSummary(),
|
|
396
|
+
tab: page.target,
|
|
397
|
+
title: page.title || "",
|
|
398
|
+
url: page.url || "",
|
|
399
|
+
humanInteraction: classifyHumanInteraction(`${page.title || ""}
|
|
400
|
+
${page.url || ""}
|
|
401
|
+
${snapshot}`),
|
|
402
|
+
message: "Opened the dedicated DM browser window for human interaction. Complete only the site interaction you authorize, then return to DM; this scoped profile is retained for future CUA sessions."
|
|
358
403
|
};
|
|
359
404
|
}
|
|
360
405
|
case "screenshot": {
|
|
@@ -465,7 +510,18 @@ export function renderBrowserActionText(result) {
|
|
|
465
510
|
return [`Navigated browser tab ${result.tab} to ${result.url || "(unknown URL)"}`, result.title ? `Title: ${result.title}` : ""].filter(Boolean).join(`
|
|
466
511
|
`);
|
|
467
512
|
case "snapshot":
|
|
468
|
-
return [
|
|
513
|
+
return [
|
|
514
|
+
`Browser snapshot for ${result.tab}`,
|
|
515
|
+
result.snapshot || "(empty snapshot)",
|
|
516
|
+
result.humanInteraction?.needed ? `Human interaction may be required: ${result.humanInteraction.reason}. Use browser_cua(handoff) to open the DM browser window.` : ""
|
|
517
|
+
].filter(Boolean).join(`
|
|
518
|
+
`);
|
|
519
|
+
case "handoff":
|
|
520
|
+
return [
|
|
521
|
+
result.message,
|
|
522
|
+
result.humanInteraction?.needed ? `Detected: ${result.humanInteraction.reason}.` : "No known verification marker was detected; the visible browser remains available if the page still needs you.",
|
|
523
|
+
"DM does not solve challenges or enter credentials for you."
|
|
524
|
+
].join(`
|
|
469
525
|
`);
|
|
470
526
|
case "screenshot":
|
|
471
527
|
return [
|
|
@@ -647,6 +647,15 @@ async function runDaemon(targetId) {
|
|
|
647
647
|
}
|
|
648
648
|
});
|
|
649
649
|
});
|
|
650
|
+
server.once("error", (error) => {
|
|
651
|
+
process.stderr.write(`Daemon: socket listener failed at ${sp}: ${error.message}
|
|
652
|
+
`);
|
|
653
|
+
try {
|
|
654
|
+
unlinkSync(sp);
|
|
655
|
+
} catch {}
|
|
656
|
+
cdp.close();
|
|
657
|
+
process.exit(1);
|
|
658
|
+
});
|
|
650
659
|
try {
|
|
651
660
|
unlinkSync(sp);
|
|
652
661
|
} catch {}
|
|
@@ -669,21 +678,37 @@ async function getOrStartTabDaemon(targetId) {
|
|
|
669
678
|
} catch {}
|
|
670
679
|
const child = spawn(nodeRuntimeCommand(), [process.argv[1], "_daemon", targetId], {
|
|
671
680
|
detached: true,
|
|
672
|
-
stdio: "ignore"
|
|
681
|
+
stdio: ["ignore", "ignore", "pipe"]
|
|
682
|
+
});
|
|
683
|
+
let daemonStderr = "";
|
|
684
|
+
let daemonExit = "";
|
|
685
|
+
child.stderr?.on("data", (chunk) => {
|
|
686
|
+
daemonStderr = `${daemonStderr}${chunk}`.slice(-2048);
|
|
687
|
+
});
|
|
688
|
+
child.on("error", (error) => {
|
|
689
|
+
daemonExit = error.message;
|
|
690
|
+
});
|
|
691
|
+
child.on("exit", (code, signal) => {
|
|
692
|
+
daemonExit = signal ? `terminated by ${signal}` : `exited with code ${code}`;
|
|
673
693
|
});
|
|
674
694
|
child.unref();
|
|
695
|
+
child.stderr?.unref?.();
|
|
675
696
|
const backoffSchedule = [50, 100, 200];
|
|
676
697
|
const deadline = Date.now() + DAEMON_CONNECT_RETRIES * DAEMON_CONNECT_DELAY;
|
|
677
698
|
let attempt = 0;
|
|
678
699
|
while (Date.now() < deadline) {
|
|
679
700
|
try {
|
|
680
|
-
|
|
701
|
+
const conn = await connectToSocket(sp);
|
|
702
|
+
child.stderr?.destroy();
|
|
703
|
+
return conn;
|
|
681
704
|
} catch {}
|
|
682
705
|
const delay = backoffSchedule[attempt] ?? DAEMON_CONNECT_DELAY;
|
|
683
706
|
attempt++;
|
|
684
707
|
await sleep(delay);
|
|
685
708
|
}
|
|
686
|
-
|
|
709
|
+
child.stderr?.destroy();
|
|
710
|
+
const diagnostic = (daemonStderr || daemonExit || "no startup diagnostic was emitted").replace(/\s+/g, " ").trim();
|
|
711
|
+
throw new Error(`Tab daemon did not become ready for ${targetId.slice(0, MIN_TARGET_PREFIX_LEN)}. ${diagnostic}. ` + "Keep the dedicated DM browser open and retry; use browser_cua(handoff) if the site needs your interaction.");
|
|
687
712
|
}
|
|
688
713
|
function sendCommand(conn, req) {
|
|
689
714
|
return new Promise((resolve, reject) => {
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
} from "node:fs";
|
|
10
10
|
import { platform, tmpdir } from "node:os";
|
|
11
11
|
import { join } from "node:path";
|
|
12
|
-
import { httpGet
|
|
12
|
+
import { httpGet } from "../src/search/minimize.mjs";
|
|
13
13
|
import {
|
|
14
14
|
ACTIVE_PORT_FILE,
|
|
15
15
|
CHROME_MODE_FILE,
|
|
@@ -275,8 +275,7 @@ async function main() {
|
|
|
275
275
|
if (isHeadless()) {
|
|
276
276
|
console.log("Ready (headless).");
|
|
277
277
|
} else {
|
|
278
|
-
|
|
279
|
-
console.log("Ready.");
|
|
278
|
+
console.log("Ready (visible).");
|
|
280
279
|
}
|
|
281
280
|
}
|
|
282
281
|
main();
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
1
2
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
-
import { homedir } from "node:os";
|
|
3
|
+
import { homedir, platform } from "node:os";
|
|
3
4
|
import { join } from "node:path";
|
|
4
5
|
import { tmpdir } from "node:os";
|
|
5
6
|
export function parseGreedySearchPort(value, fallback = 9222) {
|
|
@@ -16,7 +17,9 @@ export const CHROME_MODE_FILE = process.env.GREEDY_SEARCH_MODE_FILE || `${GREEDY
|
|
|
16
17
|
export const GREEDY_METADATA_FILE = process.env.GREEDY_SEARCH_METADATA_FILE || `${GREEDY_PROFILE_DIR}/browser-metadata.json`;
|
|
17
18
|
export const GREEDY_LAUNCH_LOCK_FILE = process.env.GREEDY_SEARCH_LAUNCH_LOCK_FILE || `${GREEDY_PROFILE_DIR}/browser-launch.lock`;
|
|
18
19
|
export const GREEDY_ACTIVITY_FILE = process.env.GREEDY_SEARCH_ACTIVITY_FILE || `${GREEDY_PROFILE_DIR}/browser-last-activity`;
|
|
19
|
-
|
|
20
|
+
const GREEDY_SOCKET_SCOPE = createHash("sha256").update(GREEDY_PROFILE_DIR).digest("hex").slice(0, 16);
|
|
21
|
+
const GREEDY_SOCKET_BASE = platform() === "win32" ? GREEDY_TMP : "/tmp";
|
|
22
|
+
export const GREEDY_CDP_SOCKET_DIR = (process.env.CDP_SOCKET_DIR || `${GREEDY_SOCKET_BASE}/dm-gs-${GREEDY_SOCKET_SCOPE}`).replaceAll("\\", "/");
|
|
20
23
|
export const VISIBLE_RECOVERY_LOG = `${GREEDY_PROFILE_DIR}/visible-recovery.jsonl`;
|
|
21
24
|
const CONFIG_DIR = join(homedir(), ".dm");
|
|
22
25
|
const CONFIG_FILE = join(CONFIG_DIR, "greedyconfig");
|
|
@@ -19,7 +19,7 @@ import { runSimpleResearchMode } from "./simple-research.mjs";
|
|
|
19
19
|
import { createProgressTracker } from "./progress.mjs";
|
|
20
20
|
const __dir = fileURLToPath(new URL(".", import.meta.url)).replace(/^\/([A-Z]:)/, "$1");
|
|
21
21
|
const SEARCH_BIN = join(__dir, "..", "..", "bin", "search.mjs");
|
|
22
|
-
const DEFAULT_RESEARCH_BUNDLE_ROOT = join(process.cwd(), ".
|
|
22
|
+
const DEFAULT_RESEARCH_BUNDLE_ROOT = join(process.cwd(), ".dm", "greedysearch-research");
|
|
23
23
|
const MAX_PROMPT_CHARS = 28000;
|
|
24
24
|
function slugifyResearchName(value) {
|
|
25
25
|
const slug = String(value || "research").toLowerCase().replaceAll(/[^a-z0-9]+/g, "-").replaceAll(/^-|-$/g, "").slice(0, 60);
|