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.
- package/dist/auth/critical_floors.d.ts.map +1 -1
- package/dist/auth/critical_floors.js +8 -0
- package/dist/auth/critical_floors.js.map +1 -1
- package/dist/evidence/anti_rationalisation.d.ts.map +1 -1
- package/dist/evidence/anti_rationalisation.js +15 -0
- package/dist/evidence/anti_rationalisation.js.map +1 -1
- package/dist/evidence/binary_freshness.d.ts +21 -0
- package/dist/evidence/binary_freshness.d.ts.map +1 -0
- package/dist/evidence/binary_freshness.js +168 -0
- package/dist/evidence/binary_freshness.js.map +1 -0
- package/dist/evidence/change_coverage.d.ts.map +1 -1
- package/dist/evidence/change_coverage.js +22 -1
- package/dist/evidence/change_coverage.js.map +1 -1
- package/dist/evidence/cycle_issues.d.ts +99 -0
- package/dist/evidence/cycle_issues.d.ts.map +1 -0
- package/dist/evidence/cycle_issues.js +120 -0
- package/dist/evidence/cycle_issues.js.map +1 -0
- package/dist/evidence/interaction_coverage.d.ts +15 -0
- package/dist/evidence/interaction_coverage.d.ts.map +1 -1
- package/dist/evidence/interaction_coverage.js +53 -4
- package/dist/evidence/interaction_coverage.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +360 -5
- package/dist/index.js.map +1 -1
- package/dist/runners/modal_close_strategies.d.ts +82 -0
- package/dist/runners/modal_close_strategies.d.ts.map +1 -0
- package/dist/runners/modal_close_strategies.js +226 -0
- package/dist/runners/modal_close_strategies.js.map +1 -0
- package/dist/runners/modal_detector.d.ts +17 -0
- package/dist/runners/modal_detector.d.ts.map +1 -1
- package/dist/runners/modal_detector.js +95 -22
- package/dist/runners/modal_detector.js.map +1 -1
- package/dist/tools/gate_check.d.ts.map +1 -1
- package/dist/tools/gate_check.js +57 -0
- package/dist/tools/gate_check.js.map +1 -1
- 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;
|
|
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
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
//
|
|
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
|
-
" $
|
|
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
|
-
" $
|
|
95
|
+
" $isModal = $pattern.Current.IsModal",
|
|
61
96
|
" } catch { }",
|
|
62
|
-
"
|
|
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
|
-
|
|
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
|
-
|
|
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":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;
|
|
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;
|
|
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"}
|
package/dist/tools/gate_check.js
CHANGED
|
@@ -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) {
|