@duckmind/dm-windows-x64 0.60.6 → 0.60.8
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/dm.exe +0 -0
- package/extensions/.dm-extensions.json +70 -123
- package/extensions/dm-9router-ext/src/index.js +410 -5
- package/extensions/dm-caveman/extensions/caveman.js +283 -12
- package/extensions/dm-cliproxy/index.js +182 -2
- package/extensions/dm-cliproxy/scripts/check-config-migration.js +66 -8
- package/extensions/dm-cliproxy/src/apply.js +228 -1
- package/extensions/dm-cliproxy/src/cache.js +42 -1
- package/extensions/dm-cliproxy/src/commands.js +50 -2
- package/extensions/dm-cliproxy/src/compat.js +81 -1
- package/extensions/dm-cliproxy/src/config.js +192 -2
- package/extensions/dm-cliproxy/src/conflicts.js +46 -1
- package/extensions/dm-cliproxy/src/fetch-models.js +190 -1
- package/extensions/dm-cliproxy/src/fetch-usage.js +41 -1
- package/extensions/dm-cliproxy/src/log.js +23 -1
- package/extensions/dm-cliproxy/src/status-quota.js +77 -1
- package/extensions/dm-cliproxy/src/ui-frame.js +50 -1
- package/extensions/dm-cliproxy/src/ui-hub/hub.js +199 -2
- package/extensions/dm-cliproxy/src/ui-hub/index.js +26 -2
- package/extensions/dm-cliproxy/src/ui-hub/shell.js +46 -1
- package/extensions/dm-cliproxy/src/ui-hub/view-diagnostics.js +69 -1
- package/extensions/dm-cliproxy/src/ui-hub/view-models.js +379 -1
- package/extensions/dm-cliproxy/src/ui-hub/view-usage.js +106 -1
- package/extensions/dm-cliproxy/src/ui-picker/catalog.js +41 -1
- package/extensions/dm-cliproxy/src/ui-picker/mutate.js +115 -1
- package/extensions/dm-cliproxy/src/ui-picker/prompt-confirm.js +35 -1
- package/extensions/dm-cliproxy/src/ui-picker/prompt-name.js +62 -1
- package/extensions/dm-cliproxy/src/ui-picker/providers.js +45 -1
- package/extensions/dm-cliproxy/src/ui-picker/render-text.js +34 -1
- package/extensions/dm-cliproxy/src/ui-picker/rows.js +57 -1
- package/extensions/dm-cliproxy/src/ui-setup.js +260 -2
- package/extensions/dm-cliproxy/src/ui-usage.js +151 -1
- package/extensions/dm-cliproxy/src/usage-shared-cache.js +97 -1
- package/extensions/dm-context/src/context.js +144 -1
- package/extensions/dm-context/src/index.js +339 -7
- package/extensions/dm-context/src/utils.js +9 -1
- package/extensions/dm-cua/bin/browser-cua.mjs +73 -8
- package/extensions/dm-cua/index.js +75 -6
- package/extensions/dm-cua/src/browser-cua-lib.mjs +490 -6
- package/extensions/dm-cua/src/browser-install.mjs +331 -2
- package/extensions/dm-fff/src/index.js +688 -12
- package/extensions/dm-fff/src/query.js +60 -1
- package/extensions/dm-goal/src/goal.js +894 -23
- package/extensions/dm-image2/index.js +103 -9
- package/extensions/dm-image2/src/image-lib.mjs +1275 -8
- package/extensions/dm-subagents/install.mjs +62 -8
- package/extensions/dm-subagents/src/agents/agent-management.js +1190 -36
- package/extensions/dm-subagents/src/agents/agent-memory.js +216 -6
- package/extensions/dm-subagents/src/agents/agent-scope.js +5 -1
- package/extensions/dm-subagents/src/agents/agent-selection.js +20 -1
- package/extensions/dm-subagents/src/agents/agent-serializer.js +120 -5
- package/extensions/dm-subagents/src/agents/agents.js +1139 -11
- package/extensions/dm-subagents/src/agents/chain-serializer.js +299 -11
- package/extensions/dm-subagents/src/agents/frontmatter.js +65 -6
- package/extensions/dm-subagents/src/agents/identity.js +29 -1
- package/extensions/dm-subagents/src/agents/proactive-skills.js +141 -1
- package/extensions/dm-subagents/src/agents/skills.js +614 -6
- package/extensions/dm-subagents/src/extension/config.js +35 -2
- package/extensions/dm-subagents/src/extension/control-notices.js +69 -4
- package/extensions/dm-subagents/src/extension/doctor.js +172 -15
- package/extensions/dm-subagents/src/extension/fanout-child.js +158 -231
- package/extensions/dm-subagents/src/extension/index.js +521 -359
- package/extensions/dm-subagents/src/extension/rpc.js +266 -7
- package/extensions/dm-subagents/src/extension/schemas.js +275 -1
- package/extensions/dm-subagents/src/extension/tool-description.js +111 -6
- package/extensions/dm-subagents/src/intercom/intercom-bridge.js +126 -4
- package/extensions/dm-subagents/src/intercom/native-supervisor-channel.js +452 -5
- package/extensions/dm-subagents/src/intercom/result-intercom.js +319 -3
- package/extensions/dm-subagents/src/profiles/profiles.js +458 -3
- package/extensions/dm-subagents/src/runs/background/async-execution.js +834 -40
- package/extensions/dm-subagents/src/runs/background/async-job-tracker.js +435 -14
- package/extensions/dm-subagents/src/runs/background/async-resume.js +334 -8
- package/extensions/dm-subagents/src/runs/background/async-status.js +313 -12
- package/extensions/dm-subagents/src/runs/background/chain-append.js +245 -2
- package/extensions/dm-subagents/src/runs/background/chain-root-attachment.js +136 -1
- package/extensions/dm-subagents/src/runs/background/completion-batcher.js +94 -1
- package/extensions/dm-subagents/src/runs/background/completion-dedupe.js +54 -1
- package/extensions/dm-subagents/src/runs/background/control-channel.js +190 -1
- package/extensions/dm-subagents/src/runs/background/fleet-view.js +483 -17
- package/extensions/dm-subagents/src/runs/background/notify.js +129 -3
- package/extensions/dm-subagents/src/runs/background/parallel-groups.js +34 -1
- package/extensions/dm-subagents/src/runs/background/result-watcher.js +236 -6
- package/extensions/dm-subagents/src/runs/background/run-id-resolver.js +76 -4
- package/extensions/dm-subagents/src/runs/background/run-status.js +427 -23
- package/extensions/dm-subagents/src/runs/background/scheduled-runs.js +487 -4
- package/extensions/dm-subagents/src/runs/background/stale-run-reconciler.js +306 -9
- package/extensions/dm-subagents/src/runs/background/subagent-runner.js +2849 -73
- package/extensions/dm-subagents/src/runs/background/top-level-async.js +5 -1
- package/extensions/dm-subagents/src/runs/background/wait.js +206 -11
- package/extensions/dm-subagents/src/runs/foreground/chain-clarify.js +1013 -12
- package/extensions/dm-subagents/src/runs/foreground/chain-execution.js +980 -101
- package/extensions/dm-subagents/src/runs/foreground/execution.js +1165 -45
- package/extensions/dm-subagents/src/runs/foreground/subagent-executor.js +3157 -222
- package/extensions/dm-subagents/src/runs/shared/acceptance.js +835 -3
- package/extensions/dm-subagents/src/runs/shared/chain-outputs.js +104 -1
- package/extensions/dm-subagents/src/runs/shared/completion-guard.js +116 -3
- package/extensions/dm-subagents/src/runs/shared/dm-args.js +208 -1
- package/extensions/dm-subagents/src/runs/shared/dm-spawn.js +90 -1
- package/extensions/dm-subagents/src/runs/shared/dynamic-fanout.js +282 -1
- package/extensions/dm-subagents/src/runs/shared/long-running-guard.js +148 -1
- package/extensions/dm-subagents/src/runs/shared/mcp-direct-tool-allowlist.js +305 -1
- package/extensions/dm-subagents/src/runs/shared/model-fallback.js +194 -1
- package/extensions/dm-subagents/src/runs/shared/model-scope.js +65 -1
- package/extensions/dm-subagents/src/runs/shared/nested-events.js +851 -8
- package/extensions/dm-subagents/src/runs/shared/nested-path.js +41 -1
- package/extensions/dm-subagents/src/runs/shared/nested-render.js +105 -1
- package/extensions/dm-subagents/src/runs/shared/parallel-utils.js +81 -4
- package/extensions/dm-subagents/src/runs/shared/run-history.js +51 -4
- package/extensions/dm-subagents/src/runs/shared/single-output.js +149 -8
- package/extensions/dm-subagents/src/runs/shared/structured-output.js +58 -1
- package/extensions/dm-subagents/src/runs/shared/subagent-control.js +166 -5
- package/extensions/dm-subagents/src/runs/shared/subagent-prompt-runtime.js +329 -13
- package/extensions/dm-subagents/src/runs/shared/tool-budget.js +73 -1
- package/extensions/dm-subagents/src/runs/shared/turn-budget.js +47 -4
- package/extensions/dm-subagents/src/runs/shared/workflow-graph.js +196 -1
- package/extensions/dm-subagents/src/runs/shared/worktree.js +435 -3
- package/extensions/dm-subagents/src/shared/artifacts.js +92 -2
- package/extensions/dm-subagents/src/shared/atomic-json.js +55 -1
- package/extensions/dm-subagents/src/shared/child-transcript.js +167 -5
- package/extensions/dm-subagents/src/shared/file-coalescer.js +25 -1
- package/extensions/dm-subagents/src/shared/fork-context.js +147 -4
- package/extensions/dm-subagents/src/shared/formatters.js +98 -7
- package/extensions/dm-subagents/src/shared/jsonl-writer.js +56 -2
- package/extensions/dm-subagents/src/shared/model-info.js +62 -1
- package/extensions/dm-subagents/src/shared/post-exit-stdio-guard.js +68 -1
- package/extensions/dm-subagents/src/shared/session-identity.js +6 -1
- package/extensions/dm-subagents/src/shared/session-tokens.js +39 -2
- package/extensions/dm-subagents/src/shared/settings.js +198 -11
- package/extensions/dm-subagents/src/shared/status-format.js +53 -1
- package/extensions/dm-subagents/src/shared/types.js +184 -6
- package/extensions/dm-subagents/src/shared/utils.js +462 -2
- package/extensions/dm-subagents/src/slash/prompt-template-bridge.js +288 -1
- package/extensions/dm-subagents/src/slash/prompt-workflows.js +297 -7
- package/extensions/dm-subagents/src/slash/slash-bridge.js +118 -1
- package/extensions/dm-subagents/src/slash/slash-commands.js +1287 -31
- package/extensions/dm-subagents/src/slash/slash-live-state.js +240 -4
- package/extensions/dm-subagents/src/tui/render-helpers.js +64 -1
- package/extensions/dm-subagents/src/tui/render.js +1542 -4
- package/extensions/dm-usage/index.js +1294 -9
- package/extensions/greedysearch-dm/bin/cdp-greedy.mjs +40 -9
- package/extensions/greedysearch-dm/bin/cdp-headless.mjs +5 -2
- package/extensions/greedysearch-dm/bin/cdp-visible.mjs +5 -2
- package/extensions/greedysearch-dm/bin/cdp.mjs +896 -30
- package/extensions/greedysearch-dm/bin/gschrome.mjs +30 -2
- package/extensions/greedysearch-dm/bin/kill-visible.mjs +7 -2
- package/extensions/greedysearch-dm/bin/launch-visible.mjs +13 -2
- package/extensions/greedysearch-dm/bin/launch.mjs +282 -10
- package/extensions/greedysearch-dm/bin/mcp.mjs +386 -361
- package/extensions/greedysearch-dm/bin/search.mjs +620 -540
- package/extensions/greedysearch-dm/bin/visible.mjs +22 -2
- package/extensions/greedysearch-dm/extractors/bing-copilot.mjs +329 -579
- package/extensions/greedysearch-dm/extractors/chatgpt.mjs +301 -583
- package/extensions/greedysearch-dm/extractors/common.mjs +408 -32
- package/extensions/greedysearch-dm/extractors/consensus.mjs +376 -365
- package/extensions/greedysearch-dm/extractors/consent.mjs +303 -14
- package/extensions/greedysearch-dm/extractors/gemini.mjs +228 -592
- package/extensions/greedysearch-dm/extractors/google-ai.mjs +78 -499
- package/extensions/greedysearch-dm/extractors/logically.mjs +270 -347
- package/extensions/greedysearch-dm/extractors/perplexity.mjs +243 -581
- package/extensions/greedysearch-dm/extractors/selectors.mjs +32 -1
- package/extensions/greedysearch-dm/extractors/semantic-scholar.mjs +130 -317
- package/extensions/greedysearch-dm/index.js +123 -23
- package/extensions/greedysearch-dm/src/fetcher.mjs +576 -2
- package/extensions/greedysearch-dm/src/formatters/results.js +95 -10
- package/extensions/greedysearch-dm/src/formatters/sources.js +57 -1
- package/extensions/greedysearch-dm/src/formatters/synthesis.js +49 -1
- package/extensions/greedysearch-dm/src/github.mjs +222 -7
- package/extensions/greedysearch-dm/src/reddit.mjs +145 -14
- package/extensions/greedysearch-dm/src/search/browser-lifecycle.mjs +340 -9
- package/extensions/greedysearch-dm/src/search/challenge-detect.mjs +112 -4
- package/extensions/greedysearch-dm/src/search/chrome.mjs +486 -285
- package/extensions/greedysearch-dm/src/search/constants.mjs +109 -8
- package/extensions/greedysearch-dm/src/search/defaults.mjs +10 -1
- package/extensions/greedysearch-dm/src/search/engines.mjs +79 -9
- package/extensions/greedysearch-dm/src/search/fetch-source.mjs +441 -349
- package/extensions/greedysearch-dm/src/search/file-sources.mjs +29 -7
- package/extensions/greedysearch-dm/src/search/minimize.mjs +86 -1
- package/extensions/greedysearch-dm/src/search/output.mjs +51 -5
- package/extensions/greedysearch-dm/src/search/paths.mjs +48 -1
- package/extensions/greedysearch-dm/src/search/pdf.mjs +63 -2
- package/extensions/greedysearch-dm/src/search/port-pid.mjs +69 -1
- package/extensions/greedysearch-dm/src/search/progress.mjs +109 -2
- package/extensions/greedysearch-dm/src/search/query.mjs +21 -1
- package/extensions/greedysearch-dm/src/search/recovery.mjs +49 -1
- package/extensions/greedysearch-dm/src/search/research.mjs +2227 -458
- package/extensions/greedysearch-dm/src/search/scale-aware.mjs +61 -11
- package/extensions/greedysearch-dm/src/search/simple-research.mjs +396 -805
- package/extensions/greedysearch-dm/src/search/sources.mjs +412 -1
- package/extensions/greedysearch-dm/src/search/synthesis-runner.mjs +127 -12
- package/extensions/greedysearch-dm/src/search/synthesis.mjs +202 -12
- package/extensions/greedysearch-dm/src/tools/greedy-search-handler.js +209 -23
- package/extensions/greedysearch-dm/src/tools/shared.js +226 -10
- package/extensions/greedysearch-dm/src/utils/content.mjs +35 -4
- package/extensions/greedysearch-dm/src/utils/helpers.js +22 -1
- package/extensions/greedysearch-dm/src/utils/node-runtime.mjs +10 -1
- package/extensions/greedysearch-dm/src/utils/system-cmds.mjs +61 -1
- package/package.json +1 -1
|
@@ -1,7 +1,339 @@
|
|
|
1
|
-
import{Type
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
1
|
+
import { Type } from "@sinclair/typebox";
|
|
2
|
+
import { formatTokens } from "./utils.js";
|
|
3
|
+
const InternalTools = ["context_tag", "context_log", "context_checkout"];
|
|
4
|
+
let CommandCtx = null;
|
|
5
|
+
let CheckoutParams = null;
|
|
6
|
+
const isInternal = (name) => InternalTools.includes(name);
|
|
7
|
+
const resolveTargetId = (sm, target) => {
|
|
8
|
+
if (target.toLowerCase() === "root") {
|
|
9
|
+
const tree = sm.getTree();
|
|
10
|
+
return tree.length > 0 ? tree[0].entry.id : target;
|
|
11
|
+
}
|
|
12
|
+
if (/^[0-9a-f]{8,}$/i.test(target))
|
|
13
|
+
return target;
|
|
14
|
+
const stack = [...sm.getTree()];
|
|
15
|
+
while (stack.length > 0) {
|
|
16
|
+
const n = stack.pop();
|
|
17
|
+
if (sm.getLabel(n.entry.id) === target)
|
|
18
|
+
return n.entry.id;
|
|
19
|
+
if (n.children?.length)
|
|
20
|
+
stack.push(...n.children);
|
|
21
|
+
}
|
|
22
|
+
return target;
|
|
23
|
+
};
|
|
24
|
+
const ContextLogParams = Type.Object({
|
|
25
|
+
limit: Type.Optional(Type.Number({ description: "History limit for visible entries (default: 50)." })),
|
|
26
|
+
verbose: Type.Optional(Type.Boolean({ description: "If true, show ALL messages. If false (default), collapses intermediate AI steps and only shows 'milestones': User messages, Tags, Branch Points, and Summaries." }))
|
|
27
|
+
});
|
|
28
|
+
const ContextCheckoutParams = Type.Object({
|
|
29
|
+
target: Type.String({ description: "Where to jump/squash to. Can be a tag name (e.g., 'task-start'), a commit ID, or 'root'. This is the base for your new branch." }),
|
|
30
|
+
message: Type.String({ description: "The 'Carryover Message' for the new branch. A summary of your *current* progress/lessons that you want to bring with you to the new state. This ensures you don't lose key information when switching contexts. Good summary message: '[Status] + [Reason] + [Important Changes] + [Carryover Data]'" }),
|
|
31
|
+
backupTag: Type.Optional(Type.String({ description: "Optional tag name to apply to the CURRENT state before checking out. Use this to create an automatic backup of the history you are about to leave/squash." }))
|
|
32
|
+
});
|
|
33
|
+
const ContextTagParams = Type.Object({
|
|
34
|
+
name: Type.String({ description: "The tag/milestone name. Use meaningful names." }),
|
|
35
|
+
target: Type.Optional(Type.String({ description: "The commit ID to tag. Defaults to HEAD (current state)." }))
|
|
36
|
+
});
|
|
37
|
+
export default function src_default(pi) {
|
|
38
|
+
pi.registerCommand("acm", {
|
|
39
|
+
description: "Enable agentic context management for the current session",
|
|
40
|
+
handler: async (args, ctx) => {
|
|
41
|
+
CommandCtx = ctx;
|
|
42
|
+
ctx.ui.notify("Agentic Context Management enabled.", "info");
|
|
43
|
+
pi.sendMessage({
|
|
44
|
+
customType: "dm-context",
|
|
45
|
+
content: "use context-management skill",
|
|
46
|
+
display: false
|
|
47
|
+
}, {
|
|
48
|
+
deliverAs: "followUp"
|
|
49
|
+
});
|
|
50
|
+
if (args) {
|
|
51
|
+
pi.sendUserMessage(args);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
const findTagInTree = (sm, nodes, tagName) => {
|
|
56
|
+
for (const n of nodes) {
|
|
57
|
+
if (sm.getLabel(n.entry.id) === tagName)
|
|
58
|
+
return n.entry.id;
|
|
59
|
+
const r = findTagInTree(sm, n.children, tagName);
|
|
60
|
+
if (r)
|
|
61
|
+
return r;
|
|
62
|
+
}
|
|
63
|
+
return null;
|
|
64
|
+
};
|
|
65
|
+
pi.registerTool({
|
|
66
|
+
name: "context_tag",
|
|
67
|
+
label: "Context Tag",
|
|
68
|
+
description: "Creates a 'Save Point' (Bookmark) in the history. Use this before trying risky changes or when a feature is stable. 'Untagged progress is risky'.",
|
|
69
|
+
parameters: ContextTagParams,
|
|
70
|
+
async execute(_id, params, _signal, _onUpdate, ctx) {
|
|
71
|
+
const sm = ctx.sessionManager;
|
|
72
|
+
const existingTagId = findTagInTree(sm, sm.getTree(), params.name);
|
|
73
|
+
if (existingTagId) {
|
|
74
|
+
return {
|
|
75
|
+
content: [{
|
|
76
|
+
type: "text",
|
|
77
|
+
text: `Error: Tag '${params.name}' already exists at ${existingTagId}. Tag names must be unique. Use a different name or delete the existing tag first.`
|
|
78
|
+
}],
|
|
79
|
+
details: {}
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
let id = params.target ? resolveTargetId(sm, params.target) : undefined;
|
|
83
|
+
if (!id) {
|
|
84
|
+
const branch = sm.getBranch();
|
|
85
|
+
for (let i = branch.length - 1;i >= 0; i--) {
|
|
86
|
+
const entry = branch[i];
|
|
87
|
+
if (entry.type === "message" && entry.message.role === "toolResult") {
|
|
88
|
+
const tr = entry.message;
|
|
89
|
+
if (isInternal(tr.toolName))
|
|
90
|
+
continue;
|
|
91
|
+
id = entry.id;
|
|
92
|
+
break;
|
|
93
|
+
}
|
|
94
|
+
if (entry.type === "message" && entry.message.role === "assistant") {
|
|
95
|
+
const m = entry.message;
|
|
96
|
+
const hasInternalTool = m.content.some((c) => c.type === "toolCall" && isInternal(c.name));
|
|
97
|
+
if (!hasInternalTool) {
|
|
98
|
+
id = entry.id;
|
|
99
|
+
break;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
id = entry.id;
|
|
103
|
+
break;
|
|
104
|
+
}
|
|
105
|
+
if (!id)
|
|
106
|
+
id = sm.getLeafId() ?? "";
|
|
107
|
+
}
|
|
108
|
+
pi.setLabel(id, params.name);
|
|
109
|
+
return { content: [{ type: "text", text: `Created tag '${params.name}' at ${id}` }], details: {} };
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
pi.registerTool({
|
|
113
|
+
name: "context_log",
|
|
114
|
+
label: "Context Log",
|
|
115
|
+
description: "Show the entire history structure (status, message, tags, milestones). Analogous to 'git log --graph --oneline --decorate'",
|
|
116
|
+
parameters: ContextLogParams,
|
|
117
|
+
async execute(_id, params, _signal, _onUpdate, ctx) {
|
|
118
|
+
const sm = ctx.sessionManager;
|
|
119
|
+
const branch = sm.getBranch();
|
|
120
|
+
const currentLeafId = sm.getLeafId();
|
|
121
|
+
const verbose = params.verbose ?? false;
|
|
122
|
+
const limit = params.limit ?? 50;
|
|
123
|
+
const backboneIds = new Set(branch.map((e) => e.id));
|
|
124
|
+
const sequence = [];
|
|
125
|
+
branch.forEach((entry) => {
|
|
126
|
+
sequence.push(entry);
|
|
127
|
+
const children = sm.getChildren(entry.id);
|
|
128
|
+
children.forEach((child) => {
|
|
129
|
+
if ((child.type === "branch_summary" || child.type === "compaction") && !backboneIds.has(child.id)) {
|
|
130
|
+
sequence.push(child);
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
const getMsgContent = (entry) => {
|
|
135
|
+
if (entry.type === "branch_summary" || entry.type === "compaction") {
|
|
136
|
+
const e = entry;
|
|
137
|
+
return e.summary || "[No summary provided]";
|
|
138
|
+
}
|
|
139
|
+
if (entry.type === "label") {
|
|
140
|
+
return `tag: ${entry.label}`;
|
|
141
|
+
}
|
|
142
|
+
if (entry.type === "message") {
|
|
143
|
+
const msg = entry.message;
|
|
144
|
+
if (msg.role === "toolResult") {
|
|
145
|
+
const tr = msg;
|
|
146
|
+
if (!verbose && isInternal(tr.toolName))
|
|
147
|
+
return "";
|
|
148
|
+
const extractText = (content) => {
|
|
149
|
+
return content.map((p) => p.type === "text" ? p.text : "").join(" ").trim();
|
|
150
|
+
};
|
|
151
|
+
let resText = extractText(tr.content);
|
|
152
|
+
const details = tr.details;
|
|
153
|
+
if ((tr.toolName === "read" || tr.toolName === "edit") && details && "path" in details && typeof details.path === "string") {
|
|
154
|
+
resText = `${details.path}: ${resText}`;
|
|
155
|
+
}
|
|
156
|
+
return `(${tr.toolName}) ${resText}`;
|
|
157
|
+
}
|
|
158
|
+
if (msg.role === "bashExecution") {
|
|
159
|
+
return `[Bash] ${msg.command}`;
|
|
160
|
+
}
|
|
161
|
+
if (msg.role === "user" || msg.role === "assistant") {
|
|
162
|
+
let text = "";
|
|
163
|
+
if (typeof msg.content === "string") {
|
|
164
|
+
text = msg.content;
|
|
165
|
+
} else if (Array.isArray(msg.content)) {
|
|
166
|
+
text = msg.content.map((p) => {
|
|
167
|
+
if (typeof p === "object" && p !== null && "text" in p)
|
|
168
|
+
return p.text;
|
|
169
|
+
return "";
|
|
170
|
+
}).join(" ").trim();
|
|
171
|
+
}
|
|
172
|
+
let toolCallsText = "";
|
|
173
|
+
if (msg.role === "assistant") {
|
|
174
|
+
const toolCalls = msg.content.filter((c) => c.type === "toolCall");
|
|
175
|
+
toolCallsText = toolCalls.filter((tc) => verbose || !isInternal(tc.name)).map((tc) => `call: ${tc.name}(${JSON.stringify(tc.arguments)})`).join("; ");
|
|
176
|
+
}
|
|
177
|
+
return [text, toolCallsText].filter(Boolean).join(" ");
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
return "";
|
|
181
|
+
};
|
|
182
|
+
const isInteresting = (entry) => {
|
|
183
|
+
if (entry.id === currentLeafId)
|
|
184
|
+
return true;
|
|
185
|
+
if (branch.length > 0 && entry.id === branch[0].id)
|
|
186
|
+
return true;
|
|
187
|
+
if (sm.getLabel(entry.id))
|
|
188
|
+
return true;
|
|
189
|
+
if (entry.type === "label")
|
|
190
|
+
return false;
|
|
191
|
+
if (entry.type === "branch_summary" || entry.type === "compaction")
|
|
192
|
+
return true;
|
|
193
|
+
if (sm.getChildren(entry.id).length > 1)
|
|
194
|
+
return true;
|
|
195
|
+
if (entry.type === "message" && entry.message.role === "user")
|
|
196
|
+
return true;
|
|
197
|
+
return false;
|
|
198
|
+
};
|
|
199
|
+
const visibleSequenceIds = new Set;
|
|
200
|
+
sequence.forEach((e) => {
|
|
201
|
+
if (verbose || isInteresting(e)) {
|
|
202
|
+
visibleSequenceIds.add(e.id);
|
|
203
|
+
}
|
|
204
|
+
});
|
|
205
|
+
let visibleEntries = sequence.filter((e) => visibleSequenceIds.has(e.id));
|
|
206
|
+
if (visibleEntries.length > limit) {
|
|
207
|
+
const allowedIds = new Set(visibleEntries.slice(-limit).map((e) => e.id));
|
|
208
|
+
visibleSequenceIds.clear();
|
|
209
|
+
allowedIds.forEach((id) => visibleSequenceIds.add(id));
|
|
210
|
+
}
|
|
211
|
+
const lines = [];
|
|
212
|
+
let hiddenCount = 0;
|
|
213
|
+
sequence.forEach((entry) => {
|
|
214
|
+
if (!visibleSequenceIds.has(entry.id)) {
|
|
215
|
+
hiddenCount++;
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
if (hiddenCount > 0) {
|
|
219
|
+
lines.push(` : ... (${hiddenCount} hidden messages) ...`);
|
|
220
|
+
hiddenCount = 0;
|
|
221
|
+
}
|
|
222
|
+
const isHead = entry.id === currentLeafId;
|
|
223
|
+
const label = sm.getLabel(entry.id);
|
|
224
|
+
const content = getMsgContent(entry).replace(/\s+/g, " ");
|
|
225
|
+
let role = entry.type.toUpperCase();
|
|
226
|
+
if (entry.type === "message") {
|
|
227
|
+
const m = entry.message;
|
|
228
|
+
role = m.role === "assistant" ? "AI" : m.role === "user" ? "USER" : m.role === "bashExecution" ? "BASH" : "TOOL";
|
|
229
|
+
} else if (entry.type === "branch_summary" || entry.type === "compaction") {
|
|
230
|
+
role = "SUMMARY";
|
|
231
|
+
}
|
|
232
|
+
if (role === "CUSTOM_MESSAGE") {
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
const id = entry.id;
|
|
236
|
+
const isRoot = branch.length > 0 && entry.id === branch[0].id;
|
|
237
|
+
const meta = [isRoot ? "ROOT" : null, isHead ? "HEAD" : null, label ? `tag: ${label}` : null].filter(Boolean).join(", ");
|
|
238
|
+
const body = content.length > 100 ? content.slice(0, 100) + "..." : content;
|
|
239
|
+
const marker = isHead ? "*" : role === "USER" ? "•" : "|";
|
|
240
|
+
lines.push(`${marker} ${id}${meta ? ` (${meta})` : ""} [${role}] ${body}`);
|
|
241
|
+
});
|
|
242
|
+
if (hiddenCount > 0) {
|
|
243
|
+
lines.push(` : ... (${hiddenCount} hidden messages) ...`);
|
|
244
|
+
}
|
|
245
|
+
const usage = await ctx.getContextUsage();
|
|
246
|
+
let usageStr = "Unknown";
|
|
247
|
+
if (usage) {
|
|
248
|
+
const percent = usage.percent;
|
|
249
|
+
usageStr = percent == null ? `${formatTokens(usage.tokens)}/${formatTokens(usage.contextWindow)}` : `${percent.toFixed(1)}% (${formatTokens(usage.tokens)}/${formatTokens(usage.contextWindow)})`;
|
|
250
|
+
}
|
|
251
|
+
let stepsSinceTag = 0;
|
|
252
|
+
let nearestTagName = "None";
|
|
253
|
+
for (let i = branch.length - 1;i >= 0; i--) {
|
|
254
|
+
const id = branch[i].id;
|
|
255
|
+
const label = sm.getLabel(id);
|
|
256
|
+
if (label) {
|
|
257
|
+
nearestTagName = label;
|
|
258
|
+
break;
|
|
259
|
+
}
|
|
260
|
+
stepsSinceTag++;
|
|
261
|
+
}
|
|
262
|
+
const hud = [
|
|
263
|
+
`[Context Dashboard]`,
|
|
264
|
+
`• Context Usage: ${usageStr}`,
|
|
265
|
+
`• Segment Size: ${stepsSinceTag} steps since last tag '${nearestTagName}'`,
|
|
266
|
+
`---------------------------------------------------`
|
|
267
|
+
].join(`
|
|
268
|
+
`);
|
|
269
|
+
return { content: [{ type: "text", text: hud + `
|
|
270
|
+
` + (lines.join(`
|
|
271
|
+
`) || "(Root Path Only)") }], details: {} };
|
|
272
|
+
}
|
|
273
|
+
});
|
|
274
|
+
pi.registerTool({
|
|
275
|
+
name: "context_checkout",
|
|
276
|
+
label: "Context Checkout",
|
|
277
|
+
description: "Navigate to ANY point in the conversation history. This checkout only resets *conversation history*, NOT disk files. ALWAYS provide a detailed 'message' to bridge context.",
|
|
278
|
+
parameters: ContextCheckoutParams,
|
|
279
|
+
async execute(_id, params, _signal, _onUpdate, ctx) {
|
|
280
|
+
if (!CommandCtx) {
|
|
281
|
+
ctx.ui.setEditorText(`/acm ${ctx.ui.getEditorText() || "continue"}`);
|
|
282
|
+
return {
|
|
283
|
+
content: [{
|
|
284
|
+
type: "text",
|
|
285
|
+
text: "Agentic context management is not enabled. Ask the user to run `/acm` in DM to enable it, then retry."
|
|
286
|
+
}],
|
|
287
|
+
details: {}
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
const sm = ctx.sessionManager;
|
|
291
|
+
const tid = resolveTargetId(sm, params.target);
|
|
292
|
+
const currentLeaf = sm.getLeafId();
|
|
293
|
+
if (currentLeaf === tid) {
|
|
294
|
+
return { content: [{ type: "text", text: `Already at target ${tid}` }], details: {} };
|
|
295
|
+
}
|
|
296
|
+
if (params.backupTag && currentLeaf) {
|
|
297
|
+
pi.setLabel(currentLeaf, params.backupTag);
|
|
298
|
+
}
|
|
299
|
+
const currentLabel = currentLeaf ? sm.getLabel(currentLeaf) : undefined;
|
|
300
|
+
const origin = currentLabel ? `tag: ${currentLabel}` : currentLeaf || "unknown";
|
|
301
|
+
const enrichedMessage = `(summary from ${origin})
|
|
302
|
+
${params.message}`;
|
|
303
|
+
const nid = await sm.branchWithSummary(tid, enrichedMessage);
|
|
304
|
+
CheckoutParams = params;
|
|
305
|
+
CheckoutParams.nid = nid;
|
|
306
|
+
CheckoutParams.tid = tid;
|
|
307
|
+
CheckoutParams.enrichedMessage = enrichedMessage;
|
|
308
|
+
return { content: [{ type: "text", text: "checkout start" }], details: {} };
|
|
309
|
+
}
|
|
310
|
+
});
|
|
311
|
+
pi.on("turn_end", async (event, ctx) => {
|
|
312
|
+
if (!CheckoutParams) {
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
ctx.abort();
|
|
316
|
+
});
|
|
317
|
+
pi.on("agent_end", async (_event, ctx) => {
|
|
318
|
+
if (!CheckoutParams) {
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
if (!CommandCtx) {
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
await CommandCtx.navigateTree(CheckoutParams.nid, {
|
|
325
|
+
summarize: false
|
|
326
|
+
});
|
|
327
|
+
ctx.ui.notify(`Checked out ${CheckoutParams.target}${CheckoutParams.target === CheckoutParams.tid ? "" : `(${CheckoutParams.tid})`}
|
|
328
|
+
Backup tag created: ${CheckoutParams.backupTag || "none"}
|
|
329
|
+
message: ${CheckoutParams.enrichedMessage}`, "info");
|
|
330
|
+
CheckoutParams = null;
|
|
331
|
+
pi.sendMessage({
|
|
332
|
+
customType: "dm-context",
|
|
333
|
+
content: "context_checkout complete. A summary of your previous branch was injected above. Read it to understand your new state. Execute the 'Next Step' from the summary",
|
|
334
|
+
display: false
|
|
335
|
+
}, {
|
|
336
|
+
triggerTurn: true
|
|
337
|
+
});
|
|
338
|
+
});
|
|
339
|
+
}
|
|
@@ -1 +1,9 @@
|
|
|
1
|
-
|
|
1
|
+
export const formatTokens = (n) => {
|
|
2
|
+
if (n == null)
|
|
3
|
+
return "N/A";
|
|
4
|
+
if (n >= 1e6)
|
|
5
|
+
return (n / 1e6).toFixed(1) + "M";
|
|
6
|
+
if (n >= 1000)
|
|
7
|
+
return Math.round(n / 1000) + "k";
|
|
8
|
+
return n.toString();
|
|
9
|
+
};
|
|
@@ -1,10 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
${h.trim()}`:""}`})}),f.on("close",(i,c)=>{s({status:i===0?"ok":"failed",exitCode:i,message:h.trim()||(c?`installer terminated by signal ${c}`:"")})}),u=setTimeout(()=>{f.kill(),s({status:"timeout",exitCode:null,message:`installer timed out after ${t}ms`})},t)})}function so(o){if(o.startsWith("homebrew-"))return"Homebrew";if(o.startsWith("winget-"))return"winget";return o.split("-")[0]}function ao(o){let n=o.message?`: ${o.message}`:"";return`${o.id} ${o.status}${n}`}async function Q({env:o=process.env,platformName:n=D(),uid:m=typeof process.getuid==="function"?process.getuid():void 0,findExecutable:t=()=>v({env:o,platformName:n,homeDir:I()}),runCommand:d=(h,u)=>io(h,u,{env:o}),sleep:a=(h)=>new Promise((u)=>{setTimeout(u,h)})}={}){let h=t();if(h)return{status:"existing",path:h,method:null,attempts:[]};if(!no(o))throw Error("Chrome was not found and automatic installation is disabled by DM_CUA_AUTO_INSTALL.");let u=oo({platformName:n,uid:m});if(u.length===0)throw Error(`Chrome was not found and no automatic Chrome installer is defined for ${n}.`);let f=[];for(let i of u){let c={status:"ok",exitCode:0,message:""};for(let $ of i.commands)if(c=await d($.command,$.args),c.status!=="ok")break;if(c.status!=="ok"){f.push({id:i.id,status:c.status,exitCode:c.exitCode,message:c.message});continue}let r;for(let $=0;$<V;$+=1){if(r=t(),r)break;if($<V-1)await a(P)}if(r)return f.push({id:i.id,status:"installed",exitCode:c.exitCode,message:c.message}),{status:"installed",path:r,method:i.id,attempts:f};f.push({id:i.id,status:"missing-after-success",exitCode:c.exitCode,message:`${so(i.id)} reported success, but Chrome was not found`})}let s=f.map(ao).join("; ");throw Error(`Chrome automatic installation failed. ${s}`)}var g=O(Ao(import.meta.url)),So=e(g,".."),wo=9322,ro=400,X=60000,To=/^(about:|https?:\/\/|file:|chrome-error:\/\/)/i,Lo={chrome:"Chrome",cloak:"CloakBrowser"};function q(o){let n=Number.parseInt(String(o??""),10);return Number.isInteger(n)&&n>1024&&n<65535?n:null}function Co(o){return ho("sha256").update(String(o||"default")).digest("hex")}function eo(o){let n=String(o||"chrome").trim().toLowerCase();if(n==="cloakbrowser"||n==="cloak-browser")return"cloak";if(n==="chromium"||n==="google-chrome"||n==="chrome")return"chrome";return n.replace(/[^a-z0-9_.-]/g,"-")||"chrome"}function Mo(o,n){let m=o.DM_CUA_BROWSER_PATH||(n==="cloak"?o.DM_CUA_CLOAK_PATH||o.CLOAKBROWSER_BINARY_PATH:void 0)||o.CHROME_PATH;return m?e(String(m)):void 0}function bo(o=process.env,n=process.cwd()){let m=o.DM_CUA_SCOPE||o.DM_CODING_AGENT_DIR||o.PI_CODING_AGENT_DIR||n,t=Co(e(String(m||"."))),d=String(o.DM_CUA_INSTANCE_ID||t.slice(0,12)).replace(/[^a-zA-Z0-9_.-]/g,"-"),a=eo(o.DM_CUA_BROWSER||o.DM_BROWSER_CUA_BROWSER||o.DM_CUA_BROWSER_KIND),h=o.DM_CUA_BROWSER_DISPLAY_NAME||Lo[a]||a,u=Mo(o,a),f=q(o.DM_CUA_PORT)??q(o.GREEDY_SEARCH_PORT)??wo+Number.parseInt(t.slice(0,8),16)%ro,s=a==="chrome"?"chrome":a,i=e(o.DM_CUA_PROFILE_DIR||w(Z(),`dm-cua-${s}-profile-${d}`)),c=lo()==="win32"?Z():"/tmp";return{instanceId:d,browserKind:a,browserDisplayName:h,browserExecutablePath:u,browserExecutableIsExplicit:Boolean(u),browserInstallation:void 0,port:f,profileDir:i,pidFile:w(i,"chrome.pid"),modeFile:w(i,"chrome-mode"),pagesCache:w(i,"cdp-pages.json"),socketDir:w(c,`dm-cua-cdp-${d}`)}}var A=bo(),yo=A.port,xo=A.profileDir;function T(){return{kind:A.browserKind,displayName:A.browserDisplayName,executablePath:A.browserExecutablePath,installation:A.browserInstallation??null,profileDir:A.profileDir,port:A.port}}async function $o(o=A,{env:n=process.env,exists:m=_,ensureChrome:t=Q}={}){if(o.browserKind==="cloak"&&!o.browserExecutablePath)throw Error(["CloakBrowser selected for browser CUA, but no executable path was provided.","Install/pre-download CloakBrowser yourself, then set DM_CUA_BROWSER_PATH, DM_CUA_CLOAK_PATH, CLOAKBROWSER_BINARY_PATH, or CHROME_PATH.","DM does not bundle the compiled CloakBrowser binary because its upstream license allows use but not redistribution."].join(" "));if(o.browserExecutablePath&&!m(o.browserExecutablePath))throw Error(`${o.browserDisplayName} executable not found: ${o.browserExecutablePath}`);if(o.browserExecutablePath)return o;if(o.browserKind!=="chrome")throw Error(`${o.browserDisplayName} browser CUA requires an explicit executable path via DM_CUA_BROWSER_PATH.`);let d=await t({env:n});return o.browserExecutablePath=d.path,o.browserInstallation={status:d.status,method:d.method,attempts:d.attempts.map(({id:a,status:h,exitCode:u})=>({id:a,status:h,exitCode:u}))},o}var k;async function jo(){if(!k)k=$o(A).catch((o)=>{throw k=void 0,o});return k}function W(){let n=[e(g,"..","greedysearch-dm"),e(g,"..","..","greedysearch-dm")].map((m)=>({launchScript:w(m,"bin","launch.mjs"),cdpScript:w(m,"bin","cdp.mjs")}));for(let m of n)if(_(m.launchScript)&&_(m.cdpScript))return m;return n[0]}function _o(o=A,n=process.env){let m=n.GREEDY_SEARCH_VISIBLE??n.DM_CUA_VISIBLE??n.DM_BROWSER_CUA_VISIBLE;return{...n,...o.browserExecutablePath?{CHROME_PATH:o.browserExecutablePath}:{},CDP_PROFILE_DIR:o.profileDir.replace(/\\/g,"/"),CDP_PAGES_CACHE:o.pagesCache.replace(/\\/g,"/"),CDP_SOCKET_DIR:o.socketDir.replace(/\\/g,"/"),GREEDY_SEARCH_BROWSER_LABEL:o.browserDisplayName,GREEDY_SEARCH_PORT:String(o.port),GREEDY_SEARCH_PROFILE_DIR:o.profileDir.replace(/\\/g,"/"),GREEDY_SEARCH_PID_FILE:o.pidFile.replace(/\\/g,"/"),GREEDY_SEARCH_MODE_FILE:o.modeFile.replace(/\\/g,"/"),GREEDY_SEARCH_ALLOW_PORT_CLEANUP:n.GREEDY_SEARCH_ALLOW_PORT_CLEANUP??"0",...m===void 0?{}:{GREEDY_SEARCH_VISIBLE:m}}}function Y(){return _o(A,process.env)}function ko(o){return To.test(o)}function Uo(o){return o.split(/\r?\n/).map((n)=>n.trimEnd()).filter(Boolean).map((n)=>{let m=n.trim().split(/\s{2,}/).filter(Boolean);if(m.length===0)return null;let t=m[0];if(m.length===1)return{target:t,title:"",url:""};if(m.length===2&&ko(m[1]))return{target:t,title:"",url:m[1]};let d=m.at(-1)??"",a=m.slice(1,-1).join(" ");return{target:t,title:a,url:d}}).filter(Boolean)}function Do(o){let n=String(o??"").trim();if(!n)throw Error("selector is required for action=click");if(/:contains\s*\(/i.test(n))throw Error(":contains(...) is not a standard CSS selector. Use browser_cua(snapshot) to inspect a standard selector, or browser_cua(evaluate) for text matching.");return n}function go(){let o=process.env.DM_CODING_AGENT_DIR||process.env.PI_CODING_AGENT_DIR;return o?e(o):w(fo(),".dm","agent")}function Jo(){let o=w(go(),"cua","screenshots");E(o,{recursive:!0});let n=new Date().toISOString().replace(/[:.]/g,"-");return w(o,`browser-${n}.png`)}function p(o,n,{env:m=process.env,timeoutMs:t=X}={}){return new Promise((d,a)=>{let h=to("node",[o,...n],{env:m,stdio:["ignore","pipe","pipe"]}),u="",f="",s=!1,i=setTimeout(()=>{if(s)return;s=!0,h.kill("SIGTERM"),a(Error(`Timed out running ${o}`))},t);h.stdout.on("data",(c)=>{u+=c.toString()}),h.stderr.on("data",(c)=>{f+=c.toString()}),h.on("error",(c)=>{if(s)return;s=!0,clearTimeout(i),a(c)}),h.on("close",(c)=>{if(s)return;if(s=!0,clearTimeout(i),c!==0){a(Error((f||u||`exit ${c}`).trim()));return}d({stdout:u,stderr:f})})})}function Xo(o,n){return new Promise((m,t)=>{let d=co.request({host:"127.0.0.1",port:yo,path:n,method:o},(a)=>{let h="";a.on("data",(u)=>{h+=u.toString()}),a.on("end",()=>{if((a.statusCode??500)>=400){t(Error(`Browser CDP HTTP ${a.statusCode}: ${h.trim()}`));return}try{m(h?JSON.parse(h):{})}catch(u){t(u)}})});d.on("error",t),d.end()})}async function Yo(o="about:blank"){return Xo("PUT",`/json/new?${encodeURIComponent(o)}`)}async function L(o,n={}){let{cdpScript:m}=W();return p(m,o,{env:Y(),timeoutMs:n.timeoutMs??X})}async function J(){let o=await L(["list"]);return Uo(o.stdout)}function G(o,n){if(!n)return o[0]??null;let m=n.toUpperCase();return o.find((t)=>t.target.toUpperCase().startsWith(m))??null}async function K(){let{launchScript:o,cdpScript:n}=W();if(!_(o)||!_(n))throw Error("Bundled browser helpers are missing. Rebuild DM so dm-cua and greedysearch-dm are staged together.");return{launchScript:o,cdpScript:n}}async function N(){let{launchScript:o}=await K();await jo(),await p(o,[],{env:Y(),timeoutMs:X});let n=await J();if(n.length===0)await Yo("about:blank"),n=await J();return n}async function y(o){let n=await N(),m=G(n,o);if(!m){if(o)throw Error(`No browser tab matching prefix "${o}". Run browser_cua(list) first.`);throw Error("No browser tab available after browser bootstrap.")}return{tab:m.target,page:m,pages:n}}async function F({action:o,tab:n,url:m,selector:t,x:d,y:a,text:h,expression:u,outputPath:f}={}){switch(o){case"list":{let s=await N();return{status:"ok",action:o,browser:T(),pages:s}}case"navigate":{if(!m)throw Error("url is required for action=navigate");let s=await y(n),i=await L(["nav",s.tab,m],{timeoutMs:90000}),c=await J(),r=G(c,s.tab)??s.page;return{status:"ok",action:o,browser:T(),tab:s.tab,url:r?.url||m,title:r?.title||"",message:i.stdout.trim(),pages:c}}case"snapshot":{let s=await y(n),i=await L(["snap",s.tab]);return{status:"ok",action:o,browser:T(),tab:s.tab,title:s.page?.title||"",url:s.page?.url||"",snapshot:i.stdout.trim()}}case"screenshot":{let s=await y(n),i=f?e(f):Jo();E(O(i),{recursive:!0});let c=await L(["shot",s.tab,i],{timeoutMs:120000}),r=uo(i).toString("base64");return{status:"ok",action:o,browser:T(),tab:s.tab,title:s.page?.title||"",url:s.page?.url||"",imagePath:i,imageBase64:r,mimeType:"image/png",message:c.stdout.trim()}}case"click":{let s=Do(t),i=await y(n),c=await L(["click",i.tab,s]);return{status:"ok",action:o,browser:T(),tab:i.tab,selector:s,message:c.stdout.trim()}}case"click_xy":{if(typeof d!=="number"||Number.isNaN(d)||typeof a!=="number"||Number.isNaN(a))throw Error("x and y are required numbers for action=click_xy");let s=await y(n),i=await L(["clickxy",s.tab,String(d),String(a)]);return{status:"ok",action:o,browser:T(),tab:s.tab,x:d,y:a,message:i.stdout.trim()}}case"type":{if(h==null||h==="")throw Error("text is required for action=type");let s=await y(n),i=await L(["type",s.tab,h]);return{status:"ok",action:o,browser:T(),tab:s.tab,textLength:h.length,message:i.stdout.trim()}}case"evaluate":{if(!u)throw Error("expression is required for action=evaluate");let s=await y(n),i=await L(["eval",s.tab,u]);return{status:"ok",action:o,browser:T(),tab:s.tab,value:i.stdout.trim()}}case"stop":{let{launchScript:s}=await K();try{await L(["stop"])}catch{}let i=await p(s,["--kill"],{env:Y(),timeoutMs:30000});return{status:"ok",action:o,browser:T(),message:(i.stdout||"Stopped browser CUA lane.").trim()}}default:throw Error(`Unsupported browser action: ${o}`)}}function B(o){switch(o.action){case"list":{if(!Array.isArray(o.pages)||o.pages.length===0)return"Browser CUA is ready, but no pages are open.";return["Open browser tabs:",...o.pages.map((n)=>n.url?`${n.target} ${n.title||"(untitled)"} ${n.url}`:`${n.target} ${n.title||"(untitled)"}`)].join(`
|
|
4
|
-
`)}case"navigate":return[`Navigated browser tab ${o.tab} to ${o.url||"(unknown URL)"}`,o.title?`Title: ${o.title}`:""].filter(Boolean).join(`
|
|
5
|
-
`);case"snapshot":return[`Browser snapshot for ${o.tab}`,o.snapshot||"(empty snapshot)"].join(`
|
|
6
|
-
`);case"screenshot":return[`Saved browser screenshot for ${o.tab} at ${o.imagePath}`,o.title?`Title: ${o.title}`:"",o.url?`URL: ${o.url}`:""].filter(Boolean).join(`
|
|
7
|
-
`);case"click":return`Clicked selector ${JSON.stringify(o.selector)} on browser tab ${o.tab}`;case"click_xy":return`Clicked browser tab ${o.tab} at CSS coordinates (${o.x}, ${o.y})`;case"type":return`Typed ${o.textLength} characters into browser tab ${o.tab}`;case"evaluate":return`Browser evaluate result for ${o.tab}: ${o.value}`;case"stop":return o.message||"Stopped browser CUA lane.";default:return JSON.stringify(o,null,2)}}function po(){console.log(`dm-cua browser helper
|
|
1
|
+
import { renderBrowserActionText, runBrowserAction } from "../src/browser-cua-lib.mjs";
|
|
2
|
+
function printUsage() {
|
|
3
|
+
console.log(`dm-cua browser helper
|
|
8
4
|
|
|
9
5
|
Usage:
|
|
10
6
|
node browser-cua.mjs list [--json]
|
|
@@ -24,4 +20,73 @@ Usage:
|
|
|
24
20
|
package-manager install; set DM_CUA_AUTO_INSTALL=0 to opt out.
|
|
25
21
|
- To try CloakBrowser, set DM_CUA_BROWSER=cloak
|
|
26
22
|
and point DM_CUA_BROWSER_PATH or CLOAKBROWSER_BINARY_PATH at its Chromium binary.
|
|
27
|
-
- screenshot defaults to ~/.dm/agent/cua/screenshots when --output is omitted.`)
|
|
23
|
+
- screenshot defaults to ~/.dm/agent/cua/screenshots when --output is omitted.`);
|
|
24
|
+
}
|
|
25
|
+
function parseArgs(argv) {
|
|
26
|
+
const options = {};
|
|
27
|
+
const positionals = [];
|
|
28
|
+
for (let index = 0;index < argv.length; index += 1) {
|
|
29
|
+
const value = argv[index];
|
|
30
|
+
if (!value.startsWith("--")) {
|
|
31
|
+
positionals.push(value);
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
const inlineIndex = value.indexOf("=");
|
|
35
|
+
const hasInlineValue = inlineIndex > 2;
|
|
36
|
+
const key = hasInlineValue ? value.slice(2, inlineIndex) : value.slice(2);
|
|
37
|
+
if (key === "json" || key === "help") {
|
|
38
|
+
options[key] = true;
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
if (hasInlineValue) {
|
|
42
|
+
options[key] = value.slice(inlineIndex + 1);
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
const next = argv[index + 1];
|
|
46
|
+
if (typeof next === "undefined" || next.startsWith("--")) {
|
|
47
|
+
throw new Error(`Missing value for --${key}`);
|
|
48
|
+
}
|
|
49
|
+
options[key] = next;
|
|
50
|
+
index += 1;
|
|
51
|
+
}
|
|
52
|
+
return { options, positionals };
|
|
53
|
+
}
|
|
54
|
+
async function main() {
|
|
55
|
+
const { options, positionals } = parseArgs(process.argv.slice(2));
|
|
56
|
+
if (options.help || positionals.length === 0) {
|
|
57
|
+
printUsage();
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
const action = positionals[0];
|
|
61
|
+
const payload = await runBrowserAction({
|
|
62
|
+
action,
|
|
63
|
+
tab: options.tab,
|
|
64
|
+
url: options.url,
|
|
65
|
+
selector: options.selector,
|
|
66
|
+
x: options.x == null ? undefined : Number(options.x),
|
|
67
|
+
y: options.y == null ? undefined : Number(options.y),
|
|
68
|
+
text: options.text,
|
|
69
|
+
expression: options.expression,
|
|
70
|
+
outputPath: options.output
|
|
71
|
+
});
|
|
72
|
+
if (options.json) {
|
|
73
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
console.log(renderBrowserActionText(payload));
|
|
77
|
+
}
|
|
78
|
+
try {
|
|
79
|
+
await main();
|
|
80
|
+
} catch (error) {
|
|
81
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
82
|
+
let wantsJson = false;
|
|
83
|
+
try {
|
|
84
|
+
wantsJson = process.argv.slice(2).includes("--json");
|
|
85
|
+
} catch {}
|
|
86
|
+
if (wantsJson) {
|
|
87
|
+
console.log(JSON.stringify({ status: "error", message }, null, 2));
|
|
88
|
+
} else {
|
|
89
|
+
console.error(message);
|
|
90
|
+
}
|
|
91
|
+
process.exit(1);
|
|
92
|
+
}
|
|
@@ -1,6 +1,75 @@
|
|
|
1
|
-
import
|
|
2
|
-
${c.trim()}`:""}`})}),e.on("close",(n,h)=>{t({status:n===0?"ok":"failed",exitCode:n,message:c.trim()||(h?`installer terminated by signal ${h}`:"")})}),r=setTimeout(()=>{e.kill(),t({status:"timeout",exitCode:null,message:`installer timed out after ${s}ms`})},s)})}function so(o){if(o.startsWith("homebrew-"))return"Homebrew";if(o.startsWith("winget-"))return"winget";return o.split("-")[0]}function co(o){let i=o.message?`: ${o.message}`:"";return`${o.id} ${o.status}${i}`}async function O({env:o=process.env,platformName:i=S(),uid:m=typeof process.getuid==="function"?process.getuid():void 0,findExecutable:s=()=>io({env:o,platformName:i,homeDir:H()}),runCommand:a=(c,r)=>ao(c,r,{env:o}),sleep:d=(c)=>new Promise((r)=>{setTimeout(r,c)})}={}){let c=s();if(c)return{status:"existing",path:c,method:null,attempts:[]};if(!no(o))throw Error("Chrome was not found and automatic installation is disabled by DM_CUA_AUTO_INSTALL.");let r=mo({platformName:i,uid:m});if(r.length===0)throw Error(`Chrome was not found and no automatic Chrome installer is defined for ${i}.`);let e=[];for(let n of r){let h={status:"ok",exitCode:0,message:""};for(let $ of n.commands)if(h=await a($.command,$.args),h.status!=="ok")break;if(h.status!=="ok"){e.push({id:n.id,status:h.status,exitCode:h.exitCode,message:h.message});continue}let L;for(let $=0;$<V;$+=1){if(L=s(),L)break;if($<V-1)await d(oo)}if(L)return e.push({id:n.id,status:"installed",exitCode:h.exitCode,message:h.message}),{status:"installed",path:L,method:n.id,attempts:e};e.push({id:n.id,status:"missing-after-success",exitCode:h.exitCode,message:`${so(n.id)} reported success, but Chrome was not found`})}let t=e.map(co).join("; ");throw Error(`Chrome automatic installation failed. ${t}`)}var X=F(wo(import.meta.url)),xo=C(X,".."),Lo=9322,Ao=400,Y=60000,To=/^(about:|https?:\/\/|file:|chrome-error:\/\/)/i,po={chrome:"Chrome",cloak:"CloakBrowser"};function j(o){let i=Number.parseInt(String(o??""),10);return Number.isInteger(i)&&i>1024&&i<65535?i:null}function Co(o){return ho("sha256").update(String(o||"default")).digest("hex")}function yo(o){let i=String(o||"chrome").trim().toLowerCase();if(i==="cloakbrowser"||i==="cloak-browser")return"cloak";if(i==="chromium"||i==="google-chrome"||i==="chrome")return"chrome";return i.replace(/[^a-z0-9_.-]/g,"-")||"chrome"}function go(o,i){let m=o.DM_CUA_BROWSER_PATH||(i==="cloak"?o.DM_CUA_CLOAK_PATH||o.CLOAKBROWSER_BINARY_PATH:void 0)||o.CHROME_PATH;return m?C(String(m)):void 0}function Mo(o=process.env,i=process.cwd()){let m=o.DM_CUA_SCOPE||o.DM_CODING_AGENT_DIR||o.PI_CODING_AGENT_DIR||i,s=Co(C(String(m||"."))),a=String(o.DM_CUA_INSTANCE_ID||s.slice(0,12)).replace(/[^a-zA-Z0-9_.-]/g,"-"),d=yo(o.DM_CUA_BROWSER||o.DM_BROWSER_CUA_BROWSER||o.DM_CUA_BROWSER_KIND),c=o.DM_CUA_BROWSER_DISPLAY_NAME||po[d]||d,r=go(o,d),e=j(o.DM_CUA_PORT)??j(o.GREEDY_SEARCH_PORT)??Lo+Number.parseInt(s.slice(0,8),16)%Ao,t=d==="chrome"?"chrome":d,n=C(o.DM_CUA_PROFILE_DIR||w(q(),`dm-cua-${t}-profile-${a}`)),h=fo()==="win32"?q():"/tmp";return{instanceId:a,browserKind:d,browserDisplayName:c,browserExecutablePath:r,browserExecutableIsExplicit:Boolean(r),browserInstallation:void 0,port:e,profileDir:n,pidFile:w(n,"chrome.pid"),modeFile:w(n,"chrome-mode"),pagesCache:w(n,"cdp-pages.json"),socketDir:w(h,`dm-cua-cdp-${a}`)}}var f=Mo(),$o=f.port,Ro=f.profileDir;function A(){return{kind:f.browserKind,displayName:f.browserDisplayName,executablePath:f.browserExecutablePath,installation:f.browserInstallation??null,profileDir:f.profileDir,port:f.port}}async function bo(o=f,{env:i=process.env,exists:m=k,ensureChrome:s=O}={}){if(o.browserKind==="cloak"&&!o.browserExecutablePath)throw Error(["CloakBrowser selected for browser CUA, but no executable path was provided.","Install/pre-download CloakBrowser yourself, then set DM_CUA_BROWSER_PATH, DM_CUA_CLOAK_PATH, CLOAKBROWSER_BINARY_PATH, or CHROME_PATH.","DM does not bundle the compiled CloakBrowser binary because its upstream license allows use but not redistribution."].join(" "));if(o.browserExecutablePath&&!m(o.browserExecutablePath))throw Error(`${o.browserDisplayName} executable not found: ${o.browserExecutablePath}`);if(o.browserExecutablePath)return o;if(o.browserKind!=="chrome")throw Error(`${o.browserDisplayName} browser CUA requires an explicit executable path via DM_CUA_BROWSER_PATH.`);let a=await s({env:i});return o.browserExecutablePath=a.path,o.browserInstallation={status:a.status,method:a.method,attempts:a.attempts.map(({id:d,status:c,exitCode:r})=>({id:d,status:c,exitCode:r}))},o}var U;async function ko(){if(!U)U=bo(f).catch((o)=>{throw U=void 0,o});return U}function _(){let i=[C(X,"..","greedysearch-dm"),C(X,"..","..","greedysearch-dm")].map((m)=>({launchScript:w(m,"bin","launch.mjs"),cdpScript:w(m,"bin","cdp.mjs")}));for(let m of i)if(k(m.launchScript)&&k(m.cdpScript))return m;return i[0]}function Uo(o=f,i=process.env){let m=i.GREEDY_SEARCH_VISIBLE??i.DM_CUA_VISIBLE??i.DM_BROWSER_CUA_VISIBLE;return{...i,...o.browserExecutablePath?{CHROME_PATH:o.browserExecutablePath}:{},CDP_PROFILE_DIR:o.profileDir.replace(/\\/g,"/"),CDP_PAGES_CACHE:o.pagesCache.replace(/\\/g,"/"),CDP_SOCKET_DIR:o.socketDir.replace(/\\/g,"/"),GREEDY_SEARCH_BROWSER_LABEL:o.browserDisplayName,GREEDY_SEARCH_PORT:String(o.port),GREEDY_SEARCH_PROFILE_DIR:o.profileDir.replace(/\\/g,"/"),GREEDY_SEARCH_PID_FILE:o.pidFile.replace(/\\/g,"/"),GREEDY_SEARCH_MODE_FILE:o.modeFile.replace(/\\/g,"/"),GREEDY_SEARCH_ALLOW_PORT_CLEANUP:i.GREEDY_SEARCH_ALLOW_PORT_CLEANUP??"0",...m===void 0?{}:{GREEDY_SEARCH_VISIBLE:m}}}function z(){return Uo(f,process.env)}function _o(o){return To.test(o)}function Do(o){return o.split(/\r?\n/).map((i)=>i.trimEnd()).filter(Boolean).map((i)=>{let m=i.trim().split(/\s{2,}/).filter(Boolean);if(m.length===0)return null;let s=m[0];if(m.length===1)return{target:s,title:"",url:""};if(m.length===2&&_o(m[1]))return{target:s,title:"",url:m[1]};let a=m.at(-1)??"",d=m.slice(1,-1).join(" ");return{target:s,title:d,url:a}}).filter(Boolean)}function So(o){let i=String(o??"").trim();if(!i)throw Error("selector is required for action=click");if(/:contains\s*\(/i.test(i))throw Error(":contains(...) is not a standard CSS selector. Use browser_cua(snapshot) to inspect a standard selector, or browser_cua(evaluate) for text matching.");return i}function Xo(){let o=process.env.DM_CODING_AGENT_DIR||process.env.PI_CODING_AGENT_DIR;return o?C(o):w(uo(),".dm","agent")}function Jo(){let o=w(Xo(),"cua","screenshots");E(o,{recursive:!0});let i=new Date().toISOString().replace(/[:.]/g,"-");return w(o,`browser-${i}.png`)}function I(o,i,{env:m=process.env,timeoutMs:s=Y}={}){return new Promise((a,d)=>{let c=ro("node",[o,...i],{env:m,stdio:["ignore","pipe","pipe"]}),r="",e="",t=!1,n=setTimeout(()=>{if(t)return;t=!0,c.kill("SIGTERM"),d(Error(`Timed out running ${o}`))},s);c.stdout.on("data",(h)=>{r+=h.toString()}),c.stderr.on("data",(h)=>{e+=h.toString()}),c.on("error",(h)=>{if(t)return;t=!0,clearTimeout(n),d(h)}),c.on("close",(h)=>{if(t)return;if(t=!0,clearTimeout(n),h!==0){d(Error((e||r||`exit ${h}`).trim()));return}a({stdout:r,stderr:e})})})}function Yo(o,i){return new Promise((m,s)=>{let a=lo.request({host:"127.0.0.1",port:$o,path:i,method:o},(d)=>{let c="";d.on("data",(r)=>{c+=r.toString()}),d.on("end",()=>{if((d.statusCode??500)>=400){s(Error(`Browser CDP HTTP ${d.statusCode}: ${c.trim()}`));return}try{m(c?JSON.parse(c):{})}catch(r){s(r)}})});a.on("error",s),a.end()})}async function zo(o="about:blank"){return Yo("PUT",`/json/new?${encodeURIComponent(o)}`)}async function T(o,i={}){let{cdpScript:m}=_();return I(m,o,{env:z(),timeoutMs:i.timeoutMs??Y})}async function J(){let o=await T(["list"]);return Do(o.stdout)}function G(o,i){if(!i)return o[0]??null;let m=i.toUpperCase();return o.find((s)=>s.target.toUpperCase().startsWith(m))??null}async function K(){let{launchScript:o,cdpScript:i}=_();if(!k(o)||!k(i))throw Error("Bundled browser helpers are missing. Rebuild DM so dm-cua and greedysearch-dm are staged together.");return{launchScript:o,cdpScript:i}}async function B(){let{launchScript:o}=await K();await ko(),await I(o,[],{env:z(),timeoutMs:Y});let i=await J();if(i.length===0)await zo("about:blank"),i=await J();return i}async function M(o){let i=await B(),m=G(i,o);if(!m){if(o)throw Error(`No browser tab matching prefix "${o}". Run browser_cua(list) first.`);throw Error("No browser tab available after browser bootstrap.")}return{tab:m.target,page:m,pages:i}}async function W({action:o,tab:i,url:m,selector:s,x:a,y:d,text:c,expression:r,outputPath:e}={}){switch(o){case"list":{let t=await B();return{status:"ok",action:o,browser:A(),pages:t}}case"navigate":{if(!m)throw Error("url is required for action=navigate");let t=await M(i),n=await T(["nav",t.tab,m],{timeoutMs:90000}),h=await J(),L=G(h,t.tab)??t.page;return{status:"ok",action:o,browser:A(),tab:t.tab,url:L?.url||m,title:L?.title||"",message:n.stdout.trim(),pages:h}}case"snapshot":{let t=await M(i),n=await T(["snap",t.tab]);return{status:"ok",action:o,browser:A(),tab:t.tab,title:t.page?.title||"",url:t.page?.url||"",snapshot:n.stdout.trim()}}case"screenshot":{let t=await M(i),n=e?C(e):Jo();E(F(n),{recursive:!0});let h=await T(["shot",t.tab,n],{timeoutMs:120000}),L=eo(n).toString("base64");return{status:"ok",action:o,browser:A(),tab:t.tab,title:t.page?.title||"",url:t.page?.url||"",imagePath:n,imageBase64:L,mimeType:"image/png",message:h.stdout.trim()}}case"click":{let t=So(s),n=await M(i),h=await T(["click",n.tab,t]);return{status:"ok",action:o,browser:A(),tab:n.tab,selector:t,message:h.stdout.trim()}}case"click_xy":{if(typeof a!=="number"||Number.isNaN(a)||typeof d!=="number"||Number.isNaN(d))throw Error("x and y are required numbers for action=click_xy");let t=await M(i),n=await T(["clickxy",t.tab,String(a),String(d)]);return{status:"ok",action:o,browser:A(),tab:t.tab,x:a,y:d,message:n.stdout.trim()}}case"type":{if(c==null||c==="")throw Error("text is required for action=type");let t=await M(i),n=await T(["type",t.tab,c]);return{status:"ok",action:o,browser:A(),tab:t.tab,textLength:c.length,message:n.stdout.trim()}}case"evaluate":{if(!r)throw Error("expression is required for action=evaluate");let t=await M(i),n=await T(["eval",t.tab,r]);return{status:"ok",action:o,browser:A(),tab:t.tab,value:n.stdout.trim()}}case"stop":{let{launchScript:t}=await K();try{await T(["stop"])}catch{}let n=await I(t,["--kill"],{env:z(),timeoutMs:30000});return{status:"ok",action:o,browser:A(),message:(n.stdout||"Stopped browser CUA lane.").trim()}}default:throw Error(`Unsupported browser action: ${o}`)}}function N(o){switch(o.action){case"list":{if(!Array.isArray(o.pages)||o.pages.length===0)return"Browser CUA is ready, but no pages are open.";return["Open browser tabs:",...o.pages.map((i)=>i.url?`${i.target} ${i.title||"(untitled)"} ${i.url}`:`${i.target} ${i.title||"(untitled)"}`)].join(`
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { Type } from "@sinclair/typebox";
|
|
3
|
+
import {
|
|
4
|
+
renderBrowserActionText,
|
|
5
|
+
resolveBrowserHelperPaths,
|
|
6
|
+
runBrowserAction
|
|
7
|
+
} from "./src/browser-cua-lib.mjs";
|
|
8
|
+
const browserActionSchema = Type.Object({
|
|
9
|
+
action: Type.Union([
|
|
10
|
+
Type.Literal("list"),
|
|
11
|
+
Type.Literal("navigate"),
|
|
12
|
+
Type.Literal("snapshot"),
|
|
13
|
+
Type.Literal("screenshot"),
|
|
14
|
+
Type.Literal("click"),
|
|
15
|
+
Type.Literal("click_xy"),
|
|
16
|
+
Type.Literal("type"),
|
|
17
|
+
Type.Literal("evaluate"),
|
|
18
|
+
Type.Literal("stop")
|
|
19
|
+
], {
|
|
20
|
+
description: "Browser action. Start with list, snapshot, or screenshot before mutating actions on unfamiliar pages."
|
|
21
|
+
}),
|
|
22
|
+
tab: Type.Optional(Type.String({
|
|
23
|
+
description: "Optional target tab prefix from browser_cua(list). If omitted, the first live page in the dedicated profile is used."
|
|
24
|
+
})),
|
|
25
|
+
url: Type.Optional(Type.String({ description: "URL for action=navigate." })),
|
|
26
|
+
selector: Type.Optional(Type.String({
|
|
27
|
+
description: "Standard CSS selector for action=click; :contains(...) is not supported. Use evaluate for text matching."
|
|
28
|
+
})),
|
|
29
|
+
x: Type.Optional(Type.Number({ description: "CSS x coordinate for action=click_xy." })),
|
|
30
|
+
y: Type.Optional(Type.Number({ description: "CSS y coordinate for action=click_xy." })),
|
|
31
|
+
text: Type.Optional(Type.String({ description: "Text for action=type." })),
|
|
32
|
+
expression: Type.Optional(Type.String({ description: "JavaScript expression for action=evaluate." }))
|
|
33
|
+
}, { additionalProperties: false });
|
|
34
|
+
export default function registerDmCua(pi) {
|
|
35
|
+
pi.on("session_start", async (_event, ctx) => {
|
|
36
|
+
const { launchScript, cdpScript } = resolveBrowserHelperPaths();
|
|
37
|
+
if (!existsSync(launchScript) || !existsSync(cdpScript)) {
|
|
38
|
+
ctx.ui.notify("dm-cua: bundled browser helpers are missing. Rebuild DM so dm-cua and greedysearch-dm are staged together.", "warning");
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
pi.registerTool({
|
|
42
|
+
name: "browser_cua",
|
|
43
|
+
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
|
+
promptSnippet: "First-class browser CUA for Chrome with automatic browser bootstrap. Inspect before acting. Screenshot complex pages before click/scroll loops.",
|
|
46
|
+
parameters: browserActionSchema,
|
|
47
|
+
async execute(_toolCallId, params) {
|
|
48
|
+
try {
|
|
49
|
+
const result = await runBrowserAction(params);
|
|
50
|
+
const content = [{ type: "text", text: renderBrowserActionText(result) }];
|
|
51
|
+
if (result.imageBase64 && result.mimeType) {
|
|
52
|
+
content.push({
|
|
53
|
+
type: "image",
|
|
54
|
+
data: result.imageBase64,
|
|
55
|
+
mimeType: result.mimeType
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
return {
|
|
59
|
+
content,
|
|
60
|
+
details: result
|
|
61
|
+
};
|
|
62
|
+
} catch (error) {
|
|
63
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
64
|
+
return {
|
|
65
|
+
content: [{ type: "text", text: `browser_cua failed: ${message}` }],
|
|
66
|
+
details: {
|
|
67
|
+
status: "error",
|
|
68
|
+
action: params?.action ?? null,
|
|
69
|
+
message
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
}
|