Skip to content

feat(control): under an agent, calls that bring an app to the front or move the pointer are refused, and recordings leave out excluded windows - #591

Merged
genesiscz merged 6 commits into
masterfrom
fix/2026-10-25-control-background
Oct 26, 2026
Merged

genesiscz merged 6 commits into
masterfrom
fix/2026-10-25-control-background

Conversation

@genesiscz

Copy link
Copy Markdown
Owner

Under an agent, a tools control call that would bring an app to the front or move the pointer is refused while the user works, recordings leave out the windows --exclude-window-ids names (ScreenCaptureKit ignores sharingType), and the macos-control skill puts working in the background first.

  • d087f18 fix(ax-tool): a recording leaves out the windows --exclude-window-ids names (ScreenCaptureKit ignores sharingType none since macOS 15), and a frame the PNG cap keeps out still counts as motion, so a long movie no longer drops to the idle rate
  • 9a49257 feat(control): under an agent, a call that would bring an app to the front or move the pointer is refused while the user worked in the last 5 s, a background click into a window that is not key says it may have done nothing, and every action takes the window_id get_app_state took
  • 00ca301 docs(skills, macos-control, control): the macos-control skill puts the background first, names the calls that take focus and the new refusal, and records the window_id, list_windows, drag and inactive-window facts agents tripped over

Gates on this branch (clean worktree, 2026-10-25): bun scripts/git/check-commits.ts, root tsc --noEmit, biome on the changed files, the related tests, and scripts/ci/placeholder-check.sh. Tests: 4 files, 177 pass. swift test --package-path native/ax-tool: 432 tests, 0 failures.

