pi_agent_rust 0.8.0

Native AI coding agent CLI - Rust port of Pi Agent
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
// One owned input transaction. stdin is a liveness lease after its JSON line.
// Only this process emits input; EOF stops new events before deferred releases.
import Foundation
#if os(macOS)
import Darwin
import CoreGraphics
import Carbon
#else
import Glibc
#endif

let ownedInputActions = ["mouse_click", "mouse_drag", "key_type", "key_press"]
let ownedInputInvocation = ProcessInfo.processInfo.environment["PI_COMPUTER_OWNED_INPUT"] == "1"

func readOwnedInputFrame() throws -> Data {
    let flags = fcntl(STDIN_FILENO, F_GETFL)
    guard flags >= 0, fcntl(STDIN_FILENO, F_SETFL, flags | O_NONBLOCK) == 0 else {
        throw DesktopFailure("input_channel")
    }
    var data = Data()
    var buffer = [UInt8](repeating: 0, count: 4096)
    let deadline = ProcessInfo.processInfo.systemUptime + 30
    while ProcessInfo.processInfo.systemUptime < deadline {
        let count = read(STDIN_FILENO, &buffer, buffer.count)
        if count > 0 {
            let chunk = buffer.prefix(count)
            if let end = chunk.firstIndex(of: 10) {
                guard end == count - 1, data.count + end <= 32 * 1024 else {
                    throw DesktopFailure("invalid_request")
                }
                data.append(contentsOf: chunk.prefix(end))
                return data
            }
            guard data.count + count <= 32 * 1024 else { throw DesktopFailure("invalid_request") }
            data.append(contentsOf: chunk)
        } else if count == 0 {
            throw DesktopFailure("input_cancelled")
        } else if errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR {
            throw DesktopFailure("input_channel")
        }
        Thread.sleep(forTimeInterval: 0.002)
    }
    throw DesktopFailure("input_cancelled")
}

func checkInputLease(_ deadline: Double) throws {
    guard ProcessInfo.processInfo.systemUptime < deadline else { throw DesktopFailure("input_cancelled") }
    for _ in 0..<4 {
        var byte: UInt8 = 0
        let count = read(STDIN_FILENO, &byte, 1)
        // Exactly one frame is authorized. EOF and a second command revoke it.
        if count >= 0 { throw DesktopFailure("input_cancelled") }
        if errno == EAGAIN || errno == EWOULDBLOCK { return }
        if errno != EINTR { throw DesktopFailure("input_channel") }
    }
    // An interrupted observation is not affirmative proof of a live lease.
    throw DesktopFailure("input_channel")
}

enum OwnedEventKind { case move, down, drag, up }
struct OwnedEvent {
    let kind: OwnedEventKind
    let code: UInt16
    let x: Double
    let y: Double
}

// The same lifecycle engine is exercised with recording sinks in portable
// tests. Cleanup is armed BEFORE a potentially failing press, removed AFTER
// its release, and never requires the cancelled lease to become live again.
func dispatchOwnedEvents(
    _ events: [OwnedEvent],
    checkpoint: () throws -> Void,
    post: (Int, OwnedEvent) throws -> Void,
    release: (UInt16) -> Void
) throws {
    guard events.count <= 64 else { throw DesktopFailure("invalid_request") }
    var held: [UInt16] = []
    defer { for code in held.reversed() { release(code) } }
    for (index, event) in events.enumerated() {
        try checkpoint()
        if event.kind == .down {
            guard !held.contains(event.code), held.count < 8 else { throw DesktopFailure("invalid_request") }
            held.append(event.code)
        }
        try post(index, event)
        if event.kind == .up {
            guard let index = held.lastIndex(of: event.code) else { throw DesktopFailure("invalid_request") }
            held.remove(at: index)
        }
    }
    guard held.isEmpty else { throw DesktopFailure("input_failed") }
    try checkpoint()
}

func mouseEventPlan(_ action: String, button: String?, startX: Double, startY: Double,
                    x: Double?, y: Double?) throws -> [OwnedEvent] {
    let code: UInt16
    switch button ?? "left" {
    case "left": code = 0
    case "right": code = 1
    case "middle": code = 2
    default: throw DesktopFailure("invalid_request")
    }
    guard startX.isFinite, startY.isFinite, (x == nil) == (y == nil),
          x?.isFinite != false, y?.isFinite != false else { throw DesktopFailure("invalid_request") }
    let endX = x ?? startX, endY = y ?? startY
    func event(_ kind: OwnedEventKind, _ x: Double, _ y: Double) -> OwnedEvent {
        OwnedEvent(kind: kind, code: code, x: x, y: y)
    }
    switch action {
    case "mouse_click":
        var events: [OwnedEvent] = []
        if x != nil { events.append(event(.move, endX, endY)) }
        events.append(event(.down, endX, endY))
        events.append(event(.up, endX, endY))
        return events
    case "mouse_drag":
        guard x != nil else { throw DesktopFailure("invalid_request") }
        var events = [event(.down, startX, startY)]
        for step in 1...24 {
            let progress = Double(step) / 24
            events.append(event(.drag, startX + (endX - startX) * progress,
                                 startY + (endY - startY) * progress))
        }
        events.append(event(.up, endX, endY))
        return events
    default: throw DesktopFailure("unsupported_action")
    }
}

// Keyboard names are logical tokens here; only the native boundary translates
// them using Carbon's named virtual-key constants. No guessed numeric keycodes.
struct KeyboardStroke {
    let key: String
    let down: Bool
    let modifiers: [String]
    var unicode: [UInt16] = []
}

func chordEventPlan(_ chord: String) throws -> [KeyboardStroke] {
    guard !chord.isEmpty, chord.utf8.count <= 128 else { throw DesktopFailure("invalid_request") }
    let parts = chord.split(separator: "+", omittingEmptySubsequences: false).map(String.init)
    guard (1...5).contains(parts.count) else { throw DesktopFailure("invalid_request") }
    var modifiers: [String] = []
    for part in parts.dropLast() {
        let modifier: String
        switch part.lowercased() {
        case "ctrl", "control": modifier = "ctrl"
        case "alt", "option": modifier = "alt"
        case "shift": modifier = "shift"
        case "super", "meta", "cmd", "command": modifier = "super"
        default: throw DesktopFailure("unsupported_key")
        }
        guard !modifiers.contains(modifier) else { throw DesktopFailure("invalid_request") }
        modifiers.append(modifier)
    }
    let last = parts.last!
    let key: String
    switch last.lowercased() {
    case "enter", "return": key = "return"
    case "escape", "esc": key = "escape"
    case "pageup", "prior": key = "pageup"
    case "pagedown", "next": key = "pagedown"
    case "arrowleft": key = "left"
    case "arrowright": key = "right"
    case "arrowup": key = "up"
    case "arrowdown": key = "down"
    case "tab", "space", "backspace", "delete", "insert", "home", "end", "left", "right", "up", "down":
        key = last.lowercased()
    default:
        if last.utf8.count == 1, let byte = last.utf8.first,
           (65...90).contains(byte) || (97...122).contains(byte) || (48...57).contains(byte) {
            key = last.lowercased()
            // Bare uppercase letters mean Shift+letter. Ctrl+C remains Ctrl+c;
            // callers must include Shift explicitly for a shifted shortcut.
            if modifiers.isEmpty && (65...90).contains(byte) { modifiers.append("shift") }
        } else if last.hasPrefix("F"), let number = Int(last.dropFirst()),
                  (1...20).contains(number), last == "F\(number)" {
            key = last.lowercased()
        } else { throw DesktopFailure("unsupported_key") }
    }
    var events: [KeyboardStroke] = []
    var active: [String] = []
    for modifier in modifiers {
        active.append(modifier)
        events.append(KeyboardStroke(key: modifier, down: true, modifiers: active))
    }
    events.append(KeyboardStroke(key: key, down: true, modifiers: active))
    events.append(KeyboardStroke(key: key, down: false, modifiers: active))
    for modifier in modifiers.reversed() {
        active.removeAll { $0 == modifier }
        events.append(KeyboardStroke(key: modifier, down: false, modifiers: active))
    }
    return events
}

func textEventPlan(_ text: String) throws -> [[KeyboardStroke]] {
    guard text.unicodeScalars.count <= 4096, !text.unicodeScalars.contains(where: {
        $0.properties.generalCategory == .control && $0.value != 10 && $0.value != 9
    }) else { throw DesktopFailure("invalid_request") }
    // Preserve scalar order, combining characters and complete surrogate pairs.
    // Newline and Tab are actual navigation keys; other text is literal Unicode.
    return text.unicodeScalars.map { scalar in
        let key = scalar.value == 10 ? "return" : scalar.value == 9 ? "tab" : "unicode"
        let units = key == "unicode" ? Array(String(scalar).utf16) : []
        return [KeyboardStroke(key: key, down: true, modifiers: [], unicode: units),
                KeyboardStroke(key: key, down: false, modifiers: [], unicode: units)]
    }
}

func keyboardLifecycle(_ strokes: [KeyboardStroke]) throws -> ([String], [OwnedEvent]) {
    var names: [String] = []
    for stroke in strokes where !names.contains(stroke.key) { names.append(stroke.key) }
    guard strokes.count <= 10, names.count <= 5 else { throw DesktopFailure("invalid_request") }
    let events = strokes.map { stroke in
        OwnedEvent(kind: stroke.down ? .down : .up, code: UInt16(names.firstIndex(of: stroke.key)!), x: 0, y: 0)
    }
    return (names, events)
}

#if os(macOS) && !PI_INPUT_TESTS
func inputCheckpoint(_ request: Request, deadline: Double) throws -> () throws -> Void {
    let target = try request.window_id.map(resolveWindow)
    return {
        try checkInputLease(deadline)
        if let (pid, window) = target {
            let (actualPID, actualWindow) = try focusedWindow()
            guard pid == actualPID, CFEqual(window, actualWindow) else {
                throw DesktopFailure("target_not_focused")
            }
        }
        // Focus queries can block. Cancellation is checked again before input.
        try checkInputLease(deadline)
    }
}

func performOwnedMouse(_ request: Request) throws -> [String: Any] {
    try requireAccessibility()
    let deadline = ProcessInfo.processInfo.systemUptime + 20
    try checkInputLease(deadline)
    guard let start = CGEvent(source: nil)?.location else { throw DesktopFailure("input_failed") }
    let events = try mouseEventPlan(request.action, button: request.button,
        startX: Double(start.x), startY: Double(start.y),
        x: request.x.map { Double($0) }, y: request.y.map { Double($0) })
    let displays = try displayList()
    // Validate the entire path before a press, including gaps between displays.
    for event in events {
        guard displays.contains(where: { display in
            guard let x = display["x"] as? Double, let y = display["y"] as? Double,
                  let w = display["width"] as? Double, let h = display["height"] as? Double else { return false }
            return event.x >= x && event.x < x + w && event.y >= y && event.y < y + h
        }) else { throw DesktopFailure("pointer_outside_displays") }
    }
    let button = CGMouseButton(rawValue: UInt32(events[0].code))!
    guard !CGEventSource.buttonState(.combinedSessionState, button: button),
          CGEventSource.flagsState(.combinedSessionState)
            .intersection([.maskShift, .maskControl, .maskAlternate, .maskCommand]).isEmpty else {
        throw DesktopFailure("input_conflict")
    }
    let checkpoint = try inputCheckpoint(request, deadline: deadline)
    func nativeEvent(_ event: OwnedEvent) throws -> CGEvent {
        let kind: CGEventType
        switch event.kind {
        case .move: kind = .mouseMoved
        case .down: kind = button == .left ? .leftMouseDown : button == .right ? .rightMouseDown : .otherMouseDown
        case .up: kind = button == .left ? .leftMouseUp : button == .right ? .rightMouseUp : .otherMouseUp
        case .drag: kind = button == .left ? .leftMouseDragged : button == .right ? .rightMouseDragged : .otherMouseDragged
        }
        guard let result = CGEvent(mouseEventSource: nil, mouseType: kind,
            mouseCursorPosition: CGPoint(x: event.x, y: event.y), mouseButton: button) else {
            throw DesktopFailure("input_failed")
        }
        result.flags = []
        if event.kind == .down || event.kind == .up {
            result.setIntegerValueField(.mouseEventClickState, value: 1)
        }
        return result
    }
    // Allocate normal and emergency release events BEFORE the first press.
    let prepared = try events.map(nativeEvent)
    let emergency = try nativeEvent(OwnedEvent(kind: .up, code: events[0].code,
        x: Double(start.x), y: Double(start.y)))
    try dispatchOwnedEvents(events, checkpoint: checkpoint, post: { index, event in
        prepared[index].post(tap: .cghidEventTap)
        if event.kind == .drag || event.kind == .down { Thread.sleep(forTimeInterval: 0.01) }
    }, release: { _ in
        // Use the actual pointer on abort, never jump back to the drag origin.
        if let position = CGEvent(source: nil)?.location { emergency.location = position }
        emergency.post(tap: .cghidEventTap)
    })
    guard let end = events.last, let actual = CGEvent(source: nil)?.location,
          abs(Double(actual.x) - end.x) <= 0.5, abs(Double(actual.y) - end.y) <= 0.5 else {
        throw DesktopFailure("pointer_unconfirmed")
    }
    var result: [String: Any] = ["dispatched": true, "released": true, "button": request.button ?? "left",
            "position": ["x": Double(actual.x), "y": Double(actual.y)],
            "application_effect": "not_verified"]
    if let id = request.window_id { result["window_id"] = id }
    return result
}

// Carbon identifiers describe physical ANSI-US key positions, not the user's
// active keyboard layout. Build the lookup once, not for every typed scalar.
let keyboardKeyCodes: [String: Int] = [
        "unicode": kVK_ANSI_A,
        "a": kVK_ANSI_A, "b": kVK_ANSI_B, "c": kVK_ANSI_C, "d": kVK_ANSI_D,
        "e": kVK_ANSI_E, "f": kVK_ANSI_F, "g": kVK_ANSI_G, "h": kVK_ANSI_H,
        "i": kVK_ANSI_I, "j": kVK_ANSI_J, "k": kVK_ANSI_K, "l": kVK_ANSI_L,
        "m": kVK_ANSI_M, "n": kVK_ANSI_N, "o": kVK_ANSI_O, "p": kVK_ANSI_P,
        "q": kVK_ANSI_Q, "r": kVK_ANSI_R, "s": kVK_ANSI_S, "t": kVK_ANSI_T,
        "u": kVK_ANSI_U, "v": kVK_ANSI_V, "w": kVK_ANSI_W, "x": kVK_ANSI_X,
        "y": kVK_ANSI_Y, "z": kVK_ANSI_Z,
        "0": kVK_ANSI_0, "1": kVK_ANSI_1, "2": kVK_ANSI_2, "3": kVK_ANSI_3,
        "4": kVK_ANSI_4, "5": kVK_ANSI_5, "6": kVK_ANSI_6, "7": kVK_ANSI_7,
        "8": kVK_ANSI_8, "9": kVK_ANSI_9,
        "return": kVK_Return, "escape": kVK_Escape, "tab": kVK_Tab, "space": kVK_Space,
        "backspace": kVK_Delete, "delete": kVK_ForwardDelete, "insert": kVK_Help,
        "home": kVK_Home, "end": kVK_End, "left": kVK_LeftArrow, "right": kVK_RightArrow,
        "up": kVK_UpArrow, "down": kVK_DownArrow, "pageup": kVK_PageUp, "pagedown": kVK_PageDown,
        "shift": kVK_Shift, "ctrl": kVK_Control, "alt": kVK_Option, "super": kVK_Command,
        "f1": kVK_F1, "f2": kVK_F2, "f3": kVK_F3, "f4": kVK_F4, "f5": kVK_F5,
        "f6": kVK_F6, "f7": kVK_F7, "f8": kVK_F8, "f9": kVK_F9, "f10": kVK_F10,
        "f11": kVK_F11, "f12": kVK_F12, "f13": kVK_F13, "f14": kVK_F14, "f15": kVK_F15,
        "f16": kVK_F16, "f17": kVK_F17, "f18": kVK_F18, "f19": kVK_F19, "f20": kVK_F20,
]

func virtualKey(_ key: String) throws -> CGKeyCode {
    guard let code = keyboardKeyCodes[key], let narrowed = CGKeyCode(exactly: code) else {
        throw DesktopFailure("unsupported_key")
    }
    return narrowed
}

func keyboardFlags(_ modifiers: [String]) throws -> CGEventFlags {
    var flags: CGEventFlags = []
    for modifier in modifiers {
        switch modifier {
        case "shift": flags.insert(.maskShift)
        case "ctrl": flags.insert(.maskControl)
        case "alt": flags.insert(.maskAlternate)
        case "super": flags.insert(.maskCommand)
        default: throw DesktopFailure("unsupported_key")
        }
    }
    return flags
}

func postKeyboardTransaction(_ strokes: [KeyboardStroke], source: CGEventSource,
                             checkpoint: () throws -> Void) throws {
    let (names, events) = try keyboardLifecycle(strokes)
    func makeEvent(_ stroke: KeyboardStroke) throws -> CGEvent {
        guard let event = CGEvent(keyboardEventSource: source, virtualKey: try virtualKey(stroke.key),
                                  keyDown: stroke.down) else { throw DesktopFailure("input_failed") }
        event.flags = try keyboardFlags(stroke.modifiers)
        if ["ctrl", "alt", "shift", "super"].contains(stroke.key) { event.type = .flagsChanged }
        event.setIntegerValueField(.keyboardEventAutorepeat, value: 0)
        if !stroke.unicode.isEmpty {
            stroke.unicode.withUnsafeBufferPointer { units in
                event.keyboardSetUnicodeString(stringLength: units.count, unicodeString: units.baseAddress)
            }
        }
        return event
    }
    // Allocate every event and emergency release before the transaction's
    // first down. Text uses one scalar transaction at a time, not 8192 CGEvents.
    let prepared = try strokes.map(makeEvent)
    let emergency = try names.map { try makeEvent(KeyboardStroke(key: $0, down: false, modifiers: [])) }
    try dispatchOwnedEvents(events, checkpoint: checkpoint, post: { index, _ in
        prepared[index].post(tap: .cghidEventTap)
        Thread.sleep(forTimeInterval: 0.001)
    }, release: { code in
        emergency[Int(code)].post(tap: .cghidEventTap)
    })
}

func performOwnedKeyboard(_ request: Request) throws -> [String: Any] {
    let transactions: [[KeyboardStroke]]
    if request.action == "key_type" {
        guard let text = request.text else { throw DesktopFailure("invalid_request") }
        transactions = try textEventPlan(text)
    } else {
        guard request.action == "key_press", let chord = request.key else { throw DesktopFailure("invalid_request") }
        transactions = [try chordEventPlan(chord)]
    }
    try requireAccessibility()
    let deadline = ProcessInfo.processInfo.systemUptime + 20
    let checkpoint = try inputCheckpoint(request, deadline: deadline)
    try checkpoint()
    guard let source = CGEventSource(stateID: .combinedSessionState) else { throw DesktopFailure("input_failed") }
    let keys = Set(transactions.flatMap { $0.map { $0.key } })
    if !keys.isEmpty {
        guard CGEventSource.flagsState(.combinedSessionState)
            .intersection([.maskShift, .maskControl, .maskAlternate, .maskCommand, .maskAlphaShift, .maskSecondaryFn]).isEmpty else {
            throw DesktopFailure("input_conflict")
        }
        for key in keys {
            guard !CGEventSource.keyState(.combinedSessionState, key: try virtualKey(key)) else {
                throw DesktopFailure("input_conflict")
            }
        }
    }
    for transaction in transactions {
        try checkpoint()
        try postKeyboardTransaction(transaction, source: source, checkpoint: checkpoint)
    }
    try checkpoint()
    var result: [String: Any] = ["dispatched": true, "released": true, "application_effect": "not_verified"]
    if request.action == "key_type" { result["char_count"] = transactions.count }
    else { result["key"] = request.key! }
    if let id = request.window_id { result["window_id"] = id }
    return result
}

func performOwnedInput(_ request: Request) throws -> [String: Any] {
    if request.action == "key_type" || request.action == "key_press" {
        return try performOwnedKeyboard(request)
    }
    return try performOwnedMouse(request)
}
#endif