1- import type { FlueClient } from '@flue/client' ;
2- import { anthropic , github , githubBody } from '@flue/client/proxies ' ;
1+ import type { FlueContext , FlueSession } from '@flue/sdk /client' ;
2+ import { defineCommand } from '@flue/sdk/node ' ;
33import * as v from 'valibot' ;
44import {
5+ GITHUB_TOKEN_BASE ,
56 type IssueDetails ,
67 type RepoLabel ,
78 addGitHubLabels ,
89 fetchIssueDetails ,
910 fetchRepoLabels ,
1011 postGitHubComment ,
1112 removeGitHubLabel ,
12- } from './github.ts' ;
13-
14- export const proxies = {
15- anthropic : anthropic ( ) ,
16- github : github ( {
17- policy : {
18- base : 'allow-read' ,
19- allow : [
20- // Allow read-only access to the GraphQL endpoint
21- { method : 'POST' , path : '/graphql' , body : githubBody . graphql ( ) } ,
22- // Allow git clone, fetch, and push over smart HTTP transport
23- { method : 'GET' , path : '/*/*/info/refs' } ,
24- { method : 'POST' , path : '/*/*/git-upload-pack' } ,
25- { method : 'POST' , path : '/*/*/git-receive-pack' } ,
26- ] ,
27- } ,
28- } ) ,
29- } ;
13+ } from '../lib/github.ts' ;
14+
15+ // CLI-only agent: no HTTP trigger. Invoked from GitHub Actions via `flue run issue-triage`.
16+ export const triggers = { } ;
17+
18+ // Define commands that are allowed as pass-through to the local GH Actions container.
19+ const bgproc = defineCommand ( 'bgproc' ) ;
20+ const agentBrowser = defineCommand ( 'agent-browser' ) ;
21+ const node = defineCommand ( 'node' ) ;
22+ const pnpm = defineCommand ( 'pnpm' ) ;
23+ const gh = defineCommand ( 'gh' , { env : { GH_TOKEN : GITHUB_TOKEN_BASE } } ) ;
24+ const git = defineCommand ( 'git' ) ;
25+ const gitWithAuth = defineCommand ( 'git' , { env : { GH_TOKEN : GITHUB_TOKEN_BASE } } ) ;
3026
3127function assert ( condition : unknown , message : string ) : asserts condition {
3228 if ( ! condition ) throw new Error ( message ) ;
3329}
3430
35- async function shouldRetriage ( flue : FlueClient , issue : IssueDetails ) : Promise < 'yes' | 'no' > {
36- return flue . prompt (
31+ async function shouldRetriage ( session : FlueSession , issue : IssueDetails ) : Promise < 'yes' | 'no' > {
32+ return session . prompt (
3733 `You are reviewing a GitHub issue conversation to decide whether a triage re-run is warranted.
3834
3935## Issue
@@ -65,7 +61,7 @@ Return only "yes" or "no" inside the ---RESULT_START--- / ---RESULT_END--- block
6561}
6662
6763async function selectTriageLabels (
68- flue : FlueClient ,
64+ session : FlueSession ,
6965 {
7066 comment,
7167 priorityLabels,
@@ -75,7 +71,7 @@ async function selectTriageLabels(
7571 const priorityLabelNames = priorityLabels . map ( ( l ) => l . name ) ;
7672 const packageLabelNames = packageLabels . map ( ( l ) => l . name ) ;
7773
78- const labelResult = await flue . prompt (
74+ const labelResult = await session . prompt (
7975 `Label the following GitHub issue based on the triage report that was already posted.
8076
8177Select labels for this issue from the lists below based on the triage report. Select exactly one priority label (the report's **Priority** section is a strong hint) and 0-3 package labels based on where the issue lives in the monorepo and how it manifests.
@@ -114,7 +110,7 @@ ${comment}
114110}
115111
116112async function runTriagePipeline (
117- flue : FlueClient ,
113+ session : FlueSession ,
118114 issueNumber : number ,
119115 issueDetails : IssueDetails ,
120116) : Promise < {
@@ -127,8 +123,9 @@ async function runTriagePipeline(
127123 fixed : boolean ;
128124 commitMessage : string | null ;
129125} > {
130- const reproduceResult = await flue . skill ( 'triage/reproduce.md' , {
126+ const reproduceResult = await session . skill ( 'triage/reproduce.md' , {
131127 args : { issueNumber, issueDetails } ,
128+ commands : [ gh , bgproc , agentBrowser , git , node , pnpm ] ,
132129 result : v . object ( {
133130 reproducible : v . pipe (
134131 v . boolean ( ) ,
@@ -155,17 +152,19 @@ async function runTriagePipeline(
155152 } ;
156153 }
157154
158- const diagnoseResult = await flue . skill ( 'triage/diagnose.md' , {
155+ const diagnoseResult = await session . skill ( 'triage/diagnose.md' , {
159156 args : { issueDetails } ,
157+ commands : [ gh , bgproc , agentBrowser , git , node , pnpm ] ,
160158 result : v . object ( {
161159 confidence : v . pipe (
162160 v . nullable ( v . picklist ( [ 'high' , 'medium' , 'low' ] ) ) ,
163161 v . description ( 'Diagnosis confidence level, null if not attempted' ) ,
164162 ) ,
165163 } ) ,
166164 } ) ;
167- const verifyResult = await flue . skill ( 'triage/verify.md' , {
165+ const verifyResult = await session . skill ( 'triage/verify.md' , {
168166 args : { issueDetails } ,
167+ commands : [ gh , bgproc , agentBrowser , git , node , pnpm ] ,
169168 result : v . object ( {
170169 verdict : v . pipe (
171170 v . picklist ( [ 'bug' , 'intended-behavior' , 'unclear' ] ) ,
@@ -190,8 +189,9 @@ async function runTriagePipeline(
190189 } ;
191190 }
192191
193- const fixResult = await flue . skill ( 'triage/fix.md' , {
192+ const fixResult = await session . skill ( 'triage/fix.md' , {
194193 args : { issueDetails } ,
194+ commands : [ gh , bgproc , agentBrowser , git , node , pnpm ] ,
195195 result : v . object ( {
196196 fixed : v . pipe (
197197 v . boolean ( ) ,
@@ -216,29 +216,31 @@ async function runTriagePipeline(
216216 } ;
217217}
218218
219- export const args = v . object ( {
220- issueNumber : v . number ( ) ,
221- } ) ;
222-
223- export default async function triage (
224- flue : FlueClient ,
225- { issueNumber } : v . InferOutput < typeof args > ,
226- ) {
219+ export default async function ( { init, payload } : FlueContext ) {
220+ const issueNumber = payload . issueNumber as number ;
227221 const branch = `flue/fix-${ issueNumber } ` ;
222+
223+ // Initialize the agent and session.
224+ const agent = await init ( {
225+ sandbox : 'local' ,
226+ model : 'anthropic/claude-opus-4-6' ,
227+ } ) ;
228+ const session = await agent . session ( ) ;
229+
228230 const issueDetails = await fetchIssueDetails ( issueNumber ) ;
229231
230232 // If there are prior comments, this is a re-triage. Check whether new
231233 // actionable information has been provided before running the full pipeline.
232234 const hasExistingConversation = issueDetails . comments . length > 0 ;
233235 if ( hasExistingConversation ) {
234- const shouldRetriageResult = await shouldRetriage ( flue , issueDetails ) ;
236+ const shouldRetriageResult = await shouldRetriage ( session , issueDetails ) ;
235237 if ( shouldRetriageResult === 'no' ) {
236238 return { skipped : true , reason : 'No new actionable information' } ;
237239 }
238240 }
239241
240242 // Run the triage pipeline: reproduce → diagnose → verify → fix
241- const triageResult = await runTriagePipeline ( flue , issueNumber , issueDetails ) ;
243+ const triageResult = await runTriagePipeline ( session , issueNumber , issueDetails ) ;
242244 let isPushed = false ;
243245
244246 // Push the fix branch if there are meaningful changes (fix, failing test, etc.).
@@ -247,19 +249,20 @@ export default async function triage(
247249 // - create a PR from that branch entirely in the GH UI
248250 // - ignore it completely
249251 {
250- const diff = await flue . shell ( 'git diff main --stat' ) ;
252+ const diff = await session . shell ( 'git diff main --stat' , { commands : [ git ] } ) ;
251253 if ( diff . stdout . trim ( ) ) {
252- const status = await flue . shell ( 'git status --porcelain' ) ;
254+ const status = await session . shell ( 'git status --porcelain' , { commands : [ git ] } ) ;
253255 if ( status . stdout . trim ( ) ) {
254- await flue . shell ( 'git add -A' ) ;
256+ await session . shell ( 'git add -A' , { commands : [ git ] } ) ;
255257 const defaultMessage = triageResult . fixed
256258 ? 'fix(auto-triage): automated fix'
257259 : 'test(auto-triage): failing test and investigation notes' ;
258- await flue . shell (
260+ await session . shell (
259261 `git commit -m ${ JSON . stringify ( triageResult . commitMessage ?? defaultMessage ) } ` ,
262+ { commands : [ git ] } ,
260263 ) ;
261264 }
262- const pushResult = await flue . shell ( `git push -f origin ${ branch } ` ) ;
265+ const pushResult = await session . shell ( `git push -f origin ${ branch } ` , { commands : [ gitWithAuth ] } ) ;
263266 console . info ( 'push result:' , pushResult ) ;
264267 isPushed = pushResult . exitCode === 0 ;
265268 }
@@ -272,8 +275,9 @@ export default async function triage(
272275 assert ( packageLabels . length > 0 , 'no package labels found' ) ;
273276
274277 const branchName = isPushed ? branch : null ;
275- const comment = await flue . skill ( 'triage/comment.md' , {
278+ const comment = await session . skill ( 'triage/comment.md' , {
276279 args : { branchName, priorityLabels, issueDetails } ,
280+ commands : [ gh , git , node , pnpm ] ,
277281 result : v . pipe (
278282 v . string ( ) ,
279283 v . description (
@@ -286,7 +290,7 @@ export default async function triage(
286290
287291 if ( triageResult . reproducible ) {
288292 await removeGitHubLabel ( issueNumber , 'needs triage' ) ;
289- const selectedLabels = await selectTriageLabels ( flue , {
293+ const selectedLabels = await selectTriageLabels ( session , {
290294 comment,
291295 priorityLabels,
292296 packageLabels,
0 commit comments