codeloop-mcp-server 0.1.53 → 0.1.55

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.
Files changed (36) hide show
  1. package/dist/auth/critical_floors.d.ts.map +1 -1
  2. package/dist/auth/critical_floors.js +8 -0
  3. package/dist/auth/critical_floors.js.map +1 -1
  4. package/dist/evidence/anti_rationalisation.d.ts.map +1 -1
  5. package/dist/evidence/anti_rationalisation.js +15 -0
  6. package/dist/evidence/anti_rationalisation.js.map +1 -1
  7. package/dist/evidence/binary_freshness.d.ts +21 -0
  8. package/dist/evidence/binary_freshness.d.ts.map +1 -0
  9. package/dist/evidence/binary_freshness.js +168 -0
  10. package/dist/evidence/binary_freshness.js.map +1 -0
  11. package/dist/evidence/change_coverage.d.ts.map +1 -1
  12. package/dist/evidence/change_coverage.js +22 -1
  13. package/dist/evidence/change_coverage.js.map +1 -1
  14. package/dist/evidence/cycle_issues.d.ts +99 -0
  15. package/dist/evidence/cycle_issues.d.ts.map +1 -0
  16. package/dist/evidence/cycle_issues.js +120 -0
  17. package/dist/evidence/cycle_issues.js.map +1 -0
  18. package/dist/evidence/interaction_coverage.d.ts +15 -0
  19. package/dist/evidence/interaction_coverage.d.ts.map +1 -1
  20. package/dist/evidence/interaction_coverage.js +53 -4
  21. package/dist/evidence/interaction_coverage.js.map +1 -1
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/index.js +360 -5
  24. package/dist/index.js.map +1 -1
  25. package/dist/runners/modal_close_strategies.d.ts +82 -0
  26. package/dist/runners/modal_close_strategies.d.ts.map +1 -0
  27. package/dist/runners/modal_close_strategies.js +226 -0
  28. package/dist/runners/modal_close_strategies.js.map +1 -0
  29. package/dist/runners/modal_detector.d.ts +17 -0
  30. package/dist/runners/modal_detector.d.ts.map +1 -1
  31. package/dist/runners/modal_detector.js +95 -22
  32. package/dist/runners/modal_detector.js.map +1 -1
  33. package/dist/tools/gate_check.d.ts.map +1 -1
  34. package/dist/tools/gate_check.js +57 -0
  35. package/dist/tools/gate_check.js.map +1 -1
  36. package/package.json +1 -1