Split out of the unified branch (#507); each commit is a cherry-pick of its unified original.

@eve-bot-lovinka

eve-bot-lovinka Bot commented Oct 25, 2026 •

Copy link
Copy Markdown

🐉 eve review — 🔴 REQUEST_CHANGES · 3 findings

→ review · run

  • Queued 02:44:16Z
  • Reading diff — 300 files
  • Building repo map
  • Analyzing (find → verify) — 5 candidates → 3 survivors · reviewed by find codex/gpt-6.1-sol + codex/gpt-6-luna, verify codex/gpt-6-luna
  • Posting review
  • Review posted 03:05:05Z (20m 50s)

run wrun_01M5V1P9JHA2YBFDXGFNNX3HSJ

Previous runs (7)
run head outcome findings took
6 2e6e8d0 ⏳ refused — 2m 57s
5 ccf9800 ✅ APPROVE 0 1m 34s
4 1d7a6b1 ✅ APPROVE 1 2m 18s
3 6784e4a 🔴 REQUEST_CHANGES 3 12m 11s
3 6784e4a 🔁 deduped (run wrun_01M owns it) — —
2 62c5112 ⏭️ superseded — 13m 11s
1 00ca301 🔴 REQUEST_CHANGES 5 11m 19s

@coderabbitai

coderabbitai Bot commented Oct 25, 2026 •

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

Next included review available in 22 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used all 3 included reviews currently available. Your 46 included PR review attempts over the past 7 days set your current allowance at 3 reviews per hour.

Learn how review limits work.

Review configuration:

Run configuration
Commits

Reviewing files that changed from the base of the PR and between 9d74bca and 2e6e8d0.

Files selected for processing: 30
  • .claude-plugin/marketplace.json
  • native/ax-tool/SnapshotSupport/ForegroundGuard.swift
  • native/ax-tool/SnapshotSupport/IsolatedComposition.swift
  • native/ax-tool/SnapshotSupport/SnapshotToken.swift
  • native/ax-tool/SnapshotSupport/UserTakeover.swift
  • native/ax-tool/SnapshotSupport/WorkflowArguments.swift
  • native/ax-tool/Sources/AppLifecycle.swift
  • native/ax-tool/Sources/IsolatedCapture.swift
  • native/ax-tool/Sources/Record.swift
  • native/ax-tool/Sources/SnapshotWorkflow.swift
  • native/ax-tool/Sources/commands/Harness.swift
  • native/ax-tool/Sources/commands/Input.swift
  • native/ax-tool/Sources/commands/SnapshotRestore.swift
  • native/ax-tool/Sources/main.swift
  • native/ax-tool/Sources/utils/InputGate.swift
  • native/ax-tool/Sources/utils/Output.swift
  • native/ax-tool/Tests/ForegroundGuardTests.swift
  • native/ax-tool/Tests/FrameSamplerTests.swift
  • native/ax-tool/Tests/IsolatedCompositionTests.swift
  • plugins/genesis-tools/.claude-plugin/plugin.json
  • plugins/genesis-tools/skills/macos-control/SKILL.md
  • src/control/commands/workflow.ts
  • src/control/lib/computer-use/schemas.ts
  • src/control/lib/computer-use/session.test.ts
  • src/control/lib/computer-use/session.ts
  • src/control/lib/decision/native.test.ts
  • src/control/lib/decision/recovery.ts
  • src/control/lib/native-platform.test.ts
  • src/control/lib/runner.test.ts
  • src/control/lib/runner.ts
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Comment @coderabbitai help to get the list of available commands.

@eve-bot-lovinka eve-bot-lovinka Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🐉 eve review — 🔴 Changes requested

00ca301 · 5 actionable findings · reviewed by find codex/gpt-6.1-sol + codex/gpt-6-luna, verify codex/gpt-6-luna · view run ↗

Severity Count
🟠 High 2
🟡 Medium 2
🔵 Low 1

Blast radius

  • ComputerUse (~) → 6 dependent(s) outside this PR: src/control/mcp/server.ts, src/control/lib/capture-native.ts
  • moved (+) → 5 dependent(s) outside this PR: src/macos/GenesisKit/Sources/GenesisKitDisk/Disk/VolumeSpace.swift, src/macos/GenesisKit/Sources/GenesisKitDisk/Disk/BigFileRadar.swift, src/macos/GenesisKit/Sources/GenesisKitDisk/Disk/DiskHistoryStore.swift, +2 more
  • bringFrontmost (~) → 4 dependent(s) outside this PR: native/ax-tool/Sources/commands/Hotkey.swift, native/ax-tool/Sources/commands/ValueCommands.swift, native/ax-tool/Sources/commands/Scroll.swift, +1 more
  • actionRefusal (~) → 2 dependent(s) outside this PR: src/control/lib/decision/assist.ts
  • computerSchemas (~) → 2 dependent(s) outside this PR: src/control/mcp/server.ts
  • RecoveryController (~) → 2 dependent(s) outside this PR: src/control/lib/decision/assist.ts
  • ComputerUse.find (~) → 1 dependent(s) outside this PR: src/control/mcp/server.ts
  • ComputerUse.launch_app (~) → 1 dependent(s) outside this PR: src/control/mcp/server.ts
  • graph truncated by caps — dependents above are a lower bound

ℹ️ Claims vs diff — 5 claims could not be checked from the diff (c2, c4, c5, c6, c7).

Comment thread native/ax-tool/Sources/utils/Output.swift Outdated
Comment thread native/ax-tool/SnapshotSupport/ForegroundGuard.swift Outdated
Comment thread native/ax-tool/Sources/utils/InputGate.swift Outdated
Comment thread native/ax-tool/Sources/Record.swift
Comment thread plugins/genesis-tools/skills/macos-control/SKILL.md
genesiscz added a commit that referenced this pull request Oct 26, 2026
…foreground gate even into the frontmost app, synthetic input never shortens the user's quiet time, a restore is gated too, and window mode refuses an excluded target
@genesiscz

Copy link
Copy Markdown
Owner Author

Review fixes 2026-10-26 02:19 from GPT-6.1 Sol judged by Claude Opus 5.5 (fable-judge)

Round 1. 5 open threads (eve 5). All 5 fixed by the worker's edits; the lead wrote this reply file and verified the change after the worker's session closed. Fixed in 62c5112.

A pointer-moving click skipped the guard when its app was already frontmost (eve-bot-lovinka)

  • Context: The foreground guard ran only inside bringFrontmost when the target was not frontmost, so a click into the frontmost app still went out through the global HID tap while the user worked.
  • Commit(s): 62c511237
  • Verdict: accepted
  • How was this fixed: every global post and press now passes ForegroundInputGate, which checks once per command before the first global event whether the app is frontmost or not; background posts (--to-pid) skip it. Tests: ForegroundGuardTests.testGlobalDispatchRefusesEvenWhenTheTargetIsAlreadyFrontmost and a source test on Harness.swift.
  • Confidence: 85%

The agent's own synthetic input could erase recent human activity (eve-bot-lovinka)

  • Context: The guard treated an idle counter no older than the agent's last post as the agent's own input, so a user who typed just before or after an allowed background post read as idle.
  • Commit(s): 62c511237
  • Verdict: accepted
  • How was this fixed: the own-input record is gone: any input inside the quiet window keeps the guard closed, so synthetic input can lengthen the wait but never shorten it. The cost is that consecutive foreground commands under an agent wait out the quiet window; background commands are unchanged.
  • Confidence: 85%

The synthetic-input file ignored the canonical storage root (eve-bot-lovinka)

  • Context: The file was built as <GENESIS_TOOLS_HOME>/control/last-synthetic-input, outside the /.genesis-tools root every other path uses.
  • Commit(s): 62c511237
  • Verdict: accepted
  • How was this fixed: the file and its writer are removed with the own-input record (see t2), so no path is built at all.
  • Confidence: 90%

Single-window capture did not honour --exclude-window-ids (eve-bot-lovinka)

  • Context: A desktop-independent window stream cannot filter its own target, so recording an excluded window would show it anyway.
  • Commit(s): 62c511237
  • Verdict: accepted
  • How was this fixed: window mode refuses an excluded target before the stream starts (validateRecordingWindow), with a test in IsolatedCompositionTests and a source-order test.
  • Confidence: 90%

The plugin version was not bumped for the new skill guidance (eve-bot-lovinka)

  • Context: The skill gained user-facing safety guidance without a plugin version bump.
  • Commit(s): 62c511237
  • Verdict: accepted
  • How was this fixed: plugin.json and both marketplace entries go to the next version.
  • Confidence: 95%

Threads: t1 accepted · t2 accepted · t3 accepted · t4 accepted · t5 accepted

genesiscz added a commit that referenced this pull request Oct 26, 2026
…foreground gate even into the frontmost app, synthetic input never shortens the user's quiet time, a restore is gated too, and window mode refuses an excluded target
@genesiscz
genesiscz force-pushed the fix/2026-10-25-control-background branch from 62c5112 to 6784e4a Compare October 26, 2026 01:21

@eve-bot-lovinka eve-bot-lovinka Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🐉 eve review — 🔴 Changes requested

6784e4a · 3 actionable findings · reviewed by find codex/gpt-6.1-sol + codex/gpt-6-luna, verify codex/gpt-6-luna · view run ↗

Severity Count
🟠 High 2
🟡 Medium 1

Blast radius

  • bringFrontmost (~) → 3 dependent(s) outside this PR: native/ax-tool/Sources/commands/Scroll.swift, native/ax-tool/Sources/commands/Hotkey.swift, native/ax-tool/Sources/commands/ValueCommands.swift
  • ComputerUse (~) → 3 dependent(s) outside this PR: src/control/mcp/server.ts
  • actionRefusal (~) → 2 dependent(s) outside this PR: src/control/lib/decision/assist.ts
  • computerSchemas (~) → 2 dependent(s) outside this PR: src/control/mcp/server.ts
  • moved (+) → 2 dependent(s) outside this PR: src/macos/GenesisTools/Tests/ReviewDiffTests.swift, src/macos/GenesisTools/Tests/PRThreadsTests.swift
  • RecoveryController (~) → 2 dependent(s) outside this PR: src/control/lib/decision/assist.ts
  • ComputerUse.find (~) → 1 dependent(s) outside this PR: src/control/mcp/server.ts
  • ComputerUse.launch_app (~) → 1 dependent(s) outside this PR: src/control/mcp/server.ts
  • graph truncated by caps — dependents above are a lower bound

ℹ️ Claims vs diff — 6 claims could not be checked from the diff (c2, c4, c5, c6, c7, c8).

Not line-anchorable (review without the full diff context)

  • 🟠 High native/ax-tool/Sources/commands/Inspect.swift:190 · confidence 90/100 — Apply the foreground guard to window focus and raise commands: The existing public control window --action focus path directly activates the application and raises its window without calling either new gate. src/control/commands/discovery.ts:200-228 still exposes this through runAx, so even with the new runner enabling GENESIS_CONTROL_FOREGROUND_GUARD, an agent can take focus while the user types. The close branch also raises the window unless --no-raise is supplied. Route these foreground mutations through the shared guard immediately before activation/raise; the project rule requires safety checks at consuming operations, not only selected callers.

Comment thread native/ax-tool/Sources/utils/Output.swift Outdated
Comment thread native/ax-tool/Sources/utils/InputGate.swift Outdated
genesiscz added a commit that referenced this pull request Oct 26, 2026
…fore a window raise or a focus move, and only a parsed --allow-foreground flag lifts the guard, never a typed value
@genesiscz

Copy link
Copy Markdown
Owner Author

Review fixes 2026-10-26 02:55 from Claude Opus 5.5 judged by Claude Opus 5.5 (fable-judge)

Round 2. 2 open eve threads, both security findings (1 High, 1 Medium); both fixed in 1d7a6b1. The lead ran ForegroundGuardTests and WorkflowArgumentsTests (37 tests, 0 failures) and built the ax-tool package.

An app already in front skipped the foreground guard before a window raise and a focus move (eve-bot-lovinka)

  • Context: bringFrontmost checked the user's quiet time only when the target app was not frontmost. Its callers then raise a window, set AXMain and move AXFocused, so an agent could move keyboard focus from window A to window B of the app the user was typing in. Preparation also focused a field.
  • Commit(s): 1d7a6b1bd
  • Verdict: accepted
  • Code before:
    if !restoring && frontmostPid() != pid {
        do {
            try checkForegroundAllowed()
  • Code after:
    if !restoring {
        do {
            try checkForegroundAllowed()
  • How was this fixed: bringFrontmost now checks the quiet time for every target, so every caller that raises a window or moves focus after it (workflow focus, set-value, type, scroll, hotkey, the raise paths) is refused while the user works. The act --prepare block checks it once more before it raises or focuses anything, so a secondary-click preparation is covered too. A restore of the user's own app stays exempt. The check reads the system idle counter, so it has no unit test; the pure decision (userIsActive) is already covered.
  • Confidence: 88%

A typed value that read --allow-foreground lifted the guard (eve-bot-lovinka)

  • Context: The override was read with CommandLine.arguments.contains, so act --text --allow-foreground (or a set_value with that text) disabled the guard although the parser read the token as the text.
  • Commit(s): 1d7a6b1bd
  • Verdict: accepted
  • Code before:
    private let foregroundAllowed = CommandLine.arguments.contains("--allow-foreground")
  • Code after:
    private let foregroundAllowed = foregroundAllowedByArguments(CommandLine.arguments)
  • How was this fixed: A pure foregroundAllowedByArguments in SnapshotSupport reads the flag from act's own WorkflowArguments parse, skips the values of launch-app's --bundle-id and --path, and honours it nowhere else (the runner passes it only to act and launch-app). A new ForegroundGuardTests case covers a typed value, a set value, a real flag, launch-app and a command that never takes it.
  • Confidence: 93%

Threads: t1 accepted · t2 accepted

@eve-bot-lovinka eve-bot-lovinka Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🐉 eve review — 🟡 Review comments

1d7a6b1 · 1 actionable finding · reviewed by find codex/gpt-6.1-sol + codex/gpt-6-luna, verify codex/gpt-6-luna · view run ↗

Severity Count
🟡 Medium 1

Comment thread native/ax-tool/Sources/SnapshotWorkflow.swift Outdated
genesiscz added a commit that referenced this pull request Oct 26, 2026
…m the preparation check, because it neither raises nor focuses
@genesiscz

Copy link
Copy Markdown
Owner Author

Review fixes 2026-10-26 02:59 from Claude Opus 5.5 judged by Claude Opus 5.5 (fable-judge)

Round 3. 1 open eve thread (Medium), a regression of round 2's security fix; fixed in ccf9800, applied as eve suggested.

The new preparation check refused a prepared right-click that runs in the background (eve-bot-lovinka)

  • Context: Round 2 added an unconditional quiet-time check at the start of act --prepare. A prepared right-click skips focus and raise and clicks in the background, so the check refused a background-safe call while the user worked.
  • Commit(s): ccf9800d4
  • Verdict: accepted
  • Code before:
    gatedOrExit { try checkForegroundAllowed() }
  • Code after:
    if !secondaryClick {
        gatedOrExit { try checkForegroundAllowed() }
    }
  • How was this fixed: The preparation check runs only when the preparation raises or focuses, which is every case but a right-click (secondaryClick: action click with --button right). The right-click path never calls workflowFocus and never sets AXFocused (that needs an action other than click), so nothing it does takes focus. The ax-tool package builds.
  • Confidence: 92%

Threads: t1 accepted

@eve-bot-lovinka eve-bot-lovinka Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🐉 eve review — ✅ Approved

ccf9800 · 0 actionable findings · reviewed by codex/gpt-6.1-sol · view run ↗

… names (ScreenCaptureKit ignores sharingType none since macOS 15), and a frame the PNG cap keeps out still counts as motion, so a long movie no longer drops to the idle rate
…front or move the pointer is refused while the user worked in the last 5 s, a background click into a window that is not key says it may have done nothing, and every action takes the window_id get_app_state took
…e background first, names the calls that take focus and the new refusal, and records the window_id, list_windows, drag and inactive-window facts agents tripped over
…foreground gate even into the frontmost app, synthetic input never shortens the user's quiet time, a restore is gated too, and window mode refuses an excluded target
…fore a window raise or a focus move, and only a parsed --allow-foreground flag lifts the guard, never a typed value
…m the preparation check, because it neither raises nor focuses
@genesiscz
genesiscz force-pushed the fix/2026-10-25-control-background branch from ccf9800 to 2e6e8d0 Compare October 26, 2026 02:41
@genesiscz
genesiscz merged commit 2e6e8d0 into master Oct 26, 2026
6 of 7 checks passed

@eve-bot-lovinka eve-bot-lovinka Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🐉 eve review — 🔴 Changes requested

2e6e8d0 · 3 actionable findings · reviewed by find codex/gpt-6.1-sol + codex/gpt-6-luna, verify codex/gpt-6-luna · view run ↗

Severity Count
🟠 High 1
🟡 Medium 2

ℹ️ This pass reviewed the full PR diff: the incremental delta since 00ca3010 was unusable after a base-branch merge saturated GitHub's compare API (300+ files, 270 of them merge noise, list truncated).

Blast radius

  • bringFrontmost (~) → 3 dependent(s) outside this PR: native/ax-tool/Sources/commands/ValueCommands.swift, native/ax-tool/Sources/commands/Scroll.swift, native/ax-tool/Sources/commands/Hotkey.swift
  • ComputerUse (~) → 3 dependent(s) outside this PR: src/control/mcp/server.ts
  • actionRefusal (~) → 2 dependent(s) outside this PR: src/control/lib/decision/assist.ts
  • computerSchemas (~) → 2 dependent(s) outside this PR: src/control/mcp/server.ts
  • RecoveryController (~) → 2 dependent(s) outside this PR: src/control/lib/decision/assist.ts
  • ComputerUse.find (~) → 1 dependent(s) outside this PR: src/control/mcp/server.ts
  • ComputerUse.launch_app (~) → 1 dependent(s) outside this PR: src/control/mcp/server.ts
  • graph truncated by caps — dependents above are a lower bound

ℹ️ Claims vs diff — 4 claims could not be checked from the diff (c2, c3, c4, c8).

Not line-anchorable (review without the full diff context)

  • 🟠 High native/ax-tool/Sources/SnapshotWorkflow.swift:1391 · confidence 94/100 — Guard explicit AXRaise actions as well as prepared focus: The foreground check covers preparation and workflowFocus, but an unprepared act --action perform --ax-action AXRaise reaches workflowAXAction directly. That helper verifies that AXRaise is exposed and then calls AXUIElementPerformAction without checking foreground permission. The ComputerUse perform_secondary_action API exposes this path too. An agent can therefore raise an observed window while the user is active, without allow_foreground. This is a separate entry point from the previously reported Inspect.swift window-command issue. Apply the foreground check at the AXRaise dispatch boundary and test that the actual AX dispatch is not reached on refusal, as required by the project's safety-guard rule.

* The window a call means, as get_app_state takes it. Refused unless the app's latest observation is of that
* window, so a call never acts on another window's references or pixels.
*/
const windowIdSchema = z.number().int().positive().optional();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📋 Spec | 🟡 Medium · confidence 90/100

📋 Spec drift

Window ID is optional on action calls

The intent says, “Every action takes the window_id obtained by get_app_state.” This schema makes window_id optional, and assertObservedWindow skips validation when it is omitted, so calls can still act without naming the observed window.

🧩 Analysis

Grep evidence: const windowIdSchema = z.number().int().positive().optional()

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Accepted. The action schema should state when a window id is required, so a call without one fails with a clear message instead of acting on the frontmost window. The pull request was merged after its fix rounds, so this finding waits for a follow-up, and this thread stays open until then.

// A window-addressed left click into an app that is not in front lands in a window that is not key.
// A SwiftUI control there takes it as the activation click and does nothing, while the click itself
// was delivered (a sheet's "Cancel" stayed open, 2026-10-23). Say so, so the caller checks the effect.
if action == "click", workflowFlag("--background"), !secondaryClick, !nonActivatingPanel, startingFrontmost != pid {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Quality | 🟡 Medium · confidence 85/100

⚠️ Potential issue

Detect inactive windows within the frontmost app too

The new effect note tests only whether another application was frontmost. When the observed target is a non-key window of the already-frontmost app, a background window-addressed click has the same activation-click limitation, but startingFrontmost equals pid and the response omits the warning. Multi-window operation is explicitly supported by the new window_id plumbing, so this misses an important supported case and callers receive plain ok:true for a potentially ineffective click. Base the note on the target window's actual key/focused-window status, not just the application's PID, and cover two windows in one app.

🧩 Analysis

Grep evidence: startingFrontmost != pid|let windowFocused = frontmostPid\(\) == pid|inactiveWindowClick

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Accepted. The follow-up will detect inactive windows within the frontmost app too. The pull request was merged after its fix rounds, so this finding waits for a follow-up, and this thread stays open until then.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant