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.
@@ -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,EAmB1C,CAAC"}
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.project_dir || params.workspace_root || projectDir);
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.project_dir || params.workspace_root || projectDir);
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.project_dir || params.workspace_root || projectDir), input: 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.project_dir || params.workspace_root || projectDir);
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.project_dir || params.workspace_root || projectDir), input: 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.project_dir || params.workspace_root || projectDir)),
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.project_dir || params.workspace_root || projectDir)),
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.project_dir || params.workspace_root || projectDir);
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.project_dir || params.workspace_root || projectDir), input: 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.project_dir || params.workspace_root || projectDir);
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.project_dir || params.workspace_root || projectDir), input: 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.project_dir || params.workspace_root || projectDir);
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.project_dir || params.workspace_root || projectDir), input: 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.project_dir || params.workspace_root || projectDir);
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.project_dir || params.workspace_root || projectDir), input: 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.project_dir || params.workspace_root || projectDir), params.platform);
1340
- }, { tool: "codeloop_discover_screens", cwd: (params.project_dir || params.workspace_root || projectDir), input: 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.project_dir || params.workspace_root || projectDir), params.platform);
1375
- }, { tool: "codeloop_discover_interactions", cwd: (params.project_dir || params.workspace_root || projectDir), input: 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.project_dir || params.workspace_root || projectDir), params.platform, params.top_n);
1418
- }, { tool: "codeloop_plan_user_journey", cwd: (params.project_dir || params.workspace_root || projectDir), input: 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.project_dir || params.workspace_root || projectDir);
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.project_dir || params.workspace_root || projectDir), input: 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.project_dir || params.workspace_root || projectDir);
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.project_dir || params.workspace_root || projectDir), input: 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.project_dir || params.workspace_root || projectDir);
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.project_dir || params.workspace_root || projectDir), input: 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.project_dir || params.workspace_root || projectDir);
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.project_dir || params.workspace_root || projectDir), input: 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.project_dir || params.workspace_root || projectDir);
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.project_dir || params.workspace_root || projectDir), input: 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.project_dir || params.workspace_root || projectDir);
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
- detail = `click at ${params.selector || `(${params.x},${params.y})`}`;
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
- detail = `double_click at ${params.selector || `(${params.x},${params.y})`}`;
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
- detail = `right_click at ${params.selector || `(${params.x},${params.y})`}`;
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
- detail = `hover at ${params.selector || `(${params.x},${params.y})`}`;
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.project_dir || params.workspace_root || projectDir);
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.project_dir || params.workspace_root || projectDir), input: 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.project_dir || params.workspace_root || projectDir);
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.project_dir || params.workspace_root || projectDir);
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.project_dir || params.workspace_root || projectDir);
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.project_dir || params.workspace_root || projectDir), input: 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.project_dir || params.workspace_root || projectDir,
3379
+ cwd: resolveCwd(params),
3299
3380
  input: params,
3300
3381
  });
3301
3382
  if (typeof authResult === "object" && authResult !== null && "error" in authResult) {