codeloop-mcp-server 0.1.49 → 0.1.50
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/auth/critical_floors.d.ts.map +1 -1
- package/dist/auth/critical_floors.js +4 -0
- package/dist/auth/critical_floors.js.map +1 -1
- package/dist/index.js +125 -44
- package/dist/index.js.map +1 -1
- package/dist/runners/resolve_project_dir.d.ts +67 -0
- package/dist/runners/resolve_project_dir.d.ts.map +1 -0
- package/dist/runners/resolve_project_dir.js +82 -0
- package/dist/runners/resolve_project_dir.js.map +1 -0
- package/dist/runners/screenshot.d.ts.map +1 -1
- package/dist/runners/screenshot.js +17 -2
- package/dist/runners/screenshot.js.map +1 -1
- package/dist/runners/uia_resolver.d.ts +70 -0
- package/dist/runners/uia_resolver.d.ts.map +1 -0
- package/dist/runners/uia_resolver.js +210 -0
- package/dist/runners/uia_resolver.js.map +1 -0
- package/dist/runners/window_manager.d.ts +28 -0
- package/dist/runners/window_manager.d.ts.map +1 -1
- package/dist/runners/window_manager.js +119 -4
- package/dist/runners/window_manager.js.map +1 -1
- package/dist/tools/design_compare.d.ts.map +1 -1
- package/dist/tools/design_compare.js +71 -33
- package/dist/tools/design_compare.js.map +1 -1
- package/dist/tools/diagnose.d.ts.map +1 -1
- package/dist/tools/diagnose.js +32 -1
- package/dist/tools/diagnose.js.map +1 -1
- package/dist/tools/discover_screens.d.ts.map +1 -1
- package/dist/tools/discover_screens.js +94 -2
- package/dist/tools/discover_screens.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"critical_floors.d.ts","sourceRoot":"","sources":["../../src/auth/critical_floors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,MAAM,WAAW,aAAa;IAC5B,4DAA4D;IAC5D,WAAW,EAAE,MAAM,CAAC;IACpB,wDAAwD;IACxD,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,eAAe,EAAE,aAAa,
|
|
1
|
+
{"version":3,"file":"critical_floors.d.ts","sourceRoot":"","sources":["../../src/auth/critical_floors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,MAAM,WAAW,aAAa;IAC5B,4DAA4D;IAC5D,WAAW,EAAE,MAAM,CAAC;IACpB,wDAAwD;IACxD,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,eAAe,EAAE,aAAa,EAwB1C,CAAC"}
|
|
@@ -56,5 +56,9 @@ export const CRITICAL_FLOORS = [
|
|
|
56
56
|
min_version: "0.1.49",
|
|
57
57
|
reason: "Cross-stack desktop-app coverage + coord-DPI fixes — pre-0.1.49 builds skipped desktopAppMode for Electron / Tauri / React Native / Flutter desktop projects (silent IDE capture recurred), didn't scale agent-supplied screenshot coords or apply Windows DPI factors (clicks missed by 2× on high-DPI displays), missed MSIX / Store-installed desktop apps and custom .NET <OutputPath> values (codeloop_launch_app returned 'no .exe found'), only classified one specific pngjs error as a skip (truncated IDAT / bad CRC / malformed IHDR still dragged the gate to 0%), surfaced coordinate_clicks_without_intent as a soft warning AFTER the gate failed instead of a hard step-7 PENDING blocker BEFORE gate_check, and lacked codeloop_self_test for pre-flight validation against any project's stack",
|
|
58
58
|
},
|
|
59
|
+
{
|
|
60
|
+
min_version: "0.1.50",
|
|
61
|
+
reason: "WPF / desktop final-mile fixes — pre-0.1.50 builds returned `(undefined, undefined)` when codeloop_interact was called with `text` / `role` / `automation_id` selectors instead of raw x/y on Windows desktop targets (every WPF tab/list-item click missed because the runner had no UIA tree walker for selectors), accepted weak cross-run design_compare matches and scored unrelated screens at 0% (e.g. `10-led-bom_*` paired with `led-design-bom-add-component`), wrote screenshot artifacts under the user's HOME folder when project_dir was omitted on Cursor-launched MCP servers, returned empty arrays from codeloop_discover_screens for desktop projects (designs/desktop/*.png never surfaced), classified MSB3027 / MSB3021 file-locked build errors as `issue_unclassified` (agents looped on the same locked-EXE forever), lacked `codeloop doctor --prune-artifacts` to clean up old corrupt-PNG runs, and surfaced bare 'App window not found' errors with no candidates / next_step diagnostic when the priority ladder exhausted",
|
|
62
|
+
},
|
|
59
63
|
];
|
|
60
64
|
//# sourceMappingURL=critical_floors.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"critical_floors.js","sourceRoot":"","sources":["../../src/auth/critical_floors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AASH;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,MAAM,eAAe,GAAoB;IAC9C;QACE,WAAW,EAAE,QAAQ;QACrB,MAAM,EACJ,ufAAuf;KAC1f;IACD;QACE,WAAW,EAAE,QAAQ;QACrB,MAAM,EAAE,4hBAA4hB;KACriB;IACD;QACE,WAAW,EAAE,QAAQ;QACrB,MAAM,EAAE,yvBAAyvB;KAClwB;IACD;QACE,WAAW,EAAE,QAAQ;QACrB,MAAM,EACJ,kxBAAkxB;KACrxB;CACF,CAAC"}
|
|
1
|
+
{"version":3,"file":"critical_floors.js","sourceRoot":"","sources":["../../src/auth/critical_floors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AASH;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,MAAM,eAAe,GAAoB;IAC9C;QACE,WAAW,EAAE,QAAQ;QACrB,MAAM,EACJ,ufAAuf;KAC1f;IACD;QACE,WAAW,EAAE,QAAQ;QACrB,MAAM,EAAE,4hBAA4hB;KACriB;IACD;QACE,WAAW,EAAE,QAAQ;QACrB,MAAM,EAAE,yvBAAyvB;KAClwB;IACD;QACE,WAAW,EAAE,QAAQ;QACrB,MAAM,EACJ,kxBAAkxB;KACrxB;IACD;QACE,WAAW,EAAE,QAAQ;QACrB,MAAM,EACJ,0/BAA0/B;KAC7/B;CACF,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -26,6 +26,7 @@ import { applyUpdate, applyUpdateInputSchema, } from "./tools/apply_update.js";
|
|
|
26
26
|
import { trackUsage } from "./auth/usage_tracker.js";
|
|
27
27
|
import { isLocalMode } from "./auth/local_mode.js";
|
|
28
28
|
import { discoverProjectDir } from "./project-discovery.js";
|
|
29
|
+
import { resolveProjectDirPath } from "./runners/resolve_project_dir.js";
|
|
29
30
|
function readImageAsBase64(path) {
|
|
30
31
|
if (!existsSync(path))
|
|
31
32
|
return null;
|
|
@@ -61,6 +62,18 @@ function mimeForPath(path) {
|
|
|
61
62
|
// when the server's auto-discovered fallback is uninitialized.
|
|
62
63
|
const discovery = discoverProjectDir();
|
|
63
64
|
const projectDir = discovery.projectDir;
|
|
65
|
+
// 0.1.50 H4 — single helper that applies the project_dir precedence
|
|
66
|
+
// ladder (explicit > workspace_root > active recording > env > walked_up
|
|
67
|
+
// > default). Used by every capture / interact / record / replay / etc
|
|
68
|
+
// handler so we can't drift back to the Photometry-DB regression where
|
|
69
|
+
// missing project_dir wrote artifacts to the user's HOME folder.
|
|
70
|
+
function resolveCwd(params) {
|
|
71
|
+
return resolveProjectDirPath({
|
|
72
|
+
project_dir: params.project_dir,
|
|
73
|
+
workspace_root: params.workspace_root,
|
|
74
|
+
default_dir: projectDir,
|
|
75
|
+
});
|
|
76
|
+
}
|
|
64
77
|
if (discovery.source !== "cwd" && discovery.source !== "env") {
|
|
65
78
|
console.error(`[CodeLoop] Auto-discovered project at: ${projectDir} (via ${discovery.source} search)`);
|
|
66
79
|
}
|
|
@@ -456,7 +469,7 @@ Returns: structured report with pass/fail counts, artifact paths, and next-step
|
|
|
456
469
|
project_dir: z.string().optional().describe("Absolute path to the project root. Defaults to CODELOOP_PROJECT_DIR env var or auto-discovered project directory. MUST be an actual project folder — passing the user's home directory is rejected. If your IDE launches the MCP server from the wrong cwd (common on Windows where Cursor uses C:\\Users\\<name> as cwd), set CODELOOP_PROJECT_DIR or pass this param explicitly."),
|
|
457
470
|
workspace_root: z.string().optional().describe("[Alias for project_dir] Same semantics; accepted because many agents reach for this conventional name. Pass either `project_dir` OR `workspace_root` — they're equivalent."),
|
|
458
471
|
}, async (params) => {
|
|
459
|
-
const cwd = (params
|
|
472
|
+
const cwd = resolveCwd(params);
|
|
460
473
|
const explicitDir = params.project_dir || params.workspace_root;
|
|
461
474
|
const cfg = explicitDir ? loadConfig(explicitDir) : config;
|
|
462
475
|
const result = await withAuth(async () => {
|
|
@@ -544,11 +557,11 @@ Returns: categorized issues with severity, evidence, root cause, and actionable
|
|
|
544
557
|
run_id: params.run_id,
|
|
545
558
|
focus_files: params.focus_files,
|
|
546
559
|
};
|
|
547
|
-
const cwd = (params
|
|
560
|
+
const cwd = resolveCwd(params);
|
|
548
561
|
const output = await runDiagnose(input, config, cwd);
|
|
549
562
|
await trackUsage(apiKey, "verification_run");
|
|
550
563
|
return output;
|
|
551
|
-
}, { tool: "codeloop_diagnose", cwd: (params
|
|
564
|
+
}, { tool: "codeloop_diagnose", cwd: resolveCwd(params), input: params });
|
|
552
565
|
// Auto-fix-loop directive. Diagnose is only useful when it leads
|
|
553
566
|
// to a fix + re-verify, not when it leads to a long deliberation
|
|
554
567
|
// over which repair to do first. The repair_tasks array in the
|
|
@@ -612,7 +625,7 @@ Returns: pass/fail for each gate, overall confidence score, and recommendation.`
|
|
|
612
625
|
spec_path: params.spec_path,
|
|
613
626
|
acceptance_path: params.acceptance_path,
|
|
614
627
|
};
|
|
615
|
-
const cwd = (params
|
|
628
|
+
const cwd = resolveCwd(params);
|
|
616
629
|
const output = await runGateCheck(input, config, cwd);
|
|
617
630
|
// Persist gate_result and confidence to meta.json
|
|
618
631
|
try {
|
|
@@ -632,7 +645,7 @@ Returns: pass/fail for each gate, overall confidence score, and recommendation.`
|
|
|
632
645
|
catch { /* best-effort persistence */ }
|
|
633
646
|
await trackUsage(apiKey, "verification_run");
|
|
634
647
|
return output;
|
|
635
|
-
}, { tool: "codeloop_gate_check", cwd: (params
|
|
648
|
+
}, { tool: "codeloop_gate_check", cwd: resolveCwd(params), input: params });
|
|
636
649
|
const resultJson = JSON.stringify(result, null, 2);
|
|
637
650
|
const gateResult = result;
|
|
638
651
|
if (gateResult.recommendation === "continue_fixing") {
|
|
@@ -690,11 +703,11 @@ Returns: pass/fail for each gate, overall confidence score, and recommendation.`
|
|
|
690
703
|
"INCOMPLETE CRUD ARC is NEVER a reason to stop — call codeloop_plan_user_journey, follow the returned per-entity script, re-record, THEN re-gate.",
|
|
691
704
|
].join("\n");
|
|
692
705
|
return {
|
|
693
|
-
content: withInitHint([{ type: "text", text: resultJson + loopDirective }], (params
|
|
706
|
+
content: withInitHint([{ type: "text", text: resultJson + loopDirective }], resolveCwd(params)),
|
|
694
707
|
};
|
|
695
708
|
}
|
|
696
709
|
return {
|
|
697
|
-
content: withInitHint([{ type: "text", text: resultJson }], (params
|
|
710
|
+
content: withInitHint([{ type: "text", text: resultJson }], resolveCwd(params)),
|
|
698
711
|
};
|
|
699
712
|
});
|
|
700
713
|
// ── Vision Tools (agent-delegated: returns images for AI agent analysis) ──
|
|
@@ -721,11 +734,11 @@ Returns: deterministic diff results + screenshot images for visual analysis.`, {
|
|
|
721
734
|
ux_checklist_path: params.ux_checklist_path,
|
|
722
735
|
viewport_sizes: params.viewport_sizes,
|
|
723
736
|
};
|
|
724
|
-
const cwd = (params
|
|
737
|
+
const cwd = resolveCwd(params);
|
|
725
738
|
const result = await runVisualReview(input, config, cwd);
|
|
726
739
|
await trackUsage(apiKey, "visual_review");
|
|
727
740
|
return result;
|
|
728
|
-
}, { tool: "codeloop_visual_review", cwd: (params
|
|
741
|
+
}, { tool: "codeloop_visual_review", cwd: resolveCwd(params), input: params });
|
|
729
742
|
if (typeof authResult === "object" && authResult !== null && "error" in authResult) {
|
|
730
743
|
return { content: [{ type: "text", text: JSON.stringify(authResult, null, 2) }] };
|
|
731
744
|
}
|
|
@@ -806,11 +819,11 @@ Returns: per-screen pixel diff scores + worst-failing reference, actual, and dif
|
|
|
806
819
|
designs_dir: params.designs_dir,
|
|
807
820
|
run_id: params.run_id,
|
|
808
821
|
};
|
|
809
|
-
const cwd = (params
|
|
822
|
+
const cwd = resolveCwd(params);
|
|
810
823
|
const result = await runDesignCompare(input, config, cwd);
|
|
811
824
|
await trackUsage(apiKey, "visual_review");
|
|
812
825
|
return result;
|
|
813
|
-
}, { tool: "codeloop_design_compare", cwd: (params
|
|
826
|
+
}, { tool: "codeloop_design_compare", cwd: resolveCwd(params), input: params });
|
|
814
827
|
if (typeof authResult === "object" && authResult !== null && "error" in authResult) {
|
|
815
828
|
return { content: [{ type: "text", text: JSON.stringify(authResult, null, 2) }] };
|
|
816
829
|
}
|
|
@@ -1125,7 +1138,7 @@ Returns: extracted key frames as images + expected flow description + app logs f
|
|
|
1125
1138
|
}, async (params) => {
|
|
1126
1139
|
const authResult = await withAuth(async () => {
|
|
1127
1140
|
const { runInteractionReplay } = await import("./tools/interaction_replay.js");
|
|
1128
|
-
const cwd = (params
|
|
1141
|
+
const cwd = resolveCwd(params);
|
|
1129
1142
|
const output = await runInteractionReplay({
|
|
1130
1143
|
video_path: params.video_path,
|
|
1131
1144
|
run_id: params.run_id,
|
|
@@ -1133,7 +1146,7 @@ Returns: extracted key frames as images + expected flow description + app logs f
|
|
|
1133
1146
|
}, config, cwd);
|
|
1134
1147
|
await trackUsage(apiKey, "visual_review");
|
|
1135
1148
|
return output;
|
|
1136
|
-
}, { tool: "codeloop_interaction_replay", cwd: (params
|
|
1149
|
+
}, { tool: "codeloop_interaction_replay", cwd: resolveCwd(params), input: params });
|
|
1137
1150
|
if (typeof authResult === "object" && authResult !== null && "error" in authResult) {
|
|
1138
1151
|
return { content: [{ type: "text", text: JSON.stringify(authResult, null, 2) }] };
|
|
1139
1152
|
}
|
|
@@ -1242,7 +1255,7 @@ Returns: confirmation + the captured image as an MCP ImageContent block so you c
|
|
|
1242
1255
|
const authResult = await withAuth(async () => {
|
|
1243
1256
|
const { captureScreenshot } = await import("./runners/screenshot.js");
|
|
1244
1257
|
const { createRunDir, getRunDir, getArtifactsBaseDir } = await import("./evidence/artifacts.js");
|
|
1245
|
-
const cwd = (params
|
|
1258
|
+
const cwd = resolveCwd(params);
|
|
1246
1259
|
let screenshotsDir;
|
|
1247
1260
|
if (params.run_id) {
|
|
1248
1261
|
const base = getArtifactsBaseDir(cwd);
|
|
@@ -1290,7 +1303,7 @@ Returns: confirmation + the captured image as an MCP ImageContent block so you c
|
|
|
1290
1303
|
}
|
|
1291
1304
|
await trackUsage(apiKey, "visual_review");
|
|
1292
1305
|
return { ...result, windowBounds };
|
|
1293
|
-
}, { tool: "codeloop_capture_screenshot", cwd: (params
|
|
1306
|
+
}, { tool: "codeloop_capture_screenshot", cwd: resolveCwd(params), input: params });
|
|
1294
1307
|
if (typeof authResult === "object" && authResult !== null && "error" in authResult) {
|
|
1295
1308
|
return { content: [{ type: "text", text: JSON.stringify(authResult, null, 2) }] };
|
|
1296
1309
|
}
|
|
@@ -1336,8 +1349,8 @@ Returns: list of discovered screens with routes, navigation triggers, confidence
|
|
|
1336
1349
|
}, async (params) => {
|
|
1337
1350
|
const result = await withAuth(async () => {
|
|
1338
1351
|
const { discoverScreens } = await import("./tools/discover_screens.js");
|
|
1339
|
-
return discoverScreens((params
|
|
1340
|
-
}, { tool: "codeloop_discover_screens", cwd: (params
|
|
1352
|
+
return discoverScreens(resolveCwd(params), params.platform);
|
|
1353
|
+
}, { tool: "codeloop_discover_screens", cwd: resolveCwd(params), input: params });
|
|
1341
1354
|
return {
|
|
1342
1355
|
content: withInitHint([{ type: "text", text: JSON.stringify(result, null, 2) }]),
|
|
1343
1356
|
};
|
|
@@ -1371,8 +1384,8 @@ selects, datagrids, upload_areas, ai_features, forms }, ai_features_detected, sc
|
|
|
1371
1384
|
}, async (params) => {
|
|
1372
1385
|
const result = await withAuth(async () => {
|
|
1373
1386
|
const { discoverInteractions } = await import("./tools/discover_interactions.js");
|
|
1374
|
-
return discoverInteractions((params
|
|
1375
|
-
}, { tool: "codeloop_discover_interactions", cwd: (params
|
|
1387
|
+
return discoverInteractions(resolveCwd(params), params.platform);
|
|
1388
|
+
}, { tool: "codeloop_discover_interactions", cwd: resolveCwd(params), input: params });
|
|
1376
1389
|
return {
|
|
1377
1390
|
content: withInitHint([{ type: "text", text: JSON.stringify(result, null, 2) }]),
|
|
1378
1391
|
};
|
|
@@ -1414,8 +1427,8 @@ ai_substantive_prompts, upload_actions, datagrid_edits }, advice, discovered_int
|
|
|
1414
1427
|
}, async (params) => {
|
|
1415
1428
|
const result = await withAuth(async () => {
|
|
1416
1429
|
const { planUserJourney } = await import("./tools/plan_user_journey.js");
|
|
1417
|
-
return planUserJourney((params
|
|
1418
|
-
}, { tool: "codeloop_plan_user_journey", cwd: (params
|
|
1430
|
+
return planUserJourney(resolveCwd(params), params.platform, params.top_n);
|
|
1431
|
+
}, { tool: "codeloop_plan_user_journey", cwd: resolveCwd(params), input: params });
|
|
1419
1432
|
// Auto-fix loop directive. The plan is ONLY useful if the agent
|
|
1420
1433
|
// now drives it via a recording session — otherwise it's a
|
|
1421
1434
|
// detailed document that gets read and then deliberated over.
|
|
@@ -1457,7 +1470,7 @@ After recording, call codeloop_interaction_replay to extract frames and analyze
|
|
|
1457
1470
|
const authResult = await withAuth(async () => {
|
|
1458
1471
|
const { recordVideo } = await import("./runners/video_recorder.js");
|
|
1459
1472
|
const { createRunDir, getRunDir, getArtifactsBaseDir } = await import("./evidence/artifacts.js");
|
|
1460
|
-
const cwd = (params
|
|
1473
|
+
const cwd = resolveCwd(params);
|
|
1461
1474
|
let videosDir;
|
|
1462
1475
|
if (params.run_id) {
|
|
1463
1476
|
const base = getArtifactsBaseDir(cwd);
|
|
@@ -1470,7 +1483,7 @@ After recording, call codeloop_interaction_replay to extract frames and analyze
|
|
|
1470
1483
|
const result = await recordVideo(videosDir, params.duration_seconds, params.app_name);
|
|
1471
1484
|
await trackUsage(apiKey, "visual_review");
|
|
1472
1485
|
return result;
|
|
1473
|
-
}, { tool: "codeloop_record_interaction", cwd: (params
|
|
1486
|
+
}, { tool: "codeloop_record_interaction", cwd: resolveCwd(params), input: params });
|
|
1474
1487
|
if (typeof authResult === "object" && authResult !== null && "error" in authResult) {
|
|
1475
1488
|
return { content: [{ type: "text", text: JSON.stringify(authResult, null, 2) }] };
|
|
1476
1489
|
}
|
|
@@ -1499,7 +1512,7 @@ init for .NET/Xcode/Android projects via detect-target-app).`, {
|
|
|
1499
1512
|
const authResult = await withAuth(async () => {
|
|
1500
1513
|
const wm = await import("./runners/window_manager.js");
|
|
1501
1514
|
const { loadConfig } = await import("./config.js");
|
|
1502
|
-
const cwd = (params
|
|
1515
|
+
const cwd = resolveCwd(params);
|
|
1503
1516
|
const cfg = loadConfig(cwd);
|
|
1504
1517
|
const appName = params.app_name || cfg.evidence?.target_app;
|
|
1505
1518
|
if (!appName) {
|
|
@@ -1510,7 +1523,7 @@ init for .NET/Xcode/Android projects via detect-target-app).`, {
|
|
|
1510
1523
|
}
|
|
1511
1524
|
const r = await wm.launchDesktopApp(appName, cwd);
|
|
1512
1525
|
return { app_name: appName, ...r };
|
|
1513
|
-
}, { tool: "codeloop_launch_app", cwd: (params
|
|
1526
|
+
}, { tool: "codeloop_launch_app", cwd: resolveCwd(params), input: params });
|
|
1514
1527
|
if (typeof authResult === "object" && authResult !== null && "error" in authResult) {
|
|
1515
1528
|
return { content: [{ type: "text", text: JSON.stringify(authResult, null, 2) }] };
|
|
1516
1529
|
}
|
|
@@ -1558,7 +1571,7 @@ App logs (stdout, logcat, simctl log) are automatically captured alongside the v
|
|
|
1558
1571
|
const { createRunDir, getRunDir, getArtifactsBaseDir } = await import("./evidence/artifacts.js");
|
|
1559
1572
|
const { detectTargetType } = await import("./runners/platform_detect.js");
|
|
1560
1573
|
const { loadConfig } = await import("./config.js");
|
|
1561
|
-
const cwd = (params
|
|
1574
|
+
const cwd = resolveCwd(params);
|
|
1562
1575
|
let videosDir;
|
|
1563
1576
|
if (params.run_id) {
|
|
1564
1577
|
const base = getArtifactsBaseDir(cwd);
|
|
@@ -1620,7 +1633,7 @@ App logs (stdout, logcat, simctl log) are automatically captured alongside the v
|
|
|
1620
1633
|
}
|
|
1621
1634
|
await trackUsage(apiKey, "visual_review");
|
|
1622
1635
|
return result;
|
|
1623
|
-
}, { tool: "codeloop_start_recording", cwd: (params
|
|
1636
|
+
}, { tool: "codeloop_start_recording", cwd: resolveCwd(params), input: params });
|
|
1624
1637
|
if (typeof authResult === "object" && authResult !== null && "error" in authResult) {
|
|
1625
1638
|
return { content: [{ type: "text", text: JSON.stringify(authResult, null, 2) }] };
|
|
1626
1639
|
}
|
|
@@ -1703,7 +1716,7 @@ The agent MUST then write the report to docs/DEVELOPMENT_LOG.md and present it t
|
|
|
1703
1716
|
const result = await withAuth(async () => {
|
|
1704
1717
|
const { listRuns, loadRunMeta, getArtifactsBaseDir, getRunDir } = await import("./evidence/artifacts.js");
|
|
1705
1718
|
const { readdirSync, existsSync } = await import("fs");
|
|
1706
|
-
const cwd = (params
|
|
1719
|
+
const cwd = resolveCwd(params);
|
|
1707
1720
|
const baseDir = getArtifactsBaseDir(cwd);
|
|
1708
1721
|
const runs = listRuns(baseDir);
|
|
1709
1722
|
const runSummaries = [];
|
|
@@ -1848,7 +1861,7 @@ The agent MUST then write the report to docs/DEVELOPMENT_LOG.md and present it t
|
|
|
1848
1861
|
};
|
|
1849
1862
|
await trackUsage(apiKey, "verification_run");
|
|
1850
1863
|
return report;
|
|
1851
|
-
}, { tool: "codeloop_generate_dev_report", cwd: (params
|
|
1864
|
+
}, { tool: "codeloop_generate_dev_report", cwd: resolveCwd(params), input: params });
|
|
1852
1865
|
if (typeof result === "object" && result !== null && "error" in result) {
|
|
1853
1866
|
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
1854
1867
|
}
|
|
@@ -1970,7 +1983,7 @@ Returns: checklist of completed and pending verification steps.`, {
|
|
|
1970
1983
|
const { listRuns, loadRunMeta, getArtifactsBaseDir, getRunDir } = await import("./evidence/artifacts.js");
|
|
1971
1984
|
const { detectPlatform } = await import("./tools/verify.js");
|
|
1972
1985
|
const { detectDesktopUI } = await import("./tools/desktop_detection.js");
|
|
1973
|
-
const cwd = (params
|
|
1986
|
+
const cwd = resolveCwd(params);
|
|
1974
1987
|
const platform = detectPlatform(cwd);
|
|
1975
1988
|
// UI detection includes desktop .NET / native: WPF, WinForms, MAUI,
|
|
1976
1989
|
// Avalonia, WinUI, UWP. Without this, every WPF/.NET 8 / MAUI / Avalonia
|
|
@@ -2174,7 +2187,7 @@ Returns: checklist of completed and pending verification steps.`, {
|
|
|
2174
2187
|
? "All CodeLoop verification steps are complete. You may proceed."
|
|
2175
2188
|
: `WARNING: ${pendingSteps.length} step(s) still pending. DO NOT declare this task complete. DO NOT ask the user what to do next. Complete the pending steps below, then call codeloop_gate_check. If gate returns continue_fixing, loop back and fix without asking.\n${pendingSteps.map(s => ` - ${s.step}: ${s.detail}`).join("\n")}`,
|
|
2176
2189
|
};
|
|
2177
|
-
}, { tool: "codeloop_check_workflow", cwd: (params
|
|
2190
|
+
}, { tool: "codeloop_check_workflow", cwd: resolveCwd(params), input: params });
|
|
2178
2191
|
return {
|
|
2179
2192
|
content: withInitHint([{ type: "text", text: JSON.stringify(result, null, 2) }]),
|
|
2180
2193
|
};
|
|
@@ -2214,11 +2227,13 @@ Wait 1-2 seconds between interactions so video frames capture state changes.`, {
|
|
|
2214
2227
|
y: z.number().optional().describe("Y coordinate for click/scroll/drag/swipe"),
|
|
2215
2228
|
x2: z.number().optional().describe("End X for drag_drop/swipe"),
|
|
2216
2229
|
y2: z.number().optional().describe("End Y for drag_drop/swipe"),
|
|
2217
|
-
text: z.string().optional().describe("Text for type/type_and_submit/type_and_tab/fill"),
|
|
2230
|
+
text: z.string().optional().describe("Text for type/type_and_submit/type_and_tab/fill. 0.1.50+: ALSO accepted on click/double_click/right_click/hover with no x/y on Windows desktop targets — walks the UIA tree to find the first element whose Name property matches (exact, then substring) and clicks its centre. Closes the Photometry-DB E2E 8 regression where `{ action: \"click\", text: \"Luminaire Photometric Data\" }` produced `click at (undefined, undefined)`."),
|
|
2218
2231
|
key: z.string().optional().describe("Key name for keystroke: enter, tab, escape, backspace, delete, etc."),
|
|
2219
2232
|
keys: z.string().optional().describe("Key combo for hotkey: cmd+s, ctrl+enter, cmd+shift+z, etc."),
|
|
2220
2233
|
selector: z.string().optional().describe("CSS selector (browser) or automation ID (Windows)"),
|
|
2221
2234
|
selector2: z.string().optional().describe("Second selector for drag target"),
|
|
2235
|
+
automation_id: z.string().optional().describe("[Windows desktop] UIA AutomationId of the target element. 0.1.50+: when supplied for click/double_click/right_click/hover with no x/y, CodeLoop walks the UIA tree and resolves the element's screen coords automatically (DPI-aware, window-origin-aware), then clicks at the centre. Most stable selector for WPF/WinUI/UWP — prefer this over `text` whenever the control exposes one."),
|
|
2236
|
+
role: z.string().optional().describe("[Windows desktop] UIA ControlType programmatic name (e.g. `ControlType.Button`, `ControlType.TabItem`). 0.1.50+: when supplied for click/double_click/right_click/hover with no x/y, walks the UIA tree and clicks the FIRST element of that ControlType. Use as a last resort when neither AutomationId nor Name is specific enough."),
|
|
2222
2237
|
url: z.string().optional().describe("URL for navigate_url or deep_link"),
|
|
2223
2238
|
direction: z.enum(["up", "down", "left", "right"]).optional().describe("Scroll/swipe direction"),
|
|
2224
2239
|
amount: z.number().optional().describe("Scroll amount or other numeric value"),
|
|
@@ -2264,7 +2279,7 @@ Wait 1-2 seconds between interactions so video frames capture state changes.`, {
|
|
|
2264
2279
|
const bi = await import("./runners/browser_interaction.js");
|
|
2265
2280
|
const vr = await import("./runners/video_recorder.js");
|
|
2266
2281
|
// Auto-detect target_type when omitted
|
|
2267
|
-
const cwd = (params
|
|
2282
|
+
const cwd = resolveCwd(params);
|
|
2268
2283
|
let tt = params.target_type;
|
|
2269
2284
|
if (!tt) {
|
|
2270
2285
|
const recordingTarget = vr.getActiveRecordingTargetType();
|
|
@@ -2329,6 +2344,39 @@ Wait 1-2 seconds between interactions so video frames capture state changes.`, {
|
|
|
2329
2344
|
}
|
|
2330
2345
|
}
|
|
2331
2346
|
}
|
|
2347
|
+
// 0.1.50 H1 — when an agent passes `text` / `role` /
|
|
2348
|
+
// `automation_id` (no x/y) to a desktop click-family action,
|
|
2349
|
+
// walk the UIA tree to resolve the centre of the matching
|
|
2350
|
+
// element. The resolved (x, y) is screen-absolute so it
|
|
2351
|
+
// bypasses translateXY (which is for agent-supplied coords).
|
|
2352
|
+
const resolveDesktopSelector = async () => {
|
|
2353
|
+
if (tt !== "desktop" || process.platform !== "win32")
|
|
2354
|
+
return null;
|
|
2355
|
+
if (params.x != null && params.y != null)
|
|
2356
|
+
return null;
|
|
2357
|
+
const appName = params.app_name || vr.getActiveRecordingAppName();
|
|
2358
|
+
if (!appName)
|
|
2359
|
+
return null;
|
|
2360
|
+
const hasSelector = (params.automation_id && params.automation_id.length > 0) ||
|
|
2361
|
+
(params.text && params.text.length > 0) ||
|
|
2362
|
+
(params.role && params.role.length > 0);
|
|
2363
|
+
if (!hasSelector)
|
|
2364
|
+
return null;
|
|
2365
|
+
try {
|
|
2366
|
+
const { resolveSelectorToXY } = await import("./runners/uia_resolver.js");
|
|
2367
|
+
const r = await resolveSelectorToXY({
|
|
2368
|
+
appName,
|
|
2369
|
+
automationId: params.automation_id,
|
|
2370
|
+
text: params.text,
|
|
2371
|
+
role: params.role,
|
|
2372
|
+
});
|
|
2373
|
+
if (r.found && r.x != null && r.y != null) {
|
|
2374
|
+
return { x: r.x, y: r.y, foundBy: r.foundBy ?? "unknown" };
|
|
2375
|
+
}
|
|
2376
|
+
}
|
|
2377
|
+
catch { /* best-effort */ }
|
|
2378
|
+
return null;
|
|
2379
|
+
};
|
|
2332
2380
|
// Helper used by every coordinate-driven desktop action below.
|
|
2333
2381
|
// Photometry-DB E2E 8 + 0.1.49 hardening: handles four modes
|
|
2334
2382
|
// (auto / window / screen / screenshot) plus an optional DPI
|
|
@@ -2407,7 +2455,16 @@ Wait 1-2 seconds between interactions so video frames capture state changes.`, {
|
|
|
2407
2455
|
const t = translateXY(params.x, params.y);
|
|
2408
2456
|
success = await wm.clickAtPosition(t.x, t.y);
|
|
2409
2457
|
}
|
|
2410
|
-
|
|
2458
|
+
else {
|
|
2459
|
+
// 0.1.50 H1 — UIA selector fallback for click without coords.
|
|
2460
|
+
const resolved = await resolveDesktopSelector();
|
|
2461
|
+
if (resolved) {
|
|
2462
|
+
success = await wm.clickAtPosition(resolved.x, resolved.y);
|
|
2463
|
+
detail = `click at ${resolved.foundBy}=${params.automation_id || params.text || params.role} → (${resolved.x},${resolved.y})`;
|
|
2464
|
+
break;
|
|
2465
|
+
}
|
|
2466
|
+
}
|
|
2467
|
+
detail = `click at ${params.selector || params.automation_id || params.text || params.role || `(${params.x},${params.y})`}`;
|
|
2411
2468
|
break;
|
|
2412
2469
|
case "double_click":
|
|
2413
2470
|
if (tt === "browser" && params.selector) {
|
|
@@ -2417,7 +2474,15 @@ Wait 1-2 seconds between interactions so video frames capture state changes.`, {
|
|
|
2417
2474
|
const t = translateXY(params.x, params.y);
|
|
2418
2475
|
success = await wm.doubleClickAtPosition(t.x, t.y);
|
|
2419
2476
|
}
|
|
2420
|
-
|
|
2477
|
+
else {
|
|
2478
|
+
const resolved = await resolveDesktopSelector();
|
|
2479
|
+
if (resolved) {
|
|
2480
|
+
success = await wm.doubleClickAtPosition(resolved.x, resolved.y);
|
|
2481
|
+
detail = `double_click at ${resolved.foundBy}=${params.automation_id || params.text || params.role} → (${resolved.x},${resolved.y})`;
|
|
2482
|
+
break;
|
|
2483
|
+
}
|
|
2484
|
+
}
|
|
2485
|
+
detail = `double_click at ${params.selector || params.automation_id || params.text || params.role || `(${params.x},${params.y})`}`;
|
|
2421
2486
|
break;
|
|
2422
2487
|
case "right_click":
|
|
2423
2488
|
if (tt === "browser" && params.selector) {
|
|
@@ -2427,7 +2492,15 @@ Wait 1-2 seconds between interactions so video frames capture state changes.`, {
|
|
|
2427
2492
|
const t = translateXY(params.x, params.y);
|
|
2428
2493
|
success = await wm.rightClickAtPosition(t.x, t.y);
|
|
2429
2494
|
}
|
|
2430
|
-
|
|
2495
|
+
else {
|
|
2496
|
+
const resolved = await resolveDesktopSelector();
|
|
2497
|
+
if (resolved) {
|
|
2498
|
+
success = await wm.rightClickAtPosition(resolved.x, resolved.y);
|
|
2499
|
+
detail = `right_click at ${resolved.foundBy}=${params.automation_id || params.text || params.role} → (${resolved.x},${resolved.y})`;
|
|
2500
|
+
break;
|
|
2501
|
+
}
|
|
2502
|
+
}
|
|
2503
|
+
detail = `right_click at ${params.selector || params.automation_id || params.text || params.role || `(${params.x},${params.y})`}`;
|
|
2431
2504
|
break;
|
|
2432
2505
|
case "hover":
|
|
2433
2506
|
if (tt === "browser" && params.selector) {
|
|
@@ -2437,7 +2510,15 @@ Wait 1-2 seconds between interactions so video frames capture state changes.`, {
|
|
|
2437
2510
|
const t = translateXY(params.x, params.y);
|
|
2438
2511
|
success = await wm.hoverAtPosition(t.x, t.y);
|
|
2439
2512
|
}
|
|
2440
|
-
|
|
2513
|
+
else {
|
|
2514
|
+
const resolved = await resolveDesktopSelector();
|
|
2515
|
+
if (resolved) {
|
|
2516
|
+
success = await wm.hoverAtPosition(resolved.x, resolved.y);
|
|
2517
|
+
detail = `hover at ${resolved.foundBy}=${params.automation_id || params.text || params.role} → (${resolved.x},${resolved.y})`;
|
|
2518
|
+
break;
|
|
2519
|
+
}
|
|
2520
|
+
}
|
|
2521
|
+
detail = `hover at ${params.selector || params.automation_id || params.text || params.role || `(${params.x},${params.y})`}`;
|
|
2441
2522
|
break;
|
|
2442
2523
|
case "type":
|
|
2443
2524
|
if (tt === "browser" && params.selector && params.text) {
|
|
@@ -2782,7 +2863,7 @@ Wait 1-2 seconds between interactions so video frames capture state changes.`, {
|
|
|
2782
2863
|
case "maestro_flow":
|
|
2783
2864
|
if (params.maestro_steps) {
|
|
2784
2865
|
const mg = await import("./runners/maestro_generator.js");
|
|
2785
|
-
const cwd = (params
|
|
2866
|
+
const cwd = resolveCwd(params);
|
|
2786
2867
|
const genResult = await mg.generateMaestroFlow(params.maestro_steps, cwd);
|
|
2787
2868
|
if ("error" in genResult) {
|
|
2788
2869
|
return { success: false, action, detail: genResult.error };
|
|
@@ -3098,7 +3179,7 @@ Wait 1-2 seconds between interactions so video frames capture state changes.`, {
|
|
|
3098
3179
|
}
|
|
3099
3180
|
catch { /* best-effort logging */ }
|
|
3100
3181
|
return { success, action, detail };
|
|
3101
|
-
}, { tool: "codeloop_interact", cwd: (params
|
|
3182
|
+
}, { tool: "codeloop_interact", cwd: resolveCwd(params), input: params });
|
|
3102
3183
|
return {
|
|
3103
3184
|
content: withInitHint([{ type: "text", text: JSON.stringify(result, null, 2) }]),
|
|
3104
3185
|
};
|
|
@@ -3120,7 +3201,7 @@ project. After it completes, proceed directly with \`codeloop_verify\`.`, {
|
|
|
3120
3201
|
workspace_root: z.string().optional().describe("[Alias for project_dir] Same semantics; accepted because many agents reach for this conventional name. Pass either `project_dir` OR `workspace_root` — they're equivalent."),
|
|
3121
3202
|
project_type: z.enum(["flutter", "web", "mobile", "xcode", "android", "dotnet", "node", "auto"]).default("auto").describe("Project type. Use 'auto' to detect automatically."),
|
|
3122
3203
|
}, async (params) => {
|
|
3123
|
-
const cwd = (params
|
|
3204
|
+
const cwd = resolveCwd(params);
|
|
3124
3205
|
const result = await (async () => {
|
|
3125
3206
|
const { runInitProject } = await import("./tools/init-project.js");
|
|
3126
3207
|
const output = await runInitProject({
|
|
@@ -3146,7 +3227,7 @@ Returns: counts for attempted / succeeded / requeued events and the queue locati
|
|
|
3146
3227
|
project_dir: z.string().optional().describe("Absolute path to the project root. Defaults to CODELOOP_PROJECT_DIR env var or auto-discovered project directory. MUST be an actual project folder — passing the user's home directory is rejected. If your IDE launches the MCP server from the wrong cwd (common on Windows where Cursor uses C:\\Users\\<name> as cwd), set CODELOOP_PROJECT_DIR or pass this param explicitly."),
|
|
3147
3228
|
workspace_root: z.string().optional().describe("[Alias for project_dir] Same semantics; accepted because many agents reach for this conventional name. Pass either `project_dir` OR `workspace_root` — they're equivalent."),
|
|
3148
3229
|
}, async (params) => {
|
|
3149
|
-
const cwd = (params
|
|
3230
|
+
const cwd = resolveCwd(params);
|
|
3150
3231
|
const { flushPersistedUsage } = await import("./auth/usage_tracker.js");
|
|
3151
3232
|
const result = await flushPersistedUsage(cwd);
|
|
3152
3233
|
return {
|
|
@@ -3257,10 +3338,10 @@ Idempotent and free — safe to call as the first step of every new chat.`, {
|
|
|
3257
3338
|
workspace_root: z.string().optional().describe("[Alias for project_dir] Same semantics."),
|
|
3258
3339
|
}, async (params) => {
|
|
3259
3340
|
const result = await withAuth(async () => {
|
|
3260
|
-
const cwd = (params
|
|
3341
|
+
const cwd = resolveCwd(params);
|
|
3261
3342
|
const { runSelfTest } = await import("./tools/self_test.js");
|
|
3262
3343
|
return runSelfTest(cwd);
|
|
3263
|
-
}, { tool: "codeloop_self_test", cwd: (params
|
|
3344
|
+
}, { tool: "codeloop_self_test", cwd: resolveCwd(params), input: params });
|
|
3264
3345
|
if (typeof result === "object" && result !== null && "error" in result) {
|
|
3265
3346
|
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
3266
3347
|
}
|
|
@@ -3295,7 +3376,7 @@ Returns: status, current/latest versions, critical reasons, commands_to_run, aut
|
|
|
3295
3376
|
return applyUpdate({ auto_respawn: params.auto_respawn });
|
|
3296
3377
|
}, {
|
|
3297
3378
|
tool: "codeloop_apply_update",
|
|
3298
|
-
cwd: params
|
|
3379
|
+
cwd: resolveCwd(params),
|
|
3299
3380
|
input: params,
|
|
3300
3381
|
});
|
|
3301
3382
|
if (typeof authResult === "object" && authResult !== null && "error" in authResult) {
|