codeloop-mcp-server 0.1.55 → 0.1.57
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/change_coverage.d.ts +6 -0
- package/dist/evidence/change_coverage.d.ts.map +1 -1
- package/dist/evidence/change_coverage.js +97 -12
- package/dist/evidence/change_coverage.js.map +1 -1
- package/dist/evidence/change_manifest.d.ts +33 -1
- package/dist/evidence/change_manifest.d.ts.map +1 -1
- package/dist/evidence/change_manifest.js +214 -3
- package/dist/evidence/change_manifest.js.map +1 -1
- package/dist/evidence/cycle_issues.d.ts +8 -0
- package/dist/evidence/cycle_issues.d.ts.map +1 -1
- package/dist/evidence/cycle_issues.js +7 -2
- package/dist/evidence/cycle_issues.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +199 -17
- package/dist/index.js.map +1 -1
- package/dist/runners/modal_detector.d.ts.map +1 -1
- package/dist/runners/modal_detector.js +38 -7
- package/dist/runners/modal_detector.js.map +1 -1
- package/dist/tools/plan_change_journey.d.ts.map +1 -1
- package/dist/tools/plan_change_journey.js +15 -0
- package/dist/tools/plan_change_journey.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -174,6 +174,35 @@ startUpdateCheck();
|
|
|
174
174
|
* issues we've already written to disk continue to gate ready_for_review.
|
|
175
175
|
*/
|
|
176
176
|
const modalPersistenceTracker = new Map();
|
|
177
|
+
/**
|
|
178
|
+
* 0.1.57 H2 — Throttle for the post-interact modal probe.
|
|
179
|
+
*
|
|
180
|
+
* 0.1.55 only ran the probe after SUCCESSFUL clicky/escape actions, so a
|
|
181
|
+
* file dialog that blocked the app (subsequent clicks fail, or the agent
|
|
182
|
+
* switches to wait / capture_screenshot / win_ui_automate) was never
|
|
183
|
+
* re-detected and the persistence counter never climbed — the modal sat
|
|
184
|
+
* open with zero cycle issues recorded (E2E #12). We now probe on the
|
|
185
|
+
* broader modal-related action set REGARDLESS of success, and use this map
|
|
186
|
+
* to keep the PowerShell cost bounded: clicky/escape always probe; other
|
|
187
|
+
* modal-related actions probe at most once per throttle window per app.
|
|
188
|
+
*/
|
|
189
|
+
const modalProbeLastAt = new Map();
|
|
190
|
+
const MODAL_PROBE_THROTTLE_MS = 1500;
|
|
191
|
+
/**
|
|
192
|
+
* 0.1.57 H3 — auto-close stuck external file dialogs.
|
|
193
|
+
*
|
|
194
|
+
* Default ON. The user can opt out per-machine with
|
|
195
|
+
* CODELOOP_AUTO_CLOSE_STUCK_MODALS=0 (or "false"/"off") if a workflow
|
|
196
|
+
* legitimately keeps a file picker open across many interactions.
|
|
197
|
+
*/
|
|
198
|
+
function autoCloseStuckModalsEnabled() {
|
|
199
|
+
const v = (process.env.CODELOOP_AUTO_CLOSE_STUCK_MODALS ?? "").trim().toLowerCase();
|
|
200
|
+
if (v === "0" || v === "false" || v === "off" || v === "no")
|
|
201
|
+
return false;
|
|
202
|
+
return true;
|
|
203
|
+
}
|
|
204
|
+
/** Consecutive same-dialog detections that mark a file dialog as genuinely stuck. */
|
|
205
|
+
const MODAL_AUTOCLOSE_THRESHOLD = 3;
|
|
177
206
|
const server = new McpServer({
|
|
178
207
|
name: "codeloop",
|
|
179
208
|
version: "0.1.14",
|
|
@@ -1924,6 +1953,59 @@ After stopping, call codeloop_interaction_replay with the run_id to extract fram
|
|
|
1924
1953
|
The response includes log_path if app logs were captured during the recording session.`, {
|
|
1925
1954
|
recording_id: z.string().describe("The recording_id returned by codeloop_start_recording"),
|
|
1926
1955
|
}, async (params) => {
|
|
1956
|
+
// 0.1.57 H4 — stuck-modal sweep. If a file dialog is still open when the
|
|
1957
|
+
// recording stops (E2E #12: the external folder picker that "kept opening
|
|
1958
|
+
// all the time"), close it automatically so it does not linger on the
|
|
1959
|
+
// user's desktop after the cycle. Best-effort and Windows-only; never
|
|
1960
|
+
// blocks the stop. Runs BEFORE finalising so the close is visible in the
|
|
1961
|
+
// final frames.
|
|
1962
|
+
let sweepNote;
|
|
1963
|
+
if (process.platform === "win32" && autoCloseStuckModalsEnabled()) {
|
|
1964
|
+
try {
|
|
1965
|
+
const { detectModal } = await import("./runners/modal_detector.js");
|
|
1966
|
+
const { closeModalWithStrategies } = await import("./runners/modal_close_strategies.js");
|
|
1967
|
+
const vrMod = await import("./runners/video_recorder.js");
|
|
1968
|
+
const appNameForModal = vrMod.getActiveRecordingAppName() || undefined;
|
|
1969
|
+
const detection = await detectModal({
|
|
1970
|
+
target_type: "desktop",
|
|
1971
|
+
app_name: appNameForModal,
|
|
1972
|
+
cwd: projectDir,
|
|
1973
|
+
});
|
|
1974
|
+
if (detection.is_modal_present && detection.modal_kind === "file_dialog") {
|
|
1975
|
+
const closeResult = await closeModalWithStrategies({
|
|
1976
|
+
initial_detection: detection,
|
|
1977
|
+
app_name: appNameForModal,
|
|
1978
|
+
cwd: projectDir,
|
|
1979
|
+
});
|
|
1980
|
+
const { recordCycleIssue } = await import("./evidence/cycle_issues.js");
|
|
1981
|
+
if (closeResult.closed) {
|
|
1982
|
+
const usedStrategy = closeResult.strategies_tried.find((s) => s.success)?.strategy ?? "ladder";
|
|
1983
|
+
sweepNote = `CodeLoop auto-closed a lingering file dialog (${detection.modal_description ?? "(unnamed)"}) at stop via ${usedStrategy}.`;
|
|
1984
|
+
await recordCycleIssue(projectDir, {
|
|
1985
|
+
kind: "modal_close_failed",
|
|
1986
|
+
modal_kind: detection.modal_kind ?? "file_dialog",
|
|
1987
|
+
modal_description: `${detection.modal_description ?? "(unnamed)"} (auto-closed by CodeLoop H4 stop sweep via ${usedStrategy})`,
|
|
1988
|
+
strategies_tried: closeResult.strategies_tried.filter((s) => s.success).map((s) => s.strategy),
|
|
1989
|
+
hwnd: detection.hwnd,
|
|
1990
|
+
auto_resolved: true,
|
|
1991
|
+
});
|
|
1992
|
+
}
|
|
1993
|
+
else {
|
|
1994
|
+
sweepNote = `A file dialog (${detection.modal_description ?? "(unnamed)"}) was still open at stop and the close ladder failed — call codeloop_kill_modal_window with hwnd ${detection.hwnd ?? "(see logs)"}.`;
|
|
1995
|
+
await recordCycleIssue(projectDir, {
|
|
1996
|
+
kind: "modal_close_failed",
|
|
1997
|
+
modal_kind: detection.modal_kind ?? "file_dialog",
|
|
1998
|
+
modal_description: detection.modal_description,
|
|
1999
|
+
strategies_tried: closeResult.strategies_tried.map((s) => s.strategy),
|
|
2000
|
+
hwnd: detection.hwnd ?? closeResult.hwnd,
|
|
2001
|
+
});
|
|
2002
|
+
}
|
|
2003
|
+
}
|
|
2004
|
+
}
|
|
2005
|
+
catch {
|
|
2006
|
+
/* best-effort sweep — never block the stop */
|
|
2007
|
+
}
|
|
2008
|
+
}
|
|
1927
2009
|
const authResult = await withAuth(async () => {
|
|
1928
2010
|
const { stopBackgroundRecording } = await import("./runners/video_recorder.js");
|
|
1929
2011
|
return stopBackgroundRecording(params.recording_id);
|
|
@@ -1945,6 +2027,7 @@ The response includes log_path if app logs were captured during the recording se
|
|
|
1945
2027
|
" 1. codeloop_interaction_replay — extract frames + app logs from the just-saved video. This populates the data the replay/journey gates score against.",
|
|
1946
2028
|
" 2. codeloop_gate_check — confirm confidence ≥ 94%. If continue_fixing, fix the failing gate's next_step and re-record / re-capture.",
|
|
1947
2029
|
"Do NOT skip step 1 — without replay frames the interaction_replay_evidence gate fails even when the video exists. Do NOT pause to ask the user 'should I run replay now?' — yes, always.",
|
|
2030
|
+
...(sweepNote ? ["", `[CodeLoop H4] ${sweepNote}`] : []),
|
|
1948
2031
|
].join("\n");
|
|
1949
2032
|
return {
|
|
1950
2033
|
content: withInitHint([{ type: "text", text: JSON.stringify(result, null, 2) + nextStepDirective }]),
|
|
@@ -3538,15 +3621,40 @@ Wait 1-2 seconds between interactions so video frames capture state changes.`, {
|
|
|
3538
3621
|
const isClosingIntent = /\b(close|dismiss|cancel|escape|exit)\b/.test(closingIntent);
|
|
3539
3622
|
const isClickyAction = action === "click" || action === "double_click" || action === "right_click";
|
|
3540
3623
|
const isEscapeKeystroke = action === "keystroke" && (params.key ?? "").toLowerCase() === "escape";
|
|
3541
|
-
|
|
3542
|
-
|
|
3624
|
+
// 0.1.57 H2 — probe on the broader modal-related action set REGARDLESS
|
|
3625
|
+
// of success. A blocked / failed click against a stuck file dialog is
|
|
3626
|
+
// itself the signal we need (E2E #12: subsequent clicks failed, so the
|
|
3627
|
+
// 0.1.55 `success &&` gate never re-ran the probe and the picker stayed
|
|
3628
|
+
// open with zero cycle issues). Clicky/escape always probe; the wider
|
|
3629
|
+
// set (type/hotkey/win_ui_automate/sequence/wait) probes at most once
|
|
3630
|
+
// per throttle window so the PowerShell cost stays bounded.
|
|
3631
|
+
const trackerKey = (params.app_name || vr.getActiveRecordingAppName() || "<default>").toLowerCase();
|
|
3632
|
+
const clicky = isClickyAction || isEscapeKeystroke;
|
|
3633
|
+
const widerModalActions = new Set([
|
|
3634
|
+
"type",
|
|
3635
|
+
"type_and_submit",
|
|
3636
|
+
"type_and_tab",
|
|
3637
|
+
"keystroke",
|
|
3638
|
+
"hotkey",
|
|
3639
|
+
"win_ui_automate",
|
|
3640
|
+
"sequence",
|
|
3641
|
+
"wait",
|
|
3642
|
+
"hover",
|
|
3643
|
+
]);
|
|
3644
|
+
const nowMs = Date.now();
|
|
3645
|
+
const lastProbeMs = modalProbeLastAt.get(trackerKey) ?? 0;
|
|
3646
|
+
const throttleOk = clicky || (widerModalActions.has(action) && nowMs - lastProbeMs > MODAL_PROBE_THROTTLE_MS);
|
|
3647
|
+
const shouldCheckModal = tt === "desktop" && throttleOk;
|
|
3648
|
+
if (shouldCheckModal) {
|
|
3543
3649
|
try {
|
|
3650
|
+
modalProbeLastAt.set(trackerKey, nowMs);
|
|
3544
3651
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
3545
3652
|
const { detectModal } = await import("./runners/modal_detector.js");
|
|
3546
|
-
const
|
|
3653
|
+
const { closeModalWithStrategies } = await import("./runners/modal_close_strategies.js");
|
|
3654
|
+
const appNameForModal = params.app_name || vr.getActiveRecordingAppName() || undefined;
|
|
3547
3655
|
const detection = await detectModal({
|
|
3548
3656
|
target_type: "desktop",
|
|
3549
|
-
app_name:
|
|
3657
|
+
app_name: appNameForModal,
|
|
3550
3658
|
cwd,
|
|
3551
3659
|
});
|
|
3552
3660
|
const { recordCycleIssue } = await import("./evidence/cycle_issues.js");
|
|
@@ -3605,11 +3713,79 @@ Wait 1-2 seconds between interactions so video frames capture state changes.`, {
|
|
|
3605
3713
|
modal_kind: detection.modal_kind,
|
|
3606
3714
|
});
|
|
3607
3715
|
}
|
|
3608
|
-
// 0.1.
|
|
3609
|
-
//
|
|
3610
|
-
// the
|
|
3611
|
-
//
|
|
3612
|
-
|
|
3716
|
+
// 0.1.57 H3 — AUTOMATIC close of stuck external file dialogs.
|
|
3717
|
+
//
|
|
3718
|
+
// E2E #12: the agent rationalised a recurring OpenFolderDialog as
|
|
3719
|
+
// a "test artifact" and NEVER called codeloop_handle_modal, so the
|
|
3720
|
+
// picker was left open and kept reopening. CodeLoop must close it
|
|
3721
|
+
// itself rather than only directing the agent.
|
|
3722
|
+
//
|
|
3723
|
+
// Trigger conditions (all required) keep this safe against legit
|
|
3724
|
+
// picker usage:
|
|
3725
|
+
// - it's a file_dialog (external OS picker — the exact class the
|
|
3726
|
+
// user reports; confirm/alert kinds may need a real decision
|
|
3727
|
+
// and are intentionally left to the agent),
|
|
3728
|
+
// - it has persisted across >= MODAL_AUTOCLOSE_THRESHOLD (3)
|
|
3729
|
+
// consecutive detections of the SAME dialog. A legitimate
|
|
3730
|
+
// open→confirm picker flow clears within 1-2 interactions and
|
|
3731
|
+
// resets the tracker, so reaching 3 means the agent is stuck.
|
|
3732
|
+
const isStuckFileDialog = detection.modal_kind === "file_dialog" &&
|
|
3733
|
+
consecutive >= MODAL_AUTOCLOSE_THRESHOLD;
|
|
3734
|
+
let autoClosed = false;
|
|
3735
|
+
if (isStuckFileDialog && autoCloseStuckModalsEnabled()) {
|
|
3736
|
+
try {
|
|
3737
|
+
const closeResult = await closeModalWithStrategies({
|
|
3738
|
+
initial_detection: detection,
|
|
3739
|
+
app_name: appNameForModal,
|
|
3740
|
+
cwd,
|
|
3741
|
+
});
|
|
3742
|
+
autoClosed = closeResult.closed;
|
|
3743
|
+
if (autoClosed) {
|
|
3744
|
+
modalPersistenceTracker.delete(trackerKey);
|
|
3745
|
+
const usedStrategy = closeResult.strategies_tried.find((s) => s.success)?.strategy ?? "ladder";
|
|
3746
|
+
detail = `${detail} | CodeLoop auto-closed stuck file dialog (${desc}) via ${usedStrategy}`;
|
|
3747
|
+
clickEffectVerification = {
|
|
3748
|
+
...(clickEffectVerification ?? { intent: closingIntent, modal_still_present: false }),
|
|
3749
|
+
modal_still_present: false,
|
|
3750
|
+
modal_description: detection.modal_description,
|
|
3751
|
+
modal_kind: detection.modal_kind,
|
|
3752
|
+
consecutive_persistences: consecutive,
|
|
3753
|
+
};
|
|
3754
|
+
// Surface the auto-handling so the dev report shows it was
|
|
3755
|
+
// caught and resolved (not silently swallowed).
|
|
3756
|
+
await recordCycleIssue(cwd, {
|
|
3757
|
+
kind: "modal_close_failed",
|
|
3758
|
+
modal_kind: detection.modal_kind ?? "file_dialog",
|
|
3759
|
+
modal_description: `${desc} (auto-closed by CodeLoop H3 after ${consecutive} consecutive detections via ${usedStrategy})`,
|
|
3760
|
+
strategies_tried: closeResult.strategies_tried
|
|
3761
|
+
.filter((s) => s.success)
|
|
3762
|
+
.map((s) => s.strategy),
|
|
3763
|
+
hwnd: detection.hwnd,
|
|
3764
|
+
auto_resolved: true,
|
|
3765
|
+
});
|
|
3766
|
+
}
|
|
3767
|
+
else {
|
|
3768
|
+
// Ladder exhausted — record the unresolved failure and
|
|
3769
|
+
// escalate the directive to kill-window.
|
|
3770
|
+
await recordCycleIssue(cwd, {
|
|
3771
|
+
kind: "modal_close_failed",
|
|
3772
|
+
modal_kind: detection.modal_kind ?? "file_dialog",
|
|
3773
|
+
modal_description: desc,
|
|
3774
|
+
strategies_tried: closeResult.strategies_tried.map((s) => s.strategy),
|
|
3775
|
+
hwnd: detection.hwnd ?? closeResult.hwnd,
|
|
3776
|
+
});
|
|
3777
|
+
}
|
|
3778
|
+
}
|
|
3779
|
+
catch {
|
|
3780
|
+
/* best-effort auto-close; fall through to the F3/F4 path */
|
|
3781
|
+
}
|
|
3782
|
+
}
|
|
3783
|
+
// 0.1.55 F3 — at 3+ consecutive persistences, treat the modal as
|
|
3784
|
+
// actively stuck and write a modal_close_failed entry so the
|
|
3785
|
+
// cycle_issues_acknowledged gate fails even when the agent never
|
|
3786
|
+
// called codeloop_handle_modal. Skipped when H3 already recorded
|
|
3787
|
+
// the (auto-resolved or unresolved) outcome above.
|
|
3788
|
+
if (consecutive >= 3 && !isStuckFileDialog) {
|
|
3613
3789
|
await recordCycleIssue(cwd, {
|
|
3614
3790
|
kind: "modal_close_failed",
|
|
3615
3791
|
modal_kind: detection.modal_kind ?? "custom",
|
|
@@ -3620,14 +3796,20 @@ Wait 1-2 seconds between interactions so video frames capture state changes.`, {
|
|
|
3620
3796
|
hwnd: detection.hwnd,
|
|
3621
3797
|
});
|
|
3622
3798
|
}
|
|
3623
|
-
// 0.1.55 F4 — HARD directive for the post-interact
|
|
3624
|
-
|
|
3625
|
-
|
|
3626
|
-
|
|
3627
|
-
|
|
3628
|
-
|
|
3629
|
-
`
|
|
3630
|
-
`
|
|
3799
|
+
// 0.1.55 F4 / 0.1.57 H3 — HARD directive for the post-interact
|
|
3800
|
+
// postscript. Suppressed when CodeLoop already auto-closed the
|
|
3801
|
+
// dialog; otherwise escalated to kill-window when an auto-close
|
|
3802
|
+
// attempt was made and failed.
|
|
3803
|
+
if (!autoClosed) {
|
|
3804
|
+
const killHint = isStuckFileDialog
|
|
3805
|
+
? `CodeLoop already attempted the multi-strategy close ladder and it FAILED — call codeloop_kill_modal_window with hwnd ${detection.hwnd ?? "(from codeloop_handle_modal)"} now. `
|
|
3806
|
+
: `Stop sending raw clicks / Escape keystrokes against it and call codeloop_handle_modal with decision: "cancel" or "dismiss" — the multi-strategy ladder (Escape → Alt+F4 → UIA Invoke "Close" → EndDialog) handles file dialogs the keystroke path cannot. If codeloop_handle_modal returns escalation: "kill_window_required", call codeloop_kill_modal_window with the returned hwnd. `;
|
|
3807
|
+
modalPersistenceDirective =
|
|
3808
|
+
`\n\n[CodeLoop F4] HARD: A ${detection.modal_kind ?? "modal"} dialog (${desc}) is STILL present after this interaction ` +
|
|
3809
|
+
`(${consecutive} consecutive interactions have not cleared it). ` +
|
|
3810
|
+
killHint +
|
|
3811
|
+
`Continuing to ignore this modal will fail the cycle_issues_acknowledged gate and block ready_for_review.`;
|
|
3812
|
+
}
|
|
3631
3813
|
}
|
|
3632
3814
|
else {
|
|
3633
3815
|
// Modal cleared — reset the tracker for this app so the next
|