| 610 | } |
| 611 | |
| 612 | export function buildComputerUseTools(deps: { |
| 613 | backend: CuDispatchBackend; |
| 614 | overlay?: CuOverlayHook; |
| 615 | /** |
| 616 | * Whether the machine is locked right now. |
| 617 | * |
| 618 | * Consulted before every action and every observation, because a locked |
| 619 | * screen is the user saying they have left: past that point the agent would |
| 620 | * be driving and reading a machine its owner deliberately closed off, with |
| 621 | * nobody watching and no way to intervene. Refusing produces the |
| 622 | * `screen_locked` session state, which is what keeps the refusal from having |
| 623 | * to be re-derived on every subsequent call — and which only |
| 624 | * `sessionEvents.screenUnlocked` can leave. |
| 625 | * |
| 626 | * Takes the session so the host can record which sessions it will have to |
| 627 | * release when the machine comes back. Without that, a session whose very |
| 628 | * first call landed on a locked screen would latch `screen_locked` while |
| 629 | * being unknown to the host, and would stay latched for the rest of its life. |
| 630 | * |
| 631 | * Absent means the host cannot answer the question, not that the machine is |
| 632 | * unlocked; there is no lock guard at all in that case. |
| 633 | */ |
| 634 | screenLocked?: (context: { sessionId: string }) => boolean | Promise<boolean>; |
| 635 | presentationReadyTimeoutMs?: number; |
| 636 | presentationFinishedTimeoutMs?: number; |
| 637 | /** Diagnostics only. Never on by default, never able to change an outcome. */ |
| 638 | debug?: (record: CuDebugRecord) => void; |
| 639 | }): ComputerUseToolSet { |
| 640 | const presentationReadyTimeoutMs = deps.presentationReadyTimeoutMs ?? 1_000; |
| 641 | const presentationFinishedTimeoutMs = |
| 642 | deps.presentationFinishedTimeoutMs ?? DEFAULT_PRESENTATION_FINISHED_TIMEOUT_MS; |
| 643 | const invocationQueues = new Map<string, Promise<void>>(); |
| 644 | const presentationWaiters = new Map<string, Set<() => void>>(); |
| 645 | const presentationQueueWaiters = new Map<string, Set<() => void>>(); |
| 646 | const presentationGenerations = new Map<string, number>(); |
| 647 | const pendingInvocationTurns = new Map<string, Set<string>>(); |
| 648 | let presentationQueue = Promise.resolve(); |
| 649 | let accessibilityPermissionRequested = false; |
| 650 | interface SessionObservationRecord { |
| 651 | turnId: string; |
| 652 | state: CuaFrameState; |
| 653 | backendObservationId?: string; |
| 654 | appId?: string; |
| 655 | /** The non-canonical name that resolved this observation, if any. */ |
| 656 | appAlias?: string; |
| 657 | windowId?: number; |
| 658 | elements?: Map<string, CuObservedElement>; |
| 659 | /** From the last observation: the windows stacked above the target. */ |
| 660 | obscuringRects?: Array<{ x: number; y: number; width: number; height: number }>; |
| 661 | } |
| 662 | const observations = new Map<string, SessionObservationRecord>(); |
| 663 | interface SessionStateRecord { |
| 664 | turnId?: string; |
| 665 | state: CuaSessionState; |
| 666 | } |
| 667 | const sessionStates = new Map<string, SessionStateRecord>(); |
| 668 | |
| 669 | function sessionState(sessionId: string, turnId?: string): CuaSessionState { |