@@ -0,0 +1,82 @@
1
+ import { type ModalDetectionResult } from "./modal_detector.js";
2
+ /**
3
+ * 0.1.54 E1 — Multi-strategy modal close.
4
+ *
5
+ * Photometry-DB E2E #11 transcript shipped at 100% confidence after the
6
+ * agent had to HARD-RESTART the app process to drain a stack of three
7
+ * .NET 8 OpenFolderDialog instances that ignored every close attempt
8
+ * codeloop_handle_modal made (`WM_CLOSE` / `SC_CLOSE` / focused-Escape).
9
+ * The pre-0.1.54 handler only sent `Enter` / `Escape` keystrokes — when
10
+ * a Win32 file dialog stops responding to those (which it does after
11
+ * being stacked), the loop had no fallback path.
12
+ *
13
+ * This module ships a 4-step ladder, executed in order with re-detection
14
+ * after each step. Stops as soon as detectModal returns is_modal_present:
15
+ * false. Returns the full sequence of attempts so the agent (and the
16
+ * cycle_issues evidence channel from E2) can audit which strategy worked
17
+ * or whether the kill-window escalation is required.
18
+ *
19
+ * Strategies:
20
+ * 1. focused Escape (current 0.1.51 behaviour — works for most simple
21
+ * WPF / WinForms confirmation dialogs).
22
+ * 2. Alt+F4 against the foreground window — closes file dialogs that
23
+ * have rejected Escape because they're waiting on a button click.
24
+ * 3. UIA InvokePattern on the dialog's "Close" button — handles file
25
+ * dialogs whose top-right X is technically a button, not a window
26
+ * caption click.
27
+ * 4. Win32 EndDialog(hwnd, IDCANCEL) via Add-Type — last resort before
28
+ * kill-window. Works for true MODAL dialogs even when the message
29
+ * pump is wedged.
30
+ *
31
+ * If all 4 fail the caller surfaces the kill-window escalation; the
32
+ * separate `codeloop_kill_modal_window` tool sends WM_CLOSE to the
33
+ * specific HWND, then TerminateProcess if the dialog is still up
34
+ * after a 2 s grace period.
35
+ */
36
+ export type StrategyName = "escape" | "alt_f4" | "uia_invoke_close" | "end_dialog";
37
+ export interface StrategyAttempt {
38
+ strategy: StrategyName;
39
+ success: boolean;
40
+ error?: string;
41
+ }
42
+ export interface MultiStrategyCloseResult {
43
+ closed: boolean;
44
+ strategies_tried: StrategyAttempt[];
45
+ /** Modal description as reported by the LAST detectModal probe. */
46
+ final_detection: ModalDetectionResult;
47
+ /** When closed=false, the agent should escalate via codeloop_kill_modal_window. */
48
+ escalation: "none" | "kill_window_required";
49
+ /** HWND captured at first detection, used by kill-window escalation. */
50
+ hwnd?: string;
51
+ }
52
+ /**
53
+ * Run the strategy ladder against the detected modal. Re-detects after
54
+ * each step. Caller is expected to have already called detectModal and
55
+ * confirmed `is_modal_present: true`.
56
+ */
57
+ export declare function closeModalWithStrategies(opts: {
58
+ initial_detection: ModalDetectionResult;
59
+ app_name?: string;
60
+ cwd: string;
61
+ }): Promise<MultiStrategyCloseResult>;
62
+ /**
63
+ * 0.1.54 E1 — last-resort kill: PostMessage(WM_CLOSE) to the HWND, wait
64
+ * 2 s, re-detect, and if the modal is still up TerminateProcess on the
65
+ * owning process. Used by codeloop_kill_modal_window when the strategy
66
+ * ladder gives up.
67
+ */
68
+ export interface KillModalWindowResult {
69
+ closed: boolean;
70
+ steps: Array<{
71
+ step: "post_wm_close" | "terminate_process";
72
+ success: boolean;
73
+ error?: string;
74
+ }>;
75
+ final_detection: ModalDetectionResult;
76
+ }
77
+ export declare function killModalWindow(opts: {
78
+ hwnd: string;
79
+ app_name?: string;
80
+ cwd: string;
81
+ }): Promise<KillModalWindowResult>;
82
+ //# sourceMappingURL=modal_close_strategies.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"modal_close_strategies.d.ts","sourceRoot":"","sources":["../../src/runners/modal_close_strategies.ts"],"names":[],"mappings":"AACA,OAAO,EAAe,KAAK,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAE7E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAEH,MAAM,MAAM,YAAY,GAAG,QAAQ,GAAG,QAAQ,GAAG,kBAAkB,GAAG,YAAY,CAAC;AAEnF,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,YAAY,CAAC;IACvB,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,wBAAwB;IACvC,MAAM,EAAE,OAAO,CAAC;IAChB,gBAAgB,EAAE,eAAe,EAAE,CAAC;IACpC,mEAAmE;IACnE,eAAe,EAAE,oBAAoB,CAAC;IACtC,mFAAmF;IACnF,UAAU,EAAE,MAAM,GAAG,sBAAsB,CAAC;IAC5C,wEAAwE;IACxE,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAoGD;;;;GAIG;AACH,wBAAsB,wBAAwB,CAAC,IAAI,EAAE;IACnD,iBAAiB,EAAE,oBAAoB,CAAC;IACxC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,GAAG,EAAE,MAAM,CAAC;CACb,GAAG,OAAO,CAAC,wBAAwB,CAAC,CAyEpC;AAED;;;;;GAKG;AACH,MAAM,WAAW,qBAAqB;IACpC,MAAM,EAAE,OAAO,CAAC;IAChB,KAAK,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,eAAe,GAAG,mBAAmB,CAAC;QAAC,OAAO,EAAE,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAChG,eAAe,EAAE,oBAAoB,CAAC;CACvC;AAED,wBAAsB,eAAe,CAAC,IAAI,EAAE;IAC1C,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,GAAG,EAAE,MAAM,CAAC;CACb,GAAG,OAAO,CAAC,qBAAqB,CAAC,CAqEjC"}
@@ -0,0 +1,226 @@
1
+ import { runCommand } from "./base.js";
2
+ import { detectModal } from "./modal_detector.js";
3
+ const RE_DETECT_DELAY_MS = 400;
4
+ async function sleep(ms) {
5
+ return new Promise((resolve) => setTimeout(resolve, ms));
6
+ }
7
+ async function pressEscapeWindows(cwd) {
8
+ // SendKeys-based — focused window receives Escape. Mirrors what
9
+ // codeloop_interact action="keystroke", key="escape" does.
10
+ const script = [
11
+ "Add-Type -AssemblyName System.Windows.Forms",
12
+ "[System.Windows.Forms.SendKeys]::SendWait('{ESC}')",
13
+ ].join("\n");
14
+ const r = await runCommand("powershell", ["-NoProfile", "-Command", script], cwd, undefined, undefined, 5000);
15
+ return r.exit_code === 0 ? { ok: true } : { ok: false, err: r.stderr.slice(0, 200) };
16
+ }
17
+ async function pressAltF4Windows(cwd) {
18
+ const script = [
19
+ "Add-Type -AssemblyName System.Windows.Forms",
20
+ "[System.Windows.Forms.SendKeys]::SendWait('%{F4}')",
21
+ ].join("\n");
22
+ const r = await runCommand("powershell", ["-NoProfile", "-Command", script], cwd, undefined, undefined, 5000);
23
+ return r.exit_code === 0 ? { ok: true } : { ok: false, err: r.stderr.slice(0, 200) };
24
+ }
25
+ async function uiaInvokeCloseButtonWindows(cwd, hwnd) {
26
+ // Walk the modal window's children looking for a button named "Close",
27
+ // "Cancel", or "X". When hwnd is provided we can target the dialog
28
+ // directly via FromHandle; otherwise fall back to the foreground
29
+ // window via NativeWindowHandle property.
30
+ const handleArg = hwnd ?? "$null";
31
+ const script = [
32
+ "Add-Type -AssemblyName UIAutomationClient",
33
+ `$hwnd = ${handleArg}`,
34
+ "if ($hwnd -ne $null -and $hwnd -ne 0) {",
35
+ " $w = [System.Windows.Automation.AutomationElement]::FromHandle([IntPtr]$hwnd)",
36
+ "} else {",
37
+ " $root = [System.Windows.Automation.AutomationElement]::RootElement",
38
+ " $cond = New-Object System.Windows.Automation.PropertyCondition([System.Windows.Automation.AutomationElement]::ControlTypeProperty, [System.Windows.Automation.ControlType]::Window)",
39
+ " $w = $root.FindFirst([System.Windows.Automation.TreeScope]::Children, $cond)",
40
+ "}",
41
+ "if ($w -eq $null) { Write-Error 'no window found'; exit 1 }",
42
+ "$btnCond = New-Object System.Windows.Automation.PropertyCondition([System.Windows.Automation.AutomationElement]::ControlTypeProperty, [System.Windows.Automation.ControlType]::Button)",
43
+ "$buttons = $w.FindAll([System.Windows.Automation.TreeScope]::Subtree, $btnCond)",
44
+ "$invoked = $false",
45
+ "foreach ($b in $buttons) {",
46
+ " $name = $b.Current.Name",
47
+ " if ($name -match '^(Close|Cancel|X|Dismiss)$') {",
48
+ " try {",
49
+ " $invoke = $b.GetCurrentPattern([System.Windows.Automation.InvokePattern]::Pattern)",
50
+ " $invoke.Invoke()",
51
+ " $invoked = $true",
52
+ " break",
53
+ " } catch { }",
54
+ " }",
55
+ "}",
56
+ "if (-not $invoked) { Write-Error 'no close button found'; exit 1 }",
57
+ ].join("\n");
58
+ const r = await runCommand("powershell", ["-NoProfile", "-Command", script], cwd, undefined, undefined, 8000);
59
+ return r.exit_code === 0 ? { ok: true } : { ok: false, err: r.stderr.slice(0, 200) };
60
+ }
61
+ async function endDialogWindows(cwd, hwnd) {
62
+ if (!hwnd) {
63
+ return { ok: false, err: "EndDialog requires hwnd; detector did not capture one" };
64
+ }
65
+ // EndDialog Win32 call. Falls back to PostMessage(WM_CLOSE) when the
66
+ // window isn't a true dialog (EndDialog returns FALSE in that case but
67
+ // PostMessage can still terminate it).
68
+ const script = [
69
+ "Add-Type @\"",
70
+ "using System;",
71
+ "using System.Runtime.InteropServices;",
72
+ "public class CL {",
73
+ " [DllImport(\"user32.dll\")] public static extern bool EndDialog(IntPtr hDlg, IntPtr nResult);",
74
+ " [DllImport(\"user32.dll\")] public static extern bool PostMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);",
75
+ "}",
76
+ "\"@",
77
+ `$hwnd = [IntPtr]${hwnd}`,
78
+ "$ok = [CL]::EndDialog($hwnd, [IntPtr]2)",
79
+ // 2 == IDCANCEL
80
+ "if (-not $ok) {",
81
+ " $WM_CLOSE = 0x0010",
82
+ " $ok2 = [CL]::PostMessage($hwnd, $WM_CLOSE, [IntPtr]::Zero, [IntPtr]::Zero)",
83
+ " if (-not $ok2) { Write-Error 'EndDialog and PostMessage(WM_CLOSE) both failed'; exit 1 }",
84
+ "}",
85
+ ].join("\n");
86
+ const r = await runCommand("powershell", ["-NoProfile", "-Command", script], cwd, undefined, undefined, 5000);
87
+ return r.exit_code === 0 ? { ok: true } : { ok: false, err: r.stderr.slice(0, 200) };
88
+ }
89
+ /**
90
+ * Run the strategy ladder against the detected modal. Re-detects after
91
+ * each step. Caller is expected to have already called detectModal and
92
+ * confirmed `is_modal_present: true`.
93
+ */
94
+ export async function closeModalWithStrategies(opts) {
95
+ const { initial_detection: initial, app_name, cwd } = opts;
96
+ const hwnd = initial.hwnd;
97
+ const attempts = [];
98
+ const stepDispatch = {
99
+ escape: () => pressEscapeWindows(cwd),
100
+ alt_f4: () => pressAltF4Windows(cwd),
101
+ uia_invoke_close: () => uiaInvokeCloseButtonWindows(cwd, hwnd),
102
+ end_dialog: () => endDialogWindows(cwd, hwnd),
103
+ };
104
+ // 0.1.54 E1 — strategy order: cheap-and-fast first, expensive last.
105
+ // For confirm/alert kinds we don't really need Alt+F4 or EndDialog
106
+ // (they bias toward 'confirm' rather than 'cancel'), but the ladder
107
+ // is still safe to run because we re-detect between steps.
108
+ const ladder = ["escape", "alt_f4", "uia_invoke_close", "end_dialog"];
109
+ let lastDetection = initial;
110
+ for (const strategy of ladder) {
111
+ if (process.platform !== "win32") {
112
+ // Non-Windows desktop modal handling stays at the 0.1.51 single-
113
+ // keystroke behaviour because macOS / Linux modals don't show
114
+ // the same OpenFolderDialog stuck-stack problem. Surface that
115
+ // limitation explicitly so the caller doesn't get confused.
116
+ attempts.push({
117
+ strategy,
118
+ success: false,
119
+ error: "multi-strategy close ladder is Windows-only; macOS/Linux fall back to keystroke",
120
+ });
121
+ break;
122
+ }
123
+ const r = await stepDispatch[strategy]();
124
+ attempts.push({
125
+ strategy,
126
+ success: r.ok,
127
+ error: r.err,
128
+ });
129
+ if (!r.ok)
130
+ continue;
131
+ // Re-detect. If the modal is gone, we're done.
132
+ await sleep(RE_DETECT_DELAY_MS);
133
+ lastDetection = await detectModal({
134
+ target_type: "desktop",
135
+ app_name,
136
+ cwd,
137
+ });
138
+ if (!lastDetection.is_modal_present) {
139
+ return {
140
+ closed: true,
141
+ strategies_tried: attempts,
142
+ final_detection: lastDetection,
143
+ escalation: "none",
144
+ hwnd,
145
+ };
146
+ }
147
+ // If the HWND changed (modal stack drained one level), keep going
148
+ // through the ladder against the next-up modal — but cap the loop
149
+ // by exhausting our 4 strategies. The next call to handle_modal
150
+ // will pick up any remaining stacked modal.
151
+ }
152
+ return {
153
+ closed: false,
154
+ strategies_tried: attempts,
155
+ final_detection: lastDetection,
156
+ escalation: "kill_window_required",
157
+ hwnd,
158
+ };
159
+ }
160
+ export async function killModalWindow(opts) {
161
+ if (process.platform !== "win32") {
162
+ return {
163
+ closed: false,
164
+ steps: [
165
+ {
166
+ step: "post_wm_close",
167
+ success: false,
168
+ error: "kill-window escalation is Windows-only",
169
+ },
170
+ ],
171
+ final_detection: { is_modal_present: false, target_type: "desktop" },
172
+ };
173
+ }
174
+ const steps = [];
175
+ // Step 1: PostMessage(WM_CLOSE).
176
+ const wmCloseScript = [
177
+ "Add-Type @\"",
178
+ "using System;",
179
+ "using System.Runtime.InteropServices;",
180
+ "public class CL2 {",
181
+ " [DllImport(\"user32.dll\")] public static extern bool PostMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);",
182
+ " [DllImport(\"user32.dll\")] public static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);",
183
+ "}",
184
+ "\"@",
185
+ `$hwnd = [IntPtr]${opts.hwnd}`,
186
+ "$WM_CLOSE = 0x0010",
187
+ "$ok = [CL2]::PostMessage($hwnd, $WM_CLOSE, [IntPtr]::Zero, [IntPtr]::Zero)",
188
+ "if (-not $ok) { Write-Error 'PostMessage(WM_CLOSE) failed'; exit 1 }",
189
+ ].join("\n");
190
+ const r1 = await runCommand("powershell", ["-NoProfile", "-Command", wmCloseScript], opts.cwd, undefined, undefined, 5000);
191
+ steps.push({ step: "post_wm_close", success: r1.exit_code === 0, error: r1.exit_code === 0 ? undefined : r1.stderr.slice(0, 200) });
192
+ await sleep(2000);
193
+ let detection = await detectModal({
194
+ target_type: "desktop",
195
+ app_name: opts.app_name,
196
+ cwd: opts.cwd,
197
+ });
198
+ if (!detection.is_modal_present) {
199
+ return { closed: true, steps, final_detection: detection };
200
+ }
201
+ // Step 2: TerminateProcess on the owning PID.
202
+ const terminateScript = [
203
+ "Add-Type @\"",
204
+ "using System;",
205
+ "using System.Runtime.InteropServices;",
206
+ "public class CL3 {",
207
+ " [DllImport(\"user32.dll\")] public static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);",
208
+ "}",
209
+ "\"@",
210
+ `$hwnd = [IntPtr]${opts.hwnd}`,
211
+ "$pid_out = 0",
212
+ "[void][CL3]::GetWindowThreadProcessId($hwnd, [ref]$pid_out)",
213
+ "if ($pid_out -eq 0) { Write-Error 'could not resolve PID for HWND'; exit 1 }",
214
+ "Stop-Process -Id $pid_out -Force",
215
+ ].join("\n");
216
+ const r2 = await runCommand("powershell", ["-NoProfile", "-Command", terminateScript], opts.cwd, undefined, undefined, 5000);
217
+ steps.push({ step: "terminate_process", success: r2.exit_code === 0, error: r2.exit_code === 0 ? undefined : r2.stderr.slice(0, 200) });
218
+ await sleep(1000);
219
+ detection = await detectModal({
220
+ target_type: "desktop",
221
+ app_name: opts.app_name,
222
+ cwd: opts.cwd,
223
+ });
224
+ return { closed: !detection.is_modal_present, steps, final_detection: detection };
225
+ }
226
+ //# sourceMappingURL=modal_close_strategies.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"modal_close_strategies.js","sourceRoot":"","sources":["../../src/runners/modal_close_strategies.ts"],"names":[],"mappings":"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"}
@@ -32,10 +32,25 @@ import type { CodeLoopConfig } from "@codelooptech/shared";
32
32
  * negative as proof there's no modal — the post-interact directive
