diff --git a/.github/workflows/fro-bot.yaml b/.github/workflows/fro-bot.yaml index 7529459d..4ce74244 100644 --- a/.github/workflows/fro-bot.yaml +++ b/.github/workflows/fro-bot.yaml @@ -123,3 +123,13 @@ jobs: model: ${{ vars.FRO_BOT_MODEL }} omo-providers: ${{ secrets.OMO_PROVIDERS }} prompt: ${{ env.PROMPT }} + + - if: always() + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + with: + name: opencode-logs-${{ github.run_id }}-${{ github.run_attempt }} + path: ~/.local/share/opencode/log + retention-days: 7 + compression-level: 9 + include-hidden-files: true + if-no-files-found: warn diff --git a/dist/main.js b/dist/main.js index d5f29311..d17fcb6f 100644 --- a/dist/main.js +++ b/dist/main.js @@ -1,7 +1,7 @@ -import{A as e,B as t,C as n,D as r,E as i,F as a,G as o,H as s,I as c,J as l,K as u,L as d,N as f,O as p,P as m,R as h,S as g,T as _,U as v,V as y,W as b,X as x,Y as S,Z as C,_ as w,a as T,b as E,c as D,d as O,f as k,g as ee,h as te,i as ne,j as A,k as j,l as re,m as ie,n as ae,o as oe,p as se,q as ce,r as le,s as ue,t as de,u as fe,v as pe,w as me,x as he,y as ge,z as M}from"./cache-DhWIcDW7.js";import N from"node:process";import*as _e from"os";import{EOL as ve}from"os";import*as ye from"crypto";import*as P from"fs";import{existsSync as be,readFileSync as xe}from"fs";import*as F from"path";import{ok as Se}from"assert";import*as Ce from"util";import{Buffer as we}from"node:buffer";import*as Te from"node:crypto";import{pathToFileURL as Ee}from"node:url";import*as I from"node:fs/promises";import De from"node:fs/promises";import*as L from"node:path";import R,{join as Oe}from"node:path";import{spawn as ke}from"node:child_process";import*as Ae from"node:os";import je,{homedir as Me}from"node:os";import*as Ne from"stream";function Pe(e){switch(e){case`hit`:return`βœ… hit`;case`miss`:return`πŸ†• miss`;case`corrupted`:return`⚠️ corrupted (clean start)`}}function Fe(e){let t=Math.round(e/1e3);return t<60?`${t}s`:`${Math.floor(t/60)}m ${t%60}s`}async function Ie(e,t){let{eventType:n,repo:i,ref:a,runId:s,runUrl:c,metrics:l,agent:u}=e;try{if(o.addHeading(`Fro Bot Agent Run`,2).addTable([[{data:`Field`,header:!0},{data:`Value`,header:!0}],[`Event`,n],[`Repository`,i],[`Ref`,a],[`Run ID`,`[${s}](${c})`],[`Agent`,u],[`Cache Status`,Pe(l.cacheStatus)],[`Duration`,l.duration==null?`N/A`:Fe(l.duration)]]),(l.sessionsUsed.length>0||l.sessionsCreated.length>0)&&(o.addHeading(`Sessions`,3),l.sessionsUsed.length>0&&o.addRaw(`**Used:** ${l.sessionsUsed.join(`, `)}\n`),l.sessionsCreated.length>0&&o.addRaw(`**Created:** ${l.sessionsCreated.join(`, `)}\n`)),l.tokenUsage!=null&&(o.addHeading(`Token Usage`,3),o.addTable([[{data:`Metric`,header:!0},{data:`Count`,header:!0}],[`Input`,l.tokenUsage.input.toLocaleString()],[`Output`,l.tokenUsage.output.toLocaleString()],[`Reasoning`,l.tokenUsage.reasoning.toLocaleString()],[`Cache Read`,l.tokenUsage.cache.read.toLocaleString()],[`Cache Write`,l.tokenUsage.cache.write.toLocaleString()]]),l.model!=null&&o.addRaw(`**Model:** ${l.model}\n`),l.cost!=null&&o.addRaw(`**Cost:** $${l.cost.toFixed(4)}\n`)),(l.prsCreated.length>0||l.commitsCreated.length>0||l.commentsPosted>0)&&(o.addHeading(`Created Artifacts`,3),l.prsCreated.length>0&&o.addList([...l.prsCreated]),l.commitsCreated.length>0&&o.addList(l.commitsCreated.map(e=>`Commit \`${e.slice(0,7)}\``)),l.commentsPosted>0&&o.addRaw(`**Comments Posted:** ${l.commentsPosted}\n`)),l.errors.length>0){o.addHeading(`Errors`,3);for(let e of l.errors){let t=e.recoverable?`πŸ”„ Recovered`:`❌ Failed`;o.addRaw(`- **${e.type}** (${t}): ${e.message}\n`)}}await o.write(),t.debug(`Wrote job summary`)}catch(e){let n=r(e);t.warning(`Failed to write job summary`,{error:n}),h(`Failed to write job summary: ${n}`)}}function Le(){let e=0,t=null,n=`miss`,r=[],i=[],a=[],o=[],s=0,c=null,l=null,u=null,d=[];return{start(){e=Date.now()},end(){t=Date.now()},setCacheStatus(e){n=e},addSessionUsed(e){r.includes(e)||r.push(e)},addSessionCreated(e){i.includes(e)||i.push(e)},addPRCreated(e){a.includes(e)||a.push(e)},addCommitCreated(e){o.includes(e)||o.push(e)},incrementComments(){s++},setTokenUsage(e,t,n){c=e,l=t,u=n},recordError(e,t,n){d.push({timestamp:new Date().toISOString(),type:e,message:t,recoverable:n})},getMetrics(){let f=t==null?Date.now()-e:t-e;return Object.freeze({startTime:e,endTime:t,duration:f,cacheStatus:n,sessionsUsed:Object.freeze([...r]),sessionsCreated:Object.freeze([...i]),prsCreated:Object.freeze([...a]),commitsCreated:Object.freeze([...o]),commentsPosted:s,tokenUsage:c,model:l,cost:u,errors:Object.freeze([...d])})}}}function Re(e){d(`session-id`,e.sessionId??``),d(`cache-status`,e.cacheStatus),d(`duration`,e.duration)}function z(e){let[t,n]=e.split(`/`);if(t==null||n==null||t.length===0||n.length===0)throw Error(`Invalid repository string: ${e}`);return{owner:t,repo:n}}async function ze(e,t,n,i,a){try{let{owner:r,repo:o}=z(t),{data:s}=await e.rest.reactions.createForIssueComment({owner:r,repo:o,comment_id:n,content:i});return a.debug(`Created comment reaction`,{commentId:n,content:i,reactionId:s.id}),{id:s.id}}catch(e){return a.warning(`Failed to create comment reaction`,{commentId:n,content:i,error:r(e)}),null}}async function Be(e,t,n,i){try{let{owner:r,repo:i}=z(t),{data:a}=await e.rest.reactions.listForIssueComment({owner:r,repo:i,comment_id:n,per_page:100});return a.map(e=>({id:e.id,content:e.content,userLogin:e.user?.login??null}))}catch(e){return i.warning(`Failed to list comment reactions`,{commentId:n,error:r(e)}),[]}}async function Ve(e,t,n,i,a){try{let{owner:r,repo:o}=z(t);return await e.rest.reactions.deleteForIssueComment({owner:r,repo:o,comment_id:n,reaction_id:i}),a.debug(`Deleted comment reaction`,{commentId:n,reactionId:i}),!0}catch(e){return a.warning(`Failed to delete comment reaction`,{commentId:n,reactionId:i,error:r(e)}),!1}}async function He(e,t,n,i,a,o){let{owner:s,repo:c}=z(t);try{return await e.rest.issues.createLabel({owner:s,repo:c,name:n,color:i,description:a}),o.debug(`Created label`,{name:n,color:i}),!0}catch(e){return e instanceof Error&&`status`in e&&e.status===422?(o.debug(`Label already exists`,{name:n}),!0):(o.warning(`Failed to create label`,{name:n,error:r(e)}),!1)}}async function Ue(e,t,n,i,a){try{let{owner:r,repo:o}=z(t);return await e.rest.issues.addLabels({owner:r,repo:o,issue_number:n,labels:[...i]}),a.debug(`Added labels to issue`,{issueNumber:n,labels:i}),!0}catch(e){return a.warning(`Failed to add labels to issue`,{issueNumber:n,labels:i,error:r(e)}),!1}}async function We(e,t,n,i,a){try{let{owner:r,repo:o}=z(t);return await e.rest.issues.removeLabel({owner:r,repo:o,issue_number:n,name:i}),a.debug(`Removed label from issue`,{issueNumber:n,label:i}),!0}catch(e){return e instanceof Error&&`status`in e&&e.status===404?(a.debug(`Label was not present on issue`,{issueNumber:n,label:i}),!0):(a.warning(`Failed to remove label from issue`,{issueNumber:n,label:i,error:r(e)}),!1)}}async function Ge(e,t,n){try{let{owner:n,repo:r}=z(t),{data:i}=await e.rest.repos.get({owner:n,repo:r});return i.default_branch}catch(e){return n.warning(`Failed to get default branch`,{repo:t,error:r(e)}),`main`}}const Ke={admin:`OWNER`,maintain:`MEMBER`,write:`COLLABORATOR`,triage:`COLLABORATOR`};async function qe(e,t,n,i,a){try{let{data:r}=await e.rest.repos.getCollaboratorPermissionLevel({owner:t,repo:n,username:i}),o=Ke[r.permission]??null;return a.debug(`Resolved sender permission`,{username:i,permission:r.permission,association:o}),o}catch(e){return a.warning(`Failed to resolve sender permission`,{username:i,error:r(e)}),null}}async function Je(e,t,n){try{let{data:n}=await e.rest.users.getByUsername({username:t});return{id:n.id,login:n.login}}catch(e){return n.debug(`Failed to get user by username`,{username:t,error:r(e)}),null}}const Ye={maxComments:50,maxCommits:100,maxFiles:100,maxReviews:100,maxBodyBytes:10*1024,maxTotalBytes:100*1024},Xe=`…[truncated]`;function Ze(e,t){if(e.length===0)return{text:``,truncated:!1};let n=new TextEncoder,r=n.encode(e);if(r.length<=t)return{text:e,truncated:!1};let i=t-n.encode(Xe).length;if(i<=0)return{text:Xe,truncated:!0};let a=r.slice(0,i),o=new TextDecoder(`utf-8`,{fatal:!1}).decode(a);for(;o.length>0&&o.charCodeAt(o.length-1)===65533;)a=a.slice(0,-1),o=new TextDecoder(`utf-8`,{fatal:!1}).decode(a);return{text:o+Xe,truncated:!0}}function Qe(e){return e.length===0?``:`**Labels:** ${e.map(e=>`\`${e.name}\``).join(`, `)}\n`}function $e(e){return e.length===0?``:`**Assignees:** ${e.map(e=>`@${e.login}`).join(`, `)}\n`}function et(e){let t=[];t.push(`## Issue #${e.number}`),t.push(``),t.push(`**Title:** ${e.title}`),t.push(`**State:** ${e.state}`),t.push(`**Author:** ${e.author??`unknown`}`),t.push(`**Created:** ${e.createdAt}`);let n=Qe(e.labels);n.length>0&&t.push(n.trimEnd());let r=$e(e.assignees);if(r.length>0&&t.push(r.trimEnd()),t.push(``),t.push(`### Body`),t.push(``),t.push(e.body),e.bodyTruncated&&(t.push(``),t.push(`*Note: Body was truncated due to size limits.*`)),e.comments.length>0){t.push(``),t.push(`### Comments (${e.comments.length}${e.commentsTruncated?` of ${e.totalComments}`:``})`),e.commentsTruncated&&(t.push(``),t.push(`*Note: Comments were truncated due to limits.*`)),t.push(``);for(let n of e.comments)t.push(`**${n.author??`unknown`}** (${n.createdAt}):`),t.push(n.body),t.push(``)}return t.join(` -`)}function tt(e){let t=[];t.push(`## Pull Request #${e.number}`),t.push(``),t.push(`**Title:** ${e.title}`),t.push(`**State:** ${e.state}`),t.push(`**Author:** ${e.author??`unknown`}`),t.push(`**Created:** ${e.createdAt}`),t.push(`**Base:** ${e.baseBranch} ← **Head:** ${e.headBranch}`),e.isFork&&t.push(`**Fork:** Yes (external contributor)`);let n=Qe(e.labels);n.length>0&&t.push(n.trimEnd());let r=$e(e.assignees);if(r.length>0&&t.push(r.trimEnd()),t.push(``),t.push(`### Description`),t.push(``),t.push(e.body),e.bodyTruncated&&(t.push(``),t.push(`*Note: Description was truncated due to size limits.*`)),e.files.length>0){t.push(``),t.push(`### Files Changed (${e.files.length}${e.filesTruncated?` of ${e.totalFiles}`:``})`),t.push(``),t.push(`| File | +/- |`),t.push(`|------|-----|`);for(let n of e.files)t.push(`| \`${n.path}\` | +${n.additions}/-${n.deletions} |`)}if(e.commits.length>0){t.push(``),t.push(`### Commits (${e.commits.length}${e.commitsTruncated?` of ${e.totalCommits}`:``})`),t.push(``);for(let n of e.commits){let e=n.oid.slice(0,7);t.push(`- \`${e}\` ${n.message.split(` +import{A as e,B as t,C as n,D as r,E as i,F as a,G as o,H as s,I as c,J as l,K as u,L as d,N as f,O as p,P as m,R as h,S as g,T as _,U as v,V as y,W as b,X as x,Y as S,Z as C,_ as w,a as T,b as E,c as D,d as O,f as k,g as ee,h as te,i as ne,j as A,k as j,l as re,m as ie,n as ae,o as oe,p as se,q as ce,r as le,s as ue,t as de,u as fe,v as pe,w as me,x as he,y as ge,z as M}from"./cache-DhWIcDW7.js";import N from"node:process";import*as _e from"os";import{EOL as ve}from"os";import*as ye from"crypto";import*as P from"fs";import{existsSync as be,readFileSync as xe}from"fs";import*as F from"path";import{ok as Se}from"assert";import*as Ce from"util";import{Buffer as we}from"node:buffer";import*as Te from"node:crypto";import{createHash as Ee}from"node:crypto";import{pathToFileURL as De}from"node:url";import*as I from"node:fs/promises";import Oe from"node:fs/promises";import*as L from"node:path";import ke,{join as Ae}from"node:path";import{spawn as je}from"node:child_process";import*as Me from"node:os";import Ne,{homedir as Pe}from"node:os";import*as Fe from"stream";function Ie(e){switch(e){case`hit`:return`βœ… hit`;case`miss`:return`πŸ†• miss`;case`corrupted`:return`⚠️ corrupted (clean start)`}}function Le(e){let t=Math.round(e/1e3);return t<60?`${t}s`:`${Math.floor(t/60)}m ${t%60}s`}async function Re(e,t){let{eventType:n,repo:i,ref:a,runId:s,runUrl:c,metrics:l,agent:u}=e;try{if(o.addHeading(`Fro Bot Agent Run`,2).addTable([[{data:`Field`,header:!0},{data:`Value`,header:!0}],[`Event`,n],[`Repository`,i],[`Ref`,a],[`Run ID`,`[${s}](${c})`],[`Agent`,u],[`Cache Status`,Ie(l.cacheStatus)],[`Duration`,l.duration==null?`N/A`:Le(l.duration)]]),(l.sessionsUsed.length>0||l.sessionsCreated.length>0)&&(o.addHeading(`Sessions`,3),l.sessionsUsed.length>0&&o.addRaw(`**Used:** ${l.sessionsUsed.join(`, `)}\n`),l.sessionsCreated.length>0&&o.addRaw(`**Created:** ${l.sessionsCreated.join(`, `)}\n`)),l.tokenUsage!=null&&(o.addHeading(`Token Usage`,3),o.addTable([[{data:`Metric`,header:!0},{data:`Count`,header:!0}],[`Input`,l.tokenUsage.input.toLocaleString()],[`Output`,l.tokenUsage.output.toLocaleString()],[`Reasoning`,l.tokenUsage.reasoning.toLocaleString()],[`Cache Read`,l.tokenUsage.cache.read.toLocaleString()],[`Cache Write`,l.tokenUsage.cache.write.toLocaleString()]]),l.model!=null&&o.addRaw(`**Model:** ${l.model}\n`),l.cost!=null&&o.addRaw(`**Cost:** $${l.cost.toFixed(4)}\n`)),(l.prsCreated.length>0||l.commitsCreated.length>0||l.commentsPosted>0)&&(o.addHeading(`Created Artifacts`,3),l.prsCreated.length>0&&o.addList([...l.prsCreated]),l.commitsCreated.length>0&&o.addList(l.commitsCreated.map(e=>`Commit \`${e.slice(0,7)}\``)),l.commentsPosted>0&&o.addRaw(`**Comments Posted:** ${l.commentsPosted}\n`)),l.errors.length>0){o.addHeading(`Errors`,3);for(let e of l.errors){let t=e.recoverable?`πŸ”„ Recovered`:`❌ Failed`;o.addRaw(`- **${e.type}** (${t}): ${e.message}\n`)}}await o.write(),t.debug(`Wrote job summary`)}catch(e){let n=r(e);t.warning(`Failed to write job summary`,{error:n}),h(`Failed to write job summary: ${n}`)}}function ze(){let e=0,t=null,n=`miss`,r=[],i=[],a=[],o=[],s=0,c=null,l=null,u=null,d=[];return{start(){e=Date.now()},end(){t=Date.now()},setCacheStatus(e){n=e},addSessionUsed(e){r.includes(e)||r.push(e)},addSessionCreated(e){i.includes(e)||i.push(e)},addPRCreated(e){a.includes(e)||a.push(e)},addCommitCreated(e){o.includes(e)||o.push(e)},incrementComments(){s++},setTokenUsage(e,t,n){c=e,l=t,u=n},recordError(e,t,n){d.push({timestamp:new Date().toISOString(),type:e,message:t,recoverable:n})},getMetrics(){let f=t==null?Date.now()-e:t-e;return Object.freeze({startTime:e,endTime:t,duration:f,cacheStatus:n,sessionsUsed:Object.freeze([...r]),sessionsCreated:Object.freeze([...i]),prsCreated:Object.freeze([...a]),commitsCreated:Object.freeze([...o]),commentsPosted:s,tokenUsage:c,model:l,cost:u,errors:Object.freeze([...d])})}}}function Be(e){d(`session-id`,e.sessionId??``),d(`cache-status`,e.cacheStatus),d(`duration`,e.duration)}function R(e){let[t,n]=e.split(`/`);if(t==null||n==null||t.length===0||n.length===0)throw Error(`Invalid repository string: ${e}`);return{owner:t,repo:n}}async function Ve(e,t,n,i,a){try{let{owner:r,repo:o}=R(t),{data:s}=await e.rest.reactions.createForIssueComment({owner:r,repo:o,comment_id:n,content:i});return a.debug(`Created comment reaction`,{commentId:n,content:i,reactionId:s.id}),{id:s.id}}catch(e){return a.warning(`Failed to create comment reaction`,{commentId:n,content:i,error:r(e)}),null}}async function He(e,t,n,i){try{let{owner:r,repo:i}=R(t),{data:a}=await e.rest.reactions.listForIssueComment({owner:r,repo:i,comment_id:n,per_page:100});return a.map(e=>({id:e.id,content:e.content,userLogin:e.user?.login??null}))}catch(e){return i.warning(`Failed to list comment reactions`,{commentId:n,error:r(e)}),[]}}async function Ue(e,t,n,i,a){try{let{owner:r,repo:o}=R(t);return await e.rest.reactions.deleteForIssueComment({owner:r,repo:o,comment_id:n,reaction_id:i}),a.debug(`Deleted comment reaction`,{commentId:n,reactionId:i}),!0}catch(e){return a.warning(`Failed to delete comment reaction`,{commentId:n,reactionId:i,error:r(e)}),!1}}async function We(e,t,n,i,a,o){let{owner:s,repo:c}=R(t);try{return await e.rest.issues.createLabel({owner:s,repo:c,name:n,color:i,description:a}),o.debug(`Created label`,{name:n,color:i}),!0}catch(e){return e instanceof Error&&`status`in e&&e.status===422?(o.debug(`Label already exists`,{name:n}),!0):(o.warning(`Failed to create label`,{name:n,error:r(e)}),!1)}}async function Ge(e,t,n,i,a){try{let{owner:r,repo:o}=R(t);return await e.rest.issues.addLabels({owner:r,repo:o,issue_number:n,labels:[...i]}),a.debug(`Added labels to issue`,{issueNumber:n,labels:i}),!0}catch(e){return a.warning(`Failed to add labels to issue`,{issueNumber:n,labels:i,error:r(e)}),!1}}async function Ke(e,t,n,i,a){try{let{owner:r,repo:o}=R(t);return await e.rest.issues.removeLabel({owner:r,repo:o,issue_number:n,name:i}),a.debug(`Removed label from issue`,{issueNumber:n,label:i}),!0}catch(e){return e instanceof Error&&`status`in e&&e.status===404?(a.debug(`Label was not present on issue`,{issueNumber:n,label:i}),!0):(a.warning(`Failed to remove label from issue`,{issueNumber:n,label:i,error:r(e)}),!1)}}async function qe(e,t,n){try{let{owner:n,repo:r}=R(t),{data:i}=await e.rest.repos.get({owner:n,repo:r});return i.default_branch}catch(e){return n.warning(`Failed to get default branch`,{repo:t,error:r(e)}),`main`}}const Je={admin:`OWNER`,maintain:`MEMBER`,write:`COLLABORATOR`,triage:`COLLABORATOR`};async function Ye(e,t,n,i,a){try{let{data:r}=await e.rest.repos.getCollaboratorPermissionLevel({owner:t,repo:n,username:i}),o=Je[r.permission]??null;return a.debug(`Resolved sender permission`,{username:i,permission:r.permission,association:o}),o}catch(e){return a.warning(`Failed to resolve sender permission`,{username:i,error:r(e)}),null}}async function Xe(e,t,n){try{let{data:n}=await e.rest.users.getByUsername({username:t});return{id:n.id,login:n.login}}catch(e){return n.debug(`Failed to get user by username`,{username:t,error:r(e)}),null}}const Ze={maxComments:50,maxCommits:100,maxFiles:100,maxReviews:100,maxBodyBytes:10*1024,maxTotalBytes:100*1024},Qe=`…[truncated]`;function $e(e,t){if(e.length===0)return{text:``,truncated:!1};let n=new TextEncoder,r=n.encode(e);if(r.length<=t)return{text:e,truncated:!1};let i=t-n.encode(Qe).length;if(i<=0)return{text:Qe,truncated:!0};let a=r.slice(0,i),o=new TextDecoder(`utf-8`,{fatal:!1}).decode(a);for(;o.length>0&&o.charCodeAt(o.length-1)===65533;)a=a.slice(0,-1),o=new TextDecoder(`utf-8`,{fatal:!1}).decode(a);return{text:o+Qe,truncated:!0}}function et(e){return e.length===0?``:`**Labels:** ${e.map(e=>`\`${e.name}\``).join(`, `)}\n`}function tt(e){return e.length===0?``:`**Assignees:** ${e.map(e=>`@${e.login}`).join(`, `)}\n`}function nt(e){let t=[];t.push(`## Issue #${e.number}`),t.push(``),t.push(`**Title:** ${e.title}`),t.push(`**State:** ${e.state}`),t.push(`**Author:** ${e.author??`unknown`}`),t.push(`**Created:** ${e.createdAt}`);let n=et(e.labels);n.length>0&&t.push(n.trimEnd());let r=tt(e.assignees);if(r.length>0&&t.push(r.trimEnd()),t.push(``),t.push(`### Body`),t.push(``),t.push(e.body),e.bodyTruncated&&(t.push(``),t.push(`*Note: Body was truncated due to size limits.*`)),e.comments.length>0){t.push(``),t.push(`### Comments (${e.comments.length}${e.commentsTruncated?` of ${e.totalComments}`:``})`),e.commentsTruncated&&(t.push(``),t.push(`*Note: Comments were truncated due to limits.*`)),t.push(``);for(let n of e.comments)t.push(`**${n.author??`unknown`}** (${n.createdAt}):`),t.push(n.body),t.push(``)}return t.join(` +`)}function rt(e){let t=[];t.push(`## Pull Request #${e.number}`),t.push(``),t.push(`**Title:** ${e.title}`),t.push(`**State:** ${e.state}`),t.push(`**Author:** ${e.author??`unknown`}`),t.push(`**Created:** ${e.createdAt}`),t.push(`**Base:** ${e.baseBranch} ← **Head:** ${e.headBranch}`),e.isFork&&t.push(`**Fork:** Yes (external contributor)`);let n=et(e.labels);n.length>0&&t.push(n.trimEnd());let r=tt(e.assignees);if(r.length>0&&t.push(r.trimEnd()),t.push(``),t.push(`### Description`),t.push(``),t.push(e.body),e.bodyTruncated&&(t.push(``),t.push(`*Note: Description was truncated due to size limits.*`)),e.files.length>0){t.push(``),t.push(`### Files Changed (${e.files.length}${e.filesTruncated?` of ${e.totalFiles}`:``})`),t.push(``),t.push(`| File | +/- |`),t.push(`|------|-----|`);for(let n of e.files)t.push(`| \`${n.path}\` | +${n.additions}/-${n.deletions} |`)}if(e.commits.length>0){t.push(``),t.push(`### Commits (${e.commits.length}${e.commitsTruncated?` of ${e.totalCommits}`:``})`),t.push(``);for(let n of e.commits){let e=n.oid.slice(0,7);t.push(`- \`${e}\` ${n.message.split(` `)[0]}`)}}if(e.reviews.length>0){t.push(``),t.push(`### Reviews (${e.reviews.length}${e.reviewsTruncated?` of ${e.totalReviews}`:``})`),t.push(``);for(let n of e.reviews)t.push(`**${n.author??`unknown`}** - ${n.state}`),n.body.length>0&&t.push(n.body),t.push(``)}if(e.comments.length>0){t.push(``),t.push(`### Comments (${e.comments.length}${e.commentsTruncated?` of ${e.totalComments}`:``})`),t.push(``);for(let n of e.comments)t.push(`**${n.author??`unknown`}** (${n.createdAt}):`),t.push(n.body),t.push(``)}return t.join(` -`)}function nt(e){return e.type===`issue`?et(e):tt(e)}async function rt(e,t,n,i,a,o){try{let[r,o]=await Promise.all([e.rest.issues.get({owner:t,repo:n,issue_number:i}),e.rest.issues.listComments({owner:t,repo:n,issue_number:i,per_page:a.maxComments})]),s=r.data,c=Ze(s.body??``,a.maxBodyBytes),l=o.data.slice(0,a.maxComments).map(e=>({id:e.node_id??String(e.id),author:e.user?.login??null,body:e.body??``,createdAt:e.created_at,authorAssociation:e.author_association,isMinimized:!1})),u=(s.labels??[]).filter(e=>typeof e==`object`&&!!e&&`name`in e).map(e=>({name:e.name??``,color:e.color})),d=(s.assignees??[]).map(e=>({login:e?.login??``}));return{type:`issue`,number:s.number,title:s.title,body:c.text,bodyTruncated:c.truncated,state:s.state,author:s.user?.login??null,createdAt:s.created_at,labels:u,assignees:d,comments:l,commentsTruncated:o.data.length>=a.maxComments,totalComments:o.data.length}}catch(e){return o.warning(`REST issue fallback failed`,{owner:t,repo:n,number:i,error:r(e)}),null}}async function it(e,t,n,i,a,o){try{let[s,c,l,u,d]=await Promise.all([e.rest.pulls.get({owner:t,repo:n,pull_number:i}),e.rest.pulls.listCommits({owner:t,repo:n,pull_number:i,per_page:a.maxCommits}),e.rest.pulls.listFiles({owner:t,repo:n,pull_number:i,per_page:a.maxFiles}),e.rest.pulls.listReviews({owner:t,repo:n,pull_number:i,per_page:a.maxReviews}),e.rest.issues.listComments({owner:t,repo:n,issue_number:i,per_page:a.maxComments})]),f=await e.rest.pulls.listRequestedReviewers({owner:t,repo:n,pull_number:i}).catch(e=>(o.warning(`Failed to fetch requested reviewers, defaulting to empty`,{owner:t,repo:n,number:i,error:r(e)}),{data:{users:[],teams:[]}})),p=s.data,m=Ze(p.body??``,a.maxBodyBytes),h=p.base.repo?.owner.login,g=p.head.repo?.owner.login,_=g==null||h!==g,v=d.data.slice(0,a.maxComments).map(e=>({id:e.node_id??String(e.id),author:e.user?.login??null,body:e.body??``,createdAt:e.created_at,authorAssociation:e.author_association,isMinimized:!1})),y=c.data.slice(0,a.maxCommits).map(e=>({oid:e.sha,message:e.commit.message,author:e.commit.author?.name??null})),b=l.data.slice(0,a.maxFiles).map(e=>({path:e.filename,additions:e.additions,deletions:e.deletions,status:e.status})),x=u.data.slice(0,a.maxReviews).map(e=>({author:e.user?.login??null,state:e.state,body:e.body??``,createdAt:e.submitted_at??``,comments:[]})),S=(p.labels??[]).map(e=>({name:e.name??``,color:e.color})),C=(p.assignees??[]).map(e=>({login:e?.login??``})),w=(f.data.users??[]).map(e=>e.login),T=(f.data.teams??[]).map(e=>e.name);return{type:`pull_request`,number:p.number,title:p.title,body:m.text,bodyTruncated:m.truncated,state:p.state,author:p.user?.login??null,createdAt:p.created_at,baseBranch:p.base.ref,headBranch:p.head.ref,isFork:_,labels:S,assignees:C,comments:v,commentsTruncated:d.data.length>=a.maxComments,totalComments:d.data.length,commits:y,commitsTruncated:c.data.length>=a.maxCommits,totalCommits:c.data.length,files:b,filesTruncated:l.data.length>=a.maxFiles,totalFiles:l.data.length,reviews:x,reviewsTruncated:u.data.length>=a.maxReviews,totalReviews:u.data.length,authorAssociation:p.author_association,requestedReviewers:w,requestedReviewerTeams:T}}catch(e){return o.warning(`REST pull request fallback failed`,{owner:t,repo:n,number:i,error:r(e)}),null}}async function at(e,t,n,i,a,o){try{return await e.graphql(` +`)}function it(e){return e.type===`issue`?nt(e):rt(e)}async function at(e,t,n,i,a,o){try{let[r,o]=await Promise.all([e.rest.issues.get({owner:t,repo:n,issue_number:i}),e.rest.issues.listComments({owner:t,repo:n,issue_number:i,per_page:a.maxComments})]),s=r.data,c=$e(s.body??``,a.maxBodyBytes),l=o.data.slice(0,a.maxComments).map(e=>({id:e.node_id??String(e.id),author:e.user?.login??null,body:e.body??``,createdAt:e.created_at,authorAssociation:e.author_association,isMinimized:!1})),u=(s.labels??[]).filter(e=>typeof e==`object`&&!!e&&`name`in e).map(e=>({name:e.name??``,color:e.color})),d=(s.assignees??[]).map(e=>({login:e?.login??``}));return{type:`issue`,number:s.number,title:s.title,body:c.text,bodyTruncated:c.truncated,state:s.state,author:s.user?.login??null,createdAt:s.created_at,labels:u,assignees:d,comments:l,commentsTruncated:o.data.length>=a.maxComments,totalComments:o.data.length}}catch(e){return o.warning(`REST issue fallback failed`,{owner:t,repo:n,number:i,error:r(e)}),null}}async function ot(e,t,n,i,a,o){try{let[s,c,l,u,d]=await Promise.all([e.rest.pulls.get({owner:t,repo:n,pull_number:i}),e.rest.pulls.listCommits({owner:t,repo:n,pull_number:i,per_page:a.maxCommits}),e.rest.pulls.listFiles({owner:t,repo:n,pull_number:i,per_page:a.maxFiles}),e.rest.pulls.listReviews({owner:t,repo:n,pull_number:i,per_page:a.maxReviews}),e.rest.issues.listComments({owner:t,repo:n,issue_number:i,per_page:a.maxComments})]),f=await e.rest.pulls.listRequestedReviewers({owner:t,repo:n,pull_number:i}).catch(e=>(o.warning(`Failed to fetch requested reviewers, defaulting to empty`,{owner:t,repo:n,number:i,error:r(e)}),{data:{users:[],teams:[]}})),p=s.data,m=$e(p.body??``,a.maxBodyBytes),h=p.base.repo?.owner.login,g=p.head.repo?.owner.login,_=g==null||h!==g,v=d.data.slice(0,a.maxComments).map(e=>({id:e.node_id??String(e.id),author:e.user?.login??null,body:e.body??``,createdAt:e.created_at,authorAssociation:e.author_association,isMinimized:!1})),y=c.data.slice(0,a.maxCommits).map(e=>({oid:e.sha,message:e.commit.message,author:e.commit.author?.name??null})),b=l.data.slice(0,a.maxFiles).map(e=>({path:e.filename,additions:e.additions,deletions:e.deletions,status:e.status})),x=u.data.slice(0,a.maxReviews).map(e=>({author:e.user?.login??null,state:e.state,body:e.body??``,createdAt:e.submitted_at??``,comments:[]})),S=(p.labels??[]).map(e=>({name:e.name??``,color:e.color})),C=(p.assignees??[]).map(e=>({login:e?.login??``})),w=(f.data.users??[]).map(e=>e.login),T=(f.data.teams??[]).map(e=>e.name);return{type:`pull_request`,number:p.number,title:p.title,body:m.text,bodyTruncated:m.truncated,state:p.state,author:p.user?.login??null,createdAt:p.created_at,baseBranch:p.base.ref,headBranch:p.head.ref,isFork:_,labels:S,assignees:C,comments:v,commentsTruncated:d.data.length>=a.maxComments,totalComments:d.data.length,commits:y,commitsTruncated:c.data.length>=a.maxCommits,totalCommits:c.data.length,files:b,filesTruncated:l.data.length>=a.maxFiles,totalFiles:l.data.length,reviews:x,reviewsTruncated:u.data.length>=a.maxReviews,totalReviews:u.data.length,authorAssociation:p.author_association,requestedReviewers:w,requestedReviewerTeams:T}}catch(e){return o.warning(`REST pull request fallback failed`,{owner:t,repo:n,number:i,error:r(e)}),null}}async function st(e,t,n,i,a,o){try{return await e.graphql(` query GetIssue($owner: String!, $repo: String!, $number: Int!, $maxComments: Int!) { repository(owner: $owner, name: $repo) { issue(number: $number) { @@ -31,7 +31,7 @@ import{A as e,B as t,C as n,D as r,E as i,F as a,G as o,H as s,I as c,J as l,K a } } } -`,{owner:t,repo:n,number:i,maxComments:a})}catch(e){return o.warning(`GraphQL issue query failed`,{owner:t,repo:n,number:i,error:r(e)}),null}}async function ot(e,t,n,i,a,o,s,c,l){try{return await e.graphql(` +`,{owner:t,repo:n,number:i,maxComments:a})}catch(e){return o.warning(`GraphQL issue query failed`,{owner:t,repo:n,number:i,error:r(e)}),null}}async function ct(e,t,n,i,a,o,s,c,l){try{return await e.graphql(` query GetPullRequest( $owner: String!, $repo: String!, @@ -119,35 +119,35 @@ import{A as e,B as t,C as n,D as r,E as i,F as a,G as o,H as s,I as c,J as l,K a } } } -`,{owner:t,repo:n,number:i,maxComments:a,maxCommits:o,maxFiles:s,maxReviews:c})}catch(e){return l.warning(`GraphQL pull request query failed`,{owner:t,repo:n,number:i,error:r(e)}),null}}async function st(e,t,n,r,i,a){let o=await at(e,t,n,r,i.maxComments,a);if(o==null)return null;let s=o.repository.issue;if(s==null)return a.debug(`Issue not found`,{owner:t,repo:n,number:r}),null;let c=Ze(s.body??``,i.maxBodyBytes),l=s.comments.nodes.slice(0,i.maxComments),u=s.comments.totalCount>l.length,d=l.map(e=>({id:e.id,author:e.author?.login??null,body:e.body,createdAt:e.createdAt,authorAssociation:e.authorAssociation,isMinimized:e.isMinimized})),f=s.labels.nodes.map(e=>({name:e.name,color:e.color})),p=s.assignees.nodes.map(e=>({login:e.login}));return{type:`issue`,number:s.number,title:s.title,body:c.text,bodyTruncated:c.truncated,state:s.state,author:s.author?.login??null,createdAt:s.createdAt,labels:f,assignees:p,comments:d,commentsTruncated:u,totalComments:s.comments.totalCount}}async function ct(e,t,n,r,i,a){let o=await ot(e,t,n,r,i.maxComments,i.maxCommits,i.maxFiles,i.maxReviews,a);if(o==null)return null;let s=o.repository.pullRequest;if(s==null)return a.debug(`Pull request not found`,{owner:t,repo:n,number:r}),null;let c=Ze(s.body??``,i.maxBodyBytes),l=s.baseRepository?.owner.login,u=s.headRepository?.owner.login,d=u==null||l!==u,f=s.comments.nodes.slice(0,i.maxComments),p=s.comments.totalCount>f.length,m=f.map(e=>({id:e.id,author:e.author?.login??null,body:e.body,createdAt:e.createdAt,authorAssociation:e.authorAssociation,isMinimized:e.isMinimized})),h=s.commits.nodes.slice(0,i.maxCommits),g=s.commits.totalCount>h.length,_=h.map(e=>({oid:e.commit.oid,message:e.commit.message,author:e.commit.author?.name??null})),v=s.files.nodes.slice(0,i.maxFiles),y=s.files.totalCount>v.length,b=v.map(e=>({path:e.path,additions:e.additions,deletions:e.deletions})),x=s.reviews.nodes.slice(0,i.maxReviews),S=s.reviews.totalCount>x.length,C=x.map(e=>({author:e.author?.login??null,state:e.state,body:e.body,createdAt:e.createdAt,comments:e.comments.nodes.map(e=>({id:e.id,author:e.author?.login??null,body:e.body,path:e.path,line:e.line,createdAt:e.createdAt}))})),w=s.labels.nodes.map(e=>({name:e.name,color:e.color})),T=s.assignees.nodes.map(e=>({login:e.login})),E=s.reviewRequests.nodes.map(e=>`login`in e.requestedReviewer?e.requestedReviewer.login:null).filter(e=>e!=null),D=s.reviewRequests.nodes.map(e=>`name`in e.requestedReviewer?e.requestedReviewer.name:null).filter(e=>e!=null);return{type:`pull_request`,number:s.number,title:s.title,body:c.text,bodyTruncated:c.truncated,state:s.state,author:s.author?.login??null,createdAt:s.createdAt,baseBranch:s.baseRefName,headBranch:s.headRefName,isFork:d,labels:w,assignees:T,comments:m,commentsTruncated:p,totalComments:s.comments.totalCount,commits:_,commitsTruncated:g,totalCommits:s.commits.totalCount,files:b,filesTruncated:y,totalFiles:s.files.totalCount,reviews:C,reviewsTruncated:S,totalReviews:s.reviews.totalCount,authorAssociation:s.authorAssociation,requestedReviewers:E,requestedReviewerTeams:D}}const B={PER_PAGE:100,MAX_PAGES:50};async function lt(e,t,n,r,i){i.debug(`Fetching PR diff`,{prNumber:r});let a=[],o=1,s=!1;for(;o<=B.MAX_PAGES;){let{data:c}=await e.rest.pulls.listFiles({owner:t,repo:n,pull_number:r,per_page:B.PER_PAGE,page:o}),l=c.map(e=>({filename:e.filename,status:e.status,additions:e.additions,deletions:e.deletions,patch:e.patch??null,previousFilename:e.previous_filename??null}));if(a.push(...l),c.lengthB.MAX_PAGES&&(s=!0,i.warning(`PR diff pagination limit reached`,{filesLoaded:a.length,maxPages:B.MAX_PAGES}))}let c=a.reduce((e,t)=>({additions:e.additions+t.additions,deletions:e.deletions+t.deletions}),{additions:0,deletions:0});return i.debug(`Fetched diff`,{files:a.length,additions:c.additions,deletions:c.deletions,truncated:s}),{files:a,additions:c.additions,deletions:c.deletions,changedFiles:a.length,truncated:s}}async function ut(e,t,n,i){if(e.eventType!==`pull_request`)return null;let a=e.target?.number;if(a==null)return i.debug(`No PR number in trigger context, skipping diff collection`),null;let[o,s]=n.split(`/`);if(o==null||s==null)return i.warning(`Invalid repo format, skipping diff collection`,{repo:n}),null;try{let e=await lt(t,o,s,a,i),n={changedFiles:e.changedFiles,additions:e.additions,deletions:e.deletions,truncated:e.truncated,files:e.files.slice(0,50).map(e=>({filename:e.filename,status:e.status,additions:e.additions,deletions:e.deletions}))};return i.debug(`Collected diff context`,{files:n.changedFiles,additions:n.additions,deletions:n.deletions,truncated:n.truncated}),n}catch(e){return i.warning(`Failed to fetch PR diff`,{error:r(e)}),null}}async function dt(e){let{logger:t,octokit:n,triggerContext:r,botLogin:i}=e,{repo:a,ref:o,actor:s,runId:c,target:l,author:u,commentBody:d,commentId:f}=r,p=`${a.owner}/${a.repo}`,m=l?.kind===`issue`||l?.kind===`pr`?l.kind:null,h=l?.number??null,g=l?.title??null,_=u?.login??null,v=await ut(r,n,p,t),y=await ft(n,a.owner,a.repo,h,m,t),b=y?.type===`pull_request`?y:null,x=b?.authorAssociation??null,S=i!=null&&b!=null?b.requestedReviewers.includes(i):!1;return t.info(`Collected agent context`,{eventName:r.eventName,repo:p,issueNumber:h,issueType:m,hasComment:d!=null,hasDiffContext:v!=null,hasHydratedContext:y!=null}),{eventName:r.eventName,repo:p,ref:o,actor:s,runId:String(c),issueNumber:h,issueTitle:g,issueType:m,commentBody:d,commentAuthor:_,commentId:f,defaultBranch:await Ge(n,p,t),diffContext:v,hydratedContext:y,authorAssociation:x,isRequestedReviewer:S}}async function ft(e,t,n,r,i,a){if(r==null||i==null)return null;let o=Ye;return i===`issue`?await st(e,t,n,r,o,a)??rt(e,t,n,r,o,a):await ct(e,t,n,r,o,a)??it(e,t,n,r,o,a)}const pt=({onSseError:e,onSseEvent:t,responseTransformer:n,responseValidator:r,sseDefaultRetryDelay:i,sseMaxRetryAttempts:a,sseMaxRetryDelay:o,sseSleepFn:s,url:c,...l})=>{let u,d=s??(e=>new Promise(t=>setTimeout(t,e)));return{stream:async function*(){let s=i??3e3,f=0,p=l.signal??new AbortController().signal;for(;!p.aborted;){f++;let i=l.headers instanceof Headers?l.headers:new Headers(l.headers);u!==void 0&&i.set(`Last-Event-ID`,u);try{let e=await fetch(c,{...l,headers:i,signal:p});if(!e.ok)throw Error(`SSE failed: ${e.status} ${e.statusText}`);if(!e.body)throw Error(`No body in SSE response`);let a=e.body.pipeThrough(new TextDecoderStream).getReader(),o=``,d=()=>{try{a.cancel()}catch{}};p.addEventListener(`abort`,d);try{for(;;){let{done:e,value:i}=await a.read();if(e)break;o+=i;let c=o.split(` +`,{owner:t,repo:n,number:i,maxComments:a,maxCommits:o,maxFiles:s,maxReviews:c})}catch(e){return l.warning(`GraphQL pull request query failed`,{owner:t,repo:n,number:i,error:r(e)}),null}}async function lt(e,t,n,r,i,a){let o=await st(e,t,n,r,i.maxComments,a);if(o==null)return null;let s=o.repository.issue;if(s==null)return a.debug(`Issue not found`,{owner:t,repo:n,number:r}),null;let c=$e(s.body??``,i.maxBodyBytes),l=s.comments.nodes.slice(0,i.maxComments),u=s.comments.totalCount>l.length,d=l.map(e=>({id:e.id,author:e.author?.login??null,body:e.body,createdAt:e.createdAt,authorAssociation:e.authorAssociation,isMinimized:e.isMinimized})),f=s.labels.nodes.map(e=>({name:e.name,color:e.color})),p=s.assignees.nodes.map(e=>({login:e.login}));return{type:`issue`,number:s.number,title:s.title,body:c.text,bodyTruncated:c.truncated,state:s.state,author:s.author?.login??null,createdAt:s.createdAt,labels:f,assignees:p,comments:d,commentsTruncated:u,totalComments:s.comments.totalCount}}async function ut(e,t,n,r,i,a){let o=await ct(e,t,n,r,i.maxComments,i.maxCommits,i.maxFiles,i.maxReviews,a);if(o==null)return null;let s=o.repository.pullRequest;if(s==null)return a.debug(`Pull request not found`,{owner:t,repo:n,number:r}),null;let c=$e(s.body??``,i.maxBodyBytes),l=s.baseRepository?.owner.login,u=s.headRepository?.owner.login,d=u==null||l!==u,f=s.comments.nodes.slice(0,i.maxComments),p=s.comments.totalCount>f.length,m=f.map(e=>({id:e.id,author:e.author?.login??null,body:e.body,createdAt:e.createdAt,authorAssociation:e.authorAssociation,isMinimized:e.isMinimized})),h=s.commits.nodes.slice(0,i.maxCommits),g=s.commits.totalCount>h.length,_=h.map(e=>({oid:e.commit.oid,message:e.commit.message,author:e.commit.author?.name??null})),v=s.files.nodes.slice(0,i.maxFiles),y=s.files.totalCount>v.length,b=v.map(e=>({path:e.path,additions:e.additions,deletions:e.deletions})),x=s.reviews.nodes.slice(0,i.maxReviews),S=s.reviews.totalCount>x.length,C=x.map(e=>({author:e.author?.login??null,state:e.state,body:e.body,createdAt:e.createdAt,comments:e.comments.nodes.map(e=>({id:e.id,author:e.author?.login??null,body:e.body,path:e.path,line:e.line,createdAt:e.createdAt}))})),w=s.labels.nodes.map(e=>({name:e.name,color:e.color})),T=s.assignees.nodes.map(e=>({login:e.login})),E=s.reviewRequests.nodes.map(e=>`login`in e.requestedReviewer?e.requestedReviewer.login:null).filter(e=>e!=null),D=s.reviewRequests.nodes.map(e=>`name`in e.requestedReviewer?e.requestedReviewer.name:null).filter(e=>e!=null);return{type:`pull_request`,number:s.number,title:s.title,body:c.text,bodyTruncated:c.truncated,state:s.state,author:s.author?.login??null,createdAt:s.createdAt,baseBranch:s.baseRefName,headBranch:s.headRefName,isFork:d,labels:w,assignees:T,comments:m,commentsTruncated:p,totalComments:s.comments.totalCount,commits:_,commitsTruncated:g,totalCommits:s.commits.totalCount,files:b,filesTruncated:y,totalFiles:s.files.totalCount,reviews:C,reviewsTruncated:S,totalReviews:s.reviews.totalCount,authorAssociation:s.authorAssociation,requestedReviewers:E,requestedReviewerTeams:D}}const z={PER_PAGE:100,MAX_PAGES:50};async function dt(e,t,n,r,i){i.debug(`Fetching PR diff`,{prNumber:r});let a=[],o=1,s=!1;for(;o<=z.MAX_PAGES;){let{data:c}=await e.rest.pulls.listFiles({owner:t,repo:n,pull_number:r,per_page:z.PER_PAGE,page:o}),l=c.map(e=>({filename:e.filename,status:e.status,additions:e.additions,deletions:e.deletions,patch:e.patch??null,previousFilename:e.previous_filename??null}));if(a.push(...l),c.lengthz.MAX_PAGES&&(s=!0,i.warning(`PR diff pagination limit reached`,{filesLoaded:a.length,maxPages:z.MAX_PAGES}))}let c=a.reduce((e,t)=>({additions:e.additions+t.additions,deletions:e.deletions+t.deletions}),{additions:0,deletions:0});return i.debug(`Fetched diff`,{files:a.length,additions:c.additions,deletions:c.deletions,truncated:s}),{files:a,additions:c.additions,deletions:c.deletions,changedFiles:a.length,truncated:s}}async function ft(e,t,n,i){if(e.eventType!==`pull_request`)return null;let a=e.target?.number;if(a==null)return i.debug(`No PR number in trigger context, skipping diff collection`),null;let[o,s]=n.split(`/`);if(o==null||s==null)return i.warning(`Invalid repo format, skipping diff collection`,{repo:n}),null;try{let e=await dt(t,o,s,a,i),n={changedFiles:e.changedFiles,additions:e.additions,deletions:e.deletions,truncated:e.truncated,files:e.files.slice(0,50).map(e=>({filename:e.filename,status:e.status,additions:e.additions,deletions:e.deletions}))};return i.debug(`Collected diff context`,{files:n.changedFiles,additions:n.additions,deletions:n.deletions,truncated:n.truncated}),n}catch(e){return i.warning(`Failed to fetch PR diff`,{error:r(e)}),null}}async function pt(e){let{logger:t,octokit:n,triggerContext:r,botLogin:i}=e,{repo:a,ref:o,actor:s,runId:c,target:l,author:u,commentBody:d,commentId:f}=r,p=`${a.owner}/${a.repo}`,m=l?.kind===`issue`||l?.kind===`pr`?l.kind:null,h=l?.number??null,g=l?.title??null,_=u?.login??null,v=await ft(r,n,p,t),y=await mt(n,a.owner,a.repo,h,m,t),b=y?.type===`pull_request`?y:null,x=b?.authorAssociation??null,S=i!=null&&b!=null?b.requestedReviewers.includes(i):!1;return t.info(`Collected agent context`,{eventName:r.eventName,repo:p,issueNumber:h,issueType:m,hasComment:d!=null,hasDiffContext:v!=null,hasHydratedContext:y!=null}),{eventName:r.eventName,repo:p,ref:o,actor:s,runId:String(c),issueNumber:h,issueTitle:g,issueType:m,commentBody:d,commentAuthor:_,commentId:f,defaultBranch:await qe(n,p,t),diffContext:v,hydratedContext:y,authorAssociation:x,isRequestedReviewer:S}}async function mt(e,t,n,r,i,a){if(r==null||i==null)return null;let o=Ze;return i===`issue`?await lt(e,t,n,r,o,a)??at(e,t,n,r,o,a):await ut(e,t,n,r,o,a)??ot(e,t,n,r,o,a)}const ht=({onSseError:e,onSseEvent:t,responseTransformer:n,responseValidator:r,sseDefaultRetryDelay:i,sseMaxRetryAttempts:a,sseMaxRetryDelay:o,sseSleepFn:s,url:c,...l})=>{let u,d=s??(e=>new Promise(t=>setTimeout(t,e)));return{stream:async function*(){let s=i??3e3,f=0,p=l.signal??new AbortController().signal;for(;!p.aborted;){f++;let i=l.headers instanceof Headers?l.headers:new Headers(l.headers);u!==void 0&&i.set(`Last-Event-ID`,u);try{let e=await fetch(c,{...l,headers:i,signal:p});if(!e.ok)throw Error(`SSE failed: ${e.status} ${e.statusText}`);if(!e.body)throw Error(`No body in SSE response`);let a=e.body.pipeThrough(new TextDecoderStream).getReader(),o=``,d=()=>{try{a.cancel()}catch{}};p.addEventListener(`abort`,d);try{for(;;){let{done:e,value:i}=await a.read();if(e)break;o+=i;let c=o.split(` `);o=c.pop()??``;for(let e of c){let i=e.split(` `),a=[],o;for(let e of i)if(e.startsWith(`data:`))a.push(e.replace(/^data:\s*/,``));else if(e.startsWith(`event:`))o=e.replace(/^event:\s*/,``);else if(e.startsWith(`id:`))u=e.replace(/^id:\s*/,``);else if(e.startsWith(`retry:`)){let t=Number.parseInt(e.replace(/^retry:\s*/,``),10);Number.isNaN(t)||(s=t)}let c,l=!1;if(a.length){let e=a.join(` -`);try{c=JSON.parse(e),l=!0}catch{c=e}}l&&(r&&await r(c),n&&(c=await n(c))),t?.({data:c,event:o,id:u,retry:s}),a.length&&(yield c)}}}finally{p.removeEventListener(`abort`,d),a.releaseLock()}break}catch(t){if(e?.(t),a!==void 0&&f>=a)break;await d(Math.min(s*2**(f-1),o??3e4))}}}()}},mt=async(e,t)=>{let n=typeof t==`function`?await t(e):t;if(n)return e.scheme===`bearer`?`Bearer ${n}`:e.scheme===`basic`?`Basic ${btoa(n)}`:n},ht={bodySerializer:e=>JSON.stringify(e,(e,t)=>typeof t==`bigint`?t.toString():t)},gt=e=>{switch(e){case`label`:return`.`;case`matrix`:return`;`;case`simple`:return`,`;default:return`&`}},_t=e=>{switch(e){case`form`:return`,`;case`pipeDelimited`:return`|`;case`spaceDelimited`:return`%20`;default:return`,`}},vt=e=>{switch(e){case`label`:return`.`;case`matrix`:return`;`;case`simple`:return`,`;default:return`&`}},yt=({allowReserved:e,explode:t,name:n,style:r,value:i})=>{if(!t){let t=(e?i:i.map(e=>encodeURIComponent(e))).join(_t(r));switch(r){case`label`:return`.${t}`;case`matrix`:return`;${n}=${t}`;case`simple`:return t;default:return`${n}=${t}`}}let a=gt(r),o=i.map(t=>r===`label`||r===`simple`?e?t:encodeURIComponent(t):V({allowReserved:e,name:n,value:t})).join(a);return r===`label`||r===`matrix`?a+o:o},V=({allowReserved:e,name:t,value:n})=>{if(n==null)return``;if(typeof n==`object`)throw Error("Deeply-nested arrays/objects aren’t supported. Provide your own `querySerializer()` to handle these.");return`${t}=${e?n:encodeURIComponent(n)}`},bt=({allowReserved:e,explode:t,name:n,style:r,value:i,valueOnly:a})=>{if(i instanceof Date)return a?i.toISOString():`${n}=${i.toISOString()}`;if(r!==`deepObject`&&!t){let t=[];Object.entries(i).forEach(([n,r])=>{t=[...t,n,e?r:encodeURIComponent(r)]});let a=t.join(`,`);switch(r){case`form`:return`${n}=${a}`;case`label`:return`.${a}`;case`matrix`:return`;${n}=${a}`;default:return a}}let o=vt(r),s=Object.entries(i).map(([t,i])=>V({allowReserved:e,name:r===`deepObject`?`${n}[${t}]`:t,value:i})).join(o);return r===`label`||r===`matrix`?o+s:s},xt=/\{[^{}]+\}/g,St=({path:e,url:t})=>{let n=t,r=t.match(xt);if(r)for(let t of r){let r=!1,i=t.substring(1,t.length-1),a=`simple`;i.endsWith(`*`)&&(r=!0,i=i.substring(0,i.length-1)),i.startsWith(`.`)?(i=i.substring(1),a=`label`):i.startsWith(`;`)&&(i=i.substring(1),a=`matrix`);let o=e[i];if(o==null)continue;if(Array.isArray(o)){n=n.replace(t,yt({explode:r,name:i,style:a,value:o}));continue}if(typeof o==`object`){n=n.replace(t,bt({explode:r,name:i,style:a,value:o,valueOnly:!0}));continue}if(a===`matrix`){n=n.replace(t,`;${V({name:i,value:o})}`);continue}let s=encodeURIComponent(a===`label`?`.${o}`:o);n=n.replace(t,s)}return n},Ct=({baseUrl:e,path:t,query:n,querySerializer:r,url:i})=>{let a=i.startsWith(`/`)?i:`/${i}`,o=(e??``)+a;t&&(o=St({path:t,url:o}));let s=n?r(n):``;return s.startsWith(`?`)&&(s=s.substring(1)),s&&(o+=`?${s}`),o},wt=({allowReserved:e,array:t,object:n}={})=>r=>{let i=[];if(r&&typeof r==`object`)for(let a in r){let o=r[a];if(o!=null)if(Array.isArray(o)){let n=yt({allowReserved:e,explode:!0,name:a,style:`form`,value:o,...t});n&&i.push(n)}else if(typeof o==`object`){let t=bt({allowReserved:e,explode:!0,name:a,style:`deepObject`,value:o,...n});t&&i.push(t)}else{let t=V({allowReserved:e,name:a,value:o});t&&i.push(t)}}return i.join(`&`)},Tt=e=>{if(!e)return`stream`;let t=e.split(`;`)[0]?.trim();if(t){if(t.startsWith(`application/json`)||t.endsWith(`+json`))return`json`;if(t===`multipart/form-data`)return`formData`;if([`application/`,`audio/`,`image/`,`video/`].some(e=>t.startsWith(e)))return`blob`;if(t.startsWith(`text/`))return`text`}},Et=(e,t)=>t?!!(e.headers.has(t)||e.query?.[t]||e.headers.get(`Cookie`)?.includes(`${t}=`)):!1,Dt=async({security:e,...t})=>{for(let n of e){if(Et(t,n.name))continue;let e=await mt(n,t.auth);if(!e)continue;let r=n.name??`Authorization`;switch(n.in){case`query`:t.query||={},t.query[r]=e;break;case`cookie`:t.headers.append(`Cookie`,`${r}=${e}`);break;default:t.headers.set(r,e);break}}},Ot=e=>Ct({baseUrl:e.baseUrl,path:e.path,query:e.query,querySerializer:typeof e.querySerializer==`function`?e.querySerializer:wt(e.querySerializer),url:e.url}),kt=(e,t)=>{let n={...e,...t};return n.baseUrl?.endsWith(`/`)&&(n.baseUrl=n.baseUrl.substring(0,n.baseUrl.length-1)),n.headers=At(e.headers,t.headers),n},At=(...e)=>{let t=new Headers;for(let n of e){if(!n||typeof n!=`object`)continue;let e=n instanceof Headers?n.entries():Object.entries(n);for(let[n,r]of e)if(r===null)t.delete(n);else if(Array.isArray(r))for(let e of r)t.append(n,e);else r!==void 0&&t.set(n,typeof r==`object`?JSON.stringify(r):r)}return t};var jt=class{_fns;constructor(){this._fns=[]}clear(){this._fns=[]}getInterceptorIndex(e){return typeof e==`number`?this._fns[e]?e:-1:this._fns.indexOf(e)}exists(e){let t=this.getInterceptorIndex(e);return!!this._fns[t]}eject(e){let t=this.getInterceptorIndex(e);this._fns[t]&&(this._fns[t]=null)}update(e,t){let n=this.getInterceptorIndex(e);return this._fns[n]?(this._fns[n]=t,e):!1}use(e){return this._fns=[...this._fns,e],this._fns.length-1}};const Mt=()=>({error:new jt,request:new jt,response:new jt}),Nt=wt({allowReserved:!1,array:{explode:!0,style:`form`},object:{explode:!0,style:`deepObject`}}),Pt={"Content-Type":`application/json`},Ft=(e={})=>({...ht,headers:Pt,parseAs:`auto`,querySerializer:Nt,...e}),It=(e={})=>{let t=kt(Ft(),e),n=()=>({...t}),r=e=>(t=kt(t,e),n()),i=Mt(),a=async e=>{let n={...t,...e,fetch:e.fetch??t.fetch??globalThis.fetch,headers:At(t.headers,e.headers),serializedBody:void 0};return n.security&&await Dt({...n,security:n.security}),n.requestValidator&&await n.requestValidator(n),n.body&&n.bodySerializer&&(n.serializedBody=n.bodySerializer(n.body)),(n.serializedBody===void 0||n.serializedBody===``)&&n.headers.delete(`Content-Type`),{opts:n,url:Ot(n)}},o=async e=>{let{opts:t,url:n}=await a(e),r={redirect:`follow`,...t,body:t.serializedBody},o=new Request(n,r);for(let e of i.request._fns)e&&(o=await e(o,t));let s=t.fetch,c=await s(o);for(let e of i.response._fns)e&&(c=await e(c,o,t));let l={request:o,response:c};if(c.ok){if(c.status===204||c.headers.get(`Content-Length`)===`0`)return t.responseStyle===`data`?{}:{data:{},...l};let e=(t.parseAs===`auto`?Tt(c.headers.get(`Content-Type`)):t.parseAs)??`json`,n;switch(e){case`arrayBuffer`:case`blob`:case`formData`:case`json`:case`text`:n=await c[e]();break;case`stream`:return t.responseStyle===`data`?c.body:{data:c.body,...l}}return e===`json`&&(t.responseValidator&&await t.responseValidator(n),t.responseTransformer&&(n=await t.responseTransformer(n))),t.responseStyle===`data`?n:{data:n,...l}}let u=await c.text(),d;try{d=JSON.parse(u)}catch{}let f=d??u,p=f;for(let e of i.error._fns)e&&(p=await e(f,c,o,t));if(p||={},t.throwOnError)throw p;return t.responseStyle===`data`?void 0:{error:p,...l}},s=e=>{let t=t=>o({...t,method:e});return t.sse=async t=>{let{opts:n,url:r}=await a(t);return pt({...n,body:n.body,headers:n.headers,method:e,url:r})},t};return{buildUrl:Ot,connect:s(`CONNECT`),delete:s(`DELETE`),get:s(`GET`),getConfig:n,head:s(`HEAD`),interceptors:i,options:s(`OPTIONS`),patch:s(`PATCH`),post:s(`POST`),put:s(`PUT`),request:o,setConfig:r,trace:s(`TRACE`)}};Object.entries({$body_:`body`,$headers_:`headers`,$path_:`path`,$query_:`query`});const Lt=It(Ft({baseUrl:`http://localhost:4096`}));var H=class{_client=Lt;constructor(e){e?.client&&(this._client=e.client)}},Rt=class extends H{event(e){return(e?.client??this._client).get.sse({url:`/global/event`,...e})}},zt=class extends H{list(e){return(e?.client??this._client).get({url:`/project`,...e})}current(e){return(e?.client??this._client).get({url:`/project/current`,...e})}},Bt=class extends H{list(e){return(e?.client??this._client).get({url:`/pty`,...e})}create(e){return(e?.client??this._client).post({url:`/pty`,...e,headers:{"Content-Type":`application/json`,...e?.headers}})}remove(e){return(e.client??this._client).delete({url:`/pty/{id}`,...e})}get(e){return(e.client??this._client).get({url:`/pty/{id}`,...e})}update(e){return(e.client??this._client).put({url:`/pty/{id}`,...e,headers:{"Content-Type":`application/json`,...e.headers}})}connect(e){return(e.client??this._client).get({url:`/pty/{id}/connect`,...e})}},Vt=class extends H{get(e){return(e?.client??this._client).get({url:`/config`,...e})}update(e){return(e?.client??this._client).patch({url:`/config`,...e,headers:{"Content-Type":`application/json`,...e?.headers}})}providers(e){return(e?.client??this._client).get({url:`/config/providers`,...e})}},Ht=class extends H{ids(e){return(e?.client??this._client).get({url:`/experimental/tool/ids`,...e})}list(e){return(e.client??this._client).get({url:`/experimental/tool`,...e})}},Ut=class extends H{dispose(e){return(e?.client??this._client).post({url:`/instance/dispose`,...e})}},Wt=class extends H{get(e){return(e?.client??this._client).get({url:`/path`,...e})}},Gt=class extends H{get(e){return(e?.client??this._client).get({url:`/vcs`,...e})}},Kt=class extends H{list(e){return(e?.client??this._client).get({url:`/session`,...e})}create(e){return(e?.client??this._client).post({url:`/session`,...e,headers:{"Content-Type":`application/json`,...e?.headers}})}status(e){return(e?.client??this._client).get({url:`/session/status`,...e})}delete(e){return(e.client??this._client).delete({url:`/session/{id}`,...e})}get(e){return(e.client??this._client).get({url:`/session/{id}`,...e})}update(e){return(e.client??this._client).patch({url:`/session/{id}`,...e,headers:{"Content-Type":`application/json`,...e.headers}})}children(e){return(e.client??this._client).get({url:`/session/{id}/children`,...e})}todo(e){return(e.client??this._client).get({url:`/session/{id}/todo`,...e})}init(e){return(e.client??this._client).post({url:`/session/{id}/init`,...e,headers:{"Content-Type":`application/json`,...e.headers}})}fork(e){return(e.client??this._client).post({url:`/session/{id}/fork`,...e,headers:{"Content-Type":`application/json`,...e.headers}})}abort(e){return(e.client??this._client).post({url:`/session/{id}/abort`,...e})}unshare(e){return(e.client??this._client).delete({url:`/session/{id}/share`,...e})}share(e){return(e.client??this._client).post({url:`/session/{id}/share`,...e})}diff(e){return(e.client??this._client).get({url:`/session/{id}/diff`,...e})}summarize(e){return(e.client??this._client).post({url:`/session/{id}/summarize`,...e,headers:{"Content-Type":`application/json`,...e.headers}})}messages(e){return(e.client??this._client).get({url:`/session/{id}/message`,...e})}prompt(e){return(e.client??this._client).post({url:`/session/{id}/message`,...e,headers:{"Content-Type":`application/json`,...e.headers}})}message(e){return(e.client??this._client).get({url:`/session/{id}/message/{messageID}`,...e})}promptAsync(e){return(e.client??this._client).post({url:`/session/{id}/prompt_async`,...e,headers:{"Content-Type":`application/json`,...e.headers}})}command(e){return(e.client??this._client).post({url:`/session/{id}/command`,...e,headers:{"Content-Type":`application/json`,...e.headers}})}shell(e){return(e.client??this._client).post({url:`/session/{id}/shell`,...e,headers:{"Content-Type":`application/json`,...e.headers}})}revert(e){return(e.client??this._client).post({url:`/session/{id}/revert`,...e,headers:{"Content-Type":`application/json`,...e.headers}})}unrevert(e){return(e.client??this._client).post({url:`/session/{id}/unrevert`,...e})}},qt=class extends H{list(e){return(e?.client??this._client).get({url:`/command`,...e})}},Jt=class extends H{authorize(e){return(e.client??this._client).post({url:`/provider/{id}/oauth/authorize`,...e,headers:{"Content-Type":`application/json`,...e.headers}})}callback(e){return(e.client??this._client).post({url:`/provider/{id}/oauth/callback`,...e,headers:{"Content-Type":`application/json`,...e.headers}})}},Yt=class extends H{list(e){return(e?.client??this._client).get({url:`/provider`,...e})}auth(e){return(e?.client??this._client).get({url:`/provider/auth`,...e})}oauth=new Jt({client:this._client})},Xt=class extends H{text(e){return(e.client??this._client).get({url:`/find`,...e})}files(e){return(e.client??this._client).get({url:`/find/file`,...e})}symbols(e){return(e.client??this._client).get({url:`/find/symbol`,...e})}},Zt=class extends H{list(e){return(e.client??this._client).get({url:`/file`,...e})}read(e){return(e.client??this._client).get({url:`/file/content`,...e})}status(e){return(e?.client??this._client).get({url:`/file/status`,...e})}},Qt=class extends H{log(e){return(e?.client??this._client).post({url:`/log`,...e,headers:{"Content-Type":`application/json`,...e?.headers}})}agents(e){return(e?.client??this._client).get({url:`/agent`,...e})}},$t=class extends H{remove(e){return(e.client??this._client).delete({url:`/mcp/{name}/auth`,...e})}start(e){return(e.client??this._client).post({url:`/mcp/{name}/auth`,...e})}callback(e){return(e.client??this._client).post({url:`/mcp/{name}/auth/callback`,...e,headers:{"Content-Type":`application/json`,...e.headers}})}authenticate(e){return(e.client??this._client).post({url:`/mcp/{name}/auth/authenticate`,...e})}set(e){return(e.client??this._client).put({url:`/auth/{id}`,...e,headers:{"Content-Type":`application/json`,...e.headers}})}},en=class extends H{status(e){return(e?.client??this._client).get({url:`/mcp`,...e})}add(e){return(e?.client??this._client).post({url:`/mcp`,...e,headers:{"Content-Type":`application/json`,...e?.headers}})}connect(e){return(e.client??this._client).post({url:`/mcp/{name}/connect`,...e})}disconnect(e){return(e.client??this._client).post({url:`/mcp/{name}/disconnect`,...e})}auth=new $t({client:this._client})},tn=class extends H{status(e){return(e?.client??this._client).get({url:`/lsp`,...e})}},nn=class extends H{status(e){return(e?.client??this._client).get({url:`/formatter`,...e})}},rn=class extends H{next(e){return(e?.client??this._client).get({url:`/tui/control/next`,...e})}response(e){return(e?.client??this._client).post({url:`/tui/control/response`,...e,headers:{"Content-Type":`application/json`,...e?.headers}})}},an=class extends H{appendPrompt(e){return(e?.client??this._client).post({url:`/tui/append-prompt`,...e,headers:{"Content-Type":`application/json`,...e?.headers}})}openHelp(e){return(e?.client??this._client).post({url:`/tui/open-help`,...e})}openSessions(e){return(e?.client??this._client).post({url:`/tui/open-sessions`,...e})}openThemes(e){return(e?.client??this._client).post({url:`/tui/open-themes`,...e})}openModels(e){return(e?.client??this._client).post({url:`/tui/open-models`,...e})}submitPrompt(e){return(e?.client??this._client).post({url:`/tui/submit-prompt`,...e})}clearPrompt(e){return(e?.client??this._client).post({url:`/tui/clear-prompt`,...e})}executeCommand(e){return(e?.client??this._client).post({url:`/tui/execute-command`,...e,headers:{"Content-Type":`application/json`,...e?.headers}})}showToast(e){return(e?.client??this._client).post({url:`/tui/show-toast`,...e,headers:{"Content-Type":`application/json`,...e?.headers}})}publish(e){return(e?.client??this._client).post({url:`/tui/publish`,...e,headers:{"Content-Type":`application/json`,...e?.headers}})}control=new rn({client:this._client})},on=class extends H{subscribe(e){return(e?.client??this._client).get.sse({url:`/event`,...e})}},sn=class extends H{postSessionIdPermissionsPermissionId(e){return(e.client??this._client).post({url:`/session/{id}/permissions/{permissionID}`,...e,headers:{"Content-Type":`application/json`,...e.headers}})}global=new Rt({client:this._client});project=new zt({client:this._client});pty=new Bt({client:this._client});config=new Vt({client:this._client});tool=new Ht({client:this._client});instance=new Ut({client:this._client});path=new Wt({client:this._client});vcs=new Gt({client:this._client});session=new Kt({client:this._client});command=new qt({client:this._client});provider=new Yt({client:this._client});find=new Xt({client:this._client});file=new Zt({client:this._client});app=new Qt({client:this._client});mcp=new en({client:this._client});lsp=new tn({client:this._client});formatter=new nn({client:this._client});tui=new an({client:this._client});auth=new $t({client:this._client});event=new on({client:this._client})};function cn(e){if(!e?.fetch){let t=e=>(e.timeout=!1,fetch(e));e={...e,fetch:t}}return e?.directory&&(e.headers={...e.headers,"x-opencode-directory":encodeURIComponent(e.directory)}),new sn({client:It(e)})}async function ln(e){e=Object.assign({hostname:`127.0.0.1`,port:4096,timeout:5e3},e??{});let t=[`serve`,`--hostname=${e.hostname}`,`--port=${e.port}`];e.config?.logLevel&&t.push(`--log-level=${e.config.logLevel}`);let n=ke(`opencode`,t,{signal:e.signal,env:{...process.env,OPENCODE_CONFIG_CONTENT:JSON.stringify(e.config??{})}});return{url:await new Promise((t,r)=>{let i=setTimeout(()=>{r(Error(`Timeout waiting for server to start after ${e.timeout}ms`))},e.timeout),a=``;n.stdout?.on(`data`,e=>{a+=e.toString();let n=a.split(` -`);for(let e of n)if(e.startsWith(`opencode server listening`)){let n=e.match(/on\s+(https?:\/\/[^\s]+)/);if(!n)throw Error(`Failed to parse server url from output: ${e}`);clearTimeout(i),t(n[1]);return}}),n.stderr?.on(`data`,e=>{a+=e.toString()}),n.on(`exit`,e=>{clearTimeout(i);let t=`Server exited with code ${e}`;a.trim()&&(t+=`\nServer output: ${a}`),r(Error(t))}),n.on(`error`,e=>{clearTimeout(i),r(e)}),e.signal&&e.signal.addEventListener(`abort`,()=>{clearTimeout(i),r(Error(`Aborted`))})}),close(){n.kill()}}}async function un(e){let t=await ln({...e});return{client:cn({baseUrl:t.url}),server:t}}async function dn(e){if(e<0||!Number.isFinite(e))throw Error(`Invalid sleep duration: ${e}`);return new Promise(t=>setTimeout(t,e))}const fn={api_error:`API Error`,configuration:`Configuration Error`,internal:`Internal Error`,llm_fetch_error:`LLM Fetch Error`,llm_timeout:`LLM Timeout`,permission:`Permission Error`,rate_limit:`Rate Limit`,validation:`Validation Error`};function pn(e){return e.type===`rate_limit`?`:warning:`:e.type===`llm_timeout`?`:hourglass:`:e.type===`llm_fetch_error`||e.retryable?`:warning:`:`:x:`}function mn(e){let t=pn(e),n=fn[e.type],r=[];return r.push(`${t} **${n}**`),r.push(``),r.push(e.message),e.details!=null&&(r.push(``),r.push(`> ${e.details}`)),e.suggestedAction!=null&&(r.push(``),r.push(`**Suggested action:** ${e.suggestedAction}`)),e.retryable&&(r.push(``),r.push(`_This error is retryable._`)),e.resetTime!=null&&(r.push(``),r.push(`_Rate limit resets at: ${e.resetTime.toISOString()}_`)),r.join(` -`)}function hn(e,t,n,r){return{type:e,message:t,retryable:n,details:r?.details,suggestedAction:r?.suggestedAction,resetTime:r?.resetTime}}const gn=[/fetch failed/i,/connect\s*timeout/i,/connecttimeouterror/i,/timed?\s*out/i,/econnrefused/i,/econnreset/i,/etimedout/i,/network error/i];function _n(e){if(e==null)return!1;let t=``;if(typeof e==`string`)t=e;else if(e instanceof Error)t=e.message,`cause`in e&&typeof e.cause==`string`&&(t+=` ${e.cause}`);else if(typeof e==`object`){let n=e;typeof n.message==`string`&&(t=n.message),typeof n.cause==`string`&&(t+=` ${n.cause}`)}return gn.some(e=>e.test(t))}function vn(e,t){return hn(`llm_fetch_error`,`LLM request failed: ${e}`,!0,{details:t==null?void 0:`Model: ${t}`,suggestedAction:`This is a transient network error. The request may succeed on retry, or try a different model.`})}function yn(e,t){return hn(`configuration`,`Agent error: ${e}`,!1,{details:t==null?void 0:`Requested agent: ${t}`,suggestedAction:`Verify the agent name is correct and the required plugins (e.g., oMo) are installed.`})}async function bn(e,t,i,a,o,s=n,c){let l=Date.now(),u=0;for(;!a.aborted;){if(await dn(500),a.aborted)return{completed:!1,error:`Aborted`};if(c?.sessionError==null)u=0;else{if(u++,u>=3)return o.error(`Session error persisted through grace period`,{sessionId:t,error:c.sessionError,graceCycles:u}),{completed:!1,error:`Session error: ${c.sessionError}`};continue}if(c?.sessionIdle===!0)return o.debug(`Session idle detected via event stream`,{sessionId:t}),{completed:!0,error:null};let n=Date.now()-l;if(s>0&&n>=s)return o.warning(`Poll timeout reached`,{elapsedMs:n,maxPollTimeMs:s}),{completed:!1,error:`Poll timeout after ${n}ms`};try{let n=((await e.session.status({query:{directory:i}})).data??{})[t];if(n==null)o.debug(`Session status not found in poll response`,{sessionId:t});else if(n.type===`idle`)return o.debug(`Session idle detected via polling`,{sessionId:t}),{completed:!0,error:null};else o.debug(`Session status`,{sessionId:t,type:n.type});if(c!=null&&!c.firstMeaningfulEventReceived){let e=Date.now()-l;if(e>=9e4)return o.error(`No agent activity detected β€” server may have crashed during prompt processing`,{elapsedMs:e,sessionId:t}),{completed:!1,error:`No agent activity detected after ${e}ms β€” server may have crashed during prompt processing`}}}catch(e){o.debug(`Poll request failed`,{error:r(e)})}}return{completed:!1,error:`Aborted`}}async function xn(e,t=2e3){await Promise.race([e,new Promise(e=>{setTimeout(e,t)})])}function Sn(e){try{let t=new URL(e);return t.hostname===`github.com`||t.hostname===`api.github.com`}catch{return!1}}function Cn(e){try{let t=new URL(e);return t.hostname===`github.com`&&(t.pathname.startsWith(`/user-attachments/assets/`)||t.pathname.startsWith(`/user-attachments/files/`))}catch{return!1}}function wn(e){let t=e.match(/https:\/\/github\.com\/[a-zA-Z0-9-]+\/[\w.-]+\/(?:pull|issues)\/\d+(?:#issuecomment-\d+)?/g)??[];return[...new Set(t)].filter(Sn)}function Tn(e){let t=/\[[\w-]+\s+([a-f0-9]{7,40})\]/g,n=[];for(let r of e.matchAll(t))r[1]!=null&&n.push(r[1]);return[...new Set(n)]}const En={todowrite:[`Todo`,`\x1B[33m\x1B[1m`],todoread:[`Todo`,`\x1B[33m\x1B[1m`],bash:[`Bash`,`\x1B[31m\x1B[1m`],edit:[`Edit`,`\x1B[32m\x1B[1m`],glob:[`Glob`,`\x1B[34m\x1B[1m`],grep:[`Grep`,`\x1B[34m\x1B[1m`],list:[`List`,`\x1B[34m\x1B[1m`],read:[`Read`,`\x1B[35m\x1B[1m`],write:[`Write`,`\x1B[32m\x1B[1m`],websearch:[`Search`,`\x1B[2m\x1B[1m`]},Dn=`\x1B[0m`;function On(){return N.env.NO_COLOR==null}function kn(e,t){let[n,r]=En[e.toLowerCase()]??[e,`\x1B[36m\x1B[1m`],i=n.padEnd(10,` `);On()?N.stdout.write(`\n${r}|${Dn} ${i} ${Dn}${t}\n`):N.stdout.write(`\n| ${i} ${t}\n`)}function An(e){N.stdout.write(`\n${e}\n`)}function jn(e,t){t.debug(`Server event`,{eventType:e.type,properties:e.properties})}function Mn(e,t,n,r,i){let a=wn(t);if(e.includes(`gh pr create`)){let e=a.filter(e=>e.includes(`/pull/`)&&!e.includes(`#`));for(let t of e)n.includes(t)||n.push(t)}if(e.includes(`git commit`)){let e=Tn(t);for(let t of e)r.includes(t)||r.push(t)}(e.includes(`gh issue comment`)||e.includes(`gh pr comment`))&&a.some(e=>e.includes(`#issuecomment`))&&i()}async function Nn(e,t,n,r,i){let a=``,o=null,s=null,c=null,l=[],u=[],d=0,f=null;for await(let p of e){if(n.aborted)break;if(jn(p,r),p.type===`message.part.updated`){let e=p.properties.part;if(e.sessionID!==t)continue;if(i!=null&&(i.firstMeaningfulEventReceived=!0),e.type===`text`&&`text`in e&&typeof e.text==`string`){a=e.text;let t=`time`in e?e.time?.end:void 0;t!=null&&Number.isFinite(t)&&(An(a),a=``)}else if(e.type===`tool`){let t=e.state;t.status===`completed`&&(kn(e.tool,t.title),e.tool.toLowerCase()===`bash`&&Mn(String(t.input.command??t.input.cmd??``),String(t.output),l,u,()=>{d++}))}}else if(p.type===`message.updated`){let e=p.properties.info;e.sessionID===t&&e.role===`assistant`&&e.tokens!=null&&(i!=null&&(i.firstMeaningfulEventReceived=!0),o={input:e.tokens.input??0,output:e.tokens.output??0,reasoning:e.tokens.reasoning??0,cache:{read:e.tokens.cache?.read??0,write:e.tokens.cache?.write??0}},s=e.modelID??null,c=e.cost??null,r.debug(`Token usage received`,{tokens:o,model:s,cost:c}))}else if(p.type===`session.error`){if(p.properties.sessionID===t){let e=p.properties.error,t=typeof e==`string`?e:String(e);r.error(`Session error`,{error:e}),f=_n(e)?vn(t,s??void 0):yn(t),i!=null&&(i.sessionError=t)}}else p.type===`session.idle`&&p.properties.sessionID===t&&(i!=null&&(i.sessionIdle=!0),a.length>0&&(An(a),a=``))}return a.length>0&&An(a),{tokens:o,model:s,cost:c,prsCreated:l,commitsCreated:u,commentsPosted:d,llmError:f}}const Pn=5e3;async function Fn(e,t,n,r,i){let a=new AbortController,o={firstMeaningfulEventReceived:!1,sessionIdle:!1,sessionError:null},s=await e.event.subscribe(),c={tokens:null,model:null,cost:null,prsCreated:[],commitsCreated:[],commentsPosted:0,llmError:null},l=Nn(s.stream,t,a.signal,i,o).then(e=>{c=e}).catch(e=>{e instanceof Error&&e.name!==`AbortError`&&i.debug(`Event stream error`,{error:e.message})}),u=async()=>{a.abort(),await xn(l)};try{let s=await bn(e,t,n,a.signal,i,r,o);if(await u(),!s.completed){let e=s.error??`Session did not reach idle state`;return i.error(`Session completion polling failed`,{error:e,sessionId:t}),{success:!1,error:e,llmError:c.llmError,shouldRetry:c.llmError!=null,eventStreamResult:c}}return{success:!0,error:null,llmError:null,shouldRetry:!1,eventStreamResult:c}}finally{a.abort(),await xn(l)}}const In=e=>{if(e?.model!=null)return{providerID:e.model.providerID,modelID:e.model.modelID};if(!(e!=null&&Object.values(e.omoProviders).some(e=>e!==`no`)))return{providerID:E.providerID,modelID:E.modelID}};async function Ln(e,t,n,r,i,a,o){let s={parts:[{type:`text`,text:n},...r??[]]},c=In(a);c!=null&&(s.model=c);let l=a?.agent??`sisyphus`;l!==`sisyphus`&&(s.agent=l);let u=await e.session.promptAsync({path:{id:t},body:s,query:{directory:i}});if(u.error!=null){let e=String(u.error),t=_n(u.error)?vn(e):null;return{success:!1,error:e,llmError:t,shouldRetry:t!=null,eventStreamResult:{tokens:null,model:null,cost:null,prsCreated:[],commitsCreated:[],commentsPosted:0,llmError:t}}}return Fn(e,t,i,a?.timeoutMs??18e5,o)}function Rn(e,t){switch(e.eventType){case`issue_comment`:return{directive:`Respond to the comment above. Post your response as a single comment on this thread.`,appendMode:!0};case`discussion_comment`:return{directive:`Respond to the discussion comment above. Post your response as a single comment.`,appendMode:!0};case`issues`:return e.action===`opened`?{directive:`Triage this issue: summarize, reproduce if possible, propose next steps. Post your response as a single comment.`,appendMode:!0}:{directive:`Respond to the mention in this issue. Post your response as a single comment.`,appendMode:!0};case`pull_request`:return{directive:[`Review this pull request for code quality, potential bugs, and improvements.`,"If you are a requested reviewer, submit a review via `gh pr review` with your full response (including Run Summary) in the --body.",`Include the Run Summary in the review body. Do not post a separate comment.`,`If the author is a collaborator, prioritize actionable feedback over style nits.`].join(` -`),appendMode:!0};case`pull_request_review_comment`:return{directive:zn(e),appendMode:!0};case`schedule`:case`workflow_dispatch`:return{directive:t??``,appendMode:!1};default:return{directive:`Execute the requested operation.`,appendMode:!0}}}function zn(e){let t=e.target,n=[`Respond to the review comment.`,``];return t?.path!=null&&n.push(`**File:** \`${t.path}\``),t?.line!=null&&n.push(`**Line:** ${t.line}`),t?.commitId!=null&&n.push(`**Commit:** \`${t.commitId}\``),t?.diffHunk!=null&&t.diffHunk.length>0&&n.push(``,`**Diff Context:**`,"```diff",t.diffHunk,"```"),n.join(` -`)}function Bn(e,t){let{directive:n,appendMode:r}=Rn(e,t),i=[`## Task`,``];return r?(i.push(n),t!=null&&t.trim().length>0&&i.push(``,`**Additional Instructions:**`,t.trim())):i.push(n),i.push(``),i.join(` -`)}function Vn(e,t){let{context:n,customPrompt:r,cacheStatus:i,sessionContext:a}=e,o=[];if(o.push(`# Agent Context +`);try{c=JSON.parse(e),l=!0}catch{c=e}}l&&(r&&await r(c),n&&(c=await n(c))),t?.({data:c,event:o,id:u,retry:s}),a.length&&(yield c)}}}finally{p.removeEventListener(`abort`,d),a.releaseLock()}break}catch(t){if(e?.(t),a!==void 0&&f>=a)break;await d(Math.min(s*2**(f-1),o??3e4))}}}()}},gt=async(e,t)=>{let n=typeof t==`function`?await t(e):t;if(n)return e.scheme===`bearer`?`Bearer ${n}`:e.scheme===`basic`?`Basic ${btoa(n)}`:n},_t={bodySerializer:e=>JSON.stringify(e,(e,t)=>typeof t==`bigint`?t.toString():t)},vt=e=>{switch(e){case`label`:return`.`;case`matrix`:return`;`;case`simple`:return`,`;default:return`&`}},yt=e=>{switch(e){case`form`:return`,`;case`pipeDelimited`:return`|`;case`spaceDelimited`:return`%20`;default:return`,`}},bt=e=>{switch(e){case`label`:return`.`;case`matrix`:return`;`;case`simple`:return`,`;default:return`&`}},xt=({allowReserved:e,explode:t,name:n,style:r,value:i})=>{if(!t){let t=(e?i:i.map(e=>encodeURIComponent(e))).join(yt(r));switch(r){case`label`:return`.${t}`;case`matrix`:return`;${n}=${t}`;case`simple`:return t;default:return`${n}=${t}`}}let a=vt(r),o=i.map(t=>r===`label`||r===`simple`?e?t:encodeURIComponent(t):B({allowReserved:e,name:n,value:t})).join(a);return r===`label`||r===`matrix`?a+o:o},B=({allowReserved:e,name:t,value:n})=>{if(n==null)return``;if(typeof n==`object`)throw Error("Deeply-nested arrays/objects aren’t supported. Provide your own `querySerializer()` to handle these.");return`${t}=${e?n:encodeURIComponent(n)}`},St=({allowReserved:e,explode:t,name:n,style:r,value:i,valueOnly:a})=>{if(i instanceof Date)return a?i.toISOString():`${n}=${i.toISOString()}`;if(r!==`deepObject`&&!t){let t=[];Object.entries(i).forEach(([n,r])=>{t=[...t,n,e?r:encodeURIComponent(r)]});let a=t.join(`,`);switch(r){case`form`:return`${n}=${a}`;case`label`:return`.${a}`;case`matrix`:return`;${n}=${a}`;default:return a}}let o=bt(r),s=Object.entries(i).map(([t,i])=>B({allowReserved:e,name:r===`deepObject`?`${n}[${t}]`:t,value:i})).join(o);return r===`label`||r===`matrix`?o+s:s},Ct=/\{[^{}]+\}/g,wt=({path:e,url:t})=>{let n=t,r=t.match(Ct);if(r)for(let t of r){let r=!1,i=t.substring(1,t.length-1),a=`simple`;i.endsWith(`*`)&&(r=!0,i=i.substring(0,i.length-1)),i.startsWith(`.`)?(i=i.substring(1),a=`label`):i.startsWith(`;`)&&(i=i.substring(1),a=`matrix`);let o=e[i];if(o==null)continue;if(Array.isArray(o)){n=n.replace(t,xt({explode:r,name:i,style:a,value:o}));continue}if(typeof o==`object`){n=n.replace(t,St({explode:r,name:i,style:a,value:o,valueOnly:!0}));continue}if(a===`matrix`){n=n.replace(t,`;${B({name:i,value:o})}`);continue}let s=encodeURIComponent(a===`label`?`.${o}`:o);n=n.replace(t,s)}return n},Tt=({baseUrl:e,path:t,query:n,querySerializer:r,url:i})=>{let a=i.startsWith(`/`)?i:`/${i}`,o=(e??``)+a;t&&(o=wt({path:t,url:o}));let s=n?r(n):``;return s.startsWith(`?`)&&(s=s.substring(1)),s&&(o+=`?${s}`),o},Et=({allowReserved:e,array:t,object:n}={})=>r=>{let i=[];if(r&&typeof r==`object`)for(let a in r){let o=r[a];if(o!=null)if(Array.isArray(o)){let n=xt({allowReserved:e,explode:!0,name:a,style:`form`,value:o,...t});n&&i.push(n)}else if(typeof o==`object`){let t=St({allowReserved:e,explode:!0,name:a,style:`deepObject`,value:o,...n});t&&i.push(t)}else{let t=B({allowReserved:e,name:a,value:o});t&&i.push(t)}}return i.join(`&`)},Dt=e=>{if(!e)return`stream`;let t=e.split(`;`)[0]?.trim();if(t){if(t.startsWith(`application/json`)||t.endsWith(`+json`))return`json`;if(t===`multipart/form-data`)return`formData`;if([`application/`,`audio/`,`image/`,`video/`].some(e=>t.startsWith(e)))return`blob`;if(t.startsWith(`text/`))return`text`}},Ot=(e,t)=>t?!!(e.headers.has(t)||e.query?.[t]||e.headers.get(`Cookie`)?.includes(`${t}=`)):!1,kt=async({security:e,...t})=>{for(let n of e){if(Ot(t,n.name))continue;let e=await gt(n,t.auth);if(!e)continue;let r=n.name??`Authorization`;switch(n.in){case`query`:t.query||={},t.query[r]=e;break;case`cookie`:t.headers.append(`Cookie`,`${r}=${e}`);break;default:t.headers.set(r,e);break}}},At=e=>Tt({baseUrl:e.baseUrl,path:e.path,query:e.query,querySerializer:typeof e.querySerializer==`function`?e.querySerializer:Et(e.querySerializer),url:e.url}),jt=(e,t)=>{let n={...e,...t};return n.baseUrl?.endsWith(`/`)&&(n.baseUrl=n.baseUrl.substring(0,n.baseUrl.length-1)),n.headers=Mt(e.headers,t.headers),n},Mt=(...e)=>{let t=new Headers;for(let n of e){if(!n||typeof n!=`object`)continue;let e=n instanceof Headers?n.entries():Object.entries(n);for(let[n,r]of e)if(r===null)t.delete(n);else if(Array.isArray(r))for(let e of r)t.append(n,e);else r!==void 0&&t.set(n,typeof r==`object`?JSON.stringify(r):r)}return t};var Nt=class{_fns;constructor(){this._fns=[]}clear(){this._fns=[]}getInterceptorIndex(e){return typeof e==`number`?this._fns[e]?e:-1:this._fns.indexOf(e)}exists(e){let t=this.getInterceptorIndex(e);return!!this._fns[t]}eject(e){let t=this.getInterceptorIndex(e);this._fns[t]&&(this._fns[t]=null)}update(e,t){let n=this.getInterceptorIndex(e);return this._fns[n]?(this._fns[n]=t,e):!1}use(e){return this._fns=[...this._fns,e],this._fns.length-1}};const Pt=()=>({error:new Nt,request:new Nt,response:new Nt}),Ft=Et({allowReserved:!1,array:{explode:!0,style:`form`},object:{explode:!0,style:`deepObject`}}),It={"Content-Type":`application/json`},Lt=(e={})=>({..._t,headers:It,parseAs:`auto`,querySerializer:Ft,...e}),Rt=(e={})=>{let t=jt(Lt(),e),n=()=>({...t}),r=e=>(t=jt(t,e),n()),i=Pt(),a=async e=>{let n={...t,...e,fetch:e.fetch??t.fetch??globalThis.fetch,headers:Mt(t.headers,e.headers),serializedBody:void 0};return n.security&&await kt({...n,security:n.security}),n.requestValidator&&await n.requestValidator(n),n.body&&n.bodySerializer&&(n.serializedBody=n.bodySerializer(n.body)),(n.serializedBody===void 0||n.serializedBody===``)&&n.headers.delete(`Content-Type`),{opts:n,url:At(n)}},o=async e=>{let{opts:t,url:n}=await a(e),r={redirect:`follow`,...t,body:t.serializedBody},o=new Request(n,r);for(let e of i.request._fns)e&&(o=await e(o,t));let s=t.fetch,c=await s(o);for(let e of i.response._fns)e&&(c=await e(c,o,t));let l={request:o,response:c};if(c.ok){if(c.status===204||c.headers.get(`Content-Length`)===`0`)return t.responseStyle===`data`?{}:{data:{},...l};let e=(t.parseAs===`auto`?Dt(c.headers.get(`Content-Type`)):t.parseAs)??`json`,n;switch(e){case`arrayBuffer`:case`blob`:case`formData`:case`json`:case`text`:n=await c[e]();break;case`stream`:return t.responseStyle===`data`?c.body:{data:c.body,...l}}return e===`json`&&(t.responseValidator&&await t.responseValidator(n),t.responseTransformer&&(n=await t.responseTransformer(n))),t.responseStyle===`data`?n:{data:n,...l}}let u=await c.text(),d;try{d=JSON.parse(u)}catch{}let f=d??u,p=f;for(let e of i.error._fns)e&&(p=await e(f,c,o,t));if(p||={},t.throwOnError)throw p;return t.responseStyle===`data`?void 0:{error:p,...l}},s=e=>{let t=t=>o({...t,method:e});return t.sse=async t=>{let{opts:n,url:r}=await a(t);return ht({...n,body:n.body,headers:n.headers,method:e,url:r})},t};return{buildUrl:At,connect:s(`CONNECT`),delete:s(`DELETE`),get:s(`GET`),getConfig:n,head:s(`HEAD`),interceptors:i,options:s(`OPTIONS`),patch:s(`PATCH`),post:s(`POST`),put:s(`PUT`),request:o,setConfig:r,trace:s(`TRACE`)}};Object.entries({$body_:`body`,$headers_:`headers`,$path_:`path`,$query_:`query`});const zt=Rt(Lt({baseUrl:`http://localhost:4096`}));var V=class{_client=zt;constructor(e){e?.client&&(this._client=e.client)}},Bt=class extends V{event(e){return(e?.client??this._client).get.sse({url:`/global/event`,...e})}},Vt=class extends V{list(e){return(e?.client??this._client).get({url:`/project`,...e})}current(e){return(e?.client??this._client).get({url:`/project/current`,...e})}},Ht=class extends V{list(e){return(e?.client??this._client).get({url:`/pty`,...e})}create(e){return(e?.client??this._client).post({url:`/pty`,...e,headers:{"Content-Type":`application/json`,...e?.headers}})}remove(e){return(e.client??this._client).delete({url:`/pty/{id}`,...e})}get(e){return(e.client??this._client).get({url:`/pty/{id}`,...e})}update(e){return(e.client??this._client).put({url:`/pty/{id}`,...e,headers:{"Content-Type":`application/json`,...e.headers}})}connect(e){return(e.client??this._client).get({url:`/pty/{id}/connect`,...e})}},Ut=class extends V{get(e){return(e?.client??this._client).get({url:`/config`,...e})}update(e){return(e?.client??this._client).patch({url:`/config`,...e,headers:{"Content-Type":`application/json`,...e?.headers}})}providers(e){return(e?.client??this._client).get({url:`/config/providers`,...e})}},Wt=class extends V{ids(e){return(e?.client??this._client).get({url:`/experimental/tool/ids`,...e})}list(e){return(e.client??this._client).get({url:`/experimental/tool`,...e})}},Gt=class extends V{dispose(e){return(e?.client??this._client).post({url:`/instance/dispose`,...e})}},Kt=class extends V{get(e){return(e?.client??this._client).get({url:`/path`,...e})}},qt=class extends V{get(e){return(e?.client??this._client).get({url:`/vcs`,...e})}},Jt=class extends V{list(e){return(e?.client??this._client).get({url:`/session`,...e})}create(e){return(e?.client??this._client).post({url:`/session`,...e,headers:{"Content-Type":`application/json`,...e?.headers}})}status(e){return(e?.client??this._client).get({url:`/session/status`,...e})}delete(e){return(e.client??this._client).delete({url:`/session/{id}`,...e})}get(e){return(e.client??this._client).get({url:`/session/{id}`,...e})}update(e){return(e.client??this._client).patch({url:`/session/{id}`,...e,headers:{"Content-Type":`application/json`,...e.headers}})}children(e){return(e.client??this._client).get({url:`/session/{id}/children`,...e})}todo(e){return(e.client??this._client).get({url:`/session/{id}/todo`,...e})}init(e){return(e.client??this._client).post({url:`/session/{id}/init`,...e,headers:{"Content-Type":`application/json`,...e.headers}})}fork(e){return(e.client??this._client).post({url:`/session/{id}/fork`,...e,headers:{"Content-Type":`application/json`,...e.headers}})}abort(e){return(e.client??this._client).post({url:`/session/{id}/abort`,...e})}unshare(e){return(e.client??this._client).delete({url:`/session/{id}/share`,...e})}share(e){return(e.client??this._client).post({url:`/session/{id}/share`,...e})}diff(e){return(e.client??this._client).get({url:`/session/{id}/diff`,...e})}summarize(e){return(e.client??this._client).post({url:`/session/{id}/summarize`,...e,headers:{"Content-Type":`application/json`,...e.headers}})}messages(e){return(e.client??this._client).get({url:`/session/{id}/message`,...e})}prompt(e){return(e.client??this._client).post({url:`/session/{id}/message`,...e,headers:{"Content-Type":`application/json`,...e.headers}})}message(e){return(e.client??this._client).get({url:`/session/{id}/message/{messageID}`,...e})}promptAsync(e){return(e.client??this._client).post({url:`/session/{id}/prompt_async`,...e,headers:{"Content-Type":`application/json`,...e.headers}})}command(e){return(e.client??this._client).post({url:`/session/{id}/command`,...e,headers:{"Content-Type":`application/json`,...e.headers}})}shell(e){return(e.client??this._client).post({url:`/session/{id}/shell`,...e,headers:{"Content-Type":`application/json`,...e.headers}})}revert(e){return(e.client??this._client).post({url:`/session/{id}/revert`,...e,headers:{"Content-Type":`application/json`,...e.headers}})}unrevert(e){return(e.client??this._client).post({url:`/session/{id}/unrevert`,...e})}},Yt=class extends V{list(e){return(e?.client??this._client).get({url:`/command`,...e})}},Xt=class extends V{authorize(e){return(e.client??this._client).post({url:`/provider/{id}/oauth/authorize`,...e,headers:{"Content-Type":`application/json`,...e.headers}})}callback(e){return(e.client??this._client).post({url:`/provider/{id}/oauth/callback`,...e,headers:{"Content-Type":`application/json`,...e.headers}})}},Zt=class extends V{list(e){return(e?.client??this._client).get({url:`/provider`,...e})}auth(e){return(e?.client??this._client).get({url:`/provider/auth`,...e})}oauth=new Xt({client:this._client})},Qt=class extends V{text(e){return(e.client??this._client).get({url:`/find`,...e})}files(e){return(e.client??this._client).get({url:`/find/file`,...e})}symbols(e){return(e.client??this._client).get({url:`/find/symbol`,...e})}},$t=class extends V{list(e){return(e.client??this._client).get({url:`/file`,...e})}read(e){return(e.client??this._client).get({url:`/file/content`,...e})}status(e){return(e?.client??this._client).get({url:`/file/status`,...e})}},en=class extends V{log(e){return(e?.client??this._client).post({url:`/log`,...e,headers:{"Content-Type":`application/json`,...e?.headers}})}agents(e){return(e?.client??this._client).get({url:`/agent`,...e})}},tn=class extends V{remove(e){return(e.client??this._client).delete({url:`/mcp/{name}/auth`,...e})}start(e){return(e.client??this._client).post({url:`/mcp/{name}/auth`,...e})}callback(e){return(e.client??this._client).post({url:`/mcp/{name}/auth/callback`,...e,headers:{"Content-Type":`application/json`,...e.headers}})}authenticate(e){return(e.client??this._client).post({url:`/mcp/{name}/auth/authenticate`,...e})}set(e){return(e.client??this._client).put({url:`/auth/{id}`,...e,headers:{"Content-Type":`application/json`,...e.headers}})}},nn=class extends V{status(e){return(e?.client??this._client).get({url:`/mcp`,...e})}add(e){return(e?.client??this._client).post({url:`/mcp`,...e,headers:{"Content-Type":`application/json`,...e?.headers}})}connect(e){return(e.client??this._client).post({url:`/mcp/{name}/connect`,...e})}disconnect(e){return(e.client??this._client).post({url:`/mcp/{name}/disconnect`,...e})}auth=new tn({client:this._client})},rn=class extends V{status(e){return(e?.client??this._client).get({url:`/lsp`,...e})}},an=class extends V{status(e){return(e?.client??this._client).get({url:`/formatter`,...e})}},on=class extends V{next(e){return(e?.client??this._client).get({url:`/tui/control/next`,...e})}response(e){return(e?.client??this._client).post({url:`/tui/control/response`,...e,headers:{"Content-Type":`application/json`,...e?.headers}})}},sn=class extends V{appendPrompt(e){return(e?.client??this._client).post({url:`/tui/append-prompt`,...e,headers:{"Content-Type":`application/json`,...e?.headers}})}openHelp(e){return(e?.client??this._client).post({url:`/tui/open-help`,...e})}openSessions(e){return(e?.client??this._client).post({url:`/tui/open-sessions`,...e})}openThemes(e){return(e?.client??this._client).post({url:`/tui/open-themes`,...e})}openModels(e){return(e?.client??this._client).post({url:`/tui/open-models`,...e})}submitPrompt(e){return(e?.client??this._client).post({url:`/tui/submit-prompt`,...e})}clearPrompt(e){return(e?.client??this._client).post({url:`/tui/clear-prompt`,...e})}executeCommand(e){return(e?.client??this._client).post({url:`/tui/execute-command`,...e,headers:{"Content-Type":`application/json`,...e?.headers}})}showToast(e){return(e?.client??this._client).post({url:`/tui/show-toast`,...e,headers:{"Content-Type":`application/json`,...e?.headers}})}publish(e){return(e?.client??this._client).post({url:`/tui/publish`,...e,headers:{"Content-Type":`application/json`,...e?.headers}})}control=new on({client:this._client})},cn=class extends V{subscribe(e){return(e?.client??this._client).get.sse({url:`/event`,...e})}},ln=class extends V{postSessionIdPermissionsPermissionId(e){return(e.client??this._client).post({url:`/session/{id}/permissions/{permissionID}`,...e,headers:{"Content-Type":`application/json`,...e.headers}})}global=new Bt({client:this._client});project=new Vt({client:this._client});pty=new Ht({client:this._client});config=new Ut({client:this._client});tool=new Wt({client:this._client});instance=new Gt({client:this._client});path=new Kt({client:this._client});vcs=new qt({client:this._client});session=new Jt({client:this._client});command=new Yt({client:this._client});provider=new Zt({client:this._client});find=new Qt({client:this._client});file=new $t({client:this._client});app=new en({client:this._client});mcp=new nn({client:this._client});lsp=new rn({client:this._client});formatter=new an({client:this._client});tui=new sn({client:this._client});auth=new tn({client:this._client});event=new cn({client:this._client})};function un(e){if(!e?.fetch){let t=e=>(e.timeout=!1,fetch(e));e={...e,fetch:t}}return e?.directory&&(e.headers={...e.headers,"x-opencode-directory":encodeURIComponent(e.directory)}),new ln({client:Rt(e)})}async function dn(e){e=Object.assign({hostname:`127.0.0.1`,port:4096,timeout:5e3},e??{});let t=[`serve`,`--hostname=${e.hostname}`,`--port=${e.port}`];e.config?.logLevel&&t.push(`--log-level=${e.config.logLevel}`);let n=je(`opencode`,t,{signal:e.signal,env:{...process.env,OPENCODE_CONFIG_CONTENT:JSON.stringify(e.config??{})}});return{url:await new Promise((t,r)=>{let i=setTimeout(()=>{r(Error(`Timeout waiting for server to start after ${e.timeout}ms`))},e.timeout),a=``;n.stdout?.on(`data`,e=>{a+=e.toString();let n=a.split(` +`);for(let e of n)if(e.startsWith(`opencode server listening`)){let n=e.match(/on\s+(https?:\/\/[^\s]+)/);if(!n)throw Error(`Failed to parse server url from output: ${e}`);clearTimeout(i),t(n[1]);return}}),n.stderr?.on(`data`,e=>{a+=e.toString()}),n.on(`exit`,e=>{clearTimeout(i);let t=`Server exited with code ${e}`;a.trim()&&(t+=`\nServer output: ${a}`),r(Error(t))}),n.on(`error`,e=>{clearTimeout(i),r(e)}),e.signal&&e.signal.addEventListener(`abort`,()=>{clearTimeout(i),r(Error(`Aborted`))})}),close(){n.kill()}}}async function fn(e){let t=await dn({...e});return{client:un({baseUrl:t.url}),server:t}}async function pn(e){if(e<0||!Number.isFinite(e))throw Error(`Invalid sleep duration: ${e}`);return new Promise(t=>setTimeout(t,e))}const mn={api_error:`API Error`,configuration:`Configuration Error`,internal:`Internal Error`,llm_fetch_error:`LLM Fetch Error`,llm_timeout:`LLM Timeout`,permission:`Permission Error`,rate_limit:`Rate Limit`,validation:`Validation Error`};function hn(e){return e.type===`rate_limit`?`:warning:`:e.type===`llm_timeout`?`:hourglass:`:e.type===`llm_fetch_error`||e.retryable?`:warning:`:`:x:`}function gn(e){let t=hn(e),n=mn[e.type],r=[];return r.push(`${t} **${n}**`),r.push(``),r.push(e.message),e.details!=null&&(r.push(``),r.push(`> ${e.details}`)),e.suggestedAction!=null&&(r.push(``),r.push(`**Suggested action:** ${e.suggestedAction}`)),e.retryable&&(r.push(``),r.push(`_This error is retryable._`)),e.resetTime!=null&&(r.push(``),r.push(`_Rate limit resets at: ${e.resetTime.toISOString()}_`)),r.join(` +`)}function _n(e,t,n,r){return{type:e,message:t,retryable:n,details:r?.details,suggestedAction:r?.suggestedAction,resetTime:r?.resetTime}}const vn=[/fetch failed/i,/connect\s*timeout/i,/connecttimeouterror/i,/timed?\s*out/i,/econnrefused/i,/econnreset/i,/etimedout/i,/network error/i];function yn(e){if(e==null)return!1;let t=``;if(typeof e==`string`)t=e;else if(e instanceof Error)t=e.message,`cause`in e&&typeof e.cause==`string`&&(t+=` ${e.cause}`);else if(typeof e==`object`){let n=e;typeof n.message==`string`&&(t=n.message),typeof n.cause==`string`&&(t+=` ${n.cause}`)}return vn.some(e=>e.test(t))}function bn(e,t){return _n(`llm_fetch_error`,`LLM request failed: ${e}`,!0,{details:t==null?void 0:`Model: ${t}`,suggestedAction:`This is a transient network error. The request may succeed on retry, or try a different model.`})}function xn(e,t){return _n(`configuration`,`Agent error: ${e}`,!1,{details:t==null?void 0:`Requested agent: ${t}`,suggestedAction:`Verify the agent name is correct and the required plugins (e.g., oMo) are installed.`})}async function Sn(e,t,i,a,o,s=n,c){let l=Date.now(),u=0;for(;!a.aborted;){if(await pn(500),a.aborted)return{completed:!1,error:`Aborted`};if(c?.sessionError==null)u=0;else{if(u++,u>=3)return o.error(`Session error persisted through grace period`,{sessionId:t,error:c.sessionError,graceCycles:u}),{completed:!1,error:`Session error: ${c.sessionError}`};continue}if(c?.sessionIdle===!0)return o.debug(`Session idle detected via event stream`,{sessionId:t}),{completed:!0,error:null};let n=Date.now()-l;if(s>0&&n>=s)return o.warning(`Poll timeout reached`,{elapsedMs:n,maxPollTimeMs:s}),{completed:!1,error:`Poll timeout after ${n}ms`};try{let n=((await e.session.status({query:{directory:i}})).data??{})[t];if(n==null)o.debug(`Session status not found in poll response`,{sessionId:t});else if(n.type===`idle`)return o.debug(`Session idle detected via polling`,{sessionId:t}),{completed:!0,error:null};else o.debug(`Session status`,{sessionId:t,type:n.type});if(c!=null&&!c.firstMeaningfulEventReceived){let e=Date.now()-l;if(e>=9e4)return o.error(`No agent activity detected β€” server may have crashed during prompt processing`,{elapsedMs:e,sessionId:t}),{completed:!1,error:`No agent activity detected after ${e}ms β€” server may have crashed during prompt processing`}}}catch(e){o.debug(`Poll request failed`,{error:r(e)})}}return{completed:!1,error:`Aborted`}}async function Cn(e,t=2e3){await Promise.race([e,new Promise(e=>{setTimeout(e,t)})])}function wn(e){try{let t=new URL(e);return t.hostname===`github.com`||t.hostname===`api.github.com`}catch{return!1}}function Tn(e){try{let t=new URL(e);return t.hostname===`github.com`&&(t.pathname.startsWith(`/user-attachments/assets/`)||t.pathname.startsWith(`/user-attachments/files/`))}catch{return!1}}function En(e){let t=e.match(/https:\/\/github\.com\/[a-zA-Z0-9-]+\/[\w.-]+\/(?:pull|issues)\/\d+(?:#issuecomment-\d+)?/g)??[];return[...new Set(t)].filter(wn)}function Dn(e){let t=/\[[\w-]+\s+([a-f0-9]{7,40})\]/g,n=[];for(let r of e.matchAll(t))r[1]!=null&&n.push(r[1]);return[...new Set(n)]}const On={todowrite:[`Todo`,`\x1B[33m\x1B[1m`],todoread:[`Todo`,`\x1B[33m\x1B[1m`],bash:[`Bash`,`\x1B[31m\x1B[1m`],edit:[`Edit`,`\x1B[32m\x1B[1m`],glob:[`Glob`,`\x1B[34m\x1B[1m`],grep:[`Grep`,`\x1B[34m\x1B[1m`],list:[`List`,`\x1B[34m\x1B[1m`],read:[`Read`,`\x1B[35m\x1B[1m`],write:[`Write`,`\x1B[32m\x1B[1m`],websearch:[`Search`,`\x1B[2m\x1B[1m`]},kn=`\x1B[0m`;function An(){return N.env.NO_COLOR==null}function jn(e,t){let[n,r]=On[e.toLowerCase()]??[e,`\x1B[36m\x1B[1m`],i=n.padEnd(10,` `);An()?N.stdout.write(`\n${r}|${kn} ${i} ${kn}${t}\n`):N.stdout.write(`\n| ${i} ${t}\n`)}function Mn(e){N.stdout.write(`\n${e}\n`)}function Nn(e,t){t.debug(`Server event`,{eventType:e.type,properties:e.properties})}function Pn(e,t,n,r,i){let a=En(t);if(e.includes(`gh pr create`)){let e=a.filter(e=>e.includes(`/pull/`)&&!e.includes(`#`));for(let t of e)n.includes(t)||n.push(t)}if(e.includes(`git commit`)){let e=Dn(t);for(let t of e)r.includes(t)||r.push(t)}(e.includes(`gh issue comment`)||e.includes(`gh pr comment`))&&a.some(e=>e.includes(`#issuecomment`))&&i()}async function Fn(e,t,n,r,i){let a=``,o=null,s=null,c=null,l=[],u=[],d=0,f=null;for await(let p of e){if(n.aborted)break;if(Nn(p,r),p.type===`message.part.updated`){let e=p.properties.part;if(e.sessionID!==t)continue;if(i!=null&&(i.firstMeaningfulEventReceived=!0),e.type===`text`&&`text`in e&&typeof e.text==`string`){a=e.text;let t=`time`in e?e.time?.end:void 0;t!=null&&Number.isFinite(t)&&(Mn(a),a=``)}else if(e.type===`tool`){let t=e.state;t.status===`completed`&&(jn(e.tool,t.title),e.tool.toLowerCase()===`bash`&&Pn(String(t.input.command??t.input.cmd??``),String(t.output),l,u,()=>{d++}))}}else if(p.type===`message.updated`){let e=p.properties.info;e.sessionID===t&&e.role===`assistant`&&e.tokens!=null&&(i!=null&&(i.firstMeaningfulEventReceived=!0),o={input:e.tokens.input??0,output:e.tokens.output??0,reasoning:e.tokens.reasoning??0,cache:{read:e.tokens.cache?.read??0,write:e.tokens.cache?.write??0}},s=e.modelID??null,c=e.cost??null,r.debug(`Token usage received`,{tokens:o,model:s,cost:c}))}else if(p.type===`session.error`){if(p.properties.sessionID===t){let e=p.properties.error,t=typeof e==`string`?e:String(e);r.error(`Session error`,{error:e}),f=yn(e)?bn(t,s??void 0):xn(t),i!=null&&(i.sessionError=t)}}else p.type===`session.idle`&&p.properties.sessionID===t&&(i!=null&&(i.sessionIdle=!0),a.length>0&&(Mn(a),a=``))}return a.length>0&&Mn(a),{tokens:o,model:s,cost:c,prsCreated:l,commitsCreated:u,commentsPosted:d,llmError:f}}const In=5e3;async function Ln(e,t,n,r,i){let a=new AbortController,o={firstMeaningfulEventReceived:!1,sessionIdle:!1,sessionError:null},s=await e.event.subscribe(),c={tokens:null,model:null,cost:null,prsCreated:[],commitsCreated:[],commentsPosted:0,llmError:null},l=Fn(s.stream,t,a.signal,i,o).then(e=>{c=e}).catch(e=>{e instanceof Error&&e.name!==`AbortError`&&i.debug(`Event stream error`,{error:e.message})}),u=async()=>{a.abort(),await Cn(l)};try{let s=await Sn(e,t,n,a.signal,i,r,o);if(await u(),!s.completed){let e=s.error??`Session did not reach idle state`;return i.error(`Session completion polling failed`,{error:e,sessionId:t}),{success:!1,error:e,llmError:c.llmError,shouldRetry:c.llmError!=null,eventStreamResult:c}}return{success:!0,error:null,llmError:null,shouldRetry:!1,eventStreamResult:c}}finally{a.abort(),await Cn(l)}}const Rn=e=>{if(e?.model!=null)return{providerID:e.model.providerID,modelID:e.model.modelID};if(!(e!=null&&Object.values(e.omoProviders).some(e=>e!==`no`)))return{providerID:E.providerID,modelID:E.modelID}};async function zn(e,t,n,r,i,a,o){let s={parts:[{type:`text`,text:n},...r??[]]},c=Rn(a);c!=null&&(s.model=c);let l=a?.agent??`sisyphus`;l!==`sisyphus`&&(s.agent=l);let u=await e.session.promptAsync({path:{id:t},body:s,query:{directory:i}});if(u.error!=null){let e=String(u.error),t=yn(u.error)?bn(e):null;return{success:!1,error:e,llmError:t,shouldRetry:t!=null,eventStreamResult:{tokens:null,model:null,cost:null,prsCreated:[],commitsCreated:[],commentsPosted:0,llmError:t}}}return Ln(e,t,i,a?.timeoutMs??18e5,o)}function Bn(){return[`## Critical Rules (NON-NEGOTIABLE)`,`- You are a NON-INTERACTIVE CI agent. Do NOT ask questions. Make decisions autonomously.`,`- Post EXACTLY ONE comment or review per invocation. Never multiple.`,`- Include the Run Summary marker block in your comment.`,"- Use `gh` CLI for all GitHub operations. Do not use the GitHub API directly.",`- Mark your comment with the bot identification marker.`].join(` +`)}function Vn(e,t,n){if(e==null)return``;let r=[`## Thread Identity`];return r.push(`**Logical Thread**: \`${e.key}\` (${e.entityType} #${e.entityId})`),t?(r.push(`**Status**: Continuing previous conversation thread.`),n!=null&&n.length>0&&(r.push(``),r.push(`**Thread Summary**:`),r.push(n))):r.push(`**Status**: Fresh conversation β€” no prior thread found for this entity.`),r.join(` +`)}function Hn(e){return e==null||e.length===0?``:[`## Current Thread Context`,`This is work from your PREVIOUS runs on this same entity:`,``,e].join(` +`)}function Un(){return[`## Reminder: Critical Rules`,"- ONE comment/review only. Include Run Summary marker. Use `gh` CLI only."].join(` +`)}function Wn(e,t){switch(e.eventType){case`issue_comment`:return{directive:`Respond to the comment above. Post your response as a single comment on this thread.`,appendMode:!0};case`discussion_comment`:return{directive:`Respond to the discussion comment above. Post your response as a single comment.`,appendMode:!0};case`issues`:return e.action===`opened`?{directive:`Triage this issue: summarize, reproduce if possible, propose next steps. Post your response as a single comment.`,appendMode:!0}:{directive:`Respond to the mention in this issue. Post your response as a single comment.`,appendMode:!0};case`pull_request`:return{directive:[`Review this pull request for code quality, potential bugs, and improvements.`,"If you are a requested reviewer, submit a review via `gh pr review` with your full response (including Run Summary) in the --body.",`Include the Run Summary in the review body. Do not post a separate comment.`,`If the author is a collaborator, prioritize actionable feedback over style nits.`].join(` +`),appendMode:!0};case`pull_request_review_comment`:return{directive:Gn(e),appendMode:!0};case`schedule`:case`workflow_dispatch`:return{directive:t??``,appendMode:!1};default:return{directive:`Execute the requested operation.`,appendMode:!0}}}function Gn(e){let t=e.target,n=[`Respond to the review comment.`,``];return t?.path!=null&&n.push(`**File:** \`${t.path}\``),t?.line!=null&&n.push(`**Line:** ${t.line}`),t?.commitId!=null&&n.push(`**Commit:** \`${t.commitId}\``),t?.diffHunk!=null&&t.diffHunk.length>0&&n.push(``,`**Diff Context:**`,"```diff",t.diffHunk,"```"),n.join(` +`)}function Kn(e,t){let{directive:n,appendMode:r}=Wn(e,t),i=[`## Task`,``];return r?(i.push(n),t!=null&&t.trim().length>0&&i.push(``,`**Additional Instructions:**`,t.trim())):i.push(n),i.push(``),i.join(` +`)}function qn(e,t){let{context:n,customPrompt:r,cacheStatus:i,sessionContext:a,logicalKey:o,isContinuation:s,currentThreadSessionId:c}=e,l=[],u=s===!0;l.push(Bn());let d=Vn(o??null,u,null);d.length>0&&l.push(d);let f=a!=null&&u&&c!=null?Xn(a.priorWorkContext,c):null;e.triggerContext==null?n.commentBody==null?l.push(`## Task -You are the Fro Bot Agent running in a non-interactive CI environment (GitHub Actions). +Execute the requested operation for repository ${n.repo}. Follow all instructions and requirements listed in this prompt. +`):l.push(`## Task -## Operating Environment +Respond to the trigger comment above. Follow all instructions and requirements listed in this prompt. +`):l.push(Kn(e.triggerContext,r)),n.commentBody!=null&&l.push(`## Trigger Comment +**Author:** ${n.commentAuthor??`unknown`} -- **This is NOT an interactive session.** There is no human reading your assistant messages in real time. -- Your assistant messages are logged to the GitHub Actions job output. Use them only for diagnostic information (e.g., files read, decisions made, errors encountered) that helps troubleshoot issues in CI logs. -- The human who invoked you will ONLY see what you post as a GitHub comment or review. Your assistant messages are invisible to them. -- You MUST post your response using the gh CLI (see Response Protocol below). Do not rely on assistant message output to communicate with the user. -`),r!=null&&r.trim().length>0&&e.triggerContext==null&&o.push(` +\`\`\` +${n.commentBody} +\`\`\` +`);let p=Hn(f);if(p.length>0&&l.push(p),r!=null&&r.trim().length>0&&e.triggerContext==null&&l.push(` ${r.trim()} -`),e.triggerContext==null?n.commentBody==null?o.push(`## Task - -Execute the requested operation for repository ${n.repo}. Follow all instructions and requirements listed in this prompt. -`):o.push(`## Task - -Respond to the trigger comment above. Follow all instructions and requirements listed in this prompt. -`):o.push(Bn(e.triggerContext,r)),e.triggerContext!=null){let t=e.triggerContext.eventType;(t===`pull_request`||t===`pull_request_review_comment`)&&o.push(Gn(n))}if(o.push(` +`),e.triggerContext!=null){let t=e.triggerContext.eventType;(t===`pull_request`||t===`pull_request_review_comment`)&&l.push($n(n))}if(l.push(` ## Environment - **Repository:** ${n.repo} - **Branch/Ref:** ${n.ref} @@ -155,17 +155,21 @@ Respond to the trigger comment above. Follow all instructions and requirements l - **Actor:** ${n.actor} - **Run ID:** ${n.runId} - **Cache Status:** ${i} -`),n.issueNumber!=null){let e=n.issueType===`pr`?`Pull Request`:`Issue`;o.push(`## ${e} Context +`),n.issueNumber!=null){let e=n.issueType===`pr`?`Pull Request`:`Issue`;l.push(`## ${e} Context - **Number:** #${n.issueNumber} - **Title:** ${n.issueTitle??`N/A`} - **Type:** ${n.issueType??`unknown`} -`)}n.commentBody!=null&&o.push(`## Trigger Comment -**Author:** ${n.commentAuthor??`unknown`} +`)}if(n.diffContext!=null&&l.push(Qn(n.diffContext)),n.hydratedContext!=null&&l.push(it(n.hydratedContext)),a!=null){let e=Zn(a,u,c,f!=null);e!=null&&l.push(e)}l.push(`# Agent Context -\`\`\` -${n.commentBody} -\`\`\` -`),a!=null&&o.push(Un(a)),n.diffContext!=null&&o.push(Wn(n.diffContext)),n.hydratedContext!=null&&o.push(nt(n.hydratedContext)),o.push(`## Session Management (REQUIRED) +You are the Fro Bot Agent running in a non-interactive CI environment (GitHub Actions). + +## Operating Environment + +- **This is NOT an interactive session.** There is no human reading your assistant messages in real time. +- Your assistant messages are logged to the GitHub Actions job output. Use them only for diagnostic information (e.g., files read, decisions made, errors encountered) that helps troubleshoot issues in CI logs. +- The human who invoked you will ONLY see what you post as a GitHub comment or review. Your assistant messages are invisible to them. +- You MUST post your response using the gh CLI (see Response Protocol below). Do not rely on assistant message output to communicate with the user. +`),l.push(`## Session Management (REQUIRED) Before investigating any issue: 1. Use \`session_search\` to find relevant prior sessions for this repository @@ -176,23 +180,23 @@ Before completing: 1. Ensure your session contains a summary of work done 2. Include key decisions, findings, and outcomes 3. This summary will be searchable in future agent runs -`),n.issueNumber!=null&&o.push(Hn(n,i,e.sessionId));let s=n.issueNumber??``,c=n.issueNumber!=null;o.push(`## GitHub Operations (Use gh CLI) +`),n.issueNumber!=null&&l.push(Jn(n,i,e.sessionId));let m=n.issueNumber??``,h=n.issueNumber!=null;l.push(`## GitHub Operations (Use gh CLI) The \`gh\` CLI is pre-authenticated. Use it for all GitHub operations. -${c?` +${h?` ### Posting Your Response See **Response Protocol** above. Post exactly one comment or review per run. \`\`\`bash # Post response as PR comment (use --body-file for long responses) -gh pr comment ${s} --body "Your response with Run Summary" +gh pr comment ${m} --body "Your response with Run Summary" # Submit PR review with response in body -gh pr review ${s} --approve --body "Your review with Run Summary" +gh pr review ${m} --approve --body "Your review with Run Summary" # Post response as issue comment -gh issue comment ${s} --body "Your response with Run Summary" +gh issue comment ${m} --body "Your response with Run Summary" \`\`\``:``} ### Creating PRs @@ -210,10 +214,10 @@ git push origin HEAD ### API Calls \`\`\`bash gh api repos/${n.repo}/issues --jq '.[].title' -gh api repos/${n.repo}/pulls/${s}/files --jq '.[].filename' +gh api repos/${n.repo}/pulls/${m}/files --jq '.[].filename' \`\`\` -`);let l=o.join(` -`);return t.debug(`Built agent prompt`,{length:l.length,hasCustom:r!=null,hasSessionContext:a!=null}),l}function Hn(e,t,n){let r=e.issueNumber??``;return`## Response Protocol (REQUIRED) +`),l.push(Un());let g=l.join(` +`);return t.debug(`Built agent prompt`,{length:g.length,hasCustom:r!=null,hasSessionContext:a!=null}),g}function Jn(e,t,n){let r=e.issueNumber??``;return`## Response Protocol (REQUIRED) You MUST post exactly ONE comment or review per invocation. All of your output β€” your response content AND the Run Summary β€” goes into that single artifact. @@ -249,16 +253,17 @@ Every response you post β€” regardless of channel (issue, PR, discussion, review \`\`\` -`}function Un(e){let t=[`## Prior Session Context`];if(e.recentSessions.length>0){t.push(``),t.push(`### Recent Sessions`),t.push(`| ID | Title | Updated | Messages | Agents |`),t.push(`|----|-------|---------|----------|--------|`);for(let n of e.recentSessions.slice(0,5)){let e=new Date(n.updatedAt).toISOString().split(`T`)[0],r=n.agents.join(`, `)||`N/A`,i=n.title||`Untitled`;t.push(`| ${n.id} | ${i} | ${e} | ${n.messageCount} | ${r} |`)}t.push(``),t.push("Use `session_read` to review any of these sessions in detail.")}if(e.priorWorkContext.length>0){t.push(``),t.push(`### Relevant Prior Work`),t.push(``),t.push(`The following sessions contain content related to this issue:`),t.push(``);for(let n of e.priorWorkContext.slice(0,3)){t.push(`**Session ${n.sessionId}:**`),t.push("```markdown");for(let e of n.matches.slice(0,2))t.push(`- ${e.excerpt}`);t.push("```"),t.push(``)}t.push("Use `session_read` to review full context before starting new investigation.")}return t.push(``),t.join(` -`)}function Wn(e){let t=[`## Pull Request Diff Summary`];if(t.push(``),t.push(`- **Changed Files:** ${e.changedFiles}`),t.push(`- **Additions:** +${e.additions}`),t.push(`- **Deletions:** -${e.deletions}`),e.truncated&&t.push(`- **Note:** Diff was truncated due to size limits`),e.files.length>0){t.push(``),t.push(`### Changed Files`),t.push(`| File | Status | +/- |`),t.push(`|------|--------|-----|`);for(let n of e.files.slice(0,20))t.push(`| \`${n.filename}\` | ${n.status} | +${n.additions}/-${n.deletions} |`);e.files.length>20&&t.push(`| ... | | +${e.files.length-20} more files |`)}return t.push(``),t.join(` -`)}function Gn(e){let t=[`## Output Contract`,``];return t.push(`- Review action: approve/request-changes if confident; otherwise comment-only`),t.push(`- Requested reviewer: ${e.isRequestedReviewer?`yes`:`no`}`),e.authorAssociation!=null&&t.push(`- Author association: ${e.authorAssociation}`),t.push(``),t.join(` -`)}async function Kn(e,t,n,i){let a=Date.now(),o=new AbortController,s=n?.timeoutMs??18e5,c=null,l=!1,u=i==null,d=null;s>0&&(c=setTimeout(()=>{l=!0,t.warning(`Execution timeout reached`,{timeoutMs:s}),o.abort()},s)),t.info(`Executing OpenCode agent (SDK mode)`,{agent:n?.agent??`sisyphus`,hasModelOverride:n?.model!=null,timeoutMs:s});try{let r;if(i==null){let e=await un({signal:o.signal});r=e.client,d=e.server}else r=i.client;let c=await r.session.create();if(c.data==null||c.error!=null)throw Error(`Failed to create session: ${c.error==null?`No data returned`:String(c.error)}`);let u=c.data.id,f=Vn({...e,sessionId:u},t),p=re();if(te()){let e=O(),n=Te.createHash(`sha256`).update(f).digest(`hex`),r=L.join(e,`prompt-${u}-${n.slice(0,8)}.txt`);try{await I.mkdir(e,{recursive:!0}),await I.writeFile(r,f,`utf8`),t.info(`Prompt artifact written`,{hash:n,path:r})}catch(e){t.warning(`Failed to write prompt artifact`,{error:e instanceof Error?e.message:String(e),path:r})}}let m={tokens:null,model:null,cost:null,prsCreated:[],commitsCreated:[],commentsPosted:0,llmError:null},h=null,g=null;for(let i=1;i<=3;i++){if(l)return{success:!1,exitCode:130,duration:Date.now()-a,sessionId:u,error:`Execution timed out after ${s}ms`,tokenUsage:m.tokens,model:m.model,cost:m.cost,prsCreated:m.prsCreated,commitsCreated:m.commitsCreated,commentsPosted:m.commentsPosted,llmError:g};if(s>0&&s-(Date.now()-a)<=5e3&&i>1)break;let o=i===1?f:`The previous request was interrupted by a network error (fetch failed). +`}function Yn(e,t,n){let r=[t];if(e.recentSessions.length>0){r.push(``),r.push(`### Recent Sessions`),r.push(`| ID | Title | Updated | Messages | Agents |`),r.push(`|----|-------|---------|----------|--------|`);for(let t of e.recentSessions.slice(0,5)){let e=new Date(t.updatedAt).toISOString().split(`T`)[0],n=t.agents.join(`, `)||`N/A`,i=t.title||`Untitled`;r.push(`| ${t.id} | ${i} | ${e} | ${t.messageCount} | ${n} |`)}r.push(``),r.push("Use `session_read` to review any of these sessions in detail.")}if(n.length>0){r.push(``),r.push(`### Relevant Prior Work`),r.push(``),r.push(`The following sessions contain content related to this issue:`),r.push(``);for(let e of n.slice(0,3)){r.push(`**Session ${e.sessionId}:**`),r.push("```markdown");for(let t of e.matches.slice(0,2))r.push(`- ${t.excerpt}`);r.push("```"),r.push(``)}r.push("Use `session_read` to review full context before starting new investigation.")}return r.push(``),r.join(` +`)}function Xn(e,t){let n=e.filter(e=>e.sessionId===t);if(n.length===0)return null;let r=[];for(let e of n.slice(0,1)){r.push(`**Session ${e.sessionId}:**`),r.push("```markdown");for(let t of e.matches.slice(0,3))r.push(`- ${t.excerpt}`);r.push("```")}return r.join(` +`)}function Zn(e,t,n,r){if(t&&n!=null){let t=e.priorWorkContext.filter(e=>e.sessionId!==n);return e.recentSessions.length===0&&t.length===0?null:Yn(e,`## Related Historical Context`,t)}return e.recentSessions.length===0&&e.priorWorkContext.length===0&&r?null:Yn(e,`## Prior Session Context`,e.priorWorkContext)}function Qn(e){let t=[`## Pull Request Diff Summary`];if(t.push(``),t.push(`- **Changed Files:** ${e.changedFiles}`),t.push(`- **Additions:** +${e.additions}`),t.push(`- **Deletions:** -${e.deletions}`),e.truncated&&t.push(`- **Note:** Diff was truncated due to size limits`),e.files.length>0){t.push(``),t.push(`### Changed Files`),t.push(`| File | Status | +/- |`),t.push(`|------|--------|-----|`);for(let n of e.files.slice(0,20))t.push(`| \`${n.filename}\` | ${n.status} | +${n.additions}/-${n.deletions} |`);e.files.length>20&&t.push(`| ... | | +${e.files.length-20} more files |`)}return t.push(``),t.join(` +`)}function $n(e){let t=[`## Output Contract`,``];return t.push(`- Review action: approve/request-changes if confident; otherwise comment-only`),t.push(`- Requested reviewer: ${e.isRequestedReviewer?`yes`:`no`}`),e.authorAssociation!=null&&t.push(`- Author association: ${e.authorAssociation}`),t.push(``),t.join(` +`)}async function er(e,t,n,i){let a=Date.now(),o=new AbortController,s=n?.timeoutMs??18e5,c=null,l=!1,u=i==null,d=null;s>0&&(c=setTimeout(()=>{l=!0,t.warning(`Execution timeout reached`,{timeoutMs:s}),o.abort()},s)),t.info(`Executing OpenCode agent (SDK mode)`,{agent:n?.agent??`sisyphus`,hasModelOverride:n?.model!=null,timeoutMs:s});try{let r;if(i==null){let e=await fn({signal:o.signal});r=e.client,d=e.server}else r=i.client;let c;if(n?.continueSessionId==null){let e=n?.sessionTitle==null?void 0:{body:{title:n.sessionTitle}},i=e==null?await r.session.create():await r.session.create(e);if(i.data==null||i.error!=null)throw Error(`Failed to create session: ${i.error==null?`No data returned`:String(i.error)}`);c=i.data.id,t.info(`Created new OpenCode session`,{sessionId:c,sessionTitle:n?.sessionTitle??null})}else c=n.continueSessionId,t.info(`Continuing existing OpenCode session`,{sessionId:c});let u=qn({...e,sessionId:c},t),f=re();if(te()){let e=O(),n=Te.createHash(`sha256`).update(u).digest(`hex`),r=L.join(e,`prompt-${c}-${n.slice(0,8)}.txt`);try{await I.mkdir(e,{recursive:!0}),await I.writeFile(r,u,`utf8`),t.info(`Prompt artifact written`,{hash:n,path:r})}catch(e){t.warning(`Failed to write prompt artifact`,{error:e instanceof Error?e.message:String(e),path:r})}}let p={tokens:null,model:null,cost:null,prsCreated:[],commitsCreated:[],commentsPosted:0,llmError:null},m=null,h=null;for(let i=1;i<=3;i++){if(l)return{success:!1,exitCode:130,duration:Date.now()-a,sessionId:c,error:`Execution timed out after ${s}ms`,tokenUsage:p.tokens,model:p.model,cost:p.cost,prsCreated:p.prsCreated,commitsCreated:p.commitsCreated,commentsPosted:p.commentsPosted,llmError:h};if(s>0&&s-(Date.now()-a)<=5e3&&i>1)break;let o=i===1?u:`The previous request was interrupted by a network error (fetch failed). Please continue where you left off. If you were in the middle of a task, resume it. -If you had completed the task, confirm the completion.`,c=i===1?e.fileParts:void 0,d=await Ln(r,u,o,c,p,n,t);if(d.success)return m=d.eventStreamResult,{success:!0,exitCode:0,duration:Date.now()-a,sessionId:u,error:null,tokenUsage:m.tokens,model:m.model,cost:m.cost,prsCreated:m.prsCreated,commitsCreated:m.commitsCreated,commentsPosted:m.commentsPosted,llmError:null};if(h=d.error,g=d.llmError,!d.shouldRetry||i>=3)break;t.warning(`LLM fetch error detected, retrying with continuation prompt`,{attempt:i,maxAttempts:3,error:d.error,delayMs:Pn,sessionId:u}),await dn(Pn)}return{success:!1,exitCode:1,duration:Date.now()-a,sessionId:u,error:h??`Unknown error`,tokenUsage:m.tokens,model:m.model,cost:m.cost,prsCreated:m.prsCreated,commitsCreated:m.commitsCreated,commentsPosted:m.commentsPosted,llmError:g}}catch(e){let n=Date.now()-a,i=r(e);return t.error(`OpenCode execution failed`,{error:i,durationMs:n}),{success:!1,exitCode:1,duration:n,sessionId:null,error:i,tokenUsage:null,model:null,cost:null,prsCreated:[],commitsCreated:[],commentsPosted:0,llmError:_n(e)?vn(i):null}}finally{c!=null&&clearTimeout(c),o.abort(),u&&d?.close()}}async function qn(e,t,n){return t.commentId==null?(n.debug(`No comment ID, skipping eyes reaction`),!1):await ze(e,t.repo,t.commentId,`eyes`,n)==null?!1:(n.info(`Added eyes reaction`,{commentId:t.commentId}),!0)}async function Jn(e,t,n){return t.issueNumber==null?(n.debug(`No issue number, skipping working label`),!1):await He(e,t.repo,`agent: working`,`fcf2e1`,`Agent is currently working on this`,n)&&await Ue(e,t.repo,t.issueNumber,[`agent: working`],n)?(n.info(`Added working label`,{issueNumber:t.issueNumber}),!0):!1}async function Yn(e,t,n){await Promise.all([qn(e,t,n),Jn(e,t,n)])}async function Xn(e,t,n){if(t.commentId==null||t.botLogin==null)return;let r=(await Be(e,t.repo,t.commentId,n)).find(e=>e.content===`eyes`&&e.userLogin===t.botLogin);r!=null&&await Ve(e,t.repo,t.commentId,r.id,n)}async function Zn(e,t,n,r){t.commentId!=null&&await ze(e,t.repo,t.commentId,n,r)}async function Qn(e,t,n){if(t.commentId==null||t.botLogin==null){n.debug(`Missing comment ID or bot login, skipping reaction update`);return}try{await Xn(e,t,n),await Zn(e,t,`hooray`,n),n.info(`Updated reaction to success indicator`,{commentId:t.commentId,reaction:`hooray`})}catch(e){n.warning(`Failed to update reaction (non-fatal)`,{error:r(e)})}}async function $n(e,t,n){if(t.commentId==null||t.botLogin==null){n.debug(`Missing comment ID or bot login, skipping reaction update`);return}try{await Xn(e,t,n),await Zn(e,t,`confused`,n),n.info(`Updated reaction to confused`,{commentId:t.commentId})}catch(e){n.warning(`Failed to update failure reaction (non-fatal)`,{error:r(e)})}}async function er(e,t,n){if(t.issueNumber==null){n.debug(`No issue number, skipping label removal`);return}await We(e,t.repo,t.issueNumber,`agent: working`,n)&&n.info(`Removed working label`,{issueNumber:t.issueNumber})}async function tr(e,t,n,r){n?await Qn(e,t,r):await $n(e,t,r),await er(e,t,r)}var nr=class{constructor(){if(this.payload={},process.env.GITHUB_EVENT_PATH)if(be(process.env.GITHUB_EVENT_PATH))this.payload=JSON.parse(xe(process.env.GITHUB_EVENT_PATH,{encoding:`utf8`}));else{let e=process.env.GITHUB_EVENT_PATH;process.stdout.write(`GITHUB_EVENT_PATH ${e} does not exist${ve}`)}this.eventName=process.env.GITHUB_EVENT_NAME,this.sha=process.env.GITHUB_SHA,this.ref=process.env.GITHUB_REF,this.workflow=process.env.GITHUB_WORKFLOW,this.action=process.env.GITHUB_ACTION,this.actor=process.env.GITHUB_ACTOR,this.job=process.env.GITHUB_JOB,this.runAttempt=parseInt(process.env.GITHUB_RUN_ATTEMPT,10),this.runNumber=parseInt(process.env.GITHUB_RUN_NUMBER,10),this.runId=parseInt(process.env.GITHUB_RUN_ID,10),this.apiUrl=process.env.GITHUB_API_URL??`https://api.github.com`,this.serverUrl=process.env.GITHUB_SERVER_URL??`https://github.com`,this.graphqlUrl=process.env.GITHUB_GRAPHQL_URL??`https://api.github.com/graphql`}get issue(){let e=this.payload;return Object.assign(Object.assign({},this.repo),{number:(e.issue||e.pull_request||e).number})}get repo(){if(process.env.GITHUB_REPOSITORY){let[e,t]=process.env.GITHUB_REPOSITORY.split(`/`);return{owner:e,repo:t}}if(this.payload.repository)return{owner:this.payload.repository.owner.login,repo:this.payload.repository.name};throw Error(`context.repo requires a GITHUB_REPOSITORY environment variable like 'owner/repo'`)}},rr=S((e=>{Object.defineProperty(e,`__esModule`,{value:!0}),e.getProxyUrl=t,e.checkBypass=n;function t(e){let t=e.protocol===`https:`;if(n(e))return;let r=t?process.env.https_proxy||process.env.HTTPS_PROXY:process.env.http_proxy||process.env.HTTP_PROXY;if(r)try{return new i(r)}catch{if(!r.startsWith(`http://`)&&!r.startsWith(`https://`))return new i(`http://${r}`)}else return}function n(e){if(!e.hostname)return!1;let t=e.hostname;if(r(t))return!0;let n=process.env.no_proxy||process.env.NO_PROXY||``;if(!n)return!1;let i;e.port?i=Number(e.port):e.protocol===`http:`?i=80:e.protocol===`https:`&&(i=443);let a=[e.hostname.toUpperCase()];typeof i==`number`&&a.push(`${a[0]}:${i}`);for(let e of n.split(`,`).map(e=>e.trim().toUpperCase()).filter(e=>e))if(e===`*`||a.some(t=>t===e||t.endsWith(`.${e}`)||e.startsWith(`.`)&&t.endsWith(`${e}`)))return!0;return!1}function r(e){let t=e.toLowerCase();return t===`localhost`||t.startsWith(`127.`)||t.startsWith(`[::1]`)||t.startsWith(`[0:0:0:0:0:0:0:1]`)}var i=class extends URL{constructor(e,t){super(e,t),this._decodedUsername=decodeURIComponent(super.username),this._decodedPassword=decodeURIComponent(super.password)}get username(){return this._decodedUsername}get password(){return this._decodedPassword}}})),ir=C(S((e=>{var t=e&&e.__createBinding||(Object.create?(function(e,t,n,r){r===void 0&&(r=n);var i=Object.getOwnPropertyDescriptor(t,n);(!i||(`get`in i?!t.__esModule:i.writable||i.configurable))&&(i={enumerable:!0,get:function(){return t[n]}}),Object.defineProperty(e,r,i)}):(function(e,t,n,r){r===void 0&&(r=n),e[r]=t[n]})),n=e&&e.__setModuleDefault||(Object.create?(function(e,t){Object.defineProperty(e,`default`,{enumerable:!0,value:t})}):function(e,t){e.default=t}),r=e&&e.__importStar||(function(){var e=function(t){return e=Object.getOwnPropertyNames||function(e){var t=[];for(var n in e)Object.prototype.hasOwnProperty.call(e,n)&&(t[t.length]=n);return t},e(t)};return function(r){if(r&&r.__esModule)return r;var i={};if(r!=null)for(var a=e(r),o=0;oi(this,void 0,void 0,function*(){let t=Buffer.alloc(0);this.message.on(`data`,e=>{t=Buffer.concat([t,e])}),this.message.on(`end`,()=>{e(t.toString())})}))})}readBodyBuffer(){return i(this,void 0,void 0,function*(){return new Promise(e=>i(this,void 0,void 0,function*(){let t=[];this.message.on(`data`,e=>{t.push(e)}),this.message.on(`end`,()=>{e(Buffer.concat(t))})}))})}};e.HttpClientResponse=y;function b(e){return new URL(e).protocol===`https:`}e.HttpClient=class{constructor(e,t,n){this._ignoreSslError=!1,this._allowRedirects=!0,this._allowRedirectDowngrade=!1,this._maxRedirects=50,this._allowRetries=!1,this._maxRetries=1,this._keepAlive=!1,this._disposed=!1,this.userAgent=this._getUserAgentWithOrchestrationId(e),this.handlers=t||[],this.requestOptions=n,n&&(n.ignoreSslError!=null&&(this._ignoreSslError=n.ignoreSslError),this._socketTimeout=n.socketTimeout,n.allowRedirects!=null&&(this._allowRedirects=n.allowRedirects),n.allowRedirectDowngrade!=null&&(this._allowRedirectDowngrade=n.allowRedirectDowngrade),n.maxRedirects!=null&&(this._maxRedirects=Math.max(n.maxRedirects,0)),n.keepAlive!=null&&(this._keepAlive=n.keepAlive),n.allowRetries!=null&&(this._allowRetries=n.allowRetries),n.maxRetries!=null&&(this._maxRetries=n.maxRetries))}options(e,t){return i(this,void 0,void 0,function*(){return this.request(`OPTIONS`,e,null,t||{})})}get(e,t){return i(this,void 0,void 0,function*(){return this.request(`GET`,e,null,t||{})})}del(e,t){return i(this,void 0,void 0,function*(){return this.request(`DELETE`,e,null,t||{})})}post(e,t,n){return i(this,void 0,void 0,function*(){return this.request(`POST`,e,t,n||{})})}patch(e,t,n){return i(this,void 0,void 0,function*(){return this.request(`PATCH`,e,t,n||{})})}put(e,t,n){return i(this,void 0,void 0,function*(){return this.request(`PUT`,e,t,n||{})})}head(e,t){return i(this,void 0,void 0,function*(){return this.request(`HEAD`,e,null,t||{})})}sendStream(e,t,n,r){return i(this,void 0,void 0,function*(){return this.request(e,t,n,r)})}getJson(e){return i(this,arguments,void 0,function*(e,t={}){t[f.Accept]=this._getExistingOrDefaultHeader(t,f.Accept,p.ApplicationJson);let n=yield this.get(e,t);return this._processResponse(n,this.requestOptions)})}postJson(e,t){return i(this,arguments,void 0,function*(e,t,n={}){let r=JSON.stringify(t,null,2);n[f.Accept]=this._getExistingOrDefaultHeader(n,f.Accept,p.ApplicationJson),n[f.ContentType]=this._getExistingOrDefaultContentTypeHeader(n,p.ApplicationJson);let i=yield this.post(e,r,n);return this._processResponse(i,this.requestOptions)})}putJson(e,t){return i(this,arguments,void 0,function*(e,t,n={}){let r=JSON.stringify(t,null,2);n[f.Accept]=this._getExistingOrDefaultHeader(n,f.Accept,p.ApplicationJson),n[f.ContentType]=this._getExistingOrDefaultContentTypeHeader(n,p.ApplicationJson);let i=yield this.put(e,r,n);return this._processResponse(i,this.requestOptions)})}patchJson(e,t){return i(this,arguments,void 0,function*(e,t,n={}){let r=JSON.stringify(t,null,2);n[f.Accept]=this._getExistingOrDefaultHeader(n,f.Accept,p.ApplicationJson),n[f.ContentType]=this._getExistingOrDefaultContentTypeHeader(n,p.ApplicationJson);let i=yield this.patch(e,r,n);return this._processResponse(i,this.requestOptions)})}request(e,t,n,r){return i(this,void 0,void 0,function*(){if(this._disposed)throw Error(`Client has already been disposed.`);let i=new URL(t),a=this._prepareRequest(e,i,r),o=this._allowRetries&&_.includes(e)?this._maxRetries+1:1,s=0,c;do{if(c=yield this.requestRaw(a,n),c&&c.message&&c.message.statusCode===d.Unauthorized){let e;for(let t of this.handlers)if(t.canHandleAuthentication(c)){e=t;break}return e?e.handleAuthentication(this,a,n):c}let t=this._maxRedirects;for(;c.message.statusCode&&h.includes(c.message.statusCode)&&this._allowRedirects&&t>0;){let o=c.message.headers.location;if(!o)break;let s=new URL(o);if(i.protocol===`https:`&&i.protocol!==s.protocol&&!this._allowRedirectDowngrade)throw Error(`Redirect from HTTPS to HTTP protocol. This downgrade is not allowed for security reasons. If you want to allow this behavior, set the allowRedirectDowngrade option to true.`);if(yield c.readBody(),s.hostname!==i.hostname)for(let e in r)e.toLowerCase()===`authorization`&&delete r[e];a=this._prepareRequest(e,s,r),c=yield this.requestRaw(a,n),t--}if(!c.message.statusCode||!g.includes(c.message.statusCode))return c;s+=1,s{function i(e,t){e?r(e):t?n(t):r(Error(`Unknown error`))}this.requestRawWithCallback(e,t,i)})})}requestRawWithCallback(e,t,n){typeof t==`string`&&(e.options.headers||(e.options.headers={}),e.options.headers[`Content-Length`]=Buffer.byteLength(t,`utf8`));let r=!1;function i(e,t){r||(r=!0,n(e,t))}let a=e.httpModule.request(e.options,e=>{i(void 0,new y(e))}),o;a.on(`socket`,e=>{o=e}),a.setTimeout(this._socketTimeout||3*6e4,()=>{o&&o.end(),i(Error(`Request timeout: ${e.options.path}`))}),a.on(`error`,function(e){i(e)}),t&&typeof t==`string`&&a.write(t,`utf8`),t&&typeof t!=`string`?(t.on(`close`,function(){a.end()}),t.pipe(a)):a.end()}getAgent(e){let t=new URL(e);return this._getAgent(t)}getAgentDispatcher(e){let t=new URL(e),n=s.getProxyUrl(t);if(n&&n.hostname)return this._getProxyAgentDispatcher(t,n)}_prepareRequest(e,t,n){let r={};r.parsedUrl=t;let i=r.parsedUrl.protocol===`https:`;r.httpModule=i?o:a;let s=i?443:80;if(r.options={},r.options.host=r.parsedUrl.hostname,r.options.port=r.parsedUrl.port?parseInt(r.parsedUrl.port):s,r.options.path=(r.parsedUrl.pathname||``)+(r.parsedUrl.search||``),r.options.method=e,r.options.headers=this._mergeHeaders(n),this.userAgent!=null&&(r.options.headers[`user-agent`]=this.userAgent),r.options.agent=this._getAgent(r.parsedUrl),this.handlers)for(let e of this.handlers)e.prepareRequest(r.options);return r}_mergeHeaders(e){return this.requestOptions&&this.requestOptions.headers?Object.assign({},S(this.requestOptions.headers),S(e||{})):S(e||{})}_getExistingOrDefaultHeader(e,t,n){let r;if(this.requestOptions&&this.requestOptions.headers){let e=S(this.requestOptions.headers)[t];e&&(r=typeof e==`number`?e.toString():e)}let i=e[t];return i===void 0?r===void 0?n:r:typeof i==`number`?i.toString():i}_getExistingOrDefaultContentTypeHeader(e,t){let n;if(this.requestOptions&&this.requestOptions.headers){let e=S(this.requestOptions.headers)[f.ContentType];e&&(n=typeof e==`number`?String(e):Array.isArray(e)?e.join(`, `):e)}let r=e[f.ContentType];return r===void 0?n===void 0?t:n:typeof r==`number`?String(r):Array.isArray(r)?r.join(`, `):r}_getAgent(e){let t,n=s.getProxyUrl(e),r=n&&n.hostname;if(this._keepAlive&&r&&(t=this._proxyAgent),r||(t=this._agent),t)return t;let i=e.protocol===`https:`,l=100;if(this.requestOptions&&(l=this.requestOptions.maxSockets||a.globalAgent.maxSockets),n&&n.hostname){let e={maxSockets:l,keepAlive:this._keepAlive,proxy:Object.assign(Object.assign({},(n.username||n.password)&&{proxyAuth:`${n.username}:${n.password}`}),{host:n.hostname,port:n.port})},r,a=n.protocol===`https:`;r=i?a?c.httpsOverHttps:c.httpsOverHttp:a?c.httpOverHttps:c.httpOverHttp,t=r(e),this._proxyAgent=t}if(!t){let e={keepAlive:this._keepAlive,maxSockets:l};t=i?new o.Agent(e):new a.Agent(e),this._agent=t}return i&&this._ignoreSslError&&(t.options=Object.assign(t.options||{},{rejectUnauthorized:!1})),t}_getProxyAgentDispatcher(e,t){let n;if(this._keepAlive&&(n=this._proxyAgentDispatcher),n)return n;let r=e.protocol===`https:`;return n=new u.ProxyAgent(Object.assign({uri:t.href,pipelining:this._keepAlive?1:0},(t.username||t.password)&&{token:`Basic ${Buffer.from(`${t.username}:${t.password}`).toString(`base64`)}`})),this._proxyAgentDispatcher=n,r&&this._ignoreSslError&&(n.options=Object.assign(n.options.requestTls||{},{rejectUnauthorized:!1})),n}_getUserAgentWithOrchestrationId(e){let t=e||`actions/http-client`,n=process.env.ACTIONS_ORCHESTRATION_ID;return n?`${t} actions_orchestration_id/${n.replace(/[^a-z0-9_.-]/gi,`_`)}`:t}_performExponentialBackoff(e){return i(this,void 0,void 0,function*(){e=Math.min(10,e);let t=5*2**e;return new Promise(e=>setTimeout(()=>e(),t))})}_processResponse(e,t){return i(this,void 0,void 0,function*(){return new Promise((n,r)=>i(this,void 0,void 0,function*(){let i=e.message.statusCode||0,a={statusCode:i,result:null,headers:{}};i===d.NotFound&&n(a);function o(e,t){if(typeof t==`string`){let e=new Date(t);if(!isNaN(e.valueOf()))return e}return t}let s,c;try{c=yield e.readBody(),c&&c.length>0&&(s=t&&t.deserializeDates?JSON.parse(c,o):JSON.parse(c),a.result=s),a.headers=e.message.headers}catch{}if(i>299){let e;e=s&&s.message?s.message:c&&c.length>0?c:`Failed request: (${i})`;let t=new v(e,i);t.result=a.result,r(t)}else n(a)}))})}};let S=e=>Object.keys(e).reduce((t,n)=>(t[n.toLowerCase()]=e[n],t),{})}))(),1),ar=ce(),or=function(e,t,n,r){function i(e){return e instanceof n?e:new n(function(t){t(e)})}return new(n||=Promise)(function(n,a){function o(e){try{c(r.next(e))}catch(e){a(e)}}function s(e){try{c(r.throw(e))}catch(e){a(e)}}function c(e){e.done?n(e.value):i(e.value).then(o,s)}c((r=r.apply(e,t||[])).next())})};function sr(e,t){if(!e&&!t.auth)throw Error(`Parameter token or opts.auth is required`);if(e&&t.auth)throw Error(`Parameters token and opts.auth may not both be specified`);return typeof t.auth==`string`?t.auth:`token ${e}`}function cr(e){return new ir.HttpClient().getAgent(e)}function lr(e){return new ir.HttpClient().getAgentDispatcher(e)}function ur(e){let t=lr(e);return(e,n)=>or(this,void 0,void 0,function*(){return(0,ar.fetch)(e,Object.assign(Object.assign({},n),{dispatcher:t}))})}function dr(){return process.env.GITHUB_API_URL||`https://api.github.com`}function fr(){return typeof navigator==`object`&&`userAgent`in navigator?navigator.userAgent:typeof process==`object`&&process.version!==void 0?`Node.js/${process.version.substr(1)} (${process.platform}; ${process.arch})`:``}function pr(e,t,n,r){if(typeof n!=`function`)throw Error(`method for before hook must be a function`);return r||={},Array.isArray(t)?t.reverse().reduce((t,n)=>pr.bind(null,e,n,t,r),n)():Promise.resolve().then(()=>e.registry[t]?e.registry[t].reduce((e,t)=>t.hook.bind(null,e,r),n)():n(r))}function mr(e,t,n,r){let i=r;e.registry[n]||(e.registry[n]=[]),t===`before`&&(r=(e,t)=>Promise.resolve().then(i.bind(null,t)).then(e.bind(null,t))),t===`after`&&(r=(e,t)=>{let n;return Promise.resolve().then(e.bind(null,t)).then(e=>(n=e,i(n,t))).then(()=>n)}),t===`error`&&(r=(e,t)=>Promise.resolve().then(e.bind(null,t)).catch(e=>i(e,t))),e.registry[n].push({hook:r,orig:i})}function hr(e,t,n){if(!e.registry[t])return;let r=e.registry[t].map(e=>e.orig).indexOf(n);r!==-1&&e.registry[t].splice(r,1)}const gr=Function.bind,_r=gr.bind(gr);function vr(e,t,n){let r=_r(hr,null).apply(null,n?[t,n]:[t]);e.api={remove:r},e.remove=r,[`before`,`error`,`after`,`wrap`].forEach(r=>{let i=n?[t,r,n]:[t,r];e[r]=e.api[r]=_r(mr,null).apply(null,i)})}function yr(){let e=Symbol(`Singular`),t={registry:{}},n=pr.bind(null,t,e);return vr(n,t,e),n}function br(){let e={registry:{}},t=pr.bind(null,e);return vr(t,e),t}var xr={Singular:yr,Collection:br},Sr=`octokit-endpoint.js/0.0.0-development ${fr()}`,Cr={method:`GET`,baseUrl:`https://api.github.com`,headers:{accept:`application/vnd.github.v3+json`,"user-agent":Sr},mediaType:{format:``}};function wr(e){return e?Object.keys(e).reduce((t,n)=>(t[n.toLowerCase()]=e[n],t),{}):{}}function Tr(e){if(typeof e!=`object`||!e||Object.prototype.toString.call(e)!==`[object Object]`)return!1;let t=Object.getPrototypeOf(e);if(t===null)return!0;let n=Object.prototype.hasOwnProperty.call(t,`constructor`)&&t.constructor;return typeof n==`function`&&n instanceof n&&Function.prototype.call(n)===Function.prototype.call(e)}function Er(e,t){let n=Object.assign({},e);return Object.keys(t).forEach(r=>{Tr(t[r])&&r in e?n[r]=Er(e[r],t[r]):Object.assign(n,{[r]:t[r]})}),n}function Dr(e){for(let t in e)e[t]===void 0&&delete e[t];return e}function Or(e,t,n){if(typeof t==`string`){let[e,r]=t.split(` `);n=Object.assign(r?{method:e,url:r}:{url:e},n)}else n=Object.assign({},t);n.headers=wr(n.headers),Dr(n),Dr(n.headers);let r=Er(e||{},n);return n.url===`/graphql`&&(e&&e.mediaType.previews?.length&&(r.mediaType.previews=e.mediaType.previews.filter(e=>!r.mediaType.previews.includes(e)).concat(r.mediaType.previews)),r.mediaType.previews=(r.mediaType.previews||[]).map(e=>e.replace(/-preview/,``))),r}function kr(e,t){let n=/\?/.test(e)?`&`:`?`,r=Object.keys(t);return r.length===0?e:e+n+r.map(e=>e===`q`?`q=`+t.q.split(`+`).map(encodeURIComponent).join(`+`):`${e}=${encodeURIComponent(t[e])}`).join(`&`)}var Ar=/\{[^{}}]+\}/g;function jr(e){return e.replace(/(?:^\W+)|(?:(?e.concat(t),[]):[]}function Nr(e,t){let n={__proto__:null};for(let r of Object.keys(e))t.indexOf(r)===-1&&(n[r]=e[r]);return n}function Pr(e){return e.split(/(%[0-9A-Fa-f]{2})/g).map(function(e){return/%[0-9A-Fa-f]/.test(e)||(e=encodeURI(e).replace(/%5B/g,`[`).replace(/%5D/g,`]`)),e}).join(``)}function U(e){return encodeURIComponent(e).replace(/[!'()*]/g,function(e){return`%`+e.charCodeAt(0).toString(16).toUpperCase()})}function W(e,t,n){return t=e===`+`||e===`#`?Pr(t):U(t),n?U(n)+`=`+t:t}function G(e){return e!=null}function Fr(e){return e===`;`||e===`&`||e===`?`}function Ir(e,t,n,r){var i=e[n],a=[];if(G(i)&&i!==``)if(typeof i==`string`||typeof i==`number`||typeof i==`bigint`||typeof i==`boolean`)i=i.toString(),r&&r!==`*`&&(i=i.substring(0,parseInt(r,10))),a.push(W(t,i,Fr(t)?n:``));else if(r===`*`)Array.isArray(i)?i.filter(G).forEach(function(e){a.push(W(t,e,Fr(t)?n:``))}):Object.keys(i).forEach(function(e){G(i[e])&&a.push(W(t,i[e],e))});else{let e=[];Array.isArray(i)?i.filter(G).forEach(function(n){e.push(W(t,n))}):Object.keys(i).forEach(function(n){G(i[n])&&(e.push(U(n)),e.push(W(t,i[n].toString())))}),Fr(t)?a.push(U(n)+`=`+e.join(`,`)):e.length!==0&&a.push(e.join(`,`))}else t===`;`?G(i)&&a.push(U(n)):i===``&&(t===`&`||t===`?`)?a.push(U(n)+`=`):i===``&&a.push(``);return a}function Lr(e){return{expand:Rr.bind(null,e)}}function Rr(e,t){var n=[`+`,`#`,`.`,`/`,`;`,`?`,`&`];return e=e.replace(/\{([^\{\}]+)\}|([^\{\}]+)/g,function(e,r,i){if(r){let e=``,i=[];if(n.indexOf(r.charAt(0))!==-1&&(e=r.charAt(0),r=r.substr(1)),r.split(/,/g).forEach(function(n){var r=/([^:\*]*)(?::(\d+)|(\*))?/.exec(n);i.push(Ir(t,e,r[1],r[2]||r[3]))}),e&&e!==`+`){var a=`,`;return e===`?`?a=`&`:e!==`#`&&(a=e),(i.length===0?``:e)+i.join(a)}else return i.join(`,`)}else return Pr(i)}),e===`/`?e:e.replace(/\/$/,``)}function zr(e){let t=e.method.toUpperCase(),n=(e.url||`/`).replace(/:([a-z]\w+)/g,`{$1}`),r=Object.assign({},e.headers),i,a=Nr(e,[`method`,`baseUrl`,`url`,`headers`,`request`,`mediaType`]),o=Mr(n);n=Lr(n).expand(a),/^http/.test(n)||(n=e.baseUrl+n);let s=Nr(a,Object.keys(e).filter(e=>o.includes(e)).concat(`baseUrl`));return/application\/octet-stream/i.test(r.accept)||(e.mediaType.format&&(r.accept=r.accept.split(/,/).map(t=>t.replace(/application\/vnd(\.\w+)(\.v3)?(\.\w+)?(\+json)?$/,`application/vnd$1$2.${e.mediaType.format}`)).join(`,`)),n.endsWith(`/graphql`)&&e.mediaType.previews?.length&&(r.accept=(r.accept.match(/(?`application/vnd.github.${t}-preview${e.mediaType.format?`.${e.mediaType.format}`:`+json`}`).join(`,`))),[`GET`,`HEAD`].includes(t)?n=kr(n,s):`data`in s?i=s.data:Object.keys(s).length&&(i=s),!r[`content-type`]&&i!==void 0&&(r[`content-type`]=`application/json; charset=utf-8`),[`PATCH`,`PUT`].includes(t)&&i===void 0&&(i=``),Object.assign({method:t,url:n,headers:r},i===void 0?null:{body:i},e.request?{request:e.request}:null)}function Br(e,t,n){return zr(Or(e,t,n))}function Vr(e,t){let n=Or(e,t),r=Br.bind(null,n);return Object.assign(r,{DEFAULTS:n,defaults:Vr.bind(null,n),merge:Or.bind(null,n),parse:zr})}var Hr=Vr(null,Cr),Ur=S(((e,t)=>{let n=function(){};n.prototype=Object.create(null);let r=/; *([!#$%&'*+.^\w`|~-]+)=("(?:[\v\u0020\u0021\u0023-\u005b\u005d-\u007e\u0080-\u00ff]|\\[\v\u0020-\u00ff])*"|[!#$%&'*+.^\w`|~-]+) */gu,i=/\\([\v\u0020-\u00ff])/gu,a=/^[!#$%&'*+.^\w|~-]+\/[!#$%&'*+.^\w|~-]+$/u,o={type:``,parameters:new n};Object.freeze(o.parameters),Object.freeze(o);function s(e){if(typeof e!=`string`)throw TypeError(`argument header is required and must be a string`);let t=e.indexOf(`;`),o=t===-1?e.trim():e.slice(0,t).trim();if(a.test(o)===!1)throw TypeError(`invalid media type`);let s={type:o.toLowerCase(),parameters:new n};if(t===-1)return s;let c,l,u;for(r.lastIndex=t;l=r.exec(e);){if(l.index!==t)throw TypeError(`invalid parameter format`);t+=l[0].length,c=l[1].toLowerCase(),u=l[2],u[0]===`"`&&(u=u.slice(1,u.length-1),i.test(u)&&(u=u.replace(i,`$1`))),s.parameters[c]=u}if(t!==e.length)throw TypeError(`invalid parameter format`);return s}function c(e){if(typeof e!=`string`)return o;let t=e.indexOf(`;`),s=t===-1?e.trim():e.slice(0,t).trim();if(a.test(s)===!1)return o;let c={type:s.toLowerCase(),parameters:new n};if(t===-1)return c;let l,u,d;for(r.lastIndex=t;u=r.exec(e);){if(u.index!==t)return o;t+=u[0].length,l=u[1].toLowerCase(),d=u[2],d[0]===`"`&&(d=d.slice(1,d.length-1),i.test(d)&&(d=d.replace(i,`$1`))),c.parameters[l]=d}return t===e.length?c:o}t.exports.default={parse:s,safeParse:c},t.exports.parse=s,t.exports.safeParse=c,t.exports.defaultContentType=o}))();const Wr=/^-?\d+$/,Gr=/^-?\d+n+$/,Kr=JSON.stringify,qr=JSON.parse,Jr=/^-?\d+n$/,Yr=/([\[:])?"(-?\d+)n"($|([\\n]|\s)*(\s|[\\n])*[,\}\]])/g,Xr=/([\[:])?("-?\d+n+)n("$|"([\\n]|\s)*(\s|[\\n])*[,\}\]])/g,Zr=(e,t,n)=>`rawJSON`in JSON?Kr(e,(e,n)=>typeof n==`bigint`?JSON.rawJSON(n.toString()):typeof t==`function`?t(e,n):(Array.isArray(t)&&t.includes(e),n),n):e?Kr(e,(e,n)=>typeof n==`string`&&n.match(Gr)||typeof n==`bigint`?n.toString()+`n`:typeof t==`function`?t(e,n):(Array.isArray(t)&&t.includes(e),n),n).replace(Yr,`$1$2$3`).replace(Xr,`$1$2$3`):Kr(e,t,n),Qr=()=>JSON.parse(`1`,(e,t,n)=>!!n&&n.source===`1`),$r=(e,t,n,r)=>typeof t==`string`&&t.match(Jr)?BigInt(t.slice(0,-1)):typeof t==`string`&&t.match(Gr)?t.slice(0,-1):typeof r==`function`?r(e,t,n):t,ei=(e,t)=>JSON.parse(e,(e,n,r)=>{let i=typeof n==`number`&&(n>2**53-1||n<-(2**53-1)),a=r&&Wr.test(r.source);return i&&a?BigInt(r.source):typeof t==`function`?t(e,n,r):n}),ti=(2**53-1).toString(),ni=ti.length,ri=/"(?:\\.|[^"])*"|-?(0|[1-9][0-9]*)(\.[0-9]+)?([eE][+-]?[0-9]+)?/g,ii=/^"-?\d+n+"$/,ai=(e,t)=>e?Qr()?ei(e,t):qr(e.replace(ri,(e,t,n,r)=>{let i=e[0]===`"`;if(i&&e.match(ii))return e.substring(0,e.length-1)+`n"`;let a=n||r,o=t&&(t.length$r(e,n,r,t)):qr(e,t);var oi=class extends Error{name;status;request;response;constructor(e,t,n){super(e,{cause:n.cause}),this.name=`HttpError`,this.status=Number.parseInt(t),Number.isNaN(this.status)&&(this.status=0),`response`in n&&(this.response=n.response);let r=Object.assign({},n.request);n.request.headers.authorization&&(r.headers=Object.assign({},n.request.headers,{authorization:n.request.headers.authorization.replace(/(?``;async function di(e){let t=e.request?.fetch||globalThis.fetch;if(!t)throw Error(`fetch is not set. Please pass a fetch implementation as new Octokit({ request: { fetch }}). Learn more at https://github.com/octokit/octokit.js/#fetch-missing`);let n=e.request?.log||console,r=e.request?.parseSuccessResponseBody!==!1,i=li(e.body)||Array.isArray(e.body)?Zr(e.body):e.body,a=Object.fromEntries(Object.entries(e.headers).map(([e,t])=>[e,String(t)])),o;try{o=await t(e.url,{method:e.method,body:i,redirect:e.request?.redirect,headers:a,signal:e.request?.signal,...e.body&&{duplex:`half`}})}catch(t){let n=`Unknown Error`;if(t instanceof Error){if(t.name===`AbortError`)throw t.status=500,t;n=t.message,t.name===`TypeError`&&`cause`in t&&(t.cause instanceof Error?n=t.cause.message:typeof t.cause==`string`&&(n=t.cause))}let r=new oi(n,500,{request:e});throw r.cause=t,r}let s=o.status,c=o.url,l={};for(let[e,t]of o.headers)l[e]=t;let u={url:c,status:s,headers:l,data:``};if(`deprecation`in l){let t=l.link&&l.link.match(/<([^<>]+)>; rel="deprecation"/),r=t&&t.pop();n.warn(`[@octokit/request] "${e.method} ${e.url}" is deprecated. It is scheduled to be removed on ${l.sunset}${r?`. See ${r}`:``}`)}if(s===204||s===205)return u;if(e.method===`HEAD`){if(s<400)return u;throw new oi(o.statusText,s,{response:u,request:e})}if(s===304)throw u.data=await fi(o),new oi(`Not modified`,s,{response:u,request:e});if(s>=400)throw u.data=await fi(o),new oi(mi(u.data),s,{response:u,request:e});return u.data=r?await fi(o):o.body,u}async function fi(e){let t=e.headers.get(`content-type`);if(!t)return e.text().catch(ui);let n=(0,Ur.safeParse)(t);if(pi(n)){let t=``;try{return t=await e.text(),ai(t)}catch{return t}}else if(n.type.startsWith(`text/`)||n.parameters.charset?.toLowerCase()===`utf-8`)return e.text().catch(ui);else return e.arrayBuffer().catch(()=>new ArrayBuffer(0))}function pi(e){return e.type===`application/json`||e.type===`application/scim+json`}function mi(e){if(typeof e==`string`)return e;if(e instanceof ArrayBuffer)return`Unknown error`;if(`message`in e){let t=`documentation_url`in e?` - ${e.documentation_url}`:``;return Array.isArray(e.errors)?`${e.message}: ${e.errors.map(e=>JSON.stringify(e)).join(`, `)}${t}`:`${e.message}${t}`}return`Unknown error: ${JSON.stringify(e)}`}function hi(e,t){let n=e.defaults(t);return Object.assign(function(e,t){let r=n.merge(e,t);if(!r.request||!r.request.hook)return di(n.parse(r));let i=(e,t)=>di(n.parse(n.merge(e,t)));return Object.assign(i,{endpoint:n,defaults:hi.bind(null,n)}),r.request.hook(i,r)},{endpoint:n,defaults:hi.bind(null,n)})}var gi=hi(Hr,ci),_i=`0.0.0-development`;function vi(e){return`Request failed due to following response errors: +If you had completed the task, confirm the completion.`,d=i===1?e.fileParts:void 0,g=await zn(r,c,o,d,f,n,t);if(g.success){if(p=g.eventStreamResult,n?.sessionTitle!=null)try{await r.session.update({sessionID:c,title:n.sessionTitle})}catch{t.debug(`Best-effort session title re-assertion failed`,{sessionId:c})}return{success:!0,exitCode:0,duration:Date.now()-a,sessionId:c,error:null,tokenUsage:p.tokens,model:p.model,cost:p.cost,prsCreated:p.prsCreated,commitsCreated:p.commitsCreated,commentsPosted:p.commentsPosted,llmError:null}}if(m=g.error,h=g.llmError,!g.shouldRetry||i>=3)break;t.warning(`LLM fetch error detected, retrying with continuation prompt`,{attempt:i,maxAttempts:3,error:g.error,delayMs:In,sessionId:c}),await pn(In)}return{success:!1,exitCode:1,duration:Date.now()-a,sessionId:c,error:m??`Unknown error`,tokenUsage:p.tokens,model:p.model,cost:p.cost,prsCreated:p.prsCreated,commitsCreated:p.commitsCreated,commentsPosted:p.commentsPosted,llmError:h}}catch(e){let n=Date.now()-a,i=r(e);return t.error(`OpenCode execution failed`,{error:i,durationMs:n}),{success:!1,exitCode:1,duration:n,sessionId:null,error:i,tokenUsage:null,model:null,cost:null,prsCreated:[],commitsCreated:[],commentsPosted:0,llmError:yn(e)?bn(i):null}}finally{c!=null&&clearTimeout(c),o.abort(),u&&d?.close()}}async function tr(e,t,n){return t.commentId==null?(n.debug(`No comment ID, skipping eyes reaction`),!1):await Ve(e,t.repo,t.commentId,`eyes`,n)==null?!1:(n.info(`Added eyes reaction`,{commentId:t.commentId}),!0)}async function nr(e,t,n){return t.issueNumber==null?(n.debug(`No issue number, skipping working label`),!1):await We(e,t.repo,`agent: working`,`fcf2e1`,`Agent is currently working on this`,n)&&await Ge(e,t.repo,t.issueNumber,[`agent: working`],n)?(n.info(`Added working label`,{issueNumber:t.issueNumber}),!0):!1}async function rr(e,t,n){await Promise.all([tr(e,t,n),nr(e,t,n)])}async function ir(e,t,n){if(t.commentId==null||t.botLogin==null)return;let r=(await He(e,t.repo,t.commentId,n)).find(e=>e.content===`eyes`&&e.userLogin===t.botLogin);r!=null&&await Ue(e,t.repo,t.commentId,r.id,n)}async function ar(e,t,n,r){t.commentId!=null&&await Ve(e,t.repo,t.commentId,n,r)}async function or(e,t,n){if(t.commentId==null||t.botLogin==null){n.debug(`Missing comment ID or bot login, skipping reaction update`);return}try{await ir(e,t,n),await ar(e,t,`hooray`,n),n.info(`Updated reaction to success indicator`,{commentId:t.commentId,reaction:`hooray`})}catch(e){n.warning(`Failed to update reaction (non-fatal)`,{error:r(e)})}}async function sr(e,t,n){if(t.commentId==null||t.botLogin==null){n.debug(`Missing comment ID or bot login, skipping reaction update`);return}try{await ir(e,t,n),await ar(e,t,`confused`,n),n.info(`Updated reaction to confused`,{commentId:t.commentId})}catch(e){n.warning(`Failed to update failure reaction (non-fatal)`,{error:r(e)})}}async function cr(e,t,n){if(t.issueNumber==null){n.debug(`No issue number, skipping label removal`);return}await Ke(e,t.repo,t.issueNumber,`agent: working`,n)&&n.info(`Removed working label`,{issueNumber:t.issueNumber})}async function lr(e,t,n,r){n?await or(e,t,r):await sr(e,t,r),await cr(e,t,r)}var ur=class{constructor(){if(this.payload={},process.env.GITHUB_EVENT_PATH)if(be(process.env.GITHUB_EVENT_PATH))this.payload=JSON.parse(xe(process.env.GITHUB_EVENT_PATH,{encoding:`utf8`}));else{let e=process.env.GITHUB_EVENT_PATH;process.stdout.write(`GITHUB_EVENT_PATH ${e} does not exist${ve}`)}this.eventName=process.env.GITHUB_EVENT_NAME,this.sha=process.env.GITHUB_SHA,this.ref=process.env.GITHUB_REF,this.workflow=process.env.GITHUB_WORKFLOW,this.action=process.env.GITHUB_ACTION,this.actor=process.env.GITHUB_ACTOR,this.job=process.env.GITHUB_JOB,this.runAttempt=parseInt(process.env.GITHUB_RUN_ATTEMPT,10),this.runNumber=parseInt(process.env.GITHUB_RUN_NUMBER,10),this.runId=parseInt(process.env.GITHUB_RUN_ID,10),this.apiUrl=process.env.GITHUB_API_URL??`https://api.github.com`,this.serverUrl=process.env.GITHUB_SERVER_URL??`https://github.com`,this.graphqlUrl=process.env.GITHUB_GRAPHQL_URL??`https://api.github.com/graphql`}get issue(){let e=this.payload;return Object.assign(Object.assign({},this.repo),{number:(e.issue||e.pull_request||e).number})}get repo(){if(process.env.GITHUB_REPOSITORY){let[e,t]=process.env.GITHUB_REPOSITORY.split(`/`);return{owner:e,repo:t}}if(this.payload.repository)return{owner:this.payload.repository.owner.login,repo:this.payload.repository.name};throw Error(`context.repo requires a GITHUB_REPOSITORY environment variable like 'owner/repo'`)}},dr=S((e=>{Object.defineProperty(e,`__esModule`,{value:!0}),e.getProxyUrl=t,e.checkBypass=n;function t(e){let t=e.protocol===`https:`;if(n(e))return;let r=t?process.env.https_proxy||process.env.HTTPS_PROXY:process.env.http_proxy||process.env.HTTP_PROXY;if(r)try{return new i(r)}catch{if(!r.startsWith(`http://`)&&!r.startsWith(`https://`))return new i(`http://${r}`)}else return}function n(e){if(!e.hostname)return!1;let t=e.hostname;if(r(t))return!0;let n=process.env.no_proxy||process.env.NO_PROXY||``;if(!n)return!1;let i;e.port?i=Number(e.port):e.protocol===`http:`?i=80:e.protocol===`https:`&&(i=443);let a=[e.hostname.toUpperCase()];typeof i==`number`&&a.push(`${a[0]}:${i}`);for(let e of n.split(`,`).map(e=>e.trim().toUpperCase()).filter(e=>e))if(e===`*`||a.some(t=>t===e||t.endsWith(`.${e}`)||e.startsWith(`.`)&&t.endsWith(`${e}`)))return!0;return!1}function r(e){let t=e.toLowerCase();return t===`localhost`||t.startsWith(`127.`)||t.startsWith(`[::1]`)||t.startsWith(`[0:0:0:0:0:0:0:1]`)}var i=class extends URL{constructor(e,t){super(e,t),this._decodedUsername=decodeURIComponent(super.username),this._decodedPassword=decodeURIComponent(super.password)}get username(){return this._decodedUsername}get password(){return this._decodedPassword}}})),fr=C(S((e=>{var t=e&&e.__createBinding||(Object.create?(function(e,t,n,r){r===void 0&&(r=n);var i=Object.getOwnPropertyDescriptor(t,n);(!i||(`get`in i?!t.__esModule:i.writable||i.configurable))&&(i={enumerable:!0,get:function(){return t[n]}}),Object.defineProperty(e,r,i)}):(function(e,t,n,r){r===void 0&&(r=n),e[r]=t[n]})),n=e&&e.__setModuleDefault||(Object.create?(function(e,t){Object.defineProperty(e,`default`,{enumerable:!0,value:t})}):function(e,t){e.default=t}),r=e&&e.__importStar||(function(){var e=function(t){return e=Object.getOwnPropertyNames||function(e){var t=[];for(var n in e)Object.prototype.hasOwnProperty.call(e,n)&&(t[t.length]=n);return t},e(t)};return function(r){if(r&&r.__esModule)return r;var i={};if(r!=null)for(var a=e(r),o=0;oi(this,void 0,void 0,function*(){let t=Buffer.alloc(0);this.message.on(`data`,e=>{t=Buffer.concat([t,e])}),this.message.on(`end`,()=>{e(t.toString())})}))})}readBodyBuffer(){return i(this,void 0,void 0,function*(){return new Promise(e=>i(this,void 0,void 0,function*(){let t=[];this.message.on(`data`,e=>{t.push(e)}),this.message.on(`end`,()=>{e(Buffer.concat(t))})}))})}};e.HttpClientResponse=y;function b(e){return new URL(e).protocol===`https:`}e.HttpClient=class{constructor(e,t,n){this._ignoreSslError=!1,this._allowRedirects=!0,this._allowRedirectDowngrade=!1,this._maxRedirects=50,this._allowRetries=!1,this._maxRetries=1,this._keepAlive=!1,this._disposed=!1,this.userAgent=this._getUserAgentWithOrchestrationId(e),this.handlers=t||[],this.requestOptions=n,n&&(n.ignoreSslError!=null&&(this._ignoreSslError=n.ignoreSslError),this._socketTimeout=n.socketTimeout,n.allowRedirects!=null&&(this._allowRedirects=n.allowRedirects),n.allowRedirectDowngrade!=null&&(this._allowRedirectDowngrade=n.allowRedirectDowngrade),n.maxRedirects!=null&&(this._maxRedirects=Math.max(n.maxRedirects,0)),n.keepAlive!=null&&(this._keepAlive=n.keepAlive),n.allowRetries!=null&&(this._allowRetries=n.allowRetries),n.maxRetries!=null&&(this._maxRetries=n.maxRetries))}options(e,t){return i(this,void 0,void 0,function*(){return this.request(`OPTIONS`,e,null,t||{})})}get(e,t){return i(this,void 0,void 0,function*(){return this.request(`GET`,e,null,t||{})})}del(e,t){return i(this,void 0,void 0,function*(){return this.request(`DELETE`,e,null,t||{})})}post(e,t,n){return i(this,void 0,void 0,function*(){return this.request(`POST`,e,t,n||{})})}patch(e,t,n){return i(this,void 0,void 0,function*(){return this.request(`PATCH`,e,t,n||{})})}put(e,t,n){return i(this,void 0,void 0,function*(){return this.request(`PUT`,e,t,n||{})})}head(e,t){return i(this,void 0,void 0,function*(){return this.request(`HEAD`,e,null,t||{})})}sendStream(e,t,n,r){return i(this,void 0,void 0,function*(){return this.request(e,t,n,r)})}getJson(e){return i(this,arguments,void 0,function*(e,t={}){t[f.Accept]=this._getExistingOrDefaultHeader(t,f.Accept,p.ApplicationJson);let n=yield this.get(e,t);return this._processResponse(n,this.requestOptions)})}postJson(e,t){return i(this,arguments,void 0,function*(e,t,n={}){let r=JSON.stringify(t,null,2);n[f.Accept]=this._getExistingOrDefaultHeader(n,f.Accept,p.ApplicationJson),n[f.ContentType]=this._getExistingOrDefaultContentTypeHeader(n,p.ApplicationJson);let i=yield this.post(e,r,n);return this._processResponse(i,this.requestOptions)})}putJson(e,t){return i(this,arguments,void 0,function*(e,t,n={}){let r=JSON.stringify(t,null,2);n[f.Accept]=this._getExistingOrDefaultHeader(n,f.Accept,p.ApplicationJson),n[f.ContentType]=this._getExistingOrDefaultContentTypeHeader(n,p.ApplicationJson);let i=yield this.put(e,r,n);return this._processResponse(i,this.requestOptions)})}patchJson(e,t){return i(this,arguments,void 0,function*(e,t,n={}){let r=JSON.stringify(t,null,2);n[f.Accept]=this._getExistingOrDefaultHeader(n,f.Accept,p.ApplicationJson),n[f.ContentType]=this._getExistingOrDefaultContentTypeHeader(n,p.ApplicationJson);let i=yield this.patch(e,r,n);return this._processResponse(i,this.requestOptions)})}request(e,t,n,r){return i(this,void 0,void 0,function*(){if(this._disposed)throw Error(`Client has already been disposed.`);let i=new URL(t),a=this._prepareRequest(e,i,r),o=this._allowRetries&&_.includes(e)?this._maxRetries+1:1,s=0,c;do{if(c=yield this.requestRaw(a,n),c&&c.message&&c.message.statusCode===d.Unauthorized){let e;for(let t of this.handlers)if(t.canHandleAuthentication(c)){e=t;break}return e?e.handleAuthentication(this,a,n):c}let t=this._maxRedirects;for(;c.message.statusCode&&h.includes(c.message.statusCode)&&this._allowRedirects&&t>0;){let o=c.message.headers.location;if(!o)break;let s=new URL(o);if(i.protocol===`https:`&&i.protocol!==s.protocol&&!this._allowRedirectDowngrade)throw Error(`Redirect from HTTPS to HTTP protocol. This downgrade is not allowed for security reasons. If you want to allow this behavior, set the allowRedirectDowngrade option to true.`);if(yield c.readBody(),s.hostname!==i.hostname)for(let e in r)e.toLowerCase()===`authorization`&&delete r[e];a=this._prepareRequest(e,s,r),c=yield this.requestRaw(a,n),t--}if(!c.message.statusCode||!g.includes(c.message.statusCode))return c;s+=1,s{function i(e,t){e?r(e):t?n(t):r(Error(`Unknown error`))}this.requestRawWithCallback(e,t,i)})})}requestRawWithCallback(e,t,n){typeof t==`string`&&(e.options.headers||(e.options.headers={}),e.options.headers[`Content-Length`]=Buffer.byteLength(t,`utf8`));let r=!1;function i(e,t){r||(r=!0,n(e,t))}let a=e.httpModule.request(e.options,e=>{i(void 0,new y(e))}),o;a.on(`socket`,e=>{o=e}),a.setTimeout(this._socketTimeout||3*6e4,()=>{o&&o.end(),i(Error(`Request timeout: ${e.options.path}`))}),a.on(`error`,function(e){i(e)}),t&&typeof t==`string`&&a.write(t,`utf8`),t&&typeof t!=`string`?(t.on(`close`,function(){a.end()}),t.pipe(a)):a.end()}getAgent(e){let t=new URL(e);return this._getAgent(t)}getAgentDispatcher(e){let t=new URL(e),n=s.getProxyUrl(t);if(n&&n.hostname)return this._getProxyAgentDispatcher(t,n)}_prepareRequest(e,t,n){let r={};r.parsedUrl=t;let i=r.parsedUrl.protocol===`https:`;r.httpModule=i?o:a;let s=i?443:80;if(r.options={},r.options.host=r.parsedUrl.hostname,r.options.port=r.parsedUrl.port?parseInt(r.parsedUrl.port):s,r.options.path=(r.parsedUrl.pathname||``)+(r.parsedUrl.search||``),r.options.method=e,r.options.headers=this._mergeHeaders(n),this.userAgent!=null&&(r.options.headers[`user-agent`]=this.userAgent),r.options.agent=this._getAgent(r.parsedUrl),this.handlers)for(let e of this.handlers)e.prepareRequest(r.options);return r}_mergeHeaders(e){return this.requestOptions&&this.requestOptions.headers?Object.assign({},S(this.requestOptions.headers),S(e||{})):S(e||{})}_getExistingOrDefaultHeader(e,t,n){let r;if(this.requestOptions&&this.requestOptions.headers){let e=S(this.requestOptions.headers)[t];e&&(r=typeof e==`number`?e.toString():e)}let i=e[t];return i===void 0?r===void 0?n:r:typeof i==`number`?i.toString():i}_getExistingOrDefaultContentTypeHeader(e,t){let n;if(this.requestOptions&&this.requestOptions.headers){let e=S(this.requestOptions.headers)[f.ContentType];e&&(n=typeof e==`number`?String(e):Array.isArray(e)?e.join(`, `):e)}let r=e[f.ContentType];return r===void 0?n===void 0?t:n:typeof r==`number`?String(r):Array.isArray(r)?r.join(`, `):r}_getAgent(e){let t,n=s.getProxyUrl(e),r=n&&n.hostname;if(this._keepAlive&&r&&(t=this._proxyAgent),r||(t=this._agent),t)return t;let i=e.protocol===`https:`,l=100;if(this.requestOptions&&(l=this.requestOptions.maxSockets||a.globalAgent.maxSockets),n&&n.hostname){let e={maxSockets:l,keepAlive:this._keepAlive,proxy:Object.assign(Object.assign({},(n.username||n.password)&&{proxyAuth:`${n.username}:${n.password}`}),{host:n.hostname,port:n.port})},r,a=n.protocol===`https:`;r=i?a?c.httpsOverHttps:c.httpsOverHttp:a?c.httpOverHttps:c.httpOverHttp,t=r(e),this._proxyAgent=t}if(!t){let e={keepAlive:this._keepAlive,maxSockets:l};t=i?new o.Agent(e):new a.Agent(e),this._agent=t}return i&&this._ignoreSslError&&(t.options=Object.assign(t.options||{},{rejectUnauthorized:!1})),t}_getProxyAgentDispatcher(e,t){let n;if(this._keepAlive&&(n=this._proxyAgentDispatcher),n)return n;let r=e.protocol===`https:`;return n=new u.ProxyAgent(Object.assign({uri:t.href,pipelining:this._keepAlive?1:0},(t.username||t.password)&&{token:`Basic ${Buffer.from(`${t.username}:${t.password}`).toString(`base64`)}`})),this._proxyAgentDispatcher=n,r&&this._ignoreSslError&&(n.options=Object.assign(n.options.requestTls||{},{rejectUnauthorized:!1})),n}_getUserAgentWithOrchestrationId(e){let t=e||`actions/http-client`,n=process.env.ACTIONS_ORCHESTRATION_ID;return n?`${t} actions_orchestration_id/${n.replace(/[^a-z0-9_.-]/gi,`_`)}`:t}_performExponentialBackoff(e){return i(this,void 0,void 0,function*(){e=Math.min(10,e);let t=5*2**e;return new Promise(e=>setTimeout(()=>e(),t))})}_processResponse(e,t){return i(this,void 0,void 0,function*(){return new Promise((n,r)=>i(this,void 0,void 0,function*(){let i=e.message.statusCode||0,a={statusCode:i,result:null,headers:{}};i===d.NotFound&&n(a);function o(e,t){if(typeof t==`string`){let e=new Date(t);if(!isNaN(e.valueOf()))return e}return t}let s,c;try{c=yield e.readBody(),c&&c.length>0&&(s=t&&t.deserializeDates?JSON.parse(c,o):JSON.parse(c),a.result=s),a.headers=e.message.headers}catch{}if(i>299){let e;e=s&&s.message?s.message:c&&c.length>0?c:`Failed request: (${i})`;let t=new v(e,i);t.result=a.result,r(t)}else n(a)}))})}};let S=e=>Object.keys(e).reduce((t,n)=>(t[n.toLowerCase()]=e[n],t),{})}))(),1),pr=ce(),mr=function(e,t,n,r){function i(e){return e instanceof n?e:new n(function(t){t(e)})}return new(n||=Promise)(function(n,a){function o(e){try{c(r.next(e))}catch(e){a(e)}}function s(e){try{c(r.throw(e))}catch(e){a(e)}}function c(e){e.done?n(e.value):i(e.value).then(o,s)}c((r=r.apply(e,t||[])).next())})};function hr(e,t){if(!e&&!t.auth)throw Error(`Parameter token or opts.auth is required`);if(e&&t.auth)throw Error(`Parameters token and opts.auth may not both be specified`);return typeof t.auth==`string`?t.auth:`token ${e}`}function gr(e){return new fr.HttpClient().getAgent(e)}function _r(e){return new fr.HttpClient().getAgentDispatcher(e)}function vr(e){let t=_r(e);return(e,n)=>mr(this,void 0,void 0,function*(){return(0,pr.fetch)(e,Object.assign(Object.assign({},n),{dispatcher:t}))})}function yr(){return process.env.GITHUB_API_URL||`https://api.github.com`}function br(){return typeof navigator==`object`&&`userAgent`in navigator?navigator.userAgent:typeof process==`object`&&process.version!==void 0?`Node.js/${process.version.substr(1)} (${process.platform}; ${process.arch})`:``}function xr(e,t,n,r){if(typeof n!=`function`)throw Error(`method for before hook must be a function`);return r||={},Array.isArray(t)?t.reverse().reduce((t,n)=>xr.bind(null,e,n,t,r),n)():Promise.resolve().then(()=>e.registry[t]?e.registry[t].reduce((e,t)=>t.hook.bind(null,e,r),n)():n(r))}function Sr(e,t,n,r){let i=r;e.registry[n]||(e.registry[n]=[]),t===`before`&&(r=(e,t)=>Promise.resolve().then(i.bind(null,t)).then(e.bind(null,t))),t===`after`&&(r=(e,t)=>{let n;return Promise.resolve().then(e.bind(null,t)).then(e=>(n=e,i(n,t))).then(()=>n)}),t===`error`&&(r=(e,t)=>Promise.resolve().then(e.bind(null,t)).catch(e=>i(e,t))),e.registry[n].push({hook:r,orig:i})}function Cr(e,t,n){if(!e.registry[t])return;let r=e.registry[t].map(e=>e.orig).indexOf(n);r!==-1&&e.registry[t].splice(r,1)}const wr=Function.bind,Tr=wr.bind(wr);function Er(e,t,n){let r=Tr(Cr,null).apply(null,n?[t,n]:[t]);e.api={remove:r},e.remove=r,[`before`,`error`,`after`,`wrap`].forEach(r=>{let i=n?[t,r,n]:[t,r];e[r]=e.api[r]=Tr(Sr,null).apply(null,i)})}function Dr(){let e=Symbol(`Singular`),t={registry:{}},n=xr.bind(null,t,e);return Er(n,t,e),n}function Or(){let e={registry:{}},t=xr.bind(null,e);return Er(t,e),t}var kr={Singular:Dr,Collection:Or},Ar=`octokit-endpoint.js/0.0.0-development ${br()}`,jr={method:`GET`,baseUrl:`https://api.github.com`,headers:{accept:`application/vnd.github.v3+json`,"user-agent":Ar},mediaType:{format:``}};function Mr(e){return e?Object.keys(e).reduce((t,n)=>(t[n.toLowerCase()]=e[n],t),{}):{}}function Nr(e){if(typeof e!=`object`||!e||Object.prototype.toString.call(e)!==`[object Object]`)return!1;let t=Object.getPrototypeOf(e);if(t===null)return!0;let n=Object.prototype.hasOwnProperty.call(t,`constructor`)&&t.constructor;return typeof n==`function`&&n instanceof n&&Function.prototype.call(n)===Function.prototype.call(e)}function Pr(e,t){let n=Object.assign({},e);return Object.keys(t).forEach(r=>{Nr(t[r])&&r in e?n[r]=Pr(e[r],t[r]):Object.assign(n,{[r]:t[r]})}),n}function Fr(e){for(let t in e)e[t]===void 0&&delete e[t];return e}function Ir(e,t,n){if(typeof t==`string`){let[e,r]=t.split(` `);n=Object.assign(r?{method:e,url:r}:{url:e},n)}else n=Object.assign({},t);n.headers=Mr(n.headers),Fr(n),Fr(n.headers);let r=Pr(e||{},n);return n.url===`/graphql`&&(e&&e.mediaType.previews?.length&&(r.mediaType.previews=e.mediaType.previews.filter(e=>!r.mediaType.previews.includes(e)).concat(r.mediaType.previews)),r.mediaType.previews=(r.mediaType.previews||[]).map(e=>e.replace(/-preview/,``))),r}function Lr(e,t){let n=/\?/.test(e)?`&`:`?`,r=Object.keys(t);return r.length===0?e:e+n+r.map(e=>e===`q`?`q=`+t.q.split(`+`).map(encodeURIComponent).join(`+`):`${e}=${encodeURIComponent(t[e])}`).join(`&`)}var Rr=/\{[^{}}]+\}/g;function zr(e){return e.replace(/(?:^\W+)|(?:(?e.concat(t),[]):[]}function Vr(e,t){let n={__proto__:null};for(let r of Object.keys(e))t.indexOf(r)===-1&&(n[r]=e[r]);return n}function Hr(e){return e.split(/(%[0-9A-Fa-f]{2})/g).map(function(e){return/%[0-9A-Fa-f]/.test(e)||(e=encodeURI(e).replace(/%5B/g,`[`).replace(/%5D/g,`]`)),e}).join(``)}function H(e){return encodeURIComponent(e).replace(/[!'()*]/g,function(e){return`%`+e.charCodeAt(0).toString(16).toUpperCase()})}function U(e,t,n){return t=e===`+`||e===`#`?Hr(t):H(t),n?H(n)+`=`+t:t}function W(e){return e!=null}function Ur(e){return e===`;`||e===`&`||e===`?`}function Wr(e,t,n,r){var i=e[n],a=[];if(W(i)&&i!==``)if(typeof i==`string`||typeof i==`number`||typeof i==`bigint`||typeof i==`boolean`)i=i.toString(),r&&r!==`*`&&(i=i.substring(0,parseInt(r,10))),a.push(U(t,i,Ur(t)?n:``));else if(r===`*`)Array.isArray(i)?i.filter(W).forEach(function(e){a.push(U(t,e,Ur(t)?n:``))}):Object.keys(i).forEach(function(e){W(i[e])&&a.push(U(t,i[e],e))});else{let e=[];Array.isArray(i)?i.filter(W).forEach(function(n){e.push(U(t,n))}):Object.keys(i).forEach(function(n){W(i[n])&&(e.push(H(n)),e.push(U(t,i[n].toString())))}),Ur(t)?a.push(H(n)+`=`+e.join(`,`)):e.length!==0&&a.push(e.join(`,`))}else t===`;`?W(i)&&a.push(H(n)):i===``&&(t===`&`||t===`?`)?a.push(H(n)+`=`):i===``&&a.push(``);return a}function Gr(e){return{expand:Kr.bind(null,e)}}function Kr(e,t){var n=[`+`,`#`,`.`,`/`,`;`,`?`,`&`];return e=e.replace(/\{([^\{\}]+)\}|([^\{\}]+)/g,function(e,r,i){if(r){let e=``,i=[];if(n.indexOf(r.charAt(0))!==-1&&(e=r.charAt(0),r=r.substr(1)),r.split(/,/g).forEach(function(n){var r=/([^:\*]*)(?::(\d+)|(\*))?/.exec(n);i.push(Wr(t,e,r[1],r[2]||r[3]))}),e&&e!==`+`){var a=`,`;return e===`?`?a=`&`:e!==`#`&&(a=e),(i.length===0?``:e)+i.join(a)}else return i.join(`,`)}else return Hr(i)}),e===`/`?e:e.replace(/\/$/,``)}function qr(e){let t=e.method.toUpperCase(),n=(e.url||`/`).replace(/:([a-z]\w+)/g,`{$1}`),r=Object.assign({},e.headers),i,a=Vr(e,[`method`,`baseUrl`,`url`,`headers`,`request`,`mediaType`]),o=Br(n);n=Gr(n).expand(a),/^http/.test(n)||(n=e.baseUrl+n);let s=Vr(a,Object.keys(e).filter(e=>o.includes(e)).concat(`baseUrl`));return/application\/octet-stream/i.test(r.accept)||(e.mediaType.format&&(r.accept=r.accept.split(/,/).map(t=>t.replace(/application\/vnd(\.\w+)(\.v3)?(\.\w+)?(\+json)?$/,`application/vnd$1$2.${e.mediaType.format}`)).join(`,`)),n.endsWith(`/graphql`)&&e.mediaType.previews?.length&&(r.accept=(r.accept.match(/(?`application/vnd.github.${t}-preview${e.mediaType.format?`.${e.mediaType.format}`:`+json`}`).join(`,`))),[`GET`,`HEAD`].includes(t)?n=Lr(n,s):`data`in s?i=s.data:Object.keys(s).length&&(i=s),!r[`content-type`]&&i!==void 0&&(r[`content-type`]=`application/json; charset=utf-8`),[`PATCH`,`PUT`].includes(t)&&i===void 0&&(i=``),Object.assign({method:t,url:n,headers:r},i===void 0?null:{body:i},e.request?{request:e.request}:null)}function Jr(e,t,n){return qr(Ir(e,t,n))}function Yr(e,t){let n=Ir(e,t),r=Jr.bind(null,n);return Object.assign(r,{DEFAULTS:n,defaults:Yr.bind(null,n),merge:Ir.bind(null,n),parse:qr})}var Xr=Yr(null,jr),Zr=S(((e,t)=>{let n=function(){};n.prototype=Object.create(null);let r=/; *([!#$%&'*+.^\w`|~-]+)=("(?:[\v\u0020\u0021\u0023-\u005b\u005d-\u007e\u0080-\u00ff]|\\[\v\u0020-\u00ff])*"|[!#$%&'*+.^\w`|~-]+) */gu,i=/\\([\v\u0020-\u00ff])/gu,a=/^[!#$%&'*+.^\w|~-]+\/[!#$%&'*+.^\w|~-]+$/u,o={type:``,parameters:new n};Object.freeze(o.parameters),Object.freeze(o);function s(e){if(typeof e!=`string`)throw TypeError(`argument header is required and must be a string`);let t=e.indexOf(`;`),o=t===-1?e.trim():e.slice(0,t).trim();if(a.test(o)===!1)throw TypeError(`invalid media type`);let s={type:o.toLowerCase(),parameters:new n};if(t===-1)return s;let c,l,u;for(r.lastIndex=t;l=r.exec(e);){if(l.index!==t)throw TypeError(`invalid parameter format`);t+=l[0].length,c=l[1].toLowerCase(),u=l[2],u[0]===`"`&&(u=u.slice(1,u.length-1),i.test(u)&&(u=u.replace(i,`$1`))),s.parameters[c]=u}if(t!==e.length)throw TypeError(`invalid parameter format`);return s}function c(e){if(typeof e!=`string`)return o;let t=e.indexOf(`;`),s=t===-1?e.trim():e.slice(0,t).trim();if(a.test(s)===!1)return o;let c={type:s.toLowerCase(),parameters:new n};if(t===-1)return c;let l,u,d;for(r.lastIndex=t;u=r.exec(e);){if(u.index!==t)return o;t+=u[0].length,l=u[1].toLowerCase(),d=u[2],d[0]===`"`&&(d=d.slice(1,d.length-1),i.test(d)&&(d=d.replace(i,`$1`))),c.parameters[l]=d}return t===e.length?c:o}t.exports.default={parse:s,safeParse:c},t.exports.parse=s,t.exports.safeParse=c,t.exports.defaultContentType=o}))();const Qr=/^-?\d+$/,$r=/^-?\d+n+$/,ei=JSON.stringify,ti=JSON.parse,ni=/^-?\d+n$/,ri=/([\[:])?"(-?\d+)n"($|([\\n]|\s)*(\s|[\\n])*[,\}\]])/g,ii=/([\[:])?("-?\d+n+)n("$|"([\\n]|\s)*(\s|[\\n])*[,\}\]])/g,ai=(e,t,n)=>`rawJSON`in JSON?ei(e,(e,n)=>typeof n==`bigint`?JSON.rawJSON(n.toString()):typeof t==`function`?t(e,n):(Array.isArray(t)&&t.includes(e),n),n):e?ei(e,(e,n)=>typeof n==`string`&&n.match($r)||typeof n==`bigint`?n.toString()+`n`:typeof t==`function`?t(e,n):(Array.isArray(t)&&t.includes(e),n),n).replace(ri,`$1$2$3`).replace(ii,`$1$2$3`):ei(e,t,n),oi=()=>JSON.parse(`1`,(e,t,n)=>!!n&&n.source===`1`),si=(e,t,n,r)=>typeof t==`string`&&t.match(ni)?BigInt(t.slice(0,-1)):typeof t==`string`&&t.match($r)?t.slice(0,-1):typeof r==`function`?r(e,t,n):t,ci=(e,t)=>JSON.parse(e,(e,n,r)=>{let i=typeof n==`number`&&(n>2**53-1||n<-(2**53-1)),a=r&&Qr.test(r.source);return i&&a?BigInt(r.source):typeof t==`function`?t(e,n,r):n}),li=(2**53-1).toString(),ui=li.length,di=/"(?:\\.|[^"])*"|-?(0|[1-9][0-9]*)(\.[0-9]+)?([eE][+-]?[0-9]+)?/g,fi=/^"-?\d+n+"$/,pi=(e,t)=>e?oi()?ci(e,t):ti(e.replace(di,(e,t,n,r)=>{let i=e[0]===`"`;if(i&&e.match(fi))return e.substring(0,e.length-1)+`n"`;let a=n||r,o=t&&(t.lengthsi(e,n,r,t)):ti(e,t);var mi=class extends Error{name;status;request;response;constructor(e,t,n){super(e,{cause:n.cause}),this.name=`HttpError`,this.status=Number.parseInt(t),Number.isNaN(this.status)&&(this.status=0),`response`in n&&(this.response=n.response);let r=Object.assign({},n.request);n.request.headers.authorization&&(r.headers=Object.assign({},n.request.headers,{authorization:n.request.headers.authorization.replace(/(?``;async function yi(e){let t=e.request?.fetch||globalThis.fetch;if(!t)throw Error(`fetch is not set. Please pass a fetch implementation as new Octokit({ request: { fetch }}). Learn more at https://github.com/octokit/octokit.js/#fetch-missing`);let n=e.request?.log||console,r=e.request?.parseSuccessResponseBody!==!1,i=_i(e.body)||Array.isArray(e.body)?ai(e.body):e.body,a=Object.fromEntries(Object.entries(e.headers).map(([e,t])=>[e,String(t)])),o;try{o=await t(e.url,{method:e.method,body:i,redirect:e.request?.redirect,headers:a,signal:e.request?.signal,...e.body&&{duplex:`half`}})}catch(t){let n=`Unknown Error`;if(t instanceof Error){if(t.name===`AbortError`)throw t.status=500,t;n=t.message,t.name===`TypeError`&&`cause`in t&&(t.cause instanceof Error?n=t.cause.message:typeof t.cause==`string`&&(n=t.cause))}let r=new mi(n,500,{request:e});throw r.cause=t,r}let s=o.status,c=o.url,l={};for(let[e,t]of o.headers)l[e]=t;let u={url:c,status:s,headers:l,data:``};if(`deprecation`in l){let t=l.link&&l.link.match(/<([^<>]+)>; rel="deprecation"/),r=t&&t.pop();n.warn(`[@octokit/request] "${e.method} ${e.url}" is deprecated. It is scheduled to be removed on ${l.sunset}${r?`. See ${r}`:``}`)}if(s===204||s===205)return u;if(e.method===`HEAD`){if(s<400)return u;throw new mi(o.statusText,s,{response:u,request:e})}if(s===304)throw u.data=await bi(o),new mi(`Not modified`,s,{response:u,request:e});if(s>=400)throw u.data=await bi(o),new mi(Si(u.data),s,{response:u,request:e});return u.data=r?await bi(o):o.body,u}async function bi(e){let t=e.headers.get(`content-type`);if(!t)return e.text().catch(vi);let n=(0,Zr.safeParse)(t);if(xi(n)){let t=``;try{return t=await e.text(),pi(t)}catch{return t}}else if(n.type.startsWith(`text/`)||n.parameters.charset?.toLowerCase()===`utf-8`)return e.text().catch(vi);else return e.arrayBuffer().catch(()=>new ArrayBuffer(0))}function xi(e){return e.type===`application/json`||e.type===`application/scim+json`}function Si(e){if(typeof e==`string`)return e;if(e instanceof ArrayBuffer)return`Unknown error`;if(`message`in e){let t=`documentation_url`in e?` - ${e.documentation_url}`:``;return Array.isArray(e.errors)?`${e.message}: ${e.errors.map(e=>JSON.stringify(e)).join(`, `)}${t}`:`${e.message}${t}`}return`Unknown error: ${JSON.stringify(e)}`}function Ci(e,t){let n=e.defaults(t);return Object.assign(function(e,t){let r=n.merge(e,t);if(!r.request||!r.request.hook)return yi(n.parse(r));let i=(e,t)=>yi(n.parse(n.merge(e,t)));return Object.assign(i,{endpoint:n,defaults:Ci.bind(null,n)}),r.request.hook(i,r)},{endpoint:n,defaults:Ci.bind(null,n)})}var wi=Ci(Xr,gi),Ti=`0.0.0-development`;function Ei(e){return`Request failed due to following response errors: `+e.errors.map(e=>` - ${e.message}`).join(` -`)}var yi=class extends Error{constructor(e,t,n){super(vi(n)),this.request=e,this.headers=t,this.response=n,this.errors=n.errors,this.data=n.data,Error.captureStackTrace&&Error.captureStackTrace(this,this.constructor)}name=`GraphqlResponseError`;errors;data},bi=[`method`,`baseUrl`,`url`,`headers`,`request`,`query`,`mediaType`,`operationName`],xi=[`query`,`method`,`url`],Si=/\/api\/v3\/?$/;function Ci(e,t,n){if(n){if(typeof t==`string`&&`query`in n)return Promise.reject(Error(`[@octokit/graphql] "query" cannot be used as variable name`));for(let e in n)if(xi.includes(e))return Promise.reject(Error(`[@octokit/graphql] "${e}" cannot be used as variable name`))}let r=typeof t==`string`?Object.assign({query:t},n):t,i=Object.keys(r).reduce((e,t)=>bi.includes(t)?(e[t]=r[t],e):(e.variables||={},e.variables[t]=r[t],e),{}),a=r.baseUrl||e.endpoint.DEFAULTS.baseUrl;return Si.test(a)&&(i.url=a.replace(Si,`/api/graphql`)),e(i).then(e=>{if(e.data.errors){let t={};for(let n of Object.keys(e.headers))t[n]=e.headers[n];throw new yi(i,t,e.data)}return e.data.data})}function wi(e,t){let n=e.defaults(t);return Object.assign((e,t)=>Ci(n,e,t),{defaults:wi.bind(null,n),endpoint:n.endpoint})}wi(gi,{headers:{"user-agent":`octokit-graphql.js/${_i} ${fr()}`},method:`POST`,url:`/graphql`});function Ti(e){return wi(e,{method:`POST`,url:`/graphql`})}var Ei=`(?:[a-zA-Z0-9_-]+)`,Di=`\\.`,Oi=RegExp(`^${Ei}${Di}${Ei}${Di}${Ei}$`),ki=Oi.test.bind(Oi);async function Ai(e){let t=ki(e),n=e.startsWith(`v1.`)||e.startsWith(`ghs_`),r=e.startsWith(`ghu_`);return{type:`token`,token:e,tokenType:t?`app`:n?`installation`:r?`user-to-server`:`oauth`}}function ji(e){return e.split(/\./).length===3?`bearer ${e}`:`token ${e}`}async function Mi(e,t,n,r){let i=t.endpoint.merge(n,r);return i.headers.authorization=ji(e),t(i)}var Ni=function(e){if(!e)throw Error(`[@octokit/auth-token] No token passed to createTokenAuth`);if(typeof e!=`string`)throw Error(`[@octokit/auth-token] Token passed to createTokenAuth is not a string`);return e=e.replace(/^(token|bearer) +/i,``),Object.assign(Ai.bind(null,e),{hook:Mi.bind(null,e)})};const Pi=`7.0.6`,Fi=()=>{},Ii=console.warn.bind(console),Li=console.error.bind(console);function Ri(e={}){return typeof e.debug!=`function`&&(e.debug=Fi),typeof e.info!=`function`&&(e.info=Fi),typeof e.warn!=`function`&&(e.warn=Ii),typeof e.error!=`function`&&(e.error=Li),e}const zi=`octokit-core.js/${Pi} ${fr()}`;var Bi=class{static VERSION=Pi;static defaults(e){return class extends this{constructor(...t){let n=t[0]||{};if(typeof e==`function`){super(e(n));return}super(Object.assign({},e,n,n.userAgent&&e.userAgent?{userAgent:`${n.userAgent} ${e.userAgent}`}:null))}}}static plugins=[];static plugin(...e){let t=this.plugins;return class extends this{static plugins=t.concat(e.filter(e=>!t.includes(e)))}}constructor(e={}){let t=new xr.Collection,n={baseUrl:gi.endpoint.DEFAULTS.baseUrl,headers:{},request:Object.assign({},e.request,{hook:t.bind(null,`request`)}),mediaType:{previews:[],format:``}};if(n.headers[`user-agent`]=e.userAgent?`${e.userAgent} ${zi}`:zi,e.baseUrl&&(n.baseUrl=e.baseUrl),e.previews&&(n.mediaType.previews=e.previews),e.timeZone&&(n.headers[`time-zone`]=e.timeZone),this.request=gi.defaults(n),this.graphql=Ti(this.request).defaults(n),this.log=Ri(e.log),this.hook=t,e.authStrategy){let{authStrategy:n,...r}=e,i=n(Object.assign({request:this.request,log:this.log,octokit:this,octokitOptions:r},e.auth));t.wrap(`request`,i.hook),this.auth=i}else if(!e.auth)this.auth=async()=>({type:`unauthenticated`});else{let n=Ni(e.auth);t.wrap(`request`,n.hook),this.auth=n}let r=this.constructor;for(let t=0;t({async next(){if(!s)return{done:!0};try{let e=Yi(await i({method:a,url:s,headers:o}));if(s=((e.headers.link||``).match(/<([^<>]+)>;\s*rel="next"/)||[])[1],!s&&`total_commits`in e.data){let t=new URL(e.url),n=t.searchParams,r=parseInt(n.get(`page`)||`1`,10);r*parseInt(n.get(`per_page`)||`250`,10){if(i.done)return t;let a=!1;function o(){a=!0}return t=t.concat(r?r(i.value,o):i.value.data),a?t:Qi(e,t,n,r)})}Object.assign(Zi,{iterator:Xi});function $i(e){return{paginate:Object.assign(Zi.bind(null,e),{iterator:Xi.bind(null,e)})}}$i.VERSION=Ji,new nr;const ea=dr(),ta={baseUrl:ea,request:{agent:cr(ea),fetch:ur(ea)}},na=Bi.plugin(Ki,$i).defaults(ta);function ra(e,t){let n=Object.assign({},t||{}),r=sr(e,n);return r&&(n.auth=r),n}const ia=new nr;function aa(e,t,...n){return new(na.plugin(...n))(ra(e,t))}var q=C(ue(),1),oa=function(e,t,n,r){function i(e){return e instanceof n?e:new n(function(t){t(e)})}return new(n||=Promise)(function(n,a){function o(e){try{c(r.next(e))}catch(e){a(e)}}function s(e){try{c(r.throw(e))}catch(e){a(e)}}function c(e){e.done?n(e.value):i(e.value).then(o,s)}c((r=r.apply(e,t||[])).next())})},sa=class{constructor(e,t,n){if(e<1)throw Error(`max attempts should be greater than or equal to 1`);if(this.maxAttempts=e,this.minSeconds=Math.floor(t),this.maxSeconds=Math.floor(n),this.minSeconds>this.maxSeconds)throw Error(`min seconds should be less than or equal to max seconds`)}execute(e,t){return oa(this,void 0,void 0,function*(){let n=1;for(;nsetTimeout(t,e*1e3))})}},J=function(e,t,n,r){function i(e){return e instanceof n?e:new n(function(t){t(e)})}return new(n||=Promise)(function(n,a){function o(e){try{c(r.next(e))}catch(e){a(e)}}function s(e){try{c(r.throw(e))}catch(e){a(e)}}function c(e){e.done?n(e.value):i(e.value).then(o,s)}c((r=r.apply(e,t||[])).next())})},ca=class extends Error{constructor(e){super(`Unexpected HTTP response: ${e}`),this.httpStatusCode=e,Object.setPrototypeOf(this,new.target.prototype)}};const la=process.platform===`win32`;process.platform;function ua(e,t,n,r){return J(this,void 0,void 0,function*(){return t||=F.join(Ta(),ye.randomUUID()),yield s(F.dirname(t)),j(`Downloading ${e}`),j(`Destination ${t}`),yield new sa(3,Ea(`TEST_DOWNLOAD_TOOL_RETRY_MIN_SECONDS`,10),Ea(`TEST_DOWNLOAD_TOOL_RETRY_MAX_SECONDS`,20)).execute(()=>J(this,void 0,void 0,function*(){return yield da(e,t||``,n,r)}),e=>!(e instanceof ca&&e.httpStatusCode&&e.httpStatusCode<500&&e.httpStatusCode!==408&&e.httpStatusCode!==429))})}function da(e,t,n,r){return J(this,void 0,void 0,function*(){if(P.existsSync(t))throw Error(`Destination file path ${t} already exists`);let i=new u(`actions/tool-cache`,[],{allowRetries:!1});n&&(j(`set auth`),r===void 0&&(r={}),r.authorization=n);let a=yield i.get(e,r);if(a.message.statusCode!==200){let t=new ca(a.message.statusCode);throw j(`Failed to download from "${e}". Code(${a.message.statusCode}) Message(${a.message.statusMessage})`),t}let o=Ce.promisify(Ne.pipeline),s=Ea(`TEST_DOWNLOAD_TOOL_RESPONSE_MESSAGE_FACTORY`,()=>a.message)(),c=!1;try{return yield o(s,P.createWriteStream(t)),j(`download complete`),c=!0,t}finally{if(!c){j(`download failed`);try{yield v(t)}catch(e){j(`Failed to delete '${t}'. ${e.message}`)}}}})}function fa(e,t){return J(this,arguments,void 0,function*(e,t,n=`xz`){if(!e)throw Error(`parameter 'file' is required`);t=yield ya(t),j(`Checking tar --version`);let r=``;yield M(`tar --version`,[],{ignoreReturnCode:!0,silent:!0,listeners:{stdout:e=>r+=e.toString(),stderr:e=>r+=e.toString()}}),j(r.trim());let i=r.toUpperCase().includes(`GNU TAR`),a;a=n instanceof Array?n:[n],m()&&!n.includes(`v`)&&a.push(`-v`);let o=t,s=e;return la&&i&&(a.push(`--force-local`),o=t.replace(/\\/g,`/`),s=e.replace(/\\/g,`/`)),i&&(a.push(`--warning=no-unknown-keyword`),a.push(`--overwrite`)),a.push(`-C`,o,`-f`,s),yield M(`tar`,a),t})}function pa(e,t){return J(this,void 0,void 0,function*(){if(!e)throw Error(`parameter 'file' is required`);return t=yield ya(t),la?yield ma(e,t):yield ha(e,t),t})}function ma(e,t){return J(this,void 0,void 0,function*(){let n=e.replace(/'/g,`''`).replace(/"|\n|\r/g,``),r=t.replace(/'/g,`''`).replace(/"|\n|\r/g,``),i=yield b(`pwsh`,!1);if(i){let e=[`-NoLogo`,`-NoProfile`,`-NonInteractive`,`-ExecutionPolicy`,`Unrestricted`,`-Command`,[`$ErrorActionPreference = 'Stop' ;`,`try { Add-Type -AssemblyName System.IO.Compression.ZipFile } catch { } ;`,`try { [System.IO.Compression.ZipFile]::ExtractToDirectory('${n}', '${r}', $true) }`,`catch { if (($_.Exception.GetType().FullName -eq 'System.Management.Automation.MethodException') -or ($_.Exception.GetType().FullName -eq 'System.Management.Automation.RuntimeException') ){ Expand-Archive -LiteralPath '${n}' -DestinationPath '${r}' -Force } else { throw $_ } } ;`].join(` `)];j(`Using pwsh at path: ${i}`),yield M(`"${i}"`,e)}else{let e=[`-NoLogo`,`-Sta`,`-NoProfile`,`-NonInteractive`,`-ExecutionPolicy`,`Unrestricted`,`-Command`,[`$ErrorActionPreference = 'Stop' ;`,`try { Add-Type -AssemblyName System.IO.Compression.FileSystem } catch { } ;`,`if ((Get-Command -Name Expand-Archive -Module Microsoft.PowerShell.Archive -ErrorAction Ignore)) { Expand-Archive -LiteralPath '${n}' -DestinationPath '${r}' -Force }`,`else {[System.IO.Compression.ZipFile]::ExtractToDirectory('${n}', '${r}', $true) }`].join(` `)],t=yield b(`powershell`,!0);j(`Using powershell at path: ${t}`),yield M(`"${t}"`,e)}})}function ha(e,t){return J(this,void 0,void 0,function*(){let n=yield b(`unzip`,!0),r=[e];m()||r.unshift(`-q`),r.unshift(`-o`),yield M(`"${n}"`,r,{cwd:t})})}function ga(e,t,n,r){return J(this,void 0,void 0,function*(){if(n=q.clean(n)||n,r||=_e.arch(),j(`Caching tool ${t} ${n} ${r}`),j(`source dir: ${e}`),!P.statSync(e).isDirectory())throw Error(`sourceDir is not a directory`);let i=yield ba(t,n,r);for(let t of P.readdirSync(e))yield y(F.join(e,t),i,{recursive:!0});return xa(t,n,r),i})}function _a(e,t,n){if(!e)throw Error(`toolName parameter is required`);if(!t)throw Error(`versionSpec parameter is required`);n||=_e.arch(),Sa(t)||(t=Ca(va(e,n),t));let r=``;if(t){t=q.clean(t)||``;let i=F.join(wa(),e,t,n);j(`checking cache: ${i}`),P.existsSync(i)&&P.existsSync(`${i}.complete`)?(j(`Found tool in cache ${e} ${t} ${n}`),r=i):j(`not found`)}return r}function va(e,t){let n=[];t||=_e.arch();let r=F.join(wa(),e);if(P.existsSync(r)){let e=P.readdirSync(r);for(let i of e)if(Sa(i)){let e=F.join(r,i,t||``);P.existsSync(e)&&P.existsSync(`${e}.complete`)&&n.push(i)}}return n}function ya(e){return J(this,void 0,void 0,function*(){return e||=F.join(Ta(),ye.randomUUID()),yield s(e),e})}function ba(e,t,n){return J(this,void 0,void 0,function*(){let r=F.join(wa(),e,q.clean(t)||t,n||``);j(`destination ${r}`);let i=`${r}.complete`;return yield v(r),yield v(i),yield s(r),r})}function xa(e,t,n){let r=`${F.join(wa(),e,q.clean(t)||t,n||``)}.complete`;P.writeFileSync(r,``),j(`finished caching tool`)}function Sa(e){let t=q.clean(e)||``;j(`isExplicit: ${t}`);let n=q.valid(t)!=null;return j(`explicit? ${n}`),n}function Ca(e,t){let n=``;j(`evaluating ${e.length} versions`),e=e.sort((e,t)=>q.gt(e,t)?1:-1);for(let r=e.length-1;r>=0;r--){let i=e[r];if(q.satisfies(i,t)){n=i;break}}return j(n?`matched: ${n}`:`match not found`),n}function wa(){let e=process.env.RUNNER_TOOL_CACHE||``;return Se(e,`Expected RUNNER_TOOL_CACHE to be defined`),e}function Ta(){let e=process.env.RUNNER_TEMP||``;return Se(e,`Expected RUNNER_TEMP to be defined`),e}function Ea(e,t){let n=global[e];return n===void 0?t:n}function Da(e){let t;try{t=JSON.parse(e)}catch(e){throw e instanceof SyntaxError?Error(`Invalid auth-json format: ${e.message}`):e}if(typeof t!=`object`||!t||Array.isArray(t))throw Error(`auth-json must be a JSON object`);return t}async function Oa(e,t,n){let r=L.join(t,`auth.json`);await I.mkdir(t,{recursive:!0});let i=JSON.stringify(e,null,2);return await I.writeFile(r,i,{mode:384}),n.info(`Populated auth.json`,{path:r,providers:Object.keys(e).length}),r}function ka(){let e=N.platform,t=N.arch;return{os:{darwin:`darwin`,linux:`linux`,win32:`windows`}[e]??`linux`,arch:{arm64:`aarch64`,x64:`x64`}[t]??`x64`,ext:`.zip`}}function Aa(e,t){return`https://github.com/oven-sh/bun/releases/download/bun-${e.startsWith(`v`)?e:`v${e}`}/${`bun-${t.os}-${t.arch}${t.ext}`}`}async function ja(e,t,n,i,a=pe){let o=ka(),s=t.find(`bun`,a,o.arch);if(s.length>0)return e.info(`Bun found in cache`,{version:a,path:s}),i(s),await Na(s),{path:s,version:a,cached:!0};e.info(`Downloading Bun`,{version:a});let c=Aa(a,o);try{let r=await t.downloadTool(c);if(N.platform!==`win32`&&!await Pa(r,e,n))throw Error(`Downloaded Bun archive appears corrupted`);e.info(`Extracting Bun`);let s=await Ma(await t.extractZip(r),t),l=R.dirname(s);e.info(`Caching Bun`);let u=await t.cacheDir(l,`bun`,a,o.arch);return i(u),await Na(u),e.info(`Bun installed`,{version:a,path:u}),{path:u,version:a,cached:!1}}catch(e){let t=r(e);throw Error(`Failed to install Bun ${a}: ${t}`)}}async function Ma(e,t){for(let n of await De.readdir(e,{withFileTypes:!0})){let{name:r}=n,i=R.join(e,r);if(n.isFile()){if(r===`bun`||r===`bun.exe`)return i;if(/^bun.*\.zip/.test(r))return Ma(await t.extractZip(i),t)}if(r.startsWith(`bun`)&&n.isDirectory())return Ma(i,t)}throw Error(`Could not find executable: bun`)}async function Na(e){let t=e=>N.platform===`win32`?`${e}.exe`:e,n=R.join(e,t(`bun`));try{await De.symlink(n,R.join(e,t(`bunx`)))}catch(e){let t=typeof e==`object`?e.code:void 0;if(t!==`EEXIST`&&t!==`EPERM`&&t!==`EACCES`)throw e}}async function Pa(e,t,n){try{let{stdout:r}=await n.getExecOutput(`file`,[e],{silent:!0}),i=r.includes(`Zip archive`)||r.includes(`ZIP`);return i||t.warning(`Bun download validation failed`,{output:r.trim()}),i}catch{return t.debug(`Could not validate Bun download (file command unavailable)`),!0}}function Fa(e){return{debug:t=>e.debug(t),info:t=>e.info(t),warn:t=>e.warning(t),error:t=>e.error(t)}}function Ia(e){let{token:t,logger:n}=e;return n.debug(`Creating GitHub client with token`),aa(t,{log:Fa(n)})}async function La(e,t){try{let{data:n}=await e.rest.users.getAuthenticated();return t.debug(`Authenticated as`,{login:n.login,type:n.type}),n.login}catch{return t.debug(`Failed to get authenticated user, may be app token`),`fro-bot[bot]`}}async function Ra(e,t,n,r){let i=t??n,a=t==null?n.length>0?`github-token`:`none`:`app-token`;if(i.length===0)return r.warning(`No GitHub token available`),{authenticated:!1,method:`none`,botLogin:null};N.env.GH_TOKEN=i,r.info(`Configured authentication`,{method:a});let o=null;return e!=null&&(o=await La(e,r)),{authenticated:!0,method:a,botLogin:o}}async function za(e,t){let n=await t.getExecOutput(`git`,[`config`,e],{ignoreReturnCode:!0,silent:!0});return n.exitCode===0&&n.stdout.trim().length>0?n.stdout.trim():null}async function Ba(e,t,n,r){let i=await za(`user.name`,r),a=await za(`user.email`,r);if(i!=null&&a!=null){n.info(`Git identity already configured`,{name:i,email:a});return}if(t==null)throw Error(`Cannot configure Git identity: no authenticated GitHub user`);let o=null;if(a==null){let r=await Je(e,t,n);if(r==null)throw Error(`Cannot configure Git identity: failed to look up user ID for '${t}'`);o=String(r.id)}i??await r.exec(`git`,[`config`,`--global`,`user.name`,t],void 0);let s=`${o}+${t}@users.noreply.github.com`;a??await r.exec(`git`,[`config`,`--global`,`user.email`,s],void 0),n.info(`Configured git identity`,{name:i??t,email:a??s})}const Va=new Set([`__proto__`,`prototype`,`constructor`]);function Ha(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function Ua(e,t){let n=Object.create(null);for(let[t,r]of Object.entries(e))Va.has(t)||(n[t]=r);for(let[e,r]of Object.entries(t)){if(Va.has(e))continue;let t=n[e];Ha(r)&&Ha(t)?n[e]=Ua(t,r):n[e]=r}return n}async function Wa(e,t,n){let r=JSON.parse(e);if(!Ha(r))throw Error(`omo-config must be a JSON object (non-null, non-array)`);let i=r;await I.mkdir(t,{recursive:!0});let a=L.join(t,`oh-my-opencode.json`),o={};try{let e=await I.readFile(a,`utf8`),t=JSON.parse(e);typeof t==`object`&&t&&!Array.isArray(t)&&(o=t)}catch(e){n.debug(`Using empty base oMo config`,{path:a,error:String(e)})}let s=Ua(o,i);await I.writeFile(a,JSON.stringify(s,null,2)),n.info(`Wrote oMo config`,{path:a,keyCount:Object.keys(i).length})}async function Ga(e,t,n={}){let{logger:i,execAdapter:a}=t,{claude:o=`no`,copilot:s=`no`,gemini:c=`no`,openai:l=`no`,opencodeZen:u=`no`,zaiCodingPlan:d=`no`,kimiForCoding:f=`no`}=n;i.info(`Installing Oh My OpenAgent plugin`,{version:e,claude:o,copilot:s,gemini:c,openai:l,opencodeZen:u,zaiCodingPlan:d,kimiForCoding:f});let p=``,m=[`oh-my-openagent@${e}`,`install`,`--no-tui`,`--skip-auth`,`--claude=${o}`,`--copilot=${s}`,`--gemini=${c}`,`--openai=${l}`,`--opencode-zen=${u}`,`--zai-coding-plan=${d}`,`--kimi-for-coding=${f}`];try{let t=await a.exec(`bunx`,m,{listeners:{stdout:e=>{p+=e.toString()},stderr:e=>{p+=e.toString()}},ignoreReturnCode:!0});if(t!==0){let e=`bunx oh-my-openagent install returned exit code ${t}`;return i.error(e,{output:p.slice(0,1e3)}),{installed:!1,version:null,error:`${e}\n${p.slice(0,500)}`}}let n=/oh-my-opencode@(\d+\.\d+\.\d+)/i.exec(p),r=n!=null&&n[1]!=null?n[1]:e;return i.info(`oMo plugin installed`,{version:r}),{installed:!0,version:r,error:null}}catch(e){let t=r(e),n=p.length>0?`${t}\nOutput: ${p.slice(0,500)}`:t;return i.error(`Failed to run oMo installer`,{error:t,output:p.slice(0,500)}),{installed:!1,version:null,error:`bunx oh-my-openagent install failed: ${n}`}}}const Ka=`opencode`,qa=g;function Ja(){let e=je.platform(),t=je.arch(),n={darwin:`darwin`,linux:`linux`,win32:`windows`},r={x64:`x64`,arm64:`arm64`},i=e===`win32`||e===`darwin`?`.zip`:`.tar.gz`;return{os:n[e]??`linux`,arch:r[t]??`x64`,ext:i}}function Ya(e,t){return`https://github.com/anomalyco/opencode/releases/download/${e.startsWith(`v`)?e:`v${e}`}/${`opencode-${t.os}-${t.arch}${t.ext}`}`}async function Xa(e,t,n,r){if(N.platform===`win32`)return!0;try{let{stdout:i}=await r.getExecOutput(`file`,[e],{silent:!0}),a=(t===`.zip`?[`Zip archive`,`ZIP`]:[`gzip`,`tar`,`compressed`]).some(e=>i.includes(e));return a||n.warning(`Download validation failed`,{output:i.trim()}),a}catch{return n.debug(`Could not validate download (file command unavailable)`),!0}}async function Za(e,t,n,i,a=qa){let o=Ja(),s=n.find(Ka,e,o.arch);if(s.length>0)return t.info(`OpenCode found in cache`,{version:e,path:s}),{path:s,version:e,cached:!0};try{return await Qa(e,o,t,n,i)}catch(n){t.warning(`Primary version install failed, trying fallback`,{requestedVersion:e,fallbackVersion:a,error:r(n)})}if(e!==a)try{let e=await Qa(a,o,t,n,i);return t.info(`Installed fallback version`,{version:a}),e}catch(t){throw Error(`Failed to install OpenCode (tried ${e} and ${a}): ${r(t)}`)}throw Error(`Failed to install OpenCode version ${e}`)}async function Qa(e,t,n,r,i){n.info(`Downloading OpenCode`,{version:e});let a=Ya(e,t),o=await r.downloadTool(a);if(!await Xa(o,t.ext,n,i))throw Error(`Downloaded archive appears corrupted`);n.info(`Extracting OpenCode`);let s=t.ext===`.zip`?await r.extractZip(o):await r.extractTar(o);n.info(`Caching OpenCode`);let c=await r.cacheDir(s,Ka,e,t.arch);return n.info(`OpenCode installed`,{version:e,path:c}),{path:c,version:e,cached:!1}}async function $a(e){let t=await fetch(`https://api.github.com/repos/anomalyco/opencode/releases/latest`);if(!t.ok)throw Error(`Failed to fetch latest OpenCode version: ${t.statusText}`);let n=(await t.json()).tag_name.replace(/^v/,``);return e.info(`Latest OpenCode version`,{version:n}),n}const eo={restoreCache:async(e,t,n)=>T(e,t,n),saveCache:async(e,t)=>oe(e,t)};function to(e){let{os:t,opencodeVersion:n,omoVersion:r}=e;return`${me}-${t}-oc-${n}-omo-${r}`}function no(e){let{os:t,opencodeVersion:n,omoVersion:r}=e;return[`${me}-${t}-oc-${n}-omo-${r}-`]}async function ro(e){let{logger:t,os:n,opencodeVersion:i,omoVersion:a,toolCachePath:o,bunCachePath:s,omoConfigPath:c,cacheAdapter:l=eo}=e,u=to({os:n,opencodeVersion:i,omoVersion:a}),d=no({os:n,opencodeVersion:i,omoVersion:a}),f=[o,s,c];t.info(`Restoring tools cache`,{primaryKey:u,restoreKeys:[...d],paths:f});try{let e=await l.restoreCache(f,u,[...d]);return e==null?(t.info(`Tools cache miss - will install tools`),{hit:!1,restoredKey:null}):(t.info(`Tools cache restored`,{restoredKey:e}),{hit:!0,restoredKey:e})}catch(e){return t.warning(`Tools cache restore failed`,{error:r(e)}),{hit:!1,restoredKey:null}}}async function io(e){let{logger:t,os:n,opencodeVersion:i,omoVersion:a,toolCachePath:o,bunCachePath:s,omoConfigPath:c,cacheAdapter:l=eo}=e,u=to({os:n,opencodeVersion:i,omoVersion:a}),d=[o,s,c];t.info(`Saving tools cache`,{saveKey:u,paths:d});try{return await l.saveCache(d,u),t.info(`Tools cache saved`,{saveKey:u}),!0}catch(e){return e instanceof Error&&e.message.includes(`already exists`)?(t.info(`Tools cache key already exists, skipping save`),!0):(t.warning(`Tools cache save failed`,{error:r(e)}),!1)}}function ao(){return{find:_a,downloadTool:ua,extractTar:fa,extractZip:pa,cacheDir:ga}}function oo(){return{exec:M,getExecOutput:t}}async function so(t,n){let a=Date.now(),o=i({component:`setup`}),s=ao(),l=oo();try{o.info(`Starting setup`,{version:t.opencodeVersion});let i;try{i=Da(t.authJson)}catch(e){return c(`Invalid auth-json: ${r(e)}`),null}let u=t.opencodeVersion;if(u===`latest`)try{u=await $a(o)}catch(e){o.warning(`Failed to get latest version, using fallback`,{error:r(e)}),u=qa}let f=t.omoVersion,m=N.env.RUNNER_TOOL_CACHE??`/opt/hostedtoolcache`,h=Oe(m,`opencode`),g=Oe(m,`bun`),_=Oe(Me(),`.config`,`opencode`),v=se(),y=await ro({logger:o,os:v,opencodeVersion:u,omoVersion:f,toolCachePath:h,bunCachePath:g,omoConfigPath:_}),b=y.hit?`hit`:`miss`,x,S=!1,C=null;if(y.hit){let e=s.find(`opencode`,u);e.length>0?(x={path:e,version:u,cached:!0},o.info(`Tools cache hit, using cached OpenCode CLI`,{version:u,omoVersion:f})):o.warning(`Tools cache hit but binary not found in tool-cache, falling through to install`,{requestedVersion:u,restoredKey:y.restoredKey})}if(x==null)try{x=await Za(u,o,s,l)}catch(e){return c(`Failed to install OpenCode: ${r(e)}`),null}let w=!1;try{await ja(o,s,l,p,pe),w=!0}catch(e){o.warning(`Bun installation failed, oMo will be unavailable`,{error:r(e)})}if(w){if(t.omoConfig!=null)try{await Wa(t.omoConfig,_,o)}catch(e){o.warning(`Failed to write omo-config, continuing without custom config`,{error:r(e)})}let e=await Ga(f,{logger:o,execAdapter:l},t.omoProviders);e.installed?(o.info(`oMo installed`,{version:e.version}),S=!0):o.warning(`oMo installation failed, continuing without oMo`,{error:e.error??`unknown error`}),C=e.error}let T={autoupdate:!1};if(t.opencodeConfig!=null){let e;try{e=JSON.parse(t.opencodeConfig)}catch{return c(`opencode-config must be valid JSON`),null}if(typeof e!=`object`||!e||Array.isArray(e))return c(`opencode-config must be a JSON object`),null;Object.assign(T,e)}e(`OPENCODE_CONFIG_CONTENT`,JSON.stringify(T)),y.hit||await io({logger:o,os:v,opencodeVersion:u,omoVersion:f,toolCachePath:h,bunCachePath:g,omoConfigPath:_}),p(x.path),d(`opencode-path`,x.path),d(`opencode-version`,x.version),o.info(`OpenCode ready`,{version:x.version,cached:x.cached});let E=aa(n),D=await Ra(E,null,n,o);e(`GH_TOKEN`,n),o.info(`GitHub CLI configured`),await Ba(E,D.botLogin,o,l);let O=Oe(ie(),`opencode`),k=await Oa(i,O,o);d(`auth-json-path`,k),o.info(`auth.json populated`,{path:k});let ee=Date.now()-a,te={opencodePath:x.path,opencodeVersion:x.version,ghAuthenticated:D.authenticated,omoInstalled:S,omoError:C,toolsCacheStatus:b,duration:ee};return o.info(`Setup complete`,{duration:ee}),te}catch(e){let t=r(e);return o.error(`Setup failed`,{error:t}),c(t),null}}function co(e){return{success:!0,data:e}}function lo(e){return{success:!1,error:e}}const uo=[`OWNER`,`MEMBER`,`COLLABORATOR`];async function fo(e,t){try{let{client:n,server:r}=await un({signal:e});return t.debug(`OpenCode server bootstrapped`,{url:r.url}),co({client:n,server:r,shutdown:()=>{r.close()}})}catch(e){let n=e instanceof Error?e.message:String(e);return t.warning(`Failed to bootstrap OpenCode server`,{error:n}),lo(Error(`Server bootstrap failed: ${n}`))}}async function po(e,t){let n=e??`opencode`;try{let e=``;await M(n,[`--version`],{listeners:{stdout:t=>{e+=t.toString()}},silent:!0});let r=/(\d+\.\d+\.\d+)/.exec(e)?.[1]??null;return t.debug(`OpenCode version verified`,{version:r}),{available:!0,version:r}}catch{return t.debug(`OpenCode not available, will attempt auto-setup`),{available:!1,version:null}}}async function mo(e){let{logger:t,opencodeVersion:n}=e,r=N.env.OPENCODE_PATH??null,i=await po(r,t);if(i.available&&i.version!=null)return t.info(`OpenCode already available`,{version:i.version}),{path:r??`opencode`,version:i.version,didSetup:!1};t.info(`OpenCode not found, running auto-setup`,{requestedVersion:n});let a=await so({opencodeVersion:n,authJson:e.authJson,appId:null,privateKey:null,opencodeConfig:e.opencodeConfig,omoConfig:null,omoVersion:e.omoVersion,omoProviders:e.omoProviders},e.githubToken);if(a==null)throw Error(`Auto-setup failed: runSetup returned null`);return p(a.opencodePath),N.env.OPENCODE_PATH=a.opencodePath,t.info(`Auto-setup completed`,{version:a.opencodeVersion,path:a.opencodePath}),{path:a.opencodePath,version:a.opencodeVersion,didSetup:!0}}async function ho(e,t){let n={repo:e.agentContext.repo,commentId:e.agentContext.commentId,issueNumber:e.agentContext.issueNumber,issueType:e.agentContext.issueType,botLogin:e.botLogin},r=i({phase:`acknowledgment`});return await Yn(e.githubClient,n,r),t.debug(`Acknowledgment phase completed`),n}function go(e,t){try{JSON.parse(e)}catch{throw Error(`${t} must be valid JSON`)}}function _o(e,t){let n=e.trim();if(!/^\d+$/.test(n))throw Error(`${t} must be a positive integer, received: ${e}`);let r=Number.parseInt(n,10);if(r===0)throw Error(`${t} must be a positive integer, received: ${e}`);return r}function vo(e){let t=e.trim(),n=t.indexOf(`/`);if(n===-1)throw Error(`Invalid model format: "${e}". Expected "provider/model" (e.g., "anthropic/claude-sonnet-4-20250514")`);let r=t.slice(0,n).trim(),i=t.slice(n+1).trim();if(r.length===0)throw Error(`Invalid model format: "${e}". Provider cannot be empty.`);if(i.length===0)throw Error(`Invalid model format: "${e}". Model ID cannot be empty.`);return{providerID:r,modelID:i}}function yo(e,t=`timeout`){let n=e.trim();if(!/^\d+$/.test(n))throw Error(`${t} must be a non-negative integer, received: ${e}`);let r=Number.parseInt(n,10);if(Number.isNaN(r)||r<0)throw Error(`${t} must be a non-negative integer, received: ${e}`);return r}const bo=[`claude`,`claude-max20`,`copilot`,`gemini`,`openai`,`opencode-zen`,`zai-coding-plan`,`kimi-for-coding`];function xo(e){let t=e.split(`,`).map(e=>e.trim().toLowerCase()).filter(e=>e.length>0),n=`no`,r=`no`,i=`no`,a=`no`,o=`no`,s=`no`,c=`no`;for(let e of t){if(!bo.includes(e))throw Error(`Invalid omo-providers value: "${e}". Valid values: ${bo.join(`, `)}`);switch(e){case`claude`:n=`yes`;break;case`claude-max20`:n=`max20`;break;case`copilot`:r=`yes`;break;case`gemini`:i=`yes`;break;case`openai`:a=`yes`;break;case`opencode-zen`:o=`yes`;break;case`zai-coding-plan`:s=`yes`;break;case`kimi-for-coding`:c=`yes`;break}}return{claude:n,copilot:r,gemini:i,openai:a,opencodeZen:o,zaiCodingPlan:s,kimiForCoding:c}}function So(){try{let e=A(`github-token`,{required:!0}).trim();if(e.length===0)return lo(Error(`github-token is required but was not provided`));let t=A(`auth-json`,{required:!0}).trim();if(t.length===0)return lo(Error(`auth-json is required but was not provided`));go(t,`auth-json`);let r=A(`prompt`).trim(),i=r.length>0?r:null,a=A(`session-retention`).trim(),o=a.length>0?_o(a,`session-retention`):50,s=A(`s3-backup`).trim().toLowerCase()===`true`,c=A(`s3-bucket`).trim(),l=c.length>0?c:null,u=A(`aws-region`).trim(),d=u.length>0?u:null,f=A(`agent`).trim(),p=f.length>0?f:w,m=A(`model`).trim(),h=m.length>0?vo(m):null,_=A(`timeout`).trim(),v=_.length>0?yo(_):n,y=A(`opencode-version`).trim(),b=y.length>0?y:g,x=A(`skip-cache`).trim().toLowerCase()===`true`,S=A(`omo-version`).trim(),C=S.length>0?S:he,T=A(`omo-providers`).trim(),E=xo(T.length>0?T:``),D=A(`opencode-config`).trim(),O=D.length>0?D:null,k=A(`dedup-window`).trim(),ee=k.length>0?yo(k,`dedup-window`):ge;if(O!=null){go(O,`opencode-config`);let e=JSON.parse(O);if(typeof e!=`object`||!e||Array.isArray(e))throw Error(`Input 'opencode-config' must be a JSON object`)}return co({githubToken:e,authJson:t,prompt:i,sessionRetention:o,s3Backup:s,s3Bucket:l,awsRegion:d,agent:p,model:h,timeoutMs:v,opencodeVersion:b,skipCache:x,omoVersion:C,omoProviders:E,opencodeConfig:O,dedupWindow:ee})}catch(e){return lo(e instanceof Error?e:Error(String(e)))}}async function Co(e){let t=So();if(!t.success)return c(`Invalid inputs: ${t.error.message}`),null;let n=t.data,r=i({phase:`main`});r.info(`Action inputs parsed`,{sessionRetention:n.sessionRetention,s3Backup:n.s3Backup,hasGithubToken:n.githubToken.length>0,hasPrompt:n.prompt!=null,agent:n.agent,hasModelOverride:n.model!=null,timeoutMs:n.timeoutMs});let o=await mo({logger:r,opencodeVersion:n.opencodeVersion,githubToken:n.githubToken,authJson:n.authJson,omoVersion:n.omoVersion,omoProviders:n.omoProviders,opencodeConfig:n.opencodeConfig});return o.didSetup?r.info(`OpenCode auto-setup completed`,{version:o.version}):r.info(`OpenCode already available`,{version:o.version}),a(_.OPENCODE_VERSION,o.version),e.debug(`Bootstrap phase completed`,{opencodeVersion:o.version}),{inputs:n,logger:r,opencodeResult:o}}const wo=/^[0-9a-f]{40}$/i;function To(){return{exec:M,getExecOutput:t}}async function Eo(e){let{workspacePath:t,logger:n,execAdapter:i=To()}=e,a=L.join(t,`.git`),o=L.join(a,`opencode`);try{let e=(await I.readFile(o,`utf8`)).trim();if(e.length>0){if(wo.test(e))return n.debug(`Project ID loaded from cache`,{projectId:e}),{projectId:e,source:`cached`};n.warning(`Invalid cached project ID format, regenerating`,{cachedId:e})}}catch(e){n.debug(`No cached project ID found`,{error:r(e)})}try{let e=await I.readFile(a,`utf8`),n=/^gitdir: (.+)$/m.exec(e);if(n==null)return{projectId:null,source:`error`,error:`Invalid .git file format`};a=L.resolve(t,n[1]),o=L.join(a,`opencode`)}catch(e){if((typeof e==`object`?e.code:void 0)!==`EISDIR`)return{projectId:null,source:`error`,error:`Not a git repository`}}try{let{stdout:e,exitCode:a}=await i.getExecOutput(`git`,[`rev-list`,`--max-parents=0`,`--all`],{cwd:t,silent:!0});if(a!==0||e.trim().length===0)return{projectId:null,source:`error`,error:`No commits found in repository`};let s=e.trim().split(` -`).map(e=>e.trim()).filter(e=>e.length>0).sort();if(s.length===0)return{projectId:null,source:`error`,error:`No root commits found`};let c=s[0];try{await I.writeFile(o,c,{encoding:`utf8`,flag:`wx`}),n.info(`Project ID generated and cached`,{projectId:c,source:`generated`})}catch(e){(typeof e==`object`?e.code:void 0)===`EEXIST`?n.debug(`Project ID file already written by concurrent process, skipping`,{projectId:c}):n.warning(`Failed to cache project ID (continuing)`,{error:r(e)})}return{projectId:c,source:`generated`}}catch(e){return{projectId:null,source:`error`,error:r(e)}}}async function Do(e){let t=ne(),n=i({phase:`cache`}),r=re(),a=L.join(r,`.git`,`opencode`),o=await ae({components:t,logger:n,storagePath:k(),authPath:fe(),projectIdPath:a,opencodeVersion:e.opencodeResult.version}),s=o.corrupted?`corrupted`:o.hit?`hit`:`miss`;e.logger.info(`Cache restore completed`,{cacheStatus:s,key:o.key});let l=await Eo({workspacePath:r,logger:n});l.source===`error`?n.warning(`Failed to generate project ID (continuing)`,{error:l.error}):n.debug(`Project ID ready`,{projectId:l.projectId,source:l.source});let u=i({phase:`server-bootstrap`}),d=await fo(new AbortController().signal,u);if(!d.success)return c(`OpenCode server bootstrap failed: ${d.error.message}`),null;let f=d.data;return u.info(`SDK server bootstrapped successfully`),{cacheResult:o,cacheStatus:s,serverHandle:f}}const Oo={markdownImage:/!\[([^\]]*)\]\((https:\/\/github\.com\/user-attachments\/assets\/[^)]+)\)/gi,markdownLink:/\[([^\]]+)\]\((https:\/\/github\.com\/user-attachments\/files\/[^)]+)\)/gi,htmlImage:/]*src=["'](https:\/\/github\.com\/user-attachments\/assets\/[^"']+)["'][^>]*>/gi};function ko(e,t,n){e.lastIndex=0;let r=e.exec(t);for(;r!=null;)n(r),r=e.exec(t);e.lastIndex=0}function Ao(e){let t=[],n=new Set;return ko(Oo.markdownImage,e,e=>{let r=e[2],i=e[1],a=e[0];r!=null&&i!=null&&!n.has(r)&&Cn(r)&&(n.add(r),t.push({url:r,originalMarkdown:a,altText:i,type:`image`}))}),ko(Oo.markdownLink,e,e=>{let r=e[2],i=e[1],a=e[0];r!=null&&i!=null&&!n.has(r)&&Cn(r)&&(n.add(r),t.push({url:r,originalMarkdown:a,altText:i,type:`file`}))}),ko(Oo.htmlImage,e,e=>{let r=e[1],i=e[0];if(r!=null&&!n.has(r)&&Cn(r)){n.add(r);let e=/alt=["']([^"']*)["']/i.exec(i);t.push({url:r,originalMarkdown:i,altText:e?.[1]??``,type:`image`})}}),Oo.htmlImage.lastIndex=0,t}function jo(e,t,n){try{let r=new URL(e).pathname.split(`/`).at(-1);if(r!=null&&/\.[a-z0-9]+$/i.test(r))return r;if(t.trim().length>0){let e=t.replaceAll(/[^\w.-]/g,`_`).slice(0,50);return e.trim().length>0?e:`attachment_${n+1}`}return`attachment_${n+1}`}catch{return`attachment_${n+1}`}}const Mo={maxFiles:5,maxFileSizeBytes:5*1024*1024,maxTotalSizeBytes:15*1024*1024,allowedMimeTypes:[`image/png`,`image/jpeg`,`image/gif`,`image/webp`,`image/svg+xml`,`text/plain`,`text/markdown`,`text/csv`,`application/json`,`application/pdf`]},No=[`github.com`,`githubusercontent.com`];async function Po(e,t,n,i,a){a.debug(`Downloading attachment`,{url:e.url});try{let r=await fetch(e.url,{headers:{Authorization:`Bearer ${n}`,Accept:`*/*`,"User-Agent":`fro-bot-agent`},redirect:`manual`}),o=r;if(r.status>=300&&r.status<400){let t=r.headers.get(`location`);if(t==null)return a.warning(`Redirect without location`,{url:e.url}),null;let n=new URL(t);if(!No.some(e=>n.hostname===e||n.hostname.endsWith(`.${e}`)))return a.warning(`Redirect to non-GitHub host blocked`,{url:e.url,redirectTo:n.hostname}),null;o=await fetch(t,{headers:{Accept:`*/*`,"User-Agent":`fro-bot-agent`},redirect:`follow`})}if(!o.ok)return a.warning(`Attachment download failed`,{url:e.url,status:o.status}),null;let s=o.headers.get(`content-length`);if(s!=null){let t=Number.parseInt(s,10);if(t>i.maxFileSizeBytes)return a.warning(`Attachment exceeds size limit (Content-Length)`,{url:e.url,size:t,limit:i.maxFileSizeBytes}),null}let c=we.from(await o.arrayBuffer());if(c.length>i.maxFileSizeBytes)return a.warning(`Attachment exceeds size limit`,{url:e.url,size:c.length,limit:i.maxFileSizeBytes}),null;let l=o.headers.get(`content-type`)??`application/octet-stream`,u=jo(e.url,e.altText,t),d=l.split(`;`)[0],f=d==null?`application/octet-stream`:d.trim(),p=await I.mkdtemp(L.join(Ae.tmpdir(),`fro-bot-attachments-`)),m=u.trim().length>0?u:`attachment_${t+1}`,h=L.join(p,m);return await I.writeFile(h,c),a.debug(`Attachment downloaded`,{filename:u,mime:f,sizeBytes:c.length,tempPath:h}),{url:e.url,filename:u,mime:f,sizeBytes:c.length,tempPath:h}}catch(t){return a.warning(`Attachment download error`,{url:e.url,error:r(t)}),null}}async function Fo(e,t,n=Mo,r){return Promise.all(e.map(async(e,i)=>Po(e,i,t,n,r)))}async function Io(e,t){for(let n of e)try{await I.unlink(n);let e=L.dirname(n);await I.rmdir(e).catch(()=>{})}catch(e){t.debug(`Failed to cleanup temp file`,{path:n,error:r(e)})}}function Lo(e){return e.map(e=>({type:`file`,mime:e.mime,url:Ee(e.tempPath).toString(),filename:e.filename}))}function Ro(e,t,n){let r=e,i=new Set(n.map(e=>e.filename));for(let e of t){let t=n.find(e=>i.has(e.filename));t!=null&&(r=r.replace(e.originalMarkdown,`@${t.filename}`))}return r}function zo(e,t,n,r){return{processed:n,skipped:r,modifiedBody:Ro(e,t,n),fileParts:Lo(n),tempFiles:n.map(e=>e.tempPath)}}function Bo(e){if(e<0||!Number.isFinite(e))throw Error(`Invalid bytes value: ${e}`);return e<1024?`${e}B`:e<1024*1024?`${(e/1024).toFixed(1)}KB`:`${(e/(1024*1024)).toFixed(1)}MB`}function Vo(e,t=Mo,n){let r=[],i=[],a=0;for(let o of e)if(o!=null){if(r.length>=t.maxFiles){i.push({url:o.url,reason:`Exceeds max file count (${t.maxFiles})`}),n.debug(`Attachment skipped: max count`,{url:o.url});continue}if(o.sizeBytes>t.maxFileSizeBytes){i.push({url:o.url,reason:`File too large (${Bo(o.sizeBytes)} > ${Bo(t.maxFileSizeBytes)})`}),n.debug(`Attachment skipped: too large`,{url:o.url,size:o.sizeBytes});continue}if(a+o.sizeBytes>t.maxTotalSizeBytes){i.push({url:o.url,reason:`Would exceed total size limit (${Bo(t.maxTotalSizeBytes)})`}),n.debug(`Attachment skipped: total size exceeded`,{url:o.url});continue}if(!Ho(o.mime,t.allowedMimeTypes)){i.push({url:o.url,reason:`MIME type not allowed: ${o.mime}`}),n.debug(`Attachment skipped: MIME type`,{url:o.url,mime:o.mime});continue}a+=o.sizeBytes,r.push({filename:o.filename,mime:o.mime,sizeBytes:o.sizeBytes,tempPath:o.tempPath}),n.info(`Attachment validated`,{filename:o.filename,mime:o.mime,sizeBytes:o.sizeBytes})}return{validated:r,skipped:i}}function Ho(e,t){let[n]=e.split(`/`);for(let r of t)if(r===e||r.endsWith(`/*`)&&n!=null&&n===r.slice(0,-2))return!0;return!1}function Y(e){let t=L.resolve(e);return t.endsWith(L.sep)&&t.length>1?t.slice(0,-1):t}const Uo=e=>typeof e==`object`&&!!e,X=e=>typeof e==`string`?e:null,Wo=e=>typeof e==`number`?e:null;function Go(e){if(Array.isArray(e))return e.filter(Uo).map(e=>({file:X(e.file)??``,additions:Wo(e.additions)??0,deletions:Wo(e.deletions)??0}))}function Ko(e){return{id:e.id,version:e.version,projectID:e.projectID,directory:e.directory,parentID:e.parentID,title:e.title,time:{created:e.time.created,updated:e.time.updated,compacting:e.time.compacting,archived:e.time.archived},summary:e.summary==null?void 0:{additions:e.summary.additions,deletions:e.summary.deletions,files:e.summary.files,diffs:Go(e.summary.diffs)},share:e.share?.url==null?void 0:{url:e.share.url},permission:e.permission==null?void 0:{rules:e.permission.rules},revert:e.revert==null?void 0:{messageID:e.revert.messageID,partID:e.revert.partID,snapshot:e.revert.snapshot,diff:e.revert.diff}}}async function qo(e,t){let n=await e.project.list();if(n.error!=null||n.data==null)return t.warning(`SDK project list failed`,{error:String(n.error)}),[];if(!Array.isArray(n.data))return[];let r=[];for(let e of n.data){if(!Uo(e))continue;let t=X(e.id),n=X(e.worktree),i=X(e.path);t==null||n==null||i==null||r.push({id:t,worktree:n,path:i,vcs:`git`,time:{created:0,updated:0}})}return r}async function Jo(e,t,n){let r=Y(t),i=await qo(e,n);for(let e of i){if(Y(e.worktree)===r)return e;let t=X(e.path);if(t!=null&&Y(t)===r)return e}return null}function Yo(e){return e.status===`running`?{status:`running`,input:e.input,time:{start:e.time.start}}:e.status===`error`?{status:`error`,input:e.input,error:e.error,time:{start:e.time.start,end:e.time.end}}:e.status===`pending`?{status:`pending`}:{status:`completed`,input:e.input,output:e.output,title:e.title,metadata:e.metadata,time:{start:e.time.start,end:e.time.end,compacted:e.time.compacted},attachments:void 0}}function Xo(e){let t={id:e.id,sessionID:e.sessionID,messageID:e.messageID};if(e.type===`text`)return{...t,type:`text`,text:e.text,synthetic:e.synthetic,ignored:e.ignored,time:e.time,metadata:e.metadata};if(e.type===`reasoning`)return{...t,type:`reasoning`,reasoning:e.reasoning??e.text,time:e.time};if(e.type===`tool`)return{...t,type:`tool`,callID:e.callID,tool:e.tool,state:Yo(e.state),metadata:e.metadata};if(e.type!==`step-finish`)return{...t,type:`text`,text:`text`in e?e.text:``};let n=e;return{...t,type:`step-finish`,reason:n.reason,snapshot:n.snapshot,cost:n.cost,tokens:{input:n.tokens.input,output:n.tokens.output,reasoning:n.tokens.reasoning,cache:{read:n.tokens.cache.read,write:n.tokens.cache.write}}}}function Zo(e){if(e.role===`user`){let t=e;return{id:t.id,sessionID:t.sessionID,role:`user`,time:{created:t.time.created},summary:t.summary==null?void 0:{title:t.summary.title,body:t.summary.body,diffs:Go(t.summary.diffs)??[]},agent:t.agent,model:{providerID:t.model.providerID,modelID:t.model.modelID},system:t.system,tools:t.tools,variant:t.variant}}let t=e;return{id:t.id,sessionID:t.sessionID,role:`assistant`,time:{created:t.time.created,completed:t.time.completed},parentID:t.parentID,modelID:t.modelID,providerID:t.providerID,mode:t.mode,agent:t.agent??``,path:{cwd:t.path.cwd,root:t.path.root},summary:t.summary,cost:t.cost,tokens:{input:t.tokens.input,output:t.tokens.output,reasoning:t.tokens.reasoning,cache:{read:t.tokens.cache.read,write:t.tokens.cache.write}},finish:t.finish,error:t.error?{name:t.error.name,message:X(t.error.data.message)??``}:void 0}}function Qo(e){return[...e.map(e=>{let t=Zo(`info`in e?e.info:e),n=`parts`in e?e.parts.map(Xo):void 0;return n==null||n.length===0?t:{...t,parts:n}})].sort((e,t)=>e.time.created-t.time.created)}async function $o(e,t,n){let r=await e.session.list({query:{directory:t}});return r.error==null&&r.data!=null?Array.isArray(r.data)?r.data.map(Ko):[]:(n.warning(`SDK session list failed`,{error:String(r.error)}),[])}async function es(e,t,n){let r=await e.session.messages({path:{id:t}});return r.error==null&&r.data!=null?Qo(r.data):(n.warning(`SDK session messages failed`,{error:String(r.error)}),[])}async function ts(e,t,n,r){let i=await e.session.list({query:{directory:t,start:n,roots:!0,limit:10}});if(i.error!=null||i.data==null)return r.warning(`SDK session list failed`,{error:String(i.error)}),null;if(!Array.isArray(i.data)||i.data.length===0)return null;let a=i.data.map(Ko);if(a.length===0)return null;let o=a.reduce((e,t)=>t.time.created>e.time.created?t:e);return{projectID:o.projectID,session:o}}async function ns(e,t,n){let r=await e.session.delete({path:{id:t}});if(r.error!=null){n.warning(`SDK session delete failed`,{sessionID:t,error:String(r.error)});return}n.debug(`Deleted session via SDK`,{sessionID:t})}const rs={maxSessions:50,maxAgeDays:30};async function is(e,t,n,i){let{maxSessions:a,maxAgeDays:o}=n;if(i.info(`Starting session pruning`,{workspacePath:t,maxSessions:a,maxAgeDays:o}),await Jo(e,t,i)==null)return i.debug(`No project found for pruning`,{workspacePath:t}),{prunedCount:0,prunedSessionIds:[],remainingCount:0,freedBytes:0};let s=await $o(e,t,i),c=s.filter(e=>e.parentID==null);if(c.length===0)return{prunedCount:0,prunedSessionIds:[],remainingCount:0,freedBytes:0};let l=[...c].sort((e,t)=>t.time.updated-e.time.updated),u=new Date;u.setDate(u.getDate()-o);let d=u.getTime(),f=new Set;for(let e of l)e.time.updated>=d&&f.add(e.id);for(let e=0;e!f.has(e.id)),m=new Set;for(let e of p){m.add(e.id);for(let t of s)t.parentID===e.id&&m.add(t.id)}if(m.size===0)return i.info(`No sessions to prune`),{prunedCount:0,prunedSessionIds:[],remainingCount:c.length,freedBytes:0};let h=[];for(let t of m)try{await ns(e,t,i),h.push(t),i.debug(`Pruned session`,{sessionId:t})}catch(e){i.warning(`Failed to prune session`,{sessionId:t,error:r(e)})}let g=c.length-p.length;return i.info(`Session pruning complete`,{prunedCount:h.length,remainingCount:g}),{prunedCount:h.length,prunedSessionIds:h,remainingCount:g,freedBytes:0}}async function as(e,t,n,r){let{limit:i,fromDate:a,toDate:o}=n;r.debug(`Listing sessions`,{directory:t,limit:i});let s=[...(await $o(e,t,r)).filter(e=>!(e.parentID!=null||a!=null&&e.time.createdo.getTime()))].sort((e,t)=>t.time.updated-e.time.updated),c=[],l=i==null?s:s.slice(0,i);for(let t of l){let n=await es(e,t.id,r),i=os(n);c.push({id:t.id,projectID:t.projectID,directory:t.directory,title:t.title,createdAt:t.time.created,updatedAt:t.time.updated,messageCount:n.length,agents:i,isChild:!1})}return r.info(`Listed sessions`,{count:c.length,directory:t}),c}function os(e){let t=new Set;for(let n of e)n.agent!=null&&t.add(n.agent);return[...t]}async function ss(e,t,n,r,i){let{limit:a=20,caseSensitive:o=!1,sessionId:s}=r;i.debug(`Searching sessions`,{query:e,directory:n,limit:a,caseSensitive:o});let c=o?e:e.toLowerCase(),l=[],u=0;if(s!=null){let e=await cs(t,s,c,o,i);return e.length>0&&l.push({sessionId:s,matches:e.slice(0,a)}),l}let d=await as(t,n,{},i);for(let e of d){if(u>=a)break;let n=await cs(t,e.id,c,o,i);if(n.length>0){let t=a-u;l.push({sessionId:e.id,matches:n.slice(0,t)}),u+=Math.min(n.length,t)}}return i.info(`Session search complete`,{query:e,resultCount:l.length,totalMatches:u}),l}async function cs(e,t,n,r,i){let a=await es(e,t,i),o=[];for(let e of a){let t=e.parts??[];for(let i of t){let t=ls(i);if(t==null)continue;let a=r?t:t.toLowerCase();if(a.includes(n)){let r=a.indexOf(n),s=Math.max(0,r-50),c=Math.min(t.length,r+n.length+50),l=t.slice(s,c);o.push({messageId:e.id,partId:i.id,excerpt:`...${l}...`,role:e.role,agent:e.agent})}}}return o}function ls(e){switch(e.type){case`text`:return e.text;case`reasoning`:return e.reasoning;case`tool`:return e.state.status===`completed`?`${e.tool}: ${e.state.output}`:null;case`step-finish`:return null}}function us(e){let t=[`--- Fro Bot Run Summary ---`,`Event: ${e.eventType}`,`Repo: ${e.repo}`,`Ref: ${e.ref}`,`Run ID: ${e.runId}`,`Cache: ${e.cacheStatus}`,`Duration: ${e.duration}s`];return e.sessionIds.length>0&&t.push(`Sessions used: ${e.sessionIds.join(`, `)}`),e.createdPRs.length>0&&t.push(`PRs created: ${e.createdPRs.join(`, `)}`),e.createdCommits.length>0&&t.push(`Commits: ${e.createdCommits.join(`, `)}`),e.tokenUsage!=null&&t.push(`Tokens: ${e.tokenUsage.input} in / ${e.tokenUsage.output} out`),t.join(` -`)}async function ds(e,t,n,i){let a=us(t);try{let t=await n.session.prompt({path:{id:e},body:{noReply:!0,parts:[{type:`text`,text:a}]}});if(t.error!=null){i.warning(`SDK prompt writeback failed`,{sessionId:e,error:String(t.error)});return}i.info(`Session summary written via SDK`,{sessionId:e})}catch(t){i.warning(`SDK prompt writeback failed`,{sessionId:e,error:r(t)})}}async function fs(e){let{bootstrapLogger:t,reactionCtx:n,githubClient:r,agentSuccess:o,attachmentResult:s,serverHandle:c,detectedOpencodeVersion:l}=e;try{if(s!=null){let e=i({phase:`attachment-cleanup`});await Io(s.tempFiles,e)}n!=null&&r!=null&&await tr(r,n,o,i({phase:`cleanup`}));let e=i({phase:`prune`}),t=re();if(c!=null){let n=Y(t),r=await is(c.client,n,rs,e);r.prunedCount>0&&e.info(`Pruned old sessions`,{pruned:r.prunedCount,remaining:r.remainingCount})}let u=ne(),d=i({phase:`cache-save`}),f=L.join(t,`.git`,`opencode`);await de({components:u,runId:D(),logger:d,storagePath:k(),authPath:fe(),projectIdPath:f,opencodeVersion:l})&&a(_.CACHE_SAVED,`true`)}catch(e){t.warning(`Cleanup failed (non-fatal)`,{error:e instanceof Error?e.message:String(e)})}finally{if(c!=null)try{c.shutdown()}catch(e){t.warning(`Server shutdown failed (non-fatal)`,{error:e instanceof Error?e.message:String(e)})}}}const ps=L.join(je.homedir(),`.cache`,`fro-bot-dedup`);function ms(e){return e.replaceAll(`/`,`-`)}function hs(e,t){let n=ms(e);return L.join(ps,`${n}-${t.entityType}-${t.entityNumber}`)}function gs(e,t){return`${ee}-${ms(e)}-${t.entityType}-${t.entityNumber}-`}function _s(e,t,n){return`${gs(e,t)}${n}`}async function vs(e,t,n,i=le){let a=hs(e,t),o=L.join(a,`sentinel.json`),s=gs(e,t);try{if(await I.rm(a,{recursive:!0,force:!0}),await I.mkdir(a,{recursive:!0}),await i.restoreCache([a],s,[])==null)return null;let e=await I.readFile(o,`utf8`);return JSON.parse(e)}catch(e){return n.debug(`Dedup marker restore failed; proceeding without marker`,{error:r(e),entityType:t.entityType,entityNumber:t.entityNumber}),null}}async function ys(e,t,n,i,a=le){let o=hs(e,t),s=L.join(o,`sentinel.json`),c=_s(e,t,n.runId);try{return await I.mkdir(o,{recursive:!0}),await I.writeFile(s,JSON.stringify(n),`utf8`),await a.saveCache([o],c),!0}catch(e){return r(e).toLowerCase().includes(`already exists`)?!0:(i.debug(`Dedup marker save failed`,{error:r(e),entityType:t.entityType,entityNumber:t.entityNumber,saveKey:c}),!1)}}const bs=new Set([`pull_request`,`issues`]);function xs(e){return e.target==null||!bs.has(e.eventType)?null:e.eventType===`pull_request`&&e.target.kind===`pr`?{entityType:`pr`,entityNumber:e.target.number}:e.eventType===`issues`&&e.target.kind===`issue`?{entityType:`issue`,entityNumber:e.target.number}:null}async function Ss(e,t,n,r,a=i({phase:`dedup`}),o){let s=xs(t);if(e===0)return{shouldProceed:!0,entity:s};if(s==null)return{shouldProceed:!0,entity:null};let c=await vs(n,s,a,o);if(c==null||c.runId===t.runId)return{shouldProceed:!0,entity:s};let l=new Date(c.timestamp).getTime();if(Number.isNaN(l))return a.warning(`Dedup marker timestamp is invalid; proceeding without dedup`,{markerTimestamp:c.timestamp}),{shouldProceed:!0,entity:s};let u=Date.now()-l;return u<-6e4?(a.warning(`Dedup marker timestamp is too far in the future; proceeding without dedup`,{markerTimestamp:c.timestamp,markerAge:u}),{shouldProceed:!0,entity:s}):Math.max(0,u)>e?{shouldProceed:!0,entity:s}:(a.info(`Skipping duplicate trigger within dedup window`,{eventType:t.eventType,action:t.action,runId:t.runId,markerRunId:c.runId,markerTimestamp:c.timestamp,dedupWindow:e,entityType:s.entityType,entityNumber:s.entityNumber}),Re({sessionId:null,cacheStatus:`miss`,duration:Date.now()-r}),{shouldProceed:!1,entity:s})}async function Cs(e,t,n,r=i({phase:`dedup`}),a){await ys(n,t,{timestamp:new Date().toISOString(),runId:e.runId,action:e.action??`unknown`,eventType:e.eventType,entityType:t.entityType,entityNumber:t.entityNumber},r,a)}async function ws(e,t,n,r,o,s){let c={context:t.agentContext,customPrompt:e.inputs.prompt,cacheStatus:n.cacheStatus,sessionContext:{recentSessions:r.recentSessions,priorWorkContext:r.priorWorkContext},triggerContext:t.triggerResult.context,fileParts:r.attachmentResult?.fileParts},l=N.env.SKIP_AGENT_EXECUTION===`true`,u=Date.now(),d;if(l)e.logger.info(`Skipping agent execution (SKIP_AGENT_EXECUTION=true)`),d={success:!0,exitCode:0,sessionId:null,error:null,tokenUsage:null,model:null,cost:null,prsCreated:[],commitsCreated:[],commentsPosted:0,llmError:null};else{let t=await Kn(c,i({phase:`execution`}),{agent:e.inputs.agent,model:e.inputs.model,timeoutMs:e.inputs.timeoutMs,omoProviders:e.inputs.omoProviders},n.serverHandle),a=t.sessionId;if(a==null){let e=i({phase:`session`}),t=await ts(n.serverHandle.client,r.normalizedWorkspace,u,e);t!=null&&(a=t.session.id,e.debug(`Identified session from execution`,{sessionId:a}))}d={...t,sessionId:a}}d.sessionId!=null&&(a(_.SESSION_ID,d.sessionId),o.addSessionCreated(d.sessionId)),d.tokenUsage!=null&&o.setTokenUsage(d.tokenUsage,d.model,d.cost);for(let e of d.prsCreated)o.addPRCreated(e);for(let e of d.commitsCreated)o.addCommitCreated(e);for(let e=0;e`)}async function Ds(e,t,n){try{if(t.type===`pr`){let{data:n}=await e.rest.pulls.get({owner:t.owner,repo:t.repo,pull_number:t.number});return{title:n.title,body:n.body??``,author:n.user?.login??`unknown`}}let{data:n}=await e.rest.issues.get({owner:t.owner,repo:t.repo,issue_number:t.number});return{title:n.title,body:n.body??``,author:n.user?.login??`unknown`}}catch(e){return n.warning(`Failed to fetch issue/PR`,{target:t,error:r(e)}),null}}async function Os(e,t,n,i){let a=[],o=1;for(;o<=50;)try{let{data:r}=await e.rest.issues.listComments({owner:t.owner,repo:t.repo,issue_number:t.number,per_page:100,page:o});if(r.length===0)break;for(let e of r){let t=e.user?.login??`unknown`;a.push({id:e.id,body:e.body??``,author:t,authorAssociation:e.author_association??`NONE`,createdAt:e.created_at,updatedAt:e.updated_at,isBot:Es(t,e.body??``,n)})}if(r.length<100)break;o++}catch(e){i.warning(`Failed to fetch comments page`,{target:t,page:o,error:r(e)});break}return a}async function ks(e,t,n,i){try{let r=[],a=null,o=null,s=``,c=``,l=`unknown`,u=0;for(;u<50;){let d=(await e.graphql(` +`)}var Di=class extends Error{constructor(e,t,n){super(Ei(n)),this.request=e,this.headers=t,this.response=n,this.errors=n.errors,this.data=n.data,Error.captureStackTrace&&Error.captureStackTrace(this,this.constructor)}name=`GraphqlResponseError`;errors;data},Oi=[`method`,`baseUrl`,`url`,`headers`,`request`,`query`,`mediaType`,`operationName`],ki=[`query`,`method`,`url`],Ai=/\/api\/v3\/?$/;function ji(e,t,n){if(n){if(typeof t==`string`&&`query`in n)return Promise.reject(Error(`[@octokit/graphql] "query" cannot be used as variable name`));for(let e in n)if(ki.includes(e))return Promise.reject(Error(`[@octokit/graphql] "${e}" cannot be used as variable name`))}let r=typeof t==`string`?Object.assign({query:t},n):t,i=Object.keys(r).reduce((e,t)=>Oi.includes(t)?(e[t]=r[t],e):(e.variables||={},e.variables[t]=r[t],e),{}),a=r.baseUrl||e.endpoint.DEFAULTS.baseUrl;return Ai.test(a)&&(i.url=a.replace(Ai,`/api/graphql`)),e(i).then(e=>{if(e.data.errors){let t={};for(let n of Object.keys(e.headers))t[n]=e.headers[n];throw new Di(i,t,e.data)}return e.data.data})}function Mi(e,t){let n=e.defaults(t);return Object.assign((e,t)=>ji(n,e,t),{defaults:Mi.bind(null,n),endpoint:n.endpoint})}Mi(wi,{headers:{"user-agent":`octokit-graphql.js/${Ti} ${br()}`},method:`POST`,url:`/graphql`});function Ni(e){return Mi(e,{method:`POST`,url:`/graphql`})}var Pi=`(?:[a-zA-Z0-9_-]+)`,Fi=`\\.`,Ii=RegExp(`^${Pi}${Fi}${Pi}${Fi}${Pi}$`),Li=Ii.test.bind(Ii);async function Ri(e){let t=Li(e),n=e.startsWith(`v1.`)||e.startsWith(`ghs_`),r=e.startsWith(`ghu_`);return{type:`token`,token:e,tokenType:t?`app`:n?`installation`:r?`user-to-server`:`oauth`}}function zi(e){return e.split(/\./).length===3?`bearer ${e}`:`token ${e}`}async function Bi(e,t,n,r){let i=t.endpoint.merge(n,r);return i.headers.authorization=zi(e),t(i)}var Vi=function(e){if(!e)throw Error(`[@octokit/auth-token] No token passed to createTokenAuth`);if(typeof e!=`string`)throw Error(`[@octokit/auth-token] Token passed to createTokenAuth is not a string`);return e=e.replace(/^(token|bearer) +/i,``),Object.assign(Ri.bind(null,e),{hook:Bi.bind(null,e)})};const Hi=`7.0.6`,Ui=()=>{},Wi=console.warn.bind(console),Gi=console.error.bind(console);function Ki(e={}){return typeof e.debug!=`function`&&(e.debug=Ui),typeof e.info!=`function`&&(e.info=Ui),typeof e.warn!=`function`&&(e.warn=Wi),typeof e.error!=`function`&&(e.error=Gi),e}const qi=`octokit-core.js/${Hi} ${br()}`;var Ji=class{static VERSION=Hi;static defaults(e){return class extends this{constructor(...t){let n=t[0]||{};if(typeof e==`function`){super(e(n));return}super(Object.assign({},e,n,n.userAgent&&e.userAgent?{userAgent:`${n.userAgent} ${e.userAgent}`}:null))}}}static plugins=[];static plugin(...e){let t=this.plugins;return class extends this{static plugins=t.concat(e.filter(e=>!t.includes(e)))}}constructor(e={}){let t=new kr.Collection,n={baseUrl:wi.endpoint.DEFAULTS.baseUrl,headers:{},request:Object.assign({},e.request,{hook:t.bind(null,`request`)}),mediaType:{previews:[],format:``}};if(n.headers[`user-agent`]=e.userAgent?`${e.userAgent} ${qi}`:qi,e.baseUrl&&(n.baseUrl=e.baseUrl),e.previews&&(n.mediaType.previews=e.previews),e.timeZone&&(n.headers[`time-zone`]=e.timeZone),this.request=wi.defaults(n),this.graphql=Ni(this.request).defaults(n),this.log=Ki(e.log),this.hook=t,e.authStrategy){let{authStrategy:n,...r}=e,i=n(Object.assign({request:this.request,log:this.log,octokit:this,octokitOptions:r},e.auth));t.wrap(`request`,i.hook),this.auth=i}else if(!e.auth)this.auth=async()=>({type:`unauthenticated`});else{let n=Vi(e.auth);t.wrap(`request`,n.hook),this.auth=n}let r=this.constructor;for(let t=0;t({async next(){if(!s)return{done:!0};try{let e=ra(await i({method:a,url:s,headers:o}));if(s=((e.headers.link||``).match(/<([^<>]+)>;\s*rel="next"/)||[])[1],!s&&`total_commits`in e.data){let t=new URL(e.url),n=t.searchParams,r=parseInt(n.get(`page`)||`1`,10);r*parseInt(n.get(`per_page`)||`250`,10){if(i.done)return t;let a=!1;function o(){a=!0}return t=t.concat(r?r(i.value,o):i.value.data),a?t:oa(e,t,n,r)})}Object.assign(aa,{iterator:ia});function sa(e){return{paginate:Object.assign(aa.bind(null,e),{iterator:ia.bind(null,e)})}}sa.VERSION=na,new ur;const ca=yr(),la={baseUrl:ca,request:{agent:gr(ca),fetch:vr(ca)}},ua=Ji.plugin(ea,sa).defaults(la);function da(e,t){let n=Object.assign({},t||{}),r=hr(e,n);return r&&(n.auth=r),n}const fa=new ur;function pa(e,t,...n){return new(ua.plugin(...n))(da(e,t))}var K=C(ue(),1),ma=function(e,t,n,r){function i(e){return e instanceof n?e:new n(function(t){t(e)})}return new(n||=Promise)(function(n,a){function o(e){try{c(r.next(e))}catch(e){a(e)}}function s(e){try{c(r.throw(e))}catch(e){a(e)}}function c(e){e.done?n(e.value):i(e.value).then(o,s)}c((r=r.apply(e,t||[])).next())})},ha=class{constructor(e,t,n){if(e<1)throw Error(`max attempts should be greater than or equal to 1`);if(this.maxAttempts=e,this.minSeconds=Math.floor(t),this.maxSeconds=Math.floor(n),this.minSeconds>this.maxSeconds)throw Error(`min seconds should be less than or equal to max seconds`)}execute(e,t){return ma(this,void 0,void 0,function*(){let n=1;for(;nsetTimeout(t,e*1e3))})}},q=function(e,t,n,r){function i(e){return e instanceof n?e:new n(function(t){t(e)})}return new(n||=Promise)(function(n,a){function o(e){try{c(r.next(e))}catch(e){a(e)}}function s(e){try{c(r.throw(e))}catch(e){a(e)}}function c(e){e.done?n(e.value):i(e.value).then(o,s)}c((r=r.apply(e,t||[])).next())})},ga=class extends Error{constructor(e){super(`Unexpected HTTP response: ${e}`),this.httpStatusCode=e,Object.setPrototypeOf(this,new.target.prototype)}};const _a=process.platform===`win32`;process.platform;function va(e,t,n,r){return q(this,void 0,void 0,function*(){return t||=F.join(Na(),ye.randomUUID()),yield s(F.dirname(t)),j(`Downloading ${e}`),j(`Destination ${t}`),yield new ha(3,Pa(`TEST_DOWNLOAD_TOOL_RETRY_MIN_SECONDS`,10),Pa(`TEST_DOWNLOAD_TOOL_RETRY_MAX_SECONDS`,20)).execute(()=>q(this,void 0,void 0,function*(){return yield ya(e,t||``,n,r)}),e=>!(e instanceof ga&&e.httpStatusCode&&e.httpStatusCode<500&&e.httpStatusCode!==408&&e.httpStatusCode!==429))})}function ya(e,t,n,r){return q(this,void 0,void 0,function*(){if(P.existsSync(t))throw Error(`Destination file path ${t} already exists`);let i=new u(`actions/tool-cache`,[],{allowRetries:!1});n&&(j(`set auth`),r===void 0&&(r={}),r.authorization=n);let a=yield i.get(e,r);if(a.message.statusCode!==200){let t=new ga(a.message.statusCode);throw j(`Failed to download from "${e}". Code(${a.message.statusCode}) Message(${a.message.statusMessage})`),t}let o=Ce.promisify(Fe.pipeline),s=Pa(`TEST_DOWNLOAD_TOOL_RESPONSE_MESSAGE_FACTORY`,()=>a.message)(),c=!1;try{return yield o(s,P.createWriteStream(t)),j(`download complete`),c=!0,t}finally{if(!c){j(`download failed`);try{yield v(t)}catch(e){j(`Failed to delete '${t}'. ${e.message}`)}}}})}function ba(e,t){return q(this,arguments,void 0,function*(e,t,n=`xz`){if(!e)throw Error(`parameter 'file' is required`);t=yield Da(t),j(`Checking tar --version`);let r=``;yield M(`tar --version`,[],{ignoreReturnCode:!0,silent:!0,listeners:{stdout:e=>r+=e.toString(),stderr:e=>r+=e.toString()}}),j(r.trim());let i=r.toUpperCase().includes(`GNU TAR`),a;a=n instanceof Array?n:[n],m()&&!n.includes(`v`)&&a.push(`-v`);let o=t,s=e;return _a&&i&&(a.push(`--force-local`),o=t.replace(/\\/g,`/`),s=e.replace(/\\/g,`/`)),i&&(a.push(`--warning=no-unknown-keyword`),a.push(`--overwrite`)),a.push(`-C`,o,`-f`,s),yield M(`tar`,a),t})}function xa(e,t){return q(this,void 0,void 0,function*(){if(!e)throw Error(`parameter 'file' is required`);return t=yield Da(t),_a?yield Sa(e,t):yield Ca(e,t),t})}function Sa(e,t){return q(this,void 0,void 0,function*(){let n=e.replace(/'/g,`''`).replace(/"|\n|\r/g,``),r=t.replace(/'/g,`''`).replace(/"|\n|\r/g,``),i=yield b(`pwsh`,!1);if(i){let e=[`-NoLogo`,`-NoProfile`,`-NonInteractive`,`-ExecutionPolicy`,`Unrestricted`,`-Command`,[`$ErrorActionPreference = 'Stop' ;`,`try { Add-Type -AssemblyName System.IO.Compression.ZipFile } catch { } ;`,`try { [System.IO.Compression.ZipFile]::ExtractToDirectory('${n}', '${r}', $true) }`,`catch { if (($_.Exception.GetType().FullName -eq 'System.Management.Automation.MethodException') -or ($_.Exception.GetType().FullName -eq 'System.Management.Automation.RuntimeException') ){ Expand-Archive -LiteralPath '${n}' -DestinationPath '${r}' -Force } else { throw $_ } } ;`].join(` `)];j(`Using pwsh at path: ${i}`),yield M(`"${i}"`,e)}else{let e=[`-NoLogo`,`-Sta`,`-NoProfile`,`-NonInteractive`,`-ExecutionPolicy`,`Unrestricted`,`-Command`,[`$ErrorActionPreference = 'Stop' ;`,`try { Add-Type -AssemblyName System.IO.Compression.FileSystem } catch { } ;`,`if ((Get-Command -Name Expand-Archive -Module Microsoft.PowerShell.Archive -ErrorAction Ignore)) { Expand-Archive -LiteralPath '${n}' -DestinationPath '${r}' -Force }`,`else {[System.IO.Compression.ZipFile]::ExtractToDirectory('${n}', '${r}', $true) }`].join(` `)],t=yield b(`powershell`,!0);j(`Using powershell at path: ${t}`),yield M(`"${t}"`,e)}})}function Ca(e,t){return q(this,void 0,void 0,function*(){let n=yield b(`unzip`,!0),r=[e];m()||r.unshift(`-q`),r.unshift(`-o`),yield M(`"${n}"`,r,{cwd:t})})}function wa(e,t,n,r){return q(this,void 0,void 0,function*(){if(n=K.clean(n)||n,r||=_e.arch(),j(`Caching tool ${t} ${n} ${r}`),j(`source dir: ${e}`),!P.statSync(e).isDirectory())throw Error(`sourceDir is not a directory`);let i=yield Oa(t,n,r);for(let t of P.readdirSync(e))yield y(F.join(e,t),i,{recursive:!0});return ka(t,n,r),i})}function Ta(e,t,n){if(!e)throw Error(`toolName parameter is required`);if(!t)throw Error(`versionSpec parameter is required`);n||=_e.arch(),Aa(t)||(t=ja(Ea(e,n),t));let r=``;if(t){t=K.clean(t)||``;let i=F.join(Ma(),e,t,n);j(`checking cache: ${i}`),P.existsSync(i)&&P.existsSync(`${i}.complete`)?(j(`Found tool in cache ${e} ${t} ${n}`),r=i):j(`not found`)}return r}function Ea(e,t){let n=[];t||=_e.arch();let r=F.join(Ma(),e);if(P.existsSync(r)){let e=P.readdirSync(r);for(let i of e)if(Aa(i)){let e=F.join(r,i,t||``);P.existsSync(e)&&P.existsSync(`${e}.complete`)&&n.push(i)}}return n}function Da(e){return q(this,void 0,void 0,function*(){return e||=F.join(Na(),ye.randomUUID()),yield s(e),e})}function Oa(e,t,n){return q(this,void 0,void 0,function*(){let r=F.join(Ma(),e,K.clean(t)||t,n||``);j(`destination ${r}`);let i=`${r}.complete`;return yield v(r),yield v(i),yield s(r),r})}function ka(e,t,n){let r=`${F.join(Ma(),e,K.clean(t)||t,n||``)}.complete`;P.writeFileSync(r,``),j(`finished caching tool`)}function Aa(e){let t=K.clean(e)||``;j(`isExplicit: ${t}`);let n=K.valid(t)!=null;return j(`explicit? ${n}`),n}function ja(e,t){let n=``;j(`evaluating ${e.length} versions`),e=e.sort((e,t)=>K.gt(e,t)?1:-1);for(let r=e.length-1;r>=0;r--){let i=e[r];if(K.satisfies(i,t)){n=i;break}}return j(n?`matched: ${n}`:`match not found`),n}function Ma(){let e=process.env.RUNNER_TOOL_CACHE||``;return Se(e,`Expected RUNNER_TOOL_CACHE to be defined`),e}function Na(){let e=process.env.RUNNER_TEMP||``;return Se(e,`Expected RUNNER_TEMP to be defined`),e}function Pa(e,t){let n=global[e];return n===void 0?t:n}function Fa(e){let t;try{t=JSON.parse(e)}catch(e){throw e instanceof SyntaxError?Error(`Invalid auth-json format: ${e.message}`):e}if(typeof t!=`object`||!t||Array.isArray(t))throw Error(`auth-json must be a JSON object`);return t}async function Ia(e,t,n){let r=L.join(t,`auth.json`);await I.mkdir(t,{recursive:!0});let i=JSON.stringify(e,null,2);return await I.writeFile(r,i,{mode:384}),n.info(`Populated auth.json`,{path:r,providers:Object.keys(e).length}),r}function La(){let e=N.platform,t=N.arch;return{os:{darwin:`darwin`,linux:`linux`,win32:`windows`}[e]??`linux`,arch:{arm64:`aarch64`,x64:`x64`}[t]??`x64`,ext:`.zip`}}function Ra(e,t){return`https://github.com/oven-sh/bun/releases/download/bun-${e.startsWith(`v`)?e:`v${e}`}/${`bun-${t.os}-${t.arch}${t.ext}`}`}async function za(e,t,n,i,a=pe){let o=La(),s=t.find(`bun`,a,o.arch);if(s.length>0)return e.info(`Bun found in cache`,{version:a,path:s}),i(s),await Va(s),{path:s,version:a,cached:!0};e.info(`Downloading Bun`,{version:a});let c=Ra(a,o);try{let r=await t.downloadTool(c);if(N.platform!==`win32`&&!await Ha(r,e,n))throw Error(`Downloaded Bun archive appears corrupted`);e.info(`Extracting Bun`);let s=await Ba(await t.extractZip(r),t),l=ke.dirname(s);e.info(`Caching Bun`);let u=await t.cacheDir(l,`bun`,a,o.arch);return i(u),await Va(u),e.info(`Bun installed`,{version:a,path:u}),{path:u,version:a,cached:!1}}catch(e){let t=r(e);throw Error(`Failed to install Bun ${a}: ${t}`)}}async function Ba(e,t){for(let n of await Oe.readdir(e,{withFileTypes:!0})){let{name:r}=n,i=ke.join(e,r);if(n.isFile()){if(r===`bun`||r===`bun.exe`)return i;if(/^bun.*\.zip/.test(r))return Ba(await t.extractZip(i),t)}if(r.startsWith(`bun`)&&n.isDirectory())return Ba(i,t)}throw Error(`Could not find executable: bun`)}async function Va(e){let t=e=>N.platform===`win32`?`${e}.exe`:e,n=ke.join(e,t(`bun`));try{await Oe.symlink(n,ke.join(e,t(`bunx`)))}catch(e){let t=typeof e==`object`?e.code:void 0;if(t!==`EEXIST`&&t!==`EPERM`&&t!==`EACCES`)throw e}}async function Ha(e,t,n){try{let{stdout:r}=await n.getExecOutput(`file`,[e],{silent:!0}),i=r.includes(`Zip archive`)||r.includes(`ZIP`);return i||t.warning(`Bun download validation failed`,{output:r.trim()}),i}catch{return t.debug(`Could not validate Bun download (file command unavailable)`),!0}}function Ua(e){return{debug:t=>e.debug(t),info:t=>e.info(t),warn:t=>e.warning(t),error:t=>e.error(t)}}function Wa(e){let{token:t,logger:n}=e;return n.debug(`Creating GitHub client with token`),pa(t,{log:Ua(n)})}async function Ga(e,t){try{let{data:n}=await e.rest.users.getAuthenticated();return t.debug(`Authenticated as`,{login:n.login,type:n.type}),n.login}catch{return t.debug(`Failed to get authenticated user, may be app token`),`fro-bot[bot]`}}async function Ka(e,t,n,r){let i=t??n,a=t==null?n.length>0?`github-token`:`none`:`app-token`;if(i.length===0)return r.warning(`No GitHub token available`),{authenticated:!1,method:`none`,botLogin:null};N.env.GH_TOKEN=i,r.info(`Configured authentication`,{method:a});let o=null;return e!=null&&(o=await Ga(e,r)),{authenticated:!0,method:a,botLogin:o}}async function qa(e,t){let n=await t.getExecOutput(`git`,[`config`,e],{ignoreReturnCode:!0,silent:!0});return n.exitCode===0&&n.stdout.trim().length>0?n.stdout.trim():null}async function Ja(e,t,n,r){let i=await qa(`user.name`,r),a=await qa(`user.email`,r);if(i!=null&&a!=null){n.info(`Git identity already configured`,{name:i,email:a});return}if(t==null)throw Error(`Cannot configure Git identity: no authenticated GitHub user`);let o=null;if(a==null){let r=await Xe(e,t,n);if(r==null)throw Error(`Cannot configure Git identity: failed to look up user ID for '${t}'`);o=String(r.id)}i??await r.exec(`git`,[`config`,`--global`,`user.name`,t],void 0);let s=`${o}+${t}@users.noreply.github.com`;a??await r.exec(`git`,[`config`,`--global`,`user.email`,s],void 0),n.info(`Configured git identity`,{name:i??t,email:a??s})}const Ya=new Set([`__proto__`,`prototype`,`constructor`]);function Xa(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function Za(e,t){let n=Object.create(null);for(let[t,r]of Object.entries(e))Ya.has(t)||(n[t]=r);for(let[e,r]of Object.entries(t)){if(Ya.has(e))continue;let t=n[e];Xa(r)&&Xa(t)?n[e]=Za(t,r):n[e]=r}return n}async function Qa(e,t,n){let r=JSON.parse(e);if(!Xa(r))throw Error(`omo-config must be a JSON object (non-null, non-array)`);let i=r;await I.mkdir(t,{recursive:!0});let a=L.join(t,`oh-my-opencode.json`),o={};try{let e=await I.readFile(a,`utf8`),t=JSON.parse(e);typeof t==`object`&&t&&!Array.isArray(t)&&(o=t)}catch(e){n.debug(`Using empty base oMo config`,{path:a,error:String(e)})}let s=Za(o,i);await I.writeFile(a,JSON.stringify(s,null,2)),n.info(`Wrote oMo config`,{path:a,keyCount:Object.keys(i).length})}async function $a(e,t,n={}){let{logger:i,execAdapter:a}=t,{claude:o=`no`,copilot:s=`no`,gemini:c=`no`,openai:l=`no`,opencodeZen:u=`no`,zaiCodingPlan:d=`no`,kimiForCoding:f=`no`}=n;i.info(`Installing Oh My OpenAgent plugin`,{version:e,claude:o,copilot:s,gemini:c,openai:l,opencodeZen:u,zaiCodingPlan:d,kimiForCoding:f});let p=``,m=[`oh-my-openagent@${e}`,`install`,`--no-tui`,`--skip-auth`,`--claude=${o}`,`--copilot=${s}`,`--gemini=${c}`,`--openai=${l}`,`--opencode-zen=${u}`,`--zai-coding-plan=${d}`,`--kimi-for-coding=${f}`];try{let t=await a.exec(`bunx`,m,{listeners:{stdout:e=>{p+=e.toString()},stderr:e=>{p+=e.toString()}},ignoreReturnCode:!0});if(t!==0){let e=`bunx oh-my-openagent install returned exit code ${t}`;return i.error(e,{output:p.slice(0,1e3)}),{installed:!1,version:null,error:`${e}\n${p.slice(0,500)}`}}let n=/oh-my-opencode@(\d+\.\d+\.\d+)/i.exec(p),r=n!=null&&n[1]!=null?n[1]:e;return i.info(`oMo plugin installed`,{version:r}),{installed:!0,version:r,error:null}}catch(e){let t=r(e),n=p.length>0?`${t}\nOutput: ${p.slice(0,500)}`:t;return i.error(`Failed to run oMo installer`,{error:t,output:p.slice(0,500)}),{installed:!1,version:null,error:`bunx oh-my-openagent install failed: ${n}`}}}const eo=`opencode`,to=g;function no(){let e=Ne.platform(),t=Ne.arch(),n={darwin:`darwin`,linux:`linux`,win32:`windows`},r={x64:`x64`,arm64:`arm64`},i=e===`win32`||e===`darwin`?`.zip`:`.tar.gz`;return{os:n[e]??`linux`,arch:r[t]??`x64`,ext:i}}function ro(e,t){return`https://github.com/anomalyco/opencode/releases/download/${e.startsWith(`v`)?e:`v${e}`}/${`opencode-${t.os}-${t.arch}${t.ext}`}`}async function io(e,t,n,r){if(N.platform===`win32`)return!0;try{let{stdout:i}=await r.getExecOutput(`file`,[e],{silent:!0}),a=(t===`.zip`?[`Zip archive`,`ZIP`]:[`gzip`,`tar`,`compressed`]).some(e=>i.includes(e));return a||n.warning(`Download validation failed`,{output:i.trim()}),a}catch{return n.debug(`Could not validate download (file command unavailable)`),!0}}async function ao(e,t,n,i,a=to){let o=no(),s=n.find(eo,e,o.arch);if(s.length>0)return t.info(`OpenCode found in cache`,{version:e,path:s}),{path:s,version:e,cached:!0};try{return await oo(e,o,t,n,i)}catch(n){t.warning(`Primary version install failed, trying fallback`,{requestedVersion:e,fallbackVersion:a,error:r(n)})}if(e!==a)try{let e=await oo(a,o,t,n,i);return t.info(`Installed fallback version`,{version:a}),e}catch(t){throw Error(`Failed to install OpenCode (tried ${e} and ${a}): ${r(t)}`)}throw Error(`Failed to install OpenCode version ${e}`)}async function oo(e,t,n,r,i){n.info(`Downloading OpenCode`,{version:e});let a=ro(e,t),o=await r.downloadTool(a);if(!await io(o,t.ext,n,i))throw Error(`Downloaded archive appears corrupted`);n.info(`Extracting OpenCode`);let s=t.ext===`.zip`?await r.extractZip(o):await r.extractTar(o);n.info(`Caching OpenCode`);let c=await r.cacheDir(s,eo,e,t.arch);return n.info(`OpenCode installed`,{version:e,path:c}),{path:c,version:e,cached:!1}}async function so(e){let t=await fetch(`https://api.github.com/repos/anomalyco/opencode/releases/latest`);if(!t.ok)throw Error(`Failed to fetch latest OpenCode version: ${t.statusText}`);let n=(await t.json()).tag_name.replace(/^v/,``);return e.info(`Latest OpenCode version`,{version:n}),n}const co={restoreCache:async(e,t,n)=>T(e,t,n),saveCache:async(e,t)=>oe(e,t)};function lo(e){let{os:t,opencodeVersion:n,omoVersion:r}=e;return`${me}-${t}-oc-${n}-omo-${r}`}function uo(e){let{os:t,opencodeVersion:n,omoVersion:r}=e;return[`${me}-${t}-oc-${n}-omo-${r}-`]}async function fo(e){let{logger:t,os:n,opencodeVersion:i,omoVersion:a,toolCachePath:o,bunCachePath:s,omoConfigPath:c,cacheAdapter:l=co}=e,u=lo({os:n,opencodeVersion:i,omoVersion:a}),d=uo({os:n,opencodeVersion:i,omoVersion:a}),f=[o,s,c];t.info(`Restoring tools cache`,{primaryKey:u,restoreKeys:[...d],paths:f});try{let e=await l.restoreCache(f,u,[...d]);return e==null?(t.info(`Tools cache miss - will install tools`),{hit:!1,restoredKey:null}):(t.info(`Tools cache restored`,{restoredKey:e}),{hit:!0,restoredKey:e})}catch(e){return t.warning(`Tools cache restore failed`,{error:r(e)}),{hit:!1,restoredKey:null}}}async function po(e){let{logger:t,os:n,opencodeVersion:i,omoVersion:a,toolCachePath:o,bunCachePath:s,omoConfigPath:c,cacheAdapter:l=co}=e,u=lo({os:n,opencodeVersion:i,omoVersion:a}),d=[o,s,c];t.info(`Saving tools cache`,{saveKey:u,paths:d});try{return await l.saveCache(d,u),t.info(`Tools cache saved`,{saveKey:u}),!0}catch(e){return e instanceof Error&&e.message.includes(`already exists`)?(t.info(`Tools cache key already exists, skipping save`),!0):(t.warning(`Tools cache save failed`,{error:r(e)}),!1)}}function mo(){return{find:Ta,downloadTool:va,extractTar:ba,extractZip:xa,cacheDir:wa}}function ho(){return{exec:M,getExecOutput:t}}async function go(t,n){let a=Date.now(),o=i({component:`setup`}),s=mo(),l=ho();try{o.info(`Starting setup`,{version:t.opencodeVersion});let i;try{i=Fa(t.authJson)}catch(e){return c(`Invalid auth-json: ${r(e)}`),null}let u=t.opencodeVersion;if(u===`latest`)try{u=await so(o)}catch(e){o.warning(`Failed to get latest version, using fallback`,{error:r(e)}),u=to}let f=t.omoVersion,m=N.env.RUNNER_TOOL_CACHE??`/opt/hostedtoolcache`,h=Ae(m,`opencode`),g=Ae(m,`bun`),_=Ae(Pe(),`.config`,`opencode`),v=se(),y=await fo({logger:o,os:v,opencodeVersion:u,omoVersion:f,toolCachePath:h,bunCachePath:g,omoConfigPath:_}),b=y.hit?`hit`:`miss`,x,S=!1,C=null;if(y.hit){let e=s.find(`opencode`,u);e.length>0?(x={path:e,version:u,cached:!0},o.info(`Tools cache hit, using cached OpenCode CLI`,{version:u,omoVersion:f})):o.warning(`Tools cache hit but binary not found in tool-cache, falling through to install`,{requestedVersion:u,restoredKey:y.restoredKey})}if(x==null)try{x=await ao(u,o,s,l)}catch(e){return c(`Failed to install OpenCode: ${r(e)}`),null}let w=!1;try{await za(o,s,l,p,pe),w=!0}catch(e){o.warning(`Bun installation failed, oMo will be unavailable`,{error:r(e)})}if(w){if(t.omoConfig!=null)try{await Qa(t.omoConfig,_,o)}catch(e){o.warning(`Failed to write omo-config, continuing without custom config`,{error:r(e)})}let e=await $a(f,{logger:o,execAdapter:l},t.omoProviders);e.installed?(o.info(`oMo installed`,{version:e.version}),S=!0):o.warning(`oMo installation failed, continuing without oMo`,{error:e.error??`unknown error`}),C=e.error}let T={autoupdate:!1};if(t.opencodeConfig!=null){let e;try{e=JSON.parse(t.opencodeConfig)}catch{return c(`opencode-config must be valid JSON`),null}if(typeof e!=`object`||!e||Array.isArray(e))return c(`opencode-config must be a JSON object`),null;Object.assign(T,e)}e(`OPENCODE_CONFIG_CONTENT`,JSON.stringify(T)),y.hit||await po({logger:o,os:v,opencodeVersion:u,omoVersion:f,toolCachePath:h,bunCachePath:g,omoConfigPath:_}),p(x.path),d(`opencode-path`,x.path),d(`opencode-version`,x.version),o.info(`OpenCode ready`,{version:x.version,cached:x.cached});let E=pa(n),D=await Ka(E,null,n,o);e(`GH_TOKEN`,n),o.info(`GitHub CLI configured`),await Ja(E,D.botLogin,o,l);let O=Ae(ie(),`opencode`),k=await Ia(i,O,o);d(`auth-json-path`,k),o.info(`auth.json populated`,{path:k});let ee=Date.now()-a,te={opencodePath:x.path,opencodeVersion:x.version,ghAuthenticated:D.authenticated,omoInstalled:S,omoError:C,toolsCacheStatus:b,duration:ee};return o.info(`Setup complete`,{duration:ee}),te}catch(e){let t=r(e);return o.error(`Setup failed`,{error:t}),c(t),null}}function _o(e){return{success:!0,data:e}}function vo(e){return{success:!1,error:e}}const yo=[`OWNER`,`MEMBER`,`COLLABORATOR`];async function bo(e,t){try{let{client:n,server:r}=await fn({signal:e});return t.debug(`OpenCode server bootstrapped`,{url:r.url}),_o({client:n,server:r,shutdown:()=>{r.close()}})}catch(e){let n=e instanceof Error?e.message:String(e);return t.warning(`Failed to bootstrap OpenCode server`,{error:n}),vo(Error(`Server bootstrap failed: ${n}`))}}async function xo(e,t){let n=e??`opencode`;try{let e=``;await M(n,[`--version`],{listeners:{stdout:t=>{e+=t.toString()}},silent:!0});let r=/(\d+\.\d+\.\d+)/.exec(e)?.[1]??null;return t.debug(`OpenCode version verified`,{version:r}),{available:!0,version:r}}catch{return t.debug(`OpenCode not available, will attempt auto-setup`),{available:!1,version:null}}}async function So(e){let{logger:t,opencodeVersion:n}=e,r=N.env.OPENCODE_PATH??null,i=await xo(r,t);if(i.available&&i.version!=null)return t.info(`OpenCode already available`,{version:i.version}),{path:r??`opencode`,version:i.version,didSetup:!1};t.info(`OpenCode not found, running auto-setup`,{requestedVersion:n});let a=await go({opencodeVersion:n,authJson:e.authJson,appId:null,privateKey:null,opencodeConfig:e.opencodeConfig,omoConfig:null,omoVersion:e.omoVersion,omoProviders:e.omoProviders},e.githubToken);if(a==null)throw Error(`Auto-setup failed: runSetup returned null`);return p(a.opencodePath),N.env.OPENCODE_PATH=a.opencodePath,t.info(`Auto-setup completed`,{version:a.opencodeVersion,path:a.opencodePath}),{path:a.opencodePath,version:a.opencodeVersion,didSetup:!0}}async function Co(e,t){let n={repo:e.agentContext.repo,commentId:e.agentContext.commentId,issueNumber:e.agentContext.issueNumber,issueType:e.agentContext.issueType,botLogin:e.botLogin},r=i({phase:`acknowledgment`});return await rr(e.githubClient,n,r),t.debug(`Acknowledgment phase completed`),n}function wo(e,t){try{JSON.parse(e)}catch{throw Error(`${t} must be valid JSON`)}}function To(e,t){let n=e.trim();if(!/^\d+$/.test(n))throw Error(`${t} must be a positive integer, received: ${e}`);let r=Number.parseInt(n,10);if(r===0)throw Error(`${t} must be a positive integer, received: ${e}`);return r}function Eo(e){let t=e.trim(),n=t.indexOf(`/`);if(n===-1)throw Error(`Invalid model format: "${e}". Expected "provider/model" (e.g., "anthropic/claude-sonnet-4-20250514")`);let r=t.slice(0,n).trim(),i=t.slice(n+1).trim();if(r.length===0)throw Error(`Invalid model format: "${e}". Provider cannot be empty.`);if(i.length===0)throw Error(`Invalid model format: "${e}". Model ID cannot be empty.`);return{providerID:r,modelID:i}}function Do(e,t=`timeout`){let n=e.trim();if(!/^\d+$/.test(n))throw Error(`${t} must be a non-negative integer, received: ${e}`);let r=Number.parseInt(n,10);if(Number.isNaN(r)||r<0)throw Error(`${t} must be a non-negative integer, received: ${e}`);return r}const Oo=[`claude`,`claude-max20`,`copilot`,`gemini`,`openai`,`opencode-zen`,`zai-coding-plan`,`kimi-for-coding`];function ko(e){let t=e.split(`,`).map(e=>e.trim().toLowerCase()).filter(e=>e.length>0),n=`no`,r=`no`,i=`no`,a=`no`,o=`no`,s=`no`,c=`no`;for(let e of t){if(!Oo.includes(e))throw Error(`Invalid omo-providers value: "${e}". Valid values: ${Oo.join(`, `)}`);switch(e){case`claude`:n=`yes`;break;case`claude-max20`:n=`max20`;break;case`copilot`:r=`yes`;break;case`gemini`:i=`yes`;break;case`openai`:a=`yes`;break;case`opencode-zen`:o=`yes`;break;case`zai-coding-plan`:s=`yes`;break;case`kimi-for-coding`:c=`yes`;break}}return{claude:n,copilot:r,gemini:i,openai:a,opencodeZen:o,zaiCodingPlan:s,kimiForCoding:c}}function Ao(){try{let e=A(`github-token`,{required:!0}).trim();if(e.length===0)return vo(Error(`github-token is required but was not provided`));let t=A(`auth-json`,{required:!0}).trim();if(t.length===0)return vo(Error(`auth-json is required but was not provided`));wo(t,`auth-json`);let r=A(`prompt`).trim(),i=r.length>0?r:null,a=A(`session-retention`).trim(),o=a.length>0?To(a,`session-retention`):50,s=A(`s3-backup`).trim().toLowerCase()===`true`,c=A(`s3-bucket`).trim(),l=c.length>0?c:null,u=A(`aws-region`).trim(),d=u.length>0?u:null,f=A(`agent`).trim(),p=f.length>0?f:w,m=A(`model`).trim(),h=m.length>0?Eo(m):null,_=A(`timeout`).trim(),v=_.length>0?Do(_):n,y=A(`opencode-version`).trim(),b=y.length>0?y:g,x=A(`skip-cache`).trim().toLowerCase()===`true`,S=A(`omo-version`).trim(),C=S.length>0?S:he,T=A(`omo-providers`).trim(),E=ko(T.length>0?T:``),D=A(`opencode-config`).trim(),O=D.length>0?D:null,k=A(`dedup-window`).trim(),ee=k.length>0?Do(k,`dedup-window`):ge;if(O!=null){wo(O,`opencode-config`);let e=JSON.parse(O);if(typeof e!=`object`||!e||Array.isArray(e))throw Error(`Input 'opencode-config' must be a JSON object`)}return _o({githubToken:e,authJson:t,prompt:i,sessionRetention:o,s3Backup:s,s3Bucket:l,awsRegion:d,agent:p,model:h,timeoutMs:v,opencodeVersion:b,skipCache:x,omoVersion:C,omoProviders:E,opencodeConfig:O,dedupWindow:ee})}catch(e){return vo(e instanceof Error?e:Error(String(e)))}}async function jo(e){let t=Ao();if(!t.success)return c(`Invalid inputs: ${t.error.message}`),null;let n=t.data,r=i({phase:`main`});r.info(`Action inputs parsed`,{sessionRetention:n.sessionRetention,s3Backup:n.s3Backup,hasGithubToken:n.githubToken.length>0,hasPrompt:n.prompt!=null,agent:n.agent,hasModelOverride:n.model!=null,timeoutMs:n.timeoutMs});let o=await So({logger:r,opencodeVersion:n.opencodeVersion,githubToken:n.githubToken,authJson:n.authJson,omoVersion:n.omoVersion,omoProviders:n.omoProviders,opencodeConfig:n.opencodeConfig});return o.didSetup?r.info(`OpenCode auto-setup completed`,{version:o.version}):r.info(`OpenCode already available`,{version:o.version}),a(_.OPENCODE_VERSION,o.version),e.debug(`Bootstrap phase completed`,{opencodeVersion:o.version}),{inputs:n,logger:r,opencodeResult:o}}const Mo=/^[0-9a-f]{40}$/i;function No(){return{exec:M,getExecOutput:t}}async function Po(e){let{workspacePath:t,logger:n,execAdapter:i=No()}=e,a=L.join(t,`.git`),o=L.join(a,`opencode`);try{let e=(await I.readFile(o,`utf8`)).trim();if(e.length>0){if(Mo.test(e))return n.debug(`Project ID loaded from cache`,{projectId:e}),{projectId:e,source:`cached`};n.warning(`Invalid cached project ID format, regenerating`,{cachedId:e})}}catch(e){n.debug(`No cached project ID found`,{error:r(e)})}try{let e=await I.readFile(a,`utf8`),n=/^gitdir: (.+)$/m.exec(e);if(n==null)return{projectId:null,source:`error`,error:`Invalid .git file format`};a=L.resolve(t,n[1]),o=L.join(a,`opencode`)}catch(e){if((typeof e==`object`?e.code:void 0)!==`EISDIR`)return{projectId:null,source:`error`,error:`Not a git repository`}}try{let{stdout:e,exitCode:a}=await i.getExecOutput(`git`,[`rev-list`,`--max-parents=0`,`--all`],{cwd:t,silent:!0});if(a!==0||e.trim().length===0)return{projectId:null,source:`error`,error:`No commits found in repository`};let s=e.trim().split(` +`).map(e=>e.trim()).filter(e=>e.length>0).sort();if(s.length===0)return{projectId:null,source:`error`,error:`No root commits found`};let c=s[0];try{await I.writeFile(o,c,{encoding:`utf8`,flag:`wx`}),n.info(`Project ID generated and cached`,{projectId:c,source:`generated`})}catch(e){(typeof e==`object`?e.code:void 0)===`EEXIST`?n.debug(`Project ID file already written by concurrent process, skipping`,{projectId:c}):n.warning(`Failed to cache project ID (continuing)`,{error:r(e)})}return{projectId:c,source:`generated`}}catch(e){return{projectId:null,source:`error`,error:r(e)}}}async function Fo(e){let t=ne(),n=i({phase:`cache`}),r=re(),a=L.join(r,`.git`,`opencode`),o=await ae({components:t,logger:n,storagePath:k(),authPath:fe(),projectIdPath:a,opencodeVersion:e.opencodeResult.version}),s=o.corrupted?`corrupted`:o.hit?`hit`:`miss`;e.logger.info(`Cache restore completed`,{cacheStatus:s,key:o.key});let l=await Po({workspacePath:r,logger:n});l.source===`error`?n.warning(`Failed to generate project ID (continuing)`,{error:l.error}):n.debug(`Project ID ready`,{projectId:l.projectId,source:l.source});let u=i({phase:`server-bootstrap`}),d=await bo(new AbortController().signal,u);if(!d.success)return c(`OpenCode server bootstrap failed: ${d.error.message}`),null;let f=d.data;return u.info(`SDK server bootstrapped successfully`),{cacheResult:o,cacheStatus:s,serverHandle:f}}const Io={markdownImage:/!\[([^\]]*)\]\((https:\/\/github\.com\/user-attachments\/assets\/[^)]+)\)/gi,markdownLink:/\[([^\]]+)\]\((https:\/\/github\.com\/user-attachments\/files\/[^)]+)\)/gi,htmlImage:/]*src=["'](https:\/\/github\.com\/user-attachments\/assets\/[^"']+)["'][^>]*>/gi};function Lo(e,t,n){e.lastIndex=0;let r=e.exec(t);for(;r!=null;)n(r),r=e.exec(t);e.lastIndex=0}function Ro(e){let t=[],n=new Set;return Lo(Io.markdownImage,e,e=>{let r=e[2],i=e[1],a=e[0];r!=null&&i!=null&&!n.has(r)&&Tn(r)&&(n.add(r),t.push({url:r,originalMarkdown:a,altText:i,type:`image`}))}),Lo(Io.markdownLink,e,e=>{let r=e[2],i=e[1],a=e[0];r!=null&&i!=null&&!n.has(r)&&Tn(r)&&(n.add(r),t.push({url:r,originalMarkdown:a,altText:i,type:`file`}))}),Lo(Io.htmlImage,e,e=>{let r=e[1],i=e[0];if(r!=null&&!n.has(r)&&Tn(r)){n.add(r);let e=/alt=["']([^"']*)["']/i.exec(i);t.push({url:r,originalMarkdown:i,altText:e?.[1]??``,type:`image`})}}),Io.htmlImage.lastIndex=0,t}function zo(e,t,n){try{let r=new URL(e).pathname.split(`/`).at(-1);if(r!=null&&/\.[a-z0-9]+$/i.test(r))return r;if(t.trim().length>0){let e=t.replaceAll(/[^\w.-]/g,`_`).slice(0,50);return e.trim().length>0?e:`attachment_${n+1}`}return`attachment_${n+1}`}catch{return`attachment_${n+1}`}}const Bo={maxFiles:5,maxFileSizeBytes:5*1024*1024,maxTotalSizeBytes:15*1024*1024,allowedMimeTypes:[`image/png`,`image/jpeg`,`image/gif`,`image/webp`,`image/svg+xml`,`text/plain`,`text/markdown`,`text/csv`,`application/json`,`application/pdf`]},Vo=[`github.com`,`githubusercontent.com`];async function Ho(e,t,n,i,a){a.debug(`Downloading attachment`,{url:e.url});try{let r=await fetch(e.url,{headers:{Authorization:`Bearer ${n}`,Accept:`*/*`,"User-Agent":`fro-bot-agent`},redirect:`manual`}),o=r;if(r.status>=300&&r.status<400){let t=r.headers.get(`location`);if(t==null)return a.warning(`Redirect without location`,{url:e.url}),null;let n=new URL(t);if(!Vo.some(e=>n.hostname===e||n.hostname.endsWith(`.${e}`)))return a.warning(`Redirect to non-GitHub host blocked`,{url:e.url,redirectTo:n.hostname}),null;o=await fetch(t,{headers:{Accept:`*/*`,"User-Agent":`fro-bot-agent`},redirect:`follow`})}if(!o.ok)return a.warning(`Attachment download failed`,{url:e.url,status:o.status}),null;let s=o.headers.get(`content-length`);if(s!=null){let t=Number.parseInt(s,10);if(t>i.maxFileSizeBytes)return a.warning(`Attachment exceeds size limit (Content-Length)`,{url:e.url,size:t,limit:i.maxFileSizeBytes}),null}let c=we.from(await o.arrayBuffer());if(c.length>i.maxFileSizeBytes)return a.warning(`Attachment exceeds size limit`,{url:e.url,size:c.length,limit:i.maxFileSizeBytes}),null;let l=o.headers.get(`content-type`)??`application/octet-stream`,u=zo(e.url,e.altText,t),d=l.split(`;`)[0],f=d==null?`application/octet-stream`:d.trim(),p=await I.mkdtemp(L.join(Me.tmpdir(),`fro-bot-attachments-`)),m=u.trim().length>0?u:`attachment_${t+1}`,h=L.join(p,m);return await I.writeFile(h,c),a.debug(`Attachment downloaded`,{filename:u,mime:f,sizeBytes:c.length,tempPath:h}),{url:e.url,filename:u,mime:f,sizeBytes:c.length,tempPath:h}}catch(t){return a.warning(`Attachment download error`,{url:e.url,error:r(t)}),null}}async function Uo(e,t,n=Bo,r){return Promise.all(e.map(async(e,i)=>Ho(e,i,t,n,r)))}async function Wo(e,t){for(let n of e)try{await I.unlink(n);let e=L.dirname(n);await I.rmdir(e).catch(()=>{})}catch(e){t.debug(`Failed to cleanup temp file`,{path:n,error:r(e)})}}function Go(e){return e.map(e=>({type:`file`,mime:e.mime,url:De(e.tempPath).toString(),filename:e.filename}))}function Ko(e,t,n){let r=e,i=new Set(n.map(e=>e.filename));for(let e of t){let t=n.find(e=>i.has(e.filename));t!=null&&(r=r.replace(e.originalMarkdown,`@${t.filename}`))}return r}function qo(e,t,n,r){return{processed:n,skipped:r,modifiedBody:Ko(e,t,n),fileParts:Go(n),tempFiles:n.map(e=>e.tempPath)}}function Jo(e){if(e<0||!Number.isFinite(e))throw Error(`Invalid bytes value: ${e}`);return e<1024?`${e}B`:e<1024*1024?`${(e/1024).toFixed(1)}KB`:`${(e/(1024*1024)).toFixed(1)}MB`}function Yo(e,t=Bo,n){let r=[],i=[],a=0;for(let o of e)if(o!=null){if(r.length>=t.maxFiles){i.push({url:o.url,reason:`Exceeds max file count (${t.maxFiles})`}),n.debug(`Attachment skipped: max count`,{url:o.url});continue}if(o.sizeBytes>t.maxFileSizeBytes){i.push({url:o.url,reason:`File too large (${Jo(o.sizeBytes)} > ${Jo(t.maxFileSizeBytes)})`}),n.debug(`Attachment skipped: too large`,{url:o.url,size:o.sizeBytes});continue}if(a+o.sizeBytes>t.maxTotalSizeBytes){i.push({url:o.url,reason:`Would exceed total size limit (${Jo(t.maxTotalSizeBytes)})`}),n.debug(`Attachment skipped: total size exceeded`,{url:o.url});continue}if(!Xo(o.mime,t.allowedMimeTypes)){i.push({url:o.url,reason:`MIME type not allowed: ${o.mime}`}),n.debug(`Attachment skipped: MIME type`,{url:o.url,mime:o.mime});continue}a+=o.sizeBytes,r.push({filename:o.filename,mime:o.mime,sizeBytes:o.sizeBytes,tempPath:o.tempPath}),n.info(`Attachment validated`,{filename:o.filename,mime:o.mime,sizeBytes:o.sizeBytes})}return{validated:r,skipped:i}}function Xo(e,t){let[n]=e.split(`/`);for(let r of t)if(r===e||r.endsWith(`/*`)&&n!=null&&n===r.slice(0,-2))return!0;return!1}function J(e){let t=L.resolve(e);return t.endsWith(L.sep)&&t.length>1?t.slice(0,-1):t}const Zo=e=>typeof e==`object`&&!!e,Y=e=>typeof e==`string`?e:null,Qo=e=>typeof e==`number`?e:null;function $o(e){if(Array.isArray(e))return e.filter(Zo).map(e=>({file:Y(e.file)??``,additions:Qo(e.additions)??0,deletions:Qo(e.deletions)??0}))}function es(e){return{id:e.id,version:e.version,projectID:e.projectID,directory:e.directory,parentID:e.parentID,title:e.title,time:{created:e.time.created,updated:e.time.updated,compacting:e.time.compacting,archived:e.time.archived},summary:e.summary==null?void 0:{additions:e.summary.additions,deletions:e.summary.deletions,files:e.summary.files,diffs:$o(e.summary.diffs)},share:e.share?.url==null?void 0:{url:e.share.url},permission:e.permission==null?void 0:{rules:e.permission.rules},revert:e.revert==null?void 0:{messageID:e.revert.messageID,partID:e.revert.partID,snapshot:e.revert.snapshot,diff:e.revert.diff}}}async function ts(e,t){let n=await e.project.list();if(n.error!=null||n.data==null)return t.warning(`SDK project list failed`,{error:String(n.error)}),[];if(!Array.isArray(n.data))return[];let r=[];for(let e of n.data){if(!Zo(e))continue;let t=Y(e.id),n=Y(e.worktree),i=Y(e.path);t==null||n==null||i==null||r.push({id:t,worktree:n,path:i,vcs:`git`,time:{created:0,updated:0}})}return r}async function ns(e,t,n){let r=J(t),i=await ts(e,n);for(let e of i){if(J(e.worktree)===r)return e;let t=Y(e.path);if(t!=null&&J(t)===r)return e}return null}function rs(e){return e.status===`running`?{status:`running`,input:e.input,time:{start:e.time.start}}:e.status===`error`?{status:`error`,input:e.input,error:e.error,time:{start:e.time.start,end:e.time.end}}:e.status===`pending`?{status:`pending`}:{status:`completed`,input:e.input,output:e.output,title:e.title,metadata:e.metadata,time:{start:e.time.start,end:e.time.end,compacted:e.time.compacted},attachments:void 0}}function is(e){let t={id:e.id,sessionID:e.sessionID,messageID:e.messageID};if(e.type===`text`)return{...t,type:`text`,text:e.text,synthetic:e.synthetic,ignored:e.ignored,time:e.time,metadata:e.metadata};if(e.type===`reasoning`)return{...t,type:`reasoning`,reasoning:e.reasoning??e.text,time:e.time};if(e.type===`tool`)return{...t,type:`tool`,callID:e.callID,tool:e.tool,state:rs(e.state),metadata:e.metadata};if(e.type!==`step-finish`)return{...t,type:`text`,text:`text`in e?e.text:``};let n=e;return{...t,type:`step-finish`,reason:n.reason,snapshot:n.snapshot,cost:n.cost,tokens:{input:n.tokens.input,output:n.tokens.output,reasoning:n.tokens.reasoning,cache:{read:n.tokens.cache.read,write:n.tokens.cache.write}}}}function as(e){if(e.role===`user`){let t=e;return{id:t.id,sessionID:t.sessionID,role:`user`,time:{created:t.time.created},summary:t.summary==null?void 0:{title:t.summary.title,body:t.summary.body,diffs:$o(t.summary.diffs)??[]},agent:t.agent,model:{providerID:t.model.providerID,modelID:t.model.modelID},system:t.system,tools:t.tools,variant:t.variant}}let t=e;return{id:t.id,sessionID:t.sessionID,role:`assistant`,time:{created:t.time.created,completed:t.time.completed},parentID:t.parentID,modelID:t.modelID,providerID:t.providerID,mode:t.mode,agent:t.agent??``,path:{cwd:t.path.cwd,root:t.path.root},summary:t.summary,cost:t.cost,tokens:{input:t.tokens.input,output:t.tokens.output,reasoning:t.tokens.reasoning,cache:{read:t.tokens.cache.read,write:t.tokens.cache.write}},finish:t.finish,error:t.error?{name:t.error.name,message:Y(t.error.data.message)??``}:void 0}}function os(e){return[...e.map(e=>{let t=as(`info`in e?e.info:e),n=`parts`in e?e.parts.map(is):void 0;return n==null||n.length===0?t:{...t,parts:n}})].sort((e,t)=>e.time.created-t.time.created)}async function ss(e,t,n){let r=await e.session.list({query:{directory:t}});return r.error==null&&r.data!=null?Array.isArray(r.data)?r.data.map(es):[]:(n.warning(`SDK session list failed`,{error:String(r.error)}),[])}async function cs(e,t,n){let r=await e.session.messages({path:{id:t}});return r.error==null&&r.data!=null?os(r.data):(n.warning(`SDK session messages failed`,{error:String(r.error)}),[])}async function ls(e,t,n,r){let i=await e.session.list({query:{directory:t,start:n,roots:!0,limit:10}});if(i.error!=null||i.data==null)return r.warning(`SDK session list failed`,{error:String(i.error)}),null;if(!Array.isArray(i.data)||i.data.length===0)return null;let a=i.data.map(es);if(a.length===0)return null;let o=a.reduce((e,t)=>t.time.created>e.time.created?t:e);return{projectID:o.projectID,session:o}}async function us(e,t,n){let r=await e.session.delete({path:{id:t}});if(r.error!=null){n.warning(`SDK session delete failed`,{sessionID:t,error:String(r.error)});return}n.debug(`Deleted session via SDK`,{sessionID:t})}const ds={maxSessions:50,maxAgeDays:30};async function fs(e,t,n,i){let{maxSessions:a,maxAgeDays:o}=n;if(i.info(`Starting session pruning`,{workspacePath:t,maxSessions:a,maxAgeDays:o}),await ns(e,t,i)==null)return i.debug(`No project found for pruning`,{workspacePath:t}),{prunedCount:0,prunedSessionIds:[],remainingCount:0,freedBytes:0};let s=await ss(e,t,i),c=s.filter(e=>e.parentID==null);if(c.length===0)return{prunedCount:0,prunedSessionIds:[],remainingCount:0,freedBytes:0};let l=[...c].sort((e,t)=>t.time.updated-e.time.updated),u=new Date;u.setDate(u.getDate()-o);let d=u.getTime(),f=new Set;for(let e of l)e.time.updated>=d&&f.add(e.id);for(let e=0;e!f.has(e.id)),m=new Set;for(let e of p){m.add(e.id);for(let t of s)t.parentID===e.id&&m.add(t.id)}if(m.size===0)return i.info(`No sessions to prune`),{prunedCount:0,prunedSessionIds:[],remainingCount:c.length,freedBytes:0};let h=[];for(let t of m)try{await us(e,t,i),h.push(t),i.debug(`Pruned session`,{sessionId:t})}catch(e){i.warning(`Failed to prune session`,{sessionId:t,error:r(e)})}let g=c.length-p.length;return i.info(`Session pruning complete`,{prunedCount:h.length,remainingCount:g}),{prunedCount:h.length,prunedSessionIds:h,remainingCount:g,freedBytes:0}}async function ps(e,t,n,r){let{limit:i,fromDate:a,toDate:o}=n;r.debug(`Listing sessions`,{directory:t,limit:i});let s=[...(await ss(e,t,r)).filter(e=>!(e.parentID!=null||a!=null&&e.time.createdo.getTime()))].sort((e,t)=>t.time.updated-e.time.updated),c=[],l=i==null?s:s.slice(0,i);for(let t of l){let n=await cs(e,t.id,r),i=ms(n);c.push({id:t.id,projectID:t.projectID,directory:t.directory,title:t.title,createdAt:t.time.created,updatedAt:t.time.updated,messageCount:n.length,agents:i,isChild:!1})}return r.info(`Listed sessions`,{count:c.length,directory:t}),c}function ms(e){let t=new Set;for(let n of e)n.agent!=null&&t.add(n.agent);return[...t]}async function hs(e,t,n,r,i){let{limit:a=20,caseSensitive:o=!1,sessionId:s}=r;i.debug(`Searching sessions`,{query:e,directory:n,limit:a,caseSensitive:o});let c=o?e:e.toLowerCase(),l=[],u=0;if(s!=null){let e=await gs(t,s,c,o,i);return e.length>0&&l.push({sessionId:s,matches:e.slice(0,a)}),l}let d=await ps(t,n,{},i);for(let e of d){if(u>=a)break;let n=await gs(t,e.id,c,o,i);if(n.length>0){let t=a-u;l.push({sessionId:e.id,matches:n.slice(0,t)}),u+=Math.min(n.length,t)}}return i.info(`Session search complete`,{query:e,resultCount:l.length,totalMatches:u}),l}async function gs(e,t,n,r,i){let a=await cs(e,t,i),o=[];for(let e of a){let t=e.parts??[];for(let i of t){let t=_s(i);if(t==null)continue;let a=r?t:t.toLowerCase();if(a.includes(n)){let r=a.indexOf(n),s=Math.max(0,r-50),c=Math.min(t.length,r+n.length+50),l=t.slice(s,c);o.push({messageId:e.id,partId:i.id,excerpt:`...${l}...`,role:e.role,agent:e.agent})}}}return o}function _s(e){switch(e.type){case`text`:return e.text;case`reasoning`:return e.reasoning;case`tool`:return e.state.status===`completed`?`${e.tool}: ${e.state.output}`:null;case`step-finish`:return null}}function vs(e){let t=[`--- Fro Bot Run Summary ---`,`Event: ${e.eventType}`,`Repo: ${e.repo}`,`Ref: ${e.ref}`,`Run ID: ${e.runId}`,`Cache: ${e.cacheStatus}`,`Duration: ${e.duration}s`];return e.sessionIds.length>0&&t.push(`Sessions used: ${e.sessionIds.join(`, `)}`),e.logicalKey!=null&&t.push(`Logical Thread: ${e.logicalKey}`),e.createdPRs.length>0&&t.push(`PRs created: ${e.createdPRs.join(`, `)}`),e.createdCommits.length>0&&t.push(`Commits: ${e.createdCommits.join(`, `)}`),e.tokenUsage!=null&&t.push(`Tokens: ${e.tokenUsage.input} in / ${e.tokenUsage.output} out`),t.join(` +`)}async function ys(e,t,n,i){let a=vs(t);try{let t=await n.session.prompt({path:{id:e},body:{noReply:!0,parts:[{type:`text`,text:a}]}});if(t.error!=null){i.warning(`SDK prompt writeback failed`,{sessionId:e,error:String(t.error)});return}i.info(`Session summary written via SDK`,{sessionId:e})}catch(t){i.warning(`SDK prompt writeback failed`,{sessionId:e,error:r(t)})}}async function bs(e){let{bootstrapLogger:t,reactionCtx:n,githubClient:r,agentSuccess:o,attachmentResult:s,serverHandle:c,detectedOpencodeVersion:l}=e;try{if(s!=null){let e=i({phase:`attachment-cleanup`});await Wo(s.tempFiles,e)}n!=null&&r!=null&&await lr(r,n,o,i({phase:`cleanup`}));let e=i({phase:`prune`}),t=re();if(c!=null){let n=J(t),r=await fs(c.client,n,ds,e);r.prunedCount>0&&e.info(`Pruned old sessions`,{pruned:r.prunedCount,remaining:r.remainingCount})}let u=ne(),d=i({phase:`cache-save`}),f=L.join(t,`.git`,`opencode`);await de({components:u,runId:D(),logger:d,storagePath:k(),authPath:fe(),projectIdPath:f,opencodeVersion:l})&&a(_.CACHE_SAVED,`true`)}catch(e){t.warning(`Cleanup failed (non-fatal)`,{error:e instanceof Error?e.message:String(e)})}finally{if(c!=null)try{c.shutdown()}catch(e){t.warning(`Server shutdown failed (non-fatal)`,{error:e instanceof Error?e.message:String(e)})}}}const xs=L.join(Ne.homedir(),`.cache`,`fro-bot-dedup`);function Ss(e){return e.replaceAll(`/`,`-`)}function Cs(e,t){let n=Ss(e);return L.join(xs,`${n}-${t.entityType}-${t.entityNumber}`)}function ws(e,t){return`${ee}-${Ss(e)}-${t.entityType}-${t.entityNumber}-`}function Ts(e,t,n){return`${ws(e,t)}${n}`}async function Es(e,t,n,i=le){let a=Cs(e,t),o=L.join(a,`sentinel.json`),s=ws(e,t);try{if(await I.rm(a,{recursive:!0,force:!0}),await I.mkdir(a,{recursive:!0}),await i.restoreCache([a],s,[])==null)return null;let e=await I.readFile(o,`utf8`);return JSON.parse(e)}catch(e){return n.debug(`Dedup marker restore failed; proceeding without marker`,{error:r(e),entityType:t.entityType,entityNumber:t.entityNumber}),null}}async function Ds(e,t,n,i,a=le){let o=Cs(e,t),s=L.join(o,`sentinel.json`),c=Ts(e,t,n.runId);try{return await I.mkdir(o,{recursive:!0}),await I.writeFile(s,JSON.stringify(n),`utf8`),await a.saveCache([o],c),!0}catch(e){return r(e).toLowerCase().includes(`already exists`)?!0:(i.debug(`Dedup marker save failed`,{error:r(e),entityType:t.entityType,entityNumber:t.entityNumber,saveKey:c}),!1)}}const Os=new Set([`pull_request`,`issues`]);function ks(e){return e.target==null||!Os.has(e.eventType)?null:e.eventType===`pull_request`&&e.target.kind===`pr`?{entityType:`pr`,entityNumber:e.target.number}:e.eventType===`issues`&&e.target.kind===`issue`?{entityType:`issue`,entityNumber:e.target.number}:null}async function As(e,t,n,r,a=i({phase:`dedup`}),o){let s=ks(t);if(e===0)return{shouldProceed:!0,entity:s};if(s==null)return{shouldProceed:!0,entity:null};let c=await Es(n,s,a,o);if(c==null||c.runId===t.runId)return{shouldProceed:!0,entity:s};let l=new Date(c.timestamp).getTime();if(Number.isNaN(l))return a.warning(`Dedup marker timestamp is invalid; proceeding without dedup`,{markerTimestamp:c.timestamp}),{shouldProceed:!0,entity:s};let u=Date.now()-l;return u<-6e4?(a.warning(`Dedup marker timestamp is too far in the future; proceeding without dedup`,{markerTimestamp:c.timestamp,markerAge:u}),{shouldProceed:!0,entity:s}):Math.max(0,u)>e?{shouldProceed:!0,entity:s}:(a.info(`Skipping duplicate trigger within dedup window`,{eventType:t.eventType,action:t.action,runId:t.runId,markerRunId:c.runId,markerTimestamp:c.timestamp,dedupWindow:e,entityType:s.entityType,entityNumber:s.entityNumber}),Be({sessionId:null,cacheStatus:`miss`,duration:Date.now()-r}),{shouldProceed:!1,entity:s})}async function js(e,t,n,r=i({phase:`dedup`}),a){await Ds(n,t,{timestamp:new Date().toISOString(),runId:e.runId,action:e.action??`unknown`,eventType:e.eventType,entityType:t.entityType,entityNumber:t.entityNumber},r,a)}async function Ms(e,t,n,r,o,s){let c={context:t.agentContext,customPrompt:e.inputs.prompt,cacheStatus:n.cacheStatus,sessionContext:{recentSessions:r.recentSessions,priorWorkContext:r.priorWorkContext},triggerContext:t.triggerResult.context,fileParts:r.attachmentResult?.fileParts},l=N.env.SKIP_AGENT_EXECUTION===`true`,u=Date.now(),d;if(l)e.logger.info(`Skipping agent execution (SKIP_AGENT_EXECUTION=true)`),d={success:!0,exitCode:0,sessionId:null,error:null,tokenUsage:null,model:null,cost:null,prsCreated:[],commitsCreated:[],commentsPosted:0,llmError:null};else{let t=i({phase:`execution`});t.info(`Starting OpenCode execution`,{logicalKey:r.logicalKey?.key??null,continueSessionId:r.continueSessionId});let a=await er(c,t,{agent:e.inputs.agent,model:e.inputs.model,timeoutMs:e.inputs.timeoutMs,omoProviders:e.inputs.omoProviders,continueSessionId:r.continueSessionId??void 0,sessionTitle:r.sessionTitle??void 0},n.serverHandle),o=a.sessionId;if(o==null){let e=i({phase:`session`}),t=await ls(n.serverHandle.client,r.normalizedWorkspace,u,e);t!=null&&(o=t.session.id,e.debug(`Identified session from execution`,{sessionId:o}))}d={...a,sessionId:o},t.info(`Completed OpenCode execution`,{success:d.success,sessionId:d.sessionId,logicalKey:r.logicalKey?.key??null})}d.sessionId!=null&&(a(_.SESSION_ID,d.sessionId),o.addSessionCreated(d.sessionId)),d.tokenUsage!=null&&o.setTokenUsage(d.tokenUsage,d.model,d.cost);for(let e of d.prsCreated)o.addPRCreated(e);for(let e of d.commitsCreated)o.addCommitCreated(e);for(let e=0;e`)}async function Fs(e,t,n){try{if(t.type===`pr`){let{data:n}=await e.rest.pulls.get({owner:t.owner,repo:t.repo,pull_number:t.number});return{title:n.title,body:n.body??``,author:n.user?.login??`unknown`}}let{data:n}=await e.rest.issues.get({owner:t.owner,repo:t.repo,issue_number:t.number});return{title:n.title,body:n.body??``,author:n.user?.login??`unknown`}}catch(e){return n.warning(`Failed to fetch issue/PR`,{target:t,error:r(e)}),null}}async function Is(e,t,n,i){let a=[],o=1;for(;o<=50;)try{let{data:r}=await e.rest.issues.listComments({owner:t.owner,repo:t.repo,issue_number:t.number,per_page:100,page:o});if(r.length===0)break;for(let e of r){let t=e.user?.login??`unknown`;a.push({id:e.id,body:e.body??``,author:t,authorAssociation:e.author_association??`NONE`,createdAt:e.created_at,updatedAt:e.updated_at,isBot:Ps(t,e.body??``,n)})}if(r.length<100)break;o++}catch(e){i.warning(`Failed to fetch comments page`,{target:t,page:o,error:r(e)});break}return a}async function Ls(e,t,n,i){try{let r=[],a=null,o=null,s=``,c=``,l=`unknown`,u=0;for(;u<50;){let d=(await e.graphql(` query GetDiscussion($owner: String!, $repo: String!, $number: Int!, $after: String) { repository(owner: $owner, name: $repo) { discussion(number: $number) { @@ -282,7 +287,7 @@ If you had completed the task, confirm the completion.`,c=i===1?e.fileParts:void } } } -`,{owner:t.owner,repo:t.repo,number:t.number,after:a})).repository.discussion;if(d==null)return i.debug(`Discussion not found`,{target:t}),null;u===0&&(o=d.id,s=d.title,c=d.body,l=d.author?.login??`unknown`);for(let e of d.comments.nodes){let t=e.author?.login??`unknown`;r.push({id:e.id,body:e.body,author:t,authorAssociation:`NONE`,createdAt:e.createdAt,updatedAt:e.updatedAt,isBot:Es(t,e.body,n)})}if(!d.comments.pageInfo.hasNextPage)break;a=d.comments.pageInfo.endCursor,u++}return{type:`discussion`,number:t.number,title:s,body:c,author:l,comments:r,discussionId:o??void 0}}catch(e){return i.warning(`Failed to fetch discussion`,{target:t,error:r(e)}),null}}async function As(e,t,n,r){if(t.type===`discussion`)return ks(e,t,n,r);let i=await Ds(e,t,r);if(i==null)return null;let a=await Os(e,t,n,r);return{type:t.type,number:t.number,title:i.title,body:i.body,author:i.author,comments:a}}function js(e,t){let n=e.comments.filter(e=>Ts(e.author,t)&&e.body.includes(``));return n.length===0?null:n.at(-1)??null}async function Ms(e,t,n,i){try{let{data:r}=await e.rest.issues.createComment({owner:t.owner,repo:t.repo,issue_number:t.number,body:n});return i.debug(`Created issue comment`,{commentId:r.id,target:t}),{commentId:r.id,created:!0,updated:!1,url:r.html_url}}catch(e){return i.warning(`Failed to create issue comment`,{target:t,error:r(e)}),null}}async function Ns(e,t,n,i,a){try{let{data:r}=await e.rest.issues.updateComment({owner:t.owner,repo:t.repo,comment_id:n,body:i});return a.debug(`Updated issue comment`,{commentId:r.id,target:t}),{commentId:r.id,created:!1,updated:!0,url:r.html_url}}catch(e){return a.warning(`Failed to update issue comment`,{target:t,commentId:n,error:r(e)}),null}}async function Ps(e,t,n,i){try{let r=(await e.graphql(` +`,{owner:t.owner,repo:t.repo,number:t.number,after:a})).repository.discussion;if(d==null)return i.debug(`Discussion not found`,{target:t}),null;u===0&&(o=d.id,s=d.title,c=d.body,l=d.author?.login??`unknown`);for(let e of d.comments.nodes){let t=e.author?.login??`unknown`;r.push({id:e.id,body:e.body,author:t,authorAssociation:`NONE`,createdAt:e.createdAt,updatedAt:e.updatedAt,isBot:Ps(t,e.body,n)})}if(!d.comments.pageInfo.hasNextPage)break;a=d.comments.pageInfo.endCursor,u++}return{type:`discussion`,number:t.number,title:s,body:c,author:l,comments:r,discussionId:o??void 0}}catch(e){return i.warning(`Failed to fetch discussion`,{target:t,error:r(e)}),null}}async function Rs(e,t,n,r){if(t.type===`discussion`)return Ls(e,t,n,r);let i=await Fs(e,t,r);if(i==null)return null;let a=await Is(e,t,n,r);return{type:t.type,number:t.number,title:i.title,body:i.body,author:i.author,comments:a}}function zs(e,t){let n=e.comments.filter(e=>Ns(e.author,t)&&e.body.includes(``));return n.length===0?null:n.at(-1)??null}async function Bs(e,t,n,i){try{let{data:r}=await e.rest.issues.createComment({owner:t.owner,repo:t.repo,issue_number:t.number,body:n});return i.debug(`Created issue comment`,{commentId:r.id,target:t}),{commentId:r.id,created:!0,updated:!1,url:r.html_url}}catch(e){return i.warning(`Failed to create issue comment`,{target:t,error:r(e)}),null}}async function Vs(e,t,n,i,a){try{let{data:r}=await e.rest.issues.updateComment({owner:t.owner,repo:t.repo,comment_id:n,body:i});return a.debug(`Updated issue comment`,{commentId:r.id,target:t}),{commentId:r.id,created:!1,updated:!0,url:r.html_url}}catch(e){return a.warning(`Failed to update issue comment`,{target:t,commentId:n,error:r(e)}),null}}async function Hs(e,t,n,i){try{let r=(await e.graphql(` query GetDiscussionId($owner: String!, $repo: String!, $number: Int!) { repository(owner: $owner, name: $repo) { discussion(number: $number) { @@ -303,7 +308,7 @@ If you had completed the task, confirm the completion.`,c=i===1?e.fileParts:void } } } -`,{owner:t.owner,repo:t.repo,number:t.number})).repository.discussion;if(r==null)return i.warning(`Discussion not found`,{target:t}),null;if(n.updateExisting===!0&&n.botLogin!=null){let r=await As(e,t,n.botLogin,i);if(r!=null){let t=js(r,n.botLogin);if(t!=null&&typeof t.id==`string`){let r=await e.graphql(` +`,{owner:t.owner,repo:t.repo,number:t.number})).repository.discussion;if(r==null)return i.warning(`Discussion not found`,{target:t}),null;if(n.updateExisting===!0&&n.botLogin!=null){let r=await Rs(e,t,n.botLogin,i);if(r!=null){let t=zs(r,n.botLogin);if(t!=null&&typeof t.id==`string`){let r=await e.graphql(` mutation UpdateDiscussionComment($commentId: ID!, $body: String!) { updateDiscussionComment(input: {commentId: $commentId, body: $body}) { comment { id url } @@ -315,4 +320,4 @@ If you had completed the task, confirm the completion.`,c=i===1?e.fileParts:void comment { id url } } } -`,{discussionId:r.id,body:n.body});return i.debug(`Created discussion comment`,{discussionId:r.id}),{commentId:a.addDiscussionComment.comment.id,created:!0,updated:!1,url:a.addDiscussionComment.comment.url}}catch(e){return i.warning(`Failed to post discussion comment`,{target:t,error:r(e)}),null}}async function Fs(e,t,n,r){if(t.type===`discussion`)return Ps(e,t,n,r);if(n.updateExisting===!0&&n.botLogin!=null){let i=await As(e,t,n.botLogin,r);if(i!=null){let a=js(i,n.botLogin);if(a!=null&&typeof a.id==`number`)return Ns(e,t,a.id,n.body,r)}}return Ms(e,t,n.body,r)}async function Is(e,t,n,r,a,o,s){let l=Date.now()-o;if(Re({sessionId:r.sessionId,cacheStatus:n.cacheStatus,duration:l}),await Ie({eventType:t.agentContext.eventName,repo:t.agentContext.repo,ref:t.agentContext.ref,runId:Number(t.agentContext.runId),runUrl:`https://github.com/${t.agentContext.repo}/actions/runs/${t.agentContext.runId}`,metrics:a.getMetrics(),agent:e.inputs.agent},s),r.success)return s.info(`Agent run completed successfully`,{durationMs:l}),0;if(r.llmError==null)return c(`Agent execution failed with exit code ${r.exitCode}`),r.exitCode;s.info(`Agent failed with recoverable LLM error`,{error:r.llmError.message,type:r.llmError.type,durationMs:l});let[u,d]=t.agentContext.repo.split(`/`),f={type:t.triggerResult.context.eventType===`discussion_comment`?`discussion`:t.agentContext.issueType===`pr`?`pr`:`issue`,number:t.agentContext.issueNumber??0,owner:u??``,repo:d??``};if(f.number>0&&f.owner.length>0&&f.repo.length>0){let e=mn(r.llmError),n=i({phase:`error-comment`}),o=await Fs(t.githubClient,f,{body:e},n);o==null?n.warning(`Failed to post LLM error comment`):(n.info(`Posted LLM error comment`,{commentUrl:o.url}),a.incrementComments())}else s.warning(`Cannot post error comment: missing target context`);return 0}function Ls(e){switch(e){case`issue_comment`:return`issue_comment`;case`discussion`:case`discussion_comment`:return`discussion_comment`;case`workflow_dispatch`:return`workflow_dispatch`;case`issues`:return`issues`;case`pull_request`:return`pull_request`;case`pull_request_review_comment`:return`pull_request_review_comment`;case`schedule`:return`schedule`;default:return`unsupported`}}function Rs(e,t){switch(e){case`issue_comment`:{let e=t;return{type:`issue_comment`,action:e.action,issue:{number:e.issue.number,title:e.issue.title,body:e.issue.body??null,locked:e.issue.locked??!1,isPullRequest:e.issue.pull_request!=null},comment:{id:e.comment.id,body:e.comment.body,author:e.comment.user.login,authorAssociation:e.comment.author_association??`NONE`}}}case`discussion_comment`:{let e=t;return{type:`discussion_comment`,action:e.action,discussion:{number:e.discussion.number,title:e.discussion.title,body:e.discussion.body??null,locked:e.discussion.locked??!1},comment:{id:e.comment.id,body:e.comment.body??null,author:e.comment.user.login,authorAssociation:e.comment.author_association??`NONE`}}}case`issues`:{let e=t;return{type:`issues`,action:e.action,issue:{number:e.issue.number,title:e.issue.title,body:e.issue.body??null,locked:e.issue.locked??!1,authorAssociation:e.issue.author_association??`NONE`},sender:{login:e.sender.login}}}case`pull_request`:{let e=t,n=e.pull_request.requested_reviewers??[],r=`requested_reviewer`in e&&e.requested_reviewer!=null?{login:e.requested_reviewer.login,type:e.requested_reviewer.type}:null,i=`requested_team`in e&&e.requested_team!=null?{name:e.requested_team.name,slug:e.requested_team.slug}:null,a=n.flatMap(e=>`login`in e&&`type`in e?[{login:e.login,type:e.type}]:[]);return{type:`pull_request`,action:e.action,requestedReviewer:r,requestedTeam:i,pullRequest:{number:e.pull_request.number,title:e.pull_request.title,body:e.pull_request.body??null,locked:e.pull_request.locked??!1,draft:e.pull_request.draft??!1,authorAssociation:e.pull_request.author_association??`NONE`,requestedReviewers:a},sender:{login:e.sender.login}}}case`pull_request_review_comment`:{let e=t;return{type:`pull_request_review_comment`,action:e.action,pullRequest:{number:e.pull_request.number,title:e.pull_request.title,locked:e.pull_request.locked??!1},comment:{id:e.comment.id,body:e.comment.body,author:e.comment.user.login,authorAssociation:e.comment.author_association,path:e.comment.path,line:e.comment.line??null,diffHunk:e.comment.diff_hunk,commitId:e.comment.commit_id}}}case`workflow_dispatch`:return{type:`workflow_dispatch`,inputs:{prompt:t.inputs?.prompt??void 0}};case`schedule`:return{type:`schedule`,schedule:t.schedule??void 0};case`unsupported`:return{type:`unsupported`}}}function zs(e){let t=ia,n=Ls(t.eventName),r=Rs(n,t.payload);return e.debug(`Parsed GitHub context`,{eventName:t.eventName,eventType:n,repo:`${t.repo.owner}/${t.repo.repo}`}),{eventName:t.eventName,eventType:n,repo:t.repo,ref:t.ref,sha:t.sha,runId:t.runId,actor:t.actor,payload:t.payload,event:r}}function Bs(e,t){return t.includes(e)}function Z(e){return e.endsWith(`[bot]`)}function Vs(e,t){if(t.length===0)return!1;let n=t.replace(/\[bot\]$/i,``);return n.length===0?!1:new RegExp(String.raw`@${Hs(n)}(?:\[bot\])?(?:$|[^\w])`,`i`).test(e)}function Hs(e){return e.replaceAll(/[.*+?^${}()|[\]\\]/g,String.raw`\$&`)}function Us(e,t){if(t.length===0)return null;let n=t.replace(/\[bot\]$/i,``);if(n.length===0)return null;let r=new RegExp(String.raw`@${Hs(n)}(?:\[bot\])?\s*(.*)`,`is`).exec(e)?.[1];if(r==null)return null;let i=r.trim();if(i.length===0)return{raw:``,action:null,args:``};let a=i.split(/\s+/),o=a[0]??``;return{raw:i,action:o===``?null:o,args:a.slice(1).join(` `)}}function Q(e,t){if(t==null||t===``||e==null)return{hasMention:!1,command:null};let n=Vs(e,t);return{hasMention:n,command:n?Us(e,t):null}}function Ws(e,t){if(e.type!==`issue_comment`)throw Error(`Event type must be issue_comment`);let n={login:e.comment.author,association:e.comment.authorAssociation,isBot:Z(e.comment.author)},r={kind:e.issue.isPullRequest?`pr`:`issue`,number:e.issue.number,title:e.issue.title,body:e.comment.body??null,locked:e.issue.locked},{hasMention:i,command:a}=Q(e.comment.body,t);return{action:e.action,author:n,target:r,commentBody:e.comment.body,commentId:e.comment.id,hasMention:i,command:a,isBotReviewRequested:!1}}function Gs(e,t){if(e.type!==`discussion_comment`)throw Error(`Event type must be discussion_comment`);let n={login:e.comment.author,association:e.comment.authorAssociation,isBot:Z(e.comment.author)},r=e.comment.body??null,i={kind:`discussion`,number:e.discussion.number,title:e.discussion.title,body:r??e.discussion.body??null,locked:e.discussion.locked},{hasMention:a,command:o}=Q(r,t);return{action:e.action,author:n,target:i,commentBody:r,commentId:e.comment.id,hasMention:a,command:o,isBotReviewRequested:!1}}function Ks(e,t){if(e.type!==`pull_request_review_comment`)throw Error(`Event type must be pull_request_review_comment`);let n={login:e.comment.author,association:e.comment.authorAssociation,isBot:Z(e.comment.author)},r={kind:`pr`,number:e.pullRequest.number,title:e.pullRequest.title,body:e.comment.body,locked:e.pullRequest.locked,path:e.comment.path,line:e.comment.line??void 0,diffHunk:e.comment.diffHunk,commitId:e.comment.commitId},{hasMention:i,command:a}=Q(e.comment.body,t);return{action:e.action,author:n,target:r,commentBody:e.comment.body,commentId:e.comment.id,hasMention:i,command:a,isBotReviewRequested:!1}}function qs(e,t,n){if(e.type!==`workflow_dispatch`)throw Error(`Event type must be workflow_dispatch`);let r=(n??e.inputs?.prompt??``).trim();return{action:null,author:{login:t,association:`OWNER`,isBot:!1},target:{kind:`manual`,number:0,title:`Manual workflow dispatch`,body:r===``?null:r,locked:!1},commentBody:r===``?null:r,commentId:null,hasMention:!1,command:null,isBotReviewRequested:!1}}function Js(e,t,n){let r=n?.trim()??``;return{action:null,author:{login:t,association:`OWNER`,isBot:!1},target:{kind:`manual`,number:0,title:`Scheduled workflow`,body:r===``?null:r,locked:!1},commentBody:r===``?null:r,commentId:null,hasMention:!1,command:null,isBotReviewRequested:!1}}function Ys(e){return e.toLowerCase().replace(/\[bot\]$/i,``)}function Xs(e,t){if(e.type!==`pull_request`||t==null||t===``)return!1;let n=Ys(t);if(n===``)return!1;if(e.action===`review_requested`){let t=e.requestedReviewer?.login;return t!=null&&Ys(t)===n}return e.action===`ready_for_review`?e.pullRequest.requestedReviewers.some(e=>Ys(e.login)===n):!1}function Zs(e,t){if(e.type!==`issues`)throw Error(`Event type must be issues`);let n={login:e.sender.login,association:e.issue.authorAssociation,isBot:Z(e.sender.login)},r={kind:`issue`,number:e.issue.number,title:e.issue.title,body:e.issue.body,locked:e.issue.locked},{hasMention:i,command:a}=Q(e.issue.body??``,t);return{action:e.action,author:n,target:r,commentBody:e.issue.body,commentId:null,hasMention:i,command:a,isBotReviewRequested:!1}}function Qs(e,t){if(e.type!==`pull_request`)throw Error(`Event type must be pull_request`);let n={login:e.sender.login,association:e.pullRequest.authorAssociation,isBot:Z(e.sender.login)},r={kind:`pr`,number:e.pullRequest.number,title:e.pullRequest.title,body:e.pullRequest.body,locked:e.pullRequest.locked,isDraft:e.pullRequest.draft,requestedReviewerLogin:e.requestedReviewer?.login,requestedTeamSlug:e.requestedTeam?.slug,requestedReviewerLogins:e.pullRequest.requestedReviewers.map(e=>e.login)},{hasMention:i,command:a}=Q(e.pullRequest.body??``,t);return{action:e.action,author:n,target:r,commentBody:e.pullRequest.body,commentId:null,hasMention:i,command:a,isBotReviewRequested:Xs(e,t)}}function $(e,t){return{...e,action:t.action,author:t.author,target:t.target,commentBody:t.commentBody,commentId:t.commentId,hasMention:t.hasMention,command:t.command,isBotReviewRequested:t.isBotReviewRequested}}function $s(e,t,n){let r={eventType:e.eventType,eventName:e.eventName,repo:e.repo,ref:e.ref,sha:e.sha,runId:e.runId,actor:e.actor,raw:e};switch(e.eventType){case`issue_comment`:return $(r,Ws(e.event,t));case`discussion_comment`:return $(r,Gs(e.event,t));case`workflow_dispatch`:return $(r,qs(e.event,e.actor,n));case`issues`:return $(r,Zs(e.event,t));case`pull_request`:return $(r,Qs(e.event,t));case`pull_request_review_comment`:return $(r,Ks(e.event,t));case`schedule`:return $(r,Js(e.event,e.actor,n));case`unsupported`:return{...r,action:null,author:null,target:null,commentBody:null,commentId:null,hasMention:!1,command:null,isBotReviewRequested:!1}}}function ec(e,t,n){let{targetLabel:r,actionLabel:i}=n;return e.action===`created`?e.target?.locked===!0?{shouldSkip:!0,reason:`issue_locked`,message:`${r} is locked`}:e.author!=null&&e.author.isBot?{shouldSkip:!0,reason:`self_comment`,message:`Comments from bots (${e.author.login}) are not processed`}:e.author!=null&&!Bs(e.author.association,t.allowedAssociations)?{shouldSkip:!0,reason:`unauthorized_author`,message:`Author association '${e.author.association}' is not authorized`}:t.requireMention&&!e.hasMention?{shouldSkip:!0,reason:`no_mention`,message:`Comment does not mention the bot`}:{shouldSkip:!1}:{shouldSkip:!0,reason:`action_not_created`,message:`${i} action is '${e.action}', not 'created'`}}function tc(e,t){return ec(e,t,{targetLabel:`Issue or PR`,actionLabel:`Comment`})}function nc(e,t){return ec(e,t,{targetLabel:`Discussion`,actionLabel:`Discussion comment`})}const rc=[`opened`,`edited`];function ic(e){return rc.includes(e)}function ac(e,t){let n=e.action;return n==null||!ic(n)?{shouldSkip:!0,reason:`action_not_supported`,message:`Issues action '${n}' is not supported (only 'opened' and 'edited')`}:e.author!=null&&e.author.isBot?{shouldSkip:!0,reason:`self_comment`,message:`Issues from bots (${e.author.login}) are not processed`}:e.author!=null&&!Bs(e.author.association,t.allowedAssociations)?{shouldSkip:!0,reason:`unauthorized_author`,message:`Author association '${e.author.association}' is not authorized`}:n===`edited`&&!e.hasMention?{shouldSkip:!0,reason:`no_mention`,message:`Issue edit does not mention the bot`}:e.target?.locked===!0?{shouldSkip:!0,reason:`issue_locked`,message:`Issue is locked`}:{shouldSkip:!1}}function oc(e){return(e.promptInput?.trim()??``)===``?{shouldSkip:!0,reason:`prompt_required`,message:`Schedule trigger requires prompt input`}:{shouldSkip:!1}}function sc(e){return(e.commentBody?.trim()??``)===``?{shouldSkip:!0,reason:`prompt_required`,message:`Workflow dispatch requires prompt input`}:{shouldSkip:!1}}const cc=[`opened`,`synchronize`,`reopened`,`ready_for_review`,`review_requested`];function lc(e){return cc.includes(e)}function uc(e,t){let n=e.action;return n==null||!lc(n)?{shouldSkip:!0,reason:`action_not_supported`,message:`Pull request action '${n}' is not supported`}:e.action!==`review_requested`&&e.action!==`ready_for_review`&&e.author!=null&&e.author.isBot?{shouldSkip:!0,reason:`self_comment`,message:`Pull requests from bots (${e.author.login}) are not processed`}:e.author!=null&&!Bs(e.author.association,t.allowedAssociations)?{shouldSkip:!0,reason:`unauthorized_author`,message:`Author association '${e.author.association}' is not authorized`}:t.skipDraftPRs&&e.target?.isDraft===!0?{shouldSkip:!0,reason:`draft_pr`,message:`Pull request is a draft`}:e.target?.locked===!0?{shouldSkip:!0,reason:`issue_locked`,message:`Pull request is locked`}:t.botLogin!=null&&t.botLogin!==``&&(e.action===`ready_for_review`||e.action===`review_requested`)&&e.isBotReviewRequested!==!0?{shouldSkip:!0,reason:`bot_not_requested`,message:`Pull request action '${e.action}' did not request review from the bot`}:{shouldSkip:!1}}function dc(e,t){let n=e.action;return n===`created`?e.target?.locked===!0?{shouldSkip:!0,reason:`issue_locked`,message:`Pull request is locked`}:e.author!=null&&e.author.isBot?{shouldSkip:!0,reason:`self_comment`,message:`Review comments from bots (${e.author.login}) are not processed`}:e.author!=null&&!Bs(e.author.association,t.allowedAssociations)?{shouldSkip:!0,reason:`unauthorized_author`,message:`Author association '${e.author.association}' is not authorized`}:t.requireMention&&!e.hasMention?{shouldSkip:!0,reason:`no_mention`,message:`Review comment does not mention the bot`}:{shouldSkip:!1}:{shouldSkip:!0,reason:`action_not_created`,message:`Review comment action '${n}' is not supported (only 'created')`}}function fc(e){return{shouldSkip:!0,reason:`unsupported_event`,message:`Unsupported event type: ${e}`}}function pc(e,t,n){if(e.eventType===`unsupported`)return n.debug(`Skipping unsupported event`,{eventName:e.eventName}),fc(e.eventName);switch(e.eventType){case`issue_comment`:return tc(e,t);case`discussion_comment`:return nc(e,t);case`issues`:return ac(e,t);case`pull_request`:return uc(e,t);case`pull_request_review_comment`:return dc(e,t);case`schedule`:return oc(t);case`workflow_dispatch`:return sc(e);default:return{shouldSkip:!1}}}const mc={botLogin:null,requireMention:!0,allowedAssociations:uo,skipDraftPRs:!0,promptInput:null,senderAssociation:null};function hc(e,t,n={}){let r={...mc,...n},i=$s(e,r.botLogin,r.promptInput);r.senderAssociation!=null&&(i.action===`review_requested`||i.action===`ready_for_review`)&&i.author!=null&&(i={...i,author:{...i.author,association:r.senderAssociation}}),t.debug(`Routing event`,{eventName:e.eventName,eventType:e.eventType,hasMention:i.hasMention});let a=pc(i,r,t);return a.shouldSkip?{shouldProcess:!1,skipReason:a.reason,skipMessage:a.message,context:i}:{shouldProcess:!0,context:i}}async function gc(e,t){let n=i({phase:`context`}),r=zs(n),o=Ia({token:e.inputs.githubToken,logger:n}),s=await La(o,n),c=null;if(r.event.type===`pull_request`&&(r.event.action===`review_requested`||r.event.action===`ready_for_review`)){let{owner:e,repo:t}=r.repo;c=await qe(o,e,t,r.event.sender.login,n)}let l=i({phase:`trigger`}),u=hc(r,l,{botLogin:s,requireMention:!0,promptInput:e.inputs.prompt,senderAssociation:c});return u.shouldProcess?(l.info(`Event routed for processing`,{eventType:u.context.eventType,hasMention:u.context.hasMention,command:u.context.command?.action??null}),a(_.SHOULD_SAVE_CACHE,`true`),{githubClient:o,triggerResult:u,agentContext:await dt({logger:n,octokit:o,triggerContext:u.context,botLogin:s}),botLogin:s}):(l.info(`Skipping event`,{reason:u.skipReason,message:u.skipMessage}),Re({sessionId:null,cacheStatus:`miss`,duration:Date.now()-t}),null)}async function _c(e,t,n,r){let a=i({phase:`session`}),o=Y(re()),s=await as(n.serverHandle.client,o,{limit:10},a);a.debug(`Listed recent sessions`,{count:s.length});let c=t.agentContext.issueTitle??t.agentContext.repo,l=await ss(c,n.serverHandle.client,o,{limit:5},a);a.debug(`Searched prior sessions`,{query:c,resultCount:l.length});for(let e of l)r.addSessionUsed(e.sessionId);let u=i({phase:`attachments`}),d=t.agentContext.commentBody??``,f=Ao(d),p=null;if(f.length>0){u.info(`Processing attachments`,{count:f.length});let{validated:t,skipped:n}=Vo(await Fo(f,e.inputs.githubToken,void 0,u),void 0,u);(t.length>0||n.length>0)&&(p=zo(d,f,t,n),u.info(`Attachments processed`,{processed:t.length,skipped:n.length}))}return{recentSessions:s,priorWorkContext:l,attachmentResult:p,normalizedWorkspace:o}}async function vc(){let e=Date.now(),t=i({phase:`bootstrap`}),n=Le();n.start();let r=null,o=!1,s=0,l=null,u=null,d=null,f=null;a(_.SHOULD_SAVE_CACHE,`false`),a(_.CACHE_SAVED,`false`);try{t.info(`Starting Fro Bot Agent`);let i=await Co(t);if(i==null)return 1;d=i.opencodeResult.version;let a=await gc(i,e);if(a==null)return 0;l=a.githubClient;let c=`${a.triggerResult.context.repo.owner}/${a.triggerResult.context.repo.repo}`,p=await Ss(i.inputs.dedupWindow,a.triggerResult.context,c,e);if(!p.shouldProceed)return 0;r=await ho(a,i.logger);let m=await Do(i);if(m==null)return 1;f=m.serverHandle,n.setCacheStatus(m.cacheStatus);let h=await _c(i,a,m,n);u=h.attachmentResult;let g=await ws(i,a,m,h,n,e);o=g.success,o&&p.entity!=null&&await Cs(a.triggerResult.context,p.entity,c),n.end(),s=await Is(i,a,m,g,n,e,i.logger)}catch(r){s=1;let i=Date.now()-e,a=r instanceof Error?r.name:`UnknownError`,o=r instanceof Error?r.message:String(r);n.recordError(a,o,!1),n.end(),Re({sessionId:null,cacheStatus:`miss`,duration:i}),r instanceof Error?(t.error(`Agent failed`,{error:r.message}),c(r.message)):(t.error(`Agent failed with unknown error`),c(`An unknown error occurred`))}finally{await fs({bootstrapLogger:t,reactionCtx:r,githubClient:l,agentSuccess:o,attachmentResult:u,serverHandle:f,detectedOpencodeVersion:d})}return s}await vc().then(e=>{N.exit(e)});export{}; \ No newline at end of file +`,{discussionId:r.id,body:n.body});return i.debug(`Created discussion comment`,{discussionId:r.id}),{commentId:a.addDiscussionComment.comment.id,created:!0,updated:!1,url:a.addDiscussionComment.comment.url}}catch(e){return i.warning(`Failed to post discussion comment`,{target:t,error:r(e)}),null}}async function Us(e,t,n,r){if(t.type===`discussion`)return Hs(e,t,n,r);if(n.updateExisting===!0&&n.botLogin!=null){let i=await Rs(e,t,n.botLogin,r);if(i!=null){let a=zs(i,n.botLogin);if(a!=null&&typeof a.id==`number`)return Vs(e,t,a.id,n.body,r)}}return Bs(e,t,n.body,r)}async function Ws(e,t,n,r,a,o,s){let l=Date.now()-o;if(Be({sessionId:r.sessionId,cacheStatus:n.cacheStatus,duration:l}),await Re({eventType:t.agentContext.eventName,repo:t.agentContext.repo,ref:t.agentContext.ref,runId:Number(t.agentContext.runId),runUrl:`https://github.com/${t.agentContext.repo}/actions/runs/${t.agentContext.runId}`,metrics:a.getMetrics(),agent:e.inputs.agent},s),r.success)return s.info(`Agent run completed successfully`,{durationMs:l}),0;if(r.llmError==null)return c(`Agent execution failed with exit code ${r.exitCode}`),r.exitCode;s.info(`Agent failed with recoverable LLM error`,{error:r.llmError.message,type:r.llmError.type,durationMs:l});let[u,d]=t.agentContext.repo.split(`/`),f={type:t.triggerResult.context.eventType===`discussion_comment`?`discussion`:t.agentContext.issueType===`pr`?`pr`:`issue`,number:t.agentContext.issueNumber??0,owner:u??``,repo:d??``};if(f.number>0&&f.owner.length>0&&f.repo.length>0){let e=gn(r.llmError),n=i({phase:`error-comment`}),o=await Us(t.githubClient,f,{body:e},n);o==null?n.warning(`Failed to post LLM error comment`):(n.info(`Posted LLM error comment`,{commentUrl:o.url}),a.incrementComments())}else s.warning(`Cannot post error comment: missing target context`);return 0}function Gs(e){switch(e){case`issue_comment`:return`issue_comment`;case`discussion`:case`discussion_comment`:return`discussion_comment`;case`workflow_dispatch`:return`workflow_dispatch`;case`issues`:return`issues`;case`pull_request`:return`pull_request`;case`pull_request_review_comment`:return`pull_request_review_comment`;case`schedule`:return`schedule`;default:return`unsupported`}}function Ks(e,t){switch(e){case`issue_comment`:{let e=t;return{type:`issue_comment`,action:e.action,issue:{number:e.issue.number,title:e.issue.title,body:e.issue.body??null,locked:e.issue.locked??!1,isPullRequest:e.issue.pull_request!=null},comment:{id:e.comment.id,body:e.comment.body,author:e.comment.user.login,authorAssociation:e.comment.author_association??`NONE`}}}case`discussion_comment`:{let e=t;return{type:`discussion_comment`,action:e.action,discussion:{number:e.discussion.number,title:e.discussion.title,body:e.discussion.body??null,locked:e.discussion.locked??!1},comment:{id:e.comment.id,body:e.comment.body??null,author:e.comment.user.login,authorAssociation:e.comment.author_association??`NONE`}}}case`issues`:{let e=t;return{type:`issues`,action:e.action,issue:{number:e.issue.number,title:e.issue.title,body:e.issue.body??null,locked:e.issue.locked??!1,authorAssociation:e.issue.author_association??`NONE`},sender:{login:e.sender.login}}}case`pull_request`:{let e=t,n=e.pull_request.requested_reviewers??[],r=`requested_reviewer`in e&&e.requested_reviewer!=null?{login:e.requested_reviewer.login,type:e.requested_reviewer.type}:null,i=`requested_team`in e&&e.requested_team!=null?{name:e.requested_team.name,slug:e.requested_team.slug}:null,a=n.flatMap(e=>`login`in e&&`type`in e?[{login:e.login,type:e.type}]:[]);return{type:`pull_request`,action:e.action,requestedReviewer:r,requestedTeam:i,pullRequest:{number:e.pull_request.number,title:e.pull_request.title,body:e.pull_request.body??null,locked:e.pull_request.locked??!1,draft:e.pull_request.draft??!1,authorAssociation:e.pull_request.author_association??`NONE`,requestedReviewers:a},sender:{login:e.sender.login}}}case`pull_request_review_comment`:{let e=t;return{type:`pull_request_review_comment`,action:e.action,pullRequest:{number:e.pull_request.number,title:e.pull_request.title,locked:e.pull_request.locked??!1},comment:{id:e.comment.id,body:e.comment.body,author:e.comment.user.login,authorAssociation:e.comment.author_association,path:e.comment.path,line:e.comment.line??null,diffHunk:e.comment.diff_hunk,commitId:e.comment.commit_id}}}case`workflow_dispatch`:return{type:`workflow_dispatch`,inputs:{prompt:t.inputs?.prompt??void 0}};case`schedule`:return{type:`schedule`,schedule:t.schedule??void 0};case`unsupported`:return{type:`unsupported`}}}function qs(e){let t=fa,n=Gs(t.eventName),r=Ks(n,t.payload);return e.debug(`Parsed GitHub context`,{eventName:t.eventName,eventType:n,repo:`${t.repo.owner}/${t.repo.repo}`}),{eventName:t.eventName,eventType:n,repo:t.repo,ref:t.ref,sha:t.sha,runId:t.runId,actor:t.actor,payload:t.payload,event:r}}function Js(e,t){return t.includes(e)}function X(e){return e.endsWith(`[bot]`)}function Ys(e,t){if(t.length===0)return!1;let n=t.replace(/\[bot\]$/i,``);return n.length===0?!1:new RegExp(String.raw`@${Xs(n)}(?:\[bot\])?(?:$|[^\w])`,`i`).test(e)}function Xs(e){return e.replaceAll(/[.*+?^${}()|[\]\\]/g,String.raw`\$&`)}function Zs(e,t){if(t.length===0)return null;let n=t.replace(/\[bot\]$/i,``);if(n.length===0)return null;let r=new RegExp(String.raw`@${Xs(n)}(?:\[bot\])?\s*(.*)`,`is`).exec(e)?.[1];if(r==null)return null;let i=r.trim();if(i.length===0)return{raw:``,action:null,args:``};let a=i.split(/\s+/),o=a[0]??``;return{raw:i,action:o===``?null:o,args:a.slice(1).join(` `)}}function Z(e,t){if(t==null||t===``||e==null)return{hasMention:!1,command:null};let n=Ys(e,t);return{hasMention:n,command:n?Zs(e,t):null}}function Qs(e,t){if(e.type!==`issue_comment`)throw Error(`Event type must be issue_comment`);let n={login:e.comment.author,association:e.comment.authorAssociation,isBot:X(e.comment.author)},r={kind:e.issue.isPullRequest?`pr`:`issue`,number:e.issue.number,title:e.issue.title,body:e.comment.body??null,locked:e.issue.locked},{hasMention:i,command:a}=Z(e.comment.body,t);return{action:e.action,author:n,target:r,commentBody:e.comment.body,commentId:e.comment.id,hasMention:i,command:a,isBotReviewRequested:!1}}function $s(e,t){if(e.type!==`discussion_comment`)throw Error(`Event type must be discussion_comment`);let n={login:e.comment.author,association:e.comment.authorAssociation,isBot:X(e.comment.author)},r=e.comment.body??null,i={kind:`discussion`,number:e.discussion.number,title:e.discussion.title,body:r??e.discussion.body??null,locked:e.discussion.locked},{hasMention:a,command:o}=Z(r,t);return{action:e.action,author:n,target:i,commentBody:r,commentId:e.comment.id,hasMention:a,command:o,isBotReviewRequested:!1}}function ec(e,t){if(e.type!==`pull_request_review_comment`)throw Error(`Event type must be pull_request_review_comment`);let n={login:e.comment.author,association:e.comment.authorAssociation,isBot:X(e.comment.author)},r={kind:`pr`,number:e.pullRequest.number,title:e.pullRequest.title,body:e.comment.body,locked:e.pullRequest.locked,path:e.comment.path,line:e.comment.line??void 0,diffHunk:e.comment.diffHunk,commitId:e.comment.commitId},{hasMention:i,command:a}=Z(e.comment.body,t);return{action:e.action,author:n,target:r,commentBody:e.comment.body,commentId:e.comment.id,hasMention:i,command:a,isBotReviewRequested:!1}}function tc(e,t,n){if(e.type!==`workflow_dispatch`)throw Error(`Event type must be workflow_dispatch`);let r=(n??e.inputs?.prompt??``).trim();return{action:null,author:{login:t,association:`OWNER`,isBot:!1},target:{kind:`manual`,number:0,title:`Manual workflow dispatch`,body:r===``?null:r,locked:!1},commentBody:r===``?null:r,commentId:null,hasMention:!1,command:null,isBotReviewRequested:!1}}function nc(e,t,n){let r=n?.trim()??``;return{action:null,author:{login:t,association:`OWNER`,isBot:!1},target:{kind:`manual`,number:0,title:`Scheduled workflow`,body:r===``?null:r,locked:!1},commentBody:r===``?null:r,commentId:null,hasMention:!1,command:null,isBotReviewRequested:!1}}function rc(e){return e.toLowerCase().replace(/\[bot\]$/i,``)}function ic(e,t){if(e.type!==`pull_request`||t==null||t===``)return!1;let n=rc(t);if(n===``)return!1;if(e.action===`review_requested`){let t=e.requestedReviewer?.login;return t!=null&&rc(t)===n}return e.action===`ready_for_review`?e.pullRequest.requestedReviewers.some(e=>rc(e.login)===n):!1}function ac(e,t){if(e.type!==`issues`)throw Error(`Event type must be issues`);let n={login:e.sender.login,association:e.issue.authorAssociation,isBot:X(e.sender.login)},r={kind:`issue`,number:e.issue.number,title:e.issue.title,body:e.issue.body,locked:e.issue.locked},{hasMention:i,command:a}=Z(e.issue.body??``,t);return{action:e.action,author:n,target:r,commentBody:e.issue.body,commentId:null,hasMention:i,command:a,isBotReviewRequested:!1}}function oc(e,t){if(e.type!==`pull_request`)throw Error(`Event type must be pull_request`);let n={login:e.sender.login,association:e.pullRequest.authorAssociation,isBot:X(e.sender.login)},r={kind:`pr`,number:e.pullRequest.number,title:e.pullRequest.title,body:e.pullRequest.body,locked:e.pullRequest.locked,isDraft:e.pullRequest.draft,requestedReviewerLogin:e.requestedReviewer?.login,requestedTeamSlug:e.requestedTeam?.slug,requestedReviewerLogins:e.pullRequest.requestedReviewers.map(e=>e.login)},{hasMention:i,command:a}=Z(e.pullRequest.body??``,t);return{action:e.action,author:n,target:r,commentBody:e.pullRequest.body,commentId:null,hasMention:i,command:a,isBotReviewRequested:ic(e,t)}}function Q(e,t){return{...e,action:t.action,author:t.author,target:t.target,commentBody:t.commentBody,commentId:t.commentId,hasMention:t.hasMention,command:t.command,isBotReviewRequested:t.isBotReviewRequested}}function sc(e,t,n){let r={eventType:e.eventType,eventName:e.eventName,repo:e.repo,ref:e.ref,sha:e.sha,runId:e.runId,actor:e.actor,raw:e};switch(e.eventType){case`issue_comment`:return Q(r,Qs(e.event,t));case`discussion_comment`:return Q(r,$s(e.event,t));case`workflow_dispatch`:return Q(r,tc(e.event,e.actor,n));case`issues`:return Q(r,ac(e.event,t));case`pull_request`:return Q(r,oc(e.event,t));case`pull_request_review_comment`:return Q(r,ec(e.event,t));case`schedule`:return Q(r,nc(e.event,e.actor,n));case`unsupported`:return{...r,action:null,author:null,target:null,commentBody:null,commentId:null,hasMention:!1,command:null,isBotReviewRequested:!1}}}function cc(e,t,n){let{targetLabel:r,actionLabel:i}=n;return e.action===`created`?e.target?.locked===!0?{shouldSkip:!0,reason:`issue_locked`,message:`${r} is locked`}:e.author!=null&&e.author.isBot?{shouldSkip:!0,reason:`self_comment`,message:`Comments from bots (${e.author.login}) are not processed`}:e.author!=null&&!Js(e.author.association,t.allowedAssociations)?{shouldSkip:!0,reason:`unauthorized_author`,message:`Author association '${e.author.association}' is not authorized`}:t.requireMention&&!e.hasMention?{shouldSkip:!0,reason:`no_mention`,message:`Comment does not mention the bot`}:{shouldSkip:!1}:{shouldSkip:!0,reason:`action_not_created`,message:`${i} action is '${e.action}', not 'created'`}}function lc(e,t){return cc(e,t,{targetLabel:`Issue or PR`,actionLabel:`Comment`})}function uc(e,t){return cc(e,t,{targetLabel:`Discussion`,actionLabel:`Discussion comment`})}const dc=[`opened`,`edited`];function fc(e){return dc.includes(e)}function pc(e,t){let n=e.action;return n==null||!fc(n)?{shouldSkip:!0,reason:`action_not_supported`,message:`Issues action '${n}' is not supported (only 'opened' and 'edited')`}:e.author!=null&&e.author.isBot?{shouldSkip:!0,reason:`self_comment`,message:`Issues from bots (${e.author.login}) are not processed`}:e.author!=null&&!Js(e.author.association,t.allowedAssociations)?{shouldSkip:!0,reason:`unauthorized_author`,message:`Author association '${e.author.association}' is not authorized`}:n===`edited`&&!e.hasMention?{shouldSkip:!0,reason:`no_mention`,message:`Issue edit does not mention the bot`}:e.target?.locked===!0?{shouldSkip:!0,reason:`issue_locked`,message:`Issue is locked`}:{shouldSkip:!1}}function mc(e){return(e.promptInput?.trim()??``)===``?{shouldSkip:!0,reason:`prompt_required`,message:`Schedule trigger requires prompt input`}:{shouldSkip:!1}}function hc(e){return(e.commentBody?.trim()??``)===``?{shouldSkip:!0,reason:`prompt_required`,message:`Workflow dispatch requires prompt input`}:{shouldSkip:!1}}const gc=[`opened`,`synchronize`,`reopened`,`ready_for_review`,`review_requested`];function _c(e){return gc.includes(e)}function vc(e,t){let n=e.action;return n==null||!_c(n)?{shouldSkip:!0,reason:`action_not_supported`,message:`Pull request action '${n}' is not supported`}:e.action!==`review_requested`&&e.action!==`ready_for_review`&&e.author!=null&&e.author.isBot?{shouldSkip:!0,reason:`self_comment`,message:`Pull requests from bots (${e.author.login}) are not processed`}:e.author!=null&&!Js(e.author.association,t.allowedAssociations)?{shouldSkip:!0,reason:`unauthorized_author`,message:`Author association '${e.author.association}' is not authorized`}:t.skipDraftPRs&&e.target?.isDraft===!0?{shouldSkip:!0,reason:`draft_pr`,message:`Pull request is a draft`}:e.target?.locked===!0?{shouldSkip:!0,reason:`issue_locked`,message:`Pull request is locked`}:t.botLogin!=null&&t.botLogin!==``&&(e.action===`ready_for_review`||e.action===`review_requested`)&&e.isBotReviewRequested!==!0?{shouldSkip:!0,reason:`bot_not_requested`,message:`Pull request action '${e.action}' did not request review from the bot`}:{shouldSkip:!1}}function yc(e,t){let n=e.action;return n===`created`?e.target?.locked===!0?{shouldSkip:!0,reason:`issue_locked`,message:`Pull request is locked`}:e.author!=null&&e.author.isBot?{shouldSkip:!0,reason:`self_comment`,message:`Review comments from bots (${e.author.login}) are not processed`}:e.author!=null&&!Js(e.author.association,t.allowedAssociations)?{shouldSkip:!0,reason:`unauthorized_author`,message:`Author association '${e.author.association}' is not authorized`}:t.requireMention&&!e.hasMention?{shouldSkip:!0,reason:`no_mention`,message:`Review comment does not mention the bot`}:{shouldSkip:!1}:{shouldSkip:!0,reason:`action_not_created`,message:`Review comment action '${n}' is not supported (only 'created')`}}function bc(e){return{shouldSkip:!0,reason:`unsupported_event`,message:`Unsupported event type: ${e}`}}function xc(e,t,n){if(e.eventType===`unsupported`)return n.debug(`Skipping unsupported event`,{eventName:e.eventName}),bc(e.eventName);switch(e.eventType){case`issue_comment`:return lc(e,t);case`discussion_comment`:return uc(e,t);case`issues`:return pc(e,t);case`pull_request`:return vc(e,t);case`pull_request_review_comment`:return yc(e,t);case`schedule`:return mc(t);case`workflow_dispatch`:return hc(e);default:return{shouldSkip:!1}}}const Sc={botLogin:null,requireMention:!0,allowedAssociations:yo,skipDraftPRs:!0,promptInput:null,senderAssociation:null};function Cc(e,t,n={}){let r={...Sc,...n},i=sc(e,r.botLogin,r.promptInput);r.senderAssociation!=null&&(i.action===`review_requested`||i.action===`ready_for_review`)&&i.author!=null&&(i={...i,author:{...i.author,association:r.senderAssociation}}),t.debug(`Routing event`,{eventName:e.eventName,eventType:e.eventType,hasMention:i.hasMention});let a=xc(i,r,t);return a.shouldSkip?{shouldProcess:!1,skipReason:a.reason,skipMessage:a.message,context:i}:{shouldProcess:!0,context:i}}async function wc(e,t){let n=i({phase:`context`}),r=qs(n),o=Wa({token:e.inputs.githubToken,logger:n}),s=await Ga(o,n),c=null;if(r.event.type===`pull_request`&&(r.event.action===`review_requested`||r.event.action===`ready_for_review`)){let{owner:e,repo:t}=r.repo;c=await Ye(o,e,t,r.event.sender.login,n)}let l=i({phase:`trigger`}),u=Cc(r,l,{botLogin:s,requireMention:!0,promptInput:e.inputs.prompt,senderAssociation:c});return u.shouldProcess?(l.info(`Event routed for processing`,{eventType:u.context.eventType,hasMention:u.context.hasMention,command:u.context.command?.action??null}),a(_.SHOULD_SAVE_CACHE,`true`),{githubClient:o,triggerResult:u,agentContext:await pt({logger:n,octokit:o,triggerContext:u.context,botLogin:s}),botLogin:s}):(l.info(`Skipping event`,{reason:u.skipReason,message:u.skipMessage}),Be({sessionId:null,cacheStatus:`miss`,duration:Date.now()-t}),null)}function $(e,t){return{key:`${e}-${t}`,entityType:e,entityId:t}}function Tc(e){return Ee(`sha256`).update(e).digest(`hex`).slice(0,8)}function Ec(e){if(e.eventType===`unsupported`)return null;if(e.eventType===`schedule`){let t=e.raw.event.type===`schedule`?e.raw.event.schedule:void 0;return $(`schedule`,Tc((t!=null&&t.trim().length>0?t:e.action)??`default`))}return e.eventType===`workflow_dispatch`?$(`dispatch`,String(e.runId)):e.target==null?null:e.eventType===`issue_comment`?e.target.kind===`issue`?$(`issue`,String(e.target.number)):e.target.kind===`pr`?$(`pr`,String(e.target.number)):null:e.eventType===`discussion_comment`?e.target.kind===`discussion`?$(`discussion`,String(e.target.number)):null:e.eventType===`issues`?e.target.kind===`issue`?$(`issue`,String(e.target.number)):null:(e.eventType===`pull_request`||e.eventType===`pull_request_review_comment`)&&e.target.kind===`pr`?$(`pr`,String(e.target.number)):null}function Dc(e){return`fro-bot: ${e.key}`}function Oc(e,t){let n=e.filter(e=>e.title===t);return n.length===0?null:n.reduce((e,t)=>t.time.updated>e.time.updated?t:e)}async function kc(e,t,n,r){try{let i=Oc(await ss(e,t,r),Dc(n));return i==null||i.time.archived!=null||i.time.compacting!=null?{status:`not-found`}:{status:`found`,session:i}}catch(e){return{status:`error`,error:e instanceof Error?e.message:String(e)}}}async function Ac(e,t,n,r){let a=i({phase:`session`}),o=J(re()),s=await ps(n.serverHandle.client,o,{limit:10},a);a.debug(`Listed recent sessions`,{count:s.length});let c=Ec(t.triggerResult.context),l=c==null?null:Dc(c),u=null,d=!1;if(c!=null){let e=await kc(n.serverHandle.client,o,c,a);e.status===`found`?(u=e.session.id,d=!0,a.info(`Session continuity: found existing session`,{logicalKey:c.key,sessionId:u})):e.status===`error`?a.warning(`Session continuity: lookup error, will create new`,{logicalKey:c.key,error:e.error}):a.info(`Session continuity: no existing session found`,{logicalKey:c.key})}let f=c?.key??t.agentContext.issueTitle??t.agentContext.repo,p=await hs(f,n.serverHandle.client,o,{limit:5},a);a.debug(`Searched prior sessions`,{query:f,resultCount:p.length});for(let e of p)r.addSessionUsed(e.sessionId);let m=i({phase:`attachments`}),h=t.agentContext.commentBody??``,g=Ro(h),_=null;if(g.length>0){m.info(`Processing attachments`,{count:g.length});let{validated:t,skipped:n}=Yo(await Uo(g,e.inputs.githubToken,void 0,m),void 0,m);(t.length>0||n.length>0)&&(_=qo(h,g,t,n),m.info(`Attachments processed`,{processed:t.length,skipped:n.length}))}return{recentSessions:s,priorWorkContext:p,attachmentResult:_,normalizedWorkspace:o,logicalKey:c,continueSessionId:u,isContinuation:d,sessionTitle:l}}async function jc(){let e=Date.now(),t=i({phase:`bootstrap`}),n=ze();n.start();let r=null,o=!1,s=0,l=null,u=null,d=null,f=null;a(_.SHOULD_SAVE_CACHE,`false`),a(_.CACHE_SAVED,`false`);try{t.info(`Starting Fro Bot Agent`);let i=await jo(t);if(i==null)return 1;d=i.opencodeResult.version;let a=await wc(i,e);if(a==null)return 0;l=a.githubClient;let c=`${a.triggerResult.context.repo.owner}/${a.triggerResult.context.repo.repo}`,p=await As(i.inputs.dedupWindow,a.triggerResult.context,c,e);if(!p.shouldProceed)return 0;r=await Co(a,i.logger);let m=await Fo(i);if(m==null)return 1;f=m.serverHandle,n.setCacheStatus(m.cacheStatus);let h=await Ac(i,a,m,n);u=h.attachmentResult;let g=await Ms(i,a,m,h,n,e);o=g.success,o&&p.entity!=null&&await js(a.triggerResult.context,p.entity,c),n.end(),s=await Ws(i,a,m,g,n,e,i.logger)}catch(r){s=1;let i=Date.now()-e,a=r instanceof Error?r.name:`UnknownError`,o=r instanceof Error?r.message:String(r);n.recordError(a,o,!1),n.end(),Be({sessionId:null,cacheStatus:`miss`,duration:i}),r instanceof Error?(t.error(`Agent failed`,{error:r.message}),c(r.message)):(t.error(`Agent failed with unknown error`),c(`An unknown error occurred`))}finally{await bs({bootstrapLogger:t,reactionCtx:r,githubClient:l,agentSuccess:o,attachmentResult:u,serverHandle:f,detectedOpencodeVersion:d})}return s}await jc().then(e=>{N.exit(e)});export{}; \ No newline at end of file diff --git a/docs/plans/2026-03-22-feat-agent-cohesion-session-continuity-plan.md b/docs/plans/2026-03-22-feat-agent-cohesion-session-continuity-plan.md new file mode 100644 index 00000000..08f22a96 --- /dev/null +++ b/docs/plans/2026-03-22-feat-agent-cohesion-session-continuity-plan.md @@ -0,0 +1,662 @@ +--- +title: "feat: Agent Cohesion via Deterministic Session Continuity" +type: feat +status: active +date: 2026-03-22 +origin: docs/brainstorms/2026-03-22-agent-cohesion-session-context-brainstorm.md +--- + +## Enhancement Summary + +**Deepened on:** 2026-03-22 **Sections enhanced:** 8 **Research agents used:** Oracle (architecture review), 3x Librarian (prompt ordering, SDK patterns, GitHub Actions artifacts), Explore (codebase patterns), Librarian (session continuity patterns) + +### Critical Findings + +1. **Title auto-update risk**: OpenCode auto-overwrites session titles based on first message content (`packages/opencode/src/session/prompt.ts:1963-1968`). A title set at creation (`fro-bot: pr-347`) may be replaced with an auto-generated summary. **Mitigation**: Re-set title via `session.update()` after first prompt, or use the `search` query parameter for lookup instead of relying on exact title preservation. +2. **Server-side title search exists**: The `listGlobal()` endpoint accepts a `search` parameter for case-insensitive title filtering (`packages/opencode/src/server/routes/experimental.ts:223`). This avoids the N+1 performance issue of client-side filtering. +3. **`listSessions()` is expensive**: Current implementation reads messages for every session to extract agent names (`src/services/session/search.ts:40-41`). Using it for title lookup triggers N+1 message reads. Use `listSessionsForProject()` from `storage.ts` or SDK `search` parameter instead. +4. **Exact match required**: Prefix matching `fro-bot: pr-34` would collide with `fro-bot: pr-347`. Use exact string equality, not prefix/substring matching. +5. **Prompt "Instruction Sandwich"**: LLMs exhibit U-shaped attention (primacy + recency). Moving Agent Context to position 11 risks the agent missing critical constraints. Keep a short non-negotiable rules block at position 1 AND duplicate key constraints at the end. +6. **Session busy state**: Before continuing a session, must check it's not busy/compacting. OpenCode's `assertNotBusy()` will reject prompts to active sessions. + +### New Considerations Discovered + +- XML tags are 23% more effective than Markdown headers for Claude's structural parsing (Anthropic 2026 Context Engineering Guide) +- GitHub Copilot coding agent uses entity-scoped keys (Issue/PR ID) for session identity β€” validates our logical key approach +- "Rolling Summary" pattern (summary + key decisions + outstanding todos) reduces prior-work token cost by ~80% while preserving decision context +- `upload-artifact@v4+` recommends `retention-days: 7`, `compression-level: 9`, and `include-hidden-files: true` for log artifacts + +--- + +# Agent Cohesion via Deterministic Session Continuity + +## Overview + +Introduce deterministic logical session keys so repeated agent runs on the same PR, issue, discussion, or scheduled task preferentially continue the same session lineage rather than creating a new session every time. Restructure prompts so the agent sees its own prior thread context before generic history. Ship observability improvements alongside so the behavior is measurable from day one. + +## Problem Statement + +The agent currently has **persistence without continuity**. Every run calls `client.session.create()` (`src/features/agent/execution.ts:53`) and gets a new session with a meaningless timestamp title. Prior work is discovered via fuzzy text search using `issueTitle ?? repo` (`src/harness/phases/session-prep.ts:42`), which is: + +- **Unstable**: Issue titles change. Repository names are too broad to be useful as a search key. +- **Non-deterministic**: The same PR can match different prior sessions across runs depending on title edits or session content evolution. +- **Context-burying**: Even when prior work is found, it appears in the `## Prior Session Context` section near the bottom of the prompt, below generic operating rules, task directives, environment info, and response protocol. + +The result: the agent frequently re-investigates issues, doesn't recall its own decisions from prior runs, and presents generic context where specific thread continuity should dominate. + +### Case Study Evidence (see brainstorm: docs/brainstorms/2026-03-22-agent-cohesion-session-context-brainstorm.md) + +- PR test runs (via `ci.yaml`) preserve prompt/log artifacts; real Fro Bot runs (via `fro-bot.yaml`) do not upload `opencode-logs`. +- OpenCode creates sessions titled "New session - 2026-03-22T06:52:13.670Z" β€” useless as stable lookup keys. +- Prompts front-load generic operating rules before the most important continuity signal. +- Noise like `Blocked 3 postinstalls` and `tool.registry ... invalid` appears in logs without being clearly actionable or suppressed. + +## Proposed Solution + +A balanced continuity-first rollout phased across four work streams: + +1. **Logical Keys** β€” Deterministic entity-scoped keys derived from trigger context +2. **Session Mapping** β€” Persistent key-to-session-ID mapping with fallback to deterministic retrieval +3. **Prompt Restructuring** β€” Thread identity near the top; current thread before historical context +4. **Observability** β€” Universal log/prompt artifact retention for all trigger types + +## Technical Approach + +### Architecture + +``` +NormalizedEvent + | + v +buildLogicalKey(TriggerContext) --> LogicalSessionKey + | | + v v +session-prep.ts execution.ts + listSessions() + filter session.create({ body: { title } }) + by title prefix match OR continue existing session + | | + v v +resolvedSessionId prompt.ts + (continue or create) thread identity section at top + current thread context before history +``` + +The logical key is the new first-class concept. It answers: "What logical conversation does this run belong to?" + +The **session title** is the persistence mechanism. When creating a session, we set the title to a prefixed logical key (e.g., `fro-bot: pr-347`). On subsequent runs, we list sessions and match by title prefix. This gives us: + +- **O(1) stability**: The title is immutable once set by us and survives across cache cycles. +- **Human readability**: Visible in OpenCode UI and logs. +- **Self-healing**: No external state file to corrupt or lose β€” the truth lives in the sessions themselves. +- **Searchable fallback**: Title text is searchable alongside session content. + +### Logical Key Design + +Each trigger family maps to a deterministic key: + +| Event Type | Key Pattern | Source Fields | +| ----------------------------- | --------------------- | -------------------------------------------------------- | +| `issue_comment` (on issue) | `issue-{number}` | `event.issue.number`, `!event.issue.isPullRequest` | +| `issue_comment` (on PR) | `pr-{number}` | `event.issue.number`, `event.issue.isPullRequest` | +| `discussion_comment` | `discussion-{number}` | `event.discussion.number` | +| `issues` | `issue-{number}` | `event.issue.number` | +| `pull_request` | `pr-{number}` | `event.pullRequest.number` | +| `pull_request_review_comment` | `pr-{number}` | `event.pullRequest.number` | +| `schedule` | `schedule-{hash}` | SHA-256 of cron expression or `schedule-default` | +| `workflow_dispatch` | `dispatch-{runId}` | `context.runId` (each manual dispatch is its own thread) | + +The key is scoped to `{owner}/{repo}` implicitly (sessions are already project-scoped via workspace path). The key format is `{entity-type}-{identifier}` β€” simple, stable, loggable. + +**Implementation**: Pure function `buildLogicalKey(context: TriggerContext): LogicalSessionKey | null` + +```typescript +export interface LogicalSessionKey { + readonly key: string + readonly entityType: "discussion" | "dispatch" | "issue" | "pr" | "schedule" + readonly entityId: string +} +``` + +Note: The existing dedup phase (`src/harness/phases/dedup.ts:20-38`) already extracts entity type and number for PR/issue events via `extractDedupEntity()`. The logical key function should follow the same pattern but cover all trigger families. + +#### Research Insights: Key Design Edge Cases + +**Industry validation**: GitHub Copilot coding agent uses entity-scoped keys (Issue ID / PR ID) as the primary anchor for session identity. A `findLinkedCopilotPR` function searches for PRs authored by the bot that reference a specific issue number. This validates our entity-type + number approach. + +**Schedule key refinement**: The Oracle review identified that `schedule-{hash}` needs more context to avoid collisions across workflows. A single repository may have multiple workflow files with different cron schedules. Recommended key: `schedule-{hash(workflowPath + cronExpression + promptInput)}` to ensure different scheduled tasks in different workflows get their own sessions. + +**Dispatch key and reruns**: GitHub reruns maintain the same `runId` but increment `run_attempt`. Decision needed: should a rerun continue the same session or start fresh? Recommended: `dispatch-{runId}` (same session on rerun) since reruns typically mean "try again" not "do something different". Include `run_attempt` in logs but not in the key. + +**Closed/reopened PRs**: A PR that's closed and reopened should keep the same session (the investigation is still relevant). This happens naturally since the PR number doesn't change. + +**Transferred issues**: An issue transferred between repos should NOT share a session. The logical key is implicitly repo-scoped because sessions are project-scoped via workspace path. However, if the same repo is checked out at different paths across runs, workspace normalization (`normalizeWorkspacePath()` in `src/shared/paths.ts`) ensures consistency. + +### Session Title Convention + +The OpenCode SDK supports setting a title at session creation: + +```typescript +// From anomalyco/opencode β€” packages/opencode/src/session/index.ts:218 +export const create = fn( + z.object({ + parentID: SessionID.zod.optional(), + title: z.string().optional(), + permission: Info.shape.permission, + workspaceID: WorkspaceID.zod.optional(), + }), +) +``` + +The SDK client call (as used by oMo β€” `code-yeongyu/oh-my-openagent`): + +```typescript +await client.session.create({ + body: {title: `fro-bot: pr-347`}, +}) +``` + +**Title format**: `fro-bot: {logical-key}` + +- Prefix `fro-bot: ` distinguishes our sessions from manually-created ones. +- The logical key portion (`pr-347`, `issue-12`, `schedule-a1b2c3`) is the stable lookup target. +- Example titles: `fro-bot: pr-347`, `fro-bot: issue-42`, `fro-bot: discussion-5`, `fro-bot: schedule-d4e5f6` + +#### Research Insights: Session Title Robustness + +**Title auto-update risk**: OpenCode auto-generates titles from the first message content (`packages/opencode/src/session/prompt.ts:1963-1968`). A title set at creation may be overwritten after the first prompt. Mitigations: + +1. **Re-set title after prompt**: Call `client.session.update()` (PATCH endpoint at `routes/session.ts:265`) to restore the logical key title after each prompt. This is the safest approach. +2. **Use `search` parameter for lookup**: The experimental `listGlobal()` endpoint accepts `search` for case-insensitive title filtering. Even if the title is modified, a search for `fro-bot: pr-347` within the title text would still match if the original prefix is retained as a substring. +3. **Embed key in writeback summary**: The plan already includes the logical key in `writeSessionSummary()` output. This provides a content-based fallback independent of title state. + +**Recommended approach**: Set title at creation AND re-set after first prompt. Use the `search` parameter for primary lookup, with writeback content search as fallback. + +**Exact match requirement**: Use exact string equality for title matching, never prefix/substring. `fro-bot: pr-34` must NOT match `fro-bot: pr-347`. The `search` parameter does substring matching, so filter results to exact title equality client-side after server-side narrowing. + +**Server-side title filtering**: The SDK supports `search` on session listing: + +```typescript +// Server-side filtering β€” avoids N+1 message reads +const response = await client.session.list({ + query: {directory: workspacePath, search: "fro-bot: pr-347"}, +}) +// Then exact-match filter client-side +const match = response.data?.find(s => s.title === "fro-bot: pr-347") +``` + +### Session Continuity Flow + +Modified execution flow in `session-prep.ts` and `execution.ts`: + +``` +1. buildLogicalKey(triggerContext) β†’ key +2. buildSessionTitle(key) β†’ "fro-bot: {key}" +3. listSessions() β†’ find session with matching title +4. If matching session found: + a. Verify session is usable (not archived, not too old) + b. Use session.id as continuation target +5. If no title match: + a. Fallback: searchSessions() with logical key as query + b. If fallback match found β†’ use that session +6. If nothing found β†’ create new session with title set to "fro-bot: {key}" +7. Pass resolved session context to prompt builder +``` + +**SDK capabilities used**: + +- `client.session.create({ body: { title } })` β€” Create with deterministic title +- `client.session.list({ query: { directory } })` β€” List sessions, filter by title client-side +- `client.session.prompt({ path: { id }, body: { parts } })` β€” Send prompt to existing session + +When continuing an existing session: + +- Skip `client.session.create()` entirely +- Use `sendPromptToSession(client, existingSessionId, prompt, ...)` directly + +This requires modifying `executeOpenCode()` to accept an optional `continueSessionId` parameter. + +**Why not an external session map file?** The session title IS the mapping. Using the SDK's `search` parameter for server-side title filtering avoids the N+1 overhead entirely. This avoids a separate state file that can corrupt, go stale, or get out of sync with actual sessions. + +#### Research Insights: Session Lookup Performance and Safety + +**N+1 message reads**: The current `listSessions()` in `src/services/session/search.ts:39-54` calls `getSessionMessages()` for every session to extract agent names. Do NOT use this function for title-based lookup. Instead use either: + +1. `listSessionsForProject()` from `storage.ts` β€” returns `SessionInfo` metadata without reading messages +2. SDK `search` parameter β€” server-side title filtering, most efficient + +**Session busy state**: Before continuing a session, verify it's idle. OpenCode internally calls `assertNotBusy(sessionID)` before accepting prompts (`packages/opencode/src/session/revert.ts:24`). Session states to check: + +- `idle` β€” safe to continue +- `busy` β€” active operation in progress; create new session instead +- `compacting` β€” mid-compaction (`time.compacting` set); wait briefly or create new +- `archived` β€” soft-deleted (`time.archived` set); excluded from default listings + +**Error handling tri-state**: The Oracle review identified that the storage layer collapses SDK errors into empty results (`[]` / `null`). The session resolver should distinguish three outcomes: + +1. **Found** β€” session exists and is idle β†’ continue it +2. **Not found** β€” no matching session β†’ create new +3. **Lookup error** β€” SDK failure β†’ log warning, create new session, do NOT assume "not found" + +**Concurrency guard**: If two workflow runs for the same PR overlap (e.g., `synchronize` + `issue_comment` within seconds), both may attempt to continue the same session. The action cannot assume users configured workflow concurrency groups correctly. Mitigations: + +- Check session busy state before prompting +- If busy, create a fresh session with the same logical key title (the stale one gets pruned normally) +- Log when a continuity reset occurs for debugging + +### Prompt Restructuring + +Current prompt section order (from `src/features/agent/prompt.ts:120-284`): + +``` +1. Agent Context (generic CI operating rules) +2. Custom Prompt (schedule/dispatch only) +3. Task Section (trigger directive) +4. Output Contract (PR only) +5. Environment (repo, branch, actor, run ID) +6. Issue/PR Context (number, title, type) +7. Trigger Comment (comment body) +8. Prior Session Context (recent sessions + search results) <-- buried +9. PR Diff Summary +10. Hydrated Context (GraphQL) +11. Session Management Instructions +12. Response Protocol +13. GitHub CLI Reference +``` + +Proposed reordering: + +``` +1. Thread Identity (NEW β€” logical key, continuation status, thread history summary) +2. Task Section (trigger directive β€” what to do) +3. Trigger Comment (what the user said) +4. Current Thread Context (NEW β€” prior work from THIS logical thread only) +5. Output Contract (PR only) +6. Environment (repo, branch, actor, run ID) +7. Issue/PR Context (number, title, type) +8. PR Diff Summary +9. Hydrated Context (GraphQL) +10. Related Historical Context (RENAMED β€” other sessions, not current thread) +11. Agent Context (generic CI operating rules β€” demoted) +12. Session Management Instructions +13. Response Protocol +14. GitHub CLI Reference +``` + +Key changes: + +- **Thread Identity** (new section): Appears first. Contains logical key, whether this is a continuation or fresh start, and a 2-3 sentence summary of what the thread has covered so far. +- **Current Thread Context** (new section): Replaces the generic "Prior Session Context" when a mapped session exists. Shows excerpts from the exact logical thread, not fuzzy search matches. +- **Related Historical Context** (renamed): The old "Prior Session Context" becomes secondary. Only shown when there's additional relevant work outside the current thread. +- **Agent Context** (demoted): Generic operating rules move below task-specific context. The agent needs to know WHAT to do before HOW to operate. +- **Non-Negotiable Rules** (new, position 1): Short block of hard constraints that must NOT be buried. Extracted from Agent Context to ensure primacy attention. + +#### Research Insights: Prompt Ordering Best Practices + +**U-shaped attention model**: LLMs (including Claude) exhibit a consistent U-shaped performance curve β€” information at the absolute beginning (primacy) and end (recency) of the prompt gets the most attention. Content between 20% and 80% of the context window is under-attended (MIT 2025 follow-up study). This means: + +- Non-negotiable rules (single comment, Run Summary, non-interactive CI) MUST stay at position 1 AND be duplicated near the end +- Reference material (environment, CLI examples) can safely sit in the middle "valley" +- The task/trigger comment should be near the top for primacy + +**Instruction Sandwich pattern**: Place core constraints at the top AND a summary of key rules at the bottom. This is the standard pattern for 2026 agents. Revised ordering: + +``` +1. Non-Negotiable Rules (NEW β€” extracted hard constraints, ~5 lines) +2. Thread Identity (logical key, continuation status, thread summary) +3. Task Section (trigger directive) +4. Trigger Comment (what the user said) +5. Current Thread Context (prior work from THIS thread) +6. Output Contract (PR only) +7. Environment (repo, branch, actor, run ID) +8. Issue/PR Context (number, title, type) +9. PR Diff Summary +10. Hydrated Context (GraphQL) +11. Related Historical Context (other sessions) +12. Agent Context (detailed CI operating rules β€” expanded guidance) +13. Session Management Instructions +14. Response Protocol (includes Run Summary template) +15. GitHub CLI Reference +16. Constraint Reminder (NEW β€” 3-line repeat of non-negotiables for recency) +``` + +**XML tags vs Markdown**: Anthropic's 2026 Context Engineering Guide reports XML tags (``, ``) are 23% more effective than Markdown headers for Claude because they provide explicit structural cues for the attention mechanism. Consider migrating prompt sections from `## Heading` to `
` format in a future iteration. This is not blocking for the current plan but is a high-value future optimization. + +**Rolling Summary pattern**: For prior-work injection, condense the continued session's history into a structured block rather than raw excerpts: + +``` + + Reviewed PR #347: identified 3 issues in auth module, suggested test additions. + Chose JWT over session tokens for stateless auth. Approved migration path. + - Fix failing test in UserService.test.ts + +``` + +This reduces token cost by ~80% compared to injecting full message excerpts while preserving decision context. + +### Observability Improvements + +1. **Upload `opencode-logs` in `fro-bot.yaml`**: Add the same `actions/upload-artifact` step that `ci.yaml` uses at lines 211-215. + +2. **Prompt artifact retention for all triggers**: Already implemented via `isOpenCodePromptArtifactEnabled()` in `execution.ts:62`. The gap is that `fro-bot.yaml` doesn't upload the resulting artifacts. Fix is in (1) above. + +3. **Log noise triage**: Document expected vs unexpected log messages: + - `Blocked 3 postinstalls` β€” Expected noise from Bun; suppress or annotate in setup phase + - `tool.registry ... invalid` β€” Investigate whether this indicates a misconfigured tool registry path + +#### Research Insights: Artifact Upload Best Practices + +**upload-artifact v4+ recommendations** (2025-2026 best practices): + +```yaml +- name: Upload OpenCode Logs + if: always() + uses: actions/upload-artifact@v4 # pin to exact SHA in practice + with: + name: opencode-logs-${{ github.run_id }}-${{ github.run_attempt }} + path: ~/.local/share/opencode/log + retention-days: 7 # 7 days standard for debug logs + include-hidden-files: true # required if logs under hidden dirs + compression-level: 9 # max compression for text-heavy logs + if-no-files-found: warn # don't fail if agent didn't produce logs +``` + +**Key considerations**: + +- **Unique names**: Include `run_id` and `run_attempt` to avoid "Artifact already exists" errors on reruns +- **`if: always()`**: Ensures logs are captured even when the agent step fails β€” critical for debugging LLM errors +- **`retention-days: 7`**: Industry standard for temporary debugging logs; reduces storage cost while providing enough review time +- **`compression-level: 9`**: Recommended for large text-based log files; saves storage at minimal CPU cost +- **Post-action timing**: `upload-artifact` only captures files that exist at the moment it runs. Position it as the last step to capture as much as possible. Post-action hooks (like `post.ts`) run after all steps, so their logs may not be captured unless written to a persistent directory during the main run + +## System-Wide Impact + +### Interaction Graph + +- `buildLogicalKey()` is called in session-prep phase β†’ used to build session title β†’ `listSessions()` filters by title β†’ determines whether `executeOpenCode()` creates or continues a session β†’ affects prompt content (thread identity section) β†’ affects session writeback (summary includes logical key) β†’ title persists in session metadata for future lookups. +- No external state files introduced. Session titles are the single source of truth for logical key mapping, stored within OpenCode's own session storage. + +### Error Propagation + +- Title-based lookup returns no match β†’ fallback to content-based search via `searchSessions()`. No hard dependency on title matching. +- Matched session no longer usable (archived, corrupted) β†’ fall back to search, then create new session with the same title. +- SDK `session.list()` failure β†’ treat as empty list, create new session. Worst case: a duplicate session exists with the same title (harmless β€” next run picks the most recently updated). +- SDK `session.create()` with title fails β†’ fall back to create without title (current behavior), log warning. + +### State Lifecycle Risks + +- **Pruned sessions**: A session with a logical key title gets pruned by `pruneSessions()`. Next run finds no title match, falls back to search or creates fresh. No orphaned references since there's no external map. +- **Cache eviction**: GitHub Actions cache has 7-day inactivity expiry. If the cache is evicted, all sessions (and their titles) are lost. S3 backup (if enabled) preserves them. System rebuilds naturally on next run. +- **Branch isolation**: Sessions are already branch-scoped via cache keys (`opencode-storage-{repo}-{branch}-{os}`). A PR branch gets its own sessions. When the branch is deleted, the cache is eventually evicted. +- **Duplicate titles**: If two runs race and both create sessions with the same title, subsequent runs pick the most recently updated one. The stale duplicate gets pruned normally. + +### API Surface Parity + +- `listSessions()` in `src/services/session/search.ts` gains a new caller (title-based lookup) but its signature and behavior are unchanged. +- `searchSessions()` remains unchanged β€” used as fallback when title match fails and for "Related Historical Context". +- `writeSessionSummary()` in `src/services/session/writeback.ts` should include the logical key in the summary text so it's searchable by future fallback queries. + +### Integration Test Scenarios + +1. **Continuation success**: Two sequential runs for the same PR β†’ second run finds session titled `fro-bot: pr-347` β†’ continues that session, prompt shows thread identity. +2. **Stale session recovery**: Titled session was pruned β†’ second run finds no title match β†’ falls back to content search β†’ creates new session with same title. +3. **Cross-event continuity**: `pull_request` opened β†’ `issue_comment` on same PR β†’ both produce logical key `pr-347` β†’ second run finds session titled `fro-bot: pr-347` and continues it. +4. **Schedule thread stability**: Two cron-triggered runs with same expression β†’ both produce same `schedule-{hash}` key β†’ second run continues the first's session. +5. **Cache eviction recovery**: All sessions lost β†’ title match fails, content search fails β†’ creates fresh session with deterministic title β†’ continuity resumes from next run. +6. **Title creation**: First run for a new issue β†’ no matching title β†’ creates session titled `fro-bot: issue-42` β†’ subsequent runs find it by title. + +## Acceptance Criteria + +### Functional Requirements + +- [ ] `buildLogicalKey()` produces deterministic keys for all 7 supported event types (`src/services/github/types.ts` EVENT_TYPES minus `unsupported`) +- [ ] Sessions are created with deterministic titles (`fro-bot: {logical-key}`) via `client.session.create({ body: { title } })` +- [ ] Repeated runs on the same PR consistently continue the same OpenCode session (matched by title) +- [ ] Repeated runs on the same issue consistently continue the same OpenCode session +- [ ] Discussion comment runs continue the same session for the same discussion number +- [ ] Schedule runs with the same cron expression continue the same session thread +- [ ] Workflow dispatch runs create new sessions (each dispatch is independent) +- [ ] When a titled session is missing/stale, the system falls back to content-based search +- [ ] When both title match and search fail, a new session is created with the logical key title +- [ ] `executeOpenCode()` can continue an existing session instead of always creating a new one + +### Prompt Requirements + +- [ ] Thread Identity section appears near the top of the prompt (before task section) +- [ ] Current thread context appears before related historical context +- [ ] Generic operating rules (Agent Context) appear after task-specific content +- [ ] Prompt distinguishes "current thread context" from "related historical context" +- [ ] Session writeback includes logical key for future searchability + +### Observability Requirements + +- [ ] `fro-bot.yaml` uploads `opencode-logs` artifact for all trigger types +- [ ] Prompt artifacts are preserved and uploadable for schedule, issue, discussion, and comment triggers +- [ ] Session resolution is logged (title match hit/miss, fallback to search, new session created) +- [ ] Logical key is included in execution phase logs + +### Non-Functional Requirements + +- [ ] Title-based session lookup adds negligible overhead (client-side filter on ≀50 sessions) +- [ ] Fallback to content-based search maintains current behavior β€” no regression if title match fails +- [ ] No external state files introduced β€” session titles are the single source of truth + +### Quality Gates + +- [ ] All new code has colocated `.test.ts` files following BDD comments (`// #given`, `// #when`, `// #then`) +- [ ] `buildLogicalKey()` has exhaustive tests for all event types including edge cases (missing fields, null values) +- [ ] Session resolution logic has tests for title match, fallback search, and new session creation +- [ ] Prompt restructuring has snapshot tests verifying section order +- [ ] `pnpm test && pnpm lint && pnpm check-types && pnpm build` pass with no regressions +- [ ] `dist/` stays in sync after build + +## Implementation Phases + +### Phase 1: Logical Keys and Session Resolution + +**Goal**: Ship the logical key computation and title-based session resolution without changing prompt structure. + +**Tasks**: + +1. Create `src/services/session/logical-key.ts` (~80 LOC) + - `buildLogicalKey(context: TriggerContext): LogicalSessionKey | null` + - `buildSessionTitle(key: LogicalSessionKey): string` β€” returns `fro-bot: {key.key}` + - `findSessionByTitle(sessions: readonly SessionInfo[], title: string): SessionInfo | null` β€” exact match, NOT prefix/substring; picks most recently updated if multiple matches + - `resolveSessionForLogicalKey(client, workspacePath, key, logger): Promise` β€” the single entry point for session reuse decisions. Returns `{status: 'found', session} | {status: 'not-found'} | {status: 'error', error}` + - Pure key/title functions have no side effects; resolver is the only function with SDK calls + - Cover all 7 event types from `NormalizedEvent` + +2. Create `src/services/session/logical-key.test.ts` + - Exhaustive tests for all event types + - Edge cases: missing target, null fields, `unsupported` event + - Title generation and exact matching tests (including collision cases: `pr-34` must NOT match `pr-347`) + - `findSessionByTitle` with multiple matches (picks most recently updated) + - `resolveSessionForLogicalKey` with mock SDK: found/not-found/error tri-state + - Schedule key uniqueness: different workflows with same cron get different keys + +**Estimated effort**: Small. Pure data model, no integration points yet. + +**Success criteria**: All tests pass, types compile, no runtime changes. + +### Phase 2: Session Continuity in Execution + +**Goal**: Modify execution to prefer continuing an existing session when a title-matched session exists. + +**Tasks**: + +1. Modify `src/harness/phases/session-prep.ts` + - Import `buildLogicalKey`, `buildSessionTitle`, `resolveSessionForLogicalKey` + - After listing recent sessions, call `resolveSessionForLogicalKey()` β€” this is the ONLY place session reuse decisions are made + - If resolution is `found`, set `continueSessionId` for execution phase + - If resolution is `not-found` or `error`, use logical key as deterministic search query (replaces `issueTitle ?? repo`) + - Add `logicalKey`, `continueSessionId`, and `isContinuation` to `SessionPrepPhaseResult` + - Use `listSessionsForProject()` (metadata-only, no message reads) instead of `listSessions()` for title lookup + +2. Modify `src/features/agent/execution.ts` + - Add optional `continueSessionId?: string` and `sessionTitle?: string` to function params (via `ExecutionConfig` or `PromptOptions`) + - If `continueSessionId` is provided, skip `client.session.create()` and use that session ID directly + - If creating a new session, pass title: `client.session.create({ body: { title: sessionTitle } })` + - After successful prompt, **re-set title** via `session.update()` to guard against OpenCode's auto-title behavior + - Note: oMo uses `{ body: { title }, query: { directory } } as Record` pattern for the SDK call + +3. Modify `src/harness/phases/execute.ts` + - Pass `continueSessionId` and `sessionTitle` from session-prep result to `executeOpenCode()` + +4. Update `src/services/session/writeback.ts` + - Include logical key in `formatSummaryForSession()` output + - Example: `Logical Thread: pr-347` + - This makes the key searchable as a content-based fallback even if title is overwritten + +**Estimated effort**: Medium. Touches 4 files, requires careful null handling for the continuation path. + +**Success criteria**: Repeated runs on the same PR reuse the same session ID. Fallback to content search when title match fails. New sessions get deterministic titles. + +### Phase 3: Prompt Restructuring + +**Goal**: Reorder prompt sections so thread identity and current-thread context appear before generic rules. + +**Tasks**: + +1. Create `src/features/agent/prompt-thread.ts` (~100 LOC) + - `buildNonNegotiableRulesSection(): string` β€” 5-line hard constraints block (non-interactive CI, gh-only output, exactly one comment/review, Run Summary required, bot marker required) + - `buildThreadIdentitySection(logicalKey, isContinuation, threadSummary): string` + - `buildCurrentThreadContextSection(currentThreadResults): string` β€” uses "Rolling Summary" pattern (summary + key decisions + outstanding todos) to reduce token cost + - `buildConstraintReminderSection(): string` β€” 3-line repeat of non-negotiables for recency attention + +2. Modify `src/features/agent/prompt.ts` + - Add `logicalKey?: LogicalSessionKey` and `isContinuation?: boolean` to `PromptOptions` + - Insert Non-Negotiable Rules section at position 1 (Instruction Sandwich top) + - Insert Thread Identity section at position 2 + - Split session context into "Current Thread" and "Related Historical" + - Keep detailed Agent Context at position 12 (expanded guidance, not just constraints) + - Add Constraint Reminder at position 16 (Instruction Sandwich bottom) + - Update `buildSessionContextSection()` to accept `currentThreadSessionId` and partition results + +3. Update `src/features/agent/types.ts` + - Add `logicalKey`, `isContinuation`, `currentThreadSessionId` to `PromptOptions` + +4. Update prompt snapshot tests to verify new section order + - Verify Non-Negotiable Rules appears at position 1 + - Verify Constraint Reminder appears at end + - Verify Thread Identity appears before Task Section + +**Estimated effort**: Medium. Mostly restructuring existing code, but prompt tests need careful updates. + +**Success criteria**: Prompts show thread identity near top. Current thread context is separated from related history. Snapshot tests verify the new layout. + +### Phase 4: Observability + +**Goal**: Ensure all trigger types produce inspectable artifacts. + +**Tasks**: + +1. Modify `.github/workflows/fro-bot.yaml` + - Add `actions/upload-artifact` step for `opencode-logs` from `~/.local/share/opencode/log` + - Use `if: always()` so logs are captured even on failure + - Set `retention-days: 7`, `compression-level: 9`, `include-hidden-files: true` + - Include `${{ github.run_id }}-${{ github.run_attempt }}` in artifact name for uniqueness + - Set `if-no-files-found: warn` (don't fail if agent didn't produce logs) + - Position as the last step before post-action hooks + +2. Add logical key to execution-phase log entries + - Log `logicalKey` alongside `sessionId` in session-prep and execute phases + +3. Document log noise decisions in code comments: + - `Blocked N postinstalls` β†’ expected Bun behavior, no action needed + - `tool.registry ... invalid` β†’ investigate root cause, file as separate issue if needed + +**Estimated effort**: Small. Workflow file change + log field additions. + +**Success criteria**: `Fro Bot` runs produce downloadable `opencode-logs` artifacts. Logical key appears in structured logs. + +## Alternative Approaches Considered + +### 1. Retrieval-First (Rejected) + +Keep creating fresh sessions, just improve search keys. Rejected because it fragments work across many sessions β€” cohesion improves but true continuity does not. (see brainstorm: docs/brainstorms/2026-03-22-agent-cohesion-session-context-brainstorm.md) + +### 2. Observability-First (Rejected) + +Ship artifact retention first, defer continuity. Rejected because it risks becoming instrumentation without behavior change. The brainstorm resolved that artifact retention should ship alongside continuity work, not separately. + +### 3. External Session Map File + +Persist a `session-map.json` file in the OpenCode storage directory with `logicalKey β†’ sessionId` entries. Provides O(1) lookup. + +Rejected as primary mechanism because: it introduces a separate state file that can corrupt, go stale, or diverge from actual sessions. The title-based approach stores the mapping IN the sessions themselves β€” no external state to manage. If O(1) lookup ever matters (unlikely with ≀50 sessions), an in-memory cache per run is sufficient. + +### 4. Embed Key in Session Content Only + +Embed the logical key only in writeback summaries and search for it via `searchSessions()`. This works as a fallback (and we do include the key in writeback for exactly this reason), but full-text search is slower and less precise than title matching. Used as the secondary fallback when title match fails. + +## Dependencies & Prerequisites + +- **OpenCode SDK**: Uses existing `session.create()` with `{ body: { title } }`, `session.prompt()`, `session.list()`, `session.get()` endpoints. The `title` parameter on create is supported since at least `anomalyco/opencode` current dev branch (confirmed via source: `packages/opencode/src/session/index.ts:218` β€” `Session.create` schema includes `title: z.string().optional()`). The JS SDK client passes this as `{ body: { title } }` β€” same pattern used by oMo in `src/cli/run/session-resolver.ts` and `src/tools/delegate-task/sync-session-creator.ts`. +- **Dedup phase**: Already shipped (`src/harness/phases/dedup.ts`). The logical key design is complementary β€” dedup prevents redundant runs, logical keys ensure continuity across allowed runs. +- **Session storage simplification plan**: Non-blocking. That plan consolidates mapper files; this plan adds new files alongside them. They can be merged independently. +- **Cache infrastructure**: No new files to cache. Session titles are stored within OpenCode's existing session storage, which is already cached as part of the `~/.local/share/opencode/` tree. + +## Risk Analysis & Mitigation + +| Risk | Likelihood | Impact | Mitigation | +| --- | --- | --- | --- | +| OpenCode auto-overwrites session title after first prompt | **High** | **Medium** | Re-set title via `session.update()` after each prompt. Use SDK `search` parameter for lookup (substring match) then exact-match filter client-side. Embed key in writeback summary as content-based fallback. | +| SDK `session.create` doesn't accept `title` in deployed version | Low | Medium | Verify SDK version. Fall back to creating without title (current behavior) + log warning. Title can be set retroactively via `PATCH /session/{id}` with `{ title }` (confirmed in `routes/session.ts:265`). | +| Session is busy/compacting when continuation attempted | Medium | Medium | Check session state before prompting. If busy/compacting, create new session with same logical key title. Log continuity reset for debugging. | +| Title-matched session was pruned between runs | Medium | Low | Fall back to content search then create new. Natural self-healing. | +| Cache eviction loses all sessions | Medium | Medium | S3 backup (if enabled) preserves sessions and titles; system rebuilds naturally on next run | +| SDK `session.prompt()` fails on continued session | Low | Medium | Catch error, create new session with same title, log warning | +| Duplicate sessions with same title (race condition) | Low | Low | Exact-match lookup picks most recently updated. Stale duplicate pruned normally. | +| `listSessions()` N+1 message reads for title lookup | **High** | Medium | Do NOT use `listSessions()` for title lookup. Use `listSessionsForProject()` (metadata only) or SDK `search` parameter for server-side filtering. | +| Prefix collision (pr-34 matches pr-347) | Medium | Medium | Use exact string equality for title matching, never prefix/substring. Server-side `search` results must be exact-match filtered client-side. | +| Agent Context demotion causes constraint violations | Medium | Medium | Use "Instruction Sandwich" pattern: keep 5-line non-negotiable rules block at position 1 AND duplicate as constraint reminder at position 16 (end). | +| Prompt restructuring breaks existing tests | High | Low | Snapshot tests need updating; this is expected, not a bug | +| Logical key collision (unlikely) | Very Low | Low | Keys are scoped by entity type + number; collisions are impossible within a single repo | + +## Future Considerations + +- **Cross-branch continuity**: A PR branch and main might want to share session context for the same issue. Currently blocked by branch-scoped cache keys. Could be addressed by adding a secondary lookup in the default branch cache. +- **Session compaction**: Long-lived threads (schedule tasks) may accumulate large histories. OpenCode's session summarization (`POST /session/{id}/summarize`) could be used periodically. Aider's `ChatSummary` pattern triggers compaction when token count exceeds a threshold, converting oldest messages into a high-level summary β€” worth evaluating for schedule threads. +- **Multi-session threads**: Some complex issues may benefit from branched sessions (parent-child). The `parentID` field in `SessionInfo` already supports this. +- **Session rollover policy**: Very active PRs (100+ comments) may grow sessions beyond useful context limits. Consider a rollover threshold where a new session is created with a summary of the old one carried forward. +- **XML tag migration**: Anthropic's 2026 Context Engineering Guide reports XML tags are 23% more effective than Markdown headers for Claude. A future iteration could migrate prompt sections from `## Heading` to `
` format for improved structural parsing. +- **Repository-native shared memory**: Squad-style "decision log" pattern β€” append structured blocks to a versioned `decisions.md` file in the repo. If all agent memory is wiped, it reads `decisions.md` to re-synchronize. Could complement session continuity for critical architectural decisions. + +## Documentation Plan + +- Update `AGENTS.md` code map to include new files (`logical-key.ts`, `prompt-thread.ts`) +- Add session continuity to the "How It Works" section of `README.md` +- Document the logical key scheme in a brief section of `AGENTS.md` under PATTERNS + +## Sources & References + +### Origin + +- **Brainstorm document:** [docs/brainstorms/2026-03-22-agent-cohesion-session-context-brainstorm.md](docs/brainstorms/2026-03-22-agent-cohesion-session-context-brainstorm.md) β€” Key decisions carried forward: (1) optimize for stable continuity over fuzzy retrieval, (2) deterministic logical keys as first-class concept, (3) observability ships alongside continuity + +### Internal References + +- Session creation: `src/features/agent/execution.ts:53` +- Session search query: `src/harness/phases/session-prep.ts:42` +- Prompt building: `src/features/agent/prompt.ts:120-284` +- Session context section: `src/features/agent/prompt.ts:335-379` +- NormalizedEvent types: `src/services/github/types.ts:33-146` +- Dedup entity extraction: `src/harness/phases/dedup.ts:20-38` +- Session writeback: `src/services/session/writeback.ts:52-77` +- Cache save: `src/services/cache/save.ts` +- State keys: `src/harness/config/state-keys.ts` +- CI artifact upload: `.github/workflows/ci.yaml:211-215` +- Fro Bot workflow: `.github/workflows/fro-bot.yaml` (missing artifact upload) + +### Related Plans + +- `docs/plans/2026-03-21-dedup-execution.md` β€” Execution deduplication (complementary) +- `docs/plans/2026-03-04-session-storage-simplification.md` β€” Storage layer consolidation (non-blocking) +- `docs/plans/2026-02-15-feat-opencode-sqlite-session-support-plan.md` β€” SQLite backend (non-blocking) + +### External References + +- OpenCode session create schema: `anomalyco/opencode` `packages/opencode/src/session/index.ts:218` β€” `{ parentID?, title?, permission?, workspaceID? }` +- OpenCode session update route: `anomalyco/opencode` `packages/opencode/src/server/routes/session.ts:265` β€” `PATCH` with `{ title?, time? }` +- oMo session creation with title: `code-yeongyu/oh-my-openagent` `src/cli/run/session-resolver.ts`, `src/tools/delegate-task/sync-session-creator.ts` +- OpenCode SDK JS API: `POST /session` (optional `{ title, parentID, permission }`), `POST /session/{id}/message`, `GET /session/{id}` +- OpenCode SDK docs: https://opencode.ai/docs/ diff --git a/src/features/agent/execution.ts b/src/features/agent/execution.ts index f1b597f9..e2151d81 100644 --- a/src/features/agent/execution.ts +++ b/src/features/agent/execution.ts @@ -50,12 +50,22 @@ export async function executeOpenCode( server = opencode.server } else client = serverHandle.client - const sessionResponse = await client.session.create() - if (sessionResponse.data == null || sessionResponse.error != null) - throw new Error( - `Failed to create session: ${sessionResponse.error == null ? 'No data returned' : String(sessionResponse.error)}`, - ) - const sessionId = sessionResponse.data.id + let sessionId: string + if (config?.continueSessionId == null) { + const createPayload = + config?.sessionTitle == null ? undefined : ({body: {title: config.sessionTitle}} as Record) + const sessionResponse = + createPayload == null ? await client.session.create() : await client.session.create(createPayload) + if (sessionResponse.data == null || sessionResponse.error != null) + throw new Error( + `Failed to create session: ${sessionResponse.error == null ? 'No data returned' : String(sessionResponse.error)}`, + ) + sessionId = sessionResponse.data.id + logger.info('Created new OpenCode session', {sessionId, sessionTitle: config?.sessionTitle ?? null}) + } else { + sessionId = config.continueSessionId + logger.info('Continuing existing OpenCode session', {sessionId}) + } const initialPrompt = buildAgentPrompt({...promptOptions, sessionId}, logger) const directory = getGitHubWorkspace() @@ -109,6 +119,20 @@ export async function executeOpenCode( const result = await sendPromptToSession(client, sessionId, prompt, files, directory, config, logger) if (result.success) { final = result.eventStreamResult + + // Best-effort title re-assertion: OpenCode may auto-overwrite session titles + // based on first message content. Re-set to preserve deterministic lookup. + if (config?.sessionTitle != null) { + try { + await (client.session as unknown as {update: (args: Record) => Promise}).update({ + sessionID: sessionId, + title: config.sessionTitle, + }) + } catch { + logger.debug('Best-effort session title re-assertion failed', {sessionId}) + } + } + return { success: true, exitCode: 0, diff --git a/src/features/agent/prompt-thread.ts b/src/features/agent/prompt-thread.ts new file mode 100644 index 00000000..c89979c2 --- /dev/null +++ b/src/features/agent/prompt-thread.ts @@ -0,0 +1,58 @@ +import type {LogicalSessionKey} from '../../services/session/logical-key.js' + +export function buildNonNegotiableRulesSection(): string { + return [ + '## Critical Rules (NON-NEGOTIABLE)', + '- You are a NON-INTERACTIVE CI agent. Do NOT ask questions. Make decisions autonomously.', + '- Post EXACTLY ONE comment or review per invocation. Never multiple.', + '- Include the Run Summary marker block in your comment.', + '- Use `gh` CLI for all GitHub operations. Do not use the GitHub API directly.', + '- Mark your comment with the bot identification marker.', + ].join('\n') +} + +export function buildThreadIdentitySection( + logicalKey: LogicalSessionKey | null, + isContinuation: boolean, + threadSummary: string | null, +): string { + if (logicalKey == null) { + return '' + } + + const lines = ['## Thread Identity'] + lines.push(`**Logical Thread**: \`${logicalKey.key}\` (${logicalKey.entityType} #${logicalKey.entityId})`) + + if (isContinuation) { + lines.push('**Status**: Continuing previous conversation thread.') + if (threadSummary != null && threadSummary.length > 0) { + lines.push('') + lines.push('**Thread Summary**:') + lines.push(threadSummary) + } + } else { + lines.push('**Status**: Fresh conversation β€” no prior thread found for this entity.') + } + + return lines.join('\n') +} + +export function buildCurrentThreadContextSection(priorWorkContext: string | null): string { + if (priorWorkContext == null || priorWorkContext.length === 0) { + return '' + } + + return [ + '## Current Thread Context', + 'This is work from your PREVIOUS runs on this same entity:', + '', + priorWorkContext, + ].join('\n') +} + +export function buildConstraintReminderSection(): string { + return [ + '## Reminder: Critical Rules', + '- ONE comment/review only. Include Run Summary marker. Use `gh` CLI only.', + ].join('\n') +} diff --git a/src/features/agent/prompt.test.ts b/src/features/agent/prompt.test.ts index 28ba1de3..ab24def3 100644 --- a/src/features/agent/prompt.test.ts +++ b/src/features/agent/prompt.test.ts @@ -1,3 +1,4 @@ +import type {LogicalSessionKey} from '../../services/session/logical-key.js' import type {SessionSearchResult, SessionSummary} from '../../services/session/types.js' import type {Logger} from '../../shared/logger.js' import type {TriggerContext} from '../triggers/types.js' @@ -54,6 +55,15 @@ function createMockSearchResult(overrides: Partial = {}): S } } +function createMockLogicalKey(overrides: Partial = {}): LogicalSessionKey { + return { + key: 'pr-42', + entityType: 'pr', + entityId: '42', + ...overrides, + } +} + describe('buildAgentPrompt', () => { let mockLogger: Logger @@ -82,6 +92,95 @@ describe('buildAgentPrompt', () => { expect(prompt).toContain('**Cache Status:** hit') }) + it('includes non-negotiable rules at top and constraint reminder at end', () => { + // #given + const options: PromptOptions = { + context: createMockContext(), + customPrompt: null, + cacheStatus: 'hit', + } + + // #when + const prompt = buildAgentPrompt(options, mockLogger) + + // #then + expect(prompt).toContain('## Critical Rules (NON-NEGOTIABLE)') + expect(prompt).toContain('## Reminder: Critical Rules') + + const criticalRulesIndex = prompt.indexOf('## Critical Rules (NON-NEGOTIABLE)') + const taskIndex = prompt.indexOf('## Task') + const reminderIndex = prompt.indexOf('## Reminder: Critical Rules') + const ghOpsIndex = prompt.indexOf('## GitHub Operations (Use gh CLI)') + + expect(criticalRulesIndex).toBe(0) + expect(taskIndex).toBeGreaterThan(criticalRulesIndex) + expect(reminderIndex).toBeGreaterThan(ghOpsIndex) + }) + + it('includes thread identity section when logical key is provided', () => { + // #given + const options: PromptOptions = { + context: createMockContext(), + customPrompt: null, + cacheStatus: 'hit', + logicalKey: createMockLogicalKey(), + isContinuation: true, + } + + // #when + const prompt = buildAgentPrompt(options, mockLogger) + + // #then + expect(prompt).toContain('## Thread Identity') + expect(prompt).toContain('**Logical Thread**: `pr-42` (pr #42)') + expect(prompt).toContain('**Status**: Continuing previous conversation thread.') + }) + + it('places current thread context above environment and historical context for continuation runs', () => { + // #given + const sessionContext: SessionContext = { + recentSessions: [createMockSessionSummary()], + priorWorkContext: [ + createMockSearchResult({ + sessionId: 'ses_current', + matches: [{messageId: 'msg_1', partId: 'part_1', role: 'assistant', excerpt: 'Current thread prior work'}], + }), + createMockSearchResult({ + sessionId: 'ses_other', + matches: [{messageId: 'msg_2', partId: 'part_2', role: 'assistant', excerpt: 'Other thread context'}], + }), + ], + } + const options: PromptOptions = { + context: createMockContext(), + customPrompt: null, + cacheStatus: 'hit', + sessionContext, + logicalKey: createMockLogicalKey(), + isContinuation: true, + currentThreadSessionId: 'ses_current', + } + + // #when + const prompt = buildAgentPrompt(options, mockLogger) + + // #then + expect(prompt).toContain('## Current Thread Context') + expect(prompt).toContain('Current thread prior work') + expect(prompt).toContain('## Related Historical Context') + expect(prompt).toContain('Other thread context') + + const currentThreadIndex = prompt.indexOf('## Current Thread Context') + const environmentIndex = prompt.indexOf('## Environment') + const relatedHistoryIndex = prompt.indexOf('## Related Historical Context') + const agentContextIndex = prompt.indexOf('# Agent Context') + + expect(currentThreadIndex).toBeGreaterThan(-1) + expect(currentThreadIndex).toBeLessThan(environmentIndex) + expect(relatedHistoryIndex).toBeGreaterThan(environmentIndex) + expect(agentContextIndex).toBeGreaterThan(relatedHistoryIndex) + }) + it('includes CI environment awareness with operating environment section', () => { // #given const options: PromptOptions = { diff --git a/src/features/agent/prompt.ts b/src/features/agent/prompt.ts index 2b5c6649..4e70cc00 100644 --- a/src/features/agent/prompt.ts +++ b/src/features/agent/prompt.ts @@ -5,11 +5,18 @@ * instructions, gh CLI examples, and run summary requirements. */ +import type {SessionSearchResult} from '../../services/session/types.js' import type {Logger} from '../../shared/logger.js' import type {TriggerContext} from '../triggers/types.js' import type {AgentContext, DiffContext, PromptOptions, SessionContext} from './types.js' import {formatContextForPrompt} from '../context/index.js' import {MAX_FILES_IN_PROMPT} from './diff-context.js' +import { + buildConstraintReminderSection, + buildCurrentThreadContextSection, + buildNonNegotiableRulesSection, + buildThreadIdentitySection, +} from './prompt-thread.js' export interface TriggerDirective { readonly directive: string @@ -118,30 +125,23 @@ export function buildTaskSection(context: TriggerContext, promptInput: string | * - Custom prompt if provided */ export function buildAgentPrompt(options: PromptOptions, logger: Logger): string { - const {context, customPrompt, cacheStatus, sessionContext} = options + const {context, customPrompt, cacheStatus, sessionContext, logicalKey, isContinuation, currentThreadSessionId} = + options const parts: string[] = [] + const continuationEnabled = isContinuation === true - // System context header - parts.push(`# Agent Context - -You are the Fro Bot Agent running in a non-interactive CI environment (GitHub Actions). - -## Operating Environment - -- **This is NOT an interactive session.** There is no human reading your assistant messages in real time. -- Your assistant messages are logged to the GitHub Actions job output. Use them only for diagnostic information (e.g., files read, decisions made, errors encountered) that helps troubleshoot issues in CI logs. -- The human who invoked you will ONLY see what you post as a GitHub comment or review. Your assistant messages are invisible to them. -- You MUST post your response using the gh CLI (see Response Protocol below). Do not rely on assistant message output to communicate with the user. -`) + parts.push(buildNonNegotiableRulesSection()) - if (customPrompt != null && customPrompt.trim().length > 0 && options.triggerContext == null) { - parts.push(` -${customPrompt.trim()} - -`) + const threadIdentitySection = buildThreadIdentitySection(logicalKey ?? null, continuationEnabled, null) + if (threadIdentitySection.length > 0) { + parts.push(threadIdentitySection) } - // Task section + const currentThreadContextText = + sessionContext != null && continuationEnabled && currentThreadSessionId != null + ? buildCurrentThreadPriorWorkText(sessionContext.priorWorkContext, currentThreadSessionId) + : null + if (options.triggerContext != null) { parts.push(buildTaskSection(options.triggerContext, customPrompt)) } else if (context.commentBody == null) { @@ -156,7 +156,28 @@ Respond to the trigger comment above. Follow all instructions and requirements l `) } - // Output Contract section (PR triggers only) + if (context.commentBody != null) { + parts.push(`## Trigger Comment +**Author:** ${context.commentAuthor ?? 'unknown'} + +\`\`\` +${context.commentBody} +\`\`\` +`) + } + + const currentThreadSection = buildCurrentThreadContextSection(currentThreadContextText) + if (currentThreadSection.length > 0) { + parts.push(currentThreadSection) + } + + if (customPrompt != null && customPrompt.trim().length > 0 && options.triggerContext == null) { + parts.push(` +${customPrompt.trim()} + +`) + } + if (options.triggerContext != null) { const eventType = options.triggerContext.eventType if (eventType === 'pull_request' || eventType === 'pull_request_review_comment') { @@ -164,7 +185,6 @@ Respond to the trigger comment above. Follow all instructions and requirements l } } - // Environment context parts.push(` ## Environment - **Repository:** ${context.repo} @@ -175,7 +195,6 @@ Respond to the trigger comment above. Follow all instructions and requirements l - **Cache Status:** ${cacheStatus} `) - // Issue/PR context if (context.issueNumber != null) { const typeLabel = context.issueType === 'pr' ? 'Pull Request' : 'Issue' parts.push(`## ${typeLabel} Context @@ -185,32 +204,38 @@ Respond to the trigger comment above. Follow all instructions and requirements l `) } - // Triggering comment - if (context.commentBody != null) { - parts.push(`## Trigger Comment -**Author:** ${context.commentAuthor ?? 'unknown'} - -\`\`\` -${context.commentBody} -\`\`\` -`) - } - - // Prior session context (RFC-004 integration) - if (sessionContext != null) { - parts.push(buildSessionContextSection(sessionContext)) - } - - // PR diff context (RFC-009 integration) if (context.diffContext != null) { parts.push(buildDiffContextSection(context.diffContext)) } - // Hydrated issue/PR context (RFC-015 integration) if (context.hydratedContext != null) { parts.push(formatContextForPrompt(context.hydratedContext)) } + if (sessionContext != null) { + const historicalSection = buildHistoricalSessionContext( + sessionContext, + continuationEnabled, + currentThreadSessionId, + currentThreadContextText != null, + ) + if (historicalSection != null) { + parts.push(historicalSection) + } + } + + parts.push(`# Agent Context + +You are the Fro Bot Agent running in a non-interactive CI environment (GitHub Actions). + +## Operating Environment + +- **This is NOT an interactive session.** There is no human reading your assistant messages in real time. +- Your assistant messages are logged to the GitHub Actions job output. Use them only for diagnostic information (e.g., files read, decisions made, errors encountered) that helps troubleshoot issues in CI logs. +- The human who invoked you will ONLY see what you post as a GitHub comment or review. Your assistant messages are invisible to them. +- You MUST post your response using the gh CLI (see Response Protocol below). Do not rely on assistant message output to communicate with the user. +`) + // Session management instructions (REQUIRED) parts.push(`## Session Management (REQUIRED) @@ -274,6 +299,8 @@ gh api repos/${context.repo}/pulls/${issueNum}/files --jq '.[].filename' \`\`\` `) + parts.push(buildConstraintReminderSection()) + const prompt = parts.join('\n') logger.debug('Built agent prompt', { length: prompt.length, @@ -332,8 +359,12 @@ Every response you post β€” regardless of channel (issue, PR, discussion, review * Build the session context section for the prompt. * Provides lightweight metadata and search excerpts to avoid prompt bloat. */ -function buildSessionContextSection(sessionContext: SessionContext): string { - const lines: string[] = ['## Prior Session Context'] +function buildSessionContextSection( + sessionContext: SessionContext, + sectionTitle: string, + priorWorkContext: readonly SessionSearchResult[], +): string { + const lines: string[] = [sectionTitle] // Recent sessions (lightweight metadata only) if (sessionContext.recentSessions.length > 0) { @@ -354,14 +385,14 @@ function buildSessionContextSection(sessionContext: SessionContext): string { } // Prior work context (search results) - if (sessionContext.priorWorkContext.length > 0) { + if (priorWorkContext.length > 0) { lines.push('') lines.push('### Relevant Prior Work') lines.push('') lines.push('The following sessions contain content related to this issue:') lines.push('') - for (const result of sessionContext.priorWorkContext.slice(0, 3)) { + for (const result of priorWorkContext.slice(0, 3)) { lines.push(`**Session ${result.sessionId}:**`) lines.push('```markdown') for (const match of result.matches.slice(0, 2)) { @@ -378,6 +409,58 @@ function buildSessionContextSection(sessionContext: SessionContext): string { return lines.join('\n') } +function buildCurrentThreadPriorWorkText( + priorWorkContext: readonly SessionSearchResult[], + currentThreadSessionId: string, +): string | null { + const currentThreadResults = priorWorkContext.filter(result => result.sessionId === currentThreadSessionId) + + if (currentThreadResults.length === 0) { + return null + } + + const lines: string[] = [] + for (const result of currentThreadResults.slice(0, 1)) { + lines.push(`**Session ${result.sessionId}:**`) + lines.push('```markdown') + for (const match of result.matches.slice(0, 3)) { + lines.push(`- ${match.excerpt}`) + } + lines.push('```') + } + + return lines.join('\n') +} + +function buildHistoricalSessionContext( + sessionContext: SessionContext, + isContinuation: boolean, + currentThreadSessionId: string | null | undefined, + hasCurrentThreadContext: boolean, +): string | null { + if (isContinuation && currentThreadSessionId != null) { + const relatedPriorWork = sessionContext.priorWorkContext.filter( + result => result.sessionId !== currentThreadSessionId, + ) + + if (sessionContext.recentSessions.length === 0 && relatedPriorWork.length === 0) { + return null + } + + return buildSessionContextSection(sessionContext, '## Related Historical Context', relatedPriorWork) + } + + if ( + sessionContext.recentSessions.length === 0 && + sessionContext.priorWorkContext.length === 0 && + hasCurrentThreadContext + ) { + return null + } + + return buildSessionContextSection(sessionContext, '## Prior Session Context', sessionContext.priorWorkContext) +} + function buildDiffContextSection(diffContext: DiffContext): string { const lines: string[] = ['## Pull Request Diff Summary'] lines.push('') diff --git a/src/features/agent/types.ts b/src/features/agent/types.ts index 4c9985fa..0b62d121 100644 --- a/src/features/agent/types.ts +++ b/src/features/agent/types.ts @@ -6,6 +6,7 @@ */ import type {FilePartInput} from '@opencode-ai/sdk' +import type {LogicalSessionKey} from '../../services/session/logical-key.js' import type {SessionSearchResult, SessionSummary} from '../../services/session/types.js' import type {ModelConfig, OmoProviders, TokenUsage} from '../../shared/types.js' import type {ErrorInfo} from '../comments/types.js' @@ -81,6 +82,9 @@ export interface PromptOptions { readonly customPrompt: string | null readonly cacheStatus: 'corrupted' | 'hit' | 'miss' readonly sessionContext?: SessionContext + readonly logicalKey?: LogicalSessionKey | null + readonly isContinuation?: boolean + readonly currentThreadSessionId?: string | null readonly sessionId?: string readonly triggerContext?: TriggerContext readonly fileParts?: readonly FilePartInput[] @@ -129,6 +133,8 @@ export interface ExecutionConfig { readonly model: ModelConfig | null readonly timeoutMs: number readonly omoProviders: OmoProviders + readonly continueSessionId?: string + readonly sessionTitle?: string } export interface EnsureOpenCodeResult { diff --git a/src/harness/phases/execute.ts b/src/harness/phases/execute.ts index aff84f61..5fce46d0 100644 --- a/src/harness/phases/execute.ts +++ b/src/harness/phases/execute.ts @@ -68,11 +68,18 @@ export async function runExecute( } } else { const execLogger = createLogger({phase: 'execution'}) + execLogger.info('Starting OpenCode execution', { + logicalKey: sessionPrep.logicalKey?.key ?? null, + continueSessionId: sessionPrep.continueSessionId, + }) + const executionConfig: ExecutionConfig = { agent: bootstrap.inputs.agent, model: bootstrap.inputs.model, timeoutMs: bootstrap.inputs.timeoutMs, omoProviders: bootstrap.inputs.omoProviders, + continueSessionId: sessionPrep.continueSessionId ?? undefined, + sessionTitle: sessionPrep.sessionTitle ?? undefined, } const execResult = await executeOpenCode(promptOptions, execLogger, executionConfig, cacheRestore.serverHandle) @@ -96,6 +103,12 @@ export async function runExecute( ...execResult, sessionId, } + + execLogger.info('Completed OpenCode execution', { + success: result.success, + sessionId: result.sessionId, + logicalKey: sessionPrep.logicalKey?.key ?? null, + }) } if (result.sessionId != null) { @@ -126,6 +139,7 @@ export async function runExecute( runId: Number(routing.agentContext.runId), cacheStatus: cacheRestore.cacheStatus, sessionIds: [result.sessionId], + logicalKey: sessionPrep.logicalKey?.key, createdPRs: [...result.prsCreated], createdCommits: [...result.commitsCreated], duration: Math.round((Date.now() - startTime) / 1000), diff --git a/src/harness/phases/session-prep.ts b/src/harness/phases/session-prep.ts index 43ae139f..b4f103ea 100644 --- a/src/harness/phases/session-prep.ts +++ b/src/harness/phases/session-prep.ts @@ -1,5 +1,6 @@ import type {AttachmentResult} from '../../features/attachments/index.js' import type {MetricsCollector} from '../../features/observability/index.js' +import type {LogicalSessionKey} from '../../services/session/logical-key.js' import type {SessionSearchResult, SessionSummary} from '../../services/session/types.js' import type {BootstrapPhaseResult} from './bootstrap.js' import type {CacheRestorePhaseResult} from './cache-restore.js' @@ -11,6 +12,7 @@ import { validateAttachments, } from '../../features/attachments/index.js' import {listSessions, searchSessions} from '../../services/session/index.js' +import {buildLogicalKey, buildSessionTitle, resolveSessionForLogicalKey} from '../../services/session/logical-key.js' import {getGitHubWorkspace} from '../../shared/env.js' import {createLogger} from '../../shared/logger.js' import {normalizeWorkspacePath} from '../../shared/paths.js' @@ -20,6 +22,10 @@ export interface SessionPrepPhaseResult { readonly priorWorkContext: readonly SessionSearchResult[] readonly attachmentResult: AttachmentResult | null readonly normalizedWorkspace: string + readonly logicalKey: LogicalSessionKey | null + readonly continueSessionId: string | null + readonly isContinuation: boolean + readonly sessionTitle: string | null } export async function runSessionPrep( @@ -39,7 +45,39 @@ export async function runSessionPrep( ) sessionLogger.debug('Listed recent sessions', {count: recentSessions.length}) - const searchQuery = routing.agentContext.issueTitle ?? routing.agentContext.repo + const logicalKey = buildLogicalKey(routing.triggerResult.context) + const sessionTitle = logicalKey == null ? null : buildSessionTitle(logicalKey) + let continueSessionId: string | null = null + let isContinuation = false + + if (logicalKey != null) { + const resolution = await resolveSessionForLogicalKey( + cacheRestore.serverHandle.client, + normalizedWorkspace, + logicalKey, + sessionLogger, + ) + + if (resolution.status === 'found') { + continueSessionId = resolution.session.id + isContinuation = true + sessionLogger.info('Session continuity: found existing session', { + logicalKey: logicalKey.key, + sessionId: continueSessionId, + }) + } else if (resolution.status === 'error') { + sessionLogger.warning('Session continuity: lookup error, will create new', { + logicalKey: logicalKey.key, + error: resolution.error, + }) + } else { + sessionLogger.info('Session continuity: no existing session found', { + logicalKey: logicalKey.key, + }) + } + } + + const searchQuery = logicalKey?.key ?? routing.agentContext.issueTitle ?? routing.agentContext.repo const priorWorkContext = await searchSessions( searchQuery, cacheRestore.serverHandle.client, @@ -80,5 +118,9 @@ export async function runSessionPrep( priorWorkContext, attachmentResult, normalizedWorkspace, + logicalKey, + continueSessionId, + isContinuation, + sessionTitle, } } diff --git a/src/services/session/logical-key.test.ts b/src/services/session/logical-key.test.ts new file mode 100644 index 00000000..ec528e55 --- /dev/null +++ b/src/services/session/logical-key.test.ts @@ -0,0 +1,416 @@ +import type {TriggerContext, TriggerTarget} from '../../features/triggers/types.js' +import type {EventType, GitHubContext} from '../github/types.js' +import type {SessionClient} from './backend.js' +import type {SessionInfo} from './types.js' +import {describe, expect, it, vi} from 'vitest' +import {createMockLogger} from '../../shared/test-helpers.js' +import {buildLogicalKey, buildSessionTitle, findSessionByTitle, resolveSessionForLogicalKey} from './logical-key.js' + +function createRawContext(eventType: EventType): GitHubContext { + return { + eventName: eventType, + eventType, + repo: {owner: 'fro-bot', repo: 'agent'}, + ref: 'refs/heads/main', + sha: 'abc123', + runId: 123, + actor: 'mrbrown', + payload: {}, + event: {type: 'unsupported'}, + } +} + +function createTarget(kind: TriggerTarget['kind'], number: number): TriggerTarget { + return { + kind, + number, + title: 'title', + body: null, + locked: false, + } +} + +function createTriggerContext(options?: { + readonly eventType?: EventType + readonly target?: TriggerTarget | null + readonly runId?: number + readonly action?: string | null +}): TriggerContext { + const eventType = options?.eventType ?? 'pull_request' + const target = options?.target === undefined ? createTarget('pr', 42) : options.target + + return { + eventType, + eventName: eventType, + repo: {owner: 'fro-bot', repo: 'agent'}, + ref: 'refs/heads/main', + sha: 'sha123', + runId: options?.runId ?? 1001, + actor: 'mrbrown', + action: options?.action ?? 'opened', + author: null, + target, + commentBody: null, + commentId: null, + hasMention: false, + command: null, + isBotReviewRequested: false, + raw: createRawContext(eventType), + } +} + +function createSession(options: { + readonly id: string + readonly title: string + readonly updated: number + readonly archived?: number + readonly compacting?: number +}): SessionInfo { + return { + id: options.id, + version: '1.1.53', + projectID: 'proj_1', + directory: '/workspace', + title: options.title, + time: { + created: options.updated - 100, + updated: options.updated, + archived: options.archived, + compacting: options.compacting, + }, + } +} + +function createMockSdkClient(options?: { + readonly sessionListResponse?: {readonly data?: unknown; readonly error?: unknown} +}) { + return { + session: { + list: vi.fn().mockResolvedValue(options?.sessionListResponse ?? {data: []}), + }, + } +} + +describe('buildLogicalKey', () => { + it('builds issue key for issue_comment on issue', () => { + // #given + const context = createTriggerContext({ + eventType: 'issue_comment', + target: createTarget('issue', 12), + }) + + // #when + const result = buildLogicalKey(context) + + // #then + expect(result).toEqual({key: 'issue-12', entityType: 'issue', entityId: '12'}) + }) + + it('builds pr key for issue_comment on pr', () => { + // #given + const context = createTriggerContext({ + eventType: 'issue_comment', + target: createTarget('pr', 347), + }) + + // #when + const result = buildLogicalKey(context) + + // #then + expect(result).toEqual({key: 'pr-347', entityType: 'pr', entityId: '347'}) + }) + + it('builds discussion key for discussion_comment', () => { + // #given + const context = createTriggerContext({ + eventType: 'discussion_comment', + target: createTarget('discussion', 5), + }) + + // #when + const result = buildLogicalKey(context) + + // #then + expect(result).toEqual({key: 'discussion-5', entityType: 'discussion', entityId: '5'}) + }) + + it('builds issue key for issues event', () => { + // #given + const context = createTriggerContext({ + eventType: 'issues', + target: createTarget('issue', 42), + }) + + // #when + const result = buildLogicalKey(context) + + // #then + expect(result).toEqual({key: 'issue-42', entityType: 'issue', entityId: '42'}) + }) + + it('builds pr key for pull_request event', () => { + // #given + const context = createTriggerContext({ + eventType: 'pull_request', + target: createTarget('pr', 88), + }) + + // #when + const result = buildLogicalKey(context) + + // #then + expect(result).toEqual({key: 'pr-88', entityType: 'pr', entityId: '88'}) + }) + + it('builds pr key for pull_request_review_comment event', () => { + // #given + const context = createTriggerContext({ + eventType: 'pull_request_review_comment', + target: createTarget('pr', 99), + }) + + // #when + const result = buildLogicalKey(context) + + // #then + expect(result).toEqual({key: 'pr-99', entityType: 'pr', entityId: '99'}) + }) + + it('builds schedule key from stable hash', () => { + // #given + const context = createTriggerContext({ + eventType: 'schedule', + action: '0 0 * * *', + target: null, + }) + + // #when + const result = buildLogicalKey(context) + + // #then + expect(result).toEqual({key: 'schedule-898cd73a', entityType: 'schedule', entityId: '898cd73a'}) + }) + + it('builds workflow dispatch key from runId', () => { + // #given + const context = createTriggerContext({ + eventType: 'workflow_dispatch', + runId: 90210, + target: null, + }) + + // #when + const result = buildLogicalKey(context) + + // #then + expect(result).toEqual({key: 'dispatch-90210', entityType: 'dispatch', entityId: '90210'}) + }) + + it('returns null for unsupported events', () => { + // #given + const context = createTriggerContext({ + eventType: 'unsupported', + target: null, + }) + + // #when + const result = buildLogicalKey(context) + + // #then + expect(result).toBeNull() + }) + + it('returns null when target is null for target-required events', () => { + // #given + const context = createTriggerContext({ + eventType: 'pull_request', + target: null, + }) + + // #when + const result = buildLogicalKey(context) + + // #then + expect(result).toBeNull() + }) + + it('returns deterministic key for same event context', () => { + // #given + const context = createTriggerContext({ + eventType: 'discussion_comment', + target: createTarget('discussion', 17), + }) + + // #when + const first = buildLogicalKey(context) + const second = buildLogicalKey(context) + + // #then + expect(first).toEqual(second) + }) +}) + +describe('buildSessionTitle', () => { + it('prefixes logical key with fro-bot namespace', () => { + // #given + const key = {key: 'pr-347', entityType: 'pr' as const, entityId: '347'} + + // #when + const title = buildSessionTitle(key) + + // #then + expect(title).toBe('fro-bot: pr-347') + }) +}) + +describe('findSessionByTitle', () => { + it('returns exact title match', () => { + // #given + const sessions = [createSession({id: 'ses_1', title: 'fro-bot: pr-347', updated: 100})] + + // #when + const result = findSessionByTitle(sessions, 'fro-bot: pr-347') + + // #then + expect(result?.id).toBe('ses_1') + }) + + it('returns null when no exact match exists', () => { + // #given + const sessions = [createSession({id: 'ses_1', title: 'fro-bot: pr-347', updated: 100})] + + // #when + const result = findSessionByTitle(sessions, 'fro-bot: issue-347') + + // #then + expect(result).toBeNull() + }) + + it('does not match prefix collisions', () => { + // #given + const sessions = [createSession({id: 'ses_1', title: 'fro-bot: pr-347', updated: 100})] + + // #when + const result = findSessionByTitle(sessions, 'fro-bot: pr-34') + + // #then + expect(result).toBeNull() + }) + + it('returns most recently updated session when duplicates exist', () => { + // #given + const sessions = [ + createSession({id: 'ses_old', title: 'fro-bot: pr-347', updated: 100}), + createSession({id: 'ses_new', title: 'fro-bot: pr-347', updated: 200}), + createSession({id: 'ses_other', title: 'fro-bot: pr-888', updated: 500}), + ] + + // #when + const result = findSessionByTitle(sessions, 'fro-bot: pr-347') + + // #then + expect(result?.id).toBe('ses_new') + }) +}) + +describe('resolveSessionForLogicalKey', () => { + it('returns found when matching non-archived session exists', async () => { + // #given + const key = {key: 'pr-347', entityType: 'pr' as const, entityId: '347'} + const session = createSession({id: 'ses_match', title: 'fro-bot: pr-347', updated: 500}) + const client = createMockSdkClient({sessionListResponse: {data: [session]}}) + + // #when + const result = await resolveSessionForLogicalKey( + client as unknown as SessionClient, + '/workspace', + key, + createMockLogger(), + ) + + // #then + expect(client.session.list).toHaveBeenCalledWith({query: {directory: '/workspace'}}) + expect(result).toEqual({status: 'found', session}) + }) + + it('returns not-found when no matching session exists', async () => { + // #given + const key = {key: 'pr-347', entityType: 'pr' as const, entityId: '347'} + const client = createMockSdkClient({ + sessionListResponse: {data: [createSession({id: 'ses_1', title: 'fro-bot: pr-999', updated: 100})]}, + }) + + // #when + const result = await resolveSessionForLogicalKey( + client as unknown as SessionClient, + '/workspace', + key, + createMockLogger(), + ) + + // #then + expect(result).toEqual({status: 'not-found'}) + }) + + it('returns error when SDK list call throws', async () => { + // #given + const key = {key: 'pr-347', entityType: 'pr' as const, entityId: '347'} + const client = { + session: { + list: vi.fn().mockRejectedValue(new Error('sdk exploded')), + }, + } + + // #when + const result = await resolveSessionForLogicalKey( + client as unknown as SessionClient, + '/workspace', + key, + createMockLogger(), + ) + + // #then + expect(result).toEqual({status: 'error', error: 'sdk exploded'}) + }) + + it('returns not-found when matched session is archived', async () => { + // #given + const key = {key: 'pr-347', entityType: 'pr' as const, entityId: '347'} + const archived = createSession({id: 'ses_archived', title: 'fro-bot: pr-347', updated: 500, archived: 1000}) + const client = createMockSdkClient({sessionListResponse: {data: [archived]}}) + + // #when + const result = await resolveSessionForLogicalKey( + client as unknown as SessionClient, + '/workspace', + key, + createMockLogger(), + ) + + // #then + expect(result).toEqual({status: 'not-found'}) + }) + + it('returns not-found when matched session is mid-compaction', async () => { + // #given + const key = {key: 'pr-347', entityType: 'pr' as const, entityId: '347'} + const compacting = createSession({ + id: 'ses_compacting', + title: 'fro-bot: pr-347', + updated: 500, + compacting: 900, + }) + const client = createMockSdkClient({sessionListResponse: {data: [compacting]}}) + + // #when + const result = await resolveSessionForLogicalKey( + client as unknown as SessionClient, + '/workspace', + key, + createMockLogger(), + ) + + // #then + expect(result).toEqual({status: 'not-found'}) + }) +}) diff --git a/src/services/session/logical-key.ts b/src/services/session/logical-key.ts new file mode 100644 index 00000000..5027a0bf --- /dev/null +++ b/src/services/session/logical-key.ts @@ -0,0 +1,129 @@ +import type {TriggerContext} from '../../features/triggers/types.js' +import type {Logger} from '../../shared/logger.js' +import type {SessionClient} from './backend.js' +import type {SessionInfo} from './types.js' +import {createHash} from 'node:crypto' +import {listSessionsForProject} from './storage.js' + +export interface LogicalSessionKey { + readonly key: string + readonly entityType: 'discussion' | 'dispatch' | 'issue' | 'pr' | 'schedule' + readonly entityId: string +} + +export type SessionResolution = + | {readonly status: 'found'; readonly session: SessionInfo} + | {readonly status: 'not-found'} + | {readonly status: 'error'; readonly error: string} + +function buildEntityKey(entityType: LogicalSessionKey['entityType'], entityId: string): LogicalSessionKey { + return { + key: `${entityType}-${entityId}`, + entityType, + entityId, + } +} + +function buildScheduleHash(value: string): string { + return createHash('sha256').update(value).digest('hex').slice(0, 8) +} + +export function buildLogicalKey(context: TriggerContext): LogicalSessionKey | null { + if (context.eventType === 'unsupported') { + return null + } + + if (context.eventType === 'schedule') { + const scheduleExpression = context.raw.event.type === 'schedule' ? context.raw.event.schedule : undefined + const hashSeed = + scheduleExpression != null && scheduleExpression.trim().length > 0 ? scheduleExpression : context.action + const hash = buildScheduleHash(hashSeed ?? 'default') + return buildEntityKey('schedule', hash) + } + + if (context.eventType === 'workflow_dispatch') { + const runId = String(context.runId) + return buildEntityKey('dispatch', runId) + } + + if (context.target == null) { + return null + } + + if (context.eventType === 'issue_comment') { + if (context.target.kind === 'issue') { + return buildEntityKey('issue', String(context.target.number)) + } + + if (context.target.kind === 'pr') { + return buildEntityKey('pr', String(context.target.number)) + } + + return null + } + + if (context.eventType === 'discussion_comment') { + if (context.target.kind !== 'discussion') { + return null + } + + return buildEntityKey('discussion', String(context.target.number)) + } + + if (context.eventType === 'issues') { + if (context.target.kind !== 'issue') { + return null + } + + return buildEntityKey('issue', String(context.target.number)) + } + + if (context.eventType === 'pull_request' || context.eventType === 'pull_request_review_comment') { + if (context.target.kind !== 'pr') { + return null + } + + return buildEntityKey('pr', String(context.target.number)) + } + + return null +} + +export function buildSessionTitle(key: LogicalSessionKey): string { + return `fro-bot: ${key.key}` +} + +export function findSessionByTitle(sessions: readonly SessionInfo[], title: string): SessionInfo | null { + const matchingSessions = sessions.filter(session => session.title === title) + if (matchingSessions.length === 0) { + return null + } + + return matchingSessions.reduce((latest, current) => (current.time.updated > latest.time.updated ? current : latest)) +} + +export async function resolveSessionForLogicalKey( + client: SessionClient, + workspacePath: string, + key: LogicalSessionKey, + logger: Logger, +): Promise { + try { + const sessions = await listSessionsForProject(client, workspacePath, logger) + const title = buildSessionTitle(key) + const matchedSession = findSessionByTitle(sessions, title) + + if (matchedSession == null) { + return {status: 'not-found'} + } + + if (matchedSession.time.archived != null || matchedSession.time.compacting != null) { + return {status: 'not-found'} + } + + return {status: 'found', session: matchedSession} + } catch (error: unknown) { + const message = error instanceof Error ? error.message : String(error) + return {status: 'error', error: message} + } +} diff --git a/src/services/session/writeback.ts b/src/services/session/writeback.ts index ce9d51fb..4d17a9d7 100644 --- a/src/services/session/writeback.ts +++ b/src/services/session/writeback.ts @@ -22,6 +22,10 @@ function formatSummaryForSession(summary: RunSummary): string { lines.push(`Sessions used: ${summary.sessionIds.join(', ')}`) } + if (summary.logicalKey != null) { + lines.push(`Logical Thread: ${summary.logicalKey}`) + } + if (summary.createdPRs.length > 0) { lines.push(`PRs created: ${summary.createdPRs.join(', ')}`) } diff --git a/src/shared/types.ts b/src/shared/types.ts index dbe360b4..67a82363 100644 --- a/src/shared/types.ts +++ b/src/shared/types.ts @@ -103,6 +103,7 @@ export interface RunSummary { readonly runId: number readonly cacheStatus: 'corrupted' | 'hit' | 'miss' readonly sessionIds: readonly string[] + readonly logicalKey?: string readonly createdPRs: readonly string[] readonly createdCommits: readonly string[] readonly duration: number