koda-tui 2.3.28 → 2.3.29

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +3 -3
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -594,7 +594,7 @@ ${B.report}`).join(`
594
594
  `)?"":`
595
595
  `;se.appendFileSync(s,`${A}# Koda AI agent data
596
596
  .koda/
597
- `)}}}catch{}}setArchetypeVirtualFiles(t){let n=".koda/archetypes/.koda-full-sync-reset",i={...t},s=!!i[n];if(delete i[n],s){for(let l of Object.keys(this._archetypeVirtualFiles))l.startsWith(".koda/archetypes/")&&delete this._archetypeVirtualFiles[l];try{se.rmSync(Te.join(this.cwd,".koda","archetypes"),{recursive:!0,force:!0})}catch{}}this._archetypeVirtualFiles={...this._archetypeVirtualFiles,...i};for(let[l,u]of Object.entries(i))try{let A=Te.isAbsolute(l)?l:Te.join(this.cwd,l),p=Te.dirname(A);se.existsSync(p)||se.mkdirSync(p,{recursive:!0}),u.startsWith("__b64__:")?se.writeFileSync(A,Buffer.from(u.slice(8),"base64")):se.existsSync(A)||se.writeFileSync(A,u,"utf-8")}catch{}}start(){return this._shutdownRequested=!1,this._started=!0,this._initialized=!0,this.emit("connected"),Promise.resolve()}async fetchInfo(){return await this._httpGet("/api/info")}_httpGet(t,n,i=0){return new Promise((s,l)=>{let u=!!this._cookies.koda_app_session,A=`${this.baseUrl}${t}`,p=new URL(A),g=p.protocol==="https:"?Qo:gc,h={};this._shouldSendAuthHeader()&&(h.Authorization=`Bearer ${this.authToken}`);let T=this._cookieHeader();T&&(h.Cookie=T);let D=p.protocol==="https:"?Qo.globalAgent:gc.globalAgent,N=g.request(A,{method:"GET",headers:h,agent:D},R=>{if(this._captureCookies(R),R.statusCode===401||R.statusCode===403){this._clearRuntimeSessionCookie();let B="";R.setEncoding("utf-8"),R.on("data",L=>{B+=L}),R.on("end",()=>{let L=`HTTP ${R.statusCode}`;try{let M=JSON.parse(B);M.error&&(L=`HTTP ${R.statusCode}: ${M.error}`)}catch{}if(R.statusCode===401&&u&&!n&&this.authToken){this._httpGet(t,!0).then(s,l);return}if(R.statusCode===401&&!n&&this._tokenRefresher){this._refreshAuth().then(M=>{M?(this.authToken=M,this._httpGet(t,!0).then(s,l)):(this.emit("auth_required",{status:R.statusCode,message:L}),l(new Error(L)))}).catch(()=>{this.emit("auth_required",{status:R.statusCode,message:L}),l(new Error(L))});return}this.emit("auth_required",{status:R.statusCode,message:L}),l(new Error(L))});return}if((R.statusCode||0)<200||(R.statusCode||0)>=300){let B="";R.setEncoding("utf-8"),R.on("data",L=>{B+=L}),R.on("end",()=>{let L=R.statusCode||0;if(v_(L,B)&&i<R_){let P=b_(i+1);setTimeout(()=>{this._httpGet(t,n,i+1).then(s,l)},P);return}let M=`HTTP ${L}`;try{let P=JSON.parse(B);P.error&&(M=`HTTP ${L}: ${P.error}`)}catch{let P=B.replace(/\s+/g," ").trim().slice(0,240);P&&(M=`HTTP ${L} \u2014 ${P}`)}l(new Error(M))});return}let I="";R.setEncoding("utf-8"),R.on("data",B=>{I+=B}),R.on("end",()=>{try{s(JSON.parse(I))}catch{s({result:I})}})});N.on("error",R=>{if(v_(void 0,R.message)&&i<R_){let I=b_(i+1);setTimeout(()=>{this._httpGet(t,n,i+1).then(s,l)},I);return}l(R)}),N.setTimeout(3e4,()=>{N.destroy(new Error("HTTP GET timeout"))}),N.end()})}async request(t,n={}){let i=this.nextId++;if(n.working_directory||(n={...n,working_directory:this.cwd}),t==="send_message"&&n.rules_content===void 0){let A=this._buildProjectInstructionsContent();A&&(n={...n,rules_content:A})}let s=n.working_directory||this.cwd,l=this._handleLocalRpc(t,n,s);if(l!==void 0)return l;let u={id:i,method:t,params:n};try{let A=await this._httpPost("/api/rpc",u);if(t==="initialize"&&A&&A.result&&A.result.session_id&&(this._sessionId=A.result.session_id,this._initialized=!0),t==="switch_model"&&n.model){let p=n.model,g=this._lastModel;this._lastModel=p;try{let h=p==="auto"&&g!=="auto",T=p!=="auto"&&g==="auto";if(h){let D=this._handleLocalRpc("list_builtin_skills",{},s);this._preAutoSkills=(D?.skills||[]).filter(N=>N.enabled).map(N=>N.slug);for(let N of D?.skills||[])N.available&&!N.enabled&&this._handleLocalRpc("enable_builtin_skill",{slug:N.slug},s)}else if(T&&this._preAutoSkills!==null){let D=new Set(this._preAutoSkills);this._preAutoSkills=null;let N=this._handleLocalRpc("list_builtin_skills",{},s);for(let R of N?.skills||[])R.enabled&&!D.has(R.slug)&&this._handleLocalRpc("disable_builtin_skill",{slug:R.slug},s)}}catch{}}if(A&&A.error)throw new Error(A.error.message||"RPC error");return A?.result||{}}catch(A){throw A}}async notify(t,n={}){await this.request(t,n)}_countBuiltinSkillFolders(t){if(!se.existsSync(t)||!se.statSync(t).isDirectory())return{total:0,openAi:0,anthropic:0};let n=0,i=0,s=0;try{for(let l of se.readdirSync(t,{withFileTypes:!0}))l.isDirectory()&&se.existsSync(Te.join(t,l.name,"SKILL.md"))&&(n+=1,l.name.startsWith("openai-")?i+=1:s+=1)}catch{return{total:0,openAi:0,anthropic:0}}return{total:n,openAi:i,anthropic:s}}_getBuiltinSkillsDir(){let t=[process.env.KODA_BUILTIN_SKILLS_DIR,Te.resolve(__dirname,"..","..","backend","builtin_skills"),Te.resolve(__dirname,"..","..","ide","extensions","kingkoda","built-in-skills"),Te.resolve(__dirname,"..","backend","builtin_skills"),Te.resolve(__dirname,"..","built-in-skills"),Te.resolve(__dirname,"..","builtin_skills"),Te.join(oE.homedir(),".koda","builtin_skills"),Te.join(oE.homedir(),".koda","built-in-skills")].filter(i=>!!i),n=null;for(let i of new Set(t)){let s=this._countBuiltinSkillFolders(i);s.total!==0&&(!n||s.total>n.total||s.total===n.total&&s.openAi>n.openAi||s.total===n.total&&s.openAi===n.openAi&&s.anthropic>n.anthropic)&&(n={dir:i,...s})}return n?.dir||null}_parseBuiltinSkillUiMetadata(t,n){let i=Te.join(t,"agents","openai.yaml");if(se.existsSync(i))try{let l=se.readFileSync(i,"utf-8"),u=l.match(/^\s*display_name\s*:\s*(.+)\s*$/m)?.[1]?.trim().replace(/^["']|["']$/g,""),A=l.match(/^\s*short_description\s*:\s*(.+)\s*$/m)?.[1]?.trim().replace(/^["']|["']$/g,"");if(u||A)return{name:u||n.replace(/^openai-/,"").replace(/-/g," ").replace(/\b\w/g,p=>p.toUpperCase()),description:A||""}}catch{}let s=Te.join(t,"SKILL.md");if(se.existsSync(s))try{let l=this._parseSkillMd(se.readFileSync(s,"utf-8"),n);return{name:l.title,description:l.description}}catch{}return{name:n.replace(/^openai-/,"").replace(/-/g," ").replace(/\b\w/g,l=>l.toUpperCase()),description:""}}_buildBuiltinSkillsCatalog(t){let n=[{slug:"pdf",name:"PDF Processing",category:"document",description:"Read, create, merge, split, watermark, encrypt, OCR, and manipulate PDF files.",provider:"anthropic",providerLabel:"Anthropic",exclusiveGroup:"pdf"},{slug:"docx",name:"Word Documents",category:"document",description:"Create, read, edit, and format Word (.docx) documents.",provider:"anthropic",providerLabel:"Anthropic",exclusiveGroup:"docx"},{slug:"xlsx",name:"Spreadsheets",category:"document",description:"Open, read, edit, create, and convert spreadsheet files.",provider:"anthropic",providerLabel:"Anthropic",exclusiveGroup:"xlsx"},{slug:"pptx",name:"Presentations",category:"document",description:"Create, read, edit, and manipulate PowerPoint slide decks.",provider:"anthropic",providerLabel:"Anthropic",exclusiveGroup:"pptx"},{slug:"canvas-design",name:"Canvas Design",category:"design",description:"Create beautiful visual art in PNG and PDF.",provider:"anthropic",providerLabel:"Anthropic",exclusiveGroup:""},{slug:"algorithmic-art",name:"Algorithmic Art",category:"design",description:"Create generative algorithmic art using p5.js.",provider:"anthropic",providerLabel:"Anthropic",exclusiveGroup:""},{slug:"frontend-design",name:"Frontend Design",category:"design",description:"Build distinctive, production-grade frontend interfaces.",provider:"anthropic",providerLabel:"Anthropic",exclusiveGroup:""},{slug:"theme-factory",name:"Theme Factory",category:"design",description:"Apply pre-set or custom themes to slides, docs, and web pages.",provider:"anthropic",providerLabel:"Anthropic",exclusiveGroup:""},{slug:"brand-guidelines",name:"Brand Guidelines",category:"design",description:"Apply official brand colors and typography to artifacts.",provider:"anthropic",providerLabel:"Anthropic",exclusiveGroup:""},{slug:"mcp-builder",name:"MCP Builder",category:"development",description:"Build high-quality MCP servers for LLM integration.",provider:"anthropic",providerLabel:"Anthropic",exclusiveGroup:""},{slug:"web-artifacts-builder",name:"Web Artifacts Builder",category:"development",description:"Create elaborate multi-component HTML artifacts.",provider:"anthropic",providerLabel:"Anthropic",exclusiveGroup:""},{slug:"webapp-testing",name:"Web App Testing",category:"development",description:"Test and debug local web applications using Playwright.",provider:"anthropic",providerLabel:"Anthropic",exclusiveGroup:""},{slug:"skill-creator",name:"Skill Creator",category:"workflow",description:"Create new skills, modify existing ones, run evals.",provider:"anthropic",providerLabel:"Anthropic",exclusiveGroup:"skill-creator"},{slug:"doc-coauthoring",name:"Doc Co-authoring",category:"workflow",description:"Structured workflow for co-authoring documentation.",provider:"anthropic",providerLabel:"Anthropic",exclusiveGroup:""},{slug:"internal-comms",name:"Internal Comms",category:"workflow",description:"Write internal communications: status reports, updates.",provider:"anthropic",providerLabel:"Anthropic",exclusiveGroup:""},{slug:"slack-gif-creator",name:"Slack GIF Creator",category:"utility",description:"Create animated GIFs optimized for Slack.",provider:"anthropic",providerLabel:"Anthropic",exclusiveGroup:""}],i=new Set(["openai-docs","openai-skill-creator","openai-skill-installer"]),s={"openai-pdf":"pdf","openai-doc":"docx","openai-spreadsheet":"xlsx","openai-slides":"pptx","openai-skill-creator":"skill-creator"},l=[];if(t&&se.existsSync(t))try{let u=se.readdirSync(t,{withFileTypes:!0}).filter(A=>A.isDirectory()&&A.name.startsWith("openai-")).sort((A,p)=>A.name.localeCompare(p.name));for(let A of u){let p=Te.join(t,A.name);if(!se.existsSync(Te.join(p,"SKILL.md")))continue;let g=this._parseBuiltinSkillUiMetadata(p,A.name);l.push({slug:A.name,name:g.name,category:i.has(A.name)?"system":"curated",description:g.description,provider:"openai",providerLabel:"Open AI",exclusiveGroup:s[A.name]||""})}}catch{}return[...n,...l]}_disableBuiltinSkillTarget(t,n){let i=Te.join(t,".koda","skills",n);return se.existsSync(i)?se.existsSync(Te.join(i,".builtin"))?(se.rmSync(i,{recursive:!0,force:!0}),{success:!0}):{error:`Folder .koda/skills/${n}/ exists but was not installed by Koda.`}:{success:!0,message:"Already disabled."}}_extractWorkspaceConfigDescription(t){let n=t.trimStart(),i=t;if(n.startsWith("---")){let s=n.indexOf("---",3);if(s>0){for(let l of n.slice(3,s).split(`
597
+ `)}}}catch{}}setArchetypeVirtualFiles(t){let n=".koda/archetypes/.koda-full-sync-reset",i={...t},s=!!i[n];if(delete i[n],s){for(let l of Object.keys(this._archetypeVirtualFiles))l.startsWith(".koda/archetypes/")&&delete this._archetypeVirtualFiles[l];try{se.rmSync(Te.join(this.cwd,".koda","archetypes"),{recursive:!0,force:!0})}catch{}}this._archetypeVirtualFiles={...this._archetypeVirtualFiles,...i};for(let[l,u]of Object.entries(i))try{let A=Te.isAbsolute(l)?l:Te.join(this.cwd,l),p=Te.dirname(A);se.existsSync(p)||se.mkdirSync(p,{recursive:!0}),u.startsWith("__b64__:")?se.writeFileSync(A,Buffer.from(u.slice(8),"base64")):se.existsSync(A)||se.writeFileSync(A,u,"utf-8")}catch{}}start(){return this._shutdownRequested=!1,this._started=!0,this._initialized=!0,this.emit("connected"),Promise.resolve()}async fetchInfo(){return await this._httpGet("/api/info")}_httpGet(t,n,i=0){return new Promise((s,l)=>{let u=!!this._cookies.koda_app_session,A=`${this.baseUrl}${t}`,p=new URL(A),g=p.protocol==="https:"?Qo:gc,h={};this._shouldSendAuthHeader()&&(h.Authorization=`Bearer ${this.authToken}`);let T=this._cookieHeader();T&&(h.Cookie=T);let D=p.protocol==="https:"?Qo.globalAgent:gc.globalAgent,N=g.request(A,{method:"GET",headers:h,agent:D},R=>{if(this._captureCookies(R),R.statusCode===401||R.statusCode===403){this._clearRuntimeSessionCookie();let B="";R.setEncoding("utf-8"),R.on("data",L=>{B+=L}),R.on("end",()=>{let L=`HTTP ${R.statusCode}`;try{let M=JSON.parse(B);M.error&&(L=`HTTP ${R.statusCode}: ${M.error}`)}catch{}if(R.statusCode===401&&u&&!n&&this.authToken){this._httpGet(t,!0).then(s,l);return}if(R.statusCode===401&&!n&&this._tokenRefresher){this._refreshAuth().then(M=>{M?(this.authToken=M,this._httpGet(t,!0).then(s,l)):(this.emit("auth_required",{status:R.statusCode,message:L}),l(new Error(L)))}).catch(()=>{this.emit("auth_required",{status:R.statusCode,message:L}),l(new Error(L))});return}this.emit("auth_required",{status:R.statusCode,message:L}),l(new Error(L))});return}if((R.statusCode||0)<200||(R.statusCode||0)>=300){let B="";R.setEncoding("utf-8"),R.on("data",L=>{B+=L}),R.on("end",()=>{let L=R.statusCode||0;if(v_(L,B)&&i<R_){let P=b_(i+1);setTimeout(()=>{this._httpGet(t,n,i+1).then(s,l)},P);return}let M=`HTTP ${L}`;try{let P=JSON.parse(B);P.error&&(M=`HTTP ${L}: ${P.error}`)}catch{let P=B.replace(/\s+/g," ").trim().slice(0,240);P&&(M=`HTTP ${L} \u2014 ${P}`)}l(new Error(M))});return}let I="";R.setEncoding("utf-8"),R.on("data",B=>{I+=B}),R.on("end",()=>{try{s(JSON.parse(I))}catch{s({result:I})}})});N.on("error",R=>{if(v_(void 0,R.message)&&i<R_){let I=b_(i+1);setTimeout(()=>{this._httpGet(t,n,i+1).then(s,l)},I);return}l(R)}),N.setTimeout(3e4,()=>{N.destroy(new Error("HTTP GET timeout"))}),N.end()})}async request(t,n={}){let i=this.nextId++;if(n.working_directory||(n={...n,working_directory:this.cwd}),t==="initialize"&&!n.client_platform&&(n={...n,client_platform:process.platform}),t==="send_message"&&n.rules_content===void 0){let A=this._buildProjectInstructionsContent();A&&(n={...n,rules_content:A})}let s=n.working_directory||this.cwd,l=this._handleLocalRpc(t,n,s);if(l!==void 0)return l;let u={id:i,method:t,params:n};try{let A=await this._httpPost("/api/rpc",u);if(t==="initialize"&&A&&A.result&&A.result.session_id&&(this._sessionId=A.result.session_id,this._initialized=!0),t==="switch_model"&&n.model){let p=n.model,g=this._lastModel;this._lastModel=p;try{let h=p==="auto"&&g!=="auto",T=p!=="auto"&&g==="auto";if(h){let D=this._handleLocalRpc("list_builtin_skills",{},s);this._preAutoSkills=(D?.skills||[]).filter(N=>N.enabled).map(N=>N.slug);for(let N of D?.skills||[])N.available&&!N.enabled&&this._handleLocalRpc("enable_builtin_skill",{slug:N.slug},s)}else if(T&&this._preAutoSkills!==null){let D=new Set(this._preAutoSkills);this._preAutoSkills=null;let N=this._handleLocalRpc("list_builtin_skills",{},s);for(let R of N?.skills||[])R.enabled&&!D.has(R.slug)&&this._handleLocalRpc("disable_builtin_skill",{slug:R.slug},s)}}catch{}}if(A&&A.error)throw new Error(A.error.message||"RPC error");return A?.result||{}}catch(A){throw A}}async notify(t,n={}){await this.request(t,n)}_countBuiltinSkillFolders(t){if(!se.existsSync(t)||!se.statSync(t).isDirectory())return{total:0,openAi:0,anthropic:0};let n=0,i=0,s=0;try{for(let l of se.readdirSync(t,{withFileTypes:!0}))l.isDirectory()&&se.existsSync(Te.join(t,l.name,"SKILL.md"))&&(n+=1,l.name.startsWith("openai-")?i+=1:s+=1)}catch{return{total:0,openAi:0,anthropic:0}}return{total:n,openAi:i,anthropic:s}}_getBuiltinSkillsDir(){let t=[process.env.KODA_BUILTIN_SKILLS_DIR,Te.resolve(__dirname,"..","..","backend","builtin_skills"),Te.resolve(__dirname,"..","..","ide","extensions","kingkoda","built-in-skills"),Te.resolve(__dirname,"..","backend","builtin_skills"),Te.resolve(__dirname,"..","built-in-skills"),Te.resolve(__dirname,"..","builtin_skills"),Te.join(oE.homedir(),".koda","builtin_skills"),Te.join(oE.homedir(),".koda","built-in-skills")].filter(i=>!!i),n=null;for(let i of new Set(t)){let s=this._countBuiltinSkillFolders(i);s.total!==0&&(!n||s.total>n.total||s.total===n.total&&s.openAi>n.openAi||s.total===n.total&&s.openAi===n.openAi&&s.anthropic>n.anthropic)&&(n={dir:i,...s})}return n?.dir||null}_parseBuiltinSkillUiMetadata(t,n){let i=Te.join(t,"agents","openai.yaml");if(se.existsSync(i))try{let l=se.readFileSync(i,"utf-8"),u=l.match(/^\s*display_name\s*:\s*(.+)\s*$/m)?.[1]?.trim().replace(/^["']|["']$/g,""),A=l.match(/^\s*short_description\s*:\s*(.+)\s*$/m)?.[1]?.trim().replace(/^["']|["']$/g,"");if(u||A)return{name:u||n.replace(/^openai-/,"").replace(/-/g," ").replace(/\b\w/g,p=>p.toUpperCase()),description:A||""}}catch{}let s=Te.join(t,"SKILL.md");if(se.existsSync(s))try{let l=this._parseSkillMd(se.readFileSync(s,"utf-8"),n);return{name:l.title,description:l.description}}catch{}return{name:n.replace(/^openai-/,"").replace(/-/g," ").replace(/\b\w/g,l=>l.toUpperCase()),description:""}}_buildBuiltinSkillsCatalog(t){let n=[{slug:"pdf",name:"PDF Processing",category:"document",description:"Read, create, merge, split, watermark, encrypt, OCR, and manipulate PDF files.",provider:"anthropic",providerLabel:"Anthropic",exclusiveGroup:"pdf"},{slug:"docx",name:"Word Documents",category:"document",description:"Create, read, edit, and format Word (.docx) documents.",provider:"anthropic",providerLabel:"Anthropic",exclusiveGroup:"docx"},{slug:"xlsx",name:"Spreadsheets",category:"document",description:"Open, read, edit, create, and convert spreadsheet files.",provider:"anthropic",providerLabel:"Anthropic",exclusiveGroup:"xlsx"},{slug:"pptx",name:"Presentations",category:"document",description:"Create, read, edit, and manipulate PowerPoint slide decks.",provider:"anthropic",providerLabel:"Anthropic",exclusiveGroup:"pptx"},{slug:"canvas-design",name:"Canvas Design",category:"design",description:"Create beautiful visual art in PNG and PDF.",provider:"anthropic",providerLabel:"Anthropic",exclusiveGroup:""},{slug:"algorithmic-art",name:"Algorithmic Art",category:"design",description:"Create generative algorithmic art using p5.js.",provider:"anthropic",providerLabel:"Anthropic",exclusiveGroup:""},{slug:"frontend-design",name:"Frontend Design",category:"design",description:"Build distinctive, production-grade frontend interfaces.",provider:"anthropic",providerLabel:"Anthropic",exclusiveGroup:""},{slug:"theme-factory",name:"Theme Factory",category:"design",description:"Apply pre-set or custom themes to slides, docs, and web pages.",provider:"anthropic",providerLabel:"Anthropic",exclusiveGroup:""},{slug:"brand-guidelines",name:"Brand Guidelines",category:"design",description:"Apply official brand colors and typography to artifacts.",provider:"anthropic",providerLabel:"Anthropic",exclusiveGroup:""},{slug:"mcp-builder",name:"MCP Builder",category:"development",description:"Build high-quality MCP servers for LLM integration.",provider:"anthropic",providerLabel:"Anthropic",exclusiveGroup:""},{slug:"web-artifacts-builder",name:"Web Artifacts Builder",category:"development",description:"Create elaborate multi-component HTML artifacts.",provider:"anthropic",providerLabel:"Anthropic",exclusiveGroup:""},{slug:"webapp-testing",name:"Web App Testing",category:"development",description:"Test and debug local web applications using Playwright.",provider:"anthropic",providerLabel:"Anthropic",exclusiveGroup:""},{slug:"skill-creator",name:"Skill Creator",category:"workflow",description:"Create new skills, modify existing ones, run evals.",provider:"anthropic",providerLabel:"Anthropic",exclusiveGroup:"skill-creator"},{slug:"doc-coauthoring",name:"Doc Co-authoring",category:"workflow",description:"Structured workflow for co-authoring documentation.",provider:"anthropic",providerLabel:"Anthropic",exclusiveGroup:""},{slug:"internal-comms",name:"Internal Comms",category:"workflow",description:"Write internal communications: status reports, updates.",provider:"anthropic",providerLabel:"Anthropic",exclusiveGroup:""},{slug:"slack-gif-creator",name:"Slack GIF Creator",category:"utility",description:"Create animated GIFs optimized for Slack.",provider:"anthropic",providerLabel:"Anthropic",exclusiveGroup:""}],i=new Set(["openai-docs","openai-skill-creator","openai-skill-installer"]),s={"openai-pdf":"pdf","openai-doc":"docx","openai-spreadsheet":"xlsx","openai-slides":"pptx","openai-skill-creator":"skill-creator"},l=[];if(t&&se.existsSync(t))try{let u=se.readdirSync(t,{withFileTypes:!0}).filter(A=>A.isDirectory()&&A.name.startsWith("openai-")).sort((A,p)=>A.name.localeCompare(p.name));for(let A of u){let p=Te.join(t,A.name);if(!se.existsSync(Te.join(p,"SKILL.md")))continue;let g=this._parseBuiltinSkillUiMetadata(p,A.name);l.push({slug:A.name,name:g.name,category:i.has(A.name)?"system":"curated",description:g.description,provider:"openai",providerLabel:"Open AI",exclusiveGroup:s[A.name]||""})}}catch{}return[...n,...l]}_disableBuiltinSkillTarget(t,n){let i=Te.join(t,".koda","skills",n);return se.existsSync(i)?se.existsSync(Te.join(i,".builtin"))?(se.rmSync(i,{recursive:!0,force:!0}),{success:!0}):{error:`Folder .koda/skills/${n}/ exists but was not installed by Koda.`}:{success:!0,message:"Already disabled."}}_extractWorkspaceConfigDescription(t){let n=t.trimStart(),i=t;if(n.startsWith("---")){let s=n.indexOf("---",3);if(s>0){for(let l of n.slice(3,s).split(`
598
598
  `))if(l.toLowerCase().trim().startsWith("description"))return l.split(":",2)[1]?.trim()||"";i=n.slice(s+3).replace(/^\n+/,"")}}for(let s of i.split(`
599
599
  `)){let l=s.trim();if(l.startsWith("#"))return l.replace(/^#+\s*/,"");if(l)return l.slice(0,80)}return""}_listWorkspaceConfigEntries(t,n){let i=Te.join(t,".koda",n);if(!se.existsSync(i))return[];if(n!=="skills"){let u=[];for(let A of se.readdirSync(i).sort()){let p=Te.join(i,A);if(!A.endsWith(".md")||!se.existsSync(p)||!se.statSync(p).isFile())continue;let g="";try{g=this._extractWorkspaceConfigDescription(se.readFileSync(p,"utf-8"))}catch{}u.push({name:A,description:g})}return u}let s=[],l=new Set;for(let u of se.readdirSync(i).sort()){let A=Te.join(i,u);if(!u.endsWith(".md")||!se.existsSync(A)||!se.statSync(A).isFile())continue;let p=u.replace(/\.md$/,"");l.add(p);let g="";try{g=this._extractWorkspaceConfigDescription(se.readFileSync(A,"utf-8"))}catch{}s.push({name:u,description:g,isBuiltin:!1,isFolderSkill:!1})}for(let u of se.readdirSync(i,{withFileTypes:!0}).sort((A,p)=>A.name.localeCompare(p.name))){if(!u.isDirectory()||l.has(u.name))continue;let A=Te.join(i,u.name),p=Te.join(A,"SKILL.md");if(!se.existsSync(p))continue;let g="";try{g=this._extractWorkspaceConfigDescription(se.readFileSync(p,"utf-8"))}catch{}s.push({name:u.name,description:g,isBuiltin:se.existsSync(Te.join(A,".builtin")),isFolderSkill:!0})}return s.sort((u,A)=>u.name.localeCompare(A.name))}_resolveWorkspaceConfigTarget(t,n,i){let s=Te.join(t,".koda",n),l=i.replace(/\\/g,"/").replace(/^\/+|\/+$/g,"");if(!l)return null;if(n!=="skills"){let g=Te.join(s,l);return se.existsSync(g)&&se.statSync(g).isFile()?{readPath:g,deletePath:g,isFolderSkill:!1}:null}let u=[],A=Te.join(s,...l.split("/"));if(l.endsWith("/SKILL.md")&&u.push({readPath:A,deletePath:Te.dirname(A),isFolderSkill:!0}),!l.endsWith(".md")&&!l.endsWith("/SKILL.md")){let g=Te.join(s,`${l}.md`),h=Te.join(s,l);u.push({readPath:g,deletePath:g,isFolderSkill:!1}),u.push({readPath:Te.join(h,"SKILL.md"),deletePath:h,isFolderSkill:!0})}if(u.push({readPath:A,deletePath:A,isFolderSkill:!1}),l.endsWith(".md")&&!l.endsWith("/SKILL.md")){let g=l.slice(0,-3),h=Te.join(s,g);u.push({readPath:Te.join(h,"SKILL.md"),deletePath:h,isFolderSkill:!0})}let p=new Set;for(let g of u){let h=`${g.readPath}::${g.deletePath}::${g.isFolderSkill?"1":"0"}`;if(!p.has(h)&&(p.add(h),se.existsSync(g.readPath)&&se.statSync(g.readPath).isFile()))return g}return null}_handleLocalRpc(t,n,i){let s=this._getBuiltinSkillsDir(),l=this._buildBuiltinSkillsCatalog(s);if(t==="list_workspace_config"){let u=n.folder||"";return{files:this._listWorkspaceConfigEntries(i,u)}}if(t==="read_workspace_config"){let u=n.folder||"",A=n.file_name||"",p=this._resolveWorkspaceConfigTarget(i,u,A);return p?{content:se.readFileSync(p.readPath,"utf-8"),name:p.isFolderSkill?Te.basename(p.deletePath):Te.basename(p.readPath),is_folder_skill:p.isFolderSkill,is_builtin:p.isFolderSkill&&se.existsSync(Te.join(p.deletePath,".builtin"))}:{error:"File not found"}}if(t==="save_workspace_config"){let u=n.folder||"",A=n.file_name||"",p=n.content||"",g=Te.join(i,".koda",u);se.mkdirSync(g,{recursive:!0});let h=A.replace(/\\/g,"/").replace(/^\/+|\/+$/g,""),T=Te.join(g,A);if(u==="skills"&&h&&!h.endsWith(".md")&&!h.endsWith("/SKILL.md")){let D=Te.join(g,h),N=Te.join(D,"SKILL.md");T=se.existsSync(N)?N:Te.join(g,`${h}.md`)}else h&&(T=Te.join(g,...h.split("/")));return se.mkdirSync(Te.dirname(T),{recursive:!0}),se.writeFileSync(T,p,"utf-8"),{success:!0}}if(t==="delete_workspace_config"){let u=n.folder||"",A=n.file_name||"",p=this._resolveWorkspaceConfigTarget(i,u,A);return p&&(p.isFolderSkill?se.rmSync(p.deletePath,{recursive:!0,force:!0}):se.existsSync(p.deletePath)&&se.unlinkSync(p.deletePath)),{success:!0}}if(t==="list_builtin_skills")return{skills:l.map(A=>{let p=s?se.existsSync(Te.join(s,A.slug)):!1,g=Te.join(i,".koda","skills",A.slug,"SKILL.md"),h=i?se.existsSync(g):!1;return{...A,enabled:h,available:p}})};if(t==="enable_builtin_skill"){let u=n.slug||"";if(!s)return{error:"Built-in skills source directory not found"};let A=l.find(h=>h.slug===u);if(!A)return{error:`Unknown built-in skill: ${u}`};let p=Te.join(s,u);if(!se.existsSync(p))return{error:`Built-in skill source not found: ${u}`};if(A.exclusiveGroup)for(let h of l){if(h.slug===u||h.exclusiveGroup!==A.exclusiveGroup)continue;let T=Te.join(i,".koda","skills",h.slug);if(se.existsSync(Te.join(T,".builtin"))){let D=this._disableBuiltinSkillTarget(i,h.slug);if(D.error)return D}}let g=Te.join(i,".koda","skills",u);return se.mkdirSync(Te.dirname(g),{recursive:!0}),se.existsSync(g)&&se.rmSync(g,{recursive:!0,force:!0}),se.cpSync(p,g,{recursive:!0}),se.writeFileSync(Te.join(g,".builtin"),`built-in koda skill