33
33
  * (layer 3) covers that case by always nudging the agent to look.
34
34
  */
35
+ /**
36
+ * 0.1.54 E1 — modal_kind classifies the detected modal so codeloop_handle_modal
37
+ * can pick the right close strategy. .NET 8 OpenFolderDialog / OpenFileDialog
38
+ * are the dominant "stuck" modal class — Photometry-DB E2E #11 had three
39
+ * stacked file dialogs that ignored every close attempt the handler tried.
40
+ * "file_dialog" routes them through the multi-strategy close ladder
41
+ * (Escape → Alt+F4 → UIA Invoke "Close" → EndDialog → kill-window).
42
+ */
43
+ export type ModalKind = "file_dialog" | "confirm" | "alert" | "custom";
35
44
  export interface ModalDetectionResult {
36
45
  is_modal_present: boolean;
37
46
  /** Human-readable description of the detected modal, when present. */
38
47
  modal_description?: string;
48
+ /** 0.1.54 E1 — classification used by codeloop_handle_modal to pick close strategy. */
49
+ modal_kind?: ModalKind;
50
+ /** 0.1.54 E1 — Win32 HWND when available, used by codeloop_kill_modal_window. */
51
+ hwnd?: string;
52
+ /** 0.1.54 E1 — UIA ClassName when available (e.g. #32770 for Win32 dialogs). */
53
+ class_name?: string;
39
54
  /** Suggested action for the agent: "confirm" | "cancel" | "dismiss" | "inspect". */
40
55
  suggested_action?: string;
41
56
  /** Heuristic confidence 0-1; > 0.7 should be acted on. */
@@ -45,6 +60,8 @@ export interface ModalDetectionResult {
45
60
  /** target_type the detector ran against. */
46
61
  target_type: "browser" | "desktop" | "android_emulator" | "ios_simulator" | "unknown";
47
62
  }
63
+ /** 0.1.54 E1 — exported so codeloop_handle_modal can reuse the same heuristic. */
64
+ export declare function classifyModalKind(name: string, className?: string): ModalKind;
48
65
  export declare function detectModal(opts: {
49
66
  target_type?: "browser" | "desktop" | "android_emulator" | "ios_simulator";
50
67
  app_name?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"modal_detector.d.ts","sourceRoot":"","sources":["../../src/runners/modal_detector.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAE3D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAEH,MAAM,WAAW,oBAAoB;IACnC,gBAAgB,EAAE,OAAO,CAAC;IAC1B,sEAAsE;IACtE,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,oFAAoF;IACpF,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,0DAA0D;IAC1D,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,+FAA+F;IAC/F,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,4CAA4C;IAC5C,WAAW,EAAE,SAAS,GAAG,SAAS,GAAG,kBAAkB,GAAG,eAAe,GAAG,SAAS,CAAC;CACvF;AAED,wBAAsB,WAAW,CAAC,IAAI,EAAE;IACtC,WAAW,CAAC,EAAE,SAAS,GAAG,SAAS,GAAG,kBAAkB,GAAG,eAAe,CAAC;IAC3E,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,CAAC,EAAE,cAAc,CAAC;CACzB,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAiBhC;AA6ID;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAiB5D"}
1
+ {"version":3,"file":"modal_detector.d.ts","sourceRoot":"","sources":["../../src/runners/modal_detector.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAE3D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAEH;;;;;;;GAOG;AACH,MAAM,MAAM,SAAS,GAAG,aAAa,GAAG,SAAS,GAAG,OAAO,GAAG,QAAQ,CAAC;AAEvE,MAAM,WAAW,oBAAoB;IACnC,gBAAgB,EAAE,OAAO,CAAC;IAC1B,sEAAsE;IACtE,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,uFAAuF;IACvF,UAAU,CAAC,EAAE,SAAS,CAAC;IACvB,iFAAiF;IACjF,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,gFAAgF;IAChF,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,oFAAoF;IACpF,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,0DAA0D;IAC1D,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,+FAA+F;IAC/F,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,4CAA4C;IAC5C,WAAW,EAAE,SAAS,GAAG,SAAS,GAAG,kBAAkB,GAAG,eAAe,GAAG,SAAS,CAAC;CACvF;AAqBD,kFAAkF;AAClF,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAO7E;AAED,wBAAsB,WAAW,CAAC,IAAI,EAAE;IACtC,WAAW,CAAC,EAAE,SAAS,GAAG,SAAS,GAAG,kBAAkB,GAAG,eAAe,CAAC;IAC3E,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,CAAC,EAAE,cAAc,CAAC;CACzB,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAiBhC;AAyLD;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAiB5D"}
@@ -1,4 +1,35 @@
1
1
  import { runCommand } from "./base.js";
2
+ /**
3
+ * 0.1.54 E1 — Class names that identify file dialogs across .NET / Win32:
4
+ * - `#32770` — every Win32 common dialog (CommonItemDialog included)
5
+ * - `Shell_Dialog` — Shell-based file pickers
6
+ * - `DirectUIHWND` — child of file dialogs but useful for ownership match
7
+ * - `WinUIDesktopWin32WindowClass` — WinUI 3 / .NET 8 desktop
8
+ *
9
+ * Name patterns that match file-dialog titles in EN locale; we accept any
10
+ * substring so localised "Open File" / "Save As" / "Browse Folder" still hit.
11
+ */
12
+ const FILE_DIALOG_CLASS_NAMES = ["#32770", "Shell_Dialog", "DirectUIHWND", "WinUIDesktopWin32WindowClass"];
13
+ const FILE_DIALOG_NAME_PATTERNS = [
14
+ /\b(open|save|select|browse|choose)\b.*\b(file|folder|directory|location)\b/i,
15
+ /\bfile\s*open\b/i,
16
+ /\bsave\s*as\b/i,
17
+ /\bopenfolderdialog\b/i,
18
+ /\bopenfiledialog\b/i,
19
+ ];
20
+ /** 0.1.54 E1 — exported so codeloop_handle_modal can reuse the same heuristic. */
21
+ export function classifyModalKind(name, className) {
22
+ const cls = (className ?? "").trim();
23
+ if (FILE_DIALOG_CLASS_NAMES.includes(cls))
24
+ return "file_dialog";
25
+ if (FILE_DIALOG_NAME_PATTERNS.some((re) => re.test(name)))
26
+ return "file_dialog";
27
+ if (/\b(confirm|are you sure|delete\?|discard)\b/i.test(name))
28
+ return "confirm";
29
+ if (/\b(error|warning|alert|attention)\b/i.test(name))
30
+ return "alert";
31
+ return "custom";
32
+ }
2
33
  export async function detectModal(opts) {
3
34
  const target = opts.target_type ?? "desktop";
4
35
  try {
@@ -42,10 +73,12 @@ async function detectDesktopModal(opts) {
42
73
  }
43
74
  async function detectWindowsModal(opts) {
44
75
  const app = opts.app_name ?? "";
45
- const filterClause = app
46
- ? `if ($name -notlike '*' + '${app.replace(/'/g, "''")}' + '*') { continue }`
47
- : "";
48
- // PowerShell UIA probe. Returns JSON the agent can parse.
76
+ const appLower = app.toLowerCase();
77
+ // PowerShell UIA probe. 0.1.54 E1 also collects ClassName + HWND so
78
+ // codeloop_handle_modal can pick the right close strategy and so the
79
+ // kill-window escalation knows which window to terminate. We additionally
80
+ // accept windows that are NOT marked IsModal=true but match a file-dialog
81
+ // class name (.NET 8 OpenFolderDialog often does not set IsModal).
49
82
  const script = [
50
83
  "Add-Type -AssemblyName UIAutomationClient",
51
84
  "$root = [System.Windows.Automation.AutomationElement]::RootElement",
@@ -54,15 +87,14 @@ async function detectWindowsModal(opts) {
54
87
  "$result = @()",
55
88
  "foreach ($w in $windows) {",
56
89
  " $name = $w.Current.Name",
57
- " $owned = $false",
90
+ " $cls = $w.Current.ClassName",
91
+ " $hwnd = $w.Current.NativeWindowHandle",
92
+ " $isModal = $false",
58
93
  " try {",
59
94
  " $pattern = $w.GetCurrentPattern([System.Windows.Automation.WindowPattern]::Pattern)",
60
- " $owned = $pattern.Current.IsModal",
95
+ " $isModal = $pattern.Current.IsModal",
61
96
  " } catch { }",
62
- " " + filterClause,
63
- " if ($owned) {",
64
- " $result += @{ name = $name; modal = $true }",
65
- " }",
97
+ " $result += @{ name = $name; class_name = $cls; hwnd = $hwnd; is_modal = $isModal }",
66
98
  "}",
67
99
  "$result | ConvertTo-Json -Compress",
68
100
  ].join("\n");
@@ -74,22 +106,63 @@ async function detectWindowsModal(opts) {
74
106
  detection_error: r.stderr.slice(0, 200),
75
107
  };
76
108
  }
109
+ let arr = [];
77
110
  try {
78
111
  const parsed = r.stdout.trim() ? JSON.parse(r.stdout) : null;
79
- const arr = Array.isArray(parsed) ? parsed : parsed ? [parsed] : [];
80
- const modal = arr.find((w) => w.modal);
81
- if (modal) {
82
- return {
83
- is_modal_present: true,
84
- modal_description: `Windows modal: ${modal.name ?? "(unnamed)"}`,
85
- suggested_action: "inspect",
86
- confidence: 0.85,
87
- target_type: "desktop",
88
- };
89
- }
112
+ arr = Array.isArray(parsed) ? parsed : parsed ? [parsed] : [];
90
113
  }
91
114
  catch {
92
- /* fall through */
115
+ return { is_modal_present: false, target_type: "desktop" };
116
+ }
117
+ // First pass: explicitly modal windows that pass the app filter.
118
+ // Second pass: file-dialog class match (regardless of IsModal flag) when
119
+ // the window's name suggests a file picker. The two-pass split makes sure
120
+ // we never spuriously match an unrelated #32770 dialog the OS itself owns
121
+ // (Windows Spotlight, Snipping Tool, etc.) — the file-dialog match
122
+ // requires either a name pattern OR an app filter that hits the running
123
+ // process.
124
+ const matchesAppFilter = (name) => {
125
+ if (!appLower)
126
+ return true;
127
+ return name.toLowerCase().includes(appLower);
128
+ };
129
+ const explicit = arr.find((w) => w.is_modal && matchesAppFilter(w.name ?? ""));
130
+ if (explicit) {
131
+ const name = explicit.name ?? "(unnamed)";
132
+ const cls = explicit.class_name ?? undefined;
133
+ return {
134
+ is_modal_present: true,
135
+ modal_description: `Windows modal: ${name}`,
136
+ modal_kind: classifyModalKind(name, cls),
137
+ class_name: cls,
138
+ hwnd: explicit.hwnd != null ? String(explicit.hwnd) : undefined,
139
+ suggested_action: "inspect",
140
+ confidence: 0.85,
141
+ target_type: "desktop",
142
+ };
143
+ }
144
+ const fileDialog = arr.find((w) => {
145
+ const name = w.name ?? "";
146
+ const cls = w.class_name ?? "";
147
+ if (!FILE_DIALOG_CLASS_NAMES.includes(cls))
148
+ return false;
149
+ // The app filter applies for class-only matches — without it any Win32
150
+ // dialog on the desktop would trigger.
151
+ return matchesAppFilter(name) || FILE_DIALOG_NAME_PATTERNS.some((re) => re.test(name));
152
+ });
153
+ if (fileDialog) {
154
+ const name = fileDialog.name ?? "(unnamed)";
155
+ const cls = fileDialog.class_name ?? undefined;
156
+ return {
157
+ is_modal_present: true,
158
+ modal_description: `Windows file dialog: ${name}`,
159
+ modal_kind: "file_dialog",
160
+ class_name: cls,
161
+ hwnd: fileDialog.hwnd != null ? String(fileDialog.hwnd) : undefined,
162
+ suggested_action: "dismiss",
163
+ confidence: 0.75,
164
+ target_type: "desktop",
165
+ };
93
166
  }
94
167
  return { is_modal_present: false, target_type: "desktop" };
95
168
  }
@@ -1 +1 @@
1
- {"version":3,"file":"modal_detector.js","sourceRoot":"","sources":["../../src/runners/modal_detector.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"modal_detector.js","sourceRoot":"","sources":["../../src/runners/modal_detector.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"gate_check.d.ts","sourceRoot":"","sources":["../../src/tools/gate_check.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,cAAc,EACd,eAAe,EAEf,cAAc,EAEf,MAAM,sBAAsB,CAAC;AAoC9B,wBAAsB,YAAY,CAChC,KAAK,EAAE,cAAc,EACrB,MAAM,EAAE,cAAc,EACtB,GAAG,GAAE,MAAsB,GAC1B,OAAO,CAAC,eAAe,CAAC,CAiG1B;AAyDD,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,EAAE,WAAW,EAAE,CAAC"}
1
+ {"version":3,"file":"gate_check.d.ts","sourceRoot":"","sources":["../../src/tools/gate_check.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,cAAc,EACd,eAAe,EAEf,cAAc,EAEf,MAAM,sBAAsB,CAAC;AAwC9B,wBAAsB,YAAY,CAChC,KAAK,EAAE,cAAc,EACrB,MAAM,EAAE,cAAc,EACtB,GAAG,GAAE,MAAsB,GAC1B,OAAO,CAAC,eAAe,CAAC,CAiG1B;AA2DD,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,EAAE,WAAW,EAAE,CAAC"}
@@ -8,6 +8,7 @@ import { incrementGateAttempts, resetLoopState, snapshotLoopStateToRun, shouldEs
8
8
  import { collectInteractionCoverage, detectJourneyArcs, evaluateDepth, evaluateUserJourney, resolveDepthMinimums, } from "../evidence/interaction_coverage.js";
9
9
  import { evaluateChangeCoverage, resolveChangeCoverageConfig, } from "../evidence/change_coverage.js";
10
10
  import { formatStalenessSuffix, isEvidenceStale, isRunEvidenceStale, runEvidenceMtime, } from "../evidence/evidence_freshness.js";
11
+ import { loadCycleIssues, summariseCycleIssue, } from "../evidence/cycle_issues.js";
11
12
  export async function runGateCheck(input, config, cwd = process.cwd()) {
12
13
  const baseDir = getArtifactsBaseDir(cwd);
13
14
  const meta = loadRunMeta(input.run_id, baseDir);
@@ -127,6 +128,8 @@ function nextStepForGate(name, reason, cwd) {
127
128
  "[H11] If the journey stalled mid-arc, an UNHANDLED MODAL is the most likely cause: Save? / Confirm delete / validation toast / license dialog. AFTER each codeloop_interact call inspect the latest screenshot, then call codeloop_handle_modal with the appropriate decision (\"confirm\" / \"cancel\" / \"dismiss\") before the next interact. An unhandled modal blocks every subsequent click and silently drops the rest of the planned arc on the floor.");
128
129
  case "design_compare_evidence":
129
130
  return `Design comparison failed (${reason}). Either (a) the reference designs depict a populated state that differs from the running app — seed the app's database / state to match the references before re-capturing; or (b) the captured screenshots are named differently from the references — re-capture using the reference filename (e.g. codeloop_capture_screenshot screen_name='dashboard' matches designs/dashboard.png); or (c) update / remove the irrelevant reference designs under designs/ if they no longer reflect the intended UI.`;
131
+ case "cycle_issues_acknowledged":
132
+ return "Recurrence-class issues were recorded mid-cycle (modal_close_failed / binary_mismatch / click_missed_target / app_restart / high_failure_rate). Read artifacts/runs/<latest>/logs/cycle_issues.jsonl, then either (a) fix the root cause and call codeloop_verify again so the resolution is implicit, or (b) write one line per issue into logs/cycle_issue_resolutions.jsonl with { issue_kind, resolved_by, note } and explicitly enumerate each in your gate-response summary. Do NOT use 'all gates green' boilerplate — the C6 anti-rationalisation scan blocks that exact phrasing.";
130
133
  case "visual_regression_threshold":
131
134
  return "A visual diff exceeded the regression threshold. Open artifacts/runs/<run_id>/diffs/ to see which screen changed; fix the regression in the implementation OR update the baseline by copying the new screenshot into the baseline path.";
132
135
  default:
@@ -312,6 +315,22 @@ async function evaluateEvidenceGates(runId, cwd, config) {
312
315
  }
313
316
  catch { /* best-effort */ }
314
317
  const verdict = evaluateDepth(coverage, minimums, discoverySnapshot);
318
+ // 0.1.54 E6 — Failed-interaction-rate warning. evaluateDepth attaches
319
+ // a structured `failure_rate_warning` when failed/(failed+successful)
320
+ // exceeds 10%. We mirror that into cycle_issues.jsonl so the new
321
+ // cycle_issues_acknowledged blocker surfaces it to the agent's gate
322
+ // response instead of letting a "depth=passed" verdict mask the
323
+ // bad-coordinate loop the agent kept losing time on.
324
+ if (verdict.failure_rate_warning) {
325
+ const { recordCycleIssue } = await import("../evidence/cycle_issues.js");
326
+ await recordCycleIssue(cwd, {
327
+ kind: "high_failure_rate",
328
+ failed: verdict.failure_rate_warning.failed,
329
+ successful: verdict.failure_rate_warning.successful,
330
+ total: verdict.failure_rate_warning.total,
331
+ ratio: verdict.failure_rate_warning.ratio,
332
+ });
333
+ }
315
334
  // 0.1.53 D2 — Evidence freshness for interaction logs. The base
316
335
  // verdict aggregates EVERY interaction_log.jsonl across EVERY run in
317
336
  // the project's artifacts directory, so once an agent has historically
@@ -550,6 +569,44 @@ async function evaluateEvidenceGates(runId, cwd, config) {
550
569
  ? changeVerdict.reason
551
570
  : `${changeVerdict.reason}\n${changeVerdict.next_step}`,
552
571
  });
572
+ // 0.1.54 E2 — cycle_issues_acknowledged blocker.
573
+ //
574
+ // Photometry-DB E2E #11 shipped at 100% confidence with `ready_for_review`
575
+ // even though three real recurrence-class issues happened mid-cycle (stuck
576
+ // OpenFolderDialog, binary-vs-source mismatch, hard app restart). They
577
+ // were buried in the dev log and never surfaced to the user. This gate
578
+ // blocks the whole cycle until the agent enumerates each unresolved entry
579
+ // from cycle_issues.jsonl in its gate response — see the directive
580
+ // generated below.
581
+ const cycleIssuesGate = {
582
+ name: "cycle_issues_acknowledged",
583
+ description: "Recurrence-class issues recorded during the cycle must be acknowledged before reporting ready_for_review.",
584
+ rule: "Every entry in cycle_issues.jsonl has a sibling resolution OR the agent's gate response enumerates each one",
585
+ input_artifacts: ["logs/cycle_issues.jsonl", "logs/cycle_issue_resolutions.jsonl"],
586
+ pass_threshold: true,
587
+ severity_if_failed: "blocker",
588
+ retry_allowance: 5,
589
+ escalation_condition: "Cycle issues remain unresolved after 5 attempts",
590
+ };
591
+ const cycleIssues = await loadCycleIssues(cwd);
592
+ if (cycleIssues.unresolved.length === 0) {
593
+ results.push({
594
+ gate: cycleIssuesGate,
595
+ passed: true,
596
+ reason: cycleIssues.issues.length === 0
597
+ ? "No recurrence-class issues recorded this cycle"
598
+ : `All ${cycleIssues.issues.length} recorded cycle issue(s) have a sibling resolution`,
599
+ });
600
+ }
601
+ else {
602
+ const summary = cycleIssues.unresolved.map((issue) => ` • ${summariseCycleIssue(issue)}`).join("\n");
603
+ results.push({
604
+ gate: cycleIssuesGate,
605
+ passed: false,
606
+ reason: `${cycleIssues.unresolved.length} unresolved cycle issue(s) — your gate-response summary MUST enumerate each one and how it was resolved (or escalated). Do NOT report ready_for_review without acknowledging:\n${summary}\n` +
607
+ `Resolutions: either fix the underlying problem and call codeloop_verify again, OR call codeloop_escalate with the issue's kind to surface it to the user. Generic "all gates green" / "everything looks good" summaries will fail the C6 anti-rationalisation scan.`,
608
+ });
609
+ }
553
610
  return results;
554
611
  }
555
612
  function evaluateGate(gate, meta, config, specContent, acceptanceContent, cwd) {