@@ -14,20 +14,65 @@ const required = (name) => {
1414 return value ;
1515} ;
1616
17- const runGit = ( args , options = { } ) => {
18- execFileSync ( "git" , args , {
17+ const runCommandOutput = ( command , args , options = { } ) =>
18+ execFileSync ( command , args , {
1919 stdio : "pipe" ,
2020 encoding : "utf8" ,
2121 ...options ,
2222 } ) ;
23- } ;
2423
25- const runGitOutput = ( args , options = { } ) =>
24+ const runGit = ( args , options = { } ) => {
2625 execFileSync ( "git" , args , {
2726 stdio : "pipe" ,
2827 encoding : "utf8" ,
2928 ...options ,
3029 } ) ;
30+ } ;
31+
32+ const runGitOutput = ( args , options = { } ) => runCommandOutput ( "git" , args , options ) ;
33+
34+ const resolveGitHubToken = ( ) => {
35+ const envToken = process . env . GITHUB_TOKEN ?. trim ( ) || process . env . GH_TOKEN ?. trim ( ) ;
36+ if ( envToken ) {
37+ return envToken ;
38+ }
39+
40+ try {
41+ const ghToken = runCommandOutput ( "gh" , [ "auth" , "token" ] ) . trim ( ) ;
42+ if ( ghToken ) {
43+ return ghToken ;
44+ }
45+ } catch {
46+ // Ignore and throw with clear message below.
47+ }
48+
49+ throw new Error ( "Missing GitHub token. Set GITHUB_TOKEN or GH_TOKEN, or run `gh auth login`." ) ;
50+ } ;
51+
52+ const resolveGitHubRepository = ( ) => {
53+ const repository = process . env . GITHUB_REPOSITORY ?. trim ( ) ;
54+ if ( repository ) {
55+ return repository ;
56+ }
57+
58+ const remoteUrl = runGitOutput ( [ "remote" , "get-url" , "origin" ] ) . trim ( ) ;
59+ const normalizedUrl = remoteUrl . startsWith ( "git@github.com:" )
60+ ? remoteUrl . replace ( "git@github.com:" , "https://github.com/" )
61+ : remoteUrl ;
62+ const parsedUrl = new URL ( normalizedUrl ) ;
63+
64+ if ( parsedUrl . hostname !== "github.com" ) {
65+ throw new Error ( `Unsupported remote host for origin: ${ parsedUrl . hostname } ` ) ;
66+ }
67+
68+ const pathname = parsedUrl . pathname . replace ( / ^ \/ + / , "" ) . replace ( / \. g i t $ / , "" ) ;
69+ const [ owner , repoName ] = pathname . split ( "/" ) ;
70+ if ( ! owner || ! repoName ) {
71+ throw new Error ( `Failed to infer GITHUB_REPOSITORY from origin remote: ${ remoteUrl } ` ) ;
72+ }
73+
74+ return `${ owner } /${ repoName } ` ;
75+ } ;
3176
3277const escapeMarkdown = ( value ) => String ( value ?? "" ) . replace ( / ` / g, "\\`" ) ;
3378
@@ -63,8 +108,7 @@ const getPreviewUrl = (branchName) => {
63108 return template . replaceAll ( "{branch}" , branchName . replaceAll ( "/" , "-" ) ) ;
64109} ;
65110
66- const githubRequest = async ( method , pathName , body ) => {
67- const token = required ( "GITHUB_TOKEN" ) ;
111+ const githubRequest = async ( token , method , pathName , body ) => {
68112 const response = await fetch ( `https://api.github.com${ pathName } ` , {
69113 method,
70114 headers : {
@@ -84,8 +128,7 @@ const githubRequest = async (method, pathName, body) => {
84128 return response . json ( ) ;
85129} ;
86130
87- const findOpenPrByHead = async ( owner , repo , branchName ) => {
88- const token = required ( "GITHUB_TOKEN" ) ;
131+ const findOpenPrByHead = async ( token , owner , repo , branchName ) => {
89132 const response = await fetch (
90133 `https://api.github.com/repos/${ owner } /${ repo } /pulls?state=open&head=${ owner } :${ encodeURIComponent ( branchName ) } ` ,
91134 {
@@ -149,7 +192,7 @@ const sendDiscordNotification = async ({ taskId, prUrl, previewUrl, instruction
149192 }
150193} ;
151194
152- const requestPatchFromAiEndpoint = async ( { taskId, task, branchName } ) => {
195+ const requestPatchFromAiEndpoint = async ( { taskId, task, branchName, repository , baseBranch } ) => {
153196 const endpoint = process . env . AI_INSPECTOR_PATCH_ENDPOINT ;
154197 if ( ! endpoint ) {
155198 return {
@@ -168,9 +211,9 @@ const requestPatchFromAiEndpoint = async ({ taskId, task, branchName }) => {
168211 body : JSON . stringify ( {
169212 taskId,
170213 task,
171- repository : process . env . GITHUB_REPOSITORY ,
214+ repository,
172215 branchName,
173- baseBranch : process . env . AI_INSPECTOR_BASE_BRANCH || "main" ,
216+ baseBranch,
174217 } ) ,
175218 } ) ;
176219
@@ -215,11 +258,16 @@ const claimQueuedTask = async (db, collectionName) => {
215258 const queued = await db
216259 . collection ( collectionName )
217260 . where ( "status" , "==" , "queued" )
218- . orderBy ( "createdAt" , "asc" )
219261 . limit ( 10 )
220262 . get ( ) ;
221263
222- for ( const doc of queued . docs ) {
264+ const orderedDocs = [ ...queued . docs ] . sort ( ( a , b ) => {
265+ const aMillis = a . get ( "createdAt" ) ?. toMillis ?. ( ) ?? Number . MAX_SAFE_INTEGER ;
266+ const bMillis = b . get ( "createdAt" ) ?. toMillis ?. ( ) ?? Number . MAX_SAFE_INTEGER ;
267+ return aMillis - bMillis ;
268+ } ) ;
269+
270+ for ( const doc of orderedDocs ) {
223271 const taskRef = doc . ref ;
224272 const claimedTask = await db . runTransaction ( async ( transaction ) => {
225273 const snapshot = await transaction . get ( taskRef ) ;
@@ -253,7 +301,8 @@ const claimQueuedTask = async (db, collectionName) => {
253301} ;
254302
255303const main = async ( ) => {
256- const repo = required ( "GITHUB_REPOSITORY" ) ;
304+ const githubToken = resolveGitHubToken ( ) ;
305+ const repo = resolveGitHubRepository ( ) ;
257306 const [ owner , repoName ] = repo . split ( "/" ) ;
258307 const baseBranch = process . env . AI_INSPECTOR_BASE_BRANCH || "main" ;
259308 const collectionName = process . env . AI_INSPECTOR_FIRESTORE_COLLECTION || "aiInspectorTasks" ;
@@ -292,7 +341,13 @@ const main = async () => {
292341 fs . writeFileSync ( filePath , buildTaskMarkdown ( taskId , task ) , "utf8" ) ;
293342 runGit ( [ "add" , filePath ] ) ;
294343
295- const aiResult = await requestPatchFromAiEndpoint ( { taskId, task, branchName } ) ;
344+ const aiResult = await requestPatchFromAiEndpoint ( {
345+ taskId,
346+ task,
347+ branchName,
348+ repository : repo ,
349+ baseBranch,
350+ } ) ;
296351 const patchApplied = applyPatch ( aiResult . patch ) ;
297352
298353 const hasChanges = runGitOutput ( [ "status" , "--porcelain" ] ) . trim ( ) . length > 0 ;
@@ -306,7 +361,7 @@ const main = async () => {
306361 runGit ( [ "commit" , "-m" , commitMessage ] ) ;
307362 runGit ( [ "push" , "-u" , "origin" , branchName ] ) ;
308363
309- const existingPr = await findOpenPrByHead ( owner , repoName , branchName ) ;
364+ const existingPr = await findOpenPrByHead ( githubToken , owner , repoName , branchName ) ;
310365 const title =
311366 aiResult . title || `[AI Inspector] ${ String ( task . instruction ?? "UI update request" ) . slice ( 0 , 72 ) } ` . trim ( ) ;
312367 const body = [
@@ -324,7 +379,7 @@ const main = async () => {
324379
325380 const pr =
326381 existingPr ??
327- ( await githubRequest ( "POST" , `/repos/${ owner } /${ repoName } /pulls` , {
382+ ( await githubRequest ( githubToken , "POST" , `/repos/${ owner } /${ repoName } /pulls` , {
328383 title,
329384 head : branchName ,
330385 base : baseBranch ,
0 commit comments