From da19fa6b0dcc2947c446d959862b4ef81a540f1c Mon Sep 17 00:00:00 2001 From: "Marcus R. Brown" Date: Sun, 29 Mar 2026 19:45:28 -0700 Subject: [PATCH] feat(setup): enhance Systematic integration with cache scoping, config input, and CI config extraction - Add systematicVersion to tools cache key so cache busts on version change - Extract CI config assembly from setup.ts into ci-config.ts (+ adapters.ts) to bring setup.ts under the 200 LOC modular code limit - Add systematic-config action input following the omo-config deep-merge pattern, writing to ~/.config/opencode/systematic.json - Update versioned-tool skill table with Systematic entry - 17 new tests across ci-config, systematic-config, and tools-cache modules --- .agents/skills/versioned-tool/SKILL.md | 1 + README.md | 1 + action.yaml | 3 + dist/main.js | 32 ++-- src/features/agent/opencode.test.ts | 2 + src/features/agent/server.ts | 18 ++- src/harness/config/inputs.test.ts | 36 +++++ src/harness/config/inputs.ts | 4 + src/harness/phases/bootstrap.ts | 1 + src/services/setup/adapters.ts | 20 +++ src/services/setup/ci-config.test.ts | 108 +++++++++++++ src/services/setup/ci-config.ts | 43 ++++++ src/services/setup/index.ts | 4 + src/services/setup/setup.test.ts | 151 +++++++++---------- src/services/setup/setup.ts | 88 +++-------- src/services/setup/systematic-config.test.ts | 106 +++++++++++++ src/services/setup/systematic-config.ts | 36 +++++ src/services/setup/tools-cache.test.ts | 36 +++-- src/services/setup/tools-cache.ts | 19 ++- src/services/setup/types.ts | 1 + src/shared/types.ts | 1 + 21 files changed, 519 insertions(+), 192 deletions(-) create mode 100644 src/services/setup/adapters.ts create mode 100644 src/services/setup/ci-config.test.ts create mode 100644 src/services/setup/ci-config.ts create mode 100644 src/services/setup/systematic-config.test.ts create mode 100644 src/services/setup/systematic-config.ts diff --git a/.agents/skills/versioned-tool/SKILL.md b/.agents/skills/versioned-tool/SKILL.md index f1e837fe..a94717eb 100644 --- a/.agents/skills/versioned-tool/SKILL.md +++ b/.agents/skills/versioned-tool/SKILL.md @@ -16,6 +16,7 @@ This project manages external CLI tools through a **single-source-of-truth versi | OpenCode | `DEFAULT_OPENCODE_VERSION` | `opencode-version` | `github-releases` (`anomalyco/opencode`) | | oMo | `DEFAULT_OMO_VERSION` | `omo-version` | `npm` (`oh-my-openagent`) | | Bun | `DEFAULT_BUN_VERSION` | _(internal only)_ | `github-releases` (`oven-sh/bun`, extract `bun-v` prefix) | +| Systematic | `DEFAULT_SYSTEMATIC_VERSION` | `systematic-version` | `npm` (`@fro.bot/systematic`) | ## Quick Start diff --git a/README.md b/README.md index 2ab80369..b107c24f 100644 --- a/README.md +++ b/README.md @@ -378,6 +378,7 @@ concurrency: | `aws-region` | No | — | AWS region for S3 bucket | | `skip-cache` | No | `false` | Skip cache restore (useful for debugging) | | `omo-config` | No | — | Custom oMo configuration JSON (deep-merged) | +| `systematic-config` | No | — | Custom Systematic configuration JSON (deep-merged) | | `opencode-config` | No | — | Custom OpenCode configuration JSON (deep-merged) | ### Action Outputs diff --git a/action.yaml b/action.yaml index 1edf35cb..bfc5337f 100644 --- a/action.yaml +++ b/action.yaml @@ -67,6 +67,9 @@ inputs: Custom oMo configuration JSON. Written before installer runs. Deep-merged with existing config. required: false + systematic-config: + description: Custom Systematic plugin configuration JSON (deep-merged with existing config). + required: false dedup-window: description: >- Deduplication window in milliseconds. Skip execution if the agent already diff --git a/dist/main.js b/dist/main.js index 1b02d5a1..f35fa5d4 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,M as d,N as f,O as p,P as m,Q as h,R as g,S as _,T as v,U as y,V as b,W as x,X as S,Y as C,Z as w,_ as T,a as E,b as D,c as O,d as k,f as A,g as j,h as M,i as N,j as P,k as F,l as ee,m as te,n as ne,o as re,p as I,q as ie,r as ae,s as oe,t as se,u as ce,v as le,w as ue,x as de,y as fe,z as pe}from"./artifact-DnDoCHSt.js";import L from"node:process";import*as me from"os";import*as he from"crypto";import*as R from"fs";import*as z from"path";import{ok as ge}from"assert";import*as _e from"util";import{Buffer as ve}from"node:buffer";import*as ye from"node:crypto";import{createHash as be}from"node:crypto";import{pathToFileURL as xe}from"node:url";import*as B from"node:fs/promises";import Se from"node:fs/promises";import*as V from"node:path";import H,{join as Ce}from"node:path";import{spawn as we}from"node:child_process";import*as Te from"node:os";import Ee,{homedir as De}from"node:os";import*as Oe from"stream";function ke(e){switch(e){case`hit`:return`✅ hit`;case`miss`:return`🆕 miss`;case`corrupted`:return`⚠️ corrupted (clean start)`}}function Ae(e){let t=Math.round(e/1e3);return t<60?`${t}s`:`${Math.floor(t/60)}m ${t%60}s`}async function je(e,t){let{eventType:n,repo:r,ref:i,runId:a,runUrl:o,metrics:s,agent:c}=e;try{if(S.addHeading(`Fro Bot Agent Run`,2).addTable([[{data:`Field`,header:!0},{data:`Value`,header:!0}],[`Event`,n],[`Repository`,r],[`Ref`,i],[`Run ID`,`[${a}](${o})`],[`Agent`,c],[`Cache Status`,ke(s.cacheStatus)],[`Duration`,s.duration==null?`N/A`:Ae(s.duration)]]),(s.sessionsUsed.length>0||s.sessionsCreated.length>0)&&(S.addHeading(`Sessions`,3),s.sessionsUsed.length>0&&S.addRaw(`**Used:** ${s.sessionsUsed.join(`, `)}\n`),s.sessionsCreated.length>0&&S.addRaw(`**Created:** ${s.sessionsCreated.join(`, `)}\n`)),s.tokenUsage!=null&&(S.addHeading(`Token Usage`,3),S.addTable([[{data:`Metric`,header:!0},{data:`Count`,header:!0}],[`Input`,s.tokenUsage.input.toLocaleString()],[`Output`,s.tokenUsage.output.toLocaleString()],[`Reasoning`,s.tokenUsage.reasoning.toLocaleString()],[`Cache Read`,s.tokenUsage.cache.read.toLocaleString()],[`Cache Write`,s.tokenUsage.cache.write.toLocaleString()]]),s.model!=null&&S.addRaw(`**Model:** ${s.model}\n`),s.cost!=null&&S.addRaw(`**Cost:** $${s.cost.toFixed(4)}\n`)),(s.prsCreated.length>0||s.commitsCreated.length>0||s.commentsPosted>0)&&(S.addHeading(`Created Artifacts`,3),s.prsCreated.length>0&&S.addList([...s.prsCreated]),s.commitsCreated.length>0&&S.addList(s.commitsCreated.map(e=>`Commit \`${e.slice(0,7)}\``)),s.commentsPosted>0&&S.addRaw(`**Comments Posted:** ${s.commentsPosted}\n`)),s.errors.length>0){S.addHeading(`Errors`,3);for(let e of s.errors){let t=e.recoverable?`🔄 Recovered`:`❌ Failed`;S.addRaw(`- **${e.type}** (${t}): ${e.message}\n`)}}await S.write(),t.debug(`Wrote job summary`)}catch(e){let n=d(e);t.warning(`Failed to write job summary`,{error:n}),y(`Failed to write job summary: ${n}`)}}function Me(){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 Ne(e){s(`session-id`,e.sessionId??``),s(`cache-status`,e.cacheStatus),s(`duration`,e.duration)}function U(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 Pe(e,t,n,r,i){try{let{owner:a,repo:o}=U(t),{data:s}=await e.rest.reactions.createForIssueComment({owner:a,repo:o,comment_id:n,content:r});return i.debug(`Created comment reaction`,{commentId:n,content:r,reactionId:s.id}),{id:s.id}}catch(e){return i.warning(`Failed to create comment reaction`,{commentId:n,content:r,error:d(e)}),null}}async function Fe(e,t,n,r){try{let{owner:r,repo:i}=U(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 r.warning(`Failed to list comment reactions`,{commentId:n,error:d(e)}),[]}}async function Ie(e,t,n,r,i){try{let{owner:a,repo:o}=U(t);return await e.rest.reactions.deleteForIssueComment({owner:a,repo:o,comment_id:n,reaction_id:r}),i.debug(`Deleted comment reaction`,{commentId:n,reactionId:r}),!0}catch(e){return i.warning(`Failed to delete comment reaction`,{commentId:n,reactionId:r,error:d(e)}),!1}}async function Le(e,t,n,r,i,a){let{owner:o,repo:s}=U(t);try{return await e.rest.issues.createLabel({owner:o,repo:s,name:n,color:r,description:i}),a.debug(`Created label`,{name:n,color:r}),!0}catch(e){return e instanceof Error&&`status`in e&&e.status===422?(a.debug(`Label already exists`,{name:n}),!0):(a.warning(`Failed to create label`,{name:n,error:d(e)}),!1)}}async function Re(e,t,n,r,i){try{let{owner:a,repo:o}=U(t);return await e.rest.issues.addLabels({owner:a,repo:o,issue_number:n,labels:[...r]}),i.debug(`Added labels to issue`,{issueNumber:n,labels:r}),!0}catch(e){return i.warning(`Failed to add labels to issue`,{issueNumber:n,labels:r,error:d(e)}),!1}}async function ze(e,t,n,r,i){try{let{owner:a,repo:o}=U(t);return await e.rest.issues.removeLabel({owner:a,repo:o,issue_number:n,name:r}),i.debug(`Removed label from issue`,{issueNumber:n,label:r}),!0}catch(e){return e instanceof Error&&`status`in e&&e.status===404?(i.debug(`Label was not present on issue`,{issueNumber:n,label:r}),!0):(i.warning(`Failed to remove label from issue`,{issueNumber:n,label:r,error:d(e)}),!1)}}async function Be(e,t,n){try{let{owner:n,repo:r}=U(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:d(e)}),`main`}}const Ve={admin:`OWNER`,maintain:`MEMBER`,write:`COLLABORATOR`,triage:`COLLABORATOR`};async function He(e,t,n,r,i){try{let{data:a}=await e.rest.repos.getCollaboratorPermissionLevel({owner:t,repo:n,username:r}),o=Ve[a.permission]??null;return i.debug(`Resolved sender permission`,{username:r,permission:a.permission,association:o}),o}catch(e){return i.warning(`Failed to resolve sender permission`,{username:r,error:d(e)}),null}}async function Ue(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:d(e)}),null}}const We={maxComments:50,maxCommits:100,maxFiles:100,maxReviews:100,maxBodyBytes:10*1024,maxTotalBytes:100*1024},Ge=`…[truncated]`;function Ke(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(Ge).length;if(i<=0)return{text:Ge,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+Ge,truncated:!0}}function qe(e){return e.length===0?``:`**Labels:** ${e.map(e=>`\`${e.name}\``).join(`, `)}\n`}function Je(e){return e.length===0?``:`**Assignees:** ${e.map(e=>`@${e.login}`).join(`, `)}\n`}function Ye(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=Je(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 Xe(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=Je(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,M as d,N as f,O as p,P as m,Q as h,R as g,S as _,T as v,U as y,V as b,W as x,X as S,Y as C,Z as w,_ as ee,a as T,b as E,c as D,d as te,f as O,g as ne,h as k,i as A,j,k as re,l as ie,m as ae,n as oe,o as se,p as M,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 _e}from"./artifact-DnDoCHSt.js";import N from"node:process";import*as ve from"os";import*as ye from"crypto";import*as P from"fs";import*as F from"path";import{ok as be}from"assert";import*as xe from"util";import{Buffer as Se}from"node:buffer";import*as Ce from"node:crypto";import{createHash as we}from"node:crypto";import{pathToFileURL as Te}from"node:url";import*as I from"node:fs/promises";import Ee from"node:fs/promises";import*as L from"node:path";import R,{join as z}from"node:path";import{spawn as De}from"node:child_process";import*as Oe from"node:os";import ke,{homedir as Ae}from"node:os";import*as je from"stream";function Me(e){switch(e){case`hit`:return`✅ hit`;case`miss`:return`🆕 miss`;case`corrupted`:return`⚠️ corrupted (clean start)`}}function Ne(e){let t=Math.round(e/1e3);return t<60?`${t}s`:`${Math.floor(t/60)}m ${t%60}s`}async function Pe(e,t){let{eventType:n,repo:r,ref:i,runId:a,runUrl:o,metrics:s,agent:c}=e;try{if(S.addHeading(`Fro Bot Agent Run`,2).addTable([[{data:`Field`,header:!0},{data:`Value`,header:!0}],[`Event`,n],[`Repository`,r],[`Ref`,i],[`Run ID`,`[${a}](${o})`],[`Agent`,c],[`Cache Status`,Me(s.cacheStatus)],[`Duration`,s.duration==null?`N/A`:Ne(s.duration)]]),(s.sessionsUsed.length>0||s.sessionsCreated.length>0)&&(S.addHeading(`Sessions`,3),s.sessionsUsed.length>0&&S.addRaw(`**Used:** ${s.sessionsUsed.join(`, `)}\n`),s.sessionsCreated.length>0&&S.addRaw(`**Created:** ${s.sessionsCreated.join(`, `)}\n`)),s.tokenUsage!=null&&(S.addHeading(`Token Usage`,3),S.addTable([[{data:`Metric`,header:!0},{data:`Count`,header:!0}],[`Input`,s.tokenUsage.input.toLocaleString()],[`Output`,s.tokenUsage.output.toLocaleString()],[`Reasoning`,s.tokenUsage.reasoning.toLocaleString()],[`Cache Read`,s.tokenUsage.cache.read.toLocaleString()],[`Cache Write`,s.tokenUsage.cache.write.toLocaleString()]]),s.model!=null&&S.addRaw(`**Model:** ${s.model}\n`),s.cost!=null&&S.addRaw(`**Cost:** $${s.cost.toFixed(4)}\n`)),(s.prsCreated.length>0||s.commitsCreated.length>0||s.commentsPosted>0)&&(S.addHeading(`Created Artifacts`,3),s.prsCreated.length>0&&S.addList([...s.prsCreated]),s.commitsCreated.length>0&&S.addList(s.commitsCreated.map(e=>`Commit \`${e.slice(0,7)}\``)),s.commentsPosted>0&&S.addRaw(`**Comments Posted:** ${s.commentsPosted}\n`)),s.errors.length>0){S.addHeading(`Errors`,3);for(let e of s.errors){let t=e.recoverable?`🔄 Recovered`:`❌ Failed`;S.addRaw(`- **${e.type}** (${t}): ${e.message}\n`)}}await S.write(),t.debug(`Wrote job summary`)}catch(e){let n=d(e);t.warning(`Failed to write job summary`,{error:n}),y(`Failed to write job summary: ${n}`)}}function Fe(){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 B(e){s(`session-id`,e.sessionId??``),s(`cache-status`,e.cacheStatus),s(`duration`,e.duration)}function V(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 Ie(e,t,n,r,i){try{let{owner:a,repo:o}=V(t),{data:s}=await e.rest.reactions.createForIssueComment({owner:a,repo:o,comment_id:n,content:r});return i.debug(`Created comment reaction`,{commentId:n,content:r,reactionId:s.id}),{id:s.id}}catch(e){return i.warning(`Failed to create comment reaction`,{commentId:n,content:r,error:d(e)}),null}}async function Le(e,t,n,r){try{let{owner:r,repo:i}=V(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 r.warning(`Failed to list comment reactions`,{commentId:n,error:d(e)}),[]}}async function Re(e,t,n,r,i){try{let{owner:a,repo:o}=V(t);return await e.rest.reactions.deleteForIssueComment({owner:a,repo:o,comment_id:n,reaction_id:r}),i.debug(`Deleted comment reaction`,{commentId:n,reactionId:r}),!0}catch(e){return i.warning(`Failed to delete comment reaction`,{commentId:n,reactionId:r,error:d(e)}),!1}}async function ze(e,t,n,r,i,a){let{owner:o,repo:s}=V(t);try{return await e.rest.issues.createLabel({owner:o,repo:s,name:n,color:r,description:i}),a.debug(`Created label`,{name:n,color:r}),!0}catch(e){return e instanceof Error&&`status`in e&&e.status===422?(a.debug(`Label already exists`,{name:n}),!0):(a.warning(`Failed to create label`,{name:n,error:d(e)}),!1)}}async function Be(e,t,n,r,i){try{let{owner:a,repo:o}=V(t);return await e.rest.issues.addLabels({owner:a,repo:o,issue_number:n,labels:[...r]}),i.debug(`Added labels to issue`,{issueNumber:n,labels:r}),!0}catch(e){return i.warning(`Failed to add labels to issue`,{issueNumber:n,labels:r,error:d(e)}),!1}}async function Ve(e,t,n,r,i){try{let{owner:a,repo:o}=V(t);return await e.rest.issues.removeLabel({owner:a,repo:o,issue_number:n,name:r}),i.debug(`Removed label from issue`,{issueNumber:n,label:r}),!0}catch(e){return e instanceof Error&&`status`in e&&e.status===404?(i.debug(`Label was not present on issue`,{issueNumber:n,label:r}),!0):(i.warning(`Failed to remove label from issue`,{issueNumber:n,label:r,error:d(e)}),!1)}}async function He(e,t,n){try{let{owner:n,repo:r}=V(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:d(e)}),`main`}}const Ue={admin:`OWNER`,maintain:`MEMBER`,write:`COLLABORATOR`,triage:`COLLABORATOR`};async function We(e,t,n,r,i){try{let{data:a}=await e.rest.repos.getCollaboratorPermissionLevel({owner:t,repo:n,username:r}),o=Ue[a.permission]??null;return i.debug(`Resolved sender permission`,{username:r,permission:a.permission,association:o}),o}catch(e){return i.warning(`Failed to resolve sender permission`,{username:r,error:d(e)}),null}}async function Ge(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:d(e)}),null}}const Ke={maxComments:50,maxCommits:100,maxFiles:100,maxReviews:100,maxBodyBytes:10*1024,maxTotalBytes:100*1024},qe=`…[truncated]`;function H(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 Je(e){return e.length===0?``:`**Labels:** ${e.map(e=>`\`${e.name}\``).join(`, `)}\n`}function Ye(e){return e.length===0?``:`**Assignees:** ${e.map(e=>`@${e.login}`).join(`, `)}\n`}function Xe(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=Je(e.labels);n.length>0&&t.push(n.trimEnd());let r=Ye(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 Ze(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=Je(e.labels);n.length>0&&t.push(n.trimEnd());let r=Ye(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 Ze(e){return e.type===`issue`?Ye(e):Xe(e)}async function Qe(e,t,n,r,i,a){try{let[a,o]=await Promise.all([e.rest.issues.get({owner:t,repo:n,issue_number:r}),e.rest.issues.listComments({owner:t,repo:n,issue_number:r,per_page:i.maxComments})]),s=a.data,c=Ke(s.body??``,i.maxBodyBytes),l=o.data.slice(0,i.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>=i.maxComments,totalComments:o.data.length}}catch(e){return a.warning(`REST issue fallback failed`,{owner:t,repo:n,number:r,error:d(e)}),null}}async function $e(e,t,n,r,i,a){try{let[o,s,c,l,u]=await Promise.all([e.rest.pulls.get({owner:t,repo:n,pull_number:r}),e.rest.pulls.listCommits({owner:t,repo:n,pull_number:r,per_page:i.maxCommits}),e.rest.pulls.listFiles({owner:t,repo:n,pull_number:r,per_page:i.maxFiles}),e.rest.pulls.listReviews({owner:t,repo:n,pull_number:r,per_page:i.maxReviews}),e.rest.issues.listComments({owner:t,repo:n,issue_number:r,per_page:i.maxComments})]),f=await e.rest.pulls.listRequestedReviewers({owner:t,repo:n,pull_number:r}).catch(e=>(a.warning(`Failed to fetch requested reviewers, defaulting to empty`,{owner:t,repo:n,number:r,error:d(e)}),{data:{users:[],teams:[]}})),p=o.data,m=Ke(p.body??``,i.maxBodyBytes),h=p.base.repo?.owner.login,g=p.head.repo?.owner.login,_=g==null||h!==g,v=u.data.slice(0,i.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=s.data.slice(0,i.maxCommits).map(e=>({oid:e.sha,message:e.commit.message,author:e.commit.author?.name??null})),b=c.data.slice(0,i.maxFiles).map(e=>({path:e.filename,additions:e.additions,deletions:e.deletions,status:e.status})),x=l.data.slice(0,i.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:u.data.length>=i.maxComments,totalComments:u.data.length,commits:y,commitsTruncated:s.data.length>=i.maxCommits,totalCommits:s.data.length,files:b,filesTruncated:c.data.length>=i.maxFiles,totalFiles:c.data.length,reviews:x,reviewsTruncated:l.data.length>=i.maxReviews,totalReviews:l.data.length,authorAssociation:p.author_association,requestedReviewers:w,requestedReviewerTeams:T}}catch(e){return a.warning(`REST pull request fallback failed`,{owner:t,repo:n,number:r,error:d(e)}),null}}async function et(e,t,n,r,i,a){try{return await e.graphql(` +`)}function Qe(e){return e.type===`issue`?Xe(e):Ze(e)}async function $e(e,t,n,r,i,a){try{let[a,o]=await Promise.all([e.rest.issues.get({owner:t,repo:n,issue_number:r}),e.rest.issues.listComments({owner:t,repo:n,issue_number:r,per_page:i.maxComments})]),s=a.data,c=H(s.body??``,i.maxBodyBytes),l=o.data.slice(0,i.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>=i.maxComments,totalComments:o.data.length}}catch(e){return a.warning(`REST issue fallback failed`,{owner:t,repo:n,number:r,error:d(e)}),null}}async function et(e,t,n,r,i,a){try{let[o,s,c,l,u]=await Promise.all([e.rest.pulls.get({owner:t,repo:n,pull_number:r}),e.rest.pulls.listCommits({owner:t,repo:n,pull_number:r,per_page:i.maxCommits}),e.rest.pulls.listFiles({owner:t,repo:n,pull_number:r,per_page:i.maxFiles}),e.rest.pulls.listReviews({owner:t,repo:n,pull_number:r,per_page:i.maxReviews}),e.rest.issues.listComments({owner:t,repo:n,issue_number:r,per_page:i.maxComments})]),f=await e.rest.pulls.listRequestedReviewers({owner:t,repo:n,pull_number:r}).catch(e=>(a.warning(`Failed to fetch requested reviewers, defaulting to empty`,{owner:t,repo:n,number:r,error:d(e)}),{data:{users:[],teams:[]}})),p=o.data,m=H(p.body??``,i.maxBodyBytes),h=p.base.repo?.owner.login,g=p.head.repo?.owner.login,_=g==null||h!==g,v=u.data.slice(0,i.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=s.data.slice(0,i.maxCommits).map(e=>({oid:e.sha,message:e.commit.message,author:e.commit.author?.name??null})),b=c.data.slice(0,i.maxFiles).map(e=>({path:e.filename,additions:e.additions,deletions:e.deletions,status:e.status})),x=l.data.slice(0,i.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),ee=(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:u.data.length>=i.maxComments,totalComments:u.data.length,commits:y,commitsTruncated:s.data.length>=i.maxCommits,totalCommits:s.data.length,files:b,filesTruncated:c.data.length>=i.maxFiles,totalFiles:c.data.length,reviews:x,reviewsTruncated:l.data.length>=i.maxReviews,totalReviews:l.data.length,authorAssociation:p.author_association,requestedReviewers:w,requestedReviewerTeams:ee}}catch(e){return a.warning(`REST pull request fallback failed`,{owner:t,repo:n,number:r,error:d(e)}),null}}async function tt(e,t,n,r,i,a){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:r,maxComments:i})}catch(e){return a.warning(`GraphQL issue query failed`,{owner:t,repo:n,number:r,error:d(e)}),null}}async function tt(e,t,n,r,i,a,o,s,c){try{return await e.graphql(` +`,{owner:t,repo:n,number:r,maxComments:i})}catch(e){return a.warning(`GraphQL issue query failed`,{owner:t,repo:n,number:r,error:d(e)}),null}}async function nt(e,t,n,r,i,a,o,s,c){try{return await e.graphql(` query GetPullRequest( $owner: String!, $repo: String!, @@ -119,13 +119,13 @@ 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:r,maxComments:i,maxCommits:a,maxFiles:o,maxReviews:s})}catch(e){return c.warning(`GraphQL pull request query failed`,{owner:t,repo:n,number:r,error:d(e)}),null}}async function nt(e,t,n,r,i,a){let o=await et(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=Ke(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 rt(e,t,n,r,i,a){let o=await tt(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=Ke(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 W={PER_PAGE:100,MAX_PAGES:50};async function it(e,t,n,r,i){i.debug(`Fetching PR diff`,{prNumber:r});let a=[],o=1,s=!1;for(;o<=W.MAX_PAGES;){let{data:c}=await e.rest.pulls.listFiles({owner:t,repo:n,pull_number:r,per_page:W.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.lengthW.MAX_PAGES&&(s=!0,i.warning(`PR diff pagination limit reached`,{filesLoaded:a.length,maxPages:W.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 at(e,t,n,r){if(e.eventType!==`pull_request`)return null;let i=e.target?.number;if(i==null)return r.debug(`No PR number in trigger context, skipping diff collection`),null;let[a,o]=n.split(`/`);if(a==null||o==null)return r.warning(`Invalid repo format, skipping diff collection`,{repo:n}),null;try{let e=await it(t,a,o,i,r),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 r.debug(`Collected diff context`,{files:n.changedFiles,additions:n.additions,deletions:n.deletions,truncated:n.truncated}),n}catch(e){return r.warning(`Failed to fetch PR diff`,{error:d(e)}),null}}async function ot(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 at(r,n,p,t),y=await st(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 Be(n,p,t),diffContext:v,hydratedContext:y,authorAssociation:x,isRequestedReviewer:S}}async function st(e,t,n,r,i,a){if(r==null||i==null)return null;let o=We;return i===`issue`?await nt(e,t,n,r,o,a)??Qe(e,t,n,r,o,a):await rt(e,t,n,r,o,a)??$e(e,t,n,r,o,a)}const ct=({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:r,maxComments:i,maxCommits:a,maxFiles:o,maxReviews:s})}catch(e){return c.warning(`GraphQL pull request query failed`,{owner:t,repo:n,number:r,error:d(e)}),null}}async function rt(e,t,n,r,i,a){let o=await tt(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=H(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 it(e,t,n,r,i,a){let o=await nt(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=H(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})),ee=s.assignees.nodes.map(e=>({login:e.login})),T=s.reviewRequests.nodes.map(e=>`login`in e.requestedReviewer?e.requestedReviewer.login:null).filter(e=>e!=null),E=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:ee,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:T,requestedReviewerTeams:E}}const U={PER_PAGE:100,MAX_PAGES:50};async function at(e,t,n,r,i){i.debug(`Fetching PR diff`,{prNumber:r});let a=[],o=1,s=!1;for(;o<=U.MAX_PAGES;){let{data:c}=await e.rest.pulls.listFiles({owner:t,repo:n,pull_number:r,per_page:U.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.lengthU.MAX_PAGES&&(s=!0,i.warning(`PR diff pagination limit reached`,{filesLoaded:a.length,maxPages:U.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 ot(e,t,n,r){if(e.eventType!==`pull_request`)return null;let i=e.target?.number;if(i==null)return r.debug(`No PR number in trigger context, skipping diff collection`),null;let[a,o]=n.split(`/`);if(a==null||o==null)return r.warning(`Invalid repo format, skipping diff collection`,{repo:n}),null;try{let e=await at(t,a,o,i,r),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 r.debug(`Collected diff context`,{files:n.changedFiles,additions:n.additions,deletions:n.deletions,truncated:n.truncated}),n}catch(e){return r.warning(`Failed to fetch PR diff`,{error:d(e)}),null}}async function st(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 ot(r,n,p,t),y=await ct(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 He(n,p,t),diffContext:v,hydratedContext:y,authorAssociation:x,isRequestedReviewer:S}}async function ct(e,t,n,r,i,a){if(r==null||i==null)return null;let o=Ke;return i===`issue`?await rt(e,t,n,r,o,a)??$e(e,t,n,r,o,a):await it(e,t,n,r,o,a)??et(e,t,n,r,o,a)}const lt=({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))}}}()}},lt=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},ut={bodySerializer:e=>JSON.stringify(e,(e,t)=>typeof t==`bigint`?t.toString():t)},dt=e=>{switch(e){case`label`:return`.`;case`matrix`:return`;`;case`simple`:return`,`;default:return`&`}},ft=e=>{switch(e){case`form`:return`,`;case`pipeDelimited`:return`|`;case`spaceDelimited`:return`%20`;default:return`,`}},pt=e=>{switch(e){case`label`:return`.`;case`matrix`:return`;`;case`simple`:return`,`;default:return`&`}},mt=({allowReserved:e,explode:t,name:n,style:r,value:i})=>{if(!t){let t=(e?i:i.map(e=>encodeURIComponent(e))).join(ft(r));switch(r){case`label`:return`.${t}`;case`matrix`:return`;${n}=${t}`;case`simple`:return t;default:return`${n}=${t}`}}let a=dt(r),o=i.map(t=>r===`label`||r===`simple`?e?t:encodeURIComponent(t):ht({allowReserved:e,name:n,value:t})).join(a);return r===`label`||r===`matrix`?a+o:o},ht=({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)}`},gt=({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=pt(r),s=Object.entries(i).map(([t,i])=>ht({allowReserved:e,name:r===`deepObject`?`${n}[${t}]`:t,value:i})).join(o);return r===`label`||r===`matrix`?o+s:s},_t=/\{[^{}]+\}/g,vt=({path:e,url:t})=>{let n=t,r=t.match(_t);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,mt({explode:r,name:i,style:a,value:o}));continue}if(typeof o==`object`){n=n.replace(t,gt({explode:r,name:i,style:a,value:o,valueOnly:!0}));continue}if(a===`matrix`){n=n.replace(t,`;${ht({name:i,value:o})}`);continue}let s=encodeURIComponent(a===`label`?`.${o}`:o);n=n.replace(t,s)}return n},yt=({baseUrl:e,path:t,query:n,querySerializer:r,url:i})=>{let a=i.startsWith(`/`)?i:`/${i}`,o=(e??``)+a;t&&(o=vt({path:t,url:o}));let s=n?r(n):``;return s.startsWith(`?`)&&(s=s.substring(1)),s&&(o+=`?${s}`),o},bt=({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=mt({allowReserved:e,explode:!0,name:a,style:`form`,value:o,...t});n&&i.push(n)}else if(typeof o==`object`){let t=gt({allowReserved:e,explode:!0,name:a,style:`deepObject`,value:o,...n});t&&i.push(t)}else{let t=ht({allowReserved:e,name:a,value:o});t&&i.push(t)}}return i.join(`&`)},xt=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`}},St=(e,t)=>t?!!(e.headers.has(t)||e.query?.[t]||e.headers.get(`Cookie`)?.includes(`${t}=`)):!1,Ct=async({security:e,...t})=>{for(let n of e){if(St(t,n.name))continue;let e=await lt(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}}},wt=e=>yt({baseUrl:e.baseUrl,path:e.path,query:e.query,querySerializer:typeof e.querySerializer==`function`?e.querySerializer:bt(e.querySerializer),url:e.url}),Tt=(e,t)=>{let n={...e,...t};return n.baseUrl?.endsWith(`/`)&&(n.baseUrl=n.baseUrl.substring(0,n.baseUrl.length-1)),n.headers=Et(e.headers,t.headers),n},Et=(...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 Dt=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 Ot=()=>({error:new Dt,request:new Dt,response:new Dt}),kt=bt({allowReserved:!1,array:{explode:!0,style:`form`},object:{explode:!0,style:`deepObject`}}),At={"Content-Type":`application/json`},jt=(e={})=>({...ut,headers:At,parseAs:`auto`,querySerializer:kt,...e}),Mt=(e={})=>{let t=Tt(jt(),e),n=()=>({...t}),r=e=>(t=Tt(t,e),n()),i=Ot(),a=async e=>{let n={...t,...e,fetch:e.fetch??t.fetch??globalThis.fetch,headers:Et(t.headers,e.headers),serializedBody:void 0};return n.security&&await Ct({...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:wt(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`?xt(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 ct({...n,body:n.body,headers:n.headers,method:e,url:r})},t};return{buildUrl:wt,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 Nt=Mt(jt({baseUrl:`http://localhost:4096`}));var G=class{_client=Nt;constructor(e){e?.client&&(this._client=e.client)}},Pt=class extends G{event(e){return(e?.client??this._client).get.sse({url:`/global/event`,...e})}},Ft=class extends G{list(e){return(e?.client??this._client).get({url:`/project`,...e})}current(e){return(e?.client??this._client).get({url:`/project/current`,...e})}},It=class extends G{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})}},Lt=class extends G{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})}},Rt=class extends G{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})}},zt=class extends G{dispose(e){return(e?.client??this._client).post({url:`/instance/dispose`,...e})}},Bt=class extends G{get(e){return(e?.client??this._client).get({url:`/path`,...e})}},Vt=class extends G{get(e){return(e?.client??this._client).get({url:`/vcs`,...e})}},Ht=class extends G{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})}},Ut=class extends G{list(e){return(e?.client??this._client).get({url:`/command`,...e})}},Wt=class extends G{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}})}},Gt=class extends G{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 Wt({client:this._client})},Kt=class extends G{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})}},qt=class extends G{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})}},Jt=class extends G{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})}},Yt=class extends G{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}})}},Xt=class extends G{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 Yt({client:this._client})},Zt=class extends G{status(e){return(e?.client??this._client).get({url:`/lsp`,...e})}},Qt=class extends G{status(e){return(e?.client??this._client).get({url:`/formatter`,...e})}},$t=class extends G{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}})}},en=class extends G{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 $t({client:this._client})},tn=class extends G{subscribe(e){return(e?.client??this._client).get.sse({url:`/event`,...e})}},nn=class extends G{postSessionIdPermissionsPermissionId(e){return(e.client??this._client).post({url:`/session/{id}/permissions/{permissionID}`,...e,headers:{"Content-Type":`application/json`,...e.headers}})}global=new Pt({client:this._client});project=new Ft({client:this._client});pty=new It({client:this._client});config=new Lt({client:this._client});tool=new Rt({client:this._client});instance=new zt({client:this._client});path=new Bt({client:this._client});vcs=new Vt({client:this._client});session=new Ht({client:this._client});command=new Ut({client:this._client});provider=new Gt({client:this._client});find=new Kt({client:this._client});file=new qt({client:this._client});app=new Jt({client:this._client});mcp=new Xt({client:this._client});lsp=new Zt({client:this._client});formatter=new Qt({client:this._client});tui=new en({client:this._client});auth=new Yt({client:this._client});event=new tn({client:this._client})};function rn(e,t){if(e)return t&&(e===t||e===encodeURIComponent(t))?t:e}function an(e,t){if(e.method!==`GET`&&e.method!==`HEAD`)return e;let n=rn(e.headers.get(`x-opencode-directory`),t);if(!n)return e;let r=new URL(e.url);r.searchParams.has(`directory`)||r.searchParams.set(`directory`,n);let i=new Request(r,e);return i.headers.delete(`x-opencode-directory`),i}function on(e){if(!e?.fetch){let t=e=>(e.timeout=!1,fetch(e));e={...e,fetch:t}}e?.directory&&(e.headers={...e.headers,"x-opencode-directory":encodeURIComponent(e.directory)});let t=Mt(e);return t.interceptors.request.use(t=>an(t,e?.directory)),new nn({client:t})}async function sn(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=we(`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(` +`);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))}}}()}},ut=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},dt={bodySerializer:e=>JSON.stringify(e,(e,t)=>typeof t==`bigint`?t.toString():t)},ft=e=>{switch(e){case`label`:return`.`;case`matrix`:return`;`;case`simple`:return`,`;default:return`&`}},pt=e=>{switch(e){case`form`:return`,`;case`pipeDelimited`:return`|`;case`spaceDelimited`:return`%20`;default:return`,`}},mt=e=>{switch(e){case`label`:return`.`;case`matrix`:return`;`;case`simple`:return`,`;default:return`&`}},ht=({allowReserved:e,explode:t,name:n,style:r,value:i})=>{if(!t){let t=(e?i:i.map(e=>encodeURIComponent(e))).join(pt(r));switch(r){case`label`:return`.${t}`;case`matrix`:return`;${n}=${t}`;case`simple`:return t;default:return`${n}=${t}`}}let a=ft(r),o=i.map(t=>r===`label`||r===`simple`?e?t:encodeURIComponent(t):W({allowReserved:e,name:n,value:t})).join(a);return r===`label`||r===`matrix`?a+o:o},W=({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)}`},gt=({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=mt(r),s=Object.entries(i).map(([t,i])=>W({allowReserved:e,name:r===`deepObject`?`${n}[${t}]`:t,value:i})).join(o);return r===`label`||r===`matrix`?o+s:s},_t=/\{[^{}]+\}/g,vt=({path:e,url:t})=>{let n=t,r=t.match(_t);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,ht({explode:r,name:i,style:a,value:o}));continue}if(typeof o==`object`){n=n.replace(t,gt({explode:r,name:i,style:a,value:o,valueOnly:!0}));continue}if(a===`matrix`){n=n.replace(t,`;${W({name:i,value:o})}`);continue}let s=encodeURIComponent(a===`label`?`.${o}`:o);n=n.replace(t,s)}return n},yt=({baseUrl:e,path:t,query:n,querySerializer:r,url:i})=>{let a=i.startsWith(`/`)?i:`/${i}`,o=(e??``)+a;t&&(o=vt({path:t,url:o}));let s=n?r(n):``;return s.startsWith(`?`)&&(s=s.substring(1)),s&&(o+=`?${s}`),o},bt=({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=ht({allowReserved:e,explode:!0,name:a,style:`form`,value:o,...t});n&&i.push(n)}else if(typeof o==`object`){let t=gt({allowReserved:e,explode:!0,name:a,style:`deepObject`,value:o,...n});t&&i.push(t)}else{let t=W({allowReserved:e,name:a,value:o});t&&i.push(t)}}return i.join(`&`)},xt=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`}},St=(e,t)=>t?!!(e.headers.has(t)||e.query?.[t]||e.headers.get(`Cookie`)?.includes(`${t}=`)):!1,Ct=async({security:e,...t})=>{for(let n of e){if(St(t,n.name))continue;let e=await ut(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}}},wt=e=>yt({baseUrl:e.baseUrl,path:e.path,query:e.query,querySerializer:typeof e.querySerializer==`function`?e.querySerializer:bt(e.querySerializer),url:e.url}),Tt=(e,t)=>{let n={...e,...t};return n.baseUrl?.endsWith(`/`)&&(n.baseUrl=n.baseUrl.substring(0,n.baseUrl.length-1)),n.headers=Et(e.headers,t.headers),n},Et=(...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 Dt=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 Ot=()=>({error:new Dt,request:new Dt,response:new Dt}),kt=bt({allowReserved:!1,array:{explode:!0,style:`form`},object:{explode:!0,style:`deepObject`}}),At={"Content-Type":`application/json`},jt=(e={})=>({...dt,headers:At,parseAs:`auto`,querySerializer:kt,...e}),Mt=(e={})=>{let t=Tt(jt(),e),n=()=>({...t}),r=e=>(t=Tt(t,e),n()),i=Ot(),a=async e=>{let n={...t,...e,fetch:e.fetch??t.fetch??globalThis.fetch,headers:Et(t.headers,e.headers),serializedBody:void 0};return n.security&&await Ct({...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:wt(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`?xt(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 lt({...n,body:n.body,headers:n.headers,method:e,url:r})},t};return{buildUrl:wt,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 Nt=Mt(jt({baseUrl:`http://localhost:4096`}));var G=class{_client=Nt;constructor(e){e?.client&&(this._client=e.client)}},Pt=class extends G{event(e){return(e?.client??this._client).get.sse({url:`/global/event`,...e})}},Ft=class extends G{list(e){return(e?.client??this._client).get({url:`/project`,...e})}current(e){return(e?.client??this._client).get({url:`/project/current`,...e})}},It=class extends G{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})}},Lt=class extends G{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})}},Rt=class extends G{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})}},zt=class extends G{dispose(e){return(e?.client??this._client).post({url:`/instance/dispose`,...e})}},Bt=class extends G{get(e){return(e?.client??this._client).get({url:`/path`,...e})}},Vt=class extends G{get(e){return(e?.client??this._client).get({url:`/vcs`,...e})}},Ht=class extends G{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})}},Ut=class extends G{list(e){return(e?.client??this._client).get({url:`/command`,...e})}},Wt=class extends G{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}})}},Gt=class extends G{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 Wt({client:this._client})},Kt=class extends G{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})}},qt=class extends G{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})}},Jt=class extends G{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})}},Yt=class extends G{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}})}},Xt=class extends G{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 Yt({client:this._client})},Zt=class extends G{status(e){return(e?.client??this._client).get({url:`/lsp`,...e})}},Qt=class extends G{status(e){return(e?.client??this._client).get({url:`/formatter`,...e})}},$t=class extends G{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}})}},en=class extends G{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 $t({client:this._client})},tn=class extends G{subscribe(e){return(e?.client??this._client).get.sse({url:`/event`,...e})}},nn=class extends G{postSessionIdPermissionsPermissionId(e){return(e.client??this._client).post({url:`/session/{id}/permissions/{permissionID}`,...e,headers:{"Content-Type":`application/json`,...e.headers}})}global=new Pt({client:this._client});project=new Ft({client:this._client});pty=new It({client:this._client});config=new Lt({client:this._client});tool=new Rt({client:this._client});instance=new zt({client:this._client});path=new Bt({client:this._client});vcs=new Vt({client:this._client});session=new Ht({client:this._client});command=new Ut({client:this._client});provider=new Gt({client:this._client});find=new Kt({client:this._client});file=new qt({client:this._client});app=new Jt({client:this._client});mcp=new Xt({client:this._client});lsp=new Zt({client:this._client});formatter=new Qt({client:this._client});tui=new en({client:this._client});auth=new Yt({client:this._client});event=new tn({client:this._client})};function rn(e,t){if(e)return t&&(e===t||e===encodeURIComponent(t))?t:e}function an(e,t){if(e.method!==`GET`&&e.method!==`HEAD`)return e;let n=rn(e.headers.get(`x-opencode-directory`),t);if(!n)return e;let r=new URL(e.url);r.searchParams.has(`directory`)||r.searchParams.set(`directory`,n);let i=new Request(r,e);return i.headers.delete(`x-opencode-directory`),i}function on(e){if(!e?.fetch){let t=e=>(e.timeout=!1,fetch(e));e={...e,fetch:t}}e?.directory&&(e.headers={...e.headers,"x-opencode-directory":encodeURIComponent(e.directory)});let t=Mt(e);return t.interceptors.request.use(t=>an(t,e?.directory)),new nn({client:t})}async function sn(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=De(`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 cn(e){let t=await sn({...e});return{client:on({baseUrl:t.url}),server:t}}async function ln(e,t,n,r){if(n!=null)try{let i=await e.session.update({path:{id:t},body:{title:n}});i.error!=null&&r.warning(`Best-effort session title re-assertion failed`,{sessionId:t,sessionTitle:n,error:String(i.error)})}catch(e){r.warning(`Best-effort session title re-assertion failed`,{sessionId:t,sessionTitle:n,error:e instanceof Error?e.message:String(e)})}}async function un(e){if(e<0||!Number.isFinite(e))throw Error(`Invalid sleep duration: ${e}`);return new Promise(t=>setTimeout(t,e))}const dn={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 fn(e){return e.type===`rate_limit`?`:warning:`:e.type===`llm_timeout`?`:hourglass:`:e.type===`llm_fetch_error`||e.retryable?`:warning:`:`:x:`}function pn(e){let t=fn(e),n=dn[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 mn(e,t,n,r){return{type:e,message:t,retryable:n,details:r?.details,suggestedAction:r?.suggestedAction,resetTime:r?.resetTime}}const hn=[/fetch failed/i,/connect\s*timeout/i,/connecttimeouterror/i,/timed?\s*out/i,/econnrefused/i,/econnreset/i,/etimedout/i,/network error/i];function gn(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 hn.some(e=>e.test(t))}function _n(e,t){return mn(`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 vn(e,t){return mn(`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 yn(e,t,n,r,i,a=p,o){let s=Date.now(),c=0;for(;!r.aborted;){if(await un(500),r.aborted)return{completed:!1,error:`Aborted`};if(o?.sessionError==null)c=0;else{if(c++,c>=3)return i.error(`Session error persisted through grace period`,{sessionId:t,error:o.sessionError,graceCycles:c}),{completed:!1,error:`Session error: ${o.sessionError}`};continue}if(o?.sessionIdle===!0)return i.debug(`Session idle detected via event stream`,{sessionId:t}),{completed:!0,error:null};let l=Date.now()-s;if(a>0&&l>=a)return i.warning(`Poll timeout reached`,{elapsedMs:l,maxPollTimeMs:a}),{completed:!1,error:`Poll timeout after ${l}ms`};try{let r=((await e.session.status({query:{directory:n}})).data??{})[t];if(r==null)i.debug(`Session status not found in poll response`,{sessionId:t});else if(r.type===`idle`)return i.debug(`Session idle detected via polling`,{sessionId:t}),{completed:!0,error:null};else i.debug(`Session status`,{sessionId:t,type:r.type});if(o!=null&&!o.firstMeaningfulEventReceived){let e=Date.now()-s;if(e>=9e4)return i.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){i.debug(`Poll request failed`,{error:d(e)})}}return{completed:!1,error:`Aborted`}}async function bn(e,t=2e3){await Promise.race([e,new Promise(e=>{setTimeout(e,t)})])}function xn(e){try{let t=new URL(e);return t.hostname===`github.com`||t.hostname===`api.github.com`}catch{return!1}}function Sn(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 Cn(e){let t=e.match(/https:\/\/github\.com\/[a-zA-Z0-9-]+\/[\w.-]+\/(?:pull|issues)\/\d+(?:#issuecomment-\d+)?/g)??[];return[...new Set(t)].filter(xn)}function wn(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 Tn={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`]},En=`\x1B[0m`;function Dn(){return L.env.NO_COLOR==null}function On(e,t){let[n,r]=Tn[e.toLowerCase()]??[e,`\x1B[36m\x1B[1m`],i=n.padEnd(10,` `);Dn()?L.stdout.write(`\n${r}|${En} ${i} ${En}${t}\n`):L.stdout.write(`\n| ${i} ${t}\n`)}function kn(e){L.stdout.write(`\n${e}\n`)}function An(e,t){t.debug(`Server event`,{eventType:e.type,properties:e.properties})}function jn(e,t,n,r,i){let a=Cn(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=wn(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 Mn(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(An(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)&&(kn(a),a=``)}else if(e.type===`tool`){let t=e.state;t.status===`completed`&&(On(e.tool,t.title),e.tool.toLowerCase()===`bash`&&jn(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=gn(e)?_n(t,s??void 0):vn(t),i!=null&&(i.sessionError=t)}}else p.type===`session.idle`&&p.properties.sessionID===t&&(i!=null&&(i.sessionIdle=!0),a.length>0&&(kn(a),a=``))}return a.length>0&&kn(a),{tokens:o,model:s,cost:c,prsCreated:l,commitsCreated:u,commentsPosted:d,llmError:f}}const Nn=5e3;async function Pn(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=Mn(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 bn(l)};try{let s=await yn(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 bn(l)}}const Fn=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:ue.providerID,modelID:ue.modelID}};async function In(e,t,n,r,i,a,o){let s={parts:[{type:`text`,text:n},...r??[]]},c=Fn(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=gn(u.error)?_n(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 Pn(e,t,i,a?.timeoutMs??18e5,o)}function Ln(){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 mn(e,t,n,r){return{type:e,message:t,retryable:n,details:r?.details,suggestedAction:r?.suggestedAction,resetTime:r?.resetTime}}const hn=[/fetch failed/i,/connect\s*timeout/i,/connecttimeouterror/i,/timed?\s*out/i,/econnrefused/i,/econnreset/i,/etimedout/i,/network error/i];function gn(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 hn.some(e=>e.test(t))}function _n(e,t){return mn(`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 vn(e,t){return mn(`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 yn(e,t,n,r,i,a=p,o){let s=Date.now(),c=0;for(;!r.aborted;){if(await un(500),r.aborted)return{completed:!1,error:`Aborted`};if(o?.sessionError==null)c=0;else{if(c++,c>=3)return i.error(`Session error persisted through grace period`,{sessionId:t,error:o.sessionError,graceCycles:c}),{completed:!1,error:`Session error: ${o.sessionError}`};continue}if(o?.sessionIdle===!0)return i.debug(`Session idle detected via event stream`,{sessionId:t}),{completed:!0,error:null};let l=Date.now()-s;if(a>0&&l>=a)return i.warning(`Poll timeout reached`,{elapsedMs:l,maxPollTimeMs:a}),{completed:!1,error:`Poll timeout after ${l}ms`};try{let r=((await e.session.status({query:{directory:n}})).data??{})[t];if(r==null)i.debug(`Session status not found in poll response`,{sessionId:t});else if(r.type===`idle`)return i.debug(`Session idle detected via polling`,{sessionId:t}),{completed:!0,error:null};else i.debug(`Session status`,{sessionId:t,type:r.type});if(o!=null&&!o.firstMeaningfulEventReceived){let e=Date.now()-s;if(e>=9e4)return i.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){i.debug(`Poll request failed`,{error:d(e)})}}return{completed:!1,error:`Aborted`}}async function bn(e,t=2e3){await Promise.race([e,new Promise(e=>{setTimeout(e,t)})])}function xn(e){try{let t=new URL(e);return t.hostname===`github.com`||t.hostname===`api.github.com`}catch{return!1}}function Sn(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 Cn(e){let t=e.match(/https:\/\/github\.com\/[a-zA-Z0-9-]+\/[\w.-]+\/(?:pull|issues)\/\d+(?:#issuecomment-\d+)?/g)??[];return[...new Set(t)].filter(xn)}function wn(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 Tn={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`]},En=`\x1B[0m`;function Dn(){return N.env.NO_COLOR==null}function On(e,t){let[n,r]=Tn[e.toLowerCase()]??[e,`\x1B[36m\x1B[1m`],i=n.padEnd(10,` `);Dn()?N.stdout.write(`\n${r}|${En} ${i} ${En}${t}\n`):N.stdout.write(`\n| ${i} ${t}\n`)}function kn(e){N.stdout.write(`\n${e}\n`)}function An(e,t){t.debug(`Server event`,{eventType:e.type,properties:e.properties})}function jn(e,t,n,r,i){let a=Cn(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=wn(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 Mn(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(An(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)&&(kn(a),a=``)}else if(e.type===`tool`){let t=e.state;t.status===`completed`&&(On(e.tool,t.title),e.tool.toLowerCase()===`bash`&&jn(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=gn(e)?_n(t,s??void 0):vn(t),i!=null&&(i.sessionError=t)}}else p.type===`session.idle`&&p.properties.sessionID===t&&(i!=null&&(i.sessionIdle=!0),a.length>0&&(kn(a),a=``))}return a.length>0&&kn(a),{tokens:o,model:s,cost:c,prsCreated:l,commitsCreated:u,commentsPosted:d,llmError:f}}const Nn=5e3;async function Pn(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=Mn(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 bn(l)};try{let s=await yn(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 bn(l)}}const Fn=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:me.providerID,modelID:me.modelID}};async function In(e,t,n,r,i,a,o){let s={parts:[{type:`text`,text:n},...r??[]]},c=Fn(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=gn(u.error)?_n(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 Pn(e,t,i,a?.timeoutMs??18e5,o)}function Ln(){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 Rn(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 zn(e){return e==null||e.length===0?``:[`## Current Thread Context`,`This is work from your PREVIOUS runs on this same entity:`,``,e].join(` `)}function Bn(){return[`## Reminder: Critical Rules`,"- ONE comment/review only. Include Run Summary marker. Use `gh` CLI only."].join(` @@ -159,7 +159,7 @@ ${r.trim()} - **Number:** #${n.issueNumber} - **Title:** ${n.issueTitle??`N/A`} - **Type:** ${n.issueType??`unknown`} -`)}if(n.diffContext!=null&&l.push(Yn(n.diffContext)),n.hydratedContext!=null&&l.push(Ze(n.hydratedContext)),a!=null){let e=Jn(a,u,c,f!=null);e!=null&&l.push(e)}l.push(`# Agent Context +`)}if(n.diffContext!=null&&l.push(Yn(n.diffContext)),n.hydratedContext!=null&&l.push(Qe(n.hydratedContext)),a!=null){let e=Jn(a,u,c,f!=null);e!=null&&l.push(e)}l.push(`# Agent Context You are the Fro Bot Agent running in a non-interactive CI environment (GitHub Actions). @@ -257,13 +257,13 @@ Every response you post — regardless of channel (issue, PR, discussion, review `)}function qn(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 Jn(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:Kn(e,`## Related Historical Context`,t)}return e.recentSessions.length===0&&e.priorWorkContext.length===0&&r?null:Kn(e,`## Prior Session Context`,e.priorWorkContext)}function Yn(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 Xn(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 Zn(e,t,n,r){let i=Date.now(),a=new AbortController,o=n?.timeoutMs??18e5,s=null,c=!1,l=r==null,u=null;o>0&&(s=setTimeout(()=>{c=!0,t.warning(`Execution timeout reached`,{timeoutMs:o}),a.abort()},o)),t.info(`Executing OpenCode agent (SDK mode)`,{agent:n?.agent??`sisyphus`,hasModelOverride:n?.model!=null,timeoutMs:o});try{let s;if(r==null){let e=await cn({signal:a.signal});s=e.client,u=e.server}else s=r.client;let l;if(n?.continueSessionId==null){let e=n?.sessionTitle==null?void 0:{body:{title:n.sessionTitle}},r=e==null?await s.session.create():await s.session.create(e);if(r.data==null||r.error!=null)throw Error(`Failed to create session: ${r.error==null?`No data returned`:String(r.error)}`);l=r.data.id,t.info(`Created new OpenCode session`,{sessionId:l,sessionTitle:n?.sessionTitle??null})}else l=n.continueSessionId,t.info(`Continuing existing OpenCode session`,{sessionId:l});let d=Wn({...e,sessionId:l},t),f=I();if(fe()){let e=M(),n=ye.createHash(`sha256`).update(d).digest(`hex`),r=V.join(e,`prompt-${l}-${n.slice(0,8)}.txt`);try{await B.mkdir(e,{recursive:!0}),await B.writeFile(r,d,`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 r=1;r<=3;r++){if(c)return{success:!1,exitCode:130,duration:Date.now()-i,sessionId:l,error:`Execution timed out after ${o}ms`,tokenUsage:p.tokens,model:p.model,cost:p.cost,prsCreated:p.prsCreated,commitsCreated:p.commitsCreated,commentsPosted:p.commentsPosted,llmError:h};if(o>0&&o-(Date.now()-i)<=5e3&&r>1)break;let a=r===1?d:`The previous request was interrupted by a network error (fetch failed). +`)}async function Zn(e,t,n,r){let i=Date.now(),a=new AbortController,o=n?.timeoutMs??18e5,s=null,c=!1,l=r==null,u=null;o>0&&(s=setTimeout(()=>{c=!0,t.warning(`Execution timeout reached`,{timeoutMs:o}),a.abort()},o)),t.info(`Executing OpenCode agent (SDK mode)`,{agent:n?.agent??`sisyphus`,hasModelOverride:n?.model!=null,timeoutMs:o});try{let s;if(r==null){let e=await cn({signal:a.signal});s=e.client,u=e.server}else s=r.client;let l;if(n?.continueSessionId==null){let e=n?.sessionTitle==null?void 0:{body:{title:n.sessionTitle}},r=e==null?await s.session.create():await s.session.create(e);if(r.data==null||r.error!=null)throw Error(`Failed to create session: ${r.error==null?`No data returned`:String(r.error)}`);l=r.data.id,t.info(`Created new OpenCode session`,{sessionId:l,sessionTitle:n?.sessionTitle??null})}else l=n.continueSessionId,t.info(`Continuing existing OpenCode session`,{sessionId:l});let d=Wn({...e,sessionId:l},t),f=M();if(ge()){let e=k(),n=Ce.createHash(`sha256`).update(d).digest(`hex`),r=L.join(e,`prompt-${l}-${n.slice(0,8)}.txt`);try{await I.mkdir(e,{recursive:!0}),await I.writeFile(r,d,`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 r=1;r<=3;r++){if(c)return{success:!1,exitCode:130,duration:Date.now()-i,sessionId:l,error:`Execution timed out after ${o}ms`,tokenUsage:p.tokens,model:p.model,cost:p.cost,prsCreated:p.prsCreated,commitsCreated:p.commitsCreated,commentsPosted:p.commentsPosted,llmError:h};if(o>0&&o-(Date.now()-i)<=5e3&&r>1)break;let a=r===1?d:`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.`,u=r===1?e.fileParts:void 0,g=await(async()=>{try{return await In(s,l,a,u,f,n,t)}finally{await ln(s,l,n?.sessionTitle,t)}})();if(g.success)return p=g.eventStreamResult,{success:!0,exitCode:0,duration:Date.now()-i,sessionId:l,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||r>=3)break;t.warning(`LLM fetch error detected, retrying with continuation prompt`,{attempt:r,maxAttempts:3,error:g.error,delayMs:Nn,sessionId:l}),await un(Nn)}return{success:!1,exitCode:1,duration:Date.now()-i,sessionId:l,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()-i,r=d(e);return t.error(`OpenCode execution failed`,{error:r,durationMs:n}),{success:!1,exitCode:1,duration:n,sessionId:null,error:r,tokenUsage:null,model:null,cost:null,prsCreated:[],commitsCreated:[],commentsPosted:0,llmError:gn(e)?_n(r):null}}finally{s!=null&&clearTimeout(s),a.abort(),l&&u?.close()}}async function Qn(e,t,n){return t.commentId==null?(n.debug(`No comment ID, skipping eyes reaction`),!1):await Pe(e,t.repo,t.commentId,`eyes`,n)==null?!1:(n.info(`Added eyes reaction`,{commentId:t.commentId}),!0)}async function $n(e,t,n){return t.issueNumber==null?(n.debug(`No issue number, skipping working label`),!1):await Le(e,t.repo,`agent: working`,`fcf2e1`,`Agent is currently working on this`,n)&&await Re(e,t.repo,t.issueNumber,[`agent: working`],n)?(n.info(`Added working label`,{issueNumber:t.issueNumber}),!0):!1}async function er(e,t,n){await Promise.all([Qn(e,t,n),$n(e,t,n)])}async function tr(e,t,n){if(t.commentId==null||t.botLogin==null)return;let r=(await Fe(e,t.repo,t.commentId,n)).find(e=>e.content===`eyes`&&e.userLogin===t.botLogin);r!=null&&await Ie(e,t.repo,t.commentId,r.id,n)}async function nr(e,t,n,r){t.commentId!=null&&await Pe(e,t.repo,t.commentId,n,r)}async function rr(e,t,n){if(t.commentId==null||t.botLogin==null){n.debug(`Missing comment ID or bot login, skipping reaction update`);return}try{await tr(e,t,n),await nr(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:d(e)})}}async function ir(e,t,n){if(t.commentId==null||t.botLogin==null){n.debug(`Missing comment ID or bot login, skipping reaction update`);return}try{await tr(e,t,n),await nr(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:d(e)})}}async function ar(e,t,n){if(t.issueNumber==null){n.debug(`No issue number, skipping label removal`);return}await ze(e,t.repo,t.issueNumber,`agent: working`,n)&&n.info(`Removed working label`,{issueNumber:t.issueNumber})}async function or(e,t,n,r){n?await rr(e,t,r):await ir(e,t,r),await ar(e,t,r)}var K=h(O(),1),sr=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())})},cr=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 sr(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())})},lr=class extends Error{constructor(e){super(`Unexpected HTTP response: ${e}`),this.httpStatusCode=e,Object.setPrototypeOf(this,new.target.prototype)}};const ur=process.platform===`win32`;process.platform;function dr(e,t,n,r){return q(this,void 0,void 0,function*(){return t||=z.join(Er(),he.randomUUID()),yield ie(z.dirname(t)),m(`Downloading ${e}`),m(`Destination ${t}`),yield new cr(3,Dr(`TEST_DOWNLOAD_TOOL_RETRY_MIN_SECONDS`,10),Dr(`TEST_DOWNLOAD_TOOL_RETRY_MAX_SECONDS`,20)).execute(()=>q(this,void 0,void 0,function*(){return yield fr(e,t||``,n,r)}),e=>!(e instanceof lr&&e.httpStatusCode&&e.httpStatusCode<500&&e.httpStatusCode!==408&&e.httpStatusCode!==429))})}function fr(e,t,n,r){return q(this,void 0,void 0,function*(){if(R.existsSync(t))throw Error(`Destination file path ${t} already exists`);let i=new w(`actions/tool-cache`,[],{allowRetries:!1});n&&(m(`set auth`),r===void 0&&(r={}),r.authorization=n);let a=yield i.get(e,r);if(a.message.statusCode!==200){let t=new lr(a.message.statusCode);throw m(`Failed to download from "${e}". Code(${a.message.statusCode}) Message(${a.message.statusMessage})`),t}let o=_e.promisify(Oe.pipeline),s=Dr(`TEST_DOWNLOAD_TOOL_RESPONSE_MESSAGE_FACTORY`,()=>a.message)(),c=!1;try{return yield o(s,R.createWriteStream(t)),m(`download complete`),c=!0,t}finally{if(!c){m(`download failed`);try{yield l(t)}catch(e){m(`Failed to delete '${t}'. ${e.message}`)}}}})}function pr(e,t){return q(this,arguments,void 0,function*(e,t,n=`xz`){if(!e)throw Error(`parameter 'file' is required`);t=yield br(t),m(`Checking tar --version`);let r=``;yield x(`tar --version`,[],{ignoreReturnCode:!0,silent:!0,listeners:{stdout:e=>r+=e.toString(),stderr:e=>r+=e.toString()}}),m(r.trim());let i=r.toUpperCase().includes(`GNU TAR`),a;a=n instanceof Array?n:[n],pe()&&!n.includes(`v`)&&a.push(`-v`);let o=t,s=e;return ur&&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 x(`tar`,a),t})}function mr(e,t){return q(this,void 0,void 0,function*(){if(!e)throw Error(`parameter 'file' is required`);return t=yield br(t),ur?yield hr(e,t):yield gr(e,t),t})}function hr(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 C(`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(` `)];m(`Using pwsh at path: ${i}`),yield x(`"${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 C(`powershell`,!0);m(`Using powershell at path: ${t}`),yield x(`"${t}"`,e)}})}function gr(e,t){return q(this,void 0,void 0,function*(){let n=yield C(`unzip`,!0),r=[e];pe()||r.unshift(`-q`),r.unshift(`-o`),yield x(`"${n}"`,r,{cwd:t})})}function _r(e,t,n,r){return q(this,void 0,void 0,function*(){if(n=K.clean(n)||n,r||=me.arch(),m(`Caching tool ${t} ${n} ${r}`),m(`source dir: ${e}`),!R.statSync(e).isDirectory())throw Error(`sourceDir is not a directory`);let i=yield xr(t,n,r);for(let t of R.readdirSync(e))yield u(z.join(e,t),i,{recursive:!0});return Sr(t,n,r),i})}function vr(e,t,n){if(!e)throw Error(`toolName parameter is required`);if(!t)throw Error(`versionSpec parameter is required`);n||=me.arch(),Cr(t)||(t=wr(yr(e,n),t));let r=``;if(t){t=K.clean(t)||``;let i=z.join(Tr(),e,t,n);m(`checking cache: ${i}`),R.existsSync(i)&&R.existsSync(`${i}.complete`)?(m(`Found tool in cache ${e} ${t} ${n}`),r=i):m(`not found`)}return r}function yr(e,t){let n=[];t||=me.arch();let r=z.join(Tr(),e);if(R.existsSync(r)){let e=R.readdirSync(r);for(let i of e)if(Cr(i)){let e=z.join(r,i,t||``);R.existsSync(e)&&R.existsSync(`${e}.complete`)&&n.push(i)}}return n}function br(e){return q(this,void 0,void 0,function*(){return e||=z.join(Er(),he.randomUUID()),yield ie(e),e})}function xr(e,t,n){return q(this,void 0,void 0,function*(){let r=z.join(Tr(),e,K.clean(t)||t,n||``);m(`destination ${r}`);let i=`${r}.complete`;return yield l(r),yield l(i),yield ie(r),r})}function Sr(e,t,n){let r=`${z.join(Tr(),e,K.clean(t)||t,n||``)}.complete`;R.writeFileSync(r,``),m(`finished caching tool`)}function Cr(e){let t=K.clean(e)||``;m(`isExplicit: ${t}`);let n=K.valid(t)!=null;return m(`explicit? ${n}`),n}function wr(e,t){let n=``;m(`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 m(n?`matched: ${n}`:`match not found`),n}function Tr(){let e=process.env.RUNNER_TOOL_CACHE||``;return ge(e,`Expected RUNNER_TOOL_CACHE to be defined`),e}function Er(){let e=process.env.RUNNER_TEMP||``;return ge(e,`Expected RUNNER_TEMP to be defined`),e}function Dr(e,t){let n=global[e];return n===void 0?t:n}function Or(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 kr(e,t,n){let r=V.join(t,`auth.json`);await B.mkdir(t,{recursive:!0});let i=JSON.stringify(e,null,2);return await B.writeFile(r,i,{mode:384}),n.info(`Populated auth.json`,{path:r,providers:Object.keys(e).length}),r}function Ar(){let e=L.platform,t=L.arch;return{os:{darwin:`darwin`,linux:`linux`,win32:`windows`}[e]??`linux`,arch:{arm64:`aarch64`,x64:`x64`}[t]??`x64`,ext:`.zip`}}function jr(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 Mr(e,t,n,r,i=_){let a=Ar(),o=t.find(`bun`,i,a.arch);if(o.length>0)return e.info(`Bun found in cache`,{version:i,path:o}),r(o),await Pr(o),{path:o,version:i,cached:!0};e.info(`Downloading Bun`,{version:i});let s=jr(i,a);try{let o=await t.downloadTool(s);if(L.platform!==`win32`&&!await Fr(o,e,n))throw Error(`Downloaded Bun archive appears corrupted`);e.info(`Extracting Bun`);let c=await Nr(await t.extractZip(o),t),l=H.dirname(c);e.info(`Caching Bun`);let u=await t.cacheDir(l,`bun`,i,a.arch);return r(u),await Pr(u),e.info(`Bun installed`,{version:i,path:u}),{path:u,version:i,cached:!1}}catch(e){let t=d(e);throw Error(`Failed to install Bun ${i}: ${t}`)}}async function Nr(e,t){for(let n of await Se.readdir(e,{withFileTypes:!0})){let{name:r}=n,i=H.join(e,r);if(n.isFile()){if(r===`bun`||r===`bun.exe`)return i;if(/^bun.*\.zip/.test(r))return Nr(await t.extractZip(i),t)}if(r.startsWith(`bun`)&&n.isDirectory())return Nr(i,t)}throw Error(`Could not find executable: bun`)}async function Pr(e){let t=e=>L.platform===`win32`?`${e}.exe`:e,n=H.join(e,t(`bun`));try{await Se.symlink(n,H.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 Fr(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 Ir(e){return{debug:t=>e.debug(t),info:t=>e.info(t),warn:t=>e.warning(t),error:t=>e.error(t)}}function Lr(e){let{token:t,logger:n}=e;return n.debug(`Creating GitHub client with token`),ce(t,{log:Ir(n)})}async function Rr(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 zr(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};L.env.GH_TOKEN=i,r.info(`Configured authentication`,{method:a});let o=null;return e!=null&&(o=await Rr(e,r)),{authenticated:!0,method:a,botLogin:o}}async function Br(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 Vr(e,t,n,r){let i=await Br(`user.name`,r),a=await Br(`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 Ue(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 Hr=new Set([`__proto__`,`prototype`,`constructor`]);function Ur(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function Wr(e,t){let n=Object.create(null);for(let[t,r]of Object.entries(e))Hr.has(t)||(n[t]=r);for(let[e,r]of Object.entries(t)){if(Hr.has(e))continue;let t=n[e];Ur(r)&&Ur(t)?n[e]=Wr(t,r):n[e]=r}return n}async function Gr(e,t,n){let r=JSON.parse(e);if(!Ur(r))throw Error(`omo-config must be a JSON object (non-null, non-array)`);let i=r;await B.mkdir(t,{recursive:!0});let a=V.join(t,`oh-my-opencode.json`),o={};try{let e=await B.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=Wr(o,i);await B.writeFile(a,JSON.stringify(s,null,2)),n.info(`Wrote oMo config`,{path:a,keyCount:Object.keys(i).length})}async function Kr(e,t,n={}){let{logger:r,execAdapter:i}=t,{claude:a=`no`,copilot:o=`no`,gemini:s=`no`,openai:c=`no`,opencodeZen:l=`no`,zaiCodingPlan:u=`no`,kimiForCoding:f=`no`}=n;r.info(`Installing Oh My OpenAgent plugin`,{version:e,claude:a,copilot:o,gemini:s,openai:c,opencodeZen:l,zaiCodingPlan:u,kimiForCoding:f});let p=``,m=[`oh-my-openagent@${e}`,`install`,`--no-tui`,`--skip-auth`,`--claude=${a}`,`--copilot=${o}`,`--gemini=${s}`,`--openai=${c}`,`--opencode-zen=${l}`,`--zai-coding-plan=${u}`,`--kimi-for-coding=${f}`];try{let t=await i.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 r.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),a=n!=null&&n[1]!=null?n[1]:e;return r.info(`oMo plugin installed`,{version:a}),{installed:!0,version:a,error:null}}catch(e){let t=d(e),n=p.length>0?`${t}\nOutput: ${p.slice(0,500)}`:t;return r.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 qr=`opencode`,Jr=i;function Yr(){let e=Ee.platform(),t=Ee.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 Xr(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 Zr(e,t,n,r){if(L.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 Qr(e,t,n,r,i=Jr){let a=Yr(),o=n.find(qr,e,a.arch);if(o.length>0)return t.info(`OpenCode found in cache`,{version:e,path:o}),{path:o,version:e,cached:!0};try{return await $r(e,a,t,n,r)}catch(n){t.warning(`Primary version install failed, trying fallback`,{requestedVersion:e,fallbackVersion:i,error:d(n)})}if(e!==i)try{let e=await $r(i,a,t,n,r);return t.info(`Installed fallback version`,{version:i}),e}catch(t){throw Error(`Failed to install OpenCode (tried ${e} and ${i}): ${d(t)}`)}throw Error(`Failed to install OpenCode version ${e}`)}async function $r(e,t,n,r,i){n.info(`Downloading OpenCode`,{version:e});let a=Xr(e,t),o=await r.downloadTool(a);if(!await Zr(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,qr,e,t.arch);return n.info(`OpenCode installed`,{version:e,path:c}),{path:c,version:e,cached:!1}}async function ei(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 ti={restoreCache:async(e,t,n)=>re(e,t,n),saveCache:async(e,t)=>oe(e,t)};function ni(e){let{os:t,opencodeVersion:n,omoVersion:r}=e;return`${F}-${t}-oc-${n}-omo-${r}`}function ri(e){let{os:t,opencodeVersion:n,omoVersion:r}=e;return[`${F}-${t}-oc-${n}-omo-${r}-`]}async function ii(e){let{logger:t,os:n,opencodeVersion:r,omoVersion:i,toolCachePath:a,bunCachePath:o,omoConfigPath:s,cacheAdapter:c=ti}=e,l=ni({os:n,opencodeVersion:r,omoVersion:i}),u=ri({os:n,opencodeVersion:r,omoVersion:i}),f=[a,o,s];t.info(`Restoring tools cache`,{primaryKey:l,restoreKeys:[...u],paths:f});try{let e=await c.restoreCache(f,l,[...u]);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:d(e)}),{hit:!1,restoredKey:null}}}async function ai(e){let{logger:t,os:n,opencodeVersion:r,omoVersion:i,toolCachePath:a,bunCachePath:o,omoConfigPath:s,cacheAdapter:c=ti}=e,l=ni({os:n,opencodeVersion:r,omoVersion:i}),u=[a,o,s];t.info(`Saving tools cache`,{saveKey:l,paths:u});try{return await c.saveCache(u,l),t.info(`Tools cache saved`,{saveKey:l}),!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:d(e)}),!1)}}function oi(){return{find:vr,downloadTool:dr,extractTar:pr,extractZip:mr,cacheDir:_r}}function si(){return{exec:x,getExecOutput:o}}async function ci(e,t){let n=Date.now(),r=P({component:`setup`}),i=oi(),o=si();try{r.info(`Starting setup`,{version:e.opencodeVersion});let c;try{c=Or(e.authJson)}catch(e){return b(`Invalid auth-json: ${d(e)}`),null}let l=e.opencodeVersion;if(l===`latest`)try{l=await ei(r)}catch(e){r.warning(`Failed to get latest version, using fallback`,{error:d(e)}),l=Jr}let u=e.omoVersion,p=L.env.RUNNER_TOOL_CACHE??`/opt/hostedtoolcache`,m=Ce(p,`opencode`),h=Ce(p,`bun`),g=Ce(De(),`.config`,`opencode`),v=T(),y=await ii({logger:r,os:v,opencodeVersion:l,omoVersion:u,toolCachePath:m,bunCachePath:h,omoConfigPath:g}),x=y.hit?`hit`:`miss`,S,C=!1,w=null;if(y.hit){let e=i.find(`opencode`,l);e.length>0?(S={path:e,version:l,cached:!0},r.info(`Tools cache hit, using cached OpenCode CLI`,{version:l,omoVersion:u})):r.warning(`Tools cache hit but binary not found in tool-cache, falling through to install`,{requestedVersion:l,restoredKey:y.restoredKey})}if(S==null)try{S=await Qr(l,r,i,o)}catch(e){return b(`Failed to install OpenCode: ${d(e)}`),null}let E=!1;try{await Mr(r,i,o,f,_),E=!0}catch(e){r.warning(`Bun installation failed, oMo will be unavailable`,{error:d(e)})}if(E){if(e.omoConfig!=null)try{await Gr(e.omoConfig,g,r)}catch(e){r.warning(`Failed to write omo-config, continuing without custom config`,{error:d(e)})}let t=await Kr(u,{logger:r,execAdapter:o},e.omoProviders);t.installed?(r.info(`oMo installed`,{version:t.version}),C=!0):r.warning(`oMo installation failed, continuing without oMo`,{error:t.error??`unknown error`}),w=t.error}let D={autoupdate:!1};if(e.opencodeConfig!=null){let t;try{t=JSON.parse(e.opencodeConfig)}catch{return b(`opencode-config must be valid JSON`),null}if(typeof t!=`object`||!t||Array.isArray(t))return b(`opencode-config must be a JSON object`),null;Object.assign(D,t)}let O=`@fro.bot/systematic@${e.systematicVersion}`,k=Array.isArray(D.plugins)?D.plugins:[];k.some(e=>typeof e==`string`&&e.startsWith(`@fro.bot/systematic`))||(D.plugins=[...k,O]),a(`OPENCODE_CONFIG_CONTENT`,JSON.stringify(D)),y.hit||await ai({logger:r,os:v,opencodeVersion:l,omoVersion:u,toolCachePath:m,bunCachePath:h,omoConfigPath:g}),f(S.path),s(`opencode-path`,S.path),s(`opencode-version`,S.version),r.info(`OpenCode ready`,{version:S.version,cached:S.cached});let A=ce(t),j=await zr(A,null,t,r);a(`GH_TOKEN`,t),r.info(`GitHub CLI configured`),await Vr(A,j.botLogin,r,o);let M=Ce(le(),`opencode`),N=await kr(c,M,r);s(`auth-json-path`,N),r.info(`auth.json populated`,{path:N});let P=Date.now()-n,F={opencodePath:S.path,opencodeVersion:S.version,ghAuthenticated:j.authenticated,omoInstalled:C,omoError:w,toolsCacheStatus:x,duration:P};return r.info(`Setup complete`,{duration:P}),F}catch(e){let t=d(e);return r.error(`Setup failed`,{error:t}),b(t),null}}function li(e){return{success:!0,data:e}}function ui(e){return{success:!1,error:e}}const di=[`OWNER`,`MEMBER`,`COLLABORATOR`];async function fi(e,t){try{let{client:n,server:r}=await cn({signal:e});return t.debug(`OpenCode server bootstrapped`,{url:r.url}),li({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}),ui(Error(`Server bootstrap failed: ${n}`))}}async function pi(e,t){let n=e??`opencode`;try{let e=``;await x(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 mi(e){let{logger:t,opencodeVersion:n}=e,r=L.env.OPENCODE_PATH??null,i=await pi(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 ci({opencodeVersion:n,authJson:e.authJson,appId:null,privateKey:null,opencodeConfig:e.opencodeConfig,omoConfig:null,omoVersion:e.omoVersion,systematicVersion:e.systematicVersion,omoProviders:e.omoProviders},e.githubToken);if(a==null)throw Error(`Auto-setup failed: runSetup returned null`);return f(a.opencodePath),L.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 hi(e,t){let n={repo:e.agentContext.repo,commentId:e.agentContext.commentId,issueNumber:e.agentContext.issueNumber,issueType:e.agentContext.issueType,botLogin:e.botLogin},r=P({phase:`acknowledgment`});return await er(e.githubClient,n,r),t.debug(`Acknowledgment phase completed`),n}function gi(e,t){try{JSON.parse(e)}catch{throw Error(`${t} must be valid JSON`)}}function _i(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 vi(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 yi(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 bi=[`claude`,`claude-max20`,`copilot`,`gemini`,`openai`,`opencode-zen`,`zai-coding-plan`,`kimi-for-coding`];function xi(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(!bi.includes(e))throw Error(`Invalid omo-providers value: "${e}". Valid values: ${bi.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 Si(){try{let e=c(`github-token`,{required:!0}).trim();if(e.length===0)return ui(Error(`github-token is required but was not provided`));let t=c(`auth-json`,{required:!0}).trim();if(t.length===0)return ui(Error(`auth-json is required but was not provided`));gi(t,`auth-json`);let a=c(`prompt`).trim(),o=a.length>0?a:null,s=c(`session-retention`).trim(),l=s.length>0?_i(s,`session-retention`):50,u=c(`s3-backup`).trim().toLowerCase()===`true`,d=c(`s3-bucket`).trim(),f=d.length>0?d:null,m=c(`aws-region`).trim(),h=m.length>0?m:null,g=c(`agent`).trim(),_=g.length>0?g:de,y=c(`model`).trim(),b=y.length>0?vi(y):null,x=c(`timeout`).trim(),S=x.length>0?yi(x):p,C=c(`opencode-version`).trim(),w=C.length>0?C:i,T=c(`skip-cache`).trim().toLowerCase()===`true`,E=c(`omo-version`).trim(),D=E.length>0?E:v,O=c(`systematic-version`).trim(),k=O.length>0?O:r,A=c(`omo-providers`).trim(),j=xi(A.length>0?A:``),M=c(`opencode-config`).trim(),N=M.length>0?M:null,P=c(`dedup-window`).trim(),F=P.length>0?yi(P,`dedup-window`):n;if(N!=null){gi(N,`opencode-config`);let e=JSON.parse(N);if(typeof e!=`object`||!e||Array.isArray(e))throw Error(`Input 'opencode-config' must be a JSON object`)}return li({githubToken:e,authJson:t,prompt:o,sessionRetention:l,s3Backup:u,s3Bucket:f,awsRegion:h,agent:_,model:b,timeoutMs:S,opencodeVersion:w,skipCache:T,omoVersion:D,systematicVersion:k,omoProviders:j,opencodeConfig:N,dedupWindow:F})}catch(e){return ui(e instanceof Error?e:Error(String(e)))}}async function Ci(n){let r=Si();if(!r.success)return b(`Invalid inputs: ${r.error.message}`),null;let i=r.data,a=P({phase:`main`});a.info(`Action inputs parsed`,{sessionRetention:i.sessionRetention,s3Backup:i.s3Backup,hasGithubToken:i.githubToken.length>0,hasPrompt:i.prompt!=null,agent:i.agent,hasModelOverride:i.model!=null,timeoutMs:i.timeoutMs});let o=await mi({logger:a,opencodeVersion:i.opencodeVersion,githubToken:i.githubToken,authJson:i.authJson,omoVersion:i.omoVersion,systematicVersion:i.systematicVersion,omoProviders:i.omoProviders,opencodeConfig:i.opencodeConfig});return o.didSetup?a.info(`OpenCode auto-setup completed`,{version:o.version}):a.info(`OpenCode already available`,{version:o.version}),t(e.OPENCODE_VERSION,o.version),n.debug(`Bootstrap phase completed`,{opencodeVersion:o.version}),{inputs:i,logger:a,opencodeResult:o}}const wi=/^[0-9a-f]{40}$/i;function Ti(){return{exec:x,getExecOutput:o}}async function Ei(e){let{workspacePath:t,logger:n,execAdapter:r=Ti()}=e,i=V.join(t,`.git`),a=V.join(i,`opencode`);try{let e=(await B.readFile(a,`utf8`)).trim();if(e.length>0){if(wi.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:d(e)})}try{let e=await B.readFile(i,`utf8`),n=/^gitdir: (.+)$/m.exec(e);if(n==null)return{projectId:null,source:`error`,error:`Invalid .git file format`};i=V.resolve(t,n[1]),a=V.join(i,`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:i}=await r.getExecOutput(`git`,[`rev-list`,`--max-parents=0`,`--all`],{cwd:t,silent:!0});if(i!==0||e.trim().length===0)return{projectId:null,source:`error`,error:`No commits found in repository`};let o=e.trim().split(` -`).map(e=>e.trim()).filter(e=>e.length>0).sort();if(o.length===0)return{projectId:null,source:`error`,error:`No root commits found`};let s=o[0];try{await B.writeFile(a,s,{encoding:`utf8`,flag:`wx`}),n.info(`Project ID generated and cached`,{projectId:s,source:`generated`})}catch(e){(typeof e==`object`?e.code:void 0)===`EEXIST`?n.debug(`Project ID file already written by concurrent process, skipping`,{projectId:s}):n.warning(`Failed to cache project ID (continuing)`,{error:d(e)})}return{projectId:s,source:`generated`}}catch(e){return{projectId:null,source:`error`,error:d(e)}}}async function Di(e){let t=E(),n=P({phase:`cache`}),r=I(),i=V.join(r,`.git`,`opencode`),a=await ae({components:t,logger:n,storagePath:j(),authPath:te(),projectIdPath:i,opencodeVersion:e.opencodeResult.version}),o=a.corrupted?`corrupted`:a.hit?`hit`:`miss`;e.logger.info(`Cache restore completed`,{cacheStatus:o,key:a.key});let s=await Ei({workspacePath:r,logger:n});s.source===`error`?n.warning(`Failed to generate project ID (continuing)`,{error:s.error}):n.debug(`Project ID ready`,{projectId:s.projectId,source:s.source});let c=P({phase:`server-bootstrap`}),l=await fi(new AbortController().signal,c);if(!l.success)return b(`OpenCode server bootstrap failed: ${l.error.message}`),null;let u=l.data;return c.info(`SDK server bootstrapped successfully`),{cacheResult:a,cacheStatus:o,serverHandle:u}}const Oi={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 ki(e,t,n){e.lastIndex=0;let r=e.exec(t);for(;r!=null;)n(r),r=e.exec(t);e.lastIndex=0}function Ai(e){let t=[],n=new Set;return ki(Oi.markdownImage,e,e=>{let r=e[2],i=e[1],a=e[0];r!=null&&i!=null&&!n.has(r)&&Sn(r)&&(n.add(r),t.push({url:r,originalMarkdown:a,altText:i,type:`image`}))}),ki(Oi.markdownLink,e,e=>{let r=e[2],i=e[1],a=e[0];r!=null&&i!=null&&!n.has(r)&&Sn(r)&&(n.add(r),t.push({url:r,originalMarkdown:a,altText:i,type:`file`}))}),ki(Oi.htmlImage,e,e=>{let r=e[1],i=e[0];if(r!=null&&!n.has(r)&&Sn(r)){n.add(r);let e=/alt=["']([^"']*)["']/i.exec(i);t.push({url:r,originalMarkdown:i,altText:e?.[1]??``,type:`image`})}}),Oi.htmlImage.lastIndex=0,t}function ji(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 Mi={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`]},Ni=[`github.com`,`githubusercontent.com`];async function Pi(e,t,n,r,i){i.debug(`Downloading attachment`,{url:e.url});try{let a=await fetch(e.url,{headers:{Authorization:`Bearer ${n}`,Accept:`*/*`,"User-Agent":`fro-bot-agent`},redirect:`manual`}),o=a;if(a.status>=300&&a.status<400){let t=a.headers.get(`location`);if(t==null)return i.warning(`Redirect without location`,{url:e.url}),null;let n=new URL(t);if(!Ni.some(e=>n.hostname===e||n.hostname.endsWith(`.${e}`)))return i.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 i.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>r.maxFileSizeBytes)return i.warning(`Attachment exceeds size limit (Content-Length)`,{url:e.url,size:t,limit:r.maxFileSizeBytes}),null}let c=ve.from(await o.arrayBuffer());if(c.length>r.maxFileSizeBytes)return i.warning(`Attachment exceeds size limit`,{url:e.url,size:c.length,limit:r.maxFileSizeBytes}),null;let l=o.headers.get(`content-type`)??`application/octet-stream`,u=ji(e.url,e.altText,t),d=l.split(`;`)[0],f=d==null?`application/octet-stream`:d.trim(),p=await B.mkdtemp(V.join(Te.tmpdir(),`fro-bot-attachments-`)),m=u.trim().length>0?u:`attachment_${t+1}`,h=V.join(p,m);return await B.writeFile(h,c),i.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 i.warning(`Attachment download error`,{url:e.url,error:d(t)}),null}}async function Fi(e,t,n=Mi,r){return Promise.all(e.map(async(e,i)=>Pi(e,i,t,n,r)))}async function Ii(e,t){for(let n of e)try{await B.unlink(n);let e=V.dirname(n);await B.rmdir(e).catch(()=>{})}catch(e){t.debug(`Failed to cleanup temp file`,{path:n,error:d(e)})}}function Li(e){return e.map(e=>({type:`file`,mime:e.mime,url:xe(e.tempPath).toString(),filename:e.filename}))}function Ri(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 zi(e,t,n,r){return{processed:n,skipped:r,modifiedBody:Ri(e,t,n),fileParts:Li(n),tempFiles:n.map(e=>e.tempPath)}}function Bi(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 Vi(e,t=Mi,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 (${Bi(o.sizeBytes)} > ${Bi(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 (${Bi(t.maxTotalSizeBytes)})`}),n.debug(`Attachment skipped: total size exceeded`,{url:o.url});continue}if(!Hi(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 Hi(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=V.resolve(e);return t.endsWith(V.sep)&&t.length>1?t.slice(0,-1):t}const Ui=e=>typeof e==`object`&&!!e,Y=e=>typeof e==`string`?e:null,Wi=e=>typeof e==`number`?e:null;function Gi(e){if(Array.isArray(e))return e.filter(Ui).map(e=>({file:Y(e.file)??``,additions:Wi(e.additions)??0,deletions:Wi(e.deletions)??0}))}function Ki(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:Gi(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 qi(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(!Ui(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 Ji(e,t,n){let r=J(t),i=await qi(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 Yi(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 Xi(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:Yi(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 Zi(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:Gi(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 Qi(e){return[...e.map(e=>{let t=Zi(`info`in e?e.info:e),n=`parts`in e?e.parts.map(Xi):void 0;return n==null||n.length===0?t:{...t,parts:n}})].sort((e,t)=>e.time.created-t.time.created)}async function $i(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(Ki):[]:(n.warning(`SDK session list failed`,{error:String(r.error)}),[])}async function ea(e,t,n){let r=await e.session.messages({path:{id:t}});return r.error==null&&r.data!=null?Qi(r.data):(n.warning(`SDK session messages failed`,{error:String(r.error)}),[])}async function ta(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(Ki);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 na(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 ra={maxSessions:50,maxAgeDays:30};async function ia(e,t,n,r){let{maxSessions:i,maxAgeDays:a}=n;if(r.info(`Starting session pruning`,{workspacePath:t,maxSessions:i,maxAgeDays:a}),await Ji(e,t,r)==null)return r.debug(`No project found for pruning`,{workspacePath:t}),{prunedCount:0,prunedSessionIds:[],remainingCount:0,freedBytes:0};let o=await $i(e,t,r),s=o.filter(e=>e.parentID==null);if(s.length===0)return{prunedCount:0,prunedSessionIds:[],remainingCount:0,freedBytes:0};let c=[...s].sort((e,t)=>t.time.updated-e.time.updated),l=new Date;l.setDate(l.getDate()-a);let u=l.getTime(),f=new Set;for(let e of c)e.time.updated>=u&&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 o)t.parentID===e.id&&m.add(t.id)}if(m.size===0)return r.info(`No sessions to prune`),{prunedCount:0,prunedSessionIds:[],remainingCount:s.length,freedBytes:0};let h=[];for(let t of m)try{await na(e,t,r),h.push(t),r.debug(`Pruned session`,{sessionId:t})}catch(e){r.warning(`Failed to prune session`,{sessionId:t,error:d(e)})}let g=s.length-p.length;return r.info(`Session pruning complete`,{prunedCount:h.length,remainingCount:g}),{prunedCount:h.length,prunedSessionIds:h,remainingCount:g,freedBytes:0}}async function aa(e,t,n,r){let{limit:i,fromDate:a,toDate:o}=n;r.debug(`Listing sessions`,{directory:t,limit:i});let s=[...(await $i(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 ea(e,t.id,r),i=oa(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 oa(e){let t=new Set;for(let n of e)n.agent!=null&&t.add(n.agent);return[...t]}async function sa(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 ca(t,s,c,o,i);return e.length>0&&l.push({sessionId:s,matches:e.slice(0,a)}),l}let d=await aa(t,n,{},i);for(let e of d){if(u>=a)break;let n=await ca(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 ca(e,t,n,r,i){let a=await ea(e,t,i),o=[];for(let e of a){let t=e.parts??[];for(let i of t){let t=la(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 la(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 ua(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 da(e,t,n,r){let i=ua(t);try{let t=await n.session.prompt({path:{id:e},body:{noReply:!0,parts:[{type:`text`,text:i}]}});if(t.error!=null){r.warning(`SDK prompt writeback failed`,{sessionId:e,error:String(t.error)});return}r.info(`Session summary written via SDK`,{sessionId:e})}catch(t){r.warning(`SDK prompt writeback failed`,{sessionId:e,error:d(t)})}}async function fa(n){let{bootstrapLogger:r,reactionCtx:i,githubClient:a,agentSuccess:o,attachmentResult:s,serverHandle:c,detectedOpencodeVersion:l}=n;try{if(s!=null){let e=P({phase:`attachment-cleanup`});await Ii(s.tempFiles,e)}i!=null&&a!=null&&await or(a,i,o,P({phase:`cleanup`}));let n=P({phase:`prune`}),r=I();if(c!=null){let e=J(r),t=await ia(c.client,e,ra,n);t.prunedCount>0&&n.info(`Pruned old sessions`,{pruned:t.prunedCount,remaining:t.remainingCount})}let u=E(),d=P({phase:`cache-save`}),f=V.join(r,`.git`,`opencode`);if(await ne({components:u,runId:A(),logger:d,storagePath:j(),authPath:te(),projectIdPath:f,opencodeVersion:l})&&t(e.CACHE_SAVED,`true`),fe()){let n=P({phase:`artifact-upload`});await se({logPath:M(),runId:A(),runAttempt:k(),logger:n})&&t(e.ARTIFACT_UPLOADED,`true`)}}catch(e){r.warning(`Cleanup failed (non-fatal)`,{error:e instanceof Error?e.message:String(e)})}finally{if(c!=null)try{c.shutdown()}catch(e){r.warning(`Server shutdown failed (non-fatal)`,{error:e instanceof Error?e.message:String(e)})}}}const pa=V.join(Ee.homedir(),`.cache`,`fro-bot-dedup`);function ma(e){return e.replaceAll(`/`,`-`)}function ha(e,t){let n=ma(e);return V.join(pa,`${n}-${t.entityType}-${t.entityNumber}`)}function ga(e,t){return`${D}-${ma(e)}-${t.entityType}-${t.entityNumber}-`}function _a(e,t,n){return`${ga(e,t)}${n}`}async function va(e,t,n,r=N){let i=ha(e,t),a=V.join(i,`sentinel.json`),o=ga(e,t);try{if(await B.rm(i,{recursive:!0,force:!0}),await B.mkdir(i,{recursive:!0}),await r.restoreCache([i],o,[])==null)return null;let e=await B.readFile(a,`utf8`);return JSON.parse(e)}catch(e){return n.debug(`Dedup marker restore failed; proceeding without marker`,{error:d(e),entityType:t.entityType,entityNumber:t.entityNumber}),null}}async function ya(e,t,n,r,i=N){let a=ha(e,t),o=V.join(a,`sentinel.json`),s=_a(e,t,n.runId);try{return await B.mkdir(a,{recursive:!0}),await B.writeFile(o,JSON.stringify(n),`utf8`),await i.saveCache([a],s),!0}catch(e){return d(e).toLowerCase().includes(`already exists`)?!0:(r.debug(`Dedup marker save failed`,{error:d(e),entityType:t.entityType,entityNumber:t.entityNumber,saveKey:s}),!1)}}const ba=new Set([`pull_request`,`issues`]),xa=new Set([`synchronize`,`reopened`]);function Sa(e){return e.target==null||!ba.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 Ca(e,t,n,r,i=P({phase:`dedup`}),a){let o=Sa(t);if(e===0)return{shouldProceed:!0,entity:o};if(o==null)return{shouldProceed:!0,entity:null};if(t.action!=null&&xa.has(t.action))return i.debug(`Dedup bypassed for action`,{action:t.action}),{shouldProceed:!0,entity:o};let s=await va(n,o,i,a);if(s==null||s.runId===t.runId)return{shouldProceed:!0,entity:o};let c=new Date(s.timestamp).getTime();if(Number.isNaN(c))return i.warning(`Dedup marker timestamp is invalid; proceeding without dedup`,{markerTimestamp:s.timestamp}),{shouldProceed:!0,entity:o};let l=Date.now()-c;if(l<-6e4)return i.warning(`Dedup marker timestamp is too far in the future; proceeding without dedup`,{markerTimestamp:s.timestamp,markerAge:l}),{shouldProceed:!0,entity:o};let u=Math.max(0,l);return u>e?{shouldProceed:!0,entity:o}:(i.info(`Skipping duplicate trigger within dedup window`,{eventType:t.eventType,action:t.action,runId:t.runId,markerRunId:s.runId,markerTimestamp:s.timestamp,dedupWindow:e,entityType:o.entityType,entityNumber:o.entityNumber}),Ne({sessionId:null,cacheStatus:`miss`,duration:Date.now()-r}),await Ta(t,o,s,u,e,i),{shouldProceed:!1,entity:o})}async function wa(e,t,n,r=P({phase:`dedup`}),i){await ya(n,t,{timestamp:new Date().toISOString(),runId:e.runId,action:e.action??`unknown`,eventType:e.eventType,entityType:t.entityType,entityNumber:t.entityNumber},r,i)}async function Ta(e,t,n,r,i,a){try{let a=Math.round(r/1e3),o=Math.round(i/1e3),s=`${t.entityType} #${t.entityNumber}`,c=`https://github.com/${e.repo.owner}/${e.repo.repo}/actions/runs/${n.runId}`;S.addHeading(`Fro Bot Agent Run — Skipped (Dedup)`,2).addRaw(`Execution skipped because the agent already ran for **${s}** recently.\n\n`).addTable([[{data:`Detail`,header:!0},{data:`Value`,header:!0}],[`Current action`,`\`${e.eventType}.${e.action??`unknown`}\``],[`Prior run`,`[${n.runId}](${c})`],[`Prior action`,`\`${n.eventType}.${n.action}\``],[`Time since prior run`,`${a}s`],[`Dedup window`,`${o}s`]]).addRaw(` +If you had completed the task, confirm the completion.`,u=r===1?e.fileParts:void 0,g=await(async()=>{try{return await In(s,l,a,u,f,n,t)}finally{await ln(s,l,n?.sessionTitle,t)}})();if(g.success)return p=g.eventStreamResult,{success:!0,exitCode:0,duration:Date.now()-i,sessionId:l,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||r>=3)break;t.warning(`LLM fetch error detected, retrying with continuation prompt`,{attempt:r,maxAttempts:3,error:g.error,delayMs:Nn,sessionId:l}),await un(Nn)}return{success:!1,exitCode:1,duration:Date.now()-i,sessionId:l,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()-i,r=d(e);return t.error(`OpenCode execution failed`,{error:r,durationMs:n}),{success:!1,exitCode:1,duration:n,sessionId:null,error:r,tokenUsage:null,model:null,cost:null,prsCreated:[],commitsCreated:[],commentsPosted:0,llmError:gn(e)?_n(r):null}}finally{s!=null&&clearTimeout(s),a.abort(),l&&u?.close()}}async function Qn(e,t,n){return t.commentId==null?(n.debug(`No comment ID, skipping eyes reaction`),!1):await Ie(e,t.repo,t.commentId,`eyes`,n)==null?!1:(n.info(`Added eyes reaction`,{commentId:t.commentId}),!0)}async function $n(e,t,n){return t.issueNumber==null?(n.debug(`No issue number, skipping working label`),!1):await ze(e,t.repo,`agent: working`,`fcf2e1`,`Agent is currently working on this`,n)&&await Be(e,t.repo,t.issueNumber,[`agent: working`],n)?(n.info(`Added working label`,{issueNumber:t.issueNumber}),!0):!1}async function er(e,t,n){await Promise.all([Qn(e,t,n),$n(e,t,n)])}async function tr(e,t,n){if(t.commentId==null||t.botLogin==null)return;let r=(await Le(e,t.repo,t.commentId,n)).find(e=>e.content===`eyes`&&e.userLogin===t.botLogin);r!=null&&await Re(e,t.repo,t.commentId,r.id,n)}async function nr(e,t,n,r){t.commentId!=null&&await Ie(e,t.repo,t.commentId,n,r)}async function rr(e,t,n){if(t.commentId==null||t.botLogin==null){n.debug(`Missing comment ID or bot login, skipping reaction update`);return}try{await tr(e,t,n),await nr(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:d(e)})}}async function ir(e,t,n){if(t.commentId==null||t.botLogin==null){n.debug(`Missing comment ID or bot login, skipping reaction update`);return}try{await tr(e,t,n),await nr(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:d(e)})}}async function ar(e,t,n){if(t.issueNumber==null){n.debug(`No issue number, skipping label removal`);return}await Ve(e,t.repo,t.issueNumber,`agent: working`,n)&&n.info(`Removed working label`,{issueNumber:t.issueNumber})}async function or(e,t,n,r){n?await rr(e,t,r):await ir(e,t,r),await ar(e,t,r)}var K=h(D(),1),sr=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())})},cr=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 sr(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())})},lr=class extends Error{constructor(e){super(`Unexpected HTTP response: ${e}`),this.httpStatusCode=e,Object.setPrototypeOf(this,new.target.prototype)}};const ur=process.platform===`win32`;process.platform;function dr(e,t,n,r){return q(this,void 0,void 0,function*(){return t||=F.join(Er(),ye.randomUUID()),yield ce(F.dirname(t)),m(`Downloading ${e}`),m(`Destination ${t}`),yield new cr(3,Dr(`TEST_DOWNLOAD_TOOL_RETRY_MIN_SECONDS`,10),Dr(`TEST_DOWNLOAD_TOOL_RETRY_MAX_SECONDS`,20)).execute(()=>q(this,void 0,void 0,function*(){return yield fr(e,t||``,n,r)}),e=>!(e instanceof lr&&e.httpStatusCode&&e.httpStatusCode<500&&e.httpStatusCode!==408&&e.httpStatusCode!==429))})}function fr(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 w(`actions/tool-cache`,[],{allowRetries:!1});n&&(m(`set auth`),r===void 0&&(r={}),r.authorization=n);let a=yield i.get(e,r);if(a.message.statusCode!==200){let t=new lr(a.message.statusCode);throw m(`Failed to download from "${e}". Code(${a.message.statusCode}) Message(${a.message.statusMessage})`),t}let o=xe.promisify(je.pipeline),s=Dr(`TEST_DOWNLOAD_TOOL_RESPONSE_MESSAGE_FACTORY`,()=>a.message)(),c=!1;try{return yield o(s,P.createWriteStream(t)),m(`download complete`),c=!0,t}finally{if(!c){m(`download failed`);try{yield l(t)}catch(e){m(`Failed to delete '${t}'. ${e.message}`)}}}})}function pr(e,t){return q(this,arguments,void 0,function*(e,t,n=`xz`){if(!e)throw Error(`parameter 'file' is required`);t=yield br(t),m(`Checking tar --version`);let r=``;yield x(`tar --version`,[],{ignoreReturnCode:!0,silent:!0,listeners:{stdout:e=>r+=e.toString(),stderr:e=>r+=e.toString()}}),m(r.trim());let i=r.toUpperCase().includes(`GNU TAR`),a;a=n instanceof Array?n:[n],_e()&&!n.includes(`v`)&&a.push(`-v`);let o=t,s=e;return ur&&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 x(`tar`,a),t})}function mr(e,t){return q(this,void 0,void 0,function*(){if(!e)throw Error(`parameter 'file' is required`);return t=yield br(t),ur?yield hr(e,t):yield gr(e,t),t})}function hr(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 C(`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(` `)];m(`Using pwsh at path: ${i}`),yield x(`"${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 C(`powershell`,!0);m(`Using powershell at path: ${t}`),yield x(`"${t}"`,e)}})}function gr(e,t){return q(this,void 0,void 0,function*(){let n=yield C(`unzip`,!0),r=[e];_e()||r.unshift(`-q`),r.unshift(`-o`),yield x(`"${n}"`,r,{cwd:t})})}function _r(e,t,n,r){return q(this,void 0,void 0,function*(){if(n=K.clean(n)||n,r||=ve.arch(),m(`Caching tool ${t} ${n} ${r}`),m(`source dir: ${e}`),!P.statSync(e).isDirectory())throw Error(`sourceDir is not a directory`);let i=yield xr(t,n,r);for(let t of P.readdirSync(e))yield u(F.join(e,t),i,{recursive:!0});return Sr(t,n,r),i})}function vr(e,t,n){if(!e)throw Error(`toolName parameter is required`);if(!t)throw Error(`versionSpec parameter is required`);n||=ve.arch(),Cr(t)||(t=wr(yr(e,n),t));let r=``;if(t){t=K.clean(t)||``;let i=F.join(Tr(),e,t,n);m(`checking cache: ${i}`),P.existsSync(i)&&P.existsSync(`${i}.complete`)?(m(`Found tool in cache ${e} ${t} ${n}`),r=i):m(`not found`)}return r}function yr(e,t){let n=[];t||=ve.arch();let r=F.join(Tr(),e);if(P.existsSync(r)){let e=P.readdirSync(r);for(let i of e)if(Cr(i)){let e=F.join(r,i,t||``);P.existsSync(e)&&P.existsSync(`${e}.complete`)&&n.push(i)}}return n}function br(e){return q(this,void 0,void 0,function*(){return e||=F.join(Er(),ye.randomUUID()),yield ce(e),e})}function xr(e,t,n){return q(this,void 0,void 0,function*(){let r=F.join(Tr(),e,K.clean(t)||t,n||``);m(`destination ${r}`);let i=`${r}.complete`;return yield l(r),yield l(i),yield ce(r),r})}function Sr(e,t,n){let r=`${F.join(Tr(),e,K.clean(t)||t,n||``)}.complete`;P.writeFileSync(r,``),m(`finished caching tool`)}function Cr(e){let t=K.clean(e)||``;m(`isExplicit: ${t}`);let n=K.valid(t)!=null;return m(`explicit? ${n}`),n}function wr(e,t){let n=``;m(`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 m(n?`matched: ${n}`:`match not found`),n}function Tr(){let e=process.env.RUNNER_TOOL_CACHE||``;return be(e,`Expected RUNNER_TOOL_CACHE to be defined`),e}function Er(){let e=process.env.RUNNER_TEMP||``;return be(e,`Expected RUNNER_TEMP to be defined`),e}function Dr(e,t){let n=global[e];return n===void 0?t:n}function Or(){return{find:vr,downloadTool:dr,extractTar:pr,extractZip:mr,cacheDir:_r}}function kr(){return{exec:x,getExecOutput:o}}function Ar(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 jr(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 Mr(){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 Nr(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 Pr(e,t,n,r,i=_){let a=Mr(),o=t.find(`bun`,i,a.arch);if(o.length>0)return e.info(`Bun found in cache`,{version:i,path:o}),r(o),await Ir(o),{path:o,version:i,cached:!0};e.info(`Downloading Bun`,{version:i});let s=Nr(i,a);try{let o=await t.downloadTool(s);if(N.platform!==`win32`&&!await Lr(o,e,n))throw Error(`Downloaded Bun archive appears corrupted`);e.info(`Extracting Bun`);let c=await Fr(await t.extractZip(o),t),l=R.dirname(c);e.info(`Caching Bun`);let u=await t.cacheDir(l,`bun`,i,a.arch);return r(u),await Ir(u),e.info(`Bun installed`,{version:i,path:u}),{path:u,version:i,cached:!1}}catch(e){let t=d(e);throw Error(`Failed to install Bun ${i}: ${t}`)}}async function Fr(e,t){for(let n of await Ee.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 Fr(await t.extractZip(i),t)}if(r.startsWith(`bun`)&&n.isDirectory())return Fr(i,t)}throw Error(`Could not find executable: bun`)}async function Ir(e){let t=e=>N.platform===`win32`?`${e}.exe`:e,n=R.join(e,t(`bun`));try{await Ee.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 Lr(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 Rr(e,t){let n={autoupdate:!1};if(e.opencodeConfig!=null){let t;try{t=JSON.parse(e.opencodeConfig)}catch{return{config:n,error:`opencode-config must be valid JSON`}}if(typeof t!=`object`||!t||Array.isArray(t))return{config:n,error:`opencode-config must be a JSON object`};Object.assign(n,t)}let r=`@fro.bot/systematic@${e.systematicVersion}`,i=Array.isArray(n.plugins)?n.plugins:[];return i.some(e=>typeof e==`string`&&e.startsWith(`@fro.bot/systematic`))||(n.plugins=[...i,r]),t.debug(`Built CI OpenCode config`,{hasUserConfig:e.opencodeConfig!=null,pluginCount:Array.isArray(n.plugins)?n.plugins.length:0}),{config:n,error:null}}function zr(e){return{debug:t=>e.debug(t),info:t=>e.info(t),warn:t=>e.warning(t),error:t=>e.error(t)}}function Br(e){let{token:t,logger:n}=e;return n.debug(`Creating GitHub client with token`),fe(t,{log:zr(n)})}async function Vr(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 Hr(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 Vr(e,r)),{authenticated:!0,method:a,botLogin:o}}async function Ur(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 Wr(e,t,n,r){let i=await Ur(`user.name`,r),a=await Ur(`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 Ge(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 Gr=new Set([`__proto__`,`prototype`,`constructor`]);function Kr(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function qr(e,t){let n=Object.create(null);for(let[t,r]of Object.entries(e))Gr.has(t)||(n[t]=r);for(let[e,r]of Object.entries(t)){if(Gr.has(e))continue;let t=n[e];Kr(r)&&Kr(t)?n[e]=qr(t,r):n[e]=r}return n}async function Jr(e,t,n){let r=JSON.parse(e);if(!Kr(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=qr(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 Yr(e,t,n={}){let{logger:r,execAdapter:i}=t,{claude:a=`no`,copilot:o=`no`,gemini:s=`no`,openai:c=`no`,opencodeZen:l=`no`,zaiCodingPlan:u=`no`,kimiForCoding:f=`no`}=n;r.info(`Installing Oh My OpenAgent plugin`,{version:e,claude:a,copilot:o,gemini:s,openai:c,opencodeZen:l,zaiCodingPlan:u,kimiForCoding:f});let p=``,m=[`oh-my-openagent@${e}`,`install`,`--no-tui`,`--skip-auth`,`--claude=${a}`,`--copilot=${o}`,`--gemini=${s}`,`--openai=${c}`,`--opencode-zen=${l}`,`--zai-coding-plan=${u}`,`--kimi-for-coding=${f}`];try{let t=await i.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 r.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),a=n!=null&&n[1]!=null?n[1]:e;return r.info(`oMo plugin installed`,{version:a}),{installed:!0,version:a,error:null}}catch(e){let t=d(e),n=p.length>0?`${t}\nOutput: ${p.slice(0,500)}`:t;return r.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 Xr=`opencode`,Zr=i;function Qr(){let e=ke.platform(),t=ke.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 $r(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 ei(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 ti(e,t,n,r,i=Zr){let a=Qr(),o=n.find(Xr,e,a.arch);if(o.length>0)return t.info(`OpenCode found in cache`,{version:e,path:o}),{path:o,version:e,cached:!0};try{return await ni(e,a,t,n,r)}catch(n){t.warning(`Primary version install failed, trying fallback`,{requestedVersion:e,fallbackVersion:i,error:d(n)})}if(e!==i)try{let e=await ni(i,a,t,n,r);return t.info(`Installed fallback version`,{version:i}),e}catch(t){throw Error(`Failed to install OpenCode (tried ${e} and ${i}): ${d(t)}`)}throw Error(`Failed to install OpenCode version ${e}`)}async function ni(e,t,n,r,i){n.info(`Downloading OpenCode`,{version:e});let a=$r(e,t),o=await r.downloadTool(a);if(!await ei(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,Xr,e,t.arch);return n.info(`OpenCode installed`,{version:e,path:c}),{path:c,version:e,cached:!1}}async function ri(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}function ii(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}async function ai(e,t,n){let r=JSON.parse(e);if(!ii(r))throw Error(`systematic-config must be a JSON object (non-null, non-array)`);await I.mkdir(t,{recursive:!0});let i=L.join(t,`systematic.json`),a={};try{let e=await I.readFile(i,`utf8`),t=JSON.parse(e);ii(t)&&(a=t)}catch(e){n.debug(`Using empty base Systematic config`,{path:i,error:String(e)})}let o=qr(a,r);await I.writeFile(i,JSON.stringify(o,null,2)),n.info(`Wrote Systematic config`,{path:i,keyCount:Object.keys(r).length})}const oi={restoreCache:async(e,t,n)=>se(e,t,n),saveCache:async(e,t)=>ue(e,t)};function si(e){let{os:t,opencodeVersion:n,omoVersion:r,systematicVersion:i}=e;return`${re}-${t}-oc-${n}-omo-${r}-sys-${i}`}function ci(e){let{os:t,opencodeVersion:n,omoVersion:r,systematicVersion:i}=e;return[`${re}-${t}-oc-${n}-omo-${r}-sys-${i}-`]}async function li(e){let{logger:t,os:n,opencodeVersion:r,omoVersion:i,systematicVersion:a,toolCachePath:o,bunCachePath:s,omoConfigPath:c,cacheAdapter:l=oi}=e,u=si({os:n,opencodeVersion:r,omoVersion:i,systematicVersion:a}),f=ci({os:n,opencodeVersion:r,omoVersion:i,systematicVersion:a}),p=[o,s,c];t.info(`Restoring tools cache`,{primaryKey:u,restoreKeys:[...f],paths:p});try{let e=await l.restoreCache(p,u,[...f]);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:d(e)}),{hit:!1,restoredKey:null}}}async function ui(e){let{logger:t,os:n,opencodeVersion:r,omoVersion:i,systematicVersion:a,toolCachePath:o,bunCachePath:s,omoConfigPath:c,cacheAdapter:l=oi}=e,u=si({os:n,opencodeVersion:r,omoVersion:i,systematicVersion:a}),f=[o,s,c];t.info(`Saving tools cache`,{saveKey:u,paths:f});try{return await l.saveCache(f,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:d(e)}),!1)}}async function di(e,t){let n=Date.now(),r=j({component:`setup`}),i=Or(),o=kr();try{r.info(`Starting setup`,{version:e.opencodeVersion});let c;try{c=Ar(e.authJson)}catch(e){return b(`Invalid auth-json: ${d(e)}`),null}let l=e.opencodeVersion;if(l===`latest`)try{l=await ri(r)}catch(e){r.warning(`Failed to get latest version, using fallback`,{error:d(e)}),l=Zr}let u=e.omoVersion,p=e.systematicVersion,m=N.env.RUNNER_TOOL_CACHE??`/opt/hostedtoolcache`,h=z(m,`opencode`),g=z(m,`bun`),v=z(Ae(),`.config`,`opencode`),y=ee(),x=await li({logger:r,os:y,opencodeVersion:l,omoVersion:u,systematicVersion:p,toolCachePath:h,bunCachePath:g,omoConfigPath:v}),S=x.hit?`hit`:`miss`,C,w=!1,T=null;if(x.hit){let e=i.find(`opencode`,l);e.length>0?(C={path:e,version:l,cached:!0},r.info(`Tools cache hit, using cached OpenCode CLI`,{version:l,omoVersion:u})):r.warning(`Tools cache hit but binary not found in tool-cache, falling through to install`,{requestedVersion:l,restoredKey:x.restoredKey})}if(C==null)try{C=await ti(l,r,i,o)}catch(e){return b(`Failed to install OpenCode: ${d(e)}`),null}let E=!1;try{await Pr(r,i,o,f,_),E=!0}catch(e){r.warning(`Bun installation failed, oMo will be unavailable`,{error:d(e)})}if(E){if(e.omoConfig!=null)try{await Jr(e.omoConfig,v,r)}catch(e){r.warning(`Failed to write omo-config, continuing without custom config`,{error:d(e)})}if(e.systematicConfig!=null)try{await ai(e.systematicConfig,v,r)}catch(e){r.warning(`systematic-config write failed: ${d(e)}`)}let t=await Yr(u,{logger:r,execAdapter:o},e.omoProviders);t.installed?(r.info(`oMo installed`,{version:t.version}),w=!0):r.warning(`oMo installation failed, continuing without oMo`,{error:t.error??`unknown error`}),T=t.error}let D=Rr({opencodeConfig:e.opencodeConfig,systematicVersion:p},r);if(D.error!=null)return b(D.error),null;a(`OPENCODE_CONFIG_CONTENT`,JSON.stringify(D.config)),x.hit||await ui({logger:r,os:y,opencodeVersion:l,omoVersion:u,systematicVersion:p,toolCachePath:h,bunCachePath:g,omoConfigPath:v}),f(C.path),s(`opencode-path`,C.path),s(`opencode-version`,C.version),r.info(`OpenCode ready`,{version:C.version,cached:C.cached});let te=fe(t),O=await Hr(te,null,t,r);a(`GH_TOKEN`,t),r.info(`GitHub CLI configured`),await Wr(te,O.botLogin,r,o);let ne=z(pe(),`opencode`),k=await jr(c,ne,r);s(`auth-json-path`,k),r.info(`auth.json populated`,{path:k});let A=Date.now()-n,j={opencodePath:C.path,opencodeVersion:C.version,ghAuthenticated:O.authenticated,omoInstalled:w,omoError:T,toolsCacheStatus:S,duration:A};return r.info(`Setup complete`,{duration:A}),j}catch(e){let t=d(e);return r.error(`Setup failed`,{error:t}),b(t),null}}function fi(e){return{success:!0,data:e}}function pi(e){return{success:!1,error:e}}const mi=[`OWNER`,`MEMBER`,`COLLABORATOR`];async function hi(e,t){try{let{client:n,server:r}=await cn({signal:e});return t.debug(`OpenCode server bootstrapped`,{url:r.url}),fi({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}),pi(Error(`Server bootstrap failed: ${n}`))}}async function gi(e,t){let n=e??`opencode`;try{let e=``;await x(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 _i(e){let{logger:t,opencodeVersion:n}=e,r=N.env.OPENCODE_PATH??null,i=await gi(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 di({opencodeVersion:n,authJson:e.authJson,appId:null,privateKey:null,opencodeConfig:e.opencodeConfig,systematicConfig:e.systematicConfig,omoConfig:null,omoVersion:e.omoVersion,systematicVersion:e.systematicVersion,omoProviders:e.omoProviders},e.githubToken);if(a==null)throw Error(`Auto-setup failed: runSetup returned null`);return f(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 vi(e,t){let n={repo:e.agentContext.repo,commentId:e.agentContext.commentId,issueNumber:e.agentContext.issueNumber,issueType:e.agentContext.issueType,botLogin:e.botLogin},r=j({phase:`acknowledgment`});return await er(e.githubClient,n,r),t.debug(`Acknowledgment phase completed`),n}function yi(e,t){try{JSON.parse(e)}catch{throw Error(`${t} must be valid JSON`)}}function bi(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 xi(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 Si(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 Ci=[`claude`,`claude-max20`,`copilot`,`gemini`,`openai`,`opencode-zen`,`zai-coding-plan`,`kimi-for-coding`];function wi(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(!Ci.includes(e))throw Error(`Invalid omo-providers value: "${e}". Valid values: ${Ci.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 Ti(){try{let e=c(`github-token`,{required:!0}).trim();if(e.length===0)return pi(Error(`github-token is required but was not provided`));let t=c(`auth-json`,{required:!0}).trim();if(t.length===0)return pi(Error(`auth-json is required but was not provided`));yi(t,`auth-json`);let a=c(`prompt`).trim(),o=a.length>0?a:null,s=c(`session-retention`).trim(),l=s.length>0?bi(s,`session-retention`):50,u=c(`s3-backup`).trim().toLowerCase()===`true`,d=c(`s3-bucket`).trim(),f=d.length>0?d:null,m=c(`aws-region`).trim(),h=m.length>0?m:null,g=c(`agent`).trim(),_=g.length>0?g:he,y=c(`model`).trim(),b=y.length>0?xi(y):null,x=c(`timeout`).trim(),S=x.length>0?Si(x):p,C=c(`opencode-version`).trim(),w=C.length>0?C:i,ee=c(`skip-cache`).trim().toLowerCase()===`true`,T=c(`omo-version`).trim(),E=T.length>0?T:v,D=c(`systematic-version`).trim(),te=D.length>0?D:r,O=c(`omo-providers`).trim(),ne=wi(O.length>0?O:``),k=c(`opencode-config`).trim(),A=k.length>0?k:null,j=c(`systematic-config`).trim(),re=j.length>0?j:null,ie=c(`dedup-window`).trim(),ae=ie.length>0?Si(ie,`dedup-window`):n;if(A!=null){yi(A,`opencode-config`);let e=JSON.parse(A);if(typeof e!=`object`||!e||Array.isArray(e))throw Error(`Input 'opencode-config' must be a JSON object`)}return fi({githubToken:e,authJson:t,prompt:o,sessionRetention:l,s3Backup:u,s3Bucket:f,awsRegion:h,agent:_,model:b,timeoutMs:S,opencodeVersion:w,skipCache:ee,omoVersion:E,systematicVersion:te,omoProviders:ne,opencodeConfig:A,systematicConfig:re,dedupWindow:ae})}catch(e){return pi(e instanceof Error?e:Error(String(e)))}}async function Ei(n){let r=Ti();if(!r.success)return b(`Invalid inputs: ${r.error.message}`),null;let i=r.data,a=j({phase:`main`});a.info(`Action inputs parsed`,{sessionRetention:i.sessionRetention,s3Backup:i.s3Backup,hasGithubToken:i.githubToken.length>0,hasPrompt:i.prompt!=null,agent:i.agent,hasModelOverride:i.model!=null,timeoutMs:i.timeoutMs});let o=await _i({logger:a,opencodeVersion:i.opencodeVersion,githubToken:i.githubToken,authJson:i.authJson,omoVersion:i.omoVersion,systematicVersion:i.systematicVersion,omoProviders:i.omoProviders,opencodeConfig:i.opencodeConfig,systematicConfig:i.systematicConfig});return o.didSetup?a.info(`OpenCode auto-setup completed`,{version:o.version}):a.info(`OpenCode already available`,{version:o.version}),t(e.OPENCODE_VERSION,o.version),n.debug(`Bootstrap phase completed`,{opencodeVersion:o.version}),{inputs:i,logger:a,opencodeResult:o}}const Di=/^[0-9a-f]{40}$/i;function Oi(){return{exec:x,getExecOutput:o}}async function ki(e){let{workspacePath:t,logger:n,execAdapter:r=Oi()}=e,i=L.join(t,`.git`),a=L.join(i,`opencode`);try{let e=(await I.readFile(a,`utf8`)).trim();if(e.length>0){if(Di.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:d(e)})}try{let e=await I.readFile(i,`utf8`),n=/^gitdir: (.+)$/m.exec(e);if(n==null)return{projectId:null,source:`error`,error:`Invalid .git file format`};i=L.resolve(t,n[1]),a=L.join(i,`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:i}=await r.getExecOutput(`git`,[`rev-list`,`--max-parents=0`,`--all`],{cwd:t,silent:!0});if(i!==0||e.trim().length===0)return{projectId:null,source:`error`,error:`No commits found in repository`};let o=e.trim().split(` +`).map(e=>e.trim()).filter(e=>e.length>0).sort();if(o.length===0)return{projectId:null,source:`error`,error:`No root commits found`};let s=o[0];try{await I.writeFile(a,s,{encoding:`utf8`,flag:`wx`}),n.info(`Project ID generated and cached`,{projectId:s,source:`generated`})}catch(e){(typeof e==`object`?e.code:void 0)===`EEXIST`?n.debug(`Project ID file already written by concurrent process, skipping`,{projectId:s}):n.warning(`Failed to cache project ID (continuing)`,{error:d(e)})}return{projectId:s,source:`generated`}}catch(e){return{projectId:null,source:`error`,error:d(e)}}}async function Ai(e){let t=T(),n=j({phase:`cache`}),r=M(),i=L.join(r,`.git`,`opencode`),a=await le({components:t,logger:n,storagePath:ne(),authPath:ae(),projectIdPath:i,opencodeVersion:e.opencodeResult.version}),o=a.corrupted?`corrupted`:a.hit?`hit`:`miss`;e.logger.info(`Cache restore completed`,{cacheStatus:o,key:a.key});let s=await ki({workspacePath:r,logger:n});s.source===`error`?n.warning(`Failed to generate project ID (continuing)`,{error:s.error}):n.debug(`Project ID ready`,{projectId:s.projectId,source:s.source});let c=j({phase:`server-bootstrap`}),l=await hi(new AbortController().signal,c);if(!l.success)return b(`OpenCode server bootstrap failed: ${l.error.message}`),null;let u=l.data;return c.info(`SDK server bootstrapped successfully`),{cacheResult:a,cacheStatus:o,serverHandle:u}}const ji={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 Mi(e,t,n){e.lastIndex=0;let r=e.exec(t);for(;r!=null;)n(r),r=e.exec(t);e.lastIndex=0}function Ni(e){let t=[],n=new Set;return Mi(ji.markdownImage,e,e=>{let r=e[2],i=e[1],a=e[0];r!=null&&i!=null&&!n.has(r)&&Sn(r)&&(n.add(r),t.push({url:r,originalMarkdown:a,altText:i,type:`image`}))}),Mi(ji.markdownLink,e,e=>{let r=e[2],i=e[1],a=e[0];r!=null&&i!=null&&!n.has(r)&&Sn(r)&&(n.add(r),t.push({url:r,originalMarkdown:a,altText:i,type:`file`}))}),Mi(ji.htmlImage,e,e=>{let r=e[1],i=e[0];if(r!=null&&!n.has(r)&&Sn(r)){n.add(r);let e=/alt=["']([^"']*)["']/i.exec(i);t.push({url:r,originalMarkdown:i,altText:e?.[1]??``,type:`image`})}}),ji.htmlImage.lastIndex=0,t}function Pi(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 Fi={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`]},Ii=[`github.com`,`githubusercontent.com`];async function Li(e,t,n,r,i){i.debug(`Downloading attachment`,{url:e.url});try{let a=await fetch(e.url,{headers:{Authorization:`Bearer ${n}`,Accept:`*/*`,"User-Agent":`fro-bot-agent`},redirect:`manual`}),o=a;if(a.status>=300&&a.status<400){let t=a.headers.get(`location`);if(t==null)return i.warning(`Redirect without location`,{url:e.url}),null;let n=new URL(t);if(!Ii.some(e=>n.hostname===e||n.hostname.endsWith(`.${e}`)))return i.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 i.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>r.maxFileSizeBytes)return i.warning(`Attachment exceeds size limit (Content-Length)`,{url:e.url,size:t,limit:r.maxFileSizeBytes}),null}let c=Se.from(await o.arrayBuffer());if(c.length>r.maxFileSizeBytes)return i.warning(`Attachment exceeds size limit`,{url:e.url,size:c.length,limit:r.maxFileSizeBytes}),null;let l=o.headers.get(`content-type`)??`application/octet-stream`,u=Pi(e.url,e.altText,t),d=l.split(`;`)[0],f=d==null?`application/octet-stream`:d.trim(),p=await I.mkdtemp(L.join(Oe.tmpdir(),`fro-bot-attachments-`)),m=u.trim().length>0?u:`attachment_${t+1}`,h=L.join(p,m);return await I.writeFile(h,c),i.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 i.warning(`Attachment download error`,{url:e.url,error:d(t)}),null}}async function Ri(e,t,n=Fi,r){return Promise.all(e.map(async(e,i)=>Li(e,i,t,n,r)))}async function zi(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:d(e)})}}function Bi(e){return e.map(e=>({type:`file`,mime:e.mime,url:Te(e.tempPath).toString(),filename:e.filename}))}function Vi(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 Hi(e,t,n,r){return{processed:n,skipped:r,modifiedBody:Vi(e,t,n),fileParts:Bi(n),tempFiles:n.map(e=>e.tempPath)}}function Ui(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 Wi(e,t=Fi,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 (${Ui(o.sizeBytes)} > ${Ui(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 (${Ui(t.maxTotalSizeBytes)})`}),n.debug(`Attachment skipped: total size exceeded`,{url:o.url});continue}if(!Gi(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 Gi(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 Ki=e=>typeof e==`object`&&!!e,Y=e=>typeof e==`string`?e:null,qi=e=>typeof e==`number`?e:null;function Ji(e){if(Array.isArray(e))return e.filter(Ki).map(e=>({file:Y(e.file)??``,additions:qi(e.additions)??0,deletions:qi(e.deletions)??0}))}function Yi(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:Ji(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 Xi(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(!Ki(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 Zi(e,t,n){let r=J(t),i=await Xi(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 Qi(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 $i(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:Qi(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 ea(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:Ji(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 ta(e){return[...e.map(e=>{let t=ea(`info`in e?e.info:e),n=`parts`in e?e.parts.map($i):void 0;return n==null||n.length===0?t:{...t,parts:n}})].sort((e,t)=>e.time.created-t.time.created)}async function na(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(Yi):[]:(n.warning(`SDK session list failed`,{error:String(r.error)}),[])}async function ra(e,t,n){let r=await e.session.messages({path:{id:t}});return r.error==null&&r.data!=null?ta(r.data):(n.warning(`SDK session messages failed`,{error:String(r.error)}),[])}async function ia(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(Yi);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 aa(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 oa={maxSessions:50,maxAgeDays:30};async function sa(e,t,n,r){let{maxSessions:i,maxAgeDays:a}=n;if(r.info(`Starting session pruning`,{workspacePath:t,maxSessions:i,maxAgeDays:a}),await Zi(e,t,r)==null)return r.debug(`No project found for pruning`,{workspacePath:t}),{prunedCount:0,prunedSessionIds:[],remainingCount:0,freedBytes:0};let o=await na(e,t,r),s=o.filter(e=>e.parentID==null);if(s.length===0)return{prunedCount:0,prunedSessionIds:[],remainingCount:0,freedBytes:0};let c=[...s].sort((e,t)=>t.time.updated-e.time.updated),l=new Date;l.setDate(l.getDate()-a);let u=l.getTime(),f=new Set;for(let e of c)e.time.updated>=u&&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 o)t.parentID===e.id&&m.add(t.id)}if(m.size===0)return r.info(`No sessions to prune`),{prunedCount:0,prunedSessionIds:[],remainingCount:s.length,freedBytes:0};let h=[];for(let t of m)try{await aa(e,t,r),h.push(t),r.debug(`Pruned session`,{sessionId:t})}catch(e){r.warning(`Failed to prune session`,{sessionId:t,error:d(e)})}let g=s.length-p.length;return r.info(`Session pruning complete`,{prunedCount:h.length,remainingCount:g}),{prunedCount:h.length,prunedSessionIds:h,remainingCount:g,freedBytes:0}}async function ca(e,t,n,r){let{limit:i,fromDate:a,toDate:o}=n;r.debug(`Listing sessions`,{directory:t,limit:i});let s=[...(await na(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 ra(e,t.id,r),i=la(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 la(e){let t=new Set;for(let n of e)n.agent!=null&&t.add(n.agent);return[...t]}async function ua(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 da(t,s,c,o,i);return e.length>0&&l.push({sessionId:s,matches:e.slice(0,a)}),l}let d=await ca(t,n,{},i);for(let e of d){if(u>=a)break;let n=await da(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 da(e,t,n,r,i){let a=await ra(e,t,i),o=[];for(let e of a){let t=e.parts??[];for(let i of t){let t=fa(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 fa(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 pa(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 ma(e,t,n,r){let i=pa(t);try{let t=await n.session.prompt({path:{id:e},body:{noReply:!0,parts:[{type:`text`,text:i}]}});if(t.error!=null){r.warning(`SDK prompt writeback failed`,{sessionId:e,error:String(t.error)});return}r.info(`Session summary written via SDK`,{sessionId:e})}catch(t){r.warning(`SDK prompt writeback failed`,{sessionId:e,error:d(t)})}}async function ha(n){let{bootstrapLogger:r,reactionCtx:i,githubClient:a,agentSuccess:o,attachmentResult:s,serverHandle:c,detectedOpencodeVersion:l}=n;try{if(s!=null){let e=j({phase:`attachment-cleanup`});await zi(s.tempFiles,e)}i!=null&&a!=null&&await or(a,i,o,j({phase:`cleanup`}));let n=j({phase:`prune`}),r=M();if(c!=null){let e=J(r),t=await sa(c.client,e,oa,n);t.prunedCount>0&&n.info(`Pruned old sessions`,{pruned:t.prunedCount,remaining:t.remainingCount})}let u=T(),d=j({phase:`cache-save`}),f=L.join(r,`.git`,`opencode`);if(await oe({components:u,runId:O(),logger:d,storagePath:ne(),authPath:ae(),projectIdPath:f,opencodeVersion:l})&&t(e.CACHE_SAVED,`true`),ge()){let n=j({phase:`artifact-upload`});await de({logPath:k(),runId:O(),runAttempt:te(),logger:n})&&t(e.ARTIFACT_UPLOADED,`true`)}}catch(e){r.warning(`Cleanup failed (non-fatal)`,{error:e instanceof Error?e.message:String(e)})}finally{if(c!=null)try{c.shutdown()}catch(e){r.warning(`Server shutdown failed (non-fatal)`,{error:e instanceof Error?e.message:String(e)})}}}const ga=L.join(ke.homedir(),`.cache`,`fro-bot-dedup`);function _a(e){return e.replaceAll(`/`,`-`)}function va(e,t){let n=_a(e);return L.join(ga,`${n}-${t.entityType}-${t.entityNumber}`)}function ya(e,t){return`${E}-${_a(e)}-${t.entityType}-${t.entityNumber}-`}function ba(e,t,n){return`${ya(e,t)}${n}`}async function xa(e,t,n,r=A){let i=va(e,t),a=L.join(i,`sentinel.json`),o=ya(e,t);try{if(await I.rm(i,{recursive:!0,force:!0}),await I.mkdir(i,{recursive:!0}),await r.restoreCache([i],o,[])==null)return null;let e=await I.readFile(a,`utf8`);return JSON.parse(e)}catch(e){return n.debug(`Dedup marker restore failed; proceeding without marker`,{error:d(e),entityType:t.entityType,entityNumber:t.entityNumber}),null}}async function Sa(e,t,n,r,i=A){let a=va(e,t),o=L.join(a,`sentinel.json`),s=ba(e,t,n.runId);try{return await I.mkdir(a,{recursive:!0}),await I.writeFile(o,JSON.stringify(n),`utf8`),await i.saveCache([a],s),!0}catch(e){return d(e).toLowerCase().includes(`already exists`)?!0:(r.debug(`Dedup marker save failed`,{error:d(e),entityType:t.entityType,entityNumber:t.entityNumber,saveKey:s}),!1)}}const Ca=new Set([`pull_request`,`issues`]),wa=new Set([`synchronize`,`reopened`]);function Ta(e){return e.target==null||!Ca.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 Ea(e,t,n,r,i=j({phase:`dedup`}),a){let o=Ta(t);if(e===0)return{shouldProceed:!0,entity:o};if(o==null)return{shouldProceed:!0,entity:null};if(t.action!=null&&wa.has(t.action))return i.debug(`Dedup bypassed for action`,{action:t.action}),{shouldProceed:!0,entity:o};let s=await xa(n,o,i,a);if(s==null||s.runId===t.runId)return{shouldProceed:!0,entity:o};let c=new Date(s.timestamp).getTime();if(Number.isNaN(c))return i.warning(`Dedup marker timestamp is invalid; proceeding without dedup`,{markerTimestamp:s.timestamp}),{shouldProceed:!0,entity:o};let l=Date.now()-c;if(l<-6e4)return i.warning(`Dedup marker timestamp is too far in the future; proceeding without dedup`,{markerTimestamp:s.timestamp,markerAge:l}),{shouldProceed:!0,entity:o};let u=Math.max(0,l);return u>e?{shouldProceed:!0,entity:o}:(i.info(`Skipping duplicate trigger within dedup window`,{eventType:t.eventType,action:t.action,runId:t.runId,markerRunId:s.runId,markerTimestamp:s.timestamp,dedupWindow:e,entityType:o.entityType,entityNumber:o.entityNumber}),B({sessionId:null,cacheStatus:`miss`,duration:Date.now()-r}),await Oa(t,o,s,u,e,i),{shouldProceed:!1,entity:o})}async function Da(e,t,n,r=j({phase:`dedup`}),i){await Sa(n,t,{timestamp:new Date().toISOString(),runId:e.runId,action:e.action??`unknown`,eventType:e.eventType,entityType:t.entityType,entityNumber:t.entityNumber},r,i)}async function Oa(e,t,n,r,i,a){try{let a=Math.round(r/1e3),o=Math.round(i/1e3),s=`${t.entityType} #${t.entityNumber}`,c=`https://github.com/${e.repo.owner}/${e.repo.repo}/actions/runs/${n.runId}`;S.addHeading(`Fro Bot Agent Run — Skipped (Dedup)`,2).addRaw(`Execution skipped because the agent already ran for **${s}** recently.\n\n`).addTable([[{data:`Detail`,header:!0},{data:`Value`,header:!0}],[`Current action`,`\`${e.eventType}.${e.action??`unknown`}\``],[`Prior run`,`[${n.runId}](${c})`],[`Prior action`,`\`${n.eventType}.${n.action}\``],[`Time since prior run`,`${a}s`],[`Dedup window`,`${o}s`]]).addRaw(` > Dedup is best-effort suppression. Use workflow concurrency groups to prevent overlapping runs. -`),await S.write()}catch(e){a.warning(`Failed to write dedup skip summary`,{error:d(e)})}}async function Ea(n,r,i,a,o,s){let c={context:r.agentContext,customPrompt:n.inputs.prompt,cacheStatus:i.cacheStatus,sessionContext:{recentSessions:a.recentSessions,priorWorkContext:a.priorWorkContext},logicalKey:a.logicalKey??null,isContinuation:a.isContinuation,currentThreadSessionId:a.continueSessionId??null,triggerContext:r.triggerResult.context,fileParts:a.attachmentResult?.fileParts},l=L.env.SKIP_AGENT_EXECUTION===`true`,u=Date.now(),d;if(l)n.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 e=P({phase:`execution`});e.info(`Starting OpenCode execution`,{logicalKey:a.logicalKey?.key??null,continueSessionId:a.continueSessionId});let t=await Zn(c,e,{agent:n.inputs.agent,model:n.inputs.model,timeoutMs:n.inputs.timeoutMs,omoProviders:n.inputs.omoProviders,continueSessionId:a.continueSessionId??void 0,sessionTitle:a.sessionTitle??void 0},i.serverHandle),r=t.sessionId;if(r==null){let e=P({phase:`session`}),t=await ta(i.serverHandle.client,a.normalizedWorkspace,u,e);t!=null&&(r=t.session.id,e.debug(`Identified session from execution`,{sessionId:r}))}d={...t,sessionId:r},e.info(`Completed OpenCode execution`,{success:d.success,sessionId:d.sessionId,logicalKey:a.logicalKey?.key??null})}d.sessionId!=null&&(t(e.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 ka(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:d(e)}),null}}async function Aa(e,t,n,r){let i=[],a=1;for(;a<=50;)try{let{data:r}=await e.rest.issues.listComments({owner:t.owner,repo:t.repo,issue_number:t.number,per_page:100,page:a});if(r.length===0)break;for(let e of r){let t=e.user?.login??`unknown`;i.push({id:e.id,body:e.body??``,author:t,authorAssociation:e.author_association??`NONE`,createdAt:e.created_at,updatedAt:e.updated_at,isBot:Oa(t,e.body??``,n)})}if(r.length<100)break;a++}catch(e){r.warning(`Failed to fetch comments page`,{target:t,page:a,error:d(e)});break}return i}async function ja(e,t,n,r){try{let i=[],a=null,o=null,s=``,c=``,l=`unknown`,u=0;for(;u<50;){let d=(await e.graphql(` +`),await S.write()}catch(e){a.warning(`Failed to write dedup skip summary`,{error:d(e)})}}async function ka(n,r,i,a,o,s){let c={context:r.agentContext,customPrompt:n.inputs.prompt,cacheStatus:i.cacheStatus,sessionContext:{recentSessions:a.recentSessions,priorWorkContext:a.priorWorkContext},logicalKey:a.logicalKey??null,isContinuation:a.isContinuation,currentThreadSessionId:a.continueSessionId??null,triggerContext:r.triggerResult.context,fileParts:a.attachmentResult?.fileParts},l=N.env.SKIP_AGENT_EXECUTION===`true`,u=Date.now(),d;if(l)n.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 e=j({phase:`execution`});e.info(`Starting OpenCode execution`,{logicalKey:a.logicalKey?.key??null,continueSessionId:a.continueSessionId});let t=await Zn(c,e,{agent:n.inputs.agent,model:n.inputs.model,timeoutMs:n.inputs.timeoutMs,omoProviders:n.inputs.omoProviders,continueSessionId:a.continueSessionId??void 0,sessionTitle:a.sessionTitle??void 0},i.serverHandle),r=t.sessionId;if(r==null){let e=j({phase:`session`}),t=await ia(i.serverHandle.client,a.normalizedWorkspace,u,e);t!=null&&(r=t.session.id,e.debug(`Identified session from execution`,{sessionId:r}))}d={...t,sessionId:r},e.info(`Completed OpenCode execution`,{success:d.success,sessionId:d.sessionId,logicalKey:a.logicalKey?.key??null})}d.sessionId!=null&&(t(e.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 Ma(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:d(e)}),null}}async function Na(e,t,n,r){let i=[],a=1;for(;a<=50;)try{let{data:r}=await e.rest.issues.listComments({owner:t.owner,repo:t.repo,issue_number:t.number,per_page:100,page:a});if(r.length===0)break;for(let e of r){let t=e.user?.login??`unknown`;i.push({id:e.id,body:e.body??``,author:t,authorAssociation:e.author_association??`NONE`,createdAt:e.created_at,updatedAt:e.updated_at,isBot:ja(t,e.body??``,n)})}if(r.length<100)break;a++}catch(e){r.warning(`Failed to fetch comments page`,{target:t,page:a,error:d(e)});break}return i}async function Pa(e,t,n,r){try{let i=[],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) { @@ -287,7 +287,7 @@ If you had completed the task, confirm the completion.`,u=r===1?e.fileParts:void } } } -`,{owner:t.owner,repo:t.repo,number:t.number,after:a})).repository.discussion;if(d==null)return r.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`;i.push({id:e.id,body:e.body,author:t,authorAssociation:`NONE`,createdAt:e.createdAt,updatedAt:e.updatedAt,isBot:Oa(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:i,discussionId:o??void 0}}catch(e){return r.warning(`Failed to fetch discussion`,{target:t,error:d(e)}),null}}async function Ma(e,t,n,r){if(t.type===`discussion`)return ja(e,t,n,r);let i=await ka(e,t,r);if(i==null)return null;let a=await Aa(e,t,n,r);return{type:t.type,number:t.number,title:i.title,body:i.body,author:i.author,comments:a}}function Na(e,t){let n=e.comments.filter(e=>Da(e.author,t)&&e.body.includes(``));return n.length===0?null:n.at(-1)??null}async function Pa(e,t,n,r){try{let{data:i}=await e.rest.issues.createComment({owner:t.owner,repo:t.repo,issue_number:t.number,body:n});return r.debug(`Created issue comment`,{commentId:i.id,target:t}),{commentId:i.id,created:!0,updated:!1,url:i.html_url}}catch(e){return r.warning(`Failed to create issue comment`,{target:t,error:d(e)}),null}}async function Fa(e,t,n,r,i){try{let{data:a}=await e.rest.issues.updateComment({owner:t.owner,repo:t.repo,comment_id:n,body:r});return i.debug(`Updated issue comment`,{commentId:a.id,target:t}),{commentId:a.id,created:!1,updated:!0,url:a.html_url}}catch(e){return i.warning(`Failed to update issue comment`,{target:t,commentId:n,error:d(e)}),null}}async function Ia(e,t,n,r){try{let i=(await e.graphql(` +`,{owner:t.owner,repo:t.repo,number:t.number,after:a})).repository.discussion;if(d==null)return r.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`;i.push({id:e.id,body:e.body,author:t,authorAssociation:`NONE`,createdAt:e.createdAt,updatedAt:e.updatedAt,isBot:ja(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:i,discussionId:o??void 0}}catch(e){return r.warning(`Failed to fetch discussion`,{target:t,error:d(e)}),null}}async function Fa(e,t,n,r){if(t.type===`discussion`)return Pa(e,t,n,r);let i=await Ma(e,t,r);if(i==null)return null;let a=await Na(e,t,n,r);return{type:t.type,number:t.number,title:i.title,body:i.body,author:i.author,comments:a}}function Ia(e,t){let n=e.comments.filter(e=>Aa(e.author,t)&&e.body.includes(``));return n.length===0?null:n.at(-1)??null}async function La(e,t,n,r){try{let{data:i}=await e.rest.issues.createComment({owner:t.owner,repo:t.repo,issue_number:t.number,body:n});return r.debug(`Created issue comment`,{commentId:i.id,target:t}),{commentId:i.id,created:!0,updated:!1,url:i.html_url}}catch(e){return r.warning(`Failed to create issue comment`,{target:t,error:d(e)}),null}}async function Ra(e,t,n,r,i){try{let{data:a}=await e.rest.issues.updateComment({owner:t.owner,repo:t.repo,comment_id:n,body:r});return i.debug(`Updated issue comment`,{commentId:a.id,target:t}),{commentId:a.id,created:!1,updated:!0,url:a.html_url}}catch(e){return i.warning(`Failed to update issue comment`,{target:t,commentId:n,error:d(e)}),null}}async function za(e,t,n,r){try{let i=(await e.graphql(` query GetDiscussionId($owner: String!, $repo: String!, $number: Int!) { repository(owner: $owner, name: $repo) { discussion(number: $number) { @@ -308,7 +308,7 @@ If you had completed the task, confirm the completion.`,u=r===1?e.fileParts:void } } } -`,{owner:t.owner,repo:t.repo,number:t.number})).repository.discussion;if(i==null)return r.warning(`Discussion not found`,{target:t}),null;if(n.updateExisting===!0&&n.botLogin!=null){let i=await Ma(e,t,n.botLogin,r);if(i!=null){let t=Na(i,n.botLogin);if(t!=null&&typeof t.id==`string`){let i=await e.graphql(` +`,{owner:t.owner,repo:t.repo,number:t.number})).repository.discussion;if(i==null)return r.warning(`Discussion not found`,{target:t}),null;if(n.updateExisting===!0&&n.botLogin!=null){let i=await Fa(e,t,n.botLogin,r);if(i!=null){let t=Ia(i,n.botLogin);if(t!=null&&typeof t.id==`string`){let i=await e.graphql(` mutation UpdateDiscussionComment($commentId: ID!, $body: String!) { updateDiscussionComment(input: {commentId: $commentId, body: $body}) { comment { id url } @@ -320,4 +320,4 @@ If you had completed the task, confirm the completion.`,u=r===1?e.fileParts:void comment { id url } } } -`,{discussionId:i.id,body:n.body});return r.debug(`Created discussion comment`,{discussionId:i.id}),{commentId:a.addDiscussionComment.comment.id,created:!0,updated:!1,url:a.addDiscussionComment.comment.url}}catch(e){return r.warning(`Failed to post discussion comment`,{target:t,error:d(e)}),null}}async function La(e,t,n,r){if(t.type===`discussion`)return Ia(e,t,n,r);if(n.updateExisting===!0&&n.botLogin!=null){let i=await Ma(e,t,n.botLogin,r);if(i!=null){let a=Na(i,n.botLogin);if(a!=null&&typeof a.id==`number`)return Fa(e,t,a.id,n.body,r)}}return Pa(e,t,n.body,r)}async function Ra(e,t,n,r,i,a,o){let s=Date.now()-a;if(Ne({sessionId:r.sessionId,cacheStatus:n.cacheStatus,duration:s}),await je({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:i.getMetrics(),agent:e.inputs.agent},o),r.success)return o.info(`Agent run completed successfully`,{durationMs:s}),0;if(r.llmError==null)return b(`Agent execution failed with exit code ${r.exitCode}`),r.exitCode;o.info(`Agent failed with recoverable LLM error`,{error:r.llmError.message,type:r.llmError.type,durationMs:s});let[c,l]=t.agentContext.repo.split(`/`),u={type:t.triggerResult.context.eventType===`discussion_comment`?`discussion`:t.agentContext.issueType===`pr`?`pr`:`issue`,number:t.agentContext.issueNumber??0,owner:c??``,repo:l??``};if(u.number>0&&u.owner.length>0&&u.repo.length>0){let e=pn(r.llmError),n=P({phase:`error-comment`}),a=await La(t.githubClient,u,{body:e},n);a==null?n.warning(`Failed to post LLM error comment`):(n.info(`Posted LLM error comment`,{commentUrl:a.url}),i.incrementComments())}else o.warning(`Cannot post error comment: missing target context`);return 0}function za(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 Ba(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 Va(e){let t=ee,n=za(t.eventName),r=Ba(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 Ha(e,t){return t.includes(e)}function X(e){return e.endsWith(`[bot]`)}function Ua(e,t){if(t.length===0)return!1;let n=t.replace(/\[bot\]$/i,``);return n.length===0?!1:new RegExp(String.raw`@${Wa(n)}(?:\[bot\])?(?:$|[^\w])`,`i`).test(e)}function Wa(e){return e.replaceAll(/[.*+?^${}()|[\]\\]/g,String.raw`\$&`)}function Ga(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`@${Wa(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=Ua(e,t);return{hasMention:n,command:n?Ga(e,t):null}}function Ka(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 qa(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 Ja(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 Ya(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 Xa(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 Za(e){return e.toLowerCase().replace(/\[bot\]$/i,``)}function Qa(e,t){if(e.type!==`pull_request`||t==null||t===``)return!1;let n=Za(t);if(n===``)return!1;if(e.action===`review_requested`){let t=e.requestedReviewer?.login;return t!=null&&Za(t)===n}return e.action===`ready_for_review`?e.pullRequest.requestedReviewers.some(e=>Za(e.login)===n):!1}function $a(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 eo(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:Qa(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 to(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,Ka(e.event,t));case`discussion_comment`:return Q(r,qa(e.event,t));case`workflow_dispatch`:return Q(r,Ya(e.event,e.actor,n));case`issues`:return Q(r,$a(e.event,t));case`pull_request`:return Q(r,eo(e.event,t));case`pull_request_review_comment`:return Q(r,Ja(e.event,t));case`schedule`:return Q(r,Xa(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 no(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&&!Ha(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 ro(e,t){return no(e,t,{targetLabel:`Issue or PR`,actionLabel:`Comment`})}function io(e,t){return no(e,t,{targetLabel:`Discussion`,actionLabel:`Discussion comment`})}const ao=[`opened`,`edited`];function oo(e){return ao.includes(e)}function so(e,t){let n=e.action;return n==null||!oo(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&&!Ha(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 co(e){return(e.promptInput?.trim()??``)===``?{shouldSkip:!0,reason:`prompt_required`,message:`Schedule trigger requires prompt input`}:{shouldSkip:!1}}function lo(e){return(e.commentBody?.trim()??``)===``?{shouldSkip:!0,reason:`prompt_required`,message:`Workflow dispatch requires prompt input`}:{shouldSkip:!1}}const uo=[`opened`,`synchronize`,`reopened`,`ready_for_review`,`review_requested`];function fo(e){return uo.includes(e)}function po(e,t){let n=e.action;return n==null||!fo(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&&!Ha(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 mo(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&&!Ha(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 ho(e){return{shouldSkip:!0,reason:`unsupported_event`,message:`Unsupported event type: ${e}`}}function go(e,t,n){if(e.eventType===`unsupported`)return n.debug(`Skipping unsupported event`,{eventName:e.eventName}),ho(e.eventName);switch(e.eventType){case`issue_comment`:return ro(e,t);case`discussion_comment`:return io(e,t);case`issues`:return so(e,t);case`pull_request`:return po(e,t);case`pull_request_review_comment`:return mo(e,t);case`schedule`:return co(t);case`workflow_dispatch`:return lo(e);default:return{shouldSkip:!1}}}const _o={botLogin:null,requireMention:!0,allowedAssociations:di,skipDraftPRs:!0,promptInput:null,senderAssociation:null};function vo(e,t,n={}){let r={..._o,...n},i=to(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=go(i,r,t);return a.shouldSkip?{shouldProcess:!1,skipReason:a.reason,skipMessage:a.message,context:i}:{shouldProcess:!0,context:i}}async function yo(n,r){let i=P({phase:`context`}),a=Va(i),o=Lr({token:n.inputs.githubToken,logger:i}),s=await Rr(o,i),c=null;if(a.event.type===`pull_request`&&(a.event.action===`review_requested`||a.event.action===`ready_for_review`)){let{owner:e,repo:t}=a.repo;c=await He(o,e,t,a.event.sender.login,i)}let l=P({phase:`trigger`}),u=vo(a,l,{botLogin:s,requireMention:!0,promptInput:n.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}),t(e.SHOULD_SAVE_CACHE,`true`),{githubClient:o,triggerResult:u,agentContext:await ot({logger:i,octokit:o,triggerContext:u.context,botLogin:s}),botLogin:s}):(l.info(`Skipping event`,{reason:u.skipReason,message:u.skipMessage}),Ne({sessionId:null,cacheStatus:`miss`,duration:Date.now()-r}),null)}function $(e,t){return{key:`${e}-${t}`,entityType:e,entityId:t}}function bo(e){return be(`sha256`).update(e).digest(`hex`).slice(0,8)}function xo(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`,bo((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 So(e){return`fro-bot: ${e.key}`}function Co(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 wo(e,t,n,r){try{let i=Co(await $i(e,t,r),So(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 To(e,t,n,r){let i=P({phase:`session`}),a=J(I()),o=await aa(n.serverHandle.client,a,{limit:10},i);i.debug(`Listed recent sessions`,{count:o.length});let s=xo(t.triggerResult.context),c=s==null?null:So(s),l=null,u=!1;if(s!=null){let e=await wo(n.serverHandle.client,a,s,i);e.status===`found`?(l=e.session.id,u=!0,i.info(`Session continuity: found existing session`,{logicalKey:s.key,sessionId:l})):e.status===`error`?i.warning(`Session continuity: lookup error, will create new`,{logicalKey:s.key,error:e.error}):i.info(`Session continuity: no existing session found`,{logicalKey:s.key})}let d=s?.key??t.agentContext.issueTitle??t.agentContext.repo,f=await sa(d,n.serverHandle.client,a,{limit:5},i);i.debug(`Searched prior sessions`,{query:d,resultCount:f.length});for(let e of f)r.addSessionUsed(e.sessionId);let p=P({phase:`attachments`}),m=t.agentContext.commentBody??``,h=Ai(m),g=null;if(h.length>0){p.info(`Processing attachments`,{count:h.length});let{validated:t,skipped:n}=Vi(await Fi(h,e.inputs.githubToken,void 0,p),void 0,p);(t.length>0||n.length>0)&&(g=zi(m,h,t,n),p.info(`Attachments processed`,{processed:t.length,skipped:n.length}))}return{recentSessions:o,priorWorkContext:f,attachmentResult:g,normalizedWorkspace:a,logicalKey:s,continueSessionId:l,isContinuation:u,sessionTitle:c}}async function Eo(){let n=Date.now(),r=P({phase:`bootstrap`}),i=Me();i.start();let a=null,o=!1,s=0,c=null,l=null,u=null,d=null;t(e.SHOULD_SAVE_CACHE,`false`),t(e.CACHE_SAVED,`false`);try{r.info(`Starting Fro Bot Agent`);let e=await Ci(r);if(e==null)return 1;u=e.opencodeResult.version;let t=await yo(e,n);if(t==null)return 0;c=t.githubClient;let f=`${t.triggerResult.context.repo.owner}/${t.triggerResult.context.repo.repo}`,p=await Ca(e.inputs.dedupWindow,t.triggerResult.context,f,n);if(!p.shouldProceed)return 0;a=await hi(t,e.logger);let m=await Di(e);if(m==null)return 1;d=m.serverHandle,i.setCacheStatus(m.cacheStatus);let h=await To(e,t,m,i);l=h.attachmentResult;let g=await Ea(e,t,m,h,i,n);o=g.success,o&&p.entity!=null&&await wa(t.triggerResult.context,p.entity,f),i.end(),s=await Ra(e,t,m,g,i,n,e.logger)}catch(e){s=1;let t=Date.now()-n,a=e instanceof Error?e.name:`UnknownError`,o=e instanceof Error?e.message:String(e);i.recordError(a,o,!1),i.end(),Ne({sessionId:null,cacheStatus:`miss`,duration:t}),e instanceof Error?(r.error(`Agent failed`,{error:e.message}),b(e.message)):(r.error(`Agent failed with unknown error`),b(`An unknown error occurred`))}finally{await fa({bootstrapLogger:r,reactionCtx:a,githubClient:c,agentSuccess:o,attachmentResult:l,serverHandle:d,detectedOpencodeVersion:u})}return s}await Eo().then(e=>{L.exit(e)});export{}; \ No newline at end of file +`,{discussionId:i.id,body:n.body});return r.debug(`Created discussion comment`,{discussionId:i.id}),{commentId:a.addDiscussionComment.comment.id,created:!0,updated:!1,url:a.addDiscussionComment.comment.url}}catch(e){return r.warning(`Failed to post discussion comment`,{target:t,error:d(e)}),null}}async function Ba(e,t,n,r){if(t.type===`discussion`)return za(e,t,n,r);if(n.updateExisting===!0&&n.botLogin!=null){let i=await Fa(e,t,n.botLogin,r);if(i!=null){let a=Ia(i,n.botLogin);if(a!=null&&typeof a.id==`number`)return Ra(e,t,a.id,n.body,r)}}return La(e,t,n.body,r)}async function Va(e,t,n,r,i,a,o){let s=Date.now()-a;if(B({sessionId:r.sessionId,cacheStatus:n.cacheStatus,duration:s}),await Pe({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:i.getMetrics(),agent:e.inputs.agent},o),r.success)return o.info(`Agent run completed successfully`,{durationMs:s}),0;if(r.llmError==null)return b(`Agent execution failed with exit code ${r.exitCode}`),r.exitCode;o.info(`Agent failed with recoverable LLM error`,{error:r.llmError.message,type:r.llmError.type,durationMs:s});let[c,l]=t.agentContext.repo.split(`/`),u={type:t.triggerResult.context.eventType===`discussion_comment`?`discussion`:t.agentContext.issueType===`pr`?`pr`:`issue`,number:t.agentContext.issueNumber??0,owner:c??``,repo:l??``};if(u.number>0&&u.owner.length>0&&u.repo.length>0){let e=pn(r.llmError),n=j({phase:`error-comment`}),a=await Ba(t.githubClient,u,{body:e},n);a==null?n.warning(`Failed to post LLM error comment`):(n.info(`Posted LLM error comment`,{commentUrl:a.url}),i.incrementComments())}else o.warning(`Cannot post error comment: missing target context`);return 0}function Ha(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 Ua(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 Wa(e){let t=ie,n=Ha(t.eventName),r=Ua(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 Ga(e,t){return t.includes(e)}function X(e){return e.endsWith(`[bot]`)}function Ka(e,t){if(t.length===0)return!1;let n=t.replace(/\[bot\]$/i,``);return n.length===0?!1:new RegExp(String.raw`@${qa(n)}(?:\[bot\])?(?:$|[^\w])`,`i`).test(e)}function qa(e){return e.replaceAll(/[.*+?^${}()|[\]\\]/g,String.raw`\$&`)}function Ja(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`@${qa(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=Ka(e,t);return{hasMention:n,command:n?Ja(e,t):null}}function Ya(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 Xa(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 Za(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 Qa(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 $a(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 eo(e){return e.toLowerCase().replace(/\[bot\]$/i,``)}function to(e,t){if(e.type!==`pull_request`||t==null||t===``)return!1;let n=eo(t);if(n===``)return!1;if(e.action===`review_requested`){let t=e.requestedReviewer?.login;return t!=null&&eo(t)===n}return e.action===`ready_for_review`?e.pullRequest.requestedReviewers.some(e=>eo(e.login)===n):!1}function no(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 ro(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:to(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 io(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,Ya(e.event,t));case`discussion_comment`:return Q(r,Xa(e.event,t));case`workflow_dispatch`:return Q(r,Qa(e.event,e.actor,n));case`issues`:return Q(r,no(e.event,t));case`pull_request`:return Q(r,ro(e.event,t));case`pull_request_review_comment`:return Q(r,Za(e.event,t));case`schedule`:return Q(r,$a(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 ao(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&&!Ga(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 oo(e,t){return ao(e,t,{targetLabel:`Issue or PR`,actionLabel:`Comment`})}function so(e,t){return ao(e,t,{targetLabel:`Discussion`,actionLabel:`Discussion comment`})}const co=[`opened`,`edited`];function lo(e){return co.includes(e)}function uo(e,t){let n=e.action;return n==null||!lo(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&&!Ga(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 fo(e){return(e.promptInput?.trim()??``)===``?{shouldSkip:!0,reason:`prompt_required`,message:`Schedule trigger requires prompt input`}:{shouldSkip:!1}}function po(e){return(e.commentBody?.trim()??``)===``?{shouldSkip:!0,reason:`prompt_required`,message:`Workflow dispatch requires prompt input`}:{shouldSkip:!1}}const mo=[`opened`,`synchronize`,`reopened`,`ready_for_review`,`review_requested`];function ho(e){return mo.includes(e)}function go(e,t){let n=e.action;return n==null||!ho(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&&!Ga(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 _o(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&&!Ga(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 vo(e){return{shouldSkip:!0,reason:`unsupported_event`,message:`Unsupported event type: ${e}`}}function yo(e,t,n){if(e.eventType===`unsupported`)return n.debug(`Skipping unsupported event`,{eventName:e.eventName}),vo(e.eventName);switch(e.eventType){case`issue_comment`:return oo(e,t);case`discussion_comment`:return so(e,t);case`issues`:return uo(e,t);case`pull_request`:return go(e,t);case`pull_request_review_comment`:return _o(e,t);case`schedule`:return fo(t);case`workflow_dispatch`:return po(e);default:return{shouldSkip:!1}}}const bo={botLogin:null,requireMention:!0,allowedAssociations:mi,skipDraftPRs:!0,promptInput:null,senderAssociation:null};function xo(e,t,n={}){let r={...bo,...n},i=io(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=yo(i,r,t);return a.shouldSkip?{shouldProcess:!1,skipReason:a.reason,skipMessage:a.message,context:i}:{shouldProcess:!0,context:i}}async function So(n,r){let i=j({phase:`context`}),a=Wa(i),o=Br({token:n.inputs.githubToken,logger:i}),s=await Vr(o,i),c=null;if(a.event.type===`pull_request`&&(a.event.action===`review_requested`||a.event.action===`ready_for_review`)){let{owner:e,repo:t}=a.repo;c=await We(o,e,t,a.event.sender.login,i)}let l=j({phase:`trigger`}),u=xo(a,l,{botLogin:s,requireMention:!0,promptInput:n.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}),t(e.SHOULD_SAVE_CACHE,`true`),{githubClient:o,triggerResult:u,agentContext:await st({logger:i,octokit:o,triggerContext:u.context,botLogin:s}),botLogin:s}):(l.info(`Skipping event`,{reason:u.skipReason,message:u.skipMessage}),B({sessionId:null,cacheStatus:`miss`,duration:Date.now()-r}),null)}function $(e,t){return{key:`${e}-${t}`,entityType:e,entityId:t}}function Co(e){return we(`sha256`).update(e).digest(`hex`).slice(0,8)}function wo(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`,Co((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 To(e){return`fro-bot: ${e.key}`}function Eo(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 Do(e,t,n,r){try{let i=Eo(await na(e,t,r),To(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 Oo(e,t,n,r){let i=j({phase:`session`}),a=J(M()),o=await ca(n.serverHandle.client,a,{limit:10},i);i.debug(`Listed recent sessions`,{count:o.length});let s=wo(t.triggerResult.context),c=s==null?null:To(s),l=null,u=!1;if(s!=null){let e=await Do(n.serverHandle.client,a,s,i);e.status===`found`?(l=e.session.id,u=!0,i.info(`Session continuity: found existing session`,{logicalKey:s.key,sessionId:l})):e.status===`error`?i.warning(`Session continuity: lookup error, will create new`,{logicalKey:s.key,error:e.error}):i.info(`Session continuity: no existing session found`,{logicalKey:s.key})}let d=s?.key??t.agentContext.issueTitle??t.agentContext.repo,f=await ua(d,n.serverHandle.client,a,{limit:5},i);i.debug(`Searched prior sessions`,{query:d,resultCount:f.length});for(let e of f)r.addSessionUsed(e.sessionId);let p=j({phase:`attachments`}),m=t.agentContext.commentBody??``,h=Ni(m),g=null;if(h.length>0){p.info(`Processing attachments`,{count:h.length});let{validated:t,skipped:n}=Wi(await Ri(h,e.inputs.githubToken,void 0,p),void 0,p);(t.length>0||n.length>0)&&(g=Hi(m,h,t,n),p.info(`Attachments processed`,{processed:t.length,skipped:n.length}))}return{recentSessions:o,priorWorkContext:f,attachmentResult:g,normalizedWorkspace:a,logicalKey:s,continueSessionId:l,isContinuation:u,sessionTitle:c}}async function ko(){let n=Date.now(),r=j({phase:`bootstrap`}),i=Fe();i.start();let a=null,o=!1,s=0,c=null,l=null,u=null,d=null;t(e.SHOULD_SAVE_CACHE,`false`),t(e.CACHE_SAVED,`false`);try{r.info(`Starting Fro Bot Agent`);let e=await Ei(r);if(e==null)return 1;u=e.opencodeResult.version;let t=await So(e,n);if(t==null)return 0;c=t.githubClient;let f=`${t.triggerResult.context.repo.owner}/${t.triggerResult.context.repo.repo}`,p=await Ea(e.inputs.dedupWindow,t.triggerResult.context,f,n);if(!p.shouldProceed)return 0;a=await vi(t,e.logger);let m=await Ai(e);if(m==null)return 1;d=m.serverHandle,i.setCacheStatus(m.cacheStatus);let h=await Oo(e,t,m,i);l=h.attachmentResult;let g=await ka(e,t,m,h,i,n);o=g.success,o&&p.entity!=null&&await Da(t.triggerResult.context,p.entity,f),i.end(),s=await Va(e,t,m,g,i,n,e.logger)}catch(e){s=1;let t=Date.now()-n,a=e instanceof Error?e.name:`UnknownError`,o=e instanceof Error?e.message:String(e);i.recordError(a,o,!1),i.end(),B({sessionId:null,cacheStatus:`miss`,duration:t}),e instanceof Error?(r.error(`Agent failed`,{error:e.message}),b(e.message)):(r.error(`Agent failed with unknown error`),b(`An unknown error occurred`))}finally{await ha({bootstrapLogger:r,reactionCtx:a,githubClient:c,agentSuccess:o,attachmentResult:l,serverHandle:d,detectedOpencodeVersion:u})}return s}await ko().then(e=>{N.exit(e)});export{}; \ No newline at end of file diff --git a/src/features/agent/opencode.test.ts b/src/features/agent/opencode.test.ts index 2c2fee5e..43b38bdf 100644 --- a/src/features/agent/opencode.test.ts +++ b/src/features/agent/opencode.test.ts @@ -1054,6 +1054,7 @@ describe('ensureOpenCodeAvailable', () => { kimiForCoding: 'no', }, opencodeConfig: null, + systematicConfig: null, }) // #then @@ -1087,6 +1088,7 @@ describe('ensureOpenCodeAvailable', () => { kimiForCoding: 'no', }, opencodeConfig: null, + systematicConfig: null, }) } catch { // Expected to fail since runSetup will fail in test environment diff --git a/src/features/agent/server.ts b/src/features/agent/server.ts index 19d9a7e2..9164a4cc 100644 --- a/src/features/agent/server.ts +++ b/src/features/agent/server.ts @@ -64,14 +64,15 @@ export async function verifyOpenCodeAvailable( } export interface EnsureOpenCodeOptions { - logger: Logger - opencodeVersion: string - githubToken: string - authJson: string - omoVersion: string - systematicVersion: string - omoProviders: SetupInputs['omoProviders'] - opencodeConfig: string | null + readonly logger: Logger + readonly opencodeVersion: string + readonly githubToken: string + readonly authJson: string + readonly omoVersion: string + readonly systematicVersion: string + readonly omoProviders: SetupInputs['omoProviders'] + readonly opencodeConfig: string | null + readonly systematicConfig: string | null } export async function ensureOpenCodeAvailable(options: EnsureOpenCodeOptions): Promise { @@ -91,6 +92,7 @@ export async function ensureOpenCodeAvailable(options: EnsureOpenCodeOptions): P appId: null, privateKey: null, opencodeConfig: options.opencodeConfig, + systematicConfig: options.systematicConfig, omoConfig: null, omoVersion: options.omoVersion, systematicVersion: options.systematicVersion, diff --git a/src/harness/config/inputs.test.ts b/src/harness/config/inputs.test.ts index de0fdb52..364b1111 100644 --- a/src/harness/config/inputs.test.ts +++ b/src/harness/config/inputs.test.ts @@ -575,6 +575,42 @@ describe('parseActionInputs', () => { expect(result.success).toBe(true) expect(result.success && result.data.opencodeConfig).toBe(null) }) + + it('parses systematic-config when provided', () => { + const mockGetInput = core.getInput as ReturnType + + mockGetInput.mockImplementation((name: string) => { + const inputs: Record = { + 'github-token': 'ghp_test123', + 'auth-json': '{"anthropic":{"type":"api","key":"sk-ant-test"}}', + 'systematic-config': '{"mode":"strict"}', + } + return inputs[name] ?? '' + }) + + const result = parseActionInputs() + + expect(result.success).toBe(true) + expect(result.success && result.data.systematicConfig).toBe('{"mode":"strict"}') + }) + + it('sets systematicConfig to null when empty string', () => { + const mockGetInput = core.getInput as ReturnType + + mockGetInput.mockImplementation((name: string) => { + const inputs: Record = { + 'github-token': 'ghp_test123', + 'auth-json': '{"anthropic":{"type":"api","key":"sk-ant-test"}}', + 'systematic-config': '', + } + return inputs[name] ?? '' + }) + + const result = parseActionInputs() + + expect(result.success).toBe(true) + expect(result.success && result.data.systematicConfig).toBe(null) + }) }) }) diff --git a/src/harness/config/inputs.ts b/src/harness/config/inputs.ts index 2aa5a8fb..9ab466c7 100644 --- a/src/harness/config/inputs.ts +++ b/src/harness/config/inputs.ts @@ -200,6 +200,9 @@ export function parseActionInputs(): Result { const opencodeConfigRaw = core.getInput('opencode-config').trim() const opencodeConfig = opencodeConfigRaw.length > 0 ? opencodeConfigRaw : null + const systematicConfigRaw = core.getInput('systematic-config').trim() + const systematicConfig = systematicConfigRaw.length > 0 ? systematicConfigRaw : null + const dedupWindowRaw = core.getInput('dedup-window').trim() const dedupWindow = dedupWindowRaw.length > 0 ? parseTimeoutMs(dedupWindowRaw, 'dedup-window') : DEFAULT_DEDUP_WINDOW_MS @@ -235,6 +238,7 @@ export function parseActionInputs(): Result { systematicVersion, omoProviders, opencodeConfig, + systematicConfig, dedupWindow, }) } catch (error) { diff --git a/src/harness/phases/bootstrap.ts b/src/harness/phases/bootstrap.ts index bfc6c83f..3ceba644 100644 --- a/src/harness/phases/bootstrap.ts +++ b/src/harness/phases/bootstrap.ts @@ -43,6 +43,7 @@ export async function runBootstrap(bootstrapLogger: Logger): Promise { + it('returns autoupdate baseline with systematic plugin when no user config', () => { + // #given + const logger = createLogger() + + // #when + const result = buildCIConfig({opencodeConfig: null, systematicVersion: '2.1.0'}, logger) + + // #then + expect(result.error).toBeNull() + expect(result.config).toEqual({autoupdate: false, plugins: ['@fro.bot/systematic@2.1.0']}) + }) + + it('merges user config keys and appends systematic plugin', () => { + // #given + const logger = createLogger() + + // #when + const result = buildCIConfig( + {opencodeConfig: '{"model":"claude-opus-4-5","autoupdate":true}', systematicVersion: '2.1.0'}, + logger, + ) + + // #then + expect(result.error).toBeNull() + expect(result.config).toEqual({ + autoupdate: true, + model: 'claude-opus-4-5', + plugins: ['@fro.bot/systematic@2.1.0'], + }) + }) + + it('appends systematic plugin to existing plugins array', () => { + // #given + const logger = createLogger() + + // #when + const result = buildCIConfig( + {opencodeConfig: '{"plugins":["custom-plugin@1.0.0"]}', systematicVersion: '2.1.0'}, + logger, + ) + + // #then + expect(result.error).toBeNull() + expect(result.config).toEqual({ + autoupdate: false, + plugins: ['custom-plugin@1.0.0', '@fro.bot/systematic@2.1.0'], + }) + }) + + it('does not duplicate systematic plugin when already present', () => { + // #given + const logger = createLogger() + + // #when + const result = buildCIConfig( + { + opencodeConfig: '{"plugins":["custom-plugin@1.0.0","@fro.bot/systematic@9.9.9"]}', + systematicVersion: '2.1.0', + }, + logger, + ) + + // #then + expect(result.error).toBeNull() + expect(result.config).toEqual({ + autoupdate: false, + plugins: ['custom-plugin@1.0.0', '@fro.bot/systematic@9.9.9'], + }) + }) + + it('returns error for invalid JSON', () => { + // #given + const logger = createLogger() + + // #when + const result = buildCIConfig({opencodeConfig: '{invalid-json}', systematicVersion: '2.1.0'}, logger) + + // #then + expect(result.error).toBe('opencode-config must be valid JSON') + }) + + it('returns error for non-object JSON values', () => { + // #given + const logger = createLogger() + + // #when + const nullResult = buildCIConfig({opencodeConfig: 'null', systematicVersion: '2.1.0'}, logger) + const arrayResult = buildCIConfig({opencodeConfig: '[1,2,3]', systematicVersion: '2.1.0'}, logger) + const numberResult = buildCIConfig({opencodeConfig: '42', systematicVersion: '2.1.0'}, logger) + const stringResult = buildCIConfig({opencodeConfig: '"hello"', systematicVersion: '2.1.0'}, logger) + + // #then + expect(nullResult.error).toBe('opencode-config must be a JSON object') + expect(arrayResult.error).toBe('opencode-config must be a JSON object') + expect(numberResult.error).toBe('opencode-config must be a JSON object') + expect(stringResult.error).toBe('opencode-config must be a JSON object') + }) +}) diff --git a/src/services/setup/ci-config.ts b/src/services/setup/ci-config.ts new file mode 100644 index 00000000..a4f73f16 --- /dev/null +++ b/src/services/setup/ci-config.ts @@ -0,0 +1,43 @@ +import type {Logger, SetupInputs} from './types.js' + +export interface CIConfigResult { + readonly config: Record + readonly error: string | null +} + +export function buildCIConfig( + inputs: Pick, + logger: Logger, +): CIConfigResult { + const ciConfig: Record = {autoupdate: false} + + if (inputs.opencodeConfig != null) { + let parsed: unknown + try { + parsed = JSON.parse(inputs.opencodeConfig) + } catch { + return {config: ciConfig, error: 'opencode-config must be valid JSON'} + } + + if (parsed == null || typeof parsed !== 'object' || Array.isArray(parsed)) { + return {config: ciConfig, error: 'opencode-config must be a JSON object'} + } + Object.assign(ciConfig, parsed) + } + + const systematicPlugin = `@fro.bot/systematic@${inputs.systematicVersion}` + const rawPlugins: unknown[] = Array.isArray(ciConfig.plugins) ? (ciConfig.plugins as unknown[]) : [] + const hasSystematic = rawPlugins.some( + (plugin): plugin is string => typeof plugin === 'string' && plugin.startsWith('@fro.bot/systematic'), + ) + if (!hasSystematic) { + ciConfig.plugins = [...rawPlugins, systematicPlugin] + } + + logger.debug('Built CI OpenCode config', { + hasUserConfig: inputs.opencodeConfig != null, + pluginCount: Array.isArray(ciConfig.plugins) ? ciConfig.plugins.length : 0, + }) + + return {config: ciConfig, error: null} +} diff --git a/src/services/setup/index.ts b/src/services/setup/index.ts index 9a91d367..11c3700a 100644 --- a/src/services/setup/index.ts +++ b/src/services/setup/index.ts @@ -1,11 +1,15 @@ // Setup module public exports +export {createExecAdapter, createToolCacheAdapter} from './adapters.js' export {parseAuthJsonInput, populateAuthJson} from './auth-json.js' export {buildBunDownloadUrl, getBunPlatformInfo, installBun, isBunAvailable} from './bun.js' export type {BunInstallResult, BunPlatformInfo} from './bun.js' +export {buildCIConfig} from './ci-config.js' +export type {CIConfigResult} from './ci-config.js' export {configureGhAuth, configureGitIdentity, getBotUserId} from './gh-auth.js' export {installOmo, verifyOmoInstallation} from './omo.js' export type {OmoInstallDeps, OmoInstallOptions} from './omo.js' export {getLatestVersion, installOpenCode} from './opencode.js' +export {writeSystematicConfig} from './systematic-config.js' export {restoreToolsCache, saveToolsCache} from './tools-cache.js' export type {ToolsCacheAdapter, ToolsCacheResult} from './tools-cache.js' diff --git a/src/services/setup/setup.test.ts b/src/services/setup/setup.test.ts index 6b16c10a..179e584d 100644 --- a/src/services/setup/setup.test.ts +++ b/src/services/setup/setup.test.ts @@ -16,6 +16,7 @@ function createSetupInputs(overrides: Partial = {}): SetupInputs { appId: null, privateKey: null, opencodeConfig: null, + systematicConfig: null, omoConfig: null, omoVersion: '3.7.4', systematicVersion: '2.1.0', @@ -88,6 +89,20 @@ vi.mock('./tools-cache.js', () => ({ saveToolsCache: vi.fn(), })) +vi.mock('./adapters.js', () => ({ + createToolCacheAdapter: vi.fn(() => ({ + find: vi.mocked(tc.find), + downloadTool: vi.mocked(tc.downloadTool), + extractTar: vi.mocked(tc.extractTar), + extractZip: vi.mocked(tc.extractZip), + cacheDir: vi.mocked(tc.cacheDir), + })), + createExecAdapter: vi.fn(() => ({ + exec: vi.mocked(exec.exec), + getExecOutput: vi.mocked(exec.getExecOutput), + })), +})) + // Mock bun module vi.mock('./bun.js', () => ({ installBun: vi.fn().mockResolvedValue({path: '/cached/bun', version: '1.3.5', cached: true}), @@ -245,8 +260,8 @@ describe('setup', () => { expect(core.exportVariable).toHaveBeenCalledWith('GH_TOKEN', 'ghs_test_token') }) - it('exports OPENCODE_CONFIG_CONTENT with autoupdate:false baseline and Systematic plugin', async () => { - // #given - no opencode-config input + it('exports OPENCODE_CONFIG_CONTENT environment variable', async () => { + // #given vi.mocked(tc.find).mockReturnValue('/cached/opencode/1.0.300') vi.mocked(exec.getExecOutput).mockResolvedValue({exitCode: 0, stdout: '', stderr: ''}) vi.mocked(exec.exec).mockResolvedValue(0) @@ -258,60 +273,7 @@ describe('setup', () => { await runSetup(createSetupInputs(), 'ghs_test_token') // #then - expect(core.exportVariable).toHaveBeenCalledWith( - 'OPENCODE_CONFIG_CONTENT', - JSON.stringify({autoupdate: false, plugins: ['@fro.bot/systematic@2.1.0']}), - ) - }) - - it('merges user opencode-config input on top of OPENCODE_CONFIG_CONTENT baseline', async () => { - // #given - user supplies opencode-config with custom settings - vi.mocked(tc.find).mockReturnValue('/cached/opencode/1.0.300') - vi.mocked(exec.getExecOutput).mockResolvedValue({exitCode: 0, stdout: '', stderr: ''}) - vi.mocked(exec.exec).mockResolvedValue(0) - vi.mocked(fs.writeFile).mockResolvedValue() - vi.mocked(fs.mkdir).mockResolvedValue(undefined) - vi.mocked(fs.access).mockRejectedValue(new Error('not found')) - - // #when - await runSetup( - createSetupInputs({opencodeConfig: '{"model": "claude-opus-4-5", "autoupdate": true}'}), - 'ghs_test_token', - ) - - // #then - user config wins on conflicting keys; Systematic plugin appended - expect(core.exportVariable).toHaveBeenCalledWith( - 'OPENCODE_CONFIG_CONTENT', - JSON.stringify({autoupdate: true, model: 'claude-opus-4-5', plugins: ['@fro.bot/systematic@2.1.0']}), - ) - }) - - it('preserves user-provided Systematic plugin entry without appending a duplicate', async () => { - // #given - user already pins Systematic in opencode-config - vi.mocked(tc.find).mockReturnValue('/cached/opencode/1.0.300') - vi.mocked(exec.getExecOutput).mockResolvedValue({exitCode: 0, stdout: '', stderr: ''}) - vi.mocked(exec.exec).mockResolvedValue(0) - vi.mocked(fs.writeFile).mockResolvedValue() - vi.mocked(fs.mkdir).mockResolvedValue(undefined) - vi.mocked(fs.access).mockRejectedValue(new Error('not found')) - - // #when - await runSetup( - createSetupInputs({ - opencodeConfig: '{"plugins": ["custom-plugin@1.0.0", "@fro.bot/systematic@9.9.9"]}', - systematicVersion: '2.1.0', - }), - 'ghs_test_token', - ) - - // #then - existing Systematic entry wins and is not duplicated - expect(core.exportVariable).toHaveBeenCalledWith( - 'OPENCODE_CONFIG_CONTENT', - JSON.stringify({ - autoupdate: false, - plugins: ['custom-plugin@1.0.0', '@fro.bot/systematic@9.9.9'], - }), - ) + expect(core.exportVariable).toHaveBeenCalledWith('OPENCODE_CONFIG_CONTENT', expect.any(String)) }) it('fails when opencode-config parses to JSON null', async () => { @@ -348,27 +310,6 @@ describe('setup', () => { expect(core.setFailed).toHaveBeenCalledWith('opencode-config must be valid JSON') }) - it('treats whitespace-only opencode-config as not provided', async () => { - // #given - vi.mocked(tc.find).mockReturnValue('/cached/opencode/1.0.300') - vi.mocked(exec.getExecOutput).mockResolvedValue({exitCode: 0, stdout: '', stderr: ''}) - vi.mocked(exec.exec).mockResolvedValue(0) - vi.mocked(fs.writeFile).mockResolvedValue() - vi.mocked(fs.mkdir).mockResolvedValue(undefined) - vi.mocked(fs.access).mockRejectedValue(new Error('not found')) - - // #when - const result = await runSetup(createSetupInputs(), 'ghs_test_token') - - // #then - expect(result).not.toBeNull() - expect(core.setFailed).not.toHaveBeenCalled() - expect(core.exportVariable).toHaveBeenCalledWith( - 'OPENCODE_CONFIG_CONTENT', - JSON.stringify({autoupdate: false, plugins: ['@fro.bot/systematic@2.1.0']}), - ) - }) - it('fails when opencode-config is an array', async () => { // #given - user supplies array as opencode-config vi.mocked(tc.find).mockReturnValue('/cached/opencode/1.0.300') @@ -584,6 +525,7 @@ describe('setup', () => { expect(callArgs).toBeDefined() expect(callArgs?.toolCachePath).toContain('opencode') expect(callArgs?.omoConfigPath).toContain('opencode') + expect(callArgs?.systematicVersion).toBe('2.1.0') }) it('calls saveToolsCache after successful installs on cache miss', async () => { @@ -603,7 +545,7 @@ describe('setup', () => { // #given tools cache hit and tc.find returns a valid path vi.mocked(toolsCache.restoreToolsCache).mockResolvedValue({ hit: true, - restoredKey: 'opencode-tools-Linux-oc-1.0.300-omo-3.5.5', + restoredKey: 'opencode-tools-Linux-oc-1.0.300-omo-3.5.5-sys-2.1.0', }) vi.mocked(tc.find).mockReturnValue('/opt/hostedtoolcache/opencode/1.0.300/x64') @@ -621,7 +563,7 @@ describe('setup', () => { // #given tools cache hit but tc.find returns empty (version mismatch) vi.mocked(toolsCache.restoreToolsCache).mockResolvedValue({ hit: true, - restoredKey: 'opencode-tools-Linux-oc-1.0.299-omo-3.5.5', + restoredKey: 'opencode-tools-Linux-oc-1.0.299-omo-3.5.5-sys-2.1.0', }) vi.mocked(tc.find).mockReturnValue('') vi.mocked(tc.downloadTool).mockResolvedValue('/tmp/opencode.tar.gz') @@ -659,7 +601,7 @@ describe('setup', () => { // #given tools cache hit but tc.find returns empty vi.mocked(toolsCache.restoreToolsCache).mockResolvedValue({ hit: true, - restoredKey: 'opencode-tools-Linux-oc-1.0.299-omo-3.5.5', + restoredKey: 'opencode-tools-Linux-oc-1.0.299-omo-3.5.5-sys-2.1.0', }) vi.mocked(tc.find).mockReturnValue('') vi.mocked(tc.downloadTool).mockResolvedValue('/tmp/opencode.tar.gz') @@ -708,7 +650,7 @@ describe('setup', () => { // #given tools cache hit vi.mocked(toolsCache.restoreToolsCache).mockResolvedValue({ hit: true, - restoredKey: 'opencode-tools-Linux-oc-1.0.300-omo-3.5.5', + restoredKey: 'opencode-tools-Linux-oc-1.0.300-omo-3.5.5-sys-2.1.0', }) // #when @@ -722,7 +664,7 @@ describe('setup', () => { // #given tools cache hit vi.mocked(toolsCache.restoreToolsCache).mockResolvedValue({ hit: true, - restoredKey: 'opencode-tools-Linux-oc-1.0.300-omo-3.5.5', + restoredKey: 'opencode-tools-Linux-oc-1.0.300-omo-3.5.5-sys-2.1.0', }) // #when @@ -803,6 +745,51 @@ describe('setup', () => { expect(core.setFailed).not.toHaveBeenCalled() expect(core.warning).toHaveBeenCalledWith(expect.stringContaining('omo-config')) }) + + it('writes systematic-config JSON to systematic.json before installer runs', async () => { + // #given + const systematicConfig = JSON.stringify({agents: {default: 'sisyphus'}, mode: 'strict'}) + + // #when + const result = await runSetup(createSetupInputs({systematicConfig}), 'ghs_test_token') + + // #then + expect(result).not.toBeNull() + const writeFileCalls = vi.mocked(fs.writeFile).mock.calls + const systematicConfigCall = writeFileCalls.find( + ([filePath]) => typeof filePath === 'string' && filePath.includes('systematic.json'), + ) + expect(systematicConfigCall).toBeDefined() + const written = JSON.parse(systematicConfigCall?.[1] as string) as Record + expect(written).toMatchObject({agents: {default: 'sisyphus'}, mode: 'strict'}) + }) + + it('does not write systematic.json when systematic-config is not provided', async () => { + // #given + + // #when + const result = await runSetup(createSetupInputs(), 'ghs_test_token') + + // #then + expect(result).not.toBeNull() + const writeFileCalls = vi.mocked(fs.writeFile).mock.calls + const systematicConfigCall = writeFileCalls.find( + ([filePath]) => typeof filePath === 'string' && filePath.includes('systematic.json'), + ) + expect(systematicConfigCall).toBeUndefined() + }) + + it('continues setup and warns when systematic-config JSON is invalid', async () => { + // #given + + // #when + const result = await runSetup(createSetupInputs({systematicConfig: '{invalid json}'}), 'ghs_test_token') + + // #then + expect(result).not.toBeNull() + expect(core.setFailed).not.toHaveBeenCalled() + expect(core.warning).toHaveBeenCalledWith(expect.stringContaining('systematic-config write failed')) + }) }) }) }) diff --git a/src/services/setup/setup.ts b/src/services/setup/setup.ts index 3916d34a..8c9e65e6 100644 --- a/src/services/setup/setup.ts +++ b/src/services/setup/setup.ts @@ -1,56 +1,24 @@ -import type {ExecAdapter, OpenCodeInstallResult, SetupInputs, SetupResult, ToolCacheAdapter} from './types.js' +import type {OpenCodeInstallResult, SetupInputs, SetupResult} from './types.js' import {homedir} from 'node:os' import {join} from 'node:path' import process from 'node:process' import * as core from '@actions/core' -import * as exec from '@actions/exec' import {getOctokit} from '@actions/github' -import * as tc from '@actions/tool-cache' import {DEFAULT_BUN_VERSION} from '../../shared/constants.js' import {getRunnerOS, getXdgDataHome} from '../../shared/env.js' import {toErrorMessage} from '../../shared/errors.js' import {createLogger} from '../../shared/logger.js' +import {createExecAdapter, createToolCacheAdapter} from './adapters.js' import {parseAuthJsonInput, populateAuthJson} from './auth-json.js' import {installBun} from './bun.js' +import {buildCIConfig} from './ci-config.js' import {configureGhAuth, configureGitIdentity} from './gh-auth.js' import {writeOmoConfig} from './omo-config.js' import {installOmo} from './omo.js' import {FALLBACK_VERSION, getLatestVersion, installOpenCode} from './opencode.js' +import {writeSystematicConfig} from './systematic-config.js' import {restoreToolsCache, saveToolsCache} from './tools-cache.js' -/** - * Create tool cache adapter from @actions/tool-cache - */ -function createToolCacheAdapter(): ToolCacheAdapter { - return { - find: tc.find, - downloadTool: tc.downloadTool, - extractTar: tc.extractTar, - extractZip: tc.extractZip, - cacheDir: tc.cacheDir, - } -} - -/** - * Create exec adapter from @actions/exec - */ -function createExecAdapter(): ExecAdapter { - return { - exec: exec.exec, - getExecOutput: exec.getExecOutput, - } -} - -/** - * Run the setup action. - * - * This function orchestrates: - * 1. Installing OpenCode CLI - * 2. Installing oMo plugin (graceful failure) - * 3. Configuring gh CLI authentication - * 4. Configuring git identity - * 5. Populating auth.json - */ export async function runSetup(inputs: SetupInputs, githubToken: string): Promise { const startTime = Date.now() const logger = createLogger({component: 'setup'}) @@ -83,6 +51,7 @@ export async function runSetup(inputs: SetupInputs, githubToken: string): Promis } const omoVersion = inputs.omoVersion + const systematicVersion = inputs.systematicVersion // Restore tools cache before installs const runnerToolCache = process.env.RUNNER_TOOL_CACHE ?? '/opt/hostedtoolcache' @@ -96,6 +65,7 @@ export async function runSetup(inputs: SetupInputs, githubToken: string): Promis os: runnerOS, opencodeVersion: version, omoVersion, + systematicVersion, toolCachePath, bunCachePath, omoConfigPath, @@ -154,6 +124,14 @@ export async function runSetup(inputs: SetupInputs, githubToken: string): Promis } } + if (inputs.systematicConfig != null) { + try { + await writeSystematicConfig(inputs.systematicConfig, omoConfigPath, logger) + } catch (error) { + logger.warning(`systematic-config write failed: ${toErrorMessage(error)}`) + } + } + const omoResult = await installOmo(omoVersion, {logger, execAdapter}, inputs.omoProviders) if (omoResult.installed) { logger.info('oMo installed', {version: omoResult.version}) @@ -166,39 +144,12 @@ export async function runSetup(inputs: SetupInputs, githubToken: string): Promis omoError = omoResult.error } - // Export CI-safe OpenCode config. OPENCODE_CONFIG_CONTENT has highest precedence over all - // other OpenCode config sources (project, global, etc.). User-supplied opencode-config input - // is merged on top of the baseline, so user values override the defaults. - const ciConfig: Record = {autoupdate: false} - - if (inputs.opencodeConfig != null) { - let parsed: unknown - try { - parsed = JSON.parse(inputs.opencodeConfig) - } catch { - core.setFailed('opencode-config must be valid JSON') - return null - } - - // Validate that the parsed result is a plain object (not null, array, or primitive) - if (parsed == null || typeof parsed !== 'object' || Array.isArray(parsed)) { - core.setFailed('opencode-config must be a JSON object') - return null - } - Object.assign(ciConfig, parsed) - } - - // Ensure the Systematic plugin is always registered in the plugins array. - // User-provided plugins via opencode-config are preserved; Systematic is appended - // only when no @fro.bot/systematic entry already exists (so user version pins win). - const systematicPlugin = `@fro.bot/systematic@${inputs.systematicVersion}` - const rawPlugins: unknown[] = Array.isArray(ciConfig.plugins) ? (ciConfig.plugins as unknown[]) : [] - const hasSystematic = rawPlugins.some((p: unknown) => typeof p === 'string' && p.startsWith('@fro.bot/systematic')) - if (!hasSystematic) { - ciConfig.plugins = [...rawPlugins, systematicPlugin] + const ciConfigResult = buildCIConfig({opencodeConfig: inputs.opencodeConfig, systematicVersion}, logger) + if (ciConfigResult.error != null) { + core.setFailed(ciConfigResult.error) + return null } - - core.exportVariable('OPENCODE_CONFIG_CONTENT', JSON.stringify(ciConfig)) + core.exportVariable('OPENCODE_CONFIG_CONTENT', JSON.stringify(ciConfigResult.config)) if (!toolsCacheResult.hit) { await saveToolsCache({ @@ -206,6 +157,7 @@ export async function runSetup(inputs: SetupInputs, githubToken: string): Promis os: runnerOS, opencodeVersion: version, omoVersion, + systematicVersion, toolCachePath, bunCachePath, omoConfigPath, diff --git a/src/services/setup/systematic-config.test.ts b/src/services/setup/systematic-config.test.ts new file mode 100644 index 00000000..feef147c --- /dev/null +++ b/src/services/setup/systematic-config.test.ts @@ -0,0 +1,106 @@ +import type {Logger} from './types.js' +import * as fs from 'node:fs/promises' +import * as os from 'node:os' +import * as path from 'node:path' +import {afterEach, beforeEach, describe, expect, it} from 'vitest' +import {createMockLogger} from '../../shared/test-helpers.js' +import {writeSystematicConfig} from './systematic-config.js' + +describe('writeSystematicConfig', () => { + let tmpDir: string + let logger: Logger + + beforeEach(async () => { + tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'systematic-config-test-')) + logger = createMockLogger() + }) + + afterEach(async () => { + await fs.rm(tmpDir, {recursive: true, force: true}) + }) + + it('creates config directory if it does not exist', async () => { + // #given + const configDir = path.join(tmpDir, 'nested', 'dir', 'opencode') + const configJson = JSON.stringify({mode: 'strict'}) + + // #when + await writeSystematicConfig(configJson, configDir, logger) + + // #then + const filePath = path.join(configDir, 'systematic.json') + const content = await fs.readFile(filePath, 'utf8') + expect(JSON.parse(content)).toEqual({mode: 'strict'}) + }) + + it('writes config to systematic.json in configDir', async () => { + // #given + const configJson = JSON.stringify({agents: {default: 'sisyphus'}, mode: 'strict'}) + + // #when + await writeSystematicConfig(configJson, tmpDir, logger) + + // #then + const filePath = path.join(tmpDir, 'systematic.json') + const content = await fs.readFile(filePath, 'utf8') + expect(JSON.parse(content)).toEqual({agents: {default: 'sisyphus'}, mode: 'strict'}) + }) + + it('deep-merges with existing config, user values win', async () => { + // #given + const existingConfig = {mode: 'permissive', agents: {default: 'oracle', fallback: 'hephaestus'}} + const filePath = path.join(tmpDir, 'systematic.json') + await fs.writeFile(filePath, JSON.stringify(existingConfig)) + const userConfig = JSON.stringify({mode: 'strict', agents: {default: 'sisyphus'}}) + + // #when + await writeSystematicConfig(userConfig, tmpDir, logger) + + // #then + const content = await fs.readFile(filePath, 'utf8') + expect(JSON.parse(content)).toEqual({ + mode: 'strict', + agents: {default: 'sisyphus', fallback: 'hephaestus'}, + }) + }) + + it('throws on invalid JSON input', async () => { + // #given + const invalidJson = '{invalid json}' + + // #when / #then + await expect(writeSystematicConfig(invalidJson, tmpDir, logger)).rejects.toThrow() + }) + + it('throws when input JSON is not an object', async () => { + // #given + const jsonNull = 'null' + const jsonArray = '[1,2,3]' + const jsonNumber = '42' + + // #when / #then + await expect(writeSystematicConfig(jsonNull, tmpDir, logger)).rejects.toThrow( + 'systematic-config must be a JSON object (non-null, non-array)', + ) + await expect(writeSystematicConfig(jsonArray, tmpDir, logger)).rejects.toThrow( + 'systematic-config must be a JSON object (non-null, non-array)', + ) + await expect(writeSystematicConfig(jsonNumber, tmpDir, logger)).rejects.toThrow( + 'systematic-config must be a JSON object (non-null, non-array)', + ) + }) + + it('overwrites existing file when existing config is invalid JSON', async () => { + // #given + const filePath = path.join(tmpDir, 'systematic.json') + await fs.writeFile(filePath, 'not valid json {{{') + const userConfig = JSON.stringify({mode: 'strict'}) + + // #when + await writeSystematicConfig(userConfig, tmpDir, logger) + + // #then + const content = await fs.readFile(filePath, 'utf8') + expect(JSON.parse(content)).toEqual({mode: 'strict'}) + }) +}) diff --git a/src/services/setup/systematic-config.ts b/src/services/setup/systematic-config.ts new file mode 100644 index 00000000..737b5fe8 --- /dev/null +++ b/src/services/setup/systematic-config.ts @@ -0,0 +1,36 @@ +import type {Logger} from './types.js' +import * as fs from 'node:fs/promises' +import * as path from 'node:path' +import {deepMerge} from './omo-config.js' + +const SYSTEMATIC_CONFIG_FILENAME = 'systematic.json' + +function isMergeableObject(value: unknown): value is Record { + return value != null && typeof value === 'object' && !Array.isArray(value) +} + +export async function writeSystematicConfig(configJson: string, configDir: string, logger: Logger): Promise { + const parsedUserConfig: unknown = JSON.parse(configJson) + if (!isMergeableObject(parsedUserConfig)) { + throw new Error('systematic-config must be a JSON object (non-null, non-array)') + } + + await fs.mkdir(configDir, {recursive: true}) + const filePath = path.join(configDir, SYSTEMATIC_CONFIG_FILENAME) + + let existingConfig: Record = {} + try { + const raw = await fs.readFile(filePath, 'utf8') + const parsed: unknown = JSON.parse(raw) + if (isMergeableObject(parsed)) { + existingConfig = parsed + } + } catch (error) { + logger.debug('Using empty base Systematic config', {path: filePath, error: String(error)}) + } + + const merged = deepMerge(existingConfig, parsedUserConfig) + await fs.writeFile(filePath, JSON.stringify(merged, null, 2)) + + logger.info('Wrote Systematic config', {path: filePath, keyCount: Object.keys(parsedUserConfig).length}) +} diff --git a/src/services/setup/tools-cache.test.ts b/src/services/setup/tools-cache.test.ts index d5ddda62..48c3e140 100644 --- a/src/services/setup/tools-cache.test.ts +++ b/src/services/setup/tools-cache.test.ts @@ -44,12 +44,13 @@ describe('buildToolsCacheKey', () => { const os = 'Linux' const opencodeVersion = '1.0.0' const omoVersion = '3.5.5' + const systematicVersion = '2.1.0' // #when building cache key - const key = buildToolsCacheKey({os, opencodeVersion, omoVersion}) + const key = buildToolsCacheKey({os, opencodeVersion, omoVersion, systematicVersion}) // #then key uses opencode-tools prefix - expect(key).toBe('opencode-tools-Linux-oc-1.0.0-omo-3.5.5') + expect(key).toBe('opencode-tools-Linux-oc-1.0.0-omo-3.5.5-sys-2.1.0') expect(key).toMatch(/^opencode-tools-/) }) @@ -58,12 +59,13 @@ describe('buildToolsCacheKey', () => { const os = 'Linux' const opencodeVersion = 'latest' const omoVersion = '3.5.5' + const systematicVersion = '2.1.0' // #when building cache key - const key = buildToolsCacheKey({os, opencodeVersion, omoVersion}) + const key = buildToolsCacheKey({os, opencodeVersion, omoVersion, systematicVersion}) // #then key includes latest - expect(key).toBe('opencode-tools-Linux-oc-latest-omo-3.5.5') + expect(key).toBe('opencode-tools-Linux-oc-latest-omo-3.5.5-sys-2.1.0') }) }) @@ -73,12 +75,13 @@ describe('buildToolsRestoreKeys', () => { const os = 'Linux' const opencodeVersion = '1.0.0' const omoVersion = '3.5.5' + const systematicVersion = '2.1.0' // #when building restore keys - const keys = buildToolsRestoreKeys({os, opencodeVersion, omoVersion}) + const keys = buildToolsRestoreKeys({os, opencodeVersion, omoVersion, systematicVersion}) // #then only version-specific prefix key is returned (no broad OS-only fallback) - expect(keys).toEqual(['opencode-tools-Linux-oc-1.0.0-omo-3.5.5-']) + expect(keys).toEqual(['opencode-tools-Linux-oc-1.0.0-omo-3.5.5-sys-2.1.0-']) }) it('does not include broad OS-only fallback key', () => { @@ -86,9 +89,10 @@ describe('buildToolsRestoreKeys', () => { const os = 'Linux' const opencodeVersion = '1.0.0' const omoVersion = '3.5.5' + const systematicVersion = '2.1.0' // #when building restore keys - const keys = buildToolsRestoreKeys({os, opencodeVersion, omoVersion}) + const keys = buildToolsRestoreKeys({os, opencodeVersion, omoVersion, systematicVersion}) // #then no OS-only key that could match stale versions const broadKeys = [...keys].filter(k => k === `opencode-tools-${os}-`) @@ -100,12 +104,13 @@ describe('buildToolsRestoreKeys', () => { const os = 'macOS' const opencodeVersion = '1.0.0' const omoVersion = '3.5.5' + const systematicVersion = '2.1.0' // #when building restore keys - const keys = buildToolsRestoreKeys({os, opencodeVersion, omoVersion}) + const keys = buildToolsRestoreKeys({os, opencodeVersion, omoVersion, systematicVersion}) // #then keys include macOS prefix - expect(keys[0]).toBe('opencode-tools-macOS-oc-1.0.0-omo-3.5.5-') + expect(keys[0]).toBe('opencode-tools-macOS-oc-1.0.0-omo-3.5.5-sys-2.1.0-') }) }) @@ -136,6 +141,7 @@ describe('restoreToolsCache', () => { os: 'Linux', opencodeVersion: '1.0.0', omoVersion: '3.5.5', + systematicVersion: '2.1.0', toolCachePath, bunCachePath, omoConfigPath, @@ -149,7 +155,7 @@ describe('restoreToolsCache', () => { it('returns hit: true with key on cache hit', async () => { // #given a cache adapter that returns a key (hit) - const restoredKey = 'opencode-tools-Linux-oc-1.0.0-omo-3.5.5' + const restoredKey = 'opencode-tools-Linux-oc-1.0.0-omo-3.5.5-sys-2.1.0' const adapter = createMockToolsCacheAdapter({restoreResult: restoredKey}) // #when restoring cache @@ -158,6 +164,7 @@ describe('restoreToolsCache', () => { os: 'Linux', opencodeVersion: '1.0.0', omoVersion: '3.5.5', + systematicVersion: '2.1.0', toolCachePath, bunCachePath, omoConfigPath, @@ -186,6 +193,7 @@ describe('restoreToolsCache', () => { os: 'Linux', opencodeVersion: '1.0.0', omoVersion: '3.5.5', + systematicVersion: '2.1.0', toolCachePath, bunCachePath, omoConfigPath, @@ -211,6 +219,7 @@ describe('restoreToolsCache', () => { os: 'Linux', opencodeVersion: '1.0.0', omoVersion: '3.5.5', + systematicVersion: '2.1.0', toolCachePath, bunCachePath, omoConfigPath, @@ -250,6 +259,7 @@ describe('saveToolsCache', () => { os: 'Linux', opencodeVersion: '1.0.0', omoVersion: '3.5.5', + systematicVersion: '2.1.0', toolCachePath, bunCachePath, omoConfigPath, @@ -277,6 +287,7 @@ describe('saveToolsCache', () => { os: 'Linux', opencodeVersion: '1.0.0', omoVersion: '3.5.5', + systematicVersion: '2.1.0', toolCachePath, bunCachePath, omoConfigPath, @@ -304,6 +315,7 @@ describe('saveToolsCache', () => { os: 'Linux', opencodeVersion: '1.0.0', omoVersion: '3.5.5', + systematicVersion: '2.1.0', toolCachePath, bunCachePath, omoConfigPath, @@ -311,7 +323,7 @@ describe('saveToolsCache', () => { }) // #then uses correct key - expect(capturedKey).toBe('opencode-tools-Linux-oc-1.0.0-omo-3.5.5') + expect(capturedKey).toBe('opencode-tools-Linux-oc-1.0.0-omo-3.5.5-sys-2.1.0') }) it('handles cache already exists error', async () => { @@ -326,6 +338,7 @@ describe('saveToolsCache', () => { os: 'Linux', opencodeVersion: '1.0.0', omoVersion: '3.5.5', + systematicVersion: '2.1.0', toolCachePath, bunCachePath, omoConfigPath, @@ -348,6 +361,7 @@ describe('saveToolsCache', () => { os: 'Linux', opencodeVersion: '1.0.0', omoVersion: '3.5.5', + systematicVersion: '2.1.0', toolCachePath, bunCachePath, omoConfigPath, diff --git a/src/services/setup/tools-cache.ts b/src/services/setup/tools-cache.ts index 621412c4..b2dcd3a2 100644 --- a/src/services/setup/tools-cache.ts +++ b/src/services/setup/tools-cache.ts @@ -7,6 +7,7 @@ export interface ToolsCacheKeyComponents { readonly os: string readonly opencodeVersion: string readonly omoVersion: string + readonly systematicVersion: string } export interface ToolsCacheAdapter { @@ -24,6 +25,7 @@ export interface RestoreToolsCacheOptions { readonly os: string readonly opencodeVersion: string readonly omoVersion: string + readonly systematicVersion: string readonly toolCachePath: string readonly bunCachePath: string readonly omoConfigPath: string @@ -35,6 +37,7 @@ export interface SaveToolsCacheOptions { readonly os: string readonly opencodeVersion: string readonly omoVersion: string + readonly systematicVersion: string readonly toolCachePath: string readonly bunCachePath: string readonly omoConfigPath: string @@ -47,14 +50,14 @@ export interface ToolsCacheResult { } export function buildToolsCacheKey(components: ToolsCacheKeyComponents): string { - const {os, opencodeVersion, omoVersion} = components - return `${TOOLS_CACHE_PREFIX}-${os}-oc-${opencodeVersion}-omo-${omoVersion}` + const {os, opencodeVersion, omoVersion, systematicVersion} = components + return `${TOOLS_CACHE_PREFIX}-${os}-oc-${opencodeVersion}-omo-${omoVersion}-sys-${systematicVersion}` } export function buildToolsRestoreKeys(components: ToolsCacheKeyComponents): readonly string[] { - const {os, opencodeVersion, omoVersion} = components + const {os, opencodeVersion, omoVersion, systematicVersion} = components - return [`${TOOLS_CACHE_PREFIX}-${os}-oc-${opencodeVersion}-omo-${omoVersion}-`] as const + return [`${TOOLS_CACHE_PREFIX}-${os}-oc-${opencodeVersion}-omo-${omoVersion}-sys-${systematicVersion}-`] as const } export async function restoreToolsCache(options: RestoreToolsCacheOptions): Promise { @@ -63,14 +66,15 @@ export async function restoreToolsCache(options: RestoreToolsCacheOptions): Prom os, opencodeVersion, omoVersion, + systematicVersion, toolCachePath, bunCachePath, omoConfigPath, cacheAdapter = defaultToolsCacheAdapter, } = options - const primaryKey = buildToolsCacheKey({os, opencodeVersion, omoVersion}) - const restoreKeys = buildToolsRestoreKeys({os, opencodeVersion, omoVersion}) + const primaryKey = buildToolsCacheKey({os, opencodeVersion, omoVersion, systematicVersion}) + const restoreKeys = buildToolsRestoreKeys({os, opencodeVersion, omoVersion, systematicVersion}) const cachePaths = [toolCachePath, bunCachePath, omoConfigPath] logger.info('Restoring tools cache', {primaryKey, restoreKeys: [...restoreKeys], paths: cachePaths}) @@ -108,13 +112,14 @@ export async function saveToolsCache(options: SaveToolsCacheOptions): Promise