Require a timeout on every agent tool

This commit is contained in:
Ian Gulliver
2026-10-22 15:35:33 -07:00
parent c59e1cb4c3
commit b2cb15dd2a
12 changed files with 196 additions and 111 deletions
+20 -13
View File
@@ -18,8 +18,8 @@ let modifierFlags: [String: CGEventFlags] = [
"fn": .maskSecondaryFn,
]
func pause(_ ms: Int) async {
try? await Task.sleep(nanoseconds: UInt64(ms) * 1_000_000)
func pause(_ ms: Int) async throws {
try await Task.sleep(nanoseconds: UInt64(ms) * 1_000_000)
}
let shifted: [Character: String] = [
@@ -56,7 +56,12 @@ func lockInput() async throws -> Int32 {
close(fd)
throw ToolError("another agent has been driving this Mac's apps for 30s, so nothing ran; try again")
}
await pause(100)
do {
try await pause(100)
} catch {
close(fd)
throw error
}
}
return fd
}
@@ -82,7 +87,7 @@ func focusWindow(_ id: Int, _ w: AXUIElement) async throws -> WindowInfo {
}
NSRunningApplication(processIdentifier: info.pid)?.activate()
AXUIElementPerformAction(w, kAXRaiseAction as CFString)
await pause(150)
try await pause(150)
let now = try window(id)
if try !isFront(now) {
throw ToolError("could not bring window \(id) to the front (\(NSWorkspace.shared.frontmostApplication?.localizedName ?? "nothing") is frontmost), so no input was sent")
@@ -90,32 +95,34 @@ func focusWindow(_ id: Int, _ w: AXUIElement) async throws -> WindowInfo {
return now
}
func click(_ p: CGPoint, right: Bool, count: Int) async {
func click(_ p: CGPoint, right: Bool, count: Int) async throws {
let (down, up): (CGEventType, CGEventType) = right ? (.rightMouseDown, .rightMouseUp) : (.leftMouseDown, .leftMouseUp)
let button: CGMouseButton = right ? .right : .left
post(CGEvent(mouseEventSource: nil, mouseType: .mouseMoved, mouseCursorPosition: p, mouseButton: button))
await pause(30)
try await pause(30)
for i in 1...max(1, count) {
for t in [down, up] {
let e = CGEvent(mouseEventSource: nil, mouseType: t, mouseCursorPosition: p, mouseButton: button)
e?.setIntegerValueField(.mouseEventClickState, value: Int64(i))
post(e)
}
await pause(30)
try await pause(30)
}
}
func drag(_ from: CGPoint, _ to: CGPoint) async {
func drag(_ from: CGPoint, _ to: CGPoint) async throws {
post(CGEvent(mouseEventSource: nil, mouseType: .mouseMoved, mouseCursorPosition: from, mouseButton: .left))
post(CGEvent(mouseEventSource: nil, mouseType: .leftMouseDown, mouseCursorPosition: from, mouseButton: .left))
await pause(50)
defer {
post(CGEvent(mouseEventSource: nil, mouseType: .leftMouseUp, mouseCursorPosition: to, mouseButton: .left))
}
try await pause(50)
for i in 1...20 {
let f = CGFloat(i) / 20
let p = CGPoint(x: from.x + (to.x - from.x) * f, y: from.y + (to.y - from.y) * f)
post(CGEvent(mouseEventSource: nil, mouseType: .leftMouseDragged, mouseCursorPosition: p, mouseButton: .left))
await pause(10)
try await pause(10)
}
post(CGEvent(mouseEventSource: nil, mouseType: .leftMouseUp, mouseCursorPosition: to, mouseButton: .left))
}
func scroll(_ p: CGPoint, dx: Int32, dy: Int32) {
@@ -123,7 +130,7 @@ func scroll(_ p: CGPoint, dx: Int32, dy: Int32) {
post(CGEvent(scrollWheelEvent2Source: nil, units: .line, wheelCount: 2, wheel1: dy, wheel2: dx, wheel3: 0))
}
func type(_ s: String) async {
func type(_ s: String) async throws {
for ch in s {
for down in [true, false] {
if let (code, flags) = keystroke(ch) {
@@ -137,7 +144,7 @@ func type(_ s: String) async {
e?.keyboardSetUnicodeString(stringLength: units.count, unicodeString: units)
post(e)
}
await pause(15)
try await pause(15)
}
}