600
600
  `),{success:!0,path:g}}if(t==="disable_builtin_skill"){let u=n.slug||"";return this._disableBuiltinSkillTarget(i,u)}}async _handleToolRequest(t){let n=this._chatSessionId||this._sessionId,i,s;try{switch(t.method){case"client.read_file":{let u=t.params.path||"";if(u.includes(".koda/archetypes/")){let T=u.replace(/\\/g,"/"),D=T.match(/\.koda\/archetypes\/(.+)$/);if(D){let N=`.koda/archetypes/${D[1]}`,R=this._archetypeVirtualFiles[N]||this._archetypeVirtualFiles[T];if(R&&!R.startsWith("__b64__:")){let I=t.params.start_line,B=t.params.end_line;if(I||B){let L=R.split(`
@@ -618,7 +618,7 @@ ${T}`:"")).slice(0,5e4)}))})});break}case"client.check_process":{let u=t.params.
618
618
  `).slice(0,50).join(`
619
619
  `):"")||`No usages found for '${u}'`}catch{i=`No usages found for '${u}'`}break}case"client.lsp_diagnostics":case"client.lsp_goto_definition":case"client.lsp_symbols":i=JSON.stringify({diagnostics:[]});break;default:s=`Unknown client method: ${t.method}`}}catch(u){s=u.message||String(u)}let l={id:t.id};s?l.error={code:-1,message:s}:l.result=i||{};try{await this._httpPostWithRetry(`/api/tool-result/${n}`,l,{maxRetries:3,baseDelay:500,label:`tool-result(${t.method}/${t.id})`})}catch(u){console.error(`[TUI Bridge] tool-result POST FAILED after retries: ${t.method} id=${t.id} sid=${n} err=${u.message}`)}}_resolvePath(t){let n=this.cwd,i=t.path||".";return Te.isAbsolute(i)?i:Te.resolve(n,i)}_hashText(t){return VZ("sha256").update(t,"utf8").digest("hex")}_rememberFullRead(t,n){this._agentReadTimes.set(t,Date.now()),this._agentReadState.set(t,{full:!0,sha256:this._hashText(n)})}_noteKnownFileState(t){this._agentReadTimes.set(t,Date.now()),this._agentReadState.delete(t)}_invalidateOverwriteAuthorizations(t=!1){this._agentReadState.clear(),t&&this._agentReadTimes.clear()}_hasMatchingFullRead(t,n){let i=this._agentReadState.get(t);return!!(i?.full&&i.sha256===this._hashText(n))}_getOverwriteGuardError(t){return`Error: Refusing to overwrite existing file ${t}. write_file may replace an existing file only after you have read the FULL current file contents. If you only need a targeted change, use edit_file.`}_getPrewriteGuardError(t,n,i){if(t===n)return null;let s=n.split(`
620
620
  `);for(let A=0;A<s.length;A++){let p=s[A].trim();if(p){for(let g of JZ)if(g.test(p))return`Error: Refusing to write ${i}. Lazy placeholder detected at line ${A+1}: \`${p}\`. Provide the real implementation instead of a placeholder comment.`}}let l=t?t.split(`
621
- `).length:0,u=s.length;if(l>=50&&u>0){let A=u/l;if(A<.35)return`Error: Refusing to write ${i}. Suspicious deletion detected: file would shrink from ${l} lines to ${u} lines (${Math.round(A*100)}% of original). Use targeted edits instead of destructive rewrites.`}return null}_captureCookies(t){let n=t.headers["set-cookie"];if(n)for(let i of Array.isArray(n)?n:[n]){let[s]=i.split(";"),l=s.indexOf("=");if(l<=0)continue;let u=s.slice(0,l).trim(),A=s.slice(l+1).trim();u&&A&&(this._cookies[u]=A)}}_cookieHeader(){let t=Object.entries(this._cookies).map(([n,i])=>`${n}=${i}`);return t.length>0?t.join("; "):""}_shouldSendAuthHeader(){return!!this.authToken}_clearRuntimeSessionCookie(){delete this._cookies.koda_app_session}_httpPost(t,n,i){let s=i?.timeout||6e4;return new Promise((l,u)=>{if(i?.expectedRunId!==void 0&&i.expectedRunId!==this._chatRunId){u(new Error("Cancelled by user."));return}let A=!!i?._isAuthRetry,p=Number(i?._proxyRetryAttempt||0),g=!!this._cookies.koda_app_session,h=`${this.baseUrl}${t}`,T=new URL(h),D=T.protocol==="https:"?Qo:gc,N=wQ.gzipSync(Buffer.from(JSON.stringify(n),"utf-8")),R={"Content-Type":"application/json","Content-Encoding":"gzip","Content-Length":N.length};this._sessionId&&(R["X-Session-ID"]=this._sessionId),this._shouldSendAuthHeader()&&(R.Authorization=`Bearer ${this.authToken}`);let I=this._cookieHeader();I&&(R.Cookie=I);let B=T.protocol==="https:"?Qo.globalAgent:gc.globalAgent,L=D.request(h,{method:"POST",headers:R,agent:B},M=>{if(this._captureCookies(M),M.statusCode===401||M.statusCode===403){this._clearRuntimeSessionCookie();let U="";M.setEncoding("utf-8"),M.on("data",pe=>{U+=pe}),M.on("end",()=>{let pe=`HTTP ${M.statusCode}`;try{let re=JSON.parse(U);re.error&&(pe=`HTTP ${M.statusCode}: ${re.error}`)}catch{}if(M.statusCode===401&&g&&!A&&this.authToken){this._httpPost(t,n,{...i,_isAuthRetry:!0}).then(l,u);return}if(M.statusCode===401&&!A&&this._tokenRefresher){this._refreshAuth().then(re=>{re?(this.authToken=re,this._httpPost(t,n,{...i,_isAuthRetry:!0}).then(l,u)):(this.emit("auth_required",{status:M.statusCode,message:pe}),u(new Error(pe)))}).catch(()=>{this.emit("auth_required",{status:M.statusCode,message:pe}),u(new Error(pe))});return}this.emit("auth_required",{status:M.statusCode,message:pe}),u(new Error(pe))});return}if((M.statusCode||0)<200||(M.statusCode||0)>=300){let U="";M.setEncoding("utf-8"),M.on("data",pe=>{U+=pe}),M.on("end",()=>{let pe=M.statusCode||0;if(v_(pe,U)&&p<R_){let j=b_(p+1);setTimeout(()=>{this._httpPost(t,n,{...i,_proxyRetryAttempt:p+1}).then(l,u)},j);return}let re=`HTTP ${pe}`;try{let j=JSON.parse(U);j.error&&(re=`HTTP ${pe}: ${j.error}`)}catch{let j=U.replace(/\s+/g," ").trim().slice(0,240);j&&(re=`HTTP ${pe} \u2014 ${j}`)}u(new Error(re))});return}let P="";M.setEncoding("utf-8"),M.on("data",U=>{P+=U}),M.on("end",()=>{try{l(JSON.parse(P))}catch{l({result:P})}})});if(L.on("error",M=>{if(v_(void 0,M.message)&&p<R_){let P=b_(p+1);setTimeout(()=>{this._httpPost(t,n,{...i,_proxyRetryAttempt:p+1}).then(l,u)},P);return}u(M)}),L.setTimeout(s,()=>{L.destroy(new Error(`HTTP request timeout (${s}ms)`))}),i?.trackSet){let M=i.trackSet;M.add(L);let P=()=>M.delete(L);L.on("close",P),L.on("error",P)}i?.trackAs&&(this[i.trackAs]=L),L.write(N),L.end()})}async _httpPostWithRetry(t,n,i={}){let s=i.maxRetries||3,l=i.baseDelay||500,u=i.label||t,A;for(let p=0;p<=s;p++)try{let g=await this._httpPost(t,n);if(g&&g.error){let h=typeof g.error=="string"?g.error:g.error.message||JSON.stringify(g.error);if(h.includes("Session not found"))return console.error(`[TUI Bridge] ${u} attempt ${p+1}: session destroyed (${h})`),g;if(p<s){let T=Math.min(l*Math.pow(2,p),8e3);console.warn(`[TUI Bridge] ${u} attempt ${p+1} server error: ${h}, retrying in ${T}ms`),await new Promise(D=>setTimeout(D,T));continue}}return p>0&&console.log(`[TUI Bridge] ${u} succeeded on retry attempt ${p+1}`),g}catch(g){if(A=g,p<s){let h=Math.min(l*Math.pow(2,p),8e3);console.warn(`[TUI Bridge] ${u} attempt ${p+1} failed: ${g.message}, retrying in ${h}ms`),await new Promise(T=>setTimeout(T,h))}else console.error(`[TUI Bridge] ${u} FAILED after ${s+1} attempts: ${g.message}`)}throw A}async shutdown(){this._shutdownRequested=!0,this.stopActiveChat()}setAuthToken(t){this.authToken=t||""}trackUsage(t,n){if(!this.authToken)return;let i=this.baseUrl.replace(/\/koda\/?$/,"");if(!i)return;let s=`${i}/api/v1/koda/usage/prompt`;try{let l=new URL(s),u=l.protocol==="https:"?Qo:gc,A=JSON.stringify({model:t,mode:n,client:"tui"}),p=l.protocol==="https:"?new Qo.Agent({rejectUnauthorized:!1}):void 0,g=u.request(s,{method:"POST",headers:{"Content-Type":"application/json","Content-Length":Buffer.byteLength(A),Authorization:`Bearer ${this.authToken}`},agent:p},()=>{});g.on("error",()=>{}),g.write(A),g.end()}catch{}}_refreshAuth(){return this._tokenRefresher?this._authRefreshPromise?this._authRefreshPromise:(this._authRefreshPromise=this._tokenRefresher().catch(()=>null).finally(()=>{this._authRefreshPromise=null}),this._authRefreshPromise):Promise.resolve(null)}isAlive(){return this._started&&!this._shutdownRequested}async chat(t){this._chatRunning=!0,this._invalidateOverwriteAuthorizations(),this._chatPersistentId||(this._chatPersistentId=`chat-${Date.now()}-${Math.random().toString(36).slice(2,10)}`);let n=this._buildProjectInstructionsContent(),i;if(!this.chatMessages.length)try{let u=t.working_directory||this.cwd,A=Xu("crypto").createHash("md5").update(u).digest("hex").slice(0,12),p=Te.basename(u)||"default",g=Te.join(oE.homedir(),".koda","workspaces",`${p}_${A}`,"workspace-map.md");se.existsSync(g)&&(i=se.readFileSync(g,"utf-8"))}catch{}let s=await this._httpPost("/api/chat",{text:t.text,messages:this.chatMessages,model:t.model||"gpt-5.2",mode:t.mode||"code",working_directory:t.working_directory||this.cwd,auto_mode:t.auto_mode!==void 0?t.auto_mode:!0,images:t.images,inline_images:t.inline_images,context_files:t.context_files,prefetched_context:t.prefetched_context,active_file:t.active_file,session_id:this._chatPersistentId,...i?{workspace_map:i}:{},...t.workflow_instructions?{workflow_instructions:t.workflow_instructions}:{},...n?{rules_content:n}:{}}),l=s?.session_id;if(!l)throw new Error(s?.error?.message||s?.error||"No session_id from POST /api/chat");return this._chatSessionId=l,this._sessionId=l,new Promise((u,A)=>{let p=`${this.baseUrl}/api/chat/events/${l}`,g=(h=0)=>{let T=new URL(p),D=T.protocol==="https:"?Qo:gc,N={Accept:"text/event-stream"};this._shouldSendAuthHeader()&&(N.Authorization=`Bearer ${this.authToken}`);let R=this._cookieHeader();R&&(N.Cookie=R);let I=T.protocol==="https:"?Qo.globalAgent:gc.globalAgent,B=D.request(p,{method:"GET",headers:N,agent:I},L=>{if(this._captureCookies(L),L.statusCode!==200){let P="";L.setEncoding("utf-8"),L.on("data",U=>{P+=U}),L.on("end",()=>{if(v_(L.statusCode,P)&&h<R_){let U=b_(h+1);setTimeout(()=>g(h+1),U);return}this._cleanupChat(),A(new Error(`Chat events: HTTP ${L.statusCode}`))});return}L.setEncoding("utf-8");let M="";L.on("data",P=>{M+=P;let U=M.split(`
621
+ `).length:0,u=s.length;if(l>=50&&u>0){let A=u/l;if(A<.35)return`Error: Refusing to write ${i}. Suspicious deletion detected: file would shrink from ${l} lines to ${u} lines (${Math.round(A*100)}% of original). Use targeted edits instead of destructive rewrites.`}return null}_captureCookies(t){let n=t.headers["set-cookie"];if(n)for(let i of Array.isArray(n)?n:[n]){let[s]=i.split(";"),l=s.indexOf("=");if(l<=0)continue;let u=s.slice(0,l).trim(),A=s.slice(l+1).trim();u&&A&&(this._cookies[u]=A)}}_cookieHeader(){let t=Object.entries(this._cookies).map(([n,i])=>`${n}=${i}`);return t.length>0?t.join("; "):""}_shouldSendAuthHeader(){return!!this.authToken}_clearRuntimeSessionCookie(){delete this._cookies.koda_app_session}_httpPost(t,n,i){let s=i?.timeout||6e4;return new Promise((l,u)=>{if(i?.expectedRunId!==void 0&&i.expectedRunId!==this._chatRunId){u(new Error("Cancelled by user."));return}let A=!!i?._isAuthRetry,p=Number(i?._proxyRetryAttempt||0),g=!!this._cookies.koda_app_session,h=`${this.baseUrl}${t}`,T=new URL(h),D=T.protocol==="https:"?Qo:gc,N=wQ.gzipSync(Buffer.from(JSON.stringify(n),"utf-8")),R={"Content-Type":"application/json","Content-Encoding":"gzip","Content-Length":N.length};this._sessionId&&(R["X-Session-ID"]=this._sessionId),this._shouldSendAuthHeader()&&(R.Authorization=`Bearer ${this.authToken}`);let I=this._cookieHeader();I&&(R.Cookie=I);let B=T.protocol==="https:"?Qo.globalAgent:gc.globalAgent,L=D.request(h,{method:"POST",headers:R,agent:B},M=>{if(this._captureCookies(M),M.statusCode===401||M.statusCode===403){this._clearRuntimeSessionCookie();let U="";M.setEncoding("utf-8"),M.on("data",pe=>{U+=pe}),M.on("end",()=>{let pe=`HTTP ${M.statusCode}`;try{let re=JSON.parse(U);re.error&&(pe=`HTTP ${M.statusCode}: ${re.error}`)}catch{}if(M.statusCode===401&&g&&!A&&this.authToken){this._httpPost(t,n,{...i,_isAuthRetry:!0}).then(l,u);return}if(M.statusCode===401&&!A&&this._tokenRefresher){this._refreshAuth().then(re=>{re?(this.authToken=re,this._httpPost(t,n,{...i,_isAuthRetry:!0}).then(l,u)):(this.emit("auth_required",{status:M.statusCode,message:pe}),u(new Error(pe)))}).catch(()=>{this.emit("auth_required",{status:M.statusCode,message:pe}),u(new Error(pe))});return}this.emit("auth_required",{status:M.statusCode,message:pe}),u(new Error(pe))});return}if((M.statusCode||0)<200||(M.statusCode||0)>=300){let U="";M.setEncoding("utf-8"),M.on("data",pe=>{U+=pe}),M.on("end",()=>{let pe=M.statusCode||0;if(v_(pe,U)&&p<R_){let j=b_(p+1);setTimeout(()=>{this._httpPost(t,n,{...i,_proxyRetryAttempt:p+1}).then(l,u)},j);return}let re=`HTTP ${pe}`;try{let j=JSON.parse(U);j.error&&(re=`HTTP ${pe}: ${j.error}`)}catch{let j=U.replace(/\s+/g," ").trim().slice(0,240);j&&(re=`HTTP ${pe} \u2014 ${j}`)}u(new Error(re))});return}let P="";M.setEncoding("utf-8"),M.on("data",U=>{P+=U}),M.on("end",()=>{try{l(JSON.parse(P))}catch{l({result:P})}})});if(L.on("error",M=>{if(v_(void 0,M.message)&&p<R_){let P=b_(p+1);setTimeout(()=>{this._httpPost(t,n,{...i,_proxyRetryAttempt:p+1}).then(l,u)},P);return}u(M)}),L.setTimeout(s,()=>{L.destroy(new Error(`HTTP request timeout (${s}ms)`))}),i?.trackSet){let M=i.trackSet;M.add(L);let P=()=>M.delete(L);L.on("close",P),L.on("error",P)}i?.trackAs&&(this[i.trackAs]=L),L.write(N),L.end()})}async _httpPostWithRetry(t,n,i={}){let s=i.maxRetries||3,l=i.baseDelay||500,u=i.label||t,A;for(let p=0;p<=s;p++)try{let g=await this._httpPost(t,n);if(g&&g.error){let h=typeof g.error=="string"?g.error:g.error.message||JSON.stringify(g.error);if(h.includes("Session not found"))return console.error(`[TUI Bridge] ${u} attempt ${p+1}: session destroyed (${h})`),g;if(p<s){let T=Math.min(l*Math.pow(2,p),8e3);console.warn(`[TUI Bridge] ${u} attempt ${p+1} server error: ${h}, retrying in ${T}ms`),await new Promise(D=>setTimeout(D,T));continue}}return p>0&&console.log(`[TUI Bridge] ${u} succeeded on retry attempt ${p+1}`),g}catch(g){if(A=g,p<s){let h=Math.min(l*Math.pow(2,p),8e3);console.warn(`[TUI Bridge] ${u} attempt ${p+1} failed: ${g.message}, retrying in ${h}ms`),await new Promise(T=>setTimeout(T,h))}else console.error(`[TUI Bridge] ${u} FAILED after ${s+1} attempts: ${g.message}`)}throw A}async shutdown(){this._shutdownRequested=!0,this.stopActiveChat()}setAuthToken(t){this.authToken=t||""}trackUsage(t,n){if(!this.authToken)return;let i=this.baseUrl.replace(/\/koda\/?$/,"");if(!i)return;let s=`${i}/api/v1/koda/usage/prompt`;try{let l=new URL(s),u=l.protocol==="https:"?Qo:gc,A=JSON.stringify({model:t,mode:n,client:"tui"}),p=l.protocol==="https:"?new Qo.Agent({rejectUnauthorized:!1}):void 0,g=u.request(s,{method:"POST",headers:{"Content-Type":"application/json","Content-Length":Buffer.byteLength(A),Authorization:`Bearer ${this.authToken}`},agent:p},()=>{});g.on("error",()=>{}),g.write(A),g.end()}catch{}}_refreshAuth(){return this._tokenRefresher?this._authRefreshPromise?this._authRefreshPromise:(this._authRefreshPromise=this._tokenRefresher().catch(()=>null).finally(()=>{this._authRefreshPromise=null}),this._authRefreshPromise):Promise.resolve(null)}isAlive(){return this._started&&!this._shutdownRequested}async chat(t){this._chatRunning=!0,this._invalidateOverwriteAuthorizations(),this._chatPersistentId||(this._chatPersistentId=`chat-${Date.now()}-${Math.random().toString(36).slice(2,10)}`);let n=this._buildProjectInstructionsContent(),i;if(!this.chatMessages.length)try{let u=t.working_directory||this.cwd,A=Xu("crypto").createHash("md5").update(u).digest("hex").slice(0,12),p=Te.basename(u)||"default",g=Te.join(oE.homedir(),".koda","workspaces",`${p}_${A}`,"workspace-map.md");se.existsSync(g)&&(i=se.readFileSync(g,"utf-8"))}catch{}let s=await this._httpPost("/api/chat",{text:t.text,messages:this.chatMessages,model:t.model||"gpt-5.2",mode:t.mode||"code",working_directory:t.working_directory||this.cwd,auto_mode:t.auto_mode!==void 0?t.auto_mode:!0,client_platform:process.platform,images:t.images,inline_images:t.inline_images,context_files:t.context_files,prefetched_context:t.prefetched_context,active_file:t.active_file,session_id:this._chatPersistentId,...i?{workspace_map:i}:{},...t.workflow_instructions?{workflow_instructions:t.workflow_instructions}:{},...n?{rules_content:n}:{}}),l=s?.session_id;if(!l)throw new Error(s?.error?.message||s?.error||"No session_id from POST /api/chat");return this._chatSessionId=l,this._sessionId=l,new Promise((u,A)=>{let p=`${this.baseUrl}/api/chat/events/${l}`,g=(h=0)=>{let T=new URL(p),D=T.protocol==="https:"?Qo:gc,N={Accept:"text/event-stream"};this._shouldSendAuthHeader()&&(N.Authorization=`Bearer ${this.authToken}`);let R=this._cookieHeader();R&&(N.Cookie=R);let I=T.protocol==="https:"?Qo.globalAgent:gc.globalAgent,B=D.request(p,{method:"GET",headers:N,agent:I},L=>{if(this._captureCookies(L),L.statusCode!==200){let P="";L.setEncoding("utf-8"),L.on("data",U=>{P+=U}),L.on("end",()=>{if(v_(L.statusCode,P)&&h<R_){let U=b_(h+1);setTimeout(()=>g(h+1),U);return}this._cleanupChat(),A(new Error(`Chat events: HTTP ${L.statusCode}`))});return}L.setEncoding("utf-8");let M="";L.on("data",P=>{M+=P;let U=M.split(`
622
622
 
623
623
  `);M=U.pop()||"";for(let pe of U){if(!pe.trim())continue;let re="message",j="";for(let oe of pe.split(`
624
624
  `))if(oe.startsWith("event: "))re=oe.slice(7).trim();else if(oe.startsWith("data: "))j+=(j?`
@@ -730,7 +730,7 @@ Follow these project-level instructions and constraints on every task. Precedenc
730
730
  `)}_buildCustomAgentContent(t){if(["code","plan","ask","studio","app_maker","koda-swe","cua"].includes(t))return"";let n=Te.join(this.cwd,".koda","agents",`${t}.md`);try{return se.existsSync(n)?se.readFileSync(n,"utf-8"):""}catch{return""}}async chatV2(t){let n=++this._chatRunId,i=()=>n===this._chatRunId,s=new Error("__chat_run_aborted__"),l=()=>{if(!i()||this._chatCancelled||this._chatComplete)throw s};this._chatRunning=!0,this._chatCancelled=!1,this._chatComplete=!1,this._lastModel=t.model||"auto",this._lastMode=t.mode||"code",this._invalidateOverwriteAuthorizations(),this._chatPersistentId||(this._chatPersistentId=`chat-${Date.now()}-${Math.random().toString(36).slice(2,10)}`);let u=this._chatTurnState||{},A=[...this.chatMessages],p=[],g=()=>{this.chatMessages=A,this._chatTurnState=u,this._persistChat(A,u)};if(t.text||t.inline_images?.length||t.images?.length){let L=t.text||"";if(!t.inline_images?.length&&!t.images?.length)A.push({role:"user",content:L});else{let M=[];L&&M.push({type:"text",text:L});for(let P of t.inline_images||[]){let U=P.data||P.base64||"";U&&M.push({type:"image_url",image_url:{url:`data:${P.mimeType||"image/png"};base64,${U}`}})}for(let P of t.images||[])try{let U=Te.isAbsolute(P)?P:Te.resolve(this.cwd,P),pe=se.readFileSync(U).toString("base64"),re=Te.extname(P).toLowerCase(),j={".png":"image/png",".jpg":"image/jpeg",".jpeg":"image/jpeg",".gif":"image/gif",".webp":"image/webp"};M.push({type:"image_url",image_url:{url:`data:${j[re]||"image/png"};base64,${pe}`}})}catch{}A.push({role:"user",content:M.length>0?M:L})}}let h=await this._buildLocalContext(t.text||"");if(l(),t.context_files?.length)for(let L of t.context_files)try{let M=Te.isAbsolute(L)?L:Te.resolve(this.cwd,L),P=se.readFileSync(M,"utf-8"),U=this._generateFileSkeleton(P,L);U&&h.push({role:"user",content:`<system-reminder>
731
731
  CONTEXT FILE: ${L}
732
732
  ${U}
733
- </system-reminder>`})}catch{}if(t.prefetched_context?.length&&h.push(...t.prefetched_context),this.emit("event","agent_started",{}),t.inline_images?.length||t.images?.length){let L=(t.inline_images?.length||0)+(t.images?.length||0);this.emit("event","system_event",{message:`Analyzing ${L>1?L+" images":"image"} with vision sub-agent\u2026`})}let T=null,D=0,N=0,R=!1;if(A=this._sanitizeMessages(A),p=A.slice(this.chatMessages.length),g(),this._chatPersistentId){try{await this._httpPost(`/api/turn/stop/${this._chatPersistentId}`,{},{timeout:5e3})}catch{}l()}let I=200,B=async(L={})=>{let M=!!(L.forceBootstrap||!this._chatServerStateReady),P=this._sanitizeMessages([...L.deltaMessages||p]),U={session_id:this._chatPersistentId,model:t.model||"gpt-5.2",mode:t.mode||"code",working_directory:t.working_directory||this.cwd,auto_mode:L.autoMode!==void 0?L.autoMode:t.auto_mode!==void 0?t.auto_mode:!0,active_file:t.active_file,context_mode:t.context_mode||"standard"};t.workflow_instructions&&(U.workflow_instructions=t.workflow_instructions);let pe=this._buildSkillsCatalog();pe&&(U.skills_catalog=pe);let re=this._buildProjectInstructionsContent();re&&(U.rules_content=re);let j=this._buildCustomAgentContent(U.mode);j&&(U.custom_agent_content=j),M&&h&&h.length>0&&(U.prefetched_context=h),M?(U.messages=this._sanitizeMessages(A),U.turn_state=u):P.length>0&&(U.delta_messages=P),!M&&T?.length&&(U.tool_results=this._serializeToolResultsForServer(T));let oe=await this._httpPost("/api/turn",U,{timeout:3e5,trackAs:"_activeV2Request"});return this._activeV2Request=null,l(),oe?.error==="session_not_found"&&oe?.rehydrate_required&&!M&&(this._chatServerStateReady=!1,oe=await this._httpPost("/api/turn",{...U,messages:this._sanitizeMessages(A),turn_state:u,delta_messages:void 0,tool_results:void 0},{timeout:3e5,trackAs:"_activeV2Request"}),this._activeV2Request=null,l()),oe?.error||(this._chatServerStateReady=!0,p=[],T=null),oe};try{for(let L=0;L<I&&!(this._chatCancelled||this._chatComplete);L++){R&&(this._invalidateOverwriteAuthorizations(),R=!1),A=this._stripEphemeralMessages(A),g();let M;try{if(this._v2TurnActive=!0,M=await B(),l(),this._chatCancelled){this._v2TurnActive=!1;break}L===0&&this._connectV2SSE()}catch(oe){if(this._v2TurnActive=!1,this._activeV2Request=null,oe===s||!i()||this._chatCancelled)break;this._chatServerStateReady=!1,this.emit("event","error",{content:E0(oe.message)}),this._chatComplete=!0;break}if(M.error){if(/turn_in_progress/i.test(String(M.error))){try{await this._httpPost(`/api/turn/stop/${this._chatPersistentId}`,{},{timeout:5e3})}catch{}if(!i()||(this._chatServerStateReady=!1,await new Promise(oe=>setTimeout(oe,1e3)),!i()))break;continue}if(M.error==="context_window_exceeded"&&M.compact_needed)try{let oe=await this._httpPost("/api/compact",{messages:A,working_directory:this.cwd},{timeout:12e4});l(),A=oe.messages||A,u=M.turn_state||u,this._invalidateOverwriteAuthorizations(),this._chatServerStateReady=!1,p=[],T=null,g();continue}catch(oe){if(oe===s)throw oe;this.emit("event","error",{content:`Compaction failed: ${oe.message}`}),this._chatComplete=!0;break}this._chatServerStateReady=!1,this.emit("event","error",{content:E0(M.error)}),this._chatComplete=!0;break}u=M.turn_state||u;for(let oe of M.thinking_blocks||[])this.emit("event","reasoning",{content:oe,is_thinking:!0});M.context_usage&&this.emit("event","context_usage",M.context_usage),M.auto_route&&this.emit("event","auto_route",M.auto_route);for(let oe of M.system_events||[])this.emit("event","system_event",{message:oe.message||oe.event});for(let oe of M.system_injections||[])A.push(oe);if(M.compact_needed&&!M.error)try{let oe=await this._httpPost("/api/compact",{messages:A,working_directory:this.cwd},{timeout:12e4});l(),A=oe.messages||A,R=!0,this._chatServerStateReady=!1,p=[],T=null,u.compact_cooldown!==void 0&&(u.compact_cooldown=3),g()}catch(oe){if(oe===s)throw oe}if(M.validation_actions?.length>0&&this._applyValidationActions(M.validation_actions,A),!M.assistant_message){this._chatComplete=!0;break}if(A.push(M.assistant_message),g(),(!M.tool_calls||M.tool_calls.length===0)&&(!M.pre_resolved_tools||M.pre_resolved_tools.length===0)){if(this._clearPendingToolCalls(),M.needs_continuation){A.push({role:"user",content:"[SYSTEM] Your previous response was interrupted mid-stream and your tool call was lost. The user did NOT see a tool result. Please re-attempt your intended action."}),this._chatServerStateReady=!1,p=[],T=null,g();continue}let oe=M.assistant_message.content||"";if(!oe.trim()&&N<3){N++,A.length>0&&A[A.length-1]===M.assistant_message&&A.pop(),A.push({role:"user",content:"[SYSTEM] You returned an empty response with no tool calls. The user is still waiting. If your previous tool results were empty or unhelpful, try a different approach \u2014 broaden your search, try different keywords, check different directories, or use alternative tools. Do NOT stop silently. You MUST either continue working or respond to the user explaining what you found and what you'll try next."}),this._chatServerStateReady=!1,p=[{role:"user",content:A[A.length-1].content}],g();continue}this.emit("event","final_response",{content:oe||"(The agent completed without generating a response.)"}),this._chatComplete=!0;break}if(this._setPendingToolCalls(M.tool_calls),M.assistant_message.content&&(this.emit("event","reasoning",{content:M.assistant_message.content}),await new Promise(oe=>setTimeout(oe,80)),l()),this._v2TurnActive=!1,!(t.auto_mode!==void 0?t.auto_mode:!0)&&M.tool_calls?.length>0){let oe=M.tool_calls.filter(J=>["run_terminal_command","write_file","edit_file","multi_edit_file","regex_replace"].includes(J.function.name));if(oe.length>0){let J=oe.map(Oe=>{let Ce;try{Ce=JSON.parse(Oe.function.arguments)}catch{Ce={}}return{tool:Oe.function.name,args:Ce,id:Oe.id}});this.emit("event","tool_approval_request",{tools:J})}}if(N=0,M.pre_resolved_tools?.length)for(let oe of M.pre_resolved_tools)A.push({role:"tool",tool_call_id:oe.tool_call_id,name:oe.name,content:oe.content}),this.emit("event","tool_result",{tool:oe.name,tool_call_id:oe.tool_call_id,content:oe.content});let U=[],{batches:pe,dedupMap:re}=this._categorizeToolBatches(M.tool_calls);this._setPendingToolCallAliases(re);for(let oe of pe){if(this._chatCancelled)break;let J=[];for(let De of oe){D++;let we;try{we=JSON.parse(De.function.arguments)}catch{we={}}J.push({stepId:D,args:we}),this.emit("event","tool_start",{tool:De.function.name,args:we,tool_call_id:De.id,step_id:D})}await this._yieldToUi(),l();let Oe=async De=>{let we=De?.function?.name||"tool";try{let yt=await this._executeToolLocally(De);return this._recordPendingToolResult(De?.id,we,yt?.content||"(no output)"),yt}catch(yt){throw this._recordPendingToolResult(De?.id,we,`Error: ${yt?.message||"Tool execution failed"}`),yt}},Ce;if(oe.length===1)try{Ce=[{status:"fulfilled",value:await Oe(oe[0])}]}catch(De){Ce=[{status:"rejected",reason:De}]}else Ce=await Promise.allSettled(oe.map(De=>Oe(De)));l(),await this._yieldToUi(),l();for(let De=0;De<oe.length;De++){let we=oe[De],{stepId:yt,args:le}=J[De],dt=Ce[De],Ze=dt.status==="fulfilled"?dt.value:{content:`Error: ${dt.reason?.message||"Tool execution failed"}`};A.push({role:"tool",tool_call_id:we.id,name:we.function.name,content:Ze.content||"(no output)"}),g(),this._markToolCallCompleted(we.id),U.push({tool_call_id:we.id,name:we.function.name,content:Ze.content||"(no output)",args:le,...Ze.file_modified?{file_snapshot:Ze.file_modified}:{},...Ze.multi_file_snapshots?{multi_file_snapshots:Ze.multi_file_snapshots}:{}}),this.emit("event","tool_end",{tool:we.function.name,args:le,tool_call_id:we.id,result:(Ze.content||"").slice(0,200),step_id:yt}),Ze.file_modified&&this.emit("event","file_modified",{...Ze.file_modified,step_id:yt}),Ze.command_output&&this.emit("event","command_output",Ze.command_output),we.function.name==="generate_image"&&Ze.content&&!String(Ze.content).startsWith("Error")&&this.emit("event","image_generated",{content:Ze.content,step_id:yt})}}if(re.size>0){let oe=new Map;for(let J of A)J.role==="tool"&&J.tool_call_id&&oe.set(J.tool_call_id,J.content);for(let J of M.tool_calls){let Oe=re.get(J.id);if(Oe){let Ce=oe.get(Oe)||"(deduplicated)",De;try{De=JSON.parse(J.function.arguments)}catch{De={}}A.push({role:"tool",tool_call_id:J.id,name:J.function.name,content:Ce}),this._markToolCallCompleted(J.id),U.push({tool_call_id:J.id,name:J.function.name,content:Ce,args:De})}}}if(this._chatCancelled&&M.tool_calls){let oe=new Set(U.map(J=>J.tool_call_id));for(let J of M.tool_calls)oe.has(J.id)||A.push({role:"tool",tool_call_id:J.id,name:J.function.name,content:"Tool execution cancelled by user."});g(),this._clearPendingToolCalls()}if(U.some(oe=>["write_file","edit_file","multi_edit_file","regex_replace"].includes(oe.name)&&(oe.file_snapshot||oe.multi_file_snapshots?.length))&&!this._chatCancelled)try{let{validateToolResults:oe}=(BQ(),I3(OQ)),J=this._buildProjectInstructionsContent(),Oe=await oe(U,this._validationConfig,{httpPost:(Ce,De,we)=>this._httpPost(Ce,Ce==="/api/turn"&&J?{...De,rules_content:J}:De,we),cwd:this.cwd,model:this._lastModel,isCancelled:()=>this._chatCancelled});l(),Oe.length>0&&this._applyValidationActions(Oe,A);for(let Ce of U)(Ce.file_snapshot||Ce.multi_file_snapshots?.length)&&["write_file","edit_file","multi_edit_file","regex_replace"].includes(Ce.name)&&(Ce._client_validated=!0)}catch(oe){console.warn(`[V2 ClientValidation] ${oe.message}`)}for(let oe of U){let J=A.find(Oe=>Oe.role==="tool"&&Oe.tool_call_id===oe.tool_call_id);J?.content&&(oe.content=J.content)}if(T=U,!this._chatCancelled){let oe=new Set(["codebase_search","search_files","multi_search","list_code_usages"]),J=[];for(let Oe of U){if(!oe.has(Oe.name)||!Oe.content||Oe.content.startsWith("Error"))continue;if(J.length>=2)break;let Ce=/(?:^|\bin )((?:[\w.\/-]+\/)?[\w.\-]+\.(?:py|ts|tsx|js|jsx|rs|go|java|rb|c|cpp|h|hpp|css|html|json|yaml|yml|toml|sql|sh))\b/gm,De;for(;(De=Ce.exec(Oe.content))!==null&&J.length<2;){let we=De[1];if(!A.some(le=>le.role!=="assistant"||!Array.isArray(le.tool_calls)?!1:le.tool_calls.some(dt=>{if(!dt.function||dt.function.name!=="read_file")return!1;try{let St=JSON.parse(dt.function.arguments||"{}").path||"";return St===we||St.endsWith("/"+we)||we.endsWith("/"+St)}catch{return!1}}))&&!J.some(le=>le.path===we))try{let le=Te.isAbsolute(we)?we:Te.resolve(this.cwd,we),dt=se.readFileSync(le,"utf8");dt&&dt.length>0&&dt.length<5e4&&J.push({path:we,content:dt})}catch{}}}if(J.length>0){let Oe=J.map((De,we)=>({id:`rule_read_${Date.now()}_${we}_${Math.random().toString(36).slice(2,8)}`,type:"function",function:{name:"read_file",arguments:JSON.stringify({path:De.path})}})),Ce={role:"assistant",content:null,tool_calls:Oe};A.push(Ce);for(let De=0;De<J.length;De++){let we=J[De],yt=Oe[De].id,le={role:"tool",tool_call_id:yt,name:"read_file",content:we.content};A.push(le),U.push({tool_call_id:yt,name:"read_file",content:we.content,args:{path:we.path}}),D++,this.emit("event","tool_start",{tool:"read_file",args:{path:we.path},tool_call_id:yt,step_id:D}),this.emit("event","tool_end",{tool:"read_file",args:{path:we.path},tool_call_id:yt,result:we.content.slice(0,200),step_id:D})}this._chatServerStateReady=!1}}g(),this.emit("event","history_update",{messages:A,message_count:A.length,token_estimate:Ch(A),token_limit:Xc(this._lastModel)}),u.context_dirty&&(u.context_dirty=!1),A=this._sanitizeMessages(A)}if(!this._chatCancelled&&!this._chatComplete)try{A.push({role:"user",content:"[SYSTEM] You have reached the maximum turn limit ("+I+` turns). You MUST stop all tool use and respond to the user NOW.
733
+ </system-reminder>`})}catch{}if(t.prefetched_context?.length&&h.push(...t.prefetched_context),this.emit("event","agent_started",{}),t.inline_images?.length||t.images?.length){let L=(t.inline_images?.length||0)+(t.images?.length||0);this.emit("event","system_event",{message:`Analyzing ${L>1?L+" images":"image"} with vision sub-agent\u2026`})}let T=null,D=0,N=0,R=!1;if(A=this._sanitizeMessages(A),p=A.slice(this.chatMessages.length),g(),this._chatPersistentId){try{await this._httpPost(`/api/turn/stop/${this._chatPersistentId}`,{},{timeout:5e3})}catch{}l()}let I=200,B=async(L={})=>{let M=!!(L.forceBootstrap||!this._chatServerStateReady),P=this._sanitizeMessages([...L.deltaMessages||p]),U={session_id:this._chatPersistentId,model:t.model||"gpt-5.2",mode:t.mode||"code",working_directory:t.working_directory||this.cwd,auto_mode:L.autoMode!==void 0?L.autoMode:t.auto_mode!==void 0?t.auto_mode:!0,active_file:t.active_file,context_mode:t.context_mode||"standard",client_platform:process.platform};t.workflow_instructions&&(U.workflow_instructions=t.workflow_instructions);let pe=this._buildSkillsCatalog();pe&&(U.skills_catalog=pe);let re=this._buildProjectInstructionsContent();re&&(U.rules_content=re);let j=this._buildCustomAgentContent(U.mode);j&&(U.custom_agent_content=j),M&&h&&h.length>0&&(U.prefetched_context=h),M?(U.messages=this._sanitizeMessages(A),U.turn_state=u):P.length>0&&(U.delta_messages=P),!M&&T?.length&&(U.tool_results=this._serializeToolResultsForServer(T));let oe=await this._httpPost("/api/turn",U,{timeout:3e5,trackAs:"_activeV2Request"});return this._activeV2Request=null,l(),oe?.error==="session_not_found"&&oe?.rehydrate_required&&!M&&(this._chatServerStateReady=!1,oe=await this._httpPost("/api/turn",{...U,messages:this._sanitizeMessages(A),turn_state:u,delta_messages:void 0,tool_results:void 0},{timeout:3e5,trackAs:"_activeV2Request"}),this._activeV2Request=null,l()),oe?.error||(this._chatServerStateReady=!0,p=[],T=null),oe};try{for(let L=0;L<I&&!(this._chatCancelled||this._chatComplete);L++){R&&(this._invalidateOverwriteAuthorizations(),R=!1),A=this._stripEphemeralMessages(A),g();let M;try{if(this._v2TurnActive=!0,M=await B(),l(),this._chatCancelled){this._v2TurnActive=!1;break}L===0&&this._connectV2SSE()}catch(oe){if(this._v2TurnActive=!1,this._activeV2Request=null,oe===s||!i()||this._chatCancelled)break;this._chatServerStateReady=!1,this.emit("event","error",{content:E0(oe.message)}),this._chatComplete=!0;break}if(M.error){if(/turn_in_progress/i.test(String(M.error))){try{await this._httpPost(`/api/turn/stop/${this._chatPersistentId}`,{},{timeout:5e3})}catch{}if(!i()||(this._chatServerStateReady=!1,await new Promise(oe=>setTimeout(oe,1e3)),!i()))break;continue}if(M.error==="context_window_exceeded"&&M.compact_needed)try{let oe=await this._httpPost("/api/compact",{messages:A,working_directory:this.cwd},{timeout:12e4});l(),A=oe.messages||A,u=M.turn_state||u,this._invalidateOverwriteAuthorizations(),this._chatServerStateReady=!1,p=[],T=null,g();continue}catch(oe){if(oe===s)throw oe;this.emit("event","error",{content:`Compaction failed: ${oe.message}`}),this._chatComplete=!0;break}this._chatServerStateReady=!1,this.emit("event","error",{content:E0(M.error)}),this._chatComplete=!0;break}u=M.turn_state||u;for(let oe of M.thinking_blocks||[])this.emit("event","reasoning",{content:oe,is_thinking:!0});M.context_usage&&this.emit("event","context_usage",M.context_usage),M.auto_route&&this.emit("event","auto_route",M.auto_route);for(let oe of M.system_events||[])this.emit("event","system_event",{message:oe.message||oe.event});for(let oe of M.system_injections||[])A.push(oe);if(M.compact_needed&&!M.error)try{let oe=await this._httpPost("/api/compact",{messages:A,working_directory:this.cwd},{timeout:12e4});l(),A=oe.messages||A,R=!0,this._chatServerStateReady=!1,p=[],T=null,u.compact_cooldown!==void 0&&(u.compact_cooldown=3),g()}catch(oe){if(oe===s)throw oe}if(M.validation_actions?.length>0&&this._applyValidationActions(M.validation_actions,A),!M.assistant_message){this._chatComplete=!0;break}if(A.push(M.assistant_message),g(),(!M.tool_calls||M.tool_calls.length===0)&&(!M.pre_resolved_tools||M.pre_resolved_tools.length===0)){if(this._clearPendingToolCalls(),M.needs_continuation){A.push({role:"user",content:"[SYSTEM] Your previous response was interrupted mid-stream and your tool call was lost. The user did NOT see a tool result. Please re-attempt your intended action."}),this._chatServerStateReady=!1,p=[],T=null,g();continue}let oe=M.assistant_message.content||"";if(!oe.trim()&&N<3){N++,A.length>0&&A[A.length-1]===M.assistant_message&&A.pop(),A.push({role:"user",content:"[SYSTEM] You returned an empty response with no tool calls. The user is still waiting. If your previous tool results were empty or unhelpful, try a different approach \u2014 broaden your search, try different keywords, check different directories, or use alternative tools. Do NOT stop silently. You MUST either continue working or respond to the user explaining what you found and what you'll try next."}),this._chatServerStateReady=!1,p=[{role:"user",content:A[A.length-1].content}],g();continue}this.emit("event","final_response",{content:oe||"(The agent completed without generating a response.)"}),this._chatComplete=!0;break}if(this._setPendingToolCalls(M.tool_calls),M.assistant_message.content&&(this.emit("event","reasoning",{content:M.assistant_message.content}),await new Promise(oe=>setTimeout(oe,80)),l()),this._v2TurnActive=!1,!(t.auto_mode!==void 0?t.auto_mode:!0)&&M.tool_calls?.length>0){let oe=M.tool_calls.filter(J=>["run_terminal_command","write_file","edit_file","multi_edit_file","regex_replace"].includes(J.function.name));if(oe.length>0){let J=oe.map(Oe=>{let Ce;try{Ce=JSON.parse(Oe.function.arguments)}catch{Ce={}}return{tool:Oe.function.name,args:Ce,id:Oe.id}});this.emit("event","tool_approval_request",{tools:J})}}if(N=0,M.pre_resolved_tools?.length)for(let oe of M.pre_resolved_tools)A.push({role:"tool",tool_call_id:oe.tool_call_id,name:oe.name,content:oe.content}),this.emit("event","tool_result",{tool:oe.name,tool_call_id:oe.tool_call_id,content:oe.content});let U=[],{batches:pe,dedupMap:re}=this._categorizeToolBatches(M.tool_calls);this._setPendingToolCallAliases(re);for(let oe of pe){if(this._chatCancelled)break;let J=[];for(let De of oe){D++;let we;try{we=JSON.parse(De.function.arguments)}catch{we={}}J.push({stepId:D,args:we}),this.emit("event","tool_start",{tool:De.function.name,args:we,tool_call_id:De.id,step_id:D})}await this._yieldToUi(),l();let Oe=async De=>{let we=De?.function?.name||"tool";try{let yt=await this._executeToolLocally(De);return this._recordPendingToolResult(De?.id,we,yt?.content||"(no output)"),yt}catch(yt){throw this._recordPendingToolResult(De?.id,we,`Error: ${yt?.message||"Tool execution failed"}`),yt}},Ce;if(oe.length===1)try{Ce=[{status:"fulfilled",value:await Oe(oe[0])}]}catch(De){Ce=[{status:"rejected",reason:De}]}else Ce=await Promise.allSettled(oe.map(De=>Oe(De)));l(),await this._yieldToUi(),l();for(let De=0;De<oe.length;De++){let we=oe[De],{stepId:yt,args:le}=J[De],dt=Ce[De],Ze=dt.status==="fulfilled"?dt.value:{content:`Error: ${dt.reason?.message||"Tool execution failed"}`};A.push({role:"tool",tool_call_id:we.id,name:we.function.name,content:Ze.content||"(no output)"}),g(),this._markToolCallCompleted(we.id),U.push({tool_call_id:we.id,name:we.function.name,content:Ze.content||"(no output)",args:le,...Ze.file_modified?{file_snapshot:Ze.file_modified}:{},...Ze.multi_file_snapshots?{multi_file_snapshots:Ze.multi_file_snapshots}:{}}),this.emit("event","tool_end",{tool:we.function.name,args:le,tool_call_id:we.id,result:(Ze.content||"").slice(0,200),step_id:yt}),Ze.file_modified&&this.emit("event","file_modified",{...Ze.file_modified,step_id:yt}),Ze.command_output&&this.emit("event","command_output",Ze.command_output),we.function.name==="generate_image"&&Ze.content&&!String(Ze.content).startsWith("Error")&&this.emit("event","image_generated",{content:Ze.content,step_id:yt})}}if(re.size>0){let oe=new Map;for(let J of A)J.role==="tool"&&J.tool_call_id&&oe.set(J.tool_call_id,J.content);for(let J of M.tool_calls){let Oe=re.get(J.id);if(Oe){let Ce=oe.get(Oe)||"(deduplicated)",De;try{De=JSON.parse(J.function.arguments)}catch{De={}}A.push({role:"tool",tool_call_id:J.id,name:J.function.name,content:Ce}),this._markToolCallCompleted(J.id),U.push({tool_call_id:J.id,name:J.function.name,content:Ce,args:De})}}}if(this._chatCancelled&&M.tool_calls){let oe=new Set(U.map(J=>J.tool_call_id));for(let J of M.tool_calls)oe.has(J.id)||A.push({role:"tool",tool_call_id:J.id,name:J.function.name,content:"Tool execution cancelled by user."});g(),this._clearPendingToolCalls()}if(U.some(oe=>["write_file","edit_file","multi_edit_file","regex_replace"].includes(oe.name)&&(oe.file_snapshot||oe.multi_file_snapshots?.length))&&!this._chatCancelled)try{let{validateToolResults:oe}=(BQ(),I3(OQ)),J=this._buildProjectInstructionsContent(),Oe=await oe(U,this._validationConfig,{httpPost:(Ce,De,we)=>this._httpPost(Ce,Ce==="/api/turn"&&J?{...De,rules_content:J}:De,we),cwd:this.cwd,model:this._lastModel,isCancelled:()=>this._chatCancelled});l(),Oe.length>0&&this._applyValidationActions(Oe,A);for(let Ce of U)(Ce.file_snapshot||Ce.multi_file_snapshots?.length)&&["write_file","edit_file","multi_edit_file","regex_replace"].includes(Ce.name)&&(Ce._client_validated=!0)}catch(oe){console.warn(`[V2 ClientValidation] ${oe.message}`)}for(let oe of U){let J=A.find(Oe=>Oe.role==="tool"&&Oe.tool_call_id===oe.tool_call_id);J?.content&&(oe.content=J.content)}if(T=U,!this._chatCancelled){let oe=new Set(["codebase_search","search_files","multi_search","list_code_usages"]),J=[];for(let Oe of U){if(!oe.has(Oe.name)||!Oe.content||Oe.content.startsWith("Error"))continue;if(J.length>=2)break;let Ce=/(?:^|\bin )((?:[\w.\/-]+\/)?[\w.\-]+\.(?:py|ts|tsx|js|jsx|rs|go|java|rb|c|cpp|h|hpp|css|html|json|yaml|yml|toml|sql|sh))\b/gm,De;for(;(De=Ce.exec(Oe.content))!==null&&J.length<2;){let we=De[1];if(!A.some(le=>le.role!=="assistant"||!Array.isArray(le.tool_calls)?!1:le.tool_calls.some(dt=>{if(!dt.function||dt.function.name!=="read_file")return!1;try{let St=JSON.parse(dt.function.arguments||"{}").path||"";return St===we||St.endsWith("/"+we)||we.endsWith("/"+St)}catch{return!1}}))&&!J.some(le=>le.path===we))try{let le=Te.isAbsolute(we)?we:Te.resolve(this.cwd,we),dt=se.readFileSync(le,"utf8");dt&&dt.length>0&&dt.length<5e4&&J.push({path:we,content:dt})}catch{}}}if(J.length>0){let Oe=J.map((De,we)=>({id:`rule_read_${Date.now()}_${we}_${Math.random().toString(36).slice(2,8)}`,type:"function",function:{name:"read_file",arguments:JSON.stringify({path:De.path})}})),Ce={role:"assistant",content:null,tool_calls:Oe};A.push(Ce);for(let De=0;De<J.length;De++){let we=J[De],yt=Oe[De].id,le={role:"tool",tool_call_id:yt,name:"read_file",content:we.content};A.push(le),U.push({tool_call_id:yt,name:"read_file",content:we.content,args:{path:we.path}}),D++,this.emit("event","tool_start",{tool:"read_file",args:{path:we.path},tool_call_id:yt,step_id:D}),this.emit("event","tool_end",{tool:"read_file",args:{path:we.path},tool_call_id:yt,result:we.content.slice(0,200),step_id:D})}this._chatServerStateReady=!1}}g(),this.emit("event","history_update",{messages:A,message_count:A.length,token_estimate:Ch(A),token_limit:Xc(this._lastModel)}),u.context_dirty&&(u.context_dirty=!1),A=this._sanitizeMessages(A)}if(!this._chatCancelled&&!this._chatComplete)try{A.push({role:"user",content:"[SYSTEM] You have reached the maximum turn limit ("+I+` turns). You MUST stop all tool use and respond to the user NOW.
734
734
  Provide a clear summary:
735
735
  1. Every file you created or modified (with what changed)
736
736
  2. Every action you took
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "koda-tui",
3
- "version": "2.3.28",
3
+ "version": "2.3.29",
4
4
  "description": "Koda — AI coding agent in your terminal",
5
5
  "type": "module",
6
6
  "license": "MIT",