@causyne/agent 1.10.19 → 1.10.21
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/docs/ARCHITECTURE.md +20 -0
- package/docs/THIRD_PARTY_NOTICES.md +44 -0
- package/native-skills/media/creative-studio-pipeline/SKILL.md +52 -0
- package/native-skills/media/photo-studio-automation/SKILL.md +53 -0
- package/native-skills/media/video-timeline-assembly/SKILL.md +52 -0
- package/package.json +1 -1
- package/src/agentLoop.js +4 -0
- package/src/appChannel.js +7 -3
- package/src/cliMode.js +33 -67
- package/src/commands.js +56 -8
- package/src/compiledSkills.js +15 -0
- package/src/computerUse/visionGrounding.js +249 -0
- package/src/computerUse/visionGrounding.ts +331 -0
- package/src/config.js +80 -11
- package/src/creative/apps.js +69 -0
- package/src/creative/apps.ts +58 -0
- package/src/creative/blenderBatch.js +233 -0
- package/src/creative/blenderBatch.ts +217 -0
- package/src/creative/pipeline.js +56 -0
- package/src/creative/pipeline.ts +82 -0
- package/src/creative/studioScripts.js +374 -0
- package/src/creative/studioScripts.ts +378 -0
- package/src/decisionPolicy.js +2 -0
- package/src/decisionPolicy.ts +1 -0
- package/src/dynamicCatalogue.js +270 -0
- package/src/dynamicCatalogue.ts +283 -0
- package/src/entrypoint.js +2 -1
- package/src/modelCatalogue.d.ts +5 -0
- package/src/modelCatalogue.js +49 -18
- package/src/providers/anthropic.js +4 -0
- package/src/sandbox.js +8 -1
- package/src/toolPacks.js +6 -3
- package/src/tools/computerUse.js +139 -0
- package/src/tools/computerUse.ts +147 -0
- package/src/tools/creativeApps.js +106 -0
- package/src/tools/creativeApps.ts +99 -0
- package/src/tools/desktopAct.d.ts +10 -0
- package/src/tools/registry.js +5 -1
- package/src/tools/uiForge.js +132 -0
- package/src/tools/uiForge.ts +145 -0
- package/src/tui/banner.js +3 -1
- package/src/tui/banner.ts +3 -1
- package/src/tui/dialogs.js +10 -0
- package/src/tui/home.js +29 -72
- package/src/tui/setup.js +104 -0
- package/src/tui/setup.ts +98 -0
- package/src/uiEngine/uiForge.js +211 -0
- package/src/uiEngine/uiForge.ts +258 -0
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2025 Bytedance, Inc. and its affiliates.
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*
|
|
5
|
+
* Adapted for Causyne Vision Desktop & Coordinate Grounding Engine.
|
|
6
|
+
*/
|
|
7
|
+
const DEFAULT_FACTOR = [1000, 1000];
|
|
8
|
+
/**
|
|
9
|
+
* Extract numbers from a coordinate string like "(250, 400)" or "[100, 200, 300, 400]"
|
|
10
|
+
*/
|
|
11
|
+
export function extractCoordinateNumbers(raw) {
|
|
12
|
+
const clean = String(raw || '').replace(/[()[\]]/g, ' ').trim();
|
|
13
|
+
if (!clean)
|
|
14
|
+
return [];
|
|
15
|
+
return clean
|
|
16
|
+
.split(/[\s,]+/)
|
|
17
|
+
.map((s) => Number.parseFloat(s))
|
|
18
|
+
.filter((n) => Number.isFinite(n));
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Ground normalized coordinates (0-1000) to actual screen pixels.
|
|
22
|
+
*/
|
|
23
|
+
export function groundPoint(nums, screen = { width: 1920, height: 1080, scaleFactor: 1 }, factor = DEFAULT_FACTOR) {
|
|
24
|
+
if (nums.length < 2)
|
|
25
|
+
return null;
|
|
26
|
+
const rawX = nums[0] ?? 0;
|
|
27
|
+
const rawY = nums[1] ?? 0;
|
|
28
|
+
const scale = screen.scaleFactor || 1;
|
|
29
|
+
const normX = Math.max(0, Math.min(1, rawX / factor[0]));
|
|
30
|
+
const normY = Math.max(0, Math.min(1, rawY / factor[1]));
|
|
31
|
+
return {
|
|
32
|
+
x: Math.round(normX * screen.width * scale),
|
|
33
|
+
y: Math.round(normY * screen.height * scale)
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Ground a bounding box (x1, y1, x2, y2) into its midpoint (center click).
|
|
38
|
+
*/
|
|
39
|
+
export function groundBoxCenter(nums, screen = { width: 1920, height: 1080, scaleFactor: 1 }, factor = DEFAULT_FACTOR) {
|
|
40
|
+
if (nums.length < 2)
|
|
41
|
+
return null;
|
|
42
|
+
if (nums.length === 2) {
|
|
43
|
+
return groundPoint(nums, screen, factor);
|
|
44
|
+
}
|
|
45
|
+
const x1 = nums[0] ?? 0;
|
|
46
|
+
const y1 = nums[1] ?? 0;
|
|
47
|
+
const x2 = nums[2] ?? x1;
|
|
48
|
+
const y2 = nums[3] ?? y1;
|
|
49
|
+
const midX = (x1 + x2) / 2;
|
|
50
|
+
const midY = (y1 + y2) / 2;
|
|
51
|
+
return groundPoint([midX, midY], screen, factor);
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Parse vision grounding action format:
|
|
55
|
+
* E.g. Action: click(start_box='(240, 500)')
|
|
56
|
+
*/
|
|
57
|
+
export function parseSingleAction(rawAction, screenContext = { width: 1920, height: 1080, scaleFactor: 1 }) {
|
|
58
|
+
const line = rawAction.trim();
|
|
59
|
+
if (!line)
|
|
60
|
+
return null;
|
|
61
|
+
const callMatch = line.match(/^([a-zA-Z_0-9]+)\s*\((.*)\)$/);
|
|
62
|
+
if (!callMatch) {
|
|
63
|
+
if (/finished/i.test(line)) {
|
|
64
|
+
return { action: 'finished', rawStr: line };
|
|
65
|
+
}
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
const funcName = callMatch[1]?.toLowerCase();
|
|
69
|
+
const argsRaw = callMatch[2] || '';
|
|
70
|
+
const argsMap = {};
|
|
71
|
+
const kvRegex = /([a-zA-Z_0-9]+)\s*=\s*['"]([^'"]+)['"]/g;
|
|
72
|
+
let match;
|
|
73
|
+
while ((match = kvRegex.exec(argsRaw)) !== null) {
|
|
74
|
+
const key = match[1];
|
|
75
|
+
const val = match[2];
|
|
76
|
+
if (key && val) {
|
|
77
|
+
argsMap[key] = val;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
if (Object.keys(argsMap).length === 0) {
|
|
81
|
+
const boxMatch = argsRaw.match(/['"]?(\([0-9\s,.]+\)|\[[0-9\s,.]+\])['"]?/);
|
|
82
|
+
if (boxMatch && boxMatch[1]) {
|
|
83
|
+
argsMap['start_box'] = boxMatch[1];
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
const actionObj = {
|
|
87
|
+
action: funcName,
|
|
88
|
+
rawStr: line
|
|
89
|
+
};
|
|
90
|
+
const startBox = argsMap['start_box'] || argsMap['point'] || argsMap['box'];
|
|
91
|
+
if (startBox) {
|
|
92
|
+
const nums = extractCoordinateNumbers(startBox);
|
|
93
|
+
const center = groundBoxCenter(nums, screenContext);
|
|
94
|
+
if (center) {
|
|
95
|
+
actionObj.coordinates = center;
|
|
96
|
+
actionObj.startCoordinates = center;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
const endBox = argsMap['end_box'];
|
|
100
|
+
if (endBox) {
|
|
101
|
+
const nums = extractCoordinateNumbers(endBox);
|
|
102
|
+
const center = groundBoxCenter(nums, screenContext);
|
|
103
|
+
if (center) {
|
|
104
|
+
actionObj.endCoordinates = center;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
if (argsMap['content']) {
|
|
108
|
+
actionObj.text = argsMap['content'];
|
|
109
|
+
}
|
|
110
|
+
if (argsMap['text']) {
|
|
111
|
+
actionObj.text = argsMap['text'];
|
|
112
|
+
}
|
|
113
|
+
if (argsMap['key']) {
|
|
114
|
+
actionObj.key = argsMap['key'];
|
|
115
|
+
}
|
|
116
|
+
if (argsMap['direction']) {
|
|
117
|
+
const dir = argsMap['direction'].toLowerCase();
|
|
118
|
+
if (dir === 'up' || dir === 'down' || dir === 'left' || dir === 'right') {
|
|
119
|
+
actionObj.direction = dir;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
if (argsMap['time'] || argsMap['seconds']) {
|
|
123
|
+
const sec = Number.parseFloat(argsMap['time'] || argsMap['seconds'] || '1');
|
|
124
|
+
actionObj.seconds = Number.isFinite(sec) ? sec : 1;
|
|
125
|
+
}
|
|
126
|
+
return actionObj;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Parse complete vision grounding prediction text with Thought and Action blocks.
|
|
130
|
+
*/
|
|
131
|
+
export function parseVisionGroundingPrediction(predictionText, screenContext = { width: 1920, height: 1080, scaleFactor: 1 }) {
|
|
132
|
+
let thought;
|
|
133
|
+
let reflection;
|
|
134
|
+
const text = (predictionText || '').trim();
|
|
135
|
+
if (text.includes('Thought:')) {
|
|
136
|
+
const tMatch = text.match(/Thought:\s*([\s\S]+?)(?=\s*Action[::]|$)/i);
|
|
137
|
+
if (tMatch && tMatch[1]) {
|
|
138
|
+
thought = tMatch[1].trim();
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
if (text.includes('Reflection:')) {
|
|
142
|
+
const rMatch = text.match(/Reflection:\s*([\s\S]+?)(?=\s*(?:Action_Summary|Action)[::]|$)/i);
|
|
143
|
+
if (rMatch && rMatch[1]) {
|
|
144
|
+
reflection = rMatch[1].trim();
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
const actionLines = [];
|
|
148
|
+
if (/Action[::]/i.test(text)) {
|
|
149
|
+
const parts = text.split(/Action[::]/i);
|
|
150
|
+
for (let i = 1; i < parts.length; i++) {
|
|
151
|
+
const seg = parts[i] || '';
|
|
152
|
+
const lines = seg.split(/\n+/).map((l) => l.trim()).filter(Boolean);
|
|
153
|
+
actionLines.push(...lines);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
actionLines.push(...text.split(/\n+/).map((l) => l.trim()).filter(Boolean));
|
|
158
|
+
}
|
|
159
|
+
const actions = [];
|
|
160
|
+
for (const line of actionLines) {
|
|
161
|
+
const parsed = parseSingleAction(line, screenContext);
|
|
162
|
+
if (parsed) {
|
|
163
|
+
if (thought && !parsed.thought)
|
|
164
|
+
parsed.thought = thought;
|
|
165
|
+
if (reflection && !parsed.reflection)
|
|
166
|
+
parsed.reflection = reflection;
|
|
167
|
+
actions.push(parsed);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return { thought, reflection, actions };
|
|
171
|
+
}
|
|
172
|
+
const MAC_KEY_CODES = {
|
|
173
|
+
return: 36,
|
|
174
|
+
enter: 36,
|
|
175
|
+
tab: 48,
|
|
176
|
+
space: 49,
|
|
177
|
+
backspace: 51,
|
|
178
|
+
escape: 53,
|
|
179
|
+
esc: 53,
|
|
180
|
+
up: 126,
|
|
181
|
+
down: 125,
|
|
182
|
+
left: 123,
|
|
183
|
+
right: 124
|
|
184
|
+
};
|
|
185
|
+
/**
|
|
186
|
+
* Convert parsed vision grounding actions into Causyne desktop_act equivalent instructions.
|
|
187
|
+
*/
|
|
188
|
+
export function visionGroundingToDesktopAct(actions) {
|
|
189
|
+
const result = [];
|
|
190
|
+
for (const act of actions) {
|
|
191
|
+
switch (act.action) {
|
|
192
|
+
case 'click':
|
|
193
|
+
case 'left_click':
|
|
194
|
+
case 'double_click':
|
|
195
|
+
case 'triple_click': {
|
|
196
|
+
const point = act.coordinates;
|
|
197
|
+
if (point) {
|
|
198
|
+
result.push({
|
|
199
|
+
verb: 'clickxy',
|
|
200
|
+
x: point.x,
|
|
201
|
+
y: point.y
|
|
202
|
+
});
|
|
203
|
+
if (act.action === 'double_click') {
|
|
204
|
+
result.push({ verb: 'clickxy', x: point.x, y: point.y });
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
break;
|
|
208
|
+
}
|
|
209
|
+
case 'type': {
|
|
210
|
+
if (act.coordinates) {
|
|
211
|
+
result.push({ verb: 'clickxy', x: act.coordinates.x, y: act.coordinates.y });
|
|
212
|
+
}
|
|
213
|
+
if (act.text) {
|
|
214
|
+
result.push({ verb: 'type', text: act.text });
|
|
215
|
+
}
|
|
216
|
+
break;
|
|
217
|
+
}
|
|
218
|
+
case 'press_key':
|
|
219
|
+
case 'hotkey': {
|
|
220
|
+
const keyName = (act.key || '').toLowerCase().trim();
|
|
221
|
+
const code = MAC_KEY_CODES[keyName];
|
|
222
|
+
if (code !== undefined) {
|
|
223
|
+
result.push({ verb: 'key', keyCode: code });
|
|
224
|
+
}
|
|
225
|
+
else if (keyName) {
|
|
226
|
+
result.push({ verb: 'type', text: keyName });
|
|
227
|
+
}
|
|
228
|
+
break;
|
|
229
|
+
}
|
|
230
|
+
case 'wait': {
|
|
231
|
+
result.push({ verb: 'wait', seconds: act.seconds || 1 });
|
|
232
|
+
break;
|
|
233
|
+
}
|
|
234
|
+
case 'drag': {
|
|
235
|
+
if (act.startCoordinates) {
|
|
236
|
+
result.push({ verb: 'clickxy', x: act.startCoordinates.x, y: act.startCoordinates.y });
|
|
237
|
+
}
|
|
238
|
+
if (act.endCoordinates) {
|
|
239
|
+
result.push({ verb: 'clickxy', x: act.endCoordinates.x, y: act.endCoordinates.y });
|
|
240
|
+
}
|
|
241
|
+
break;
|
|
242
|
+
}
|
|
243
|
+
case 'finished':
|
|
244
|
+
default:
|
|
245
|
+
break;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
return result;
|
|
249
|
+
}
|
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2025 Bytedance, Inc. and its affiliates.
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*
|
|
5
|
+
* Adapted for Causyne Vision Desktop & Coordinate Grounding Engine.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export interface ScreenContext {
|
|
9
|
+
width: number;
|
|
10
|
+
height: number;
|
|
11
|
+
scaleFactor?: number;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export type ActionVerb =
|
|
15
|
+
| 'click'
|
|
16
|
+
| 'left_click'
|
|
17
|
+
| 'right_click'
|
|
18
|
+
| 'double_click'
|
|
19
|
+
| 'triple_click'
|
|
20
|
+
| 'middle_click'
|
|
21
|
+
| 'mouse_down'
|
|
22
|
+
| 'mouse_up'
|
|
23
|
+
| 'drag'
|
|
24
|
+
| 'move'
|
|
25
|
+
| 'type'
|
|
26
|
+
| 'press_key'
|
|
27
|
+
| 'hotkey'
|
|
28
|
+
| 'scroll'
|
|
29
|
+
| 'wait'
|
|
30
|
+
| 'finished';
|
|
31
|
+
|
|
32
|
+
export interface VisionAction {
|
|
33
|
+
action: ActionVerb;
|
|
34
|
+
thought?: string;
|
|
35
|
+
reflection?: string;
|
|
36
|
+
coordinates?: { x: number; y: number };
|
|
37
|
+
startCoordinates?: { x: number; y: number };
|
|
38
|
+
endCoordinates?: { x: number; y: number };
|
|
39
|
+
text?: string;
|
|
40
|
+
key?: string;
|
|
41
|
+
direction?: 'up' | 'down' | 'left' | 'right';
|
|
42
|
+
seconds?: number;
|
|
43
|
+
rawStr?: string;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface DesktopActEquivalent {
|
|
47
|
+
verb: 'press' | 'clickxy' | 'type' | 'key' | 'wait';
|
|
48
|
+
x?: number;
|
|
49
|
+
y?: number;
|
|
50
|
+
text?: string;
|
|
51
|
+
keyCode?: number;
|
|
52
|
+
target?: string;
|
|
53
|
+
seconds?: number;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const DEFAULT_FACTOR: [number, number] = [1000, 1000];
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Extract numbers from a coordinate string like "(250, 400)" or "[100, 200, 300, 400]"
|
|
60
|
+
*/
|
|
61
|
+
export function extractCoordinateNumbers(raw: string): number[] {
|
|
62
|
+
const clean = String(raw || '').replace(/[()[\]]/g, ' ').trim();
|
|
63
|
+
if (!clean) return [];
|
|
64
|
+
return clean
|
|
65
|
+
.split(/[\s,]+/)
|
|
66
|
+
.map((s) => Number.parseFloat(s))
|
|
67
|
+
.filter((n) => Number.isFinite(n));
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Ground normalized coordinates (0-1000) to actual screen pixels.
|
|
72
|
+
*/
|
|
73
|
+
export function groundPoint(
|
|
74
|
+
nums: number[],
|
|
75
|
+
screen: ScreenContext = { width: 1920, height: 1080, scaleFactor: 1 },
|
|
76
|
+
factor: [number, number] = DEFAULT_FACTOR
|
|
77
|
+
): { x: number; y: number } | null {
|
|
78
|
+
if (nums.length < 2) return null;
|
|
79
|
+
const rawX = nums[0] ?? 0;
|
|
80
|
+
const rawY = nums[1] ?? 0;
|
|
81
|
+
const scale = screen.scaleFactor || 1;
|
|
82
|
+
|
|
83
|
+
const normX = Math.max(0, Math.min(1, rawX / factor[0]));
|
|
84
|
+
const normY = Math.max(0, Math.min(1, rawY / factor[1]));
|
|
85
|
+
|
|
86
|
+
return {
|
|
87
|
+
x: Math.round(normX * screen.width * scale),
|
|
88
|
+
y: Math.round(normY * screen.height * scale)
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Ground a bounding box (x1, y1, x2, y2) into its midpoint (center click).
|
|
94
|
+
*/
|
|
95
|
+
export function groundBoxCenter(
|
|
96
|
+
nums: number[],
|
|
97
|
+
screen: ScreenContext = { width: 1920, height: 1080, scaleFactor: 1 },
|
|
98
|
+
factor: [number, number] = DEFAULT_FACTOR
|
|
99
|
+
): { x: number; y: number } | null {
|
|
100
|
+
if (nums.length < 2) return null;
|
|
101
|
+
if (nums.length === 2) {
|
|
102
|
+
return groundPoint(nums, screen, factor);
|
|
103
|
+
}
|
|
104
|
+
const x1 = nums[0] ?? 0;
|
|
105
|
+
const y1 = nums[1] ?? 0;
|
|
106
|
+
const x2 = nums[2] ?? x1;
|
|
107
|
+
const y2 = nums[3] ?? y1;
|
|
108
|
+
|
|
109
|
+
const midX = (x1 + x2) / 2;
|
|
110
|
+
const midY = (y1 + y2) / 2;
|
|
111
|
+
return groundPoint([midX, midY], screen, factor);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Parse vision grounding action format:
|
|
116
|
+
* E.g. Action: click(start_box='(240, 500)')
|
|
117
|
+
*/
|
|
118
|
+
export function parseSingleAction(
|
|
119
|
+
rawAction: string,
|
|
120
|
+
screenContext: ScreenContext = { width: 1920, height: 1080, scaleFactor: 1 }
|
|
121
|
+
): VisionAction | null {
|
|
122
|
+
const line = rawAction.trim();
|
|
123
|
+
if (!line) return null;
|
|
124
|
+
|
|
125
|
+
const callMatch = line.match(/^([a-zA-Z_0-9]+)\s*\((.*)\)$/);
|
|
126
|
+
if (!callMatch) {
|
|
127
|
+
if (/finished/i.test(line)) {
|
|
128
|
+
return { action: 'finished', rawStr: line };
|
|
129
|
+
}
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const funcName = callMatch[1]?.toLowerCase() as ActionVerb;
|
|
134
|
+
const argsRaw = callMatch[2] || '';
|
|
135
|
+
|
|
136
|
+
const argsMap: Record<string, string> = {};
|
|
137
|
+
const kvRegex = /([a-zA-Z_0-9]+)\s*=\s*['"]([^'"]+)['"]/g;
|
|
138
|
+
let match: RegExpExecArray | null;
|
|
139
|
+
while ((match = kvRegex.exec(argsRaw)) !== null) {
|
|
140
|
+
const key = match[1];
|
|
141
|
+
const val = match[2];
|
|
142
|
+
if (key && val) {
|
|
143
|
+
argsMap[key] = val;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (Object.keys(argsMap).length === 0) {
|
|
148
|
+
const boxMatch = argsRaw.match(/['"]?(\([0-9\s,.]+\)|\[[0-9\s,.]+\])['"]?/);
|
|
149
|
+
if (boxMatch && boxMatch[1]) {
|
|
150
|
+
argsMap['start_box'] = boxMatch[1];
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const actionObj: VisionAction = {
|
|
155
|
+
action: funcName,
|
|
156
|
+
rawStr: line
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
const startBox = argsMap['start_box'] || argsMap['point'] || argsMap['box'];
|
|
160
|
+
if (startBox) {
|
|
161
|
+
const nums = extractCoordinateNumbers(startBox);
|
|
162
|
+
const center = groundBoxCenter(nums, screenContext);
|
|
163
|
+
if (center) {
|
|
164
|
+
actionObj.coordinates = center;
|
|
165
|
+
actionObj.startCoordinates = center;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const endBox = argsMap['end_box'];
|
|
170
|
+
if (endBox) {
|
|
171
|
+
const nums = extractCoordinateNumbers(endBox);
|
|
172
|
+
const center = groundBoxCenter(nums, screenContext);
|
|
173
|
+
if (center) {
|
|
174
|
+
actionObj.endCoordinates = center;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (argsMap['content']) {
|
|
179
|
+
actionObj.text = argsMap['content'];
|
|
180
|
+
}
|
|
181
|
+
if (argsMap['text']) {
|
|
182
|
+
actionObj.text = argsMap['text'];
|
|
183
|
+
}
|
|
184
|
+
if (argsMap['key']) {
|
|
185
|
+
actionObj.key = argsMap['key'];
|
|
186
|
+
}
|
|
187
|
+
if (argsMap['direction']) {
|
|
188
|
+
const dir = argsMap['direction'].toLowerCase();
|
|
189
|
+
if (dir === 'up' || dir === 'down' || dir === 'left' || dir === 'right') {
|
|
190
|
+
actionObj.direction = dir;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
if (argsMap['time'] || argsMap['seconds']) {
|
|
194
|
+
const sec = Number.parseFloat(argsMap['time'] || argsMap['seconds'] || '1');
|
|
195
|
+
actionObj.seconds = Number.isFinite(sec) ? sec : 1;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return actionObj;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Parse complete vision grounding prediction text with Thought and Action blocks.
|
|
203
|
+
*/
|
|
204
|
+
export function parseVisionGroundingPrediction(
|
|
205
|
+
predictionText: string,
|
|
206
|
+
screenContext: ScreenContext = { width: 1920, height: 1080, scaleFactor: 1 }
|
|
207
|
+
): { thought?: string | undefined; reflection?: string | undefined; actions: VisionAction[] } {
|
|
208
|
+
let thought: string | undefined;
|
|
209
|
+
let reflection: string | undefined;
|
|
210
|
+
|
|
211
|
+
const text = (predictionText || '').trim();
|
|
212
|
+
|
|
213
|
+
if (text.includes('Thought:')) {
|
|
214
|
+
const tMatch = text.match(/Thought:\s*([\s\S]+?)(?=\s*Action[::]|$)/i);
|
|
215
|
+
if (tMatch && tMatch[1]) {
|
|
216
|
+
thought = tMatch[1].trim();
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
if (text.includes('Reflection:')) {
|
|
220
|
+
const rMatch = text.match(/Reflection:\s*([\s\S]+?)(?=\s*(?:Action_Summary|Action)[::]|$)/i);
|
|
221
|
+
if (rMatch && rMatch[1]) {
|
|
222
|
+
reflection = rMatch[1].trim();
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const actionLines: string[] = [];
|
|
227
|
+
if (/Action[::]/i.test(text)) {
|
|
228
|
+
const parts = text.split(/Action[::]/i);
|
|
229
|
+
for (let i = 1; i < parts.length; i++) {
|
|
230
|
+
const seg = parts[i] || '';
|
|
231
|
+
const lines = seg.split(/\n+/).map((l) => l.trim()).filter(Boolean);
|
|
232
|
+
actionLines.push(...lines);
|
|
233
|
+
}
|
|
234
|
+
} else {
|
|
235
|
+
actionLines.push(...text.split(/\n+/).map((l) => l.trim()).filter(Boolean));
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const actions: VisionAction[] = [];
|
|
239
|
+
|
|
240
|
+
for (const line of actionLines) {
|
|
241
|
+
const parsed = parseSingleAction(line, screenContext);
|
|
242
|
+
if (parsed) {
|
|
243
|
+
if (thought && !parsed.thought) parsed.thought = thought;
|
|
244
|
+
if (reflection && !parsed.reflection) parsed.reflection = reflection;
|
|
245
|
+
actions.push(parsed);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
return { thought, reflection, actions };
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const MAC_KEY_CODES: Record<string, number> = {
|
|
253
|
+
return: 36,
|
|
254
|
+
enter: 36,
|
|
255
|
+
tab: 48,
|
|
256
|
+
space: 49,
|
|
257
|
+
backspace: 51,
|
|
258
|
+
escape: 53,
|
|
259
|
+
esc: 53,
|
|
260
|
+
up: 126,
|
|
261
|
+
down: 125,
|
|
262
|
+
left: 123,
|
|
263
|
+
right: 124
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Convert parsed vision grounding actions into Causyne desktop_act equivalent instructions.
|
|
268
|
+
*/
|
|
269
|
+
export function visionGroundingToDesktopAct(actions: VisionAction[]): DesktopActEquivalent[] {
|
|
270
|
+
const result: DesktopActEquivalent[] = [];
|
|
271
|
+
|
|
272
|
+
for (const act of actions) {
|
|
273
|
+
switch (act.action) {
|
|
274
|
+
case 'click':
|
|
275
|
+
case 'left_click':
|
|
276
|
+
case 'double_click':
|
|
277
|
+
case 'triple_click': {
|
|
278
|
+
const point = act.coordinates;
|
|
279
|
+
if (point) {
|
|
280
|
+
result.push({
|
|
281
|
+
verb: 'clickxy',
|
|
282
|
+
x: point.x,
|
|
283
|
+
y: point.y
|
|
284
|
+
});
|
|
285
|
+
if (act.action === 'double_click') {
|
|
286
|
+
result.push({ verb: 'clickxy', x: point.x, y: point.y });
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
break;
|
|
290
|
+
}
|
|
291
|
+
case 'type': {
|
|
292
|
+
if (act.coordinates) {
|
|
293
|
+
result.push({ verb: 'clickxy', x: act.coordinates.x, y: act.coordinates.y });
|
|
294
|
+
}
|
|
295
|
+
if (act.text) {
|
|
296
|
+
result.push({ verb: 'type', text: act.text });
|
|
297
|
+
}
|
|
298
|
+
break;
|
|
299
|
+
}
|
|
300
|
+
case 'press_key':
|
|
301
|
+
case 'hotkey': {
|
|
302
|
+
const keyName = (act.key || '').toLowerCase().trim();
|
|
303
|
+
const code = MAC_KEY_CODES[keyName];
|
|
304
|
+
if (code !== undefined) {
|
|
305
|
+
result.push({ verb: 'key', keyCode: code });
|
|
306
|
+
} else if (keyName) {
|
|
307
|
+
result.push({ verb: 'type', text: keyName });
|
|
308
|
+
}
|
|
309
|
+
break;
|
|
310
|
+
}
|
|
311
|
+
case 'wait': {
|
|
312
|
+
result.push({ verb: 'wait', seconds: act.seconds || 1 });
|
|
313
|
+
break;
|
|
314
|
+
}
|
|
315
|
+
case 'drag': {
|
|
316
|
+
if (act.startCoordinates) {
|
|
317
|
+
result.push({ verb: 'clickxy', x: act.startCoordinates.x, y: act.startCoordinates.y });
|
|
318
|
+
}
|
|
319
|
+
if (act.endCoordinates) {
|
|
320
|
+
result.push({ verb: 'clickxy', x: act.endCoordinates.x, y: act.endCoordinates.y });
|
|
321
|
+
}
|
|
322
|
+
break;
|
|
323
|
+
}
|
|
324
|
+
case 'finished':
|
|
325
|
+
default:
|
|
326
|
+
break;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
return result;
|
|
331
|
+
}
|
package/src/config.js
CHANGED
|
@@ -78,30 +78,41 @@ export function loadConfig() {
|
|
|
78
78
|
}
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
-
// Self-heal a model id written before src/modelIds.js existed.
|
|
82
|
-
// provider normalises too, but tokenTracker.getPricing() and the /model
|
|
83
|
-
// display both read config.model directly —
|
|
84
|
-
// they must agree with what is actually sent, not with what a stale
|
|
85
|
-
// Android build once wrote here.
|
|
81
|
+
// Self-heal a model id written before src/modelIds.js existed.
|
|
86
82
|
const canonicalModel = canonicalModelId(parsed.model);
|
|
87
83
|
if (canonicalModel !== parsed.model) {
|
|
88
84
|
parsed.model = canonicalModel;
|
|
89
85
|
saveConfig(parsed);
|
|
90
86
|
}
|
|
91
87
|
|
|
88
|
+
// Self-heal legacy unconfigured DashScope bug:
|
|
89
|
+
// If config was written with provider 'dashscope' or 'dashscope-byok' but without an apiKey,
|
|
90
|
+
// it was an unintended default from older builds. Heal to an environment key or unconfigured.
|
|
91
|
+
if ((parsed.provider === 'dashscope' || parsed.provider === 'dashscope-byok') && !parsed.apiKey) {
|
|
92
|
+
const envDetected = detectEnvironmentProvider();
|
|
93
|
+
if (envDetected) {
|
|
94
|
+
parsed.provider = envDetected.provider;
|
|
95
|
+
parsed.model = envDetected.model;
|
|
96
|
+
parsed.apiKey = envDetected.apiKey;
|
|
97
|
+
} else {
|
|
98
|
+
parsed.provider = '';
|
|
99
|
+
parsed.model = '';
|
|
100
|
+
}
|
|
101
|
+
saveConfig(parsed);
|
|
102
|
+
}
|
|
103
|
+
|
|
92
104
|
return parsed;
|
|
93
105
|
} catch (e) {
|
|
94
106
|
console.error('Error reading config.json:', e);
|
|
95
107
|
}
|
|
96
108
|
}
|
|
97
109
|
|
|
98
|
-
|
|
110
|
+
// Default config: auto-detect from environment if available, otherwise unconfigured
|
|
111
|
+
const envDetected = detectEnvironmentProvider();
|
|
99
112
|
const defaultConfig = {
|
|
100
|
-
provider: '
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
model: DEFAULT_MODEL_BY_PROVIDER.dashscope,
|
|
104
|
-
apiKey: '',
|
|
113
|
+
provider: envDetected ? envDetected.provider : '',
|
|
114
|
+
model: envDetected ? envDetected.model : '',
|
|
115
|
+
apiKey: envDetected ? envDetected.apiKey : '',
|
|
105
116
|
autonomyMode: 'B',
|
|
106
117
|
tokenBudget: {
|
|
107
118
|
enabled: false,
|
|
@@ -116,6 +127,64 @@ export function loadConfig() {
|
|
|
116
127
|
return defaultConfig;
|
|
117
128
|
}
|
|
118
129
|
|
|
130
|
+
/**
|
|
131
|
+
* Auto-detect available API keys from environment variables.
|
|
132
|
+
* Precedence matches frontier capability: Gemini, Anthropic, OpenAI, DeepSeek, xAI, Moonshot, Qwen/DashScope.
|
|
133
|
+
*/
|
|
134
|
+
export function detectEnvironmentProvider() {
|
|
135
|
+
const env = process.env;
|
|
136
|
+
if (env.GEMINI_API_KEY || env.GOOGLE_API_KEY) {
|
|
137
|
+
return {
|
|
138
|
+
provider: 'gemini',
|
|
139
|
+
apiKey: env.GEMINI_API_KEY || env.GOOGLE_API_KEY || '',
|
|
140
|
+
model: DEFAULT_MODEL_BY_PROVIDER.gemini
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
if (env.ANTHROPIC_API_KEY) {
|
|
144
|
+
return {
|
|
145
|
+
provider: 'anthropic',
|
|
146
|
+
apiKey: env.ANTHROPIC_API_KEY,
|
|
147
|
+
model: DEFAULT_MODEL_BY_PROVIDER.anthropic
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
if (env.OPENAI_API_KEY) {
|
|
151
|
+
return {
|
|
152
|
+
provider: 'openai',
|
|
153
|
+
apiKey: env.OPENAI_API_KEY,
|
|
154
|
+
model: DEFAULT_MODEL_BY_PROVIDER.openai
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
if (env.DEEPSEEK_API_KEY) {
|
|
158
|
+
return {
|
|
159
|
+
provider: 'deepseek',
|
|
160
|
+
apiKey: env.DEEPSEEK_API_KEY,
|
|
161
|
+
model: DEFAULT_MODEL_BY_PROVIDER.deepseek
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
if (env.XAI_API_KEY || env.GROK_API_KEY) {
|
|
165
|
+
return {
|
|
166
|
+
provider: 'xai',
|
|
167
|
+
apiKey: env.XAI_API_KEY || env.GROK_API_KEY || '',
|
|
168
|
+
model: DEFAULT_MODEL_BY_PROVIDER.xai
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
if (env.MOONSHOT_API_KEY) {
|
|
172
|
+
return {
|
|
173
|
+
provider: 'moonshot',
|
|
174
|
+
apiKey: env.MOONSHOT_API_KEY,
|
|
175
|
+
model: DEFAULT_MODEL_BY_PROVIDER.moonshot
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
if (env.DASHSCOPE_API_KEY || env.QWEN_API_KEY) {
|
|
179
|
+
return {
|
|
180
|
+
provider: 'dashscope-byok',
|
|
181
|
+
apiKey: env.DASHSCOPE_API_KEY || env.QWEN_API_KEY || '',
|
|
182
|
+
model: DEFAULT_MODEL_BY_PROVIDER['dashscope-byok']
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
return null;
|
|
186
|
+
}
|
|
187
|
+
|
|
119
188
|
export function saveConfig(config) {
|
|
120
189
|
ensureConfigDir();
|
|
121
190
|
// SECURITY: Deep copy to avoid mutating the in-memory config
|