char-slop/ai-dots
ai dotfiles
git clone https://git.t4t.associates/char-slop/ai-dots
dc2c12a
main
1import assert from "node:assert/strict" ; 2import childProcess from "node:child_process" ; 3import { mkdtemp , readFile , rm } from "node:fs/promises" ; 4import { syncBuiltinESMExports } from "node:module" ; 5import { tmpdir } from "node:os" ; 6import { join } from "node:path" ; 7import { test , type TestContext } from "node:test" ; 8import type { 9ContextEvent , ContextEventResult , ExtensionAPI , ExtensionToolContext , 10SessionEntry , ToolDefinition , 11} from "@earendil-works/pi-coding-agent" ; 12import computerUse from "../computer-use.ts" ; 13import { parameters , type Action } from "../computer-use/backend.ts" ; 14import { createWindowsBackend } from "../computer-use/windows.ts" ; 15 16const png = Buffer . from ( 17"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+aV1kAAAAASUVORK5CYII=" , 18"base64" , 19); 20 21async function fixture ( t :TestContext , failStartup = false ) { 22const directory = await mkdtemp ( join ( tmpdir (), "pi-desktop-test-" )); 23const log = join ( directory , "actions.jsonl" ); 24const spawn = childProcess . spawn ; 25let processes = 0 ; 26t . mock . method ( childProcess , "spawn" , () => { 27processes ++ ; 28return spawn ( process . execPath , [ "-e" , failStartup ?` 29console.error("Desktop initialization failed"); 30process.exit(1); 31` :` 32const fs = require("node:fs"); 33const rl = require("node:readline").createInterface({ input: process.stdin }); 34const record = (value) => fs.appendFileSync( ${ JSON . stringify ( log )} , JSON.stringify(value) + "\\n"); 35rl.on("line", (line) => { 36const action = JSON.parse(line); 37record(action); 38if (action.key === "Wait") return; 39const response = action.key === "Unknown" 40? { error: "Unsupported key name: Unknown" } 41: action.action === "screenshot" ? { png: ${ JSON . stringify ( png . toString ( "base64" ))} } : {}; 42console.log(JSON.stringify(response)); 43}); 44rl.on("close", () => record("released")); 45` ], { stdio :[ "pipe" , "pipe" , "pipe" ] }); 46}); 47syncBuiltinESMExports (); 48t . after ( async () => { 49t . mock . restoreAll (); 50syncBuiltinESMExports (); 51await rm ( directory , { recursive :true , force :true }); 52}); 53 54let tool :ToolDefinition < typeof parameters , { screenshotIds :string [] }> | undefined ; 55let context :(( event :ContextEvent ) => ContextEventResult ) | undefined ; 56let agentEnd :(() => void ) | undefined ; 57const platform = Object . getOwnPropertyDescriptor ( process , "platform" ) ! ; 58try { 59Object . defineProperty ( process , "platform" , { value :"win32" }); 60computerUse ({ 61on ( name :string , handler :unknown ) { 62if ( name === "context" ) context = handler as typeof context ; 63if ( name === "agent_end" ) agentEnd = handler as typeof agentEnd ; 64}, 65registerTool ( definition :ToolDefinition < typeof parameters , { screenshotIds :string [] }>) { tool = definition ; }, 66} as ExtensionAPI ); 67} finally { 68Object . defineProperty ( process , "platform" , platform ); 69} 70assert . ok ( tool ); 71assert . ok ( context ); 72assert . ok ( agentEnd ); 73const branch :SessionEntry [] = []; 74return { 75 tool, 76 context, 77 agentEnd, 78 branch, 79processes :() => processes , 80records :async () => ( await readFile ( log , "utf8" )). trim (). split ( "\n" ). map (( line ) => JSON . parse ( line )), 81execute ( actions :Action [], signal ?:AbortSignal ) { 82return tool ! . execute ( "call-1" , { actions}, signal , undefined , { 83sessionManager :{ getBranch :() => branch }, 84} as ExtensionToolContext ); 85}, 86}; 87} 88 89test ( "Windows actions share one helper and preserve drag state and Unicode payloads" , async ( t ) => { 90const f = await fixture ( t ); 91const actions :Action [] = [ 92{ action :"pointer" , x :100 , y :200 , buttons :[ "left" ] }, 93{ action :"pointer" , x :700 , y :400 , buttons :[ "left" ] }, 94{ action :"scroll" , x :700 , y :400 , direction :"right" , ticks :3 , buttons :[ "left" ] }, 95{ action :"pointer" , x :700 , y :400 , buttons :[] }, 96{ action :"type" , text :'é中😀\n\t"\\' }, 97{ action :"key" , key :"a" , modifiers :[ "ctrl" ] }, 98{ action :"new_browser_tab" , url :"https://example.org/" }, 99{ action :"screenshot" }, 100]; 101const result = await f . execute ( actions ); 102assert . equal ( f . processes (), 1 ); 103assert . deepEqual ( await f . records (), [ ...actions , "released" ]); 104assert . deepEqual ( result . content . filter (( part ) => part . type === "image" ), [ 105{ type :"image" , data :png . toString ( "base64" ), mimeType :"image/png" }, 106]); 107}); 108 109test ( "screenshot visibility and recall remain shared across backends" , async ( t ) => { 110const f = await fixture ( t ); 111const result = await f . execute ([{ action :"screenshot" }]); 112const { screenshotIds :[ id ] } = result . details as { screenshotIds :string [] }; 113const message = { 114role :"toolResult" as const , toolName :"computer_use" , toolCallId :"call-1" , 115content :result . content , details :result . details , isError :false , timestamp :Date . now (), 116}; 117f . branch . push ({ type :"message" , id :"entry-1" , parentId :null , timestamp :new Date (). toISOString (), message}); 118const event :ContextEvent = { type :"context" , messages :[ message ] }; 119assert . deepEqual ( f . context ( event ). messages , [ message ]); 120f . agentEnd (); 121assert . deepEqual ( f . context ( event ). messages , [{ 122 ...message , content :message . content . filter (( part ) => part . type !== "image" ), 123}]); 124const recalled = await f . execute ([{ action :"recall" , id}]); 125assert . deepEqual ( recalled . content . filter (( part ) => part . type === "image" ), result . content . filter (( part ) => part . type === "image" )); 126assert . equal ( f . processes (), 1 ); 127assert . deepEqual ( f . context ( event ). messages , [ message ]); 128}); 129 130test ( "native errors still close the helper and release inputs" , async ( t ) => { 131const f = await fixture ( t ); 132await assert . rejects ( f . execute ([{ action :"key" , key :"Unknown" }]), / Unsupported key name / ); 133assert . equal (( await f . records ()). at ( - 1 ), "released" ); 134}); 135 136test ( "cancellation closes stdin gracefully instead of leaving held inputs" , async ( t ) => { 137const f = await fixture ( t ); 138const controller = new AbortController (); 139const result = f . execute ([{ action :"key" , key :"Wait" }], controller . signal ); 140controller . abort (); 141await assert . rejects ( result , / Operation aborted / ); 142assert . equal (( await f . records ()). at ( - 1 ), "released" ); 143}); 144 145test ( "helper startup diagnostics are surfaced" , async ( t ) => { 146const f = await fixture ( t , true ); 147await assert . rejects ( f . execute ([{ action :"screenshot" }]), / Desktop initialization failed / ); 148}); 149 150test ( "unsafe browser URLs fail before starting a desktop helper" , async ( t ) => { 151const f = await fixture ( t ); 152await assert . rejects ( f . execute ([{ action :"new_browser_tab" , url :"file:///C:/Windows" }]), / Only HTTP and HTTPS / ); 153assert . equal ( f . processes (), 0 ); 154}); 155 156test ( "native Windows screen capture returns a 1920×1080 PNG" , { 157skip :process . platform !== "win32" || process . env . PI_TEST_WINDOWS_DESKTOP !== "1" , 158}, async () => { 159const desktop = createWindowsBackend (); 160try { 161const image = await desktop . execute ({ action :"screenshot" }); 162assert . ok ( image ); 163assert . equal ( image . subarray ( 1 , 4 ). toString (), "PNG" ); 164assert . equal ( image . readUInt32BE ( 16 ), 1920 ); 165assert . equal ( image . readUInt32BE ( 20 ), 1080 ); 166} finally { 167await desktop . close (); 168} 169});