lody 0.2.0 → 0.2.2

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.
package/dist/index.cjs ADDED
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env node
2
+ var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;l<u;l++)d=c[l],!a.call(e,d)&&d!==o&&t(e,d,{get:(e=>i[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},s=(n,r,a)=>(a=n==null?{}:e(i(n)),o(r||!n||!n.__esModule?t(a,`default`,{value:n,enumerable:!0}):a,n));let c=require(`commander`),l=require(`chalk`);l=s(l);let u=require(`ora`);u=s(u);let d=require(`inquirer`);d=s(d);let f=require(`winston`);f=s(f);let p=require(`winston-daily-rotate-file`);p=s(p);let m=require(`path`);m=s(m);let h=require(`fs`);h=s(h);let g=require(`os`);g=s(g);let _=require(`child_process`),ee=require(`util`),te=require(`convex/browser`),v=require(`@lody/convex`),y=require(`uuid`),b=require(`loro-mirror`),x=require(`@lody/shared`),S=require(`ws`);S=s(S);let ne=require(`loro-repo`),re=require(`loro-repo/transport/websocket`),ie=require(`events`),ae=require(`throttle-debounce`),C=require(`dockerode`);C=s(C);let w=require(`eventemitter3`);w=s(w);let T=require(`stream`),E=require(`@agentclientprotocol/sdk`);E=s(E);let oe=require(`crypto`),se=require(`dotenv`);var D=`0.2.2`;const O=(e={})=>{let{colorize:t=!0,timestamp:n=!0,format:r=`simple`}=e;return f.default.format.combine(f.default.format.errors({stack:!0}),f.default.format.timestamp({format:`YYYY-MM-DD HH:mm:ss`}),f.default.format.printf(e=>{let{level:i,message:a,timestamp:o,stack:s,...c}=e,u=``;if(r===`json`)return JSON.stringify({level:i,message:a,timestamp:o,...c});if(n&&o){let e=t?l.default.gray(`[${o}]`):`[${o}]`;u+=`${e} `}if(process.env.NODE_ENV===`development`){let e=i.toUpperCase().padEnd(7);if(t){let t={ERROR:l.default.red,WARN:l.default.yellow,INFO:l.default.blue,SUCCESS:l.default.green,DEBUG:l.default.gray}[i.toUpperCase()]||l.default.white;u+=`${t(e)} `}else u+=`${e} `}if(u+=a,Object.keys(c).length>0){let e=JSON.stringify(c);u+=t?l.default.gray(` ${e}`):` ${e}`}return s&&(u+=`\n${s}`),u}))},ce=e=>{let t=e.console||{};return new f.default.transports.Console({level:e.level===`silent`?`error`:e.level||`info`,format:O({colorize:t.colorize!==!1,timestamp:t.timestamp!==!1,format:t.format||`simple`})})},le=e=>{let t=e.file||{};return new p.default({level:e.level,filename:t.filename||`%DATE%.log`,dirname:t.dirname||m.default.join(process.cwd(),`logs`),datePattern:t.datePattern||`YYYY-MM-DD`,maxSize:t.maxSize||`20m`,maxFiles:t.maxFiles||14,zippedArchive:t.zippedArchive||!1,format:O({colorize:!1,timestamp:!0,format:`detailed`})})};var ue=class{winston;config;constructor(e={}){this.config={...e},this.winston=this.createWinstonLogger()}createWinstonLogger(){let e=[],t=this.config.transports||`console`;return(t===`console`||t===`both`)&&e.push(ce(this.config)),(t===`file`||t===`both`)&&e.push(le(this.config)),f.default.createLogger({level:this.config.level,transports:e,exitOnError:!1})}info(e,t){this.winston.info(e,t)}warn(e,t){this.winston.warn(e,t)}error(e,t){this.winston.error(e,t)}success(e,t){this.winston.levels.success===void 0?this.winston.info(e,{...t,level:`success`}):this.winston.success(e,t)}debug(e,t){this.winston.debug(e,t)}setLevel(e){this.config.level=e,this.winston.level=e===`silent`?`error`:e,this.winston.transports.forEach(t=>{t.level=e===`silent`?`error`:e,t instanceof f.default.transports.Console&&(t.silent=e===`silent`)})}child(e){let t=this.winston.child(e),n=Object.create(this);return n.winston=t,n}async close(){return new Promise(e=>{this.winston.close(),e()})}};let de=null;const k=(e={})=>new ue(e),fe=()=>(de||=k({transports:`console`,level:`info`,console:{colorize:!0,timestamp:!1,format:`simple`}}),de),pe=fe(),A=m.default.join(g.default.homedir(),`.lody-agent`),j=m.default.join(A,`serve.pid`),M=m.default.join(A,`serve.json`);function me(){h.default.existsSync(A)||h.default.mkdirSync(A,{recursive:!0})}function N(e,t){me(),h.default.writeFileSync(j,e.toString());let n={pid:e,...t};h.default.writeFileSync(M,JSON.stringify(n,null,2))}function he(e){N(process.pid,{...e,startTime:new Date().toISOString()})}function P(){try{if(!h.default.existsSync(j))return null;let e=parseInt(h.default.readFileSync(j,`utf-8`).trim(),10);return isNaN(e)?null:e}catch{return null}}function ge(){try{return h.default.existsSync(M)?JSON.parse(h.default.readFileSync(M,`utf-8`)):null}catch{return null}}function F(e){try{return process.kill(e,0),!0}catch{return!1}}function _e(e){try{return process.kill(e,`SIGTERM`),setTimeout(()=>{if(F(e))try{process.kill(e,`SIGKILL`)}catch{}},2e3),!0}catch{return!1}}function I(){try{h.default.existsSync(j)&&h.default.unlinkSync(j),h.default.existsSync(M)&&h.default.unlinkSync(M)}catch{}}function L(){let e=P(),t=ge();return!e||!t?{running:!1,info:null}:F(e)?{running:!0,info:t}:(I(),{running:!1,info:null})}function ve(){try{return require.resolve(`ts-node/dist/bin.js`)}catch{try{return require.resolve(`ts-node`)}catch{return null}}}function ye(e,t,n){let r=e.endsWith(`.ts`),i,a;if(r){let r=ve();if(!r)throw Error(`ts-node not found. Please install ts-node or run in production mode.`);i=process.execPath,a=[r,`--project`,m.default.join(m.default.dirname(e),`..`,`tsconfig.json`),e,...t],n?.info(`Development mode: Using ts-node to execute TypeScript`)}else i=process.execPath,a=[e,...t],n?.info(`Production mode: Using node to execute JavaScript`);let o=r?m.default.dirname(m.default.dirname(e)):process.cwd();n?.info(`Starting background service: ${i} ${a.join(` `)}`),n?.info(`Working directory: ${o}`);let s=(0,_.spawn)(i,a,{detached:!0,stdio:[`ignore`,`pipe`,`pipe`],cwd:o,env:{...process.env,LODY_AGENT_BACKGROUND:`1`}});return s.stdout?.on(`data`,e=>{let t=e.toString();n?.info(`STDOUT`,{output:t.trim()})}),s.stderr?.on(`data`,e=>{let t=e.toString();n?.error(`STDERR`,{error:t.trim()})}),s.on(`error`,e=>{n?.error(`Process error`,{error:e.message,stack:e.stack}),n?.error(`Failed to start background service:`,e)}),s.on(`close`,(e,t)=>{n?.info(`Process closed`,{exitCode:e,signal:t||`none`}),n?.info(`=== Agent Service Stopped ===`)}),s.unref(),{child:s}}const R=(0,ee.promisify)(_.exec);async function be(){try{await R(`docker ps -q`);let{stdout:e}=await R(`docker version --format "{{.Server.Version}}"`),t=e.trim();return t?{installed:!0,running:!0,version:t}:{installed:!0,running:!1,error:`Docker is installed but not running`}}catch(e){let t=e instanceof Error?e.message.toLowerCase():``;if(t.includes(`permission denied`)||t.includes(`got permission denied`)||t.includes(`connect: permission denied`)||t.includes(`dial unix /var/run/docker.sock`))try{let{stdout:e}=await R(`groups`);return e.includes(`docker`)?{installed:!0,running:!1,error:`Permission denied accessing Docker socket.
3
+ Try running: sudo chmod 666 /var/run/docker.sock
4
+ Or restart the Docker service: sudo systemctl restart docker`}:{installed:!0,running:!1,error:`Permission denied. Your user is not in the docker group.
5
+ To fix this, run: sudo usermod -aG docker $USER
6
+ Then log out and log back in for the changes to take effect.`}}catch{return{installed:!0,running:!1,error:`Permission denied. Please ensure your user is in the docker group.
7
+ To fix this, run: sudo usermod -aG docker $USER
8
+ Then log out and log back in.`}}return t.includes(`cannot connect to the docker daemon`)||t.includes(`is the docker daemon running`)?{installed:!0,running:!1,error:`Docker is installed but the daemon is not running.
9
+ Start Docker with: sudo systemctl start docker`}:t.includes(`command not found`)||t.includes(`not found`)||t.includes(`docker: not found`)?{installed:!1,running:!1,error:`Docker is not installed`}:{installed:!1,running:!1,error:`Docker check failed: ${t}`}}}function xe(){switch(process.platform){case`darwin`:return`Please install Docker Desktop for macOS from: https://docs.docker.com/desktop/install/mac-install/`;case`win32`:return`Please install Docker Desktop for Windows from: https://docs.docker.com/desktop/install/windows-install/`;case`linux`:return`Please install Docker using your package manager:
10
+ Ubuntu/Debian: sudo apt-get install docker.io
11
+ CentOS/RHEL: sudo yum install docker
12
+ Or visit: https://docs.docker.com/engine/install/`;default:return`Please visit https://docs.docker.com/get-docker/ for installation instructions.`}}function z(){let e=m.default.join(g.homedir(),`.lody`);return m.default.join(e,`credentials.json`)}const B=()=>{try{let e=[`~/.claude/local`,process.env.PATH||``].join(m.default.delimiter);return(0,_.execSync)(`claude --version`,{env:{...process.env,PATH:e},encoding:`utf-8`}).trim().replace(`(Claude Code)`,``)}catch{return!1}},Se=()=>{try{return(0,_.execSync)(`codex --version`,{env:process.env,encoding:`utf-8`}).trim().replace(`codex-cli `,``)}catch{return!1}};let V=process.env.LODY_AUTH_URL,H=process.env.LODY_SERVER_URL,U=H?.replace(/^http/,`ws`),W=`/workspace`;const Ce=()=>{V=process.env.LODY_AUTH_URL,H=process.env.LODY_SERVER_URL,U=H?.replace(/^http/,`ws`)};function we(){try{if(process.platform===`linux`){if(h.default.existsSync(`/etc/machine-id`))return h.default.readFileSync(`/etc/machine-id`,`utf-8`).trim();if(h.default.existsSync(`/var/lib/dbus/machine-id`))return h.default.readFileSync(`/var/lib/dbus/machine-id`,`utf-8`).trim()}else if(process.platform===`darwin`)try{return(0,_.execSync)(`ioreg -rd1 -c IOPlatformExpertDevice | grep IOPlatformUUID`,{encoding:`utf-8`}).match(/"IOPlatformUUID" = "(.+)"/)?.[1]||null}catch{return null}else if(process.platform===`win32`)try{return(0,_.execSync)(`wmic csproduct get UUID`,{encoding:`utf-8`}).split(`
13
+ `)[1]?.trim()||null}catch{return null}}catch{}return null}function Te(e){let t=`"${e.replace(/"/g,`\\"`)}"`;switch(process.platform){case`darwin`:return`open ${t}`;case`win32`:return`cmd /c start "" ${t.replace(/&/g,`^&`)}`;default:return`xdg-open ${t}`}}async function Ee(e){let t=Te(e);await new Promise((e,n)=>{(0,_.exec)(t,{windowsHide:!0},t=>{t?n(t):e()}).on(`error`,n)})}var G=class{serverUrl;client;constructor(e){if(this.logger=e,!V)throw Error(`LODY_AUTH_URL is not defined`);this.serverUrl=V,this.client=new te.ConvexHttpClient(this.serverUrl)}getAuthInfo(){return Oe()}async login(e){let t=we()||(0,y.v4)(),n=await this.client.mutation(v.api.deviceAuth.initializeDeviceAuth);this.logger.info(`
14
+ `+l.default.yellow(`=`.repeat(50))),this.logger.info(l.default.bold(`Device Authorization`)),this.logger.info(l.default.yellow(`=`.repeat(50))),this.logger.info(`
15
+ Please visit: `+l.default.cyan(n.verification_uri_complete));try{this.logger.info(`Attempting to open the URL in your default browser...`),await Ee(n.verification_uri_complete)}catch(e){this.logger.warn(`Could not open the browser automatically: ${e instanceof Error?e.message:`Unknown error`}`),this.logger.info(`Please open the URL manually if it did not open automatically.`)}this.logger.info(l.default.yellow(`=`.repeat(50))+`
16
+ `);let r=(0,u.default)(`Waiting for authorization...`).start(),i=!1,a=``,o=null,s={id:``,name:``},c=Date.now()+n.expires_in*1e3;for(;!i&&Date.now()<c;){await new Promise(e=>setTimeout(e,n.interval*1e3));try{let e=await this.client.query(v.api.deviceAuth.checkDeviceAuthorization,{deviceCode:n.device_code});switch(e.status){case`authorized`:i=!0,a=e.access_token,o=e.user,s=e.workspace,r.succeed(`Authorization successful!`);break;case`needs_token_generation`:await this.client.mutation(v.api.deviceAuth.generateCliToken,{deviceCode:n.device_code,userId:e.userId});let t=await this.client.query(v.api.deviceAuth.checkDeviceAuthorization,{deviceCode:n.device_code});t.status===`authorized`&&(i=!0,a=t.access_token,o=t.user,s=t.workspace,r.succeed(`Authorization successful!`));break;case`denied`:return r.fail(`Authorization was denied`),{success:!1,error:`Authorization denied`};case`expired`:return r.fail(`Authorization code expired`),{success:!1,error:`Authorization code expired`};case`error`:return r.fail(`Error: ${e.error}`),{success:!1,error:e.error||`Unknown error`};case`pending`:break}}catch{return{success:!1,error:`Network error or server error when logging in`}}}if(!i)return r.fail(`Authorization timed out`),{success:!1,error:`Authorization timed out`};let d={token:a,user:o,workspace:{id:s.id,name:s.name},machine:{machineName:e,machineId:t}};return await De(a,d.user,d.workspace,d.machine),{success:!0,...d}}logout(){let e=Oe();if(!e)return this.logger.debug(`No authentication found. You are not logged in.`),{success:!0,user:void 0};let t=z();return h.unlinkSync(t),{success:!0,user:e.user}}async validateToken(e){return await ke(e,this.client)}};async function De(e,t,n,r){let i=z(),a=m.default.dirname(i);h.mkdirSync(a,{recursive:!0});let o={token:e,user:t,workspace:n,machine:r};h.writeFileSync(i,JSON.stringify(o,null,2))}function Oe(){try{let e=z(),t=h.readFileSync(e,`utf-8`);return JSON.parse(t)}catch{return null}}async function ke(e,t){try{let n=await t.query(v.api.deviceAuth.validateCliToken,{token:e});return n.valid&&n.user?{valid:!0,user:n.user,workspace:n.workspace}:{valid:!1}}catch{return{valid:!1}}}globalThis.WebSocket=S.default;const Ae=fe();var je=class e{issues=new Map;sessions=new Map;workspaces=new Map;agents=new Map;machine=null;static async create(t,n,r){let i=await ne.LoroRepo.create({transportAdapter:new re.WebSocketTransportAdapter({url:(0,x.getLoroWsUrl)(t,process.env.LODY_SERVER_URL||`http://localhost:8787`),metadataAdaptorConfig:{onImportError(e,t){console.error(`Import error in LoroRepo metadata adapter:`,e,t)},onUpdateError(e){console.error(`Update error in LoroRepo metadata adapter:`,e)}},docAuth:void 0})}),a=await i.joinMetaRoom({roomId:`repo:meta`});return await a.firstSyncedWithRemote,new e(i,t,n,r,a)}constructor(e,t,n,r,i){this.repo=e,this.workspaceId=t,this.userId=n,this.token=r,this.metaSub=i,Ae.debug(`[${this.workspaceId}] LoroWebsocketClient url: ${U+`/ws/${this.workspaceId}`}`)}async getOrCreateAgentDoc(e){if(!this.agents.has(e)){let t=new Fe(this.repo,e);await t.init(),this.agents.set(e,t)}return this.agents.get(e)}async getOrCreateIssueDoc(e){if(!this.issues.has(e)){let t=new Me(this.repo,e);await t.init(),this.issues.set(e,t)}return this.issues.get(e)}async getOrCreateSessionDoc(e){if(!this.sessions.has(e)){let t=new Ne(this.repo,e);await t.init(),this.sessions.set(e,t)}return this.sessions.get(e)}async getOrCreateWorkspaceDoc(e,t){if(!this.workspaces.has(e)){let n=new Pe(this.repo,t,e,this);await n.init(),this.workspaces.set(e,n)}return this.workspaces.get(e)}async registerMachine(e,t){this.machine||(this.machine=new Ie(this.repo,e),await this.machine.init()),await(await this.getOrCreateWorkspaceDoc(this.workspaceId,this.userId)).registerMachine(e),await this.machine.setMetaState(t)}async removeMachineSession(e){let t=await this.machine?.getMetaState();t&&this.machine?.setMetaState({...t,sessions:t.sessions?.filter(t=>t.sessionId!==e)||[]})}async updateMachineSession(e,t){let n=await this.machine?.getMetaState();n&&this.machine?.setMetaState({...n,sessions:n.sessions?.map(n=>n.sessionId===e?{...n,status:{...n.status,...t}}:n)||[]})}async addMachineSession(e){let t=await this.machine?.getMetaState();t&&this.machine?.setMetaState({...t,sessions:[...t.sessions,e]})}async cleanUp(){for(let e of this.sessions.values())await e.destroy();for(let e of this.issues.values())await e.destroy();for(let e of this.workspaces.values())await e.destroy();for(let e of this.agents.values())await e.destroy();this.issues.clear(),this.workspaces.clear(),this.sessions.clear(),this.agents.clear(),this.metaSub.unsubscribe(),await this.repo.destroy()}async cleanSessionDoc(e){let t=this.sessions.get(e);t&&(await t.destroy(),this.sessions.delete(e))}async cleanIssueDoc(e){let t=this.issues.get(e);t&&(await t.destroy(),this.issues.delete(e))}async cleanWorkspaceDoc(e){let t=this.workspaces.get(e);t&&(await t.destroy(),this.workspaces.delete(e))}async cleanAgentDoc(e){let t=this.agents.get(e);t&&(await t.destroy(),this.agents.delete(e))}},Me=class{mirror=null;handle=null;docSub=null;roomId;constructor(e,t){this.repo=e,this.issueId=t,this.roomId=(0,x.getIssueRoomId)(this.issueId)}async init(){this.handle=await this.repo.openPersistedDoc(this.roomId),this.docSub=await this.repo.joinDocRoom(this.roomId),await this.docSub.firstSyncedWithRemote,this.mirror=new b.Mirror({doc:this.handle.doc,schema:x.issueDocSchema})}async getMetaState(){return await this.repo.getDocMeta(this.roomId)}async getDocState(){if(!this.mirror)throw Error(`IssueDocument not initialized`);return this.mirror.getState().issue}async setStatus(e){if(!this.mirror)throw Error(`Mirror not initialized`);(await this.repo.getDocMeta(this.roomId))?.status!==e&&(await this.repo.upsertDocMeta(this.roomId,{status:e}),this.mirror.setState(t=>{t.issue.actionHistory.push({type:`update`,target:`status`,value:JSON.stringify(e),by:`lody-cli`,timestamp:new Date().toISOString()})}))}async destroy(){await this.repo.unloadDoc(this.roomId),this.docSub?.unsubscribe(),this.mirror?.dispose(),this.mirror=null,this.handle=null}},Ne=class{mirror=null;handle=null;docSub=null;roomId=``;constructor(e,t){this.repo=e,this.sessionId=t,this.roomId=(0,x.getSessionRoomId)(this.sessionId)}async init(){this.handle=await this.repo.openPersistedDoc(this.roomId),this.docSub=await this.repo.joinDocRoom(this.roomId),await this.docSub.firstSyncedWithRemote,this.mirror=new b.Mirror({doc:this.handle.doc,schema:x.sessionDocSchema,initialState:{session:{id:this.sessionId,history:[]}}})}async getDocState(){if(!this.mirror)throw Error(`SessionDocument not initialized`);return this.mirror.getState().session}async getMetaState(){return await this.repo.getDocMeta(this.roomId)}async markHistoryAsRead(e){if(!this.mirror)throw Error(`SessionDocument not initialized`);this.mirror.setState(t=>{let n=t.session.history??[];for(let t of n)if(t.$cid===e){t.read=!0;break}return t})}async setChatId(e){if(!this.mirror)throw Error(`SessionDocument not initialized`);Ae.debug(`[${this.sessionId}] setChatId ${e}`),await this.repo.upsertDocMeta(this.roomId,{chatId:e})}async setError(e){if(!this.mirror)throw Error(`SessionDocument not initialized`);await this.repo.upsertDocMeta(this.roomId,{error:e,status:`error`})}async getStatus(){if(!this.mirror)throw Error(`SessionDocument not initialized`);return(await this.repo.getDocMeta(this.roomId))?.status}async isChat(){return this.mirror?!!(await this.repo.getDocMeta(this.roomId))?.chatId:!1}async getIssueId(){if(this.mirror)return(await this.repo.getDocMeta(this.roomId))?.issueId}async setStatus(e){if(!this.mirror)throw Error(`Mirror not initialized`);await this.repo.upsertDocMeta(this.roomId,{status:e})}async getHistory(){return this.mirror&&this.mirror.getState().session.history||[]}async updateHistory(e){if(!this.mirror)throw Error(`Mirror not initialized`);this.mirror.setState(t=>{let n=e(t.session.history||[]);return t.session.history=n,t})}async destroy(){if(!this.mirror)return;let e=await this.getStatus();e===`running`||e===`requestPermission`?await this.setStatus(`error`):(e===`completed`||e===`terminated`)&&await this.setStatus(`terminated`),await this.repo.unloadDoc(this.roomId),this.docSub?.unsubscribe(),this.mirror?.dispose(),this.mirror=null,this.handle=null}},Pe=class{mirror=null;handle=null;roomId;docSub=null;constructor(e,t,n,r){this.repo=e,this.userId=t,this.workspaceId=n,this.manager=r,this.roomId=(0,x.getWorkspaceRoomId)(this.workspaceId)}async init(){this.docSub=await this.repo.joinDocRoom(this.roomId),await this.docSub.firstSyncedWithRemote,this.handle=await this.repo.openPersistedDoc(this.roomId),this.mirror=new b.Mirror({doc:this.handle.doc,schema:x.workspaceDocSchema,initialState:{issues:[],sessions:[],agentConfigs:[]}})}async createSession(e,t,n){if(!this.mirror)throw Error(`WorkspaceDocument not initialized`);let r=(0,y.v4)(),i=await this.manager.getOrCreateSessionDoc(r);return await i.init(),i.mirror?.setState(i=>({session:{...i.session,id:r,machineId:e,cliType:t,userId:this.userId,status:`running`,history:[],title:n,createdAt:new Date().toISOString()}})),this.mirror.setState(e=>{e.sessions.push({id:r,isDeleted:!1})}),r}async registerMachine(e){let t=await this.getMetaState();await this.repo.upsertDocMeta(this.roomId,{...t,machineIds:[...t?.machineIds||[],e]})}async hasAgentConfig(e,t){if(!this.mirror)throw Error(`Mirror not initialized`);let n=this.mirror.getState().agentConfigs,r=!1;for(let t of n){if(t.isDeleted)continue;let n=await(await this.manager.getOrCreateAgentDoc(t.id)).getMetaState();if(await this.manager.cleanAgentDoc(t.id),r||=n?.cliType===e,r)break}return r}async createAgentConfig(e,t){if(!this.mirror)throw Error(`Mirror not initialized`);let n=(0,y.v4)();return await(await this.manager.getOrCreateAgentDoc(n)).createAgentConfig({id:n,name:e,description:void 0,cliType:e,machineId:t,env:{}}),this.mirror.setState(e=>{e.agentConfigs.push({id:n,isDeleted:!1})}),await this.manager.cleanAgentDoc(n),n}async destroy(){await this.repo.unloadDoc(this.roomId),this.mirror?.dispose(),this.mirror=null,this.handle=null,this.docSub?.unsubscribe()}async getDocState(){if(!this.mirror)throw Error(`Mirror not initialized`);return this.mirror.getState()}async getMetaState(){return await this.repo.getDocMeta(this.roomId)}},Fe=class{roomId;handle=null;constructor(e,t){this.repo=e,this.agentId=t,this.roomId=(0,x.getAgentConfigRoomId)(this.agentId)}async init(){}async destroy(){}async createAgentConfig(e){await this.repo.upsertDocMeta(this.roomId,e)}async getDocState(){}async getMetaState(){return await this.repo.getDocMeta(this.roomId)}},Ie=class{roomId;handle=null;constructor(e,t){this.repo=e,this.machineId=t,this.roomId=(0,x.getMachineRoomId)(this.machineId)}async setMetaState(e){await this.repo.upsertDocMeta(this.roomId,e)}async init(){}async destroy(){}async getMetaState(){return await this.repo.getDocMeta(this.roomId)}async getDocState(){}},Le=class extends ie.EventEmitter{ws=null;token;workspaceId;keepaliveTimer=null;isConnected=!1;logger;reconnectAttempts=0;reconnectTimer=null;shouldReconnect=!0;reconnectInitialDelay=1e3;reconnectMaxDelay=3e4;connectionPromise=null;abortConnectionAttempt=null;constructor(e){super(),this.token=e.token,this.logger=e.logger,this.workspaceId=e.workspaceId}connect(){return this.ws?.readyState===S.default.OPEN?Promise.resolve():this.connectionPromise?this.connectionPromise:(this.shouldReconnect=!0,this.clearReconnectTimer(),this.connectionPromise=new Promise((e,t)=>{let n=null,r=!1,i=()=>{n&&=(clearTimeout(n),null),this.ws&&(this.ws.removeListener(`open`,s),this.ws.removeListener(`error`,c)),this.connectionPromise=null,this.abortConnectionAttempt=null},a=()=>{r||(r=!0,i(),e())},o=e=>{r||(r=!0,i(),t(e))},s=()=>{this.reconnectAttempts=0,a()},c=e=>{o(e)};this.abortConnectionAttempt=e=>{o(e??Error(`Connection aborted`))};try{let e=new URL(`${U}/lody/${this.workspaceId}`);this.token&&e.searchParams.append(`token`,this.token),this.logger.debug(`Connecting to WebSocket: ${e}`),this.ws=new S.default(e.toString(),{perMessageDeflate:!1,handshakeTimeout:15e3}),this.setupEventHandlers(),this.ws.once(`open`,s),this.ws.once(`error`,c),n=setTimeout(()=>{o(Error(`Connection timeout`))},3e4)}catch(e){o(e)}}),this.connectionPromise)}setupEventHandlers(){this.ws&&(this.ws.on(`open`,()=>{this.isConnected=!0,this.logger.debug(`WebSocket connected`),this.emit(`open`),this.keepaliveTimer&&=(clearInterval(this.keepaliveTimer),null),this.keepaliveTimer=setInterval(()=>{this.ws?.readyState===S.default.OPEN&&this.ws.send(`ping`)},3e4)}),this.ws.on(`message`,e=>{this.emit(`message`,e)}),this.ws.on(`close`,(e,t)=>{this.isConnected=!1,this.keepaliveTimer&&=(clearInterval(this.keepaliveTimer),null),this.logger.warn(`WebSocket disconnected: code=${e}, reason=${t}`),this.ws=null,this.emit(`close`,e,t),this.shouldReconnect&&(this.logger.info(`Scheduling WebSocket reconnect with exponential backoff`),this.scheduleReconnect())}),this.ws.on(`error`,e=>{this.logger.error(`WebSocket error: ${e.message}`),e.message.includes(`401`)&&(this.logger.error(`Authentication failed. Check your token.`),this.close(),process.exit(1)),this.emit(`error`,e)}),this.ws.on(`ping`,e=>{this.ws?.readyState===S.default.OPEN&&this.ws.pong(e)}))}send(e){if(this.ws?.readyState===S.default.OPEN)this.ws.send(e);else throw this.logger.warn(`Cannot send message: WebSocket is not connected`),Error(`WebSocket is not connected`)}getSocket(){return this.ws}isReady(){return this.ws?.readyState===S.default.OPEN}close(){this.keepaliveTimer&&=(clearInterval(this.keepaliveTimer),null),this.shouldReconnect=!1,this.clearReconnectTimer(),this.reconnectAttempts=0,this.abortConnectionAttempt&&this.abortConnectionAttempt(Error(`Connection aborted by close()`)),this.ws&&=(this.ws.removeAllListeners(),this.ws.close(),null)}scheduleReconnect(){if(!this.shouldReconnect||this.reconnectTimer||this.connectionPromise)return;let e=Math.min(this.reconnectInitialDelay*2**this.reconnectAttempts,this.reconnectMaxDelay),t=this.reconnectAttempts+1;this.logger.warn(`Reconnect attempt ${t} scheduled in ${e}ms`),this.reconnectTimer=setTimeout(async()=>{if(this.reconnectTimer=null,this.shouldReconnect){this.reconnectAttempts=t;try{await this.connect(),this.logger.info(`WebSocket reconnected successfully`)}catch(e){let t=e instanceof Error?e.message:`Unknown error`;this.logger.error(`Reconnect attempt failed: ${t}`),this.scheduleReconnect()}}},e)}clearReconnectTimer(){this.reconnectTimer&&=(clearTimeout(this.reconnectTimer),null)}};const K=e=>e&&e.replace(/^['"]+|['"]+$/g,``),Re=(e,t)=>{if(!t)return e;let n=t.replace(/[.*+?^${}()|[\]\\]/g,`\\$&`);return e.replace(new RegExp(n,`g`),`***TOKEN***`)},ze=(e,t)=>{let n=K(e)??e,r=K(t?.trim());if(!r)return{cloneUrl:`https://github.com/${n}.git`};let i=encodeURIComponent(r);return r.startsWith(`ghs_`)?{cloneUrl:`https://x-access-token:${i}@github.com/${n}.git`,rewriteBase:`https://x-access-token:${i}@github.com/`}:{cloneUrl:`https://${i}@github.com/${n}.git`,rewriteBase:`https://${i}@github.com/`}},Be=(e,t)=>{if(!t)return e;let n=[`https://github.com/`,`git@github.com:`,`ssh://git@github.com/`],r=new Set;for(let e of n)r.add(e);let i=[];for(let e of r)i.push(`-c`,`url.${t}.insteadOf=${e}`);return[...i,...e]},q=async(e,t,n,r,i,a)=>{let o=Re([n,...r].join(` `),a);return t.debug(`[${e.sessionId}] Executing (${i}): ${o}`),await e.exec(n,r,i,!1)},Ve=async(e,t,n,r)=>{let i=n.sessionId,a=n.getWorkdir();try{let o=K(e?.trim())??e?.trim();if(!o){r.info(`[${i}] No GitHub repository specified, skipping clone`);return}if(r.info(`[${i}] Cloning GitHub repository: ${o}`),!o.match(/^[^/]+\/[^/]+$/))throw Error(`Invalid GitHub repository format: ${o}. Expected format: owner/repo`);let{cloneUrl:s,rewriteBase:c}=ze(o,t),l=K(s)??s,u=K(c);t?t.startsWith(`ghs_`)?r.info(`[${i}] Using GitHub App token for authentication`):r.info(`[${i}] Using GitHub token for authentication`):r.info(`[${i}] Cloning public repository`),await q(n,r,`git`,Be([`clone`,`--depth`,`1`,`--recurse-submodules`,`--shallow-submodules`,l,a],u),`/`,t),await q(n,r,`git`,Be([`-C`,a,`submodule`,`update`,`--init`,`--recursive`,`--depth`,`1`],u),`/`,t),r.info(`[${i}] Successfully cloned repository: ${o}`)}catch(e){throw r.error(`[${n.sessionId}] Failed to clone GitHub repository: ${e instanceof Error?e.message:`Unknown error`}`),e}};var He=class{socket;sessionManager;logger;machineId;token;userId;workspaceId;machineName;enableDocker;cliVersion;pendingPermissionRequests=new Map;acpUpdateBuffers=new Map;acpUpdateThrottlers=new Map;acpUpdateThrottleMs=200;constructor(e,t,n,r,i){this.workspaceDocument=n,this.socket=e,this.sessionManager=t,this.logger=r,this.token=i.token,this.userId=i.userId,this.workspaceId=i.workspaceId,this.machineName=i.machineName,this.enableDocker=i.enableDocker,this.cliVersion=i.cliVersion,this.machineId=i.machineId,this.sessionManager.setRequestPermissionHandler((e,t,n)=>this.handleAgentPermissionRequest(e,t,n)),this.setupSessionEventHandlers(),this.registerMachine()}setupSessionEventHandlers(){this.sessionManager.on(`output`,async e=>{this.logger.debug(`Output from session [${e.sessionId}]: ${e.data}`)}),this.sessionManager.on(`onACPUpdateMessage`,(e,t)=>{this.enqueueACPUpdate(e,t)}),this.sessionManager.on(`error`,async e=>{this.logger.error(`[${e.sessionId}] error occur ${e}`);let t=e.sessionId;await this.finalizeACPState(t),(await this.workspaceDocument.getOrCreateSessionDoc(t)).setError(`execution_error`),await this.sessionManager.finishSession(t),this.workspaceDocument.removeMachineSession(t)}),this.sessionManager.on(`exit`,async e=>{let t=e.sessionId;await this.finalizeACPState(t),(await this.workspaceDocument.getOrCreateSessionDoc(t)).setStatus(`completed`);let n=new Date().toISOString();this.workspaceDocument.updateMachineSession(t,{status:`completed`,completedAt:n})})}enqueueACPUpdate(e,t){let n=this.acpUpdateBuffers.get(e);n?n.push(t):this.acpUpdateBuffers.set(e,[t]),this.getOrCreateACPUpdateThrottler(e)()}getOrCreateACPUpdateThrottler(e){let t=this.acpUpdateThrottlers.get(e);return t||(t=(0,ae.throttle)(this.acpUpdateThrottleMs,()=>this.flushACPUpdates(e).catch(t=>{this.logger.error(`[${e}] Failed to flush ACP updates: ${t instanceof Error?t.message:`Unknown error`}`)})),this.acpUpdateThrottlers.set(e,t),t)}async flushACPUpdates(e){this.acpUpdateThrottlers.get(e)?.cancel({upcomingOnly:!0});let t=this.acpUpdateBuffers.get(e);if(!t||t.length===0){this.acpUpdateBuffers.delete(e);return}this.acpUpdateBuffers.delete(e),await Ue(await this.workspaceDocument.getOrCreateSessionDoc(e),t)}async flushAllACPUpdates(){let e=Array.from(new Set([...this.acpUpdateBuffers.keys(),...this.acpUpdateThrottlers.keys()]));for(let t of e)await this.finalizeACPState(t)}async finalizeACPState(e){try{await this.flushACPUpdates(e)}catch(t){this.logger.error(`[${e}] Failed to flush ACP updates during finalization: ${t instanceof Error?t.message:`Unknown error`}`)}finally{this.clearACPState(e)}}clearACPState(e){this.acpUpdateBuffers.delete(e),this.acpUpdateThrottlers.get(e)?.cancel(),this.acpUpdateThrottlers.delete(e)}registerMachine(){let e={type:`register`,role:`machine`,id:this.machineId,token:this.token,workspaceId:this.workspaceId,userId:this.userId,machineInfo:{name:this.machineName,cliVersion:this.cliVersion,os:process.platform,enableDocker:this.enableDocker}};this.sendMessage(e),this.logger.info(`Machine registered with name: ${this.machineName}`)}async handleMessage(e){switch(this.logger.debug(`Received message: ${e.type}`),e.type){case`register_response`:await this.handleRegisterResponse(e);break;case`session_create`:await this.handleSessionCreate(e);break;case`session_terminate`:await this.handleSessionTerminate(e);break;case`session_stop`:await this.handleSessionStop(e);break;case`permission_response`:await this.handlePermissionResponse(e);break;case`session_chat`:await this.handleSessionChat(e);break;default:this.logger.debug(`Unknown message: ${e}`)}}async handleSessionChat(e){let{sessionId:t,promptConfig:n,historyCid:r}=e;if(this.logger.info(`[${t}] Received chat request`),r)try{await(await this.workspaceDocument.getOrCreateSessionDoc(t)).markHistoryAsRead(r),this.logger.debug(`[${t}] Marked history as read`)}catch(e){this.logger.warn(`[${t}] Failed to mark history as read: ${e instanceof Error?e.message:`Unknown error`}`)}this.workspaceDocument.updateMachineSession(t,{status:`running`,cliType:n.agentType,createdAt:new Date().toISOString()});let i=this.sessionManager.getSession(t);i?(await i.agentClient?.connection?.prompt({sessionId:i.acpSessionId,prompt:[{type:`text`,text:n.prompt}]}),this.workspaceDocument.updateMachineSession(t,{status:`completed`,completedAt:new Date().toISOString()}),await this.finalizeACPState(t),this.logger.info(`[${t}] Chat request processed successfully`)):(this.logger.warn(`[${t}] Session not found`),(await this.workspaceDocument.getOrCreateSessionDoc(t)).setError(`session_not_found`),this.workspaceDocument.removeMachineSession(t))}async handleRegisterResponse(e){e.success?this.logger.info(`Registration successful, machine ID: ${this.machineId}`):this.logger.error(`Registration failed: ${e.error}`)}async handleSessionCreate(e){let{sessionId:t,config:n,workspaceId:r}=e;this.logger.info(`[${t}] Creating new session`);let i={sessionId:t,workspaceId:r,cliType:n.promptConfig.agentType,userId:this.userId,machineId:this.machineId,assumeDocExisting:!0,env:n.env};this.workspaceDocument.addMachineSession({sessionId:t,status:{status:`establish`,createdAt:new Date().toISOString()}});try{let e=await this.sessionManager.createSession(i);if(this.sendMessage({type:`session_create_response`,sessionId:t,success:!0}),n.githubRepo&&n.githubToken){this.workspaceDocument.updateMachineSession(t,{status:`git-clone`,repo:n.githubRepo});try{await Ve(n.githubRepo,n.githubToken,e,this.logger)}catch(e){let n=e instanceof Error?e.message:`Failed to clone GitHub repository`;this.logger.error(`[${t}] ${n}`),await this.sessionManager.setSessionError(t,`execution_error`),this.workspaceDocument.removeMachineSession(t),await this.finalizeACPState(t);return}}let r=n.promptConfig;this.workspaceDocument.updateMachineSession(t,{status:`running`,cliType:i.cliType});let a=await e.agentClient?.connection?.prompt({sessionId:e.acpSessionId,prompt:[{type:`text`,text:r.prompt}]});(await this.workspaceDocument.getOrCreateSessionDoc(t)).setStatus(`completed`);let o=new Date().toISOString();this.workspaceDocument.updateMachineSession(t,{status:`completed`,completedAt:o}),this.logger.debug(`[${t}] Prompt response: ${JSON.stringify(a)}`),await this.finalizeACPState(t),this.logger.info(`[${t}] Session created successfully`)}catch{await this.finalizeACPState(t),await this.sessionManager.setSessionError(t,`execution_error`),this.sendMessage({type:`session_create_response`,sessionId:t,success:!1})}}async handleSessionTerminate(e){let{sessionId:t,force:n}=e;this.logger.info(`[${t}] Terminating session`);try{await this.sessionManager.terminateSession(t,n),await this.finalizeACPState(t);let e={type:`session_terminated`,sessionId:t,error:void 0,reason:`user_request`};this.sendMessage(e),this.workspaceDocument.updateMachineSession(t,{status:`terminated`}),this.logger.info(`[${t}] Session terminated successfully`)}catch(e){let n={type:`session_terminated`,sessionId:t,error:`execution_error`,reason:`error`};this.sendMessage(n),this.logger.error(`[${t}] Failed to terminate: ${e instanceof Error?e.message:`Unknown error`}`)}}async handleSessionStop(e){let{sessionId:t}=e;this.logger.info(`[${t}] Received stop request`);let n=this.sessionManager.getSession(t);if(!n){this.logger.warn(`[${t}] Session not found for stop request`);return}if(!n.agentClient?.connection||!n.acpSessionId){this.logger.warn(`[${t}] Cannot stop session without active ACP connection`);return}try{await n.agentClient.connection.cancel({sessionId:n.acpSessionId}),await this.finalizeACPState(t),(await this.workspaceDocument.getOrCreateSessionDoc(t)).setStatus(`completed`);let e=new Date().toISOString();this.workspaceDocument.updateMachineSession(t,{status:`completed`,completedAt:e}),this.logger.info(`[${t}] Stop signal sent to agent`)}catch(e){this.logger.error(`[${t}] Failed to stop session: ${e instanceof Error?e.message:`Unknown error`}`)}}async handlePermissionResponse(e){this.logger.info(`[${e.sessionId}] Permission response received for ${e.requestId}: ${e.outcome.outcome}`);let t=this.pendingPermissionRequests.get(e.requestId);t?(t.resolve({outcome:e.outcome}),this.pendingPermissionRequests.delete(e.requestId)):this.logger.warn(`[${e.sessionId}] No pending permission request found for ${e.requestId}`);try{await qe(await this.workspaceDocument.getOrCreateSessionDoc(e.sessionId),e.requestId,e.outcome)}catch(t){this.logger.error(`[${e.sessionId}] Failed to update permission outcome: ${t instanceof Error?t.message:`Unknown error`}`)}}async handleAgentPermissionRequest(e,t,n){this.logger.info(`[${e}] Forwarding permission request ${t} for tool call ${n.toolCall.toolCallId}`);try{await Ke(await this.workspaceDocument.getOrCreateSessionDoc(e),t,n)}catch(t){this.logger.error(`[${e}] Failed to append permission request to history: ${t instanceof Error?t.message:`Unknown error`}`)}let r={type:`request_permission`,sessionId:e,requestId:t,request:n};return this.sendMessage(r),new Promise((n,r)=>{this.pendingPermissionRequests.has(t)&&this.logger.warn(`[${e}] Duplicate permission request ID detected: ${t}, overwriting existing handler`),this.pendingPermissionRequests.set(t,{resolve:n,reject:r,sessionId:e})})}sendMessage(e){try{this.socket.send(JSON.stringify(e))}catch(e){this.logger.error(`Failed to send message: ${e instanceof Error?e.message:`Unknown error`}`)}}updateSocket(e){this.socket=e,this.logger.debug(`WebSocket instance updated`)}async cleanup(){this.logger.info(`Cleaning up message handler resources`),await this.flushAllACPUpdates(),this.acpUpdateThrottlers.forEach(e=>e.cancel()),this.acpUpdateThrottlers.clear(),await this.sessionManager.cleanUp()}};const Ue=async(e,t)=>{let n=Array.isArray(t)?t:[t],r=[];for(let e of n)r.push(...We(e));r.length&&await e.updateHistory(e=>{let t=e;for(let e of r)t=Ge(t,e);return t})},We=e=>{let{update:t}=e;switch(t.sessionUpdate){case`user_message_chunk`:return[];case`agent_message_chunk`:switch(t.content.type){case`text`:return[{type:`text`,text:t.content.text}];case`image`:case`audio`:case`resource_link`:case`resource`:throw Error(`Unsupported content type: ${t.content.type}`)}case`agent_thought_chunk`:switch(t.content.type){case`text`:return[{type:`thought`,text:t.content.text}];case`image`:case`audio`:case`resource_link`:case`resource`:throw Error(`Unsupported content type: ${t.content.type}`)}return[];case`tool_call`:case`tool_call_update`:return[{type:`tool_call`,toolCallId:t.toolCallId,title:t.title,kind:t.kind||void 0,status:t.status||`pending`,content:t.content?.map(e=>{switch(e.type){case`diff`:return{type:e.type,path:e.path,newText:e.newText,oldText:e.oldText};case`terminal`:case`content`:return}}).filter(e=>e!==void 0)}];case`plan`:return[{type:`plan`,entries:t.entries}];case`available_commands_update`:return[{type:`available_commands`,commands:t.availableCommands}];case`current_mode_update`:throw Error(`Unsupported session update: ${t.sessionUpdate}`);default:return[]}},Ge=(e,t)=>{let n=e.length-1,r=n>=0?e[n]:null;if(!r||r.role!==`assistant`)return J(e,[t]);let i=t=>{let i=t(JSON.parse(r.contents)),a={...r,contents:JSON.stringify(i)},o=[...e];return o[n]=a,o};switch(t.type){case`text`:case`thought`:return i(e=>{let n=[...e],r=n.findIndex(e=>e.type===t.type);if(r>=0){let e=n[r];(e.type===`text`||e.type===`thought`)&&(n[r]={...e,text:e.text+t.text})}else n.push({...t});return n});case`plan`:return i(e=>{let n=[...e],r=n.findIndex(e=>e.type===`plan`);return r>=0?n[r]={...t}:n.push({...t}),n});case`tool_call`:{let n=!1,r=e.map(e=>{let r=JSON.parse(e.contents),i=!1,a=r.map(e=>{if(e.type===`tool_call`&&e.toolCallId===t.toolCallId){i=!0,n=!0;let r=e.content||[];if(t.content!==void 0){let e=t.content;e.some(e=>e.type===`diff`)&&(r=r.filter(e=>e.type!==`diff`)),r=[...r,...e]}return{...e,toolCallId:t.toolCallId,title:t.title===void 0?e.title:t.title,kind:t.kind===void 0?e.kind:t.kind,status:t.status===void 0?e.status:t.status,content:r}}return e});return i?{...e,contents:JSON.stringify(a)}:e});return n?r:J(r,[t])}case`available_commands`:return J(e,[t]);default:return e}},J=(e,t)=>[...e,{role:`assistant`,contents:JSON.stringify(t),timestamp:new Date().toISOString(),userId:void 0,read:void 0}],Ke=async(e,t,n)=>{let r=n.toolCall.toolCallId;await e.updateHistory(e=>{let i=!1;return e.forEach(e=>{let a=JSON.parse(e.contents),o=!1,s=a.map(e=>e.type===`tool_call`&&e.toolCallId===r?(o=!0,i=!0,Je(e,t,n)):e);o&&(e.contents=JSON.stringify(s))}),i||e.push({role:`assistant`,contents:JSON.stringify([Ye(t,n)]),timestamp:new Date().toISOString(),read:void 0,userId:void 0}),e})},qe=async(e,t,n)=>{await e.updateHistory(e=>(e.forEach(e=>{let r=JSON.parse(e.contents),i=!1,a=r.map(e=>e.type===`tool_call`&&e.permissionRequest?.requestId===t?(i=!0,{...e,permissionRequest:e.permissionRequest?{...e.permissionRequest,outcome:n}:e.permissionRequest}):e);i&&(e.contents=JSON.stringify(a))}),e))},Je=(e,t,n)=>{let r=n.toolCall;return{...e,title:e.title??r.title??null,kind:e.kind??r.kind??void 0,status:e.status??r.status??`pending`,content:e.content??r.content??void 0,locations:e.locations??r.locations??void 0,rawInput:e.rawInput??r.rawInput??void 0,rawOutput:e.rawOutput??r.rawOutput??void 0,permissionRequest:{requestId:t,options:n.options,outcome:e.permissionRequest?.outcome}}},Ye=(e,t)=>Je({type:`tool_call`,toolCallId:t.toolCall.toolCallId,title:t.toolCall.title??null,status:t.toolCall.status??`pending`,kind:t.toolCall.kind??void 0,content:t.toolCall.content??void 0,locations:t.toolCall.locations??void 0,rawInput:t.toolCall.rawInput??void 0,rawOutput:t.toolCall.rawOutput??void 0},e,t);var Xe=class{manager;handler=null;sessionManager=null;initialized=!1;logger;constructor(e){this.options=e,this.manager=new Le(e.managerOptions),this.logger=e.logger}async initialize(){if(this.initialized)return{socket:this.requireSocket(),sessionManager:this.requireSessionManager(),messageHandler:this.requireHandler()};await this.manager.connect();let e=this.requireSocket();return this.sessionManager=this.options.sessionManagerFactory(e),this.handler=new He(e,this.sessionManager,this.options.workspaceDocument,this.options.logger,this.options.handlerConfig),this.attachBridges(),await this.sessionManager.initialize(),this.initialized=!0,{socket:e,sessionManager:this.sessionManager,messageHandler:this.handler}}getManager(){return this.manager}getMessageHandler(){return this.handler}getSessionManager(){return this.sessionManager}async cleanup(){this.detachBridges(),this.handler&&=(await this.handler.cleanup(),null),await this.sessionManager?.cleanUp().catch(e=>{this.options.logger.error(`Cleanup failed: ${e instanceof Error?e.message:`Unknown error`}`)}),this.sessionManager=null,this.initialized=!1,this.manager.close()}attachBridges(){this.manager.on(`message`,this.handleRawMessage),this.manager.on(`open`,this.handleManagerOpen)}detachBridges(){this.manager.off(`message`,this.handleRawMessage),this.manager.off(`open`,this.handleManagerOpen)}handleRawMessage=async e=>{let t=this.handler;if(!t)return;let n=e.toString();if(n!==`pong`)try{let e=JSON.parse(n);await t.handleMessage(e)}catch(e){if(e instanceof SyntaxError){this.options.logger.debug(`Received non-JSON message: ${n}`);return}this.options.logger.error(`Failed to handle message: ${e instanceof Error?e.message:`Unknown error`}`)}};handleManagerOpen=()=>{let e=this.handler;if(!e)return;let t=this.manager.getSocket();if(!t){this.options.logger.warn(`WebSocket reopened but socket is unavailable`);return}e.updateSocket(t),e.registerMachine(),this.options.logger.info(`WebSocket connection re-established`)};requireSocket(){let e=this.manager.getSocket();if(!e)throw Error(`WebSocket connection has not been established`);return e}requireSessionManager(){if(!this.sessionManager)throw Error(`Session manager has not been initialized`);return this.sessionManager}requireHandler(){if(!this.handler)throw Error(`Message handler has not been initialized`);return this.handler}};const Ze={claude:{command:`npx`,args:[`-y`,`@zed-industries/claude-code-acp`]},codex:{command:`npx`,args:[`-y`,`@zed-industries/codex-acp`,`-c`,`shell_environment_policy.ignore_default_excludes=true`]}};var Qe=class extends w.EventEmitter{logger;machineId;token;requestPermissionHandler;constructor(e,t,n,r){super(),this.workspaceDocument=r,this.logger=e,this.token=t,this.machineId=n}async createSession(e){e.assumeDocExisting||(e.sessionId=await(await this.workspaceDocument.getOrCreateWorkspaceDoc(e.workspaceId,e.userId)).createSession(e.machineId,e.cliType,e.title)),await this.workspaceDocument.getOrCreateSessionDoc(e.sessionId),this.logger.debug(`[${e.sessionId}] Session will enter create inner`);let t=await this.createSessionInner(e),n=e.sessionId,r;try{r=await t.createAgent({command:Ze[e.cliType].command,args:Ze[e.cliType].args,onUpdateMessage:e=>{this.emit(`onACPUpdateMessage`,n,e)},onRequestPermission:(e,t)=>this.requestPermissionHandler?this.requestPermissionHandler(n,e,t):(this.logger.warn(`[${n}] Permission handler not configured`),Promise.resolve({outcome:{outcome:`cancelled`}}))})}catch(t){throw this.logger.error(`[${e.sessionId}] Failed to create agent: ${t instanceof Error?t.message:`Unknown error`}`),t}return await(await this.workspaceDocument.getOrCreateSessionDoc(n)).setChatId(r),t}async cleanUp(){await this.workspaceDocument.cleanUp(),await this.cleanupInner()}async finishSession(e){await this.workspaceDocument.cleanSessionDoc(e)}setRequestPermissionHandler(e){this.requestPermissionHandler=e}async handleFinishCommand(e,t){let n=`${H}/api/issues/${t}/review`;try{let e=await(await fetch(n,{method:`GET`,headers:{"Content-Type":`application/json`,Authorization:`Bearer ${this.token}`}})).json();e.success?this.logger.success(`Issue ${t} marked as completed`):this.logger.error(`Failed to mark issue as completed: ${e.error}`)}catch(e){this.logger.error(`Error calling issue complete API: ${e instanceof Error?e.message:`Unknown error`}`)}}async setSessionError(e,t){let n=await this.workspaceDocument.getOrCreateSessionDoc(e);n&&n.setError(t)}},$e=class extends T.Transform{buffer=``;constructor(){super({objectMode:!0})}_transform(e,t,n){this.buffer+=e.toString();let r=this.buffer.split(`
17
+ `);this.buffer=r.pop()||``;for(let e of r)if(e.trim())try{let t=JSON.parse(e);this.push(t)}catch{}n()}_flush(e){if(this.buffer.trim())try{let e=JSON.parse(this.buffer);this.push(e)}catch{this.emit(`error`,Error(`Invalid JSON: ${this.buffer}`))}e()}reset(){this.buffer=``}getBuffer(){return this.buffer}},et=class{connection=null;logger;sandbox;terminalManager;acpSessionId=null;constructor(e){this.options=e,this.logger=e.logger,this.terminalManager=e.terminalManager,this.sandbox=e.sandbox}async requestPermission(e){if(this.sandbox)return{outcome:{outcome:`selected`,optionId:(e.options.find(e=>e.kind===`allow_once`||e.kind===`allow_always`||!e.kind&&e.name.toLowerCase().includes(`allow`))||e.options[0]).optionId}};this.ensureSessionMatch(e.sessionId);let t=(0,oe.randomUUID)();return this.logger.info(`[${this.options.sessionId}] Requesting permission for tool call ${e.toolCall.toolCallId}`),this.options.onRequestPermission(t,e)}async sessionUpdate(e){this.logger.debug(`ACP Session ${this.options.sessionId} update:`,e),this.options.onUpdateMessage(e)}async writeTextFile(e){throw Error(`Method not implemented.`)}async readTextFile(e){throw Error(`Method not implemented.`)}async createTerminal(e){this.ensureSessionMatch(e.sessionId);let t=e.env?.reduce((e,t)=>(e[t.name]=t.value,e),{})??void 0;return{terminalId:await this.terminalManager.createTerminal(e.sessionId,e.command,e.args??[],e.cwd??void 0,t,e.outputByteLimit??void 0)}}async terminalOutput(e){this.ensureSessionMatch(e.sessionId);let t=await this.terminalManager.terminalOutput(e.sessionId,e.terminalId);return{output:t.output,truncated:t.truncated,exitStatus:t.exitStatus??void 0}}async releaseTerminal(e){return this.ensureSessionMatch(e.sessionId),await this.terminalManager.releaseTerminal(e.sessionId,e.terminalId),{}}async waitForTerminalExit(e){return this.ensureSessionMatch(e.sessionId),await this.terminalManager.waitForTerminalExit(e.sessionId,e.terminalId)}async killTerminal(e){return this.ensureSessionMatch(e.sessionId),await this.terminalManager.killTerminal(e.sessionId,e.terminalId),{}}async extMethod(e,t){throw Error(`Method not implemented.`)}async extNotification(e,t){throw Error(`Method not implemented.`)}async startSession(e,t){let n=new E.ClientSideConnection(()=>this,e);this.connection=n,this.logger.info(`Starting ACP Client...`);let r=await n.initialize({protocolVersion:E.PROTOCOL_VERSION,clientCapabilities:{terminal:!0}});this.logger.debug(`ACP Client initialized:`,r);let i=await n.newSession({cwd:t,mcpServers:[]});return this.logger.debug(`ACP Session started: ${i.sessionId}`),this.acpSessionId=i.sessionId,this.logger.debug(`ACP Session mode set to agent: ${i.sessionId}`),{sessionId:i.sessionId}}ensureSessionMatch(e){if(!this.acpSessionId)throw Error(`ACP session has not been initialized yet.`);if(e!==this.acpSessionId)throw Error(`Mismatched ACP session. Expected ${this.acpSessionId} but got ${e}`)}};const tt=1024*1024;var nt=class{terminals=new Map;logger;sessionLabel;getActiveSessionId;defaultOutputByteLimit;constructor(e){this.logger=e.logger,this.sessionLabel=e.sessionLabel,this.getActiveSessionId=e.getActiveAcpSessionId,this.defaultOutputByteLimit=e.defaultOutputByteLimit??1048576}async createTerminal(e,t,n,r,i,a){this.ensureValidSession(e);let o=(0,oe.randomUUID)(),s={id:o,handle:null,output:Buffer.alloc(0),outputByteLimit:a??this.defaultOutputByteLimit,truncated:!1,exitStatus:null,waiters:[]};return s.handle=await this.startProcess({terminalId:o,command:t,args:n??[],cwd:r,env:i},{onData:e=>this.appendOutput(s,e),onExit:(e,t)=>this.handleExit(s,e,t),onError:e=>{this.logger.error(`[${this.sessionLabel}] Terminal ${o} error: ${e.message}`)}}),this.terminals.set(o,s),this.logger.debug(`[${this.sessionLabel}] Terminal ${o} started: ${t}`),o}async terminalOutput(e,t){let n=this.getTerminal(e,t);return{output:n.output.toString(`utf8`),truncated:n.truncated,exitStatus:n.exitStatus}}async releaseTerminal(e,t){let n=this.getTerminal(e,t);try{await this.killHandle(n)}catch(e){this.logger.warn(`[${this.sessionLabel}] Failed to kill terminal ${t} on release: ${e}`)}n.exitStatus||(n.exitStatus={exitCode:null,signal:`SIGTERM`},this.resolveWaiters(n)),await this.disposeHandle(n),this.terminals.delete(t),this.logger.debug(`[${this.sessionLabel}] Terminal ${t} released`)}async waitForTerminalExit(e,t){let n=this.getTerminal(e,t);return n.exitStatus?n.exitStatus:await new Promise(e=>{n.waiters.push(e)})}async killTerminal(e,t){let n=this.getTerminal(e,t);await this.killHandle(n)}resolveWaiters(e){let t=e.exitStatus??{exitCode:null,signal:null};for(;e.waiters.length;){let n=e.waiters.shift();n&&n(t)}}ensureValidSession(e){let t=this.getActiveSessionId();if(!t)throw Error(`ACP session is not active yet.`);if(t!==e)throw Error(`ACP session mismatch for terminal request: ${e}`)}getTerminal(e,t){this.ensureValidSession(e);let n=this.terminals.get(t);if(!n)throw Error(`Terminal ${t} not found or already released`);return n}handleExit(e,t,n){this.terminals.has(e.id)&&(e.exitStatus={exitCode:typeof t==`number`?t:null,signal:n??void 0},this.resolveWaiters(e))}appendOutput(e,t){if(e.outputByteLimit===0){t.length&&(e.truncated=!0);return}let n=Buffer.isBuffer(t)?t:Buffer.from(t);e.output=Buffer.concat([e.output,n]),e.output.length>e.outputByteLimit&&(e.truncated=!0,e.output=ot(e.output,e.outputByteLimit))}},rt=class extends nt{resolveWorkdir;buildEnv;constructor(e){super(e),this.resolveWorkdir=e.resolveWorkdir,this.buildEnv=e.buildEnv}async startProcess(e,t){let n=this.resolveWorkdir(e.cwd),r=this.buildEnv(e.env),i=(0,_.spawn)(`bash`,[`-lc`,at(e.command,e.args)],{cwd:n,env:r,stdio:[`ignore`,`pipe`,`pipe`]}),a=e=>t.onData(e),o=e=>t.onData(e),s=(e,n)=>t.onExit(e,n),c=e=>t.onError?.(e);return i.stdout?.on(`data`,a),i.stderr?.on(`data`,o),i.on(`close`,s),i.on(`error`,c),{child:i,dispose:()=>{i.stdout?.off(`data`,a),i.stderr?.off(`data`,o),i.off(`close`,s),i.off(`error`,c)}}}async killHandle(e){!e.exitStatus&&!e.handle.child.killed&&e.handle.child.kill(`SIGTERM`)}async disposeHandle(e){e.handle.dispose()}},it=class extends nt{getContainer;resolveWorkdir;constructor(e){super(e),this.getContainer=e.getContainer,this.resolveWorkdir=e.resolveWorkdir}async startProcess(e,t){let n=this.getContainer();if(!n)throw Error(`Container is not ready for terminal execution`);let r=at(e.command,e.args),i=await n.exec({Cmd:[`/bin/bash`,`-lc`,r],WorkingDir:this.resolveWorkdir(e.cwd),AttachStdout:!0,AttachStderr:!0,AttachStdin:!0,Tty:!0,Env:e.env?Object.entries(e.env).map(([e,t])=>`${e}=${t}`):void 0}),a=await i.start({hijack:!0,stdin:!0}),o=new T.PassThrough,s=new T.PassThrough,c=e=>t.onData(e),l=e=>t.onData(e),u=e=>t.onError?.(e),d=async()=>{try{let e=await i.inspect();t.onExit(e.ExitCode??0,null)}catch(e){t.onError?.(e),t.onExit(null,null)}};return o.on(`data`,c),s.on(`data`,l),n.modem.demuxStream(a,o,s),a.on(`error`,u),a.on(`end`,d),{exec:i,stream:a,stdout:o,stderr:s,pid:await ct(i),removeListeners:()=>{o.off(`data`,c),s.off(`data`,l),a.off(`error`,u),a.off(`end`,d)}}}async killHandle(e){if(!e.exitStatus)try{if(e.handle.pid)try{process.kill(e.handle.pid,`SIGTERM`)}catch(e){let t=e;if(t.code!==`ESRCH`)throw t}e.handle.stream.destroyed||(e.handle.stream.write(``),e.handle.stream.end())}catch(e){this.logger.warn(`Failed to kill docker terminal: ${e}`)}}async disposeHandle(e){e.handle.removeListeners(),e.handle.stream.destroyed||e.handle.stream.destroy(),e.handle.stdout.destroyed||e.handle.stdout.destroy(),e.handle.stderr.destroyed||e.handle.stderr.destroy()}};function at(e,t){return t.length?`${e} ${t.map(e=>`'${e.replace(/'/g,`'"'"'`)}'`).join(` `)}`:e}function ot(e,t){if(e.length<=t)return e;let n=e.length-t;for(;n<e.length&&st(e[n]);)n+=1;return e.slice(n)}function st(e){return(e&192)==128}async function ct(e,t=5,n=100){for(let r=0;r<t;r++){try{let t=await e.inspect();if(t.Pid&&t.Pid>0)return t.Pid}catch{return null}await new Promise(e=>setTimeout(e,n))}return null}var lt=class extends w.default{isDocker=!0;docker;container=null;config;status;dockerExec=null;stream=null;healthCheckInterval;logger;lastInputWasAI=!1;sessionId;agentClient=null;acpSessionId=null;terminalManager;env={};constructor(e,t,n){super(),this.docker=e,this.config=t,this.logger=n,this.sessionId=t.sessionId,this.logger.debug(`config`,{config:t});let r=`lody-${t.sessionId.replaceAll(`:`,``).replaceAll(`@`,``)}-${Date.now()}`;this.status={sessionId:t.sessionId,containerId:``,containerName:r,status:`initializing`},this.terminalManager=new it({logger:this.logger,sessionLabel:this.sessionId,getActiveAcpSessionId:()=>this.acpSessionId,getContainer:()=>this.container,resolveWorkdir:e=>e??this.getWorkdir()})}getWorkdir(){return W}async create(){try{this.logger.info(`[${this.config.sessionId}] Creating devcontainer session`),this.status.status=`building`,await this.ensureDevcontainerImage();let e=await this.buildContainerConfig();this.status.status=`starting`,this.container=await this.docker.createContainer(e),this.status.containerId=this.container.id,this.logger.info(`[${this.config.sessionId}] Container created: ${this.status.containerName}`),await this.container.start(),this.startHealthCheck(),this.status.status=`running`,this.emit(`created`,this.status)}catch(e){throw this.status.status=`error`,this.status.error=e instanceof Error?e.message:`Unknown error`,this.logger.error(`[${this.config.sessionId}] Failed to create container: ${this.status.error}`),this.emit(`error`,{sessionId:this.config.sessionId,error:e,status:{...this.status}}),e}}async exec(e,t,n,r){let i=this.formatCommand(e,t);return this.lastInputWasAI=r,await this.executeNonInteractiveCommand(i,n,r)}async ensureDevcontainerImage(){await dt({docker:this.docker,logger:this.logger,sessionPrefix:this.config.sessionId})}async buildContainerConfig(){let e={...this.config.env,LODY_SESSION_ID:this.config.sessionId,LODY_MACHINE_SERVER_URL:process.env.LODY_MACHINE_SERVER_URL,TERM:`xterm-256color`,COLORTERM:`truecolor`};this.env=e,this.logger.debug(`[${this.config.sessionId}] Container config with env: ${JSON.stringify(e)}`);let t=[],n=m.default.join(g.default.homedir(),`.claude`),r=m.default.join(g.default.homedir(),`.codex`);h.default.existsSync(n)&&t.push(`${n}:/home/node/.claude:rw`),h.default.existsSync(r)&&t.push(`${r}:/home/node/.codex:rw`);let i=m.default.join(g.default.homedir(),`.claude.json`);return h.default.existsSync(i)&&t.push(`${i}:/home/node/.claude.json:rw`),{name:this.status.containerName,Image:`lody/devcontainer:latest`,Hostname:`lody-${this.config.sessionId.substring(0,8)}`,Env:Object.entries(e).map(([e,t])=>`${e}=${t}`),WorkingDir:this.config.workingDir||W,User:`node`,Tty:!0,OpenStdin:!0,StdinOnce:!1,AttachStdin:!0,AttachStdout:!0,AttachStderr:!0,HostConfig:{Init:!0,Binds:t,NetworkMode:`host`,RestartPolicy:{Name:`unless-stopped`}},Labels:{"lody.managed":`true`,"lody.session.id":this.config.sessionId}}}handleOutput(e){e.trim()!==``&&this.emit(`output`,{sessionId:this.config.sessionId,data:e,timestamp:new Date,stream:`stdout`,isFromAI:this.lastInputWasAI})}handleClose(e,t){this.logger.info(`[${this.config.sessionId}] process exited: code=${e}, signal=${t}`),this.status.exitCode=e||1,this.status.status=`stopped`,this.emit(`exited`,this.status)}handleError(e){this.logger.info(`[${this.config.sessionId}] docker process error: ${e}`),this.status.exitCode=1,this.status.status=`error`,this.emit(`error`,{sessionId:this.sessionId,error:e,status:{...this.status}})}formatCommand(e,t){return t.length?`${e} ${t.map(e=>`'${e.replace(/'/g,`'"'"'`)}'`).join(` `)}`:e}async createAgent(e){if(!this.container)throw Error(`Container not available`);let t=await(await this.container.exec({Cmd:[`/bin/bash`,`-c`,`${e.command} ${e.args?.join(` `)}`],WorkingDir:this.getWorkdir(),Tty:!1,AttachStdin:!0,AttachStdout:!0,AttachStderr:!0,Env:Object.entries(this.env).map(([e,t])=>`${e}=${t}`)})).start({hijack:!0,stdin:!0}),n=new WritableStream({write:e=>{t.write(e)},close:()=>{t.end()}}),r=this.container,i=(0,E.ndJsonStream)(n,new ReadableStream({start(e){let n=new T.Writable({write(t,n,r){e.enqueue(new Uint8Array(t)),r()}});r.modem.demuxStream(t,n,null),t.on(`end`,()=>e.close()),t.on(`error`,t=>e.error(t))}})),a=new et({logger:this.logger,sessionId:this.sessionId,terminalManager:this.terminalManager,onUpdateMessage:e.onUpdateMessage,onRequestPermission:e.onRequestPermission,sandbox:!0}),{sessionId:o}=await a.startSession(i,this.getWorkdir());return this.agentClient=a,this.acpSessionId=o,this.logger.debug(`[${this.sessionId}] ACP agent process started.`),o}async executeNonInteractiveCommand(e,t,n,r=!1,i=36e5){if(!this.container)throw Error(`Container not available`);this.logger.info(`[${this.config.sessionId}] Executing non-interactive command: ${e}`);try{let a=await(await this.container.exec({Cmd:[`/bin/bash`,`-c`,e],WorkingDir:t,Tty:!1,AttachStdin:!1,AttachStdout:!0,AttachStderr:!1,Env:Object.entries(this.env).map(([e,t])=>`${e}=${t}`)})).start({hijack:!0,stdin:!1,Detach:r}),o=new $e;return new Promise((t,r)=>{let s=``,c=null;c=setTimeout(()=>{a.destroy(),this.handleClose(null,null),r(Error(`Command timed out after ${i}ms: ${e}`))},i),a.on(`data`,e=>{if(e.length>=8){let t=e[0],n=e.slice(8).toString(`utf8`);t===1?(this.logger.debug(`[${this.config.sessionId}] Command stdout: ${n}`),o.write(n)):t===2&&(s+=n,this.handleError(Error(s)),r(Error(`Command failed: ${s}`)))}}),a.on(`end`,async()=>{c&&clearTimeout(c),n&&this.emit(`exited`,{sessionId:this.sessionId,exitCode:0,containerId:this.status.containerId,containerName:this.status.containerName,status:`stopped`}),t(``)}),a.on(`error`,e=>{c&&clearTimeout(c),this.handleError(e),a.destroy(),r(e)}),o.on(`data`,e=>{let t=JSON.stringify(e);n&&this.handleOutput(t)})})}catch(e){throw this.logger.error(`[${this.config.sessionId}] Failed to create non-interactive exec: ${e}`),this.handleError(e instanceof Error?e:Error(`Unknown error`)),e}}startHealthCheck(){this.healthCheckInterval=setInterval(async()=>{try{await this.updateStatus()}catch(e){this.logger.error(`[${this.config.sessionId}] Health check failed: ${e instanceof Error?e.message:`Unknown error`}`)}},3e4)}async updateStatus(){if(this.container)try{let e=await this.container.inspect();e.State.Running?this.status.status=`running`:(this.status.status=`stopped`,this.status.exitCode=e.State.ExitCode)}catch(e){this.logger.error(`[${this.config.sessionId}] Failed to update status: ${e instanceof Error?e.message:`Unknown error`}`),this.terminate(!0)}}async getStatus(){return this.container&&await this.updateStatus(),{...this.status}}getConfig(){return{...this.config}}async terminate(e=!1){if(this.logger.info(`[${this.config.sessionId}] Terminating container ${e?`(force)`:``}`),this.status.status===`stopping`||this.status.status===`terminated`){this.logger.debug(`[${this.config.sessionId}] Container already ${this.status.status}`);return}try{if(this.logger.info(`[${this.config.sessionId}] Terminating container ${e?`(force)`:``}`),this.status.status=`stopping`,this.healthCheckInterval&&=(clearInterval(this.healthCheckInterval),void 0),this.dockerExec)try{this.stream&&this.stream.destroy(),await new Promise(e=>setTimeout(e,1e3));try{(await this.dockerExec.inspect()).Running&&await this.container?.kill()}catch(e){this.logger.debug(`Exec inspect failed (likely already terminated): ${e.message}`)}}catch(e){this.logger.warn(`Failed to cleanup exec gracefully: ${e instanceof Error?e.message:`Unknown error`}`)}finally{this.dockerExec=null,this.stream=null}if(this.container){try{e?await this.container.kill():await this.container.stop({t:10})}catch(e){if(e.statusCode===409){this.logger.debug(`[${this.config.sessionId}] Container removal already in progress`);return}if(e.statusCode===304)this.logger.debug(`[${this.config.sessionId}] Container already stopped`);else throw e}await this.container.remove({force:!0}),this.container=null}this.status.status=`terminated`,this.emit(`terminated`,this.status)}catch(e){throw this.logger.error(`[${this.config.sessionId}] Failed to terminate container: ${e instanceof Error?e.message:`Unknown error`}`),e}}async cleanup(){await this.terminate(!0)}};async function ut({docker:e,sessionPrefix:t,logger:n,spinner:r}){let i=t?`[${t}] `:``;n.info(`${i}Building devcontainer image...`),r&&(r.text=`Building development environment image (this may take several minutes)...`);let a=m.default.join(__dirname,`devcontainer`,`Dockerfile`),o=await e.buildImage({context:m.default.dirname(a),src:[`Dockerfile`,`devcontainer.json`]},{t:`lody/devcontainer:latest`,buildargs:{CLAUDE_CLI_VERSION:`latest`}}),s=0,c=0,l=Date.now();await new Promise((t,a)=>{e.modem.followProgress(o,(e,n)=>{e?a(e):(r&&r.succeed(`Development environment image built successfully`),t(n))},e=>{if(e.stream){let t=e.stream.trim();n.info(`${i}Build: ${t}`);let a=t.match(/^Step (\d+)\/(\d+)\s*:\s*(.+)$/);if(a){s=parseInt(a[1]),c=parseInt(a[2]);let e=a[3];r&&(r.text=`Building image: Step ${s}/${c} - ${e}`)}let o=Date.now();r&&o-l>3e3&&(c>0?r.text=`Building image: ${Math.round(s/c*100)}% complete (Step ${s}/${c})`:r.text=`Building development environment image... (this may take several minutes)`,l=o),r&&(t.includes(`Downloading`)||t.includes(`Extracting`))&&t.length<100&&(r.text=`Building image: ${t}`)}e.error&&(n.error(`${i}Build error: ${e.error}`),r&&r.fail(`Build failed: ${e.error}`),a(Error(e.error)))})})}async function dt({docker:e,sessionPrefix:t,logger:n,spinner:r}){let i=t?`[${t}] `:``;try{await e.getImage(`lody/devcontainer:latest`).inspect(),n.info(`${i}Using existing devcontainer image`),r&&(r.text=`Development environment ready`)}catch(a){n.error(`${i}Failed to get devcontainer image: ${a instanceof Error?a.message:`Unknown error`}`),await ut({docker:e,sessionPrefix:t,logger:n,spinner:r})}}var ft=class extends Qe{isDocker=!0;docker;sessions=new Map;cleanupInterval;pingInterval;constructor(e,t,n,r){super(e,t,n,r),this.docker=new C.default,this.startPeriodicCleanup()}async initialize(){await dt({docker:this.docker,logger:this.logger}),this.logger.info(`Docker session manager initialized`)}async createSessionInner(e){let t={sessionId:e.sessionId,env:{...e.env},workingDir:W};try{return await this.createOrReuseSession(t)}catch(t){throw this.logger.error(`[${e.sessionId}] Failed to create Docker session: ${t instanceof Error?t.message:`Unknown error`}`),t}}async terminateSession(e,t=!1){let n=this.sessions.get(e);if(!n){this.logger.warn(`Session ${e} not found`);return}await n.terminate(t),this.logger.info(`[${e}] Docker session terminated`)}async cleanupInner(){this.logger.info(`Cleaning up all Docker sessions...`),this.cleanupInterval&&=(clearInterval(this.cleanupInterval),void 0);try{await this.terminateAllSessions(!0),await this.cleanupOrphanedContainers()}catch(e){this.logger.error(`Failed to cleanup Docker sessions: ${e instanceof Error?e.message:`Unknown error`}`)}}hasSession(e){return this.sessions.has(e)}getSession(e){return this.sessions.get(e)||null}async createOrReuseSession(e){let t=this.sessions.get(e.sessionId);if(t){let n=await t.getStatus();if(n.status===`running`||n.status===`starting`)return this.logger.info(`[${e.sessionId}] Reusing existing container session`),this.startPingInterval(),t;this.logger.warn(`[${e.sessionId}] Session exists but not running, terminating`),await this.terminateSession(e.sessionId,!0)}this.logger.debug(`[${e.sessionId}] Creating new devcontainer session config: ${JSON.stringify(e)}`);let n=new lt(this.docker,e,this.logger);this.registerSessionEvents(n),this.sessions.set(e.sessionId,n),this.startPingInterval();try{return await n.create(),this.logger.info(`[${e.sessionId}] Devcontainer session created successfully`),n}catch(t){throw this.sessions.delete(e.sessionId),this.tryStopPingInterval(),t}}registerSessionEvents(e){let t=e.getConfig().sessionId;e.on(`output`,e=>{let t={sessionId:e.sessionId,data:e.data,timestamp:e.timestamp};this.emit(`output`,t)}),e.on(`error`,e=>{let n=e.error instanceof Error?e.error:Error(typeof e.error==`string`?e.error:`Unknown error`),r={sessionId:e.sessionId??t,error:n};this.emit(`error`,r)}),e.on(`exited`,e=>{let t={sessionId:e.sessionId,exitCode:e.exitCode||0};this.emit(`exit`,t)}),e.on(`terminated`,e=>{this.sessions.delete(t);let n={sessionId:e.sessionId,exitCode:e.exitCode,status:e};this.emit(`terminated`,n),this.tryStopPingInterval()})}async terminateAllSessions(e=!1){let t=Array.from(this.sessions.values()).map(t=>t.terminate(e).catch(e=>this.logger.error(`Failed to terminate session: ${e instanceof Error?e.message:`Unknown error`}`)));await Promise.allSettled(t),this.sessions.clear(),this.tryStopPingInterval()}async getAllSessionsStatus(){let e=[];for(let t of this.sessions.values())try{let n=await t.getStatus();e.push(n)}catch(e){this.logger.error(`Failed to get status for session: ${e instanceof Error?e.message:`Unknown error`}`)}return e}async cleanupOrphanedContainers(){try{let e=await this.docker.listContainers({all:!0,filters:{label:[`lody.managed=true`]}}),t=new Set(this.sessions.keys());for(let n of e){let e=n.Labels[`lody.session.id`];if(!e||!t.has(e))try{let e=this.docker.getContainer(n.Id);await e.stop(),await e.remove({force:!0}),this.logger.info(`Cleaned up orphaned container: ${n.Names[0]}`)}catch(e){this.logger.warn(`Failed to cleanup container: ${e instanceof Error?e.message:`Unknown error`}`)}}}catch(e){this.logger.error(`Failed to cleanup orphaned containers: ${e instanceof Error?e.message:`Unknown error`}`)}}startPeriodicCleanup(){this.cleanupInterval=setInterval(async()=>{try{await this.cleanupOrphanedContainers()}catch(e){this.logger.error(`Periodic cleanup failed: ${e instanceof Error?e.message:`Unknown error`}`)}},3600*1e3)}startPingInterval(){this.sessions.size===0||this.pingInterval||(this.pingInterval=setInterval(()=>{this.emit(`ping`)},5e3))}tryStopPingInterval(){this.pingInterval&&this.sessions.size===0&&(clearInterval(this.pingInterval),this.pingInterval=void 0)}},pt=class extends w.default{sessionId;isDocker=!1;config;logger;status=`created`;activeProcess=null;agentProcess=null;agentClient=null;acpSessionId=null;terminalManager;constructor(e,t,n){super(),this.pwd=t,this.config=e,this.logger=n,this.sessionId=e.sessionId,this.terminalManager=new rt({logger:this.logger,sessionLabel:this.sessionId,getActiveAcpSessionId:()=>this.acpSessionId,resolveWorkdir:e=>e??this.getWorkdir(),buildEnv:e=>this.buildShellEnv(e)})}getWorkdir(){let e=m.default.join(this.pwd,`LODY_WORKDIR`,this.sessionId);return h.existsSync(e)||h.mkdirSync(e,{recursive:!0}),e}async exec(e,t,n,r){if(this.status===`failed`||this.status===`terminated`)throw Error(`Session ${this.sessionId} is not running`);let i=this.formatCommand(e,t);return await this.runCommand(i,n,r)}async terminate(e=!1){this.logger.info(`[${this.sessionId}] Terminating session${e?` (force)`:``}`);let t=t=>{t&&!t.killed&&t.kill(e?`SIGKILL`:`SIGTERM`)},n=this.activeProcess;t(n),t(this.agentProcess),this.status=`terminated`;let r={sessionId:this.sessionId,exitCode:n?.exitCode??0};this.emit(`terminated`,r)}handleParserData=e=>{let t=JSON.stringify(e);this.emitOutput(t)};handleParserError=e=>{let t=e instanceof Error?e.message:String(e);this.logger.error(`[${this.sessionId}] Parser error: ${t}`)};formatCommand(e,t){return t.length?`${e} ${t.map(e=>`'${e.replace(/'/g,`'"'"'`)}'`).join(` `)}`:e}buildShellEnv(e){return{...process.env,...this.config.env,...e,FORCE_COLOR:`1`,TERM:`xterm-256color`,PS1:``,PROMPT_COMMAND:``,LODY_SESSION_ID:this.sessionId}}async createAgent(e){let t=this.buildShellEnv();console.log(`[${this.sessionId}] Starting ACP agent process...`,this.getWorkdir());let n=(0,_.spawn)(e.command,e.args,{cwd:this.getWorkdir(),env:t,stdio:[`pipe`,`pipe`,`pipe`]});this.agentProcess=n,n.on(`error`,e=>{this.logger.error(`[${this.sessionId}] Agent process error: ${e.message}`)}),n.once(`exit`,(e,t)=>{this.logger.debug(`[${this.sessionId}] ACP agent process exited with code ${e} signal ${t}`),this.agentProcess=null});let r=(0,E.ndJsonStream)(new WritableStream({write(e){n.stdin.write(e)},close(){n.stdin.end()}}),new ReadableStream({start(e){n.stdout.on(`data`,t=>{e.enqueue(t)}),n.stdout.on(`end`,()=>{e.close()})}})),i=new et({logger:this.logger,sessionId:this.sessionId,terminalManager:this.terminalManager,onUpdateMessage:e.onUpdateMessage,onRequestPermission:e.onRequestPermission,sandbox:!0}),{sessionId:a}=await i.startSession(r,this.getWorkdir());return this.acpSessionId=a,this.agentClient=i,this.logger.debug(`[${this.sessionId}] ACP agent process started.`),a}runCommand(e,t,n){let r=this.buildShellEnv();return this.logger.info(`[${this.sessionId}] Executing command: ${e}`),new Promise((i,a)=>{let o=(0,_.spawn)(`bash`,[`-lc`,e],{cwd:t,env:r,stdio:[`ignore`,`pipe`,`pipe`]});this.logger.debug(`[${this.sessionId}] Spawned process PID: ${o.pid} with command: bash -lc ${e}`),this.activeProcess=o;let s=``,c=``,l=n?new $e:null;l&&(l.on(`data`,this.handleParserData),l.on(`error`,this.handleParserError)),o.stdout?.on(`data`,e=>{let t=e.toString();s+=t,l&&l.write(t)}),o.stderr?.on(`data`,e=>{let t=e.toString();c+=t,this.logger.warn(`[${this.sessionId}] Shell stderr: ${t}`)});let u=()=>{this.activeProcess=null,l&&(l.end(),l.removeListener(`data`,this.handleParserData),l.removeListener(`error`,this.handleParserError))};o.on(`close`,(e,t)=>{u();let r=e??0;!n&&s.trim()&&this.logger.info(`[${this.sessionId}] Command output: ${s.trim()}`),n&&this.emit(`exit`,{sessionId:this.sessionId,exitCode:r}),i(s.trimEnd())}),o.on(`error`,e=>{u(),this.emit(`error`,{sessionId:this.sessionId,error:e}),a(e)})})}emitOutput(e){if(e.trim()===``)return;let t={sessionId:this.sessionId,data:e,timestamp:new Date};this.emit(`output`,t)}},mt=class extends Qe{isDocker=!1;sessions=new Map;constructor(e,t,n,r,i){super(e,t,n,r),this.pwd=i}async initialize(){this.logger.debug(`Native session manager initialized`)}async createSessionInner(e){let t=new pt(e,this.pwd||process.cwd(),this.logger);return this.registerSessionEvents(t),this.sessions.set(e.sessionId,t),t}async terminateSession(e,t=!1){let n=this.sessions.get(e);if(!n){this.logger.warn(`Session ${e} not found`);return}await n.terminate(t),this.logger.info(`[${e}] Native session terminated`)}async cleanupInner(){this.logger.info(`Cleaning up all native sessions...`);let e=Array.from(this.sessions.values()).map(e=>e.terminate(!0).catch(t=>this.logger.error(`[${e.sessionId}] Failed to terminate session: ${t instanceof Error?t.message:`Unknown error`}`)));await Promise.allSettled(e),this.sessions.clear()}hasSession(e){return this.sessions.has(e)}getSession(e){return this.sessions.get(e)||null}registerSessionEvents(e){e.on(`output`,e=>{this.emit(`output`,e)}),e.on(`error`,e=>{this.emit(`error`,e)}),e.on(`exit`,e=>{this.sessions.delete(e.sessionId),this.emit(`exit`,e)}),e.on(`terminated`,e=>{this.sessions.delete(e.sessionId);let t={sessionId:e.sessionId,issueId:e.issueId,exitCode:e.exitCode};this.emit(`terminated`,t)})}},ht=class e{auth;logger;userId;workspaceId;token;machineId;machineName;client;static async create(t){let n=new G(t.logger).getAuthInfo();n||(t.logger.error(`Not logged in. Please run "lody login" to log in.`),process.exit(1));let r=n.workspace.id,i=n.token;return new e(t,await je.create(r,n.user.id,i),n)}constructor(e,t,n){this.options=e,this.documentManager=t,this.auth=new G(e.logger),this.logger=e.logger,this.userId=n.user.id,this.workspaceId=n.workspace.id,this.token=n.token,this.machineId=n.machine.machineId,this.machineName=n.machine.machineName,this.client=new Xe({managerOptions:{workspaceId:this.workspaceId,token:this.token,logger:this.logger},sessionManagerFactory:n=>e.docker?new ft(this.logger,this.token,this.machineId,t):new mt(this.logger,this.token,this.machineId,t,process.cwd()),workspaceDocument:t,handlerConfig:{token:this.token,workspaceId:this.workspaceId,userId:this.userId,machineId:this.machineId,machineName:this.machineName,enableDocker:e.docker||!1,cliVersion:D},logger:this.logger})}async daemon(){await this.client.initialize(),this.documentManager.registerMachine(this.machineId,{id:this.machineId,name:this.machineName,cliVersion:D,os:process.platform,isDocker:this.options.docker,sessions:[]})}async registerAgent(e){await this.client.initialize();let t=await this.documentManager.getOrCreateWorkspaceDoc(this.workspaceId,this.userId);for(let n of e)await t.hasAgentConfig(n,this.machineId)||await t.createAgentConfig(n,this.machineId)}async startNewAISession(e,t){let{sessionManager:n}=await this.client.initialize(),r=await n.createSession({workspaceId:this.workspaceId,userId:this.userId,machineId:this.machineId,cliType:e,title:t.slice(0,50),env:process.env});return await r.agentClient?.connection?.prompt({prompt:[{type:`text`,text:t}],sessionId:r.acpSessionId}),r}async resumeAISession(e){}cleanup=async()=>await this.client.cleanup()};const Y=async(e,t,n)=>{let r=k({level:n?`debug`:`info`,transports:`console`,console:{colorize:!0,timestamp:!1,format:`simple`}}),i=new G(r).getAuthInfo();i||(r.warn("You have not logged in, `lody login` first"),process.exit(0));let a=await je.create(i.workspace.id,i.user.id,i.token);await(await new ht({logger:r,docker:!1},a,i).startNewAISession(e,t)).terminate(),process.exit(0)},X=new c.Command(`start`).description(`Start agent service with or without Docker isolation`).option(`-f, --foreground`,`run in foreground (default is background)`,!1).option(`-d, --docker`,`Use Docker without prompting`).option(`-n, --native`,`Force native mode without Docker (skip prompt)`).option(`--cli-types <types...>`,"Specify CLI types to register, `claude` or `codex`",(e,t)=>t?t.concat(e):[e]).option(`-v, --verbose`,`verbose output`).action(async e=>{let t={transports:`console`,console:{colorize:!0,timestamp:!1,format:`simple`}};e.foreground||(t.transports=`both`,t.file={filename:`logs/start.log`}),e.verbose&&(t.level=`debug`);let n=k(t),r=new G(n),i,a=r.getAuthInfo();a?(n.info(`Found existing authentication, checking validity...`),(await r.validateToken(a.token)).valid?i=a.token:(n.warn(`Existing authentication is invalid, please login again`),process.exit(1))):(n.warn(`No existing authentication found, please login again`),process.exit(1));try{e.server=process.env.LODY_SERVER_URL||`http://localhost:8787`;let t=e.docker??!1,r=process.env.LODY_AGENT_BACKGROUND===`1`;if(r||(e.docker===!0?(n.info(`Docker mode: enabled (via command line)`),t=!0):e.native===!0?(n.info(`Docker mode: disabled (via command line)`),t=!1):(n.info(l.default.cyan(`
18
+ 🐳 Docker Configuration
19
+ `)),n.info(`Docker provides the following benefits:`),n.info(` • `+l.default.green(`Environment isolation`)+` - Protects your system from code execution`),n.info(` • `+l.default.green(`Parallel tasks`)+` - Run multiple AI agents simultaneously`),n.info(` • `+l.default.green(`Clean workspace`)+` - Each session runs in a fresh environment
20
+ `),t=(await d.default.prompt([{type:`confirm`,name:`useDocker`,message:`Would you like to use Docker for enhanced isolation and parallel execution?`,default:!0}])).useDocker)),t){let e=await be();e.installed||(n.error(`Docker is not installed on your system.`),n.warn(l.default.yellow(`
21
+ `+xe())),process.exit(1)),e.running||(n.error(e.error||`Docker is not running.`),n.warn(l.default.yellow(`
22
+ Please start Docker and try again.`)),process.exit(1)),n.info(`Docker detected: v${e.version}`)}e.cliTypes=await(async()=>{if(e.cliTypes&&e.cliTypes.length>0){if(!t){let t={claude:B(),codex:Se()},r=e.cliTypes.filter(e=>!t[e]);r.length>0&&(n.error(`CLI types not installed locally: ${r.join(`, `)}. Install them or use Docker mode.`),process.exit(1))}return e.cliTypes}r&&(n.error(`Background process missing CLI types. Please run start command again.`),process.exit(1));let i=B(),a=Se();return!t&&!i&&!a&&(n.error(`No supported CLI types found. Please install Claude or Codex CLI tools first.`),process.exit(1)),(await d.default.prompt([{type:`checkbox`,name:`cliTypes`,message:`Select the CLI types you want to use`,choices:[{name:i&&!t?`Claude ${l.default.gray(i)}`:`Claude`,value:`claude`,disabled:!t&&!i?`not installed`:!1,checked:!!i},{name:a&&!t?`Codex ${l.default.gray(a)}`:`Codex`,value:`codex`,disabled:!t&&!a?`not installed`:!1,checked:!!a}],validate:e=>e.length<1?`You must choose at least one CLI type.`:!0}])).cliTypes})();let o=await gt({...e,docker:t,cliTypes:e.cliTypes},i,a.machine.machineId,n);o.success?(n.success(o.message||`Agent service started successfully!`),e.foreground||process.exit(0)):(n.error(o.message||`Failed to start agent service`),process.exit(1))}catch(e){n.error(`Service error: ${e instanceof Error?e.message:`Unknown error`}`),process.exit(1)}});X.command(`status`).description(`Check agent service status`).option(`-v, --verbose`,`verbose output with session details`).action(async e=>{let t=k();try{let n=await vt(t,e.verbose);n.success?t.success(n.message||`Status check completed`):(t.error(n.message||`Status check failed`),process.exit(1))}catch(e){t.error(`Status check error: ${e instanceof Error?e.message:`Unknown error`}`),process.exit(1)}}),X.command(`stop`).description(`Stop agent service`).action(async()=>{let e=k();try{let t=await yt(e);t.success?e.success(t.message||`Service stopped successfully`):(e.error(t.message||`Failed to stop service`),process.exit(1))}catch(t){e.error(`Stop service error: ${t instanceof Error?t.message:`Unknown error`}`),process.exit(1)}});async function gt(e,t,n,r){let i=L();if(i.running)return{success:!1,message:`Service is already running (PID: ${i.info?.pid})`};let a=e.server;if(e.foreground)return await _t(e,t,n,r);{let n=process.stdout.isTTY,i=n?(0,u.default)({text:`Starting agent service in background...`,stream:process.stdout}).start():null;n||r.info(`Starting agent service in background...`);try{let n=process.argv[1],o=[`start`,`--foreground`,`--verbose`];if(e.docker&&o.push(`--docker`),e.native&&!e.docker&&o.push(`--native`),e.cliTypes&&e.cliTypes.length>0)for(let t of e.cliTypes)o.push(`--cli-types`,t);r.debug(`Starting background process: ${process.execPath} ${n} ${o.join(` `)}`);let{child:s}=ye(n,o,r);return r.debug(`Background process started with PID: ${s.pid}`),await new Promise(e=>setTimeout(e,2e3)),s.pid&&!F(s.pid)?(i?i.fail():r.error(`Background process exited unexpectedly`),{success:!1,message:`Background process exited unexpectedly. Check logs for details.`}):(N(s.pid,{token:t,startTime:new Date().toISOString(),dockerMode:e.docker}),i?i.succeed(`Agent service started in background ${e.docker?`(Docker mode)`:`(Native mode)`}`):r.success(`Agent service started successfully (PID: ${s.pid})`),{success:!0,message:`Service started (PID: ${s.pid})`,data:{pid:s.pid,serverUrl:a,dockerMode:e.docker}})}catch(e){throw i?i.fail():r.error(`Failed to start background service: ${e instanceof Error?e.message:`Unknown error`}`),e}}}async function _t(e,t,n,r){r.info(`Starting agent service ${e.docker?`(Docker mode)`:`(Native mode)`}...`);let i=await ht.create({logger:r,docker:e.docker});try{await i.daemon(),r.debug(`Registering agents: ${e.cliTypes}`),await i.registerAgent(e.cliTypes||[]),r.debug(`Connected to loro server`);let n=!1,a=async()=>{if(n){r.info(`Shutdown already in progress...`);return}n=!0,r.info(`
23
+ Shutting down gracefully...`);try{await i.cleanup()}catch(e){r.error(`Shutdown error: ${e instanceof Error?e.message:`Unknown error`}`)}finally{process.exit(0)}};return process.removeAllListeners(`SIGINT`),process.removeAllListeners(`SIGTERM`),process.on(`SIGINT`,a),process.on(`SIGTERM`,a),he({token:t,dockerMode:e.docker}),r.info(`Ready to execute commands ${e.docker?`in isolated containers`:`on this machine`}`),r.info(`Press ${l.default.yellow(`Ctrl+C`)} to stop`),await new Promise(()=>{}),{success:!0,message:`Agent service started`,data:{dockerMode:e.docker}}}catch(e){throw await i.cleanup().catch(e=>{r.error(`Cleanup failed: ${e instanceof Error?e.message:`Unknown error`}`)}),e}}async function vt(e,t){let n=L();if(!n.running)return e.info(`Agent service is not running`),{success:!0,message:`Service is not running`};let r=n.info;return e.info(`Service status: ${l.default.green(`RUNNING`)}`),e.info(`PID: ${l.default.cyan(r.pid)}`),e.info(`Started: ${l.default.cyan(r.startTime)}`),r.dockerMode!==void 0&&e.info(`Mode: ${l.default.cyan(r.dockerMode?`Docker`:`Native`)}`),t&&r.dockerMode&&e.info(`
24
+ Use --verbose with Docker mode to see container details (not implemented yet)`),{success:!0,message:`Service is running`,data:r}}async function yt(e){let t=P();if(!t)return{success:!0,message:`No service to stop`};try{return _e(t)?(await new Promise(e=>setTimeout(e,3e3)),I(),{success:!0,message:`Service stopped (PID: ${t})`}):{success:!1,message:`Failed to stop service (PID: ${t})`}}catch(e){throw e}}const bt=new c.Command(`login`).description(`Login to Lody using device authorization flow`).option(`-v, --verbose`,`verbose output`).action(async e=>{let t=k({level:e.verbose?`debug`:`info`,transports:`console`,console:{colorize:!0,timestamp:!1,format:`simple`}}),n=new G(t);try{let e=n.getAuthInfo();if(e){t.info(`Found existing authentication, checking validity...`);let r=await n.validateToken(e.token);if(r.valid&&r.user){t.success(`
25
+ `+l.default.green(`✓`)+` You are already logged in!`),t.info(` User: `+l.default.cyan(r.user.name||r.user.email)),t.info(` Email: `+l.default.cyan(r.user.email)),t.info(` Workspace: `+l.default.cyan(e.workspace.name));return}else t.info(`Existing authentication is invalid, proceeding with new login...`)}t.info(`Initializing device authorization...`);let r=await n.login(g.default.hostname());if(r.success){t.success(`
26
+ `+l.default.green(`✓`)+` Successfully logged in!`),t.info(` User: `+l.default.cyan(r.user.name||r.user.email)),t.info(` Email: `+l.default.cyan(r.user.email)),t.info(` Workspace: `+l.default.cyan(r.workspace.name));let e=(await d.default.prompt([{type:`input`,name:`machineName`,message:`Enter a name for this machine:`,default:g.default.hostname(),validate:e=>e.trim().length===0?`Machine name cannot be empty`:!0}])).machineName;t.info(` Machine Name: `+l.default.cyan(e)),De(r.token,r.user,r.workspace,{machineName:e,machineId:r.machine.machineId})}else t.error(`Login failed: ${r.error}`),process.exit(1)}catch(e){t.error(`Login failed: ${e instanceof Error?e.stack:`Unknown error`}`),process.exit(1)}}),xt=new c.Command(`logout`).description(`Logout from Lody and clear saved authentication`).option(`-v, --verbose`,`verbose output`).action(async e=>{let t=k({level:e.verbose?`debug`:`info`,transports:`console`,console:{colorize:!0,timestamp:!1,format:`simple`}}),n=new G(t).logout();if(await yt(t),n.success||(t.error(`Logout failed: ${n.error}`),process.exit(1)),!n.user){t.info(`You are not logged in.`);return}t.info(`Logging out user: ${l.default.cyan(n.user.email)}`),t.success(`
27
+ `+l.default.green(`✓`)+` Successfully logged out!`)}),Z=(e,t=[])=>(t.push(e),t),St=e=>(e.argument(`[prompt]`,`Initial instructions for the agent. Use "-" or omit to read from stdin`).passThroughOptions().option(`-c, --config <key=value>`,`Override a configuration value loaded from ~/.codex/config.toml`,Z,[]).option(`-i, --image <file>`,`Attach image(s) to the initial prompt`,Z,[]).option(`-m, --model <model>`,`Model the agent should use`).option(`--oss`,`Use the OSS runtime`).option(`-s, --sandbox <sandbox_mode>`,`Sandbox policy to use when executing shell commands`,e=>{if(![`read-only`,`workspace-write`,`danger-full-access`].includes(e))throw new c.InvalidArgumentError(`Sandbox mode must be one of read-only, workspace-write, danger-full-access`);return e}).option(`-p, --profile <config_profile>`,`Configuration profile from config.toml`).option(`--full-auto`,`Enable low-friction sandboxed automatic execution`).option(`--dangerously-bypass-approvals-and-sandbox`,`Skip all confirmation prompts and execute commands without sandboxing`).option(`-C, --cd <dir>`,`Change to the specified directory before running Codex`).option(`--skip-git-repo-check`,`Allow running Codex outside a Git repository`).option(`--output-schema <file>`,`Path to a JSON Schema file describing the model's final response shape`).option(`--color <color>`,`Color setting for output`,e=>{if(![`always`,`never`,`auto`].includes(e))throw new c.InvalidArgumentError(`Color must be one of always, never, auto`);return e}).option(`--include-plan-tool`,`Include the plan tool in the conversation`).option(`-o, --output-last-message <file>`,`File where the last message from the agent should be written`).action(async(e,t,n)=>{await Y(`codex`,e)}),e),Ct=e=>(e.argument(`[session_id]`,`Conversation or session id. Combine with --last to resume the most recent session when omitted`).argument(`[prompt]`,`Prompt to send after resuming the session. Use "-" to read from stdin`).option(`-c, --config <key=value>`,`Override configuration values loaded from ~/.codex/config.toml`,Z,[]).option(`--last`,`Resume the most recent recorded session without specifying an id`).action(async(e,t,n)=>{await Y(`codex`,t)}),e),wt=St(new c.Command(`codex`).description(`Start a codex session`).enablePositionalOptions());Ct(wt.command(`resume`).description(`Resume a Codex session`));const Tt=(e,t=[])=>(e.split(`,`).map(e=>e.trim()).filter(Boolean).forEach(e=>{t.push(e)}),t),Et=(e,t=[])=>(t.push(e),t),Q=(e,t,n)=>{if(!t.includes(e))throw new c.InvalidArgumentError(`${n} must be one of ${t.map(e=>`"${e}"`).join(`, `)}`);return e},Dt=new c.Command(`claude`).description(`Start a claude session`).argument(`[prompt]`,`Your prompt`).option(`-d, --debug`,`Enable debug mode`).option(`--verbose`,`Override verbose mode setting from config`).option(`-p, --print`,`Print response and exit (useful for pipes)`).option(`--output-format <format>`,`Output format (requires --print)`,e=>Q(e,[`text`,`json`,`stream-json`],`Output format`)).option(`--input-format <format>`,`Input format (requires --print)`,e=>Q(e,[`text`,`stream-json`],`Input format`)).option(`--mcp-debug`,`[DEPRECATED] Enable MCP debug mode`).option(`--dangerously-skip-permissions`,`Bypass all permission checks. Recommended only for isolated sandboxes`).option(`--allowedTools <tools...>`,`Comma or space-separated list of tool names to allow`,Tt,[]).option(`--disallowedTools <tools...>`,`Comma or space-separated list of tool names to deny`,Tt,[]).option(`--mcp-config <configs...>`,`Load MCP servers from JSON files or strings (space-separated)`,Et,[]).option(`--append-system-prompt <prompt>`,`Append a system prompt to the default system prompt`).option(`--permission-mode <mode>`,`Permission mode to use for the session`,e=>Q(e,[`acceptEdits`,`bypassPermissions`,`default`,`plan`],`Permission mode`)).option(`-c, --continue`,`Continue the most recent conversation`).option(`-r, --resume [sessionId]`,`Resume a conversation by id or select interactively when omitted`).option(`--model <model>`,`Model for the current session (alias like 'sonnet' or fully-qualified name)`).option(`--fallback-model <model>`,`Enable automatic fallback to specified model when default model is overloaded`).option(`--settings <file-or-json>`,`Load additional settings from a JSON file or string`).option(`--add-dir <directories...>`,`Additional directories to allow tool access to`,Et,[]).option(`--strict-mcp-config`,`Only use MCP servers from --mcp-config, ignoring all other MCP configurations`).option(`--session-id <uuid>`,`Use a specific session ID for the conversation`).action(async function(e){this.optsWithGlobals(),await Y(`claude`,e)});function Ot(){let e=process.env.NODE_ENV||`development`,t=process.cwd(),n;n=e===`production`?`.env`:`.env.dev`,(0,se.config)({path:m.join(t,n)}),Ce()}Ot();const $=new c.Command;$.name(`lody`).description(`Lody Agent CLI tool for managing remote command execution`).version(D,`-v, --version`,`display version number`).enablePositionalOptions(),$.addCommand(bt),$.addCommand(xt),$.addCommand(X),$.addCommand(wt),$.addCommand(Dt),$.configureHelp({sortSubcommands:!0,subcommandTerm:e=>e.name()+` `+e.usage()}),process.on(`uncaughtException`,e=>{console.error(l.default.red(`Uncaught Exception:`),e.message),process.exit(1)}),process.on(`unhandledRejection`,(e,t)=>{console.error(l.default.red(`Unhandled Rejection at:`),t,`reason:`,e),process.exit(1)});try{$.parse(process.argv),process.argv.slice(2).length||($.outputHelp(),process.exit(0))}catch(e){e instanceof Error&&e.name===`CommanderError`||console.error(l.default.red(`CLI Error:`),e instanceof Error?e.message:`Unknown error`),process.exit(1)}
package/dist/index.mjs ADDED
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env node
2
+ import{createRequire as e}from"node:module";import{Command as t,InvalidArgumentError as n}from"commander";import r from"chalk";import i from"ora";import a from"inquirer";import o from"winston";import s from"winston-daily-rotate-file";import*as c from"path";import l from"path";import*as u from"fs";import d from"fs";import*as ee from"os";import f from"os";import{exec as p,execSync as m,spawn as h}from"child_process";import{promisify as te}from"util";import{ConvexHttpClient as ne}from"convex/browser";import{api as g}from"@lody/convex";import{v4 as _}from"uuid";import{Mirror as v}from"loro-mirror";import{getAgentConfigRoomId as re,getIssueRoomId as ie,getLoroWsUrl as ae,getMachineRoomId as oe,getSessionRoomId as se,getWorkspaceRoomId as ce,issueDocSchema as le,sessionDocSchema as ue,workspaceDocSchema as de}from"@lody/shared";import y from"ws";import{LoroRepo as fe}from"loro-repo";import{WebSocketTransportAdapter as pe}from"loro-repo/transport/websocket";import{EventEmitter as me}from"events";import{throttle as he}from"throttle-debounce";import ge from"dockerode";import _e,{EventEmitter as ve}from"eventemitter3";import{PassThrough as ye,Transform as be,Writable as xe}from"stream";import*as b from"@agentclientprotocol/sdk";import{ndJsonStream as x}from"@agentclientprotocol/sdk";import{randomUUID as S}from"crypto";import{config as Se}from"dotenv";var C=e(import.meta.url),w=`0.2.2`;const T=(e={})=>{let{colorize:t=!0,timestamp:n=!0,format:i=`simple`}=e;return o.format.combine(o.format.errors({stack:!0}),o.format.timestamp({format:`YYYY-MM-DD HH:mm:ss`}),o.format.printf(e=>{let{level:a,message:o,timestamp:s,stack:c,...l}=e,u=``;if(i===`json`)return JSON.stringify({level:a,message:o,timestamp:s,...l});if(n&&s){let e=t?r.gray(`[${s}]`):`[${s}]`;u+=`${e} `}if(process.env.NODE_ENV===`development`){let e=a.toUpperCase().padEnd(7);if(t){let t={ERROR:r.red,WARN:r.yellow,INFO:r.blue,SUCCESS:r.green,DEBUG:r.gray}[a.toUpperCase()]||r.white;u+=`${t(e)} `}else u+=`${e} `}if(u+=o,Object.keys(l).length>0){let e=JSON.stringify(l);u+=t?r.gray(` ${e}`):` ${e}`}return c&&(u+=`\n${c}`),u}))},Ce=e=>{let t=e.console||{};return new o.transports.Console({level:e.level===`silent`?`error`:e.level||`info`,format:T({colorize:t.colorize!==!1,timestamp:t.timestamp!==!1,format:t.format||`simple`})})},we=e=>{let t=e.file||{};return new s({level:e.level,filename:t.filename||`%DATE%.log`,dirname:t.dirname||l.join(process.cwd(),`logs`),datePattern:t.datePattern||`YYYY-MM-DD`,maxSize:t.maxSize||`20m`,maxFiles:t.maxFiles||14,zippedArchive:t.zippedArchive||!1,format:T({colorize:!1,timestamp:!0,format:`detailed`})})};var Te=class{winston;config;constructor(e={}){this.config={...e},this.winston=this.createWinstonLogger()}createWinstonLogger(){let e=[],t=this.config.transports||`console`;return(t===`console`||t===`both`)&&e.push(Ce(this.config)),(t===`file`||t===`both`)&&e.push(we(this.config)),o.createLogger({level:this.config.level,transports:e,exitOnError:!1})}info(e,t){this.winston.info(e,t)}warn(e,t){this.winston.warn(e,t)}error(e,t){this.winston.error(e,t)}success(e,t){this.winston.levels.success===void 0?this.winston.info(e,{...t,level:`success`}):this.winston.success(e,t)}debug(e,t){this.winston.debug(e,t)}setLevel(e){this.config.level=e,this.winston.level=e===`silent`?`error`:e,this.winston.transports.forEach(t=>{t.level=e===`silent`?`error`:e,t instanceof o.transports.Console&&(t.silent=e===`silent`)})}child(e){let t=this.winston.child(e),n=Object.create(this);return n.winston=t,n}async close(){return new Promise(e=>{this.winston.close(),e()})}};let E=null;const D=(e={})=>new Te(e),O=()=>(E||=D({transports:`console`,level:`info`,console:{colorize:!0,timestamp:!1,format:`simple`}}),E);O();const k=l.join(f.homedir(),`.lody-agent`),A=l.join(k,`serve.pid`),j=l.join(k,`serve.json`);function Ee(){d.existsSync(k)||d.mkdirSync(k,{recursive:!0})}function M(e,t){Ee(),d.writeFileSync(A,e.toString());let n={pid:e,...t};d.writeFileSync(j,JSON.stringify(n,null,2))}function De(e){M(process.pid,{...e,startTime:new Date().toISOString()})}function N(){try{if(!d.existsSync(A))return null;let e=parseInt(d.readFileSync(A,`utf-8`).trim(),10);return isNaN(e)?null:e}catch{return null}}function Oe(){try{return d.existsSync(j)?JSON.parse(d.readFileSync(j,`utf-8`)):null}catch{return null}}function P(e){try{return process.kill(e,0),!0}catch{return!1}}function ke(e){try{return process.kill(e,`SIGTERM`),setTimeout(()=>{if(P(e))try{process.kill(e,`SIGKILL`)}catch{}},2e3),!0}catch{return!1}}function F(){try{d.existsSync(A)&&d.unlinkSync(A),d.existsSync(j)&&d.unlinkSync(j)}catch{}}function I(){let e=N(),t=Oe();return!e||!t?{running:!1,info:null}:P(e)?{running:!0,info:t}:(F(),{running:!1,info:null})}function Ae(){try{return C.resolve(`ts-node/dist/bin.js`)}catch{try{return C.resolve(`ts-node`)}catch{return null}}}function je(e,t,n){let r=e.endsWith(`.ts`),i,a;if(r){let r=Ae();if(!r)throw Error(`ts-node not found. Please install ts-node or run in production mode.`);i=process.execPath,a=[r,`--project`,l.join(l.dirname(e),`..`,`tsconfig.json`),e,...t],n?.info(`Development mode: Using ts-node to execute TypeScript`)}else i=process.execPath,a=[e,...t],n?.info(`Production mode: Using node to execute JavaScript`);let o=r?l.dirname(l.dirname(e)):process.cwd();n?.info(`Starting background service: ${i} ${a.join(` `)}`),n?.info(`Working directory: ${o}`);let s=h(i,a,{detached:!0,stdio:[`ignore`,`pipe`,`pipe`],cwd:o,env:{...process.env,LODY_AGENT_BACKGROUND:`1`}});return s.stdout?.on(`data`,e=>{let t=e.toString();n?.info(`STDOUT`,{output:t.trim()})}),s.stderr?.on(`data`,e=>{let t=e.toString();n?.error(`STDERR`,{error:t.trim()})}),s.on(`error`,e=>{n?.error(`Process error`,{error:e.message,stack:e.stack}),n?.error(`Failed to start background service:`,e)}),s.on(`close`,(e,t)=>{n?.info(`Process closed`,{exitCode:e,signal:t||`none`}),n?.info(`=== Agent Service Stopped ===`)}),s.unref(),{child:s}}const L=te(p);async function Me(){try{await L(`docker ps -q`);let{stdout:e}=await L(`docker version --format "{{.Server.Version}}"`),t=e.trim();return t?{installed:!0,running:!0,version:t}:{installed:!0,running:!1,error:`Docker is installed but not running`}}catch(e){let t=e instanceof Error?e.message.toLowerCase():``;if(t.includes(`permission denied`)||t.includes(`got permission denied`)||t.includes(`connect: permission denied`)||t.includes(`dial unix /var/run/docker.sock`))try{let{stdout:e}=await L(`groups`);return e.includes(`docker`)?{installed:!0,running:!1,error:`Permission denied accessing Docker socket.
3
+ Try running: sudo chmod 666 /var/run/docker.sock
4
+ Or restart the Docker service: sudo systemctl restart docker`}:{installed:!0,running:!1,error:`Permission denied. Your user is not in the docker group.
5
+ To fix this, run: sudo usermod -aG docker $USER
6
+ Then log out and log back in for the changes to take effect.`}}catch{return{installed:!0,running:!1,error:`Permission denied. Please ensure your user is in the docker group.
7
+ To fix this, run: sudo usermod -aG docker $USER
8
+ Then log out and log back in.`}}return t.includes(`cannot connect to the docker daemon`)||t.includes(`is the docker daemon running`)?{installed:!0,running:!1,error:`Docker is installed but the daemon is not running.
9
+ Start Docker with: sudo systemctl start docker`}:t.includes(`command not found`)||t.includes(`not found`)||t.includes(`docker: not found`)?{installed:!1,running:!1,error:`Docker is not installed`}:{installed:!1,running:!1,error:`Docker check failed: ${t}`}}}function Ne(){switch(process.platform){case`darwin`:return`Please install Docker Desktop for macOS from: https://docs.docker.com/desktop/install/mac-install/`;case`win32`:return`Please install Docker Desktop for Windows from: https://docs.docker.com/desktop/install/windows-install/`;case`linux`:return`Please install Docker using your package manager:
10
+ Ubuntu/Debian: sudo apt-get install docker.io
11
+ CentOS/RHEL: sudo yum install docker
12
+ Or visit: https://docs.docker.com/engine/install/`;default:return`Please visit https://docs.docker.com/get-docker/ for installation instructions.`}}function R(){let e=l.join(ee.homedir(),`.lody`);return l.join(e,`credentials.json`)}const z=()=>{try{let e=[`~/.claude/local`,process.env.PATH||``].join(l.delimiter);return m(`claude --version`,{env:{...process.env,PATH:e},encoding:`utf-8`}).trim().replace(`(Claude Code)`,``)}catch{return!1}},B=()=>{try{return m(`codex --version`,{env:process.env,encoding:`utf-8`}).trim().replace(`codex-cli `,``)}catch{return!1}};let V=process.env.LODY_AUTH_URL,H=process.env.LODY_SERVER_URL,U=H?.replace(/^http/,`ws`),W=`/workspace`;const Pe=()=>{V=process.env.LODY_AUTH_URL,H=process.env.LODY_SERVER_URL,U=H?.replace(/^http/,`ws`)};function Fe(){try{if(process.platform===`linux`){if(d.existsSync(`/etc/machine-id`))return d.readFileSync(`/etc/machine-id`,`utf-8`).trim();if(d.existsSync(`/var/lib/dbus/machine-id`))return d.readFileSync(`/var/lib/dbus/machine-id`,`utf-8`).trim()}else if(process.platform===`darwin`)try{return m(`ioreg -rd1 -c IOPlatformExpertDevice | grep IOPlatformUUID`,{encoding:`utf-8`}).match(/"IOPlatformUUID" = "(.+)"/)?.[1]||null}catch{return null}else if(process.platform===`win32`)try{return m(`wmic csproduct get UUID`,{encoding:`utf-8`}).split(`
13
+ `)[1]?.trim()||null}catch{return null}}catch{}return null}function Ie(e){let t=`"${e.replace(/"/g,`\\"`)}"`;switch(process.platform){case`darwin`:return`open ${t}`;case`win32`:return`cmd /c start "" ${t.replace(/&/g,`^&`)}`;default:return`xdg-open ${t}`}}async function Le(e){let t=Ie(e);await new Promise((e,n)=>{p(t,{windowsHide:!0},t=>{t?n(t):e()}).on(`error`,n)})}var G=class{serverUrl;client;constructor(e){if(this.logger=e,!V)throw Error(`LODY_AUTH_URL is not defined`);this.serverUrl=V,this.client=new ne(this.serverUrl)}getAuthInfo(){return ze()}async login(e){let t=Fe()||_(),n=await this.client.mutation(g.deviceAuth.initializeDeviceAuth);this.logger.info(`
14
+ `+r.yellow(`=`.repeat(50))),this.logger.info(r.bold(`Device Authorization`)),this.logger.info(r.yellow(`=`.repeat(50))),this.logger.info(`
15
+ Please visit: `+r.cyan(n.verification_uri_complete));try{this.logger.info(`Attempting to open the URL in your default browser...`),await Le(n.verification_uri_complete)}catch(e){this.logger.warn(`Could not open the browser automatically: ${e instanceof Error?e.message:`Unknown error`}`),this.logger.info(`Please open the URL manually if it did not open automatically.`)}this.logger.info(r.yellow(`=`.repeat(50))+`
16
+ `);let a=i(`Waiting for authorization...`).start(),o=!1,s=``,c=null,l={id:``,name:``},u=Date.now()+n.expires_in*1e3;for(;!o&&Date.now()<u;){await new Promise(e=>setTimeout(e,n.interval*1e3));try{let e=await this.client.query(g.deviceAuth.checkDeviceAuthorization,{deviceCode:n.device_code});switch(e.status){case`authorized`:o=!0,s=e.access_token,c=e.user,l=e.workspace,a.succeed(`Authorization successful!`);break;case`needs_token_generation`:await this.client.mutation(g.deviceAuth.generateCliToken,{deviceCode:n.device_code,userId:e.userId});let t=await this.client.query(g.deviceAuth.checkDeviceAuthorization,{deviceCode:n.device_code});t.status===`authorized`&&(o=!0,s=t.access_token,c=t.user,l=t.workspace,a.succeed(`Authorization successful!`));break;case`denied`:return a.fail(`Authorization was denied`),{success:!1,error:`Authorization denied`};case`expired`:return a.fail(`Authorization code expired`),{success:!1,error:`Authorization code expired`};case`error`:return a.fail(`Error: ${e.error}`),{success:!1,error:e.error||`Unknown error`};case`pending`:break}}catch{return{success:!1,error:`Network error or server error when logging in`}}}if(!o)return a.fail(`Authorization timed out`),{success:!1,error:`Authorization timed out`};let d={token:s,user:c,workspace:{id:l.id,name:l.name},machine:{machineName:e,machineId:t}};return await Re(s,d.user,d.workspace,d.machine),{success:!0,...d}}logout(){let e=ze();if(!e)return this.logger.debug(`No authentication found. You are not logged in.`),{success:!0,user:void 0};let t=R();return u.unlinkSync(t),{success:!0,user:e.user}}async validateToken(e){return await Be(e,this.client)}};async function Re(e,t,n,r){let i=R(),a=l.dirname(i);u.mkdirSync(a,{recursive:!0});let o={token:e,user:t,workspace:n,machine:r};u.writeFileSync(i,JSON.stringify(o,null,2))}function ze(){try{let e=R(),t=u.readFileSync(e,`utf-8`);return JSON.parse(t)}catch{return null}}async function Be(e,t){try{let n=await t.query(g.deviceAuth.validateCliToken,{token:e});return n.valid&&n.user?{valid:!0,user:n.user,workspace:n.workspace}:{valid:!1}}catch{return{valid:!1}}}globalThis.WebSocket=y;const Ve=O();var He=class e{issues=new Map;sessions=new Map;workspaces=new Map;agents=new Map;machine=null;static async create(t,n,r){let i=await fe.create({transportAdapter:new pe({url:ae(t,process.env.LODY_SERVER_URL||`http://localhost:8787`),metadataAdaptorConfig:{onImportError(e,t){console.error(`Import error in LoroRepo metadata adapter:`,e,t)},onUpdateError(e){console.error(`Update error in LoroRepo metadata adapter:`,e)}},docAuth:void 0})}),a=await i.joinMetaRoom({roomId:`repo:meta`});return await a.firstSyncedWithRemote,new e(i,t,n,r,a)}constructor(e,t,n,r,i){this.repo=e,this.workspaceId=t,this.userId=n,this.token=r,this.metaSub=i,Ve.debug(`[${this.workspaceId}] LoroWebsocketClient url: ${U+`/ws/${this.workspaceId}`}`)}async getOrCreateAgentDoc(e){if(!this.agents.has(e)){let t=new Ke(this.repo,e);await t.init(),this.agents.set(e,t)}return this.agents.get(e)}async getOrCreateIssueDoc(e){if(!this.issues.has(e)){let t=new Ue(this.repo,e);await t.init(),this.issues.set(e,t)}return this.issues.get(e)}async getOrCreateSessionDoc(e){if(!this.sessions.has(e)){let t=new We(this.repo,e);await t.init(),this.sessions.set(e,t)}return this.sessions.get(e)}async getOrCreateWorkspaceDoc(e,t){if(!this.workspaces.has(e)){let n=new Ge(this.repo,t,e,this);await n.init(),this.workspaces.set(e,n)}return this.workspaces.get(e)}async registerMachine(e,t){this.machine||(this.machine=new qe(this.repo,e),await this.machine.init()),await(await this.getOrCreateWorkspaceDoc(this.workspaceId,this.userId)).registerMachine(e),await this.machine.setMetaState(t)}async removeMachineSession(e){let t=await this.machine?.getMetaState();t&&this.machine?.setMetaState({...t,sessions:t.sessions?.filter(t=>t.sessionId!==e)||[]})}async updateMachineSession(e,t){let n=await this.machine?.getMetaState();n&&this.machine?.setMetaState({...n,sessions:n.sessions?.map(n=>n.sessionId===e?{...n,status:{...n.status,...t}}:n)||[]})}async addMachineSession(e){let t=await this.machine?.getMetaState();t&&this.machine?.setMetaState({...t,sessions:[...t.sessions,e]})}async cleanUp(){for(let e of this.sessions.values())await e.destroy();for(let e of this.issues.values())await e.destroy();for(let e of this.workspaces.values())await e.destroy();for(let e of this.agents.values())await e.destroy();this.issues.clear(),this.workspaces.clear(),this.sessions.clear(),this.agents.clear(),this.metaSub.unsubscribe(),await this.repo.destroy()}async cleanSessionDoc(e){let t=this.sessions.get(e);t&&(await t.destroy(),this.sessions.delete(e))}async cleanIssueDoc(e){let t=this.issues.get(e);t&&(await t.destroy(),this.issues.delete(e))}async cleanWorkspaceDoc(e){let t=this.workspaces.get(e);t&&(await t.destroy(),this.workspaces.delete(e))}async cleanAgentDoc(e){let t=this.agents.get(e);t&&(await t.destroy(),this.agents.delete(e))}},Ue=class{mirror=null;handle=null;docSub=null;roomId;constructor(e,t){this.repo=e,this.issueId=t,this.roomId=ie(this.issueId)}async init(){this.handle=await this.repo.openPersistedDoc(this.roomId),this.docSub=await this.repo.joinDocRoom(this.roomId),await this.docSub.firstSyncedWithRemote,this.mirror=new v({doc:this.handle.doc,schema:le})}async getMetaState(){return await this.repo.getDocMeta(this.roomId)}async getDocState(){if(!this.mirror)throw Error(`IssueDocument not initialized`);return this.mirror.getState().issue}async setStatus(e){if(!this.mirror)throw Error(`Mirror not initialized`);(await this.repo.getDocMeta(this.roomId))?.status!==e&&(await this.repo.upsertDocMeta(this.roomId,{status:e}),this.mirror.setState(t=>{t.issue.actionHistory.push({type:`update`,target:`status`,value:JSON.stringify(e),by:`lody-cli`,timestamp:new Date().toISOString()})}))}async destroy(){await this.repo.unloadDoc(this.roomId),this.docSub?.unsubscribe(),this.mirror?.dispose(),this.mirror=null,this.handle=null}},We=class{mirror=null;handle=null;docSub=null;roomId=``;constructor(e,t){this.repo=e,this.sessionId=t,this.roomId=se(this.sessionId)}async init(){this.handle=await this.repo.openPersistedDoc(this.roomId),this.docSub=await this.repo.joinDocRoom(this.roomId),await this.docSub.firstSyncedWithRemote,this.mirror=new v({doc:this.handle.doc,schema:ue,initialState:{session:{id:this.sessionId,history:[]}}})}async getDocState(){if(!this.mirror)throw Error(`SessionDocument not initialized`);return this.mirror.getState().session}async getMetaState(){return await this.repo.getDocMeta(this.roomId)}async markHistoryAsRead(e){if(!this.mirror)throw Error(`SessionDocument not initialized`);this.mirror.setState(t=>{let n=t.session.history??[];for(let t of n)if(t.$cid===e){t.read=!0;break}return t})}async setChatId(e){if(!this.mirror)throw Error(`SessionDocument not initialized`);Ve.debug(`[${this.sessionId}] setChatId ${e}`),await this.repo.upsertDocMeta(this.roomId,{chatId:e})}async setError(e){if(!this.mirror)throw Error(`SessionDocument not initialized`);await this.repo.upsertDocMeta(this.roomId,{error:e,status:`error`})}async getStatus(){if(!this.mirror)throw Error(`SessionDocument not initialized`);return(await this.repo.getDocMeta(this.roomId))?.status}async isChat(){return this.mirror?!!(await this.repo.getDocMeta(this.roomId))?.chatId:!1}async getIssueId(){if(this.mirror)return(await this.repo.getDocMeta(this.roomId))?.issueId}async setStatus(e){if(!this.mirror)throw Error(`Mirror not initialized`);await this.repo.upsertDocMeta(this.roomId,{status:e})}async getHistory(){return this.mirror&&this.mirror.getState().session.history||[]}async updateHistory(e){if(!this.mirror)throw Error(`Mirror not initialized`);this.mirror.setState(t=>{let n=e(t.session.history||[]);return t.session.history=n,t})}async destroy(){if(!this.mirror)return;let e=await this.getStatus();e===`running`||e===`requestPermission`?await this.setStatus(`error`):(e===`completed`||e===`terminated`)&&await this.setStatus(`terminated`),await this.repo.unloadDoc(this.roomId),this.docSub?.unsubscribe(),this.mirror?.dispose(),this.mirror=null,this.handle=null}},Ge=class{mirror=null;handle=null;roomId;docSub=null;constructor(e,t,n,r){this.repo=e,this.userId=t,this.workspaceId=n,this.manager=r,this.roomId=ce(this.workspaceId)}async init(){this.docSub=await this.repo.joinDocRoom(this.roomId),await this.docSub.firstSyncedWithRemote,this.handle=await this.repo.openPersistedDoc(this.roomId),this.mirror=new v({doc:this.handle.doc,schema:de,initialState:{issues:[],sessions:[],agentConfigs:[]}})}async createSession(e,t,n){if(!this.mirror)throw Error(`WorkspaceDocument not initialized`);let r=_(),i=await this.manager.getOrCreateSessionDoc(r);return await i.init(),i.mirror?.setState(i=>({session:{...i.session,id:r,machineId:e,cliType:t,userId:this.userId,status:`running`,history:[],title:n,createdAt:new Date().toISOString()}})),this.mirror.setState(e=>{e.sessions.push({id:r,isDeleted:!1})}),r}async registerMachine(e){let t=await this.getMetaState();await this.repo.upsertDocMeta(this.roomId,{...t,machineIds:[...t?.machineIds||[],e]})}async hasAgentConfig(e,t){if(!this.mirror)throw Error(`Mirror not initialized`);let n=this.mirror.getState().agentConfigs,r=!1;for(let t of n){if(t.isDeleted)continue;let n=await(await this.manager.getOrCreateAgentDoc(t.id)).getMetaState();if(await this.manager.cleanAgentDoc(t.id),r||=n?.cliType===e,r)break}return r}async createAgentConfig(e,t){if(!this.mirror)throw Error(`Mirror not initialized`);let n=_();return await(await this.manager.getOrCreateAgentDoc(n)).createAgentConfig({id:n,name:e,description:void 0,cliType:e,machineId:t,env:{}}),this.mirror.setState(e=>{e.agentConfigs.push({id:n,isDeleted:!1})}),await this.manager.cleanAgentDoc(n),n}async destroy(){await this.repo.unloadDoc(this.roomId),this.mirror?.dispose(),this.mirror=null,this.handle=null,this.docSub?.unsubscribe()}async getDocState(){if(!this.mirror)throw Error(`Mirror not initialized`);return this.mirror.getState()}async getMetaState(){return await this.repo.getDocMeta(this.roomId)}},Ke=class{roomId;handle=null;constructor(e,t){this.repo=e,this.agentId=t,this.roomId=re(this.agentId)}async init(){}async destroy(){}async createAgentConfig(e){await this.repo.upsertDocMeta(this.roomId,e)}async getDocState(){}async getMetaState(){return await this.repo.getDocMeta(this.roomId)}},qe=class{roomId;handle=null;constructor(e,t){this.repo=e,this.machineId=t,this.roomId=oe(this.machineId)}async setMetaState(e){await this.repo.upsertDocMeta(this.roomId,e)}async init(){}async destroy(){}async getMetaState(){return await this.repo.getDocMeta(this.roomId)}async getDocState(){}},Je=class extends me{ws=null;token;workspaceId;keepaliveTimer=null;isConnected=!1;logger;reconnectAttempts=0;reconnectTimer=null;shouldReconnect=!0;reconnectInitialDelay=1e3;reconnectMaxDelay=3e4;connectionPromise=null;abortConnectionAttempt=null;constructor(e){super(),this.token=e.token,this.logger=e.logger,this.workspaceId=e.workspaceId}connect(){return this.ws?.readyState===y.OPEN?Promise.resolve():this.connectionPromise?this.connectionPromise:(this.shouldReconnect=!0,this.clearReconnectTimer(),this.connectionPromise=new Promise((e,t)=>{let n=null,r=!1,i=()=>{n&&=(clearTimeout(n),null),this.ws&&(this.ws.removeListener(`open`,s),this.ws.removeListener(`error`,c)),this.connectionPromise=null,this.abortConnectionAttempt=null},a=()=>{r||(r=!0,i(),e())},o=e=>{r||(r=!0,i(),t(e))},s=()=>{this.reconnectAttempts=0,a()},c=e=>{o(e)};this.abortConnectionAttempt=e=>{o(e??Error(`Connection aborted`))};try{let e=new URL(`${U}/lody/${this.workspaceId}`);this.token&&e.searchParams.append(`token`,this.token),this.logger.debug(`Connecting to WebSocket: ${e}`),this.ws=new y(e.toString(),{perMessageDeflate:!1,handshakeTimeout:15e3}),this.setupEventHandlers(),this.ws.once(`open`,s),this.ws.once(`error`,c),n=setTimeout(()=>{o(Error(`Connection timeout`))},3e4)}catch(e){o(e)}}),this.connectionPromise)}setupEventHandlers(){this.ws&&(this.ws.on(`open`,()=>{this.isConnected=!0,this.logger.debug(`WebSocket connected`),this.emit(`open`),this.keepaliveTimer&&=(clearInterval(this.keepaliveTimer),null),this.keepaliveTimer=setInterval(()=>{this.ws?.readyState===y.OPEN&&this.ws.send(`ping`)},3e4)}),this.ws.on(`message`,e=>{this.emit(`message`,e)}),this.ws.on(`close`,(e,t)=>{this.isConnected=!1,this.keepaliveTimer&&=(clearInterval(this.keepaliveTimer),null),this.logger.warn(`WebSocket disconnected: code=${e}, reason=${t}`),this.ws=null,this.emit(`close`,e,t),this.shouldReconnect&&(this.logger.info(`Scheduling WebSocket reconnect with exponential backoff`),this.scheduleReconnect())}),this.ws.on(`error`,e=>{this.logger.error(`WebSocket error: ${e.message}`),e.message.includes(`401`)&&(this.logger.error(`Authentication failed. Check your token.`),this.close(),process.exit(1)),this.emit(`error`,e)}),this.ws.on(`ping`,e=>{this.ws?.readyState===y.OPEN&&this.ws.pong(e)}))}send(e){if(this.ws?.readyState===y.OPEN)this.ws.send(e);else throw this.logger.warn(`Cannot send message: WebSocket is not connected`),Error(`WebSocket is not connected`)}getSocket(){return this.ws}isReady(){return this.ws?.readyState===y.OPEN}close(){this.keepaliveTimer&&=(clearInterval(this.keepaliveTimer),null),this.shouldReconnect=!1,this.clearReconnectTimer(),this.reconnectAttempts=0,this.abortConnectionAttempt&&this.abortConnectionAttempt(Error(`Connection aborted by close()`)),this.ws&&=(this.ws.removeAllListeners(),this.ws.close(),null)}scheduleReconnect(){if(!this.shouldReconnect||this.reconnectTimer||this.connectionPromise)return;let e=Math.min(this.reconnectInitialDelay*2**this.reconnectAttempts,this.reconnectMaxDelay),t=this.reconnectAttempts+1;this.logger.warn(`Reconnect attempt ${t} scheduled in ${e}ms`),this.reconnectTimer=setTimeout(async()=>{if(this.reconnectTimer=null,this.shouldReconnect){this.reconnectAttempts=t;try{await this.connect(),this.logger.info(`WebSocket reconnected successfully`)}catch(e){let t=e instanceof Error?e.message:`Unknown error`;this.logger.error(`Reconnect attempt failed: ${t}`),this.scheduleReconnect()}}},e)}clearReconnectTimer(){this.reconnectTimer&&=(clearTimeout(this.reconnectTimer),null)}};const K=e=>e&&e.replace(/^['"]+|['"]+$/g,``),Ye=(e,t)=>{if(!t)return e;let n=t.replace(/[.*+?^${}()|[\]\\]/g,`\\$&`);return e.replace(new RegExp(n,`g`),`***TOKEN***`)},Xe=(e,t)=>{let n=K(e)??e,r=K(t?.trim());if(!r)return{cloneUrl:`https://github.com/${n}.git`};let i=encodeURIComponent(r);return r.startsWith(`ghs_`)?{cloneUrl:`https://x-access-token:${i}@github.com/${n}.git`,rewriteBase:`https://x-access-token:${i}@github.com/`}:{cloneUrl:`https://${i}@github.com/${n}.git`,rewriteBase:`https://${i}@github.com/`}},Ze=(e,t)=>{if(!t)return e;let n=[`https://github.com/`,`git@github.com:`,`ssh://git@github.com/`],r=new Set;for(let e of n)r.add(e);let i=[];for(let e of r)i.push(`-c`,`url.${t}.insteadOf=${e}`);return[...i,...e]},Qe=async(e,t,n,r,i,a)=>{let o=Ye([n,...r].join(` `),a);return t.debug(`[${e.sessionId}] Executing (${i}): ${o}`),await e.exec(n,r,i,!1)},$e=async(e,t,n,r)=>{let i=n.sessionId,a=n.getWorkdir();try{let o=K(e?.trim())??e?.trim();if(!o){r.info(`[${i}] No GitHub repository specified, skipping clone`);return}if(r.info(`[${i}] Cloning GitHub repository: ${o}`),!o.match(/^[^/]+\/[^/]+$/))throw Error(`Invalid GitHub repository format: ${o}. Expected format: owner/repo`);let{cloneUrl:s,rewriteBase:c}=Xe(o,t),l=K(s)??s,u=K(c);t?t.startsWith(`ghs_`)?r.info(`[${i}] Using GitHub App token for authentication`):r.info(`[${i}] Using GitHub token for authentication`):r.info(`[${i}] Cloning public repository`),await Qe(n,r,`git`,Ze([`clone`,`--depth`,`1`,`--recurse-submodules`,`--shallow-submodules`,l,a],u),`/`,t),await Qe(n,r,`git`,Ze([`-C`,a,`submodule`,`update`,`--init`,`--recursive`,`--depth`,`1`],u),`/`,t),r.info(`[${i}] Successfully cloned repository: ${o}`)}catch(e){throw r.error(`[${n.sessionId}] Failed to clone GitHub repository: ${e instanceof Error?e.message:`Unknown error`}`),e}};var et=class{socket;sessionManager;logger;machineId;token;userId;workspaceId;machineName;enableDocker;cliVersion;pendingPermissionRequests=new Map;acpUpdateBuffers=new Map;acpUpdateThrottlers=new Map;acpUpdateThrottleMs=200;constructor(e,t,n,r,i){this.workspaceDocument=n,this.socket=e,this.sessionManager=t,this.logger=r,this.token=i.token,this.userId=i.userId,this.workspaceId=i.workspaceId,this.machineName=i.machineName,this.enableDocker=i.enableDocker,this.cliVersion=i.cliVersion,this.machineId=i.machineId,this.sessionManager.setRequestPermissionHandler((e,t,n)=>this.handleAgentPermissionRequest(e,t,n)),this.setupSessionEventHandlers(),this.registerMachine()}setupSessionEventHandlers(){this.sessionManager.on(`output`,async e=>{this.logger.debug(`Output from session [${e.sessionId}]: ${e.data}`)}),this.sessionManager.on(`onACPUpdateMessage`,(e,t)=>{this.enqueueACPUpdate(e,t)}),this.sessionManager.on(`error`,async e=>{this.logger.error(`[${e.sessionId}] error occur ${e}`);let t=e.sessionId;await this.finalizeACPState(t),(await this.workspaceDocument.getOrCreateSessionDoc(t)).setError(`execution_error`),await this.sessionManager.finishSession(t),this.workspaceDocument.removeMachineSession(t)}),this.sessionManager.on(`exit`,async e=>{let t=e.sessionId;await this.finalizeACPState(t),(await this.workspaceDocument.getOrCreateSessionDoc(t)).setStatus(`completed`);let n=new Date().toISOString();this.workspaceDocument.updateMachineSession(t,{status:`completed`,completedAt:n})})}enqueueACPUpdate(e,t){let n=this.acpUpdateBuffers.get(e);n?n.push(t):this.acpUpdateBuffers.set(e,[t]),this.getOrCreateACPUpdateThrottler(e)()}getOrCreateACPUpdateThrottler(e){let t=this.acpUpdateThrottlers.get(e);return t||(t=he(this.acpUpdateThrottleMs,()=>this.flushACPUpdates(e).catch(t=>{this.logger.error(`[${e}] Failed to flush ACP updates: ${t instanceof Error?t.message:`Unknown error`}`)})),this.acpUpdateThrottlers.set(e,t),t)}async flushACPUpdates(e){this.acpUpdateThrottlers.get(e)?.cancel({upcomingOnly:!0});let t=this.acpUpdateBuffers.get(e);if(!t||t.length===0){this.acpUpdateBuffers.delete(e);return}this.acpUpdateBuffers.delete(e),await tt(await this.workspaceDocument.getOrCreateSessionDoc(e),t)}async flushAllACPUpdates(){let e=Array.from(new Set([...this.acpUpdateBuffers.keys(),...this.acpUpdateThrottlers.keys()]));for(let t of e)await this.finalizeACPState(t)}async finalizeACPState(e){try{await this.flushACPUpdates(e)}catch(t){this.logger.error(`[${e}] Failed to flush ACP updates during finalization: ${t instanceof Error?t.message:`Unknown error`}`)}finally{this.clearACPState(e)}}clearACPState(e){this.acpUpdateBuffers.delete(e),this.acpUpdateThrottlers.get(e)?.cancel(),this.acpUpdateThrottlers.delete(e)}registerMachine(){let e={type:`register`,role:`machine`,id:this.machineId,token:this.token,workspaceId:this.workspaceId,userId:this.userId,machineInfo:{name:this.machineName,cliVersion:this.cliVersion,os:process.platform,enableDocker:this.enableDocker}};this.sendMessage(e),this.logger.info(`Machine registered with name: ${this.machineName}`)}async handleMessage(e){switch(this.logger.debug(`Received message: ${e.type}`),e.type){case`register_response`:await this.handleRegisterResponse(e);break;case`session_create`:await this.handleSessionCreate(e);break;case`session_terminate`:await this.handleSessionTerminate(e);break;case`session_stop`:await this.handleSessionStop(e);break;case`permission_response`:await this.handlePermissionResponse(e);break;case`session_chat`:await this.handleSessionChat(e);break;default:this.logger.debug(`Unknown message: ${e}`)}}async handleSessionChat(e){let{sessionId:t,promptConfig:n,historyCid:r}=e;if(this.logger.info(`[${t}] Received chat request`),r)try{await(await this.workspaceDocument.getOrCreateSessionDoc(t)).markHistoryAsRead(r),this.logger.debug(`[${t}] Marked history as read`)}catch(e){this.logger.warn(`[${t}] Failed to mark history as read: ${e instanceof Error?e.message:`Unknown error`}`)}this.workspaceDocument.updateMachineSession(t,{status:`running`,cliType:n.agentType,createdAt:new Date().toISOString()});let i=this.sessionManager.getSession(t);i?(await i.agentClient?.connection?.prompt({sessionId:i.acpSessionId,prompt:[{type:`text`,text:n.prompt}]}),this.workspaceDocument.updateMachineSession(t,{status:`completed`,completedAt:new Date().toISOString()}),await this.finalizeACPState(t),this.logger.info(`[${t}] Chat request processed successfully`)):(this.logger.warn(`[${t}] Session not found`),(await this.workspaceDocument.getOrCreateSessionDoc(t)).setError(`session_not_found`),this.workspaceDocument.removeMachineSession(t))}async handleRegisterResponse(e){e.success?this.logger.info(`Registration successful, machine ID: ${this.machineId}`):this.logger.error(`Registration failed: ${e.error}`)}async handleSessionCreate(e){let{sessionId:t,config:n,workspaceId:r}=e;this.logger.info(`[${t}] Creating new session`);let i={sessionId:t,workspaceId:r,cliType:n.promptConfig.agentType,userId:this.userId,machineId:this.machineId,assumeDocExisting:!0,env:n.env};this.workspaceDocument.addMachineSession({sessionId:t,status:{status:`establish`,createdAt:new Date().toISOString()}});try{let e=await this.sessionManager.createSession(i);if(this.sendMessage({type:`session_create_response`,sessionId:t,success:!0}),n.githubRepo&&n.githubToken){this.workspaceDocument.updateMachineSession(t,{status:`git-clone`,repo:n.githubRepo});try{await $e(n.githubRepo,n.githubToken,e,this.logger)}catch(e){let n=e instanceof Error?e.message:`Failed to clone GitHub repository`;this.logger.error(`[${t}] ${n}`),await this.sessionManager.setSessionError(t,`execution_error`),this.workspaceDocument.removeMachineSession(t),await this.finalizeACPState(t);return}}let r=n.promptConfig;this.workspaceDocument.updateMachineSession(t,{status:`running`,cliType:i.cliType});let a=await e.agentClient?.connection?.prompt({sessionId:e.acpSessionId,prompt:[{type:`text`,text:r.prompt}]});(await this.workspaceDocument.getOrCreateSessionDoc(t)).setStatus(`completed`);let o=new Date().toISOString();this.workspaceDocument.updateMachineSession(t,{status:`completed`,completedAt:o}),this.logger.debug(`[${t}] Prompt response: ${JSON.stringify(a)}`),await this.finalizeACPState(t),this.logger.info(`[${t}] Session created successfully`)}catch{await this.finalizeACPState(t),await this.sessionManager.setSessionError(t,`execution_error`),this.sendMessage({type:`session_create_response`,sessionId:t,success:!1})}}async handleSessionTerminate(e){let{sessionId:t,force:n}=e;this.logger.info(`[${t}] Terminating session`);try{await this.sessionManager.terminateSession(t,n),await this.finalizeACPState(t);let e={type:`session_terminated`,sessionId:t,error:void 0,reason:`user_request`};this.sendMessage(e),this.workspaceDocument.updateMachineSession(t,{status:`terminated`}),this.logger.info(`[${t}] Session terminated successfully`)}catch(e){let n={type:`session_terminated`,sessionId:t,error:`execution_error`,reason:`error`};this.sendMessage(n),this.logger.error(`[${t}] Failed to terminate: ${e instanceof Error?e.message:`Unknown error`}`)}}async handleSessionStop(e){let{sessionId:t}=e;this.logger.info(`[${t}] Received stop request`);let n=this.sessionManager.getSession(t);if(!n){this.logger.warn(`[${t}] Session not found for stop request`);return}if(!n.agentClient?.connection||!n.acpSessionId){this.logger.warn(`[${t}] Cannot stop session without active ACP connection`);return}try{await n.agentClient.connection.cancel({sessionId:n.acpSessionId}),await this.finalizeACPState(t),(await this.workspaceDocument.getOrCreateSessionDoc(t)).setStatus(`completed`);let e=new Date().toISOString();this.workspaceDocument.updateMachineSession(t,{status:`completed`,completedAt:e}),this.logger.info(`[${t}] Stop signal sent to agent`)}catch(e){this.logger.error(`[${t}] Failed to stop session: ${e instanceof Error?e.message:`Unknown error`}`)}}async handlePermissionResponse(e){this.logger.info(`[${e.sessionId}] Permission response received for ${e.requestId}: ${e.outcome.outcome}`);let t=this.pendingPermissionRequests.get(e.requestId);t?(t.resolve({outcome:e.outcome}),this.pendingPermissionRequests.delete(e.requestId)):this.logger.warn(`[${e.sessionId}] No pending permission request found for ${e.requestId}`);try{await at(await this.workspaceDocument.getOrCreateSessionDoc(e.sessionId),e.requestId,e.outcome)}catch(t){this.logger.error(`[${e.sessionId}] Failed to update permission outcome: ${t instanceof Error?t.message:`Unknown error`}`)}}async handleAgentPermissionRequest(e,t,n){this.logger.info(`[${e}] Forwarding permission request ${t} for tool call ${n.toolCall.toolCallId}`);try{await it(await this.workspaceDocument.getOrCreateSessionDoc(e),t,n)}catch(t){this.logger.error(`[${e}] Failed to append permission request to history: ${t instanceof Error?t.message:`Unknown error`}`)}let r={type:`request_permission`,sessionId:e,requestId:t,request:n};return this.sendMessage(r),new Promise((n,r)=>{this.pendingPermissionRequests.has(t)&&this.logger.warn(`[${e}] Duplicate permission request ID detected: ${t}, overwriting existing handler`),this.pendingPermissionRequests.set(t,{resolve:n,reject:r,sessionId:e})})}sendMessage(e){try{this.socket.send(JSON.stringify(e))}catch(e){this.logger.error(`Failed to send message: ${e instanceof Error?e.message:`Unknown error`}`)}}updateSocket(e){this.socket=e,this.logger.debug(`WebSocket instance updated`)}async cleanup(){this.logger.info(`Cleaning up message handler resources`),await this.flushAllACPUpdates(),this.acpUpdateThrottlers.forEach(e=>e.cancel()),this.acpUpdateThrottlers.clear(),await this.sessionManager.cleanUp()}};const tt=async(e,t)=>{let n=Array.isArray(t)?t:[t],r=[];for(let e of n)r.push(...nt(e));r.length&&await e.updateHistory(e=>{let t=e;for(let e of r)t=rt(t,e);return t})},nt=e=>{let{update:t}=e;switch(t.sessionUpdate){case`user_message_chunk`:return[];case`agent_message_chunk`:switch(t.content.type){case`text`:return[{type:`text`,text:t.content.text}];case`image`:case`audio`:case`resource_link`:case`resource`:throw Error(`Unsupported content type: ${t.content.type}`)}case`agent_thought_chunk`:switch(t.content.type){case`text`:return[{type:`thought`,text:t.content.text}];case`image`:case`audio`:case`resource_link`:case`resource`:throw Error(`Unsupported content type: ${t.content.type}`)}return[];case`tool_call`:case`tool_call_update`:return[{type:`tool_call`,toolCallId:t.toolCallId,title:t.title,kind:t.kind||void 0,status:t.status||`pending`,content:t.content?.map(e=>{switch(e.type){case`diff`:return{type:e.type,path:e.path,newText:e.newText,oldText:e.oldText};case`terminal`:case`content`:return}}).filter(e=>e!==void 0)}];case`plan`:return[{type:`plan`,entries:t.entries}];case`available_commands_update`:return[{type:`available_commands`,commands:t.availableCommands}];case`current_mode_update`:throw Error(`Unsupported session update: ${t.sessionUpdate}`);default:return[]}},rt=(e,t)=>{let n=e.length-1,r=n>=0?e[n]:null;if(!r||r.role!==`assistant`)return q(e,[t]);let i=t=>{let i=t(JSON.parse(r.contents)),a={...r,contents:JSON.stringify(i)},o=[...e];return o[n]=a,o};switch(t.type){case`text`:case`thought`:return i(e=>{let n=[...e],r=n.findIndex(e=>e.type===t.type);if(r>=0){let e=n[r];(e.type===`text`||e.type===`thought`)&&(n[r]={...e,text:e.text+t.text})}else n.push({...t});return n});case`plan`:return i(e=>{let n=[...e],r=n.findIndex(e=>e.type===`plan`);return r>=0?n[r]={...t}:n.push({...t}),n});case`tool_call`:{let n=!1,r=e.map(e=>{let r=JSON.parse(e.contents),i=!1,a=r.map(e=>{if(e.type===`tool_call`&&e.toolCallId===t.toolCallId){i=!0,n=!0;let r=e.content||[];if(t.content!==void 0){let e=t.content;e.some(e=>e.type===`diff`)&&(r=r.filter(e=>e.type!==`diff`)),r=[...r,...e]}return{...e,toolCallId:t.toolCallId,title:t.title===void 0?e.title:t.title,kind:t.kind===void 0?e.kind:t.kind,status:t.status===void 0?e.status:t.status,content:r}}return e});return i?{...e,contents:JSON.stringify(a)}:e});return n?r:q(r,[t])}case`available_commands`:return q(e,[t]);default:return e}},q=(e,t)=>[...e,{role:`assistant`,contents:JSON.stringify(t),timestamp:new Date().toISOString(),userId:void 0,read:void 0}],it=async(e,t,n)=>{let r=n.toolCall.toolCallId;await e.updateHistory(e=>{let i=!1;return e.forEach(e=>{let a=JSON.parse(e.contents),o=!1,s=a.map(e=>e.type===`tool_call`&&e.toolCallId===r?(o=!0,i=!0,ot(e,t,n)):e);o&&(e.contents=JSON.stringify(s))}),i||e.push({role:`assistant`,contents:JSON.stringify([st(t,n)]),timestamp:new Date().toISOString(),read:void 0,userId:void 0}),e})},at=async(e,t,n)=>{await e.updateHistory(e=>(e.forEach(e=>{let r=JSON.parse(e.contents),i=!1,a=r.map(e=>e.type===`tool_call`&&e.permissionRequest?.requestId===t?(i=!0,{...e,permissionRequest:e.permissionRequest?{...e.permissionRequest,outcome:n}:e.permissionRequest}):e);i&&(e.contents=JSON.stringify(a))}),e))},ot=(e,t,n)=>{let r=n.toolCall;return{...e,title:e.title??r.title??null,kind:e.kind??r.kind??void 0,status:e.status??r.status??`pending`,content:e.content??r.content??void 0,locations:e.locations??r.locations??void 0,rawInput:e.rawInput??r.rawInput??void 0,rawOutput:e.rawOutput??r.rawOutput??void 0,permissionRequest:{requestId:t,options:n.options,outcome:e.permissionRequest?.outcome}}},st=(e,t)=>ot({type:`tool_call`,toolCallId:t.toolCall.toolCallId,title:t.toolCall.title??null,status:t.toolCall.status??`pending`,kind:t.toolCall.kind??void 0,content:t.toolCall.content??void 0,locations:t.toolCall.locations??void 0,rawInput:t.toolCall.rawInput??void 0,rawOutput:t.toolCall.rawOutput??void 0},e,t);var ct=class{manager;handler=null;sessionManager=null;initialized=!1;logger;constructor(e){this.options=e,this.manager=new Je(e.managerOptions),this.logger=e.logger}async initialize(){if(this.initialized)return{socket:this.requireSocket(),sessionManager:this.requireSessionManager(),messageHandler:this.requireHandler()};await this.manager.connect();let e=this.requireSocket();return this.sessionManager=this.options.sessionManagerFactory(e),this.handler=new et(e,this.sessionManager,this.options.workspaceDocument,this.options.logger,this.options.handlerConfig),this.attachBridges(),await this.sessionManager.initialize(),this.initialized=!0,{socket:e,sessionManager:this.sessionManager,messageHandler:this.handler}}getManager(){return this.manager}getMessageHandler(){return this.handler}getSessionManager(){return this.sessionManager}async cleanup(){this.detachBridges(),this.handler&&=(await this.handler.cleanup(),null),await this.sessionManager?.cleanUp().catch(e=>{this.options.logger.error(`Cleanup failed: ${e instanceof Error?e.message:`Unknown error`}`)}),this.sessionManager=null,this.initialized=!1,this.manager.close()}attachBridges(){this.manager.on(`message`,this.handleRawMessage),this.manager.on(`open`,this.handleManagerOpen)}detachBridges(){this.manager.off(`message`,this.handleRawMessage),this.manager.off(`open`,this.handleManagerOpen)}handleRawMessage=async e=>{let t=this.handler;if(!t)return;let n=e.toString();if(n!==`pong`)try{let e=JSON.parse(n);await t.handleMessage(e)}catch(e){if(e instanceof SyntaxError){this.options.logger.debug(`Received non-JSON message: ${n}`);return}this.options.logger.error(`Failed to handle message: ${e instanceof Error?e.message:`Unknown error`}`)}};handleManagerOpen=()=>{let e=this.handler;if(!e)return;let t=this.manager.getSocket();if(!t){this.options.logger.warn(`WebSocket reopened but socket is unavailable`);return}e.updateSocket(t),e.registerMachine(),this.options.logger.info(`WebSocket connection re-established`)};requireSocket(){let e=this.manager.getSocket();if(!e)throw Error(`WebSocket connection has not been established`);return e}requireSessionManager(){if(!this.sessionManager)throw Error(`Session manager has not been initialized`);return this.sessionManager}requireHandler(){if(!this.handler)throw Error(`Message handler has not been initialized`);return this.handler}};const lt={claude:{command:`npx`,args:[`-y`,`@zed-industries/claude-code-acp`]},codex:{command:`npx`,args:[`-y`,`@zed-industries/codex-acp`,`-c`,`shell_environment_policy.ignore_default_excludes=true`]}};var ut=class extends ve{logger;machineId;token;requestPermissionHandler;constructor(e,t,n,r){super(),this.workspaceDocument=r,this.logger=e,this.token=t,this.machineId=n}async createSession(e){e.assumeDocExisting||(e.sessionId=await(await this.workspaceDocument.getOrCreateWorkspaceDoc(e.workspaceId,e.userId)).createSession(e.machineId,e.cliType,e.title)),await this.workspaceDocument.getOrCreateSessionDoc(e.sessionId),this.logger.debug(`[${e.sessionId}] Session will enter create inner`);let t=await this.createSessionInner(e),n=e.sessionId,r;try{r=await t.createAgent({command:lt[e.cliType].command,args:lt[e.cliType].args,onUpdateMessage:e=>{this.emit(`onACPUpdateMessage`,n,e)},onRequestPermission:(e,t)=>this.requestPermissionHandler?this.requestPermissionHandler(n,e,t):(this.logger.warn(`[${n}] Permission handler not configured`),Promise.resolve({outcome:{outcome:`cancelled`}}))})}catch(t){throw this.logger.error(`[${e.sessionId}] Failed to create agent: ${t instanceof Error?t.message:`Unknown error`}`),t}return await(await this.workspaceDocument.getOrCreateSessionDoc(n)).setChatId(r),t}async cleanUp(){await this.workspaceDocument.cleanUp(),await this.cleanupInner()}async finishSession(e){await this.workspaceDocument.cleanSessionDoc(e)}setRequestPermissionHandler(e){this.requestPermissionHandler=e}async handleFinishCommand(e,t){let n=`${H}/api/issues/${t}/review`;try{let e=await(await fetch(n,{method:`GET`,headers:{"Content-Type":`application/json`,Authorization:`Bearer ${this.token}`}})).json();e.success?this.logger.success(`Issue ${t} marked as completed`):this.logger.error(`Failed to mark issue as completed: ${e.error}`)}catch(e){this.logger.error(`Error calling issue complete API: ${e instanceof Error?e.message:`Unknown error`}`)}}async setSessionError(e,t){let n=await this.workspaceDocument.getOrCreateSessionDoc(e);n&&n.setError(t)}},dt=class extends be{buffer=``;constructor(){super({objectMode:!0})}_transform(e,t,n){this.buffer+=e.toString();let r=this.buffer.split(`
17
+ `);this.buffer=r.pop()||``;for(let e of r)if(e.trim())try{let t=JSON.parse(e);this.push(t)}catch{}n()}_flush(e){if(this.buffer.trim())try{let e=JSON.parse(this.buffer);this.push(e)}catch{this.emit(`error`,Error(`Invalid JSON: ${this.buffer}`))}e()}reset(){this.buffer=``}getBuffer(){return this.buffer}},ft=class{connection=null;logger;sandbox;terminalManager;acpSessionId=null;constructor(e){this.options=e,this.logger=e.logger,this.terminalManager=e.terminalManager,this.sandbox=e.sandbox}async requestPermission(e){if(this.sandbox)return{outcome:{outcome:`selected`,optionId:(e.options.find(e=>e.kind===`allow_once`||e.kind===`allow_always`||!e.kind&&e.name.toLowerCase().includes(`allow`))||e.options[0]).optionId}};this.ensureSessionMatch(e.sessionId);let t=S();return this.logger.info(`[${this.options.sessionId}] Requesting permission for tool call ${e.toolCall.toolCallId}`),this.options.onRequestPermission(t,e)}async sessionUpdate(e){this.logger.debug(`ACP Session ${this.options.sessionId} update:`,e),this.options.onUpdateMessage(e)}async writeTextFile(e){throw Error(`Method not implemented.`)}async readTextFile(e){throw Error(`Method not implemented.`)}async createTerminal(e){this.ensureSessionMatch(e.sessionId);let t=e.env?.reduce((e,t)=>(e[t.name]=t.value,e),{})??void 0;return{terminalId:await this.terminalManager.createTerminal(e.sessionId,e.command,e.args??[],e.cwd??void 0,t,e.outputByteLimit??void 0)}}async terminalOutput(e){this.ensureSessionMatch(e.sessionId);let t=await this.terminalManager.terminalOutput(e.sessionId,e.terminalId);return{output:t.output,truncated:t.truncated,exitStatus:t.exitStatus??void 0}}async releaseTerminal(e){return this.ensureSessionMatch(e.sessionId),await this.terminalManager.releaseTerminal(e.sessionId,e.terminalId),{}}async waitForTerminalExit(e){return this.ensureSessionMatch(e.sessionId),await this.terminalManager.waitForTerminalExit(e.sessionId,e.terminalId)}async killTerminal(e){return this.ensureSessionMatch(e.sessionId),await this.terminalManager.killTerminal(e.sessionId,e.terminalId),{}}async extMethod(e,t){throw Error(`Method not implemented.`)}async extNotification(e,t){throw Error(`Method not implemented.`)}async startSession(e,t){let n=new b.ClientSideConnection(()=>this,e);this.connection=n,this.logger.info(`Starting ACP Client...`);let r=await n.initialize({protocolVersion:b.PROTOCOL_VERSION,clientCapabilities:{terminal:!0}});this.logger.debug(`ACP Client initialized:`,r);let i=await n.newSession({cwd:t,mcpServers:[]});return this.logger.debug(`ACP Session started: ${i.sessionId}`),this.acpSessionId=i.sessionId,this.logger.debug(`ACP Session mode set to agent: ${i.sessionId}`),{sessionId:i.sessionId}}ensureSessionMatch(e){if(!this.acpSessionId)throw Error(`ACP session has not been initialized yet.`);if(e!==this.acpSessionId)throw Error(`Mismatched ACP session. Expected ${this.acpSessionId} but got ${e}`)}},pt=class{terminals=new Map;logger;sessionLabel;getActiveSessionId;defaultOutputByteLimit;constructor(e){this.logger=e.logger,this.sessionLabel=e.sessionLabel,this.getActiveSessionId=e.getActiveAcpSessionId,this.defaultOutputByteLimit=e.defaultOutputByteLimit??1048576}async createTerminal(e,t,n,r,i,a){this.ensureValidSession(e);let o=S(),s={id:o,handle:null,output:Buffer.alloc(0),outputByteLimit:a??this.defaultOutputByteLimit,truncated:!1,exitStatus:null,waiters:[]};return s.handle=await this.startProcess({terminalId:o,command:t,args:n??[],cwd:r,env:i},{onData:e=>this.appendOutput(s,e),onExit:(e,t)=>this.handleExit(s,e,t),onError:e=>{this.logger.error(`[${this.sessionLabel}] Terminal ${o} error: ${e.message}`)}}),this.terminals.set(o,s),this.logger.debug(`[${this.sessionLabel}] Terminal ${o} started: ${t}`),o}async terminalOutput(e,t){let n=this.getTerminal(e,t);return{output:n.output.toString(`utf8`),truncated:n.truncated,exitStatus:n.exitStatus}}async releaseTerminal(e,t){let n=this.getTerminal(e,t);try{await this.killHandle(n)}catch(e){this.logger.warn(`[${this.sessionLabel}] Failed to kill terminal ${t} on release: ${e}`)}n.exitStatus||(n.exitStatus={exitCode:null,signal:`SIGTERM`},this.resolveWaiters(n)),await this.disposeHandle(n),this.terminals.delete(t),this.logger.debug(`[${this.sessionLabel}] Terminal ${t} released`)}async waitForTerminalExit(e,t){let n=this.getTerminal(e,t);return n.exitStatus?n.exitStatus:await new Promise(e=>{n.waiters.push(e)})}async killTerminal(e,t){let n=this.getTerminal(e,t);await this.killHandle(n)}resolveWaiters(e){let t=e.exitStatus??{exitCode:null,signal:null};for(;e.waiters.length;){let n=e.waiters.shift();n&&n(t)}}ensureValidSession(e){let t=this.getActiveSessionId();if(!t)throw Error(`ACP session is not active yet.`);if(t!==e)throw Error(`ACP session mismatch for terminal request: ${e}`)}getTerminal(e,t){this.ensureValidSession(e);let n=this.terminals.get(t);if(!n)throw Error(`Terminal ${t} not found or already released`);return n}handleExit(e,t,n){this.terminals.has(e.id)&&(e.exitStatus={exitCode:typeof t==`number`?t:null,signal:n??void 0},this.resolveWaiters(e))}appendOutput(e,t){if(e.outputByteLimit===0){t.length&&(e.truncated=!0);return}let n=Buffer.isBuffer(t)?t:Buffer.from(t);e.output=Buffer.concat([e.output,n]),e.output.length>e.outputByteLimit&&(e.truncated=!0,e.output=_t(e.output,e.outputByteLimit))}},mt=class extends pt{resolveWorkdir;buildEnv;constructor(e){super(e),this.resolveWorkdir=e.resolveWorkdir,this.buildEnv=e.buildEnv}async startProcess(e,t){let n=this.resolveWorkdir(e.cwd),r=this.buildEnv(e.env),i=h(`bash`,[`-lc`,gt(e.command,e.args)],{cwd:n,env:r,stdio:[`ignore`,`pipe`,`pipe`]}),a=e=>t.onData(e),o=e=>t.onData(e),s=(e,n)=>t.onExit(e,n),c=e=>t.onError?.(e);return i.stdout?.on(`data`,a),i.stderr?.on(`data`,o),i.on(`close`,s),i.on(`error`,c),{child:i,dispose:()=>{i.stdout?.off(`data`,a),i.stderr?.off(`data`,o),i.off(`close`,s),i.off(`error`,c)}}}async killHandle(e){!e.exitStatus&&!e.handle.child.killed&&e.handle.child.kill(`SIGTERM`)}async disposeHandle(e){e.handle.dispose()}},ht=class extends pt{getContainer;resolveWorkdir;constructor(e){super(e),this.getContainer=e.getContainer,this.resolveWorkdir=e.resolveWorkdir}async startProcess(e,t){let n=this.getContainer();if(!n)throw Error(`Container is not ready for terminal execution`);let r=gt(e.command,e.args),i=await n.exec({Cmd:[`/bin/bash`,`-lc`,r],WorkingDir:this.resolveWorkdir(e.cwd),AttachStdout:!0,AttachStderr:!0,AttachStdin:!0,Tty:!0,Env:e.env?Object.entries(e.env).map(([e,t])=>`${e}=${t}`):void 0}),a=await i.start({hijack:!0,stdin:!0}),o=new ye,s=new ye,c=e=>t.onData(e),l=e=>t.onData(e),u=e=>t.onError?.(e),d=async()=>{try{let e=await i.inspect();t.onExit(e.ExitCode??0,null)}catch(e){t.onError?.(e),t.onExit(null,null)}};return o.on(`data`,c),s.on(`data`,l),n.modem.demuxStream(a,o,s),a.on(`error`,u),a.on(`end`,d),{exec:i,stream:a,stdout:o,stderr:s,pid:await yt(i),removeListeners:()=>{o.off(`data`,c),s.off(`data`,l),a.off(`error`,u),a.off(`end`,d)}}}async killHandle(e){if(!e.exitStatus)try{if(e.handle.pid)try{process.kill(e.handle.pid,`SIGTERM`)}catch(e){let t=e;if(t.code!==`ESRCH`)throw t}e.handle.stream.destroyed||(e.handle.stream.write(``),e.handle.stream.end())}catch(e){this.logger.warn(`Failed to kill docker terminal: ${e}`)}}async disposeHandle(e){e.handle.removeListeners(),e.handle.stream.destroyed||e.handle.stream.destroy(),e.handle.stdout.destroyed||e.handle.stdout.destroy(),e.handle.stderr.destroyed||e.handle.stderr.destroy()}};function gt(e,t){return t.length?`${e} ${t.map(e=>`'${e.replace(/'/g,`'"'"'`)}'`).join(` `)}`:e}function _t(e,t){if(e.length<=t)return e;let n=e.length-t;for(;n<e.length&&vt(e[n]);)n+=1;return e.slice(n)}function vt(e){return(e&192)==128}async function yt(e,t=5,n=100){for(let r=0;r<t;r++){try{let t=await e.inspect();if(t.Pid&&t.Pid>0)return t.Pid}catch{return null}await new Promise(e=>setTimeout(e,n))}return null}var bt=class extends _e{isDocker=!0;docker;container=null;config;status;dockerExec=null;stream=null;healthCheckInterval;logger;lastInputWasAI=!1;sessionId;agentClient=null;acpSessionId=null;terminalManager;env={};constructor(e,t,n){super(),this.docker=e,this.config=t,this.logger=n,this.sessionId=t.sessionId,this.logger.debug(`config`,{config:t});let r=`lody-${t.sessionId.replaceAll(`:`,``).replaceAll(`@`,``)}-${Date.now()}`;this.status={sessionId:t.sessionId,containerId:``,containerName:r,status:`initializing`},this.terminalManager=new ht({logger:this.logger,sessionLabel:this.sessionId,getActiveAcpSessionId:()=>this.acpSessionId,getContainer:()=>this.container,resolveWorkdir:e=>e??this.getWorkdir()})}getWorkdir(){return W}async create(){try{this.logger.info(`[${this.config.sessionId}] Creating devcontainer session`),this.status.status=`building`,await this.ensureDevcontainerImage();let e=await this.buildContainerConfig();this.status.status=`starting`,this.container=await this.docker.createContainer(e),this.status.containerId=this.container.id,this.logger.info(`[${this.config.sessionId}] Container created: ${this.status.containerName}`),await this.container.start(),this.startHealthCheck(),this.status.status=`running`,this.emit(`created`,this.status)}catch(e){throw this.status.status=`error`,this.status.error=e instanceof Error?e.message:`Unknown error`,this.logger.error(`[${this.config.sessionId}] Failed to create container: ${this.status.error}`),this.emit(`error`,{sessionId:this.config.sessionId,error:e,status:{...this.status}}),e}}async exec(e,t,n,r){let i=this.formatCommand(e,t);return this.lastInputWasAI=r,await this.executeNonInteractiveCommand(i,n,r)}async ensureDevcontainerImage(){await St({docker:this.docker,logger:this.logger,sessionPrefix:this.config.sessionId})}async buildContainerConfig(){let e={...this.config.env,LODY_SESSION_ID:this.config.sessionId,LODY_MACHINE_SERVER_URL:process.env.LODY_MACHINE_SERVER_URL,TERM:`xterm-256color`,COLORTERM:`truecolor`};this.env=e,this.logger.debug(`[${this.config.sessionId}] Container config with env: ${JSON.stringify(e)}`);let t=[],n=l.join(f.homedir(),`.claude`),r=l.join(f.homedir(),`.codex`);d.existsSync(n)&&t.push(`${n}:/home/node/.claude:rw`),d.existsSync(r)&&t.push(`${r}:/home/node/.codex:rw`);let i=l.join(f.homedir(),`.claude.json`);return d.existsSync(i)&&t.push(`${i}:/home/node/.claude.json:rw`),{name:this.status.containerName,Image:`lody/devcontainer:latest`,Hostname:`lody-${this.config.sessionId.substring(0,8)}`,Env:Object.entries(e).map(([e,t])=>`${e}=${t}`),WorkingDir:this.config.workingDir||W,User:`node`,Tty:!0,OpenStdin:!0,StdinOnce:!1,AttachStdin:!0,AttachStdout:!0,AttachStderr:!0,HostConfig:{Init:!0,Binds:t,NetworkMode:`host`,RestartPolicy:{Name:`unless-stopped`}},Labels:{"lody.managed":`true`,"lody.session.id":this.config.sessionId}}}handleOutput(e){e.trim()!==``&&this.emit(`output`,{sessionId:this.config.sessionId,data:e,timestamp:new Date,stream:`stdout`,isFromAI:this.lastInputWasAI})}handleClose(e,t){this.logger.info(`[${this.config.sessionId}] process exited: code=${e}, signal=${t}`),this.status.exitCode=e||1,this.status.status=`stopped`,this.emit(`exited`,this.status)}handleError(e){this.logger.info(`[${this.config.sessionId}] docker process error: ${e}`),this.status.exitCode=1,this.status.status=`error`,this.emit(`error`,{sessionId:this.sessionId,error:e,status:{...this.status}})}formatCommand(e,t){return t.length?`${e} ${t.map(e=>`'${e.replace(/'/g,`'"'"'`)}'`).join(` `)}`:e}async createAgent(e){if(!this.container)throw Error(`Container not available`);let t=await(await this.container.exec({Cmd:[`/bin/bash`,`-c`,`${e.command} ${e.args?.join(` `)}`],WorkingDir:this.getWorkdir(),Tty:!1,AttachStdin:!0,AttachStdout:!0,AttachStderr:!0,Env:Object.entries(this.env).map(([e,t])=>`${e}=${t}`)})).start({hijack:!0,stdin:!0}),n=new WritableStream({write:e=>{t.write(e)},close:()=>{t.end()}}),r=this.container,i=x(n,new ReadableStream({start(e){let n=new xe({write(t,n,r){e.enqueue(new Uint8Array(t)),r()}});r.modem.demuxStream(t,n,null),t.on(`end`,()=>e.close()),t.on(`error`,t=>e.error(t))}})),a=new ft({logger:this.logger,sessionId:this.sessionId,terminalManager:this.terminalManager,onUpdateMessage:e.onUpdateMessage,onRequestPermission:e.onRequestPermission,sandbox:!0}),{sessionId:o}=await a.startSession(i,this.getWorkdir());return this.agentClient=a,this.acpSessionId=o,this.logger.debug(`[${this.sessionId}] ACP agent process started.`),o}async executeNonInteractiveCommand(e,t,n,r=!1,i=36e5){if(!this.container)throw Error(`Container not available`);this.logger.info(`[${this.config.sessionId}] Executing non-interactive command: ${e}`);try{let a=await(await this.container.exec({Cmd:[`/bin/bash`,`-c`,e],WorkingDir:t,Tty:!1,AttachStdin:!1,AttachStdout:!0,AttachStderr:!1,Env:Object.entries(this.env).map(([e,t])=>`${e}=${t}`)})).start({hijack:!0,stdin:!1,Detach:r}),o=new dt;return new Promise((t,r)=>{let s=``,c=null;c=setTimeout(()=>{a.destroy(),this.handleClose(null,null),r(Error(`Command timed out after ${i}ms: ${e}`))},i),a.on(`data`,e=>{if(e.length>=8){let t=e[0],n=e.slice(8).toString(`utf8`);t===1?(this.logger.debug(`[${this.config.sessionId}] Command stdout: ${n}`),o.write(n)):t===2&&(s+=n,this.handleError(Error(s)),r(Error(`Command failed: ${s}`)))}}),a.on(`end`,async()=>{c&&clearTimeout(c),n&&this.emit(`exited`,{sessionId:this.sessionId,exitCode:0,containerId:this.status.containerId,containerName:this.status.containerName,status:`stopped`}),t(``)}),a.on(`error`,e=>{c&&clearTimeout(c),this.handleError(e),a.destroy(),r(e)}),o.on(`data`,e=>{let t=JSON.stringify(e);n&&this.handleOutput(t)})})}catch(e){throw this.logger.error(`[${this.config.sessionId}] Failed to create non-interactive exec: ${e}`),this.handleError(e instanceof Error?e:Error(`Unknown error`)),e}}startHealthCheck(){this.healthCheckInterval=setInterval(async()=>{try{await this.updateStatus()}catch(e){this.logger.error(`[${this.config.sessionId}] Health check failed: ${e instanceof Error?e.message:`Unknown error`}`)}},3e4)}async updateStatus(){if(this.container)try{let e=await this.container.inspect();e.State.Running?this.status.status=`running`:(this.status.status=`stopped`,this.status.exitCode=e.State.ExitCode)}catch(e){this.logger.error(`[${this.config.sessionId}] Failed to update status: ${e instanceof Error?e.message:`Unknown error`}`),this.terminate(!0)}}async getStatus(){return this.container&&await this.updateStatus(),{...this.status}}getConfig(){return{...this.config}}async terminate(e=!1){if(this.logger.info(`[${this.config.sessionId}] Terminating container ${e?`(force)`:``}`),this.status.status===`stopping`||this.status.status===`terminated`){this.logger.debug(`[${this.config.sessionId}] Container already ${this.status.status}`);return}try{if(this.logger.info(`[${this.config.sessionId}] Terminating container ${e?`(force)`:``}`),this.status.status=`stopping`,this.healthCheckInterval&&=(clearInterval(this.healthCheckInterval),void 0),this.dockerExec)try{this.stream&&this.stream.destroy(),await new Promise(e=>setTimeout(e,1e3));try{(await this.dockerExec.inspect()).Running&&await this.container?.kill()}catch(e){this.logger.debug(`Exec inspect failed (likely already terminated): ${e.message}`)}}catch(e){this.logger.warn(`Failed to cleanup exec gracefully: ${e instanceof Error?e.message:`Unknown error`}`)}finally{this.dockerExec=null,this.stream=null}if(this.container){try{e?await this.container.kill():await this.container.stop({t:10})}catch(e){if(e.statusCode===409){this.logger.debug(`[${this.config.sessionId}] Container removal already in progress`);return}if(e.statusCode===304)this.logger.debug(`[${this.config.sessionId}] Container already stopped`);else throw e}await this.container.remove({force:!0}),this.container=null}this.status.status=`terminated`,this.emit(`terminated`,this.status)}catch(e){throw this.logger.error(`[${this.config.sessionId}] Failed to terminate container: ${e instanceof Error?e.message:`Unknown error`}`),e}}async cleanup(){await this.terminate(!0)}};async function xt({docker:e,sessionPrefix:t,logger:n,spinner:r}){let i=t?`[${t}] `:``;n.info(`${i}Building devcontainer image...`),r&&(r.text=`Building development environment image (this may take several minutes)...`);let a=l.join(__dirname,`devcontainer`,`Dockerfile`),o=await e.buildImage({context:l.dirname(a),src:[`Dockerfile`,`devcontainer.json`]},{t:`lody/devcontainer:latest`,buildargs:{CLAUDE_CLI_VERSION:`latest`}}),s=0,c=0,u=Date.now();await new Promise((t,a)=>{e.modem.followProgress(o,(e,n)=>{e?a(e):(r&&r.succeed(`Development environment image built successfully`),t(n))},e=>{if(e.stream){let t=e.stream.trim();n.info(`${i}Build: ${t}`);let a=t.match(/^Step (\d+)\/(\d+)\s*:\s*(.+)$/);if(a){s=parseInt(a[1]),c=parseInt(a[2]);let e=a[3];r&&(r.text=`Building image: Step ${s}/${c} - ${e}`)}let o=Date.now();r&&o-u>3e3&&(c>0?r.text=`Building image: ${Math.round(s/c*100)}% complete (Step ${s}/${c})`:r.text=`Building development environment image... (this may take several minutes)`,u=o),r&&(t.includes(`Downloading`)||t.includes(`Extracting`))&&t.length<100&&(r.text=`Building image: ${t}`)}e.error&&(n.error(`${i}Build error: ${e.error}`),r&&r.fail(`Build failed: ${e.error}`),a(Error(e.error)))})})}async function St({docker:e,sessionPrefix:t,logger:n,spinner:r}){let i=t?`[${t}] `:``;try{await e.getImage(`lody/devcontainer:latest`).inspect(),n.info(`${i}Using existing devcontainer image`),r&&(r.text=`Development environment ready`)}catch(a){n.error(`${i}Failed to get devcontainer image: ${a instanceof Error?a.message:`Unknown error`}`),await xt({docker:e,sessionPrefix:t,logger:n,spinner:r})}}var Ct=class extends ut{isDocker=!0;docker;sessions=new Map;cleanupInterval;pingInterval;constructor(e,t,n,r){super(e,t,n,r),this.docker=new ge,this.startPeriodicCleanup()}async initialize(){await St({docker:this.docker,logger:this.logger}),this.logger.info(`Docker session manager initialized`)}async createSessionInner(e){let t={sessionId:e.sessionId,env:{...e.env},workingDir:W};try{return await this.createOrReuseSession(t)}catch(t){throw this.logger.error(`[${e.sessionId}] Failed to create Docker session: ${t instanceof Error?t.message:`Unknown error`}`),t}}async terminateSession(e,t=!1){let n=this.sessions.get(e);if(!n){this.logger.warn(`Session ${e} not found`);return}await n.terminate(t),this.logger.info(`[${e}] Docker session terminated`)}async cleanupInner(){this.logger.info(`Cleaning up all Docker sessions...`),this.cleanupInterval&&=(clearInterval(this.cleanupInterval),void 0);try{await this.terminateAllSessions(!0),await this.cleanupOrphanedContainers()}catch(e){this.logger.error(`Failed to cleanup Docker sessions: ${e instanceof Error?e.message:`Unknown error`}`)}}hasSession(e){return this.sessions.has(e)}getSession(e){return this.sessions.get(e)||null}async createOrReuseSession(e){let t=this.sessions.get(e.sessionId);if(t){let n=await t.getStatus();if(n.status===`running`||n.status===`starting`)return this.logger.info(`[${e.sessionId}] Reusing existing container session`),this.startPingInterval(),t;this.logger.warn(`[${e.sessionId}] Session exists but not running, terminating`),await this.terminateSession(e.sessionId,!0)}this.logger.debug(`[${e.sessionId}] Creating new devcontainer session config: ${JSON.stringify(e)}`);let n=new bt(this.docker,e,this.logger);this.registerSessionEvents(n),this.sessions.set(e.sessionId,n),this.startPingInterval();try{return await n.create(),this.logger.info(`[${e.sessionId}] Devcontainer session created successfully`),n}catch(t){throw this.sessions.delete(e.sessionId),this.tryStopPingInterval(),t}}registerSessionEvents(e){let t=e.getConfig().sessionId;e.on(`output`,e=>{let t={sessionId:e.sessionId,data:e.data,timestamp:e.timestamp};this.emit(`output`,t)}),e.on(`error`,e=>{let n=e.error instanceof Error?e.error:Error(typeof e.error==`string`?e.error:`Unknown error`),r={sessionId:e.sessionId??t,error:n};this.emit(`error`,r)}),e.on(`exited`,e=>{let t={sessionId:e.sessionId,exitCode:e.exitCode||0};this.emit(`exit`,t)}),e.on(`terminated`,e=>{this.sessions.delete(t);let n={sessionId:e.sessionId,exitCode:e.exitCode,status:e};this.emit(`terminated`,n),this.tryStopPingInterval()})}async terminateAllSessions(e=!1){let t=Array.from(this.sessions.values()).map(t=>t.terminate(e).catch(e=>this.logger.error(`Failed to terminate session: ${e instanceof Error?e.message:`Unknown error`}`)));await Promise.allSettled(t),this.sessions.clear(),this.tryStopPingInterval()}async getAllSessionsStatus(){let e=[];for(let t of this.sessions.values())try{let n=await t.getStatus();e.push(n)}catch(e){this.logger.error(`Failed to get status for session: ${e instanceof Error?e.message:`Unknown error`}`)}return e}async cleanupOrphanedContainers(){try{let e=await this.docker.listContainers({all:!0,filters:{label:[`lody.managed=true`]}}),t=new Set(this.sessions.keys());for(let n of e){let e=n.Labels[`lody.session.id`];if(!e||!t.has(e))try{let e=this.docker.getContainer(n.Id);await e.stop(),await e.remove({force:!0}),this.logger.info(`Cleaned up orphaned container: ${n.Names[0]}`)}catch(e){this.logger.warn(`Failed to cleanup container: ${e instanceof Error?e.message:`Unknown error`}`)}}}catch(e){this.logger.error(`Failed to cleanup orphaned containers: ${e instanceof Error?e.message:`Unknown error`}`)}}startPeriodicCleanup(){this.cleanupInterval=setInterval(async()=>{try{await this.cleanupOrphanedContainers()}catch(e){this.logger.error(`Periodic cleanup failed: ${e instanceof Error?e.message:`Unknown error`}`)}},3600*1e3)}startPingInterval(){this.sessions.size===0||this.pingInterval||(this.pingInterval=setInterval(()=>{this.emit(`ping`)},5e3))}tryStopPingInterval(){this.pingInterval&&this.sessions.size===0&&(clearInterval(this.pingInterval),this.pingInterval=void 0)}},wt=class extends _e{sessionId;isDocker=!1;config;logger;status=`created`;activeProcess=null;agentProcess=null;agentClient=null;acpSessionId=null;terminalManager;constructor(e,t,n){super(),this.pwd=t,this.config=e,this.logger=n,this.sessionId=e.sessionId,this.terminalManager=new mt({logger:this.logger,sessionLabel:this.sessionId,getActiveAcpSessionId:()=>this.acpSessionId,resolveWorkdir:e=>e??this.getWorkdir(),buildEnv:e=>this.buildShellEnv(e)})}getWorkdir(){let e=l.join(this.pwd,`LODY_WORKDIR`,this.sessionId);return u.existsSync(e)||u.mkdirSync(e,{recursive:!0}),e}async exec(e,t,n,r){if(this.status===`failed`||this.status===`terminated`)throw Error(`Session ${this.sessionId} is not running`);let i=this.formatCommand(e,t);return await this.runCommand(i,n,r)}async terminate(e=!1){this.logger.info(`[${this.sessionId}] Terminating session${e?` (force)`:``}`);let t=t=>{t&&!t.killed&&t.kill(e?`SIGKILL`:`SIGTERM`)},n=this.activeProcess;t(n),t(this.agentProcess),this.status=`terminated`;let r={sessionId:this.sessionId,exitCode:n?.exitCode??0};this.emit(`terminated`,r)}handleParserData=e=>{let t=JSON.stringify(e);this.emitOutput(t)};handleParserError=e=>{let t=e instanceof Error?e.message:String(e);this.logger.error(`[${this.sessionId}] Parser error: ${t}`)};formatCommand(e,t){return t.length?`${e} ${t.map(e=>`'${e.replace(/'/g,`'"'"'`)}'`).join(` `)}`:e}buildShellEnv(e){return{...process.env,...this.config.env,...e,FORCE_COLOR:`1`,TERM:`xterm-256color`,PS1:``,PROMPT_COMMAND:``,LODY_SESSION_ID:this.sessionId}}async createAgent(e){let t=this.buildShellEnv();console.log(`[${this.sessionId}] Starting ACP agent process...`,this.getWorkdir());let n=h(e.command,e.args,{cwd:this.getWorkdir(),env:t,stdio:[`pipe`,`pipe`,`pipe`]});this.agentProcess=n,n.on(`error`,e=>{this.logger.error(`[${this.sessionId}] Agent process error: ${e.message}`)}),n.once(`exit`,(e,t)=>{this.logger.debug(`[${this.sessionId}] ACP agent process exited with code ${e} signal ${t}`),this.agentProcess=null});let r=x(new WritableStream({write(e){n.stdin.write(e)},close(){n.stdin.end()}}),new ReadableStream({start(e){n.stdout.on(`data`,t=>{e.enqueue(t)}),n.stdout.on(`end`,()=>{e.close()})}})),i=new ft({logger:this.logger,sessionId:this.sessionId,terminalManager:this.terminalManager,onUpdateMessage:e.onUpdateMessage,onRequestPermission:e.onRequestPermission,sandbox:!0}),{sessionId:a}=await i.startSession(r,this.getWorkdir());return this.acpSessionId=a,this.agentClient=i,this.logger.debug(`[${this.sessionId}] ACP agent process started.`),a}runCommand(e,t,n){let r=this.buildShellEnv();return this.logger.info(`[${this.sessionId}] Executing command: ${e}`),new Promise((i,a)=>{let o=h(`bash`,[`-lc`,e],{cwd:t,env:r,stdio:[`ignore`,`pipe`,`pipe`]});this.logger.debug(`[${this.sessionId}] Spawned process PID: ${o.pid} with command: bash -lc ${e}`),this.activeProcess=o;let s=``,c=``,l=n?new dt:null;l&&(l.on(`data`,this.handleParserData),l.on(`error`,this.handleParserError)),o.stdout?.on(`data`,e=>{let t=e.toString();s+=t,l&&l.write(t)}),o.stderr?.on(`data`,e=>{let t=e.toString();c+=t,this.logger.warn(`[${this.sessionId}] Shell stderr: ${t}`)});let u=()=>{this.activeProcess=null,l&&(l.end(),l.removeListener(`data`,this.handleParserData),l.removeListener(`error`,this.handleParserError))};o.on(`close`,(e,t)=>{u();let r=e??0;!n&&s.trim()&&this.logger.info(`[${this.sessionId}] Command output: ${s.trim()}`),n&&this.emit(`exit`,{sessionId:this.sessionId,exitCode:r}),i(s.trimEnd())}),o.on(`error`,e=>{u(),this.emit(`error`,{sessionId:this.sessionId,error:e}),a(e)})})}emitOutput(e){if(e.trim()===``)return;let t={sessionId:this.sessionId,data:e,timestamp:new Date};this.emit(`output`,t)}},Tt=class extends ut{isDocker=!1;sessions=new Map;constructor(e,t,n,r,i){super(e,t,n,r),this.pwd=i}async initialize(){this.logger.debug(`Native session manager initialized`)}async createSessionInner(e){let t=new wt(e,this.pwd||process.cwd(),this.logger);return this.registerSessionEvents(t),this.sessions.set(e.sessionId,t),t}async terminateSession(e,t=!1){let n=this.sessions.get(e);if(!n){this.logger.warn(`Session ${e} not found`);return}await n.terminate(t),this.logger.info(`[${e}] Native session terminated`)}async cleanupInner(){this.logger.info(`Cleaning up all native sessions...`);let e=Array.from(this.sessions.values()).map(e=>e.terminate(!0).catch(t=>this.logger.error(`[${e.sessionId}] Failed to terminate session: ${t instanceof Error?t.message:`Unknown error`}`)));await Promise.allSettled(e),this.sessions.clear()}hasSession(e){return this.sessions.has(e)}getSession(e){return this.sessions.get(e)||null}registerSessionEvents(e){e.on(`output`,e=>{this.emit(`output`,e)}),e.on(`error`,e=>{this.emit(`error`,e)}),e.on(`exit`,e=>{this.sessions.delete(e.sessionId),this.emit(`exit`,e)}),e.on(`terminated`,e=>{this.sessions.delete(e.sessionId);let t={sessionId:e.sessionId,issueId:e.issueId,exitCode:e.exitCode};this.emit(`terminated`,t)})}},Et=class e{auth;logger;userId;workspaceId;token;machineId;machineName;client;static async create(t){let n=new G(t.logger).getAuthInfo();n||(t.logger.error(`Not logged in. Please run "lody login" to log in.`),process.exit(1));let r=n.workspace.id,i=n.token;return new e(t,await He.create(r,n.user.id,i),n)}constructor(e,t,n){this.options=e,this.documentManager=t,this.auth=new G(e.logger),this.logger=e.logger,this.userId=n.user.id,this.workspaceId=n.workspace.id,this.token=n.token,this.machineId=n.machine.machineId,this.machineName=n.machine.machineName,this.client=new ct({managerOptions:{workspaceId:this.workspaceId,token:this.token,logger:this.logger},sessionManagerFactory:n=>e.docker?new Ct(this.logger,this.token,this.machineId,t):new Tt(this.logger,this.token,this.machineId,t,process.cwd()),workspaceDocument:t,handlerConfig:{token:this.token,workspaceId:this.workspaceId,userId:this.userId,machineId:this.machineId,machineName:this.machineName,enableDocker:e.docker||!1,cliVersion:w},logger:this.logger})}async daemon(){await this.client.initialize(),this.documentManager.registerMachine(this.machineId,{id:this.machineId,name:this.machineName,cliVersion:w,os:process.platform,isDocker:this.options.docker,sessions:[]})}async registerAgent(e){await this.client.initialize();let t=await this.documentManager.getOrCreateWorkspaceDoc(this.workspaceId,this.userId);for(let n of e)await t.hasAgentConfig(n,this.machineId)||await t.createAgentConfig(n,this.machineId)}async startNewAISession(e,t){let{sessionManager:n}=await this.client.initialize(),r=await n.createSession({workspaceId:this.workspaceId,userId:this.userId,machineId:this.machineId,cliType:e,title:t.slice(0,50),env:process.env});return await r.agentClient?.connection?.prompt({prompt:[{type:`text`,text:t}],sessionId:r.acpSessionId}),r}async resumeAISession(e){}cleanup=async()=>await this.client.cleanup()};const J=async(e,t,n)=>{let r=D({level:n?`debug`:`info`,transports:`console`,console:{colorize:!0,timestamp:!1,format:`simple`}}),i=new G(r).getAuthInfo();i||(r.warn("You have not logged in, `lody login` first"),process.exit(0));let a=await He.create(i.workspace.id,i.user.id,i.token);await(await new Et({logger:r,docker:!1},a,i).startNewAISession(e,t)).terminate(),process.exit(0)},Y=new t(`start`).description(`Start agent service with or without Docker isolation`).option(`-f, --foreground`,`run in foreground (default is background)`,!1).option(`-d, --docker`,`Use Docker without prompting`).option(`-n, --native`,`Force native mode without Docker (skip prompt)`).option(`--cli-types <types...>`,"Specify CLI types to register, `claude` or `codex`",(e,t)=>t?t.concat(e):[e]).option(`-v, --verbose`,`verbose output`).action(async e=>{let t={transports:`console`,console:{colorize:!0,timestamp:!1,format:`simple`}};e.foreground||(t.transports=`both`,t.file={filename:`logs/start.log`}),e.verbose&&(t.level=`debug`);let n=D(t),i=new G(n),o,s=i.getAuthInfo();s?(n.info(`Found existing authentication, checking validity...`),(await i.validateToken(s.token)).valid?o=s.token:(n.warn(`Existing authentication is invalid, please login again`),process.exit(1))):(n.warn(`No existing authentication found, please login again`),process.exit(1));try{e.server=process.env.LODY_SERVER_URL||`http://localhost:8787`;let t=e.docker??!1,i=process.env.LODY_AGENT_BACKGROUND===`1`;if(i||(e.docker===!0?(n.info(`Docker mode: enabled (via command line)`),t=!0):e.native===!0?(n.info(`Docker mode: disabled (via command line)`),t=!1):(n.info(r.cyan(`
18
+ 🐳 Docker Configuration
19
+ `)),n.info(`Docker provides the following benefits:`),n.info(` • `+r.green(`Environment isolation`)+` - Protects your system from code execution`),n.info(` • `+r.green(`Parallel tasks`)+` - Run multiple AI agents simultaneously`),n.info(` • `+r.green(`Clean workspace`)+` - Each session runs in a fresh environment
20
+ `),t=(await a.prompt([{type:`confirm`,name:`useDocker`,message:`Would you like to use Docker for enhanced isolation and parallel execution?`,default:!0}])).useDocker)),t){let e=await Me();e.installed||(n.error(`Docker is not installed on your system.`),n.warn(r.yellow(`
21
+ `+Ne())),process.exit(1)),e.running||(n.error(e.error||`Docker is not running.`),n.warn(r.yellow(`
22
+ Please start Docker and try again.`)),process.exit(1)),n.info(`Docker detected: v${e.version}`)}e.cliTypes=await(async()=>{if(e.cliTypes&&e.cliTypes.length>0){if(!t){let t={claude:z(),codex:B()},r=e.cliTypes.filter(e=>!t[e]);r.length>0&&(n.error(`CLI types not installed locally: ${r.join(`, `)}. Install them or use Docker mode.`),process.exit(1))}return e.cliTypes}i&&(n.error(`Background process missing CLI types. Please run start command again.`),process.exit(1));let o=z(),s=B();return!t&&!o&&!s&&(n.error(`No supported CLI types found. Please install Claude or Codex CLI tools first.`),process.exit(1)),(await a.prompt([{type:`checkbox`,name:`cliTypes`,message:`Select the CLI types you want to use`,choices:[{name:o&&!t?`Claude ${r.gray(o)}`:`Claude`,value:`claude`,disabled:!t&&!o?`not installed`:!1,checked:!!o},{name:s&&!t?`Codex ${r.gray(s)}`:`Codex`,value:`codex`,disabled:!t&&!s?`not installed`:!1,checked:!!s}],validate:e=>e.length<1?`You must choose at least one CLI type.`:!0}])).cliTypes})();let c=await Dt({...e,docker:t,cliTypes:e.cliTypes},o,s.machine.machineId,n);c.success?(n.success(c.message||`Agent service started successfully!`),e.foreground||process.exit(0)):(n.error(c.message||`Failed to start agent service`),process.exit(1))}catch(e){n.error(`Service error: ${e instanceof Error?e.message:`Unknown error`}`),process.exit(1)}});Y.command(`status`).description(`Check agent service status`).option(`-v, --verbose`,`verbose output with session details`).action(async e=>{let t=D();try{let n=await kt(t,e.verbose);n.success?t.success(n.message||`Status check completed`):(t.error(n.message||`Status check failed`),process.exit(1))}catch(e){t.error(`Status check error: ${e instanceof Error?e.message:`Unknown error`}`),process.exit(1)}}),Y.command(`stop`).description(`Stop agent service`).action(async()=>{let e=D();try{let t=await X(e);t.success?e.success(t.message||`Service stopped successfully`):(e.error(t.message||`Failed to stop service`),process.exit(1))}catch(t){e.error(`Stop service error: ${t instanceof Error?t.message:`Unknown error`}`),process.exit(1)}});async function Dt(e,t,n,r){let a=I();if(a.running)return{success:!1,message:`Service is already running (PID: ${a.info?.pid})`};let o=e.server;if(e.foreground)return await Ot(e,t,n,r);{let n=process.stdout.isTTY,a=n?i({text:`Starting agent service in background...`,stream:process.stdout}).start():null;n||r.info(`Starting agent service in background...`);try{let n=process.argv[1],i=[`start`,`--foreground`,`--verbose`];if(e.docker&&i.push(`--docker`),e.native&&!e.docker&&i.push(`--native`),e.cliTypes&&e.cliTypes.length>0)for(let t of e.cliTypes)i.push(`--cli-types`,t);r.debug(`Starting background process: ${process.execPath} ${n} ${i.join(` `)}`);let{child:s}=je(n,i,r);return r.debug(`Background process started with PID: ${s.pid}`),await new Promise(e=>setTimeout(e,2e3)),s.pid&&!P(s.pid)?(a?a.fail():r.error(`Background process exited unexpectedly`),{success:!1,message:`Background process exited unexpectedly. Check logs for details.`}):(M(s.pid,{token:t,startTime:new Date().toISOString(),dockerMode:e.docker}),a?a.succeed(`Agent service started in background ${e.docker?`(Docker mode)`:`(Native mode)`}`):r.success(`Agent service started successfully (PID: ${s.pid})`),{success:!0,message:`Service started (PID: ${s.pid})`,data:{pid:s.pid,serverUrl:o,dockerMode:e.docker}})}catch(e){throw a?a.fail():r.error(`Failed to start background service: ${e instanceof Error?e.message:`Unknown error`}`),e}}}async function Ot(e,t,n,i){i.info(`Starting agent service ${e.docker?`(Docker mode)`:`(Native mode)`}...`);let a=await Et.create({logger:i,docker:e.docker});try{await a.daemon(),i.debug(`Registering agents: ${e.cliTypes}`),await a.registerAgent(e.cliTypes||[]),i.debug(`Connected to loro server`);let n=!1,o=async()=>{if(n){i.info(`Shutdown already in progress...`);return}n=!0,i.info(`
23
+ Shutting down gracefully...`);try{await a.cleanup()}catch(e){i.error(`Shutdown error: ${e instanceof Error?e.message:`Unknown error`}`)}finally{process.exit(0)}};return process.removeAllListeners(`SIGINT`),process.removeAllListeners(`SIGTERM`),process.on(`SIGINT`,o),process.on(`SIGTERM`,o),De({token:t,dockerMode:e.docker}),i.info(`Ready to execute commands ${e.docker?`in isolated containers`:`on this machine`}`),i.info(`Press ${r.yellow(`Ctrl+C`)} to stop`),await new Promise(()=>{}),{success:!0,message:`Agent service started`,data:{dockerMode:e.docker}}}catch(e){throw await a.cleanup().catch(e=>{i.error(`Cleanup failed: ${e instanceof Error?e.message:`Unknown error`}`)}),e}}async function kt(e,t){let n=I();if(!n.running)return e.info(`Agent service is not running`),{success:!0,message:`Service is not running`};let i=n.info;return e.info(`Service status: ${r.green(`RUNNING`)}`),e.info(`PID: ${r.cyan(i.pid)}`),e.info(`Started: ${r.cyan(i.startTime)}`),i.dockerMode!==void 0&&e.info(`Mode: ${r.cyan(i.dockerMode?`Docker`:`Native`)}`),t&&i.dockerMode&&e.info(`
24
+ Use --verbose with Docker mode to see container details (not implemented yet)`),{success:!0,message:`Service is running`,data:i}}async function X(e){let t=N();if(!t)return{success:!0,message:`No service to stop`};try{return ke(t)?(await new Promise(e=>setTimeout(e,3e3)),F(),{success:!0,message:`Service stopped (PID: ${t})`}):{success:!1,message:`Failed to stop service (PID: ${t})`}}catch(e){throw e}}const At=new t(`login`).description(`Login to Lody using device authorization flow`).option(`-v, --verbose`,`verbose output`).action(async e=>{let t=D({level:e.verbose?`debug`:`info`,transports:`console`,console:{colorize:!0,timestamp:!1,format:`simple`}}),n=new G(t);try{let e=n.getAuthInfo();if(e){t.info(`Found existing authentication, checking validity...`);let i=await n.validateToken(e.token);if(i.valid&&i.user){t.success(`
25
+ `+r.green(`✓`)+` You are already logged in!`),t.info(` User: `+r.cyan(i.user.name||i.user.email)),t.info(` Email: `+r.cyan(i.user.email)),t.info(` Workspace: `+r.cyan(e.workspace.name));return}else t.info(`Existing authentication is invalid, proceeding with new login...`)}t.info(`Initializing device authorization...`);let i=await n.login(f.hostname());if(i.success){t.success(`
26
+ `+r.green(`✓`)+` Successfully logged in!`),t.info(` User: `+r.cyan(i.user.name||i.user.email)),t.info(` Email: `+r.cyan(i.user.email)),t.info(` Workspace: `+r.cyan(i.workspace.name));let e=(await a.prompt([{type:`input`,name:`machineName`,message:`Enter a name for this machine:`,default:f.hostname(),validate:e=>e.trim().length===0?`Machine name cannot be empty`:!0}])).machineName;t.info(` Machine Name: `+r.cyan(e)),Re(i.token,i.user,i.workspace,{machineName:e,machineId:i.machine.machineId})}else t.error(`Login failed: ${i.error}`),process.exit(1)}catch(e){t.error(`Login failed: ${e instanceof Error?e.stack:`Unknown error`}`),process.exit(1)}}),jt=new t(`logout`).description(`Logout from Lody and clear saved authentication`).option(`-v, --verbose`,`verbose output`).action(async e=>{let t=D({level:e.verbose?`debug`:`info`,transports:`console`,console:{colorize:!0,timestamp:!1,format:`simple`}}),n=new G(t).logout();if(await X(t),n.success||(t.error(`Logout failed: ${n.error}`),process.exit(1)),!n.user){t.info(`You are not logged in.`);return}t.info(`Logging out user: ${r.cyan(n.user.email)}`),t.success(`
27
+ `+r.green(`✓`)+` Successfully logged out!`)}),Z=(e,t=[])=>(t.push(e),t),Mt=e=>(e.argument(`[prompt]`,`Initial instructions for the agent. Use "-" or omit to read from stdin`).passThroughOptions().option(`-c, --config <key=value>`,`Override a configuration value loaded from ~/.codex/config.toml`,Z,[]).option(`-i, --image <file>`,`Attach image(s) to the initial prompt`,Z,[]).option(`-m, --model <model>`,`Model the agent should use`).option(`--oss`,`Use the OSS runtime`).option(`-s, --sandbox <sandbox_mode>`,`Sandbox policy to use when executing shell commands`,e=>{if(![`read-only`,`workspace-write`,`danger-full-access`].includes(e))throw new n(`Sandbox mode must be one of read-only, workspace-write, danger-full-access`);return e}).option(`-p, --profile <config_profile>`,`Configuration profile from config.toml`).option(`--full-auto`,`Enable low-friction sandboxed automatic execution`).option(`--dangerously-bypass-approvals-and-sandbox`,`Skip all confirmation prompts and execute commands without sandboxing`).option(`-C, --cd <dir>`,`Change to the specified directory before running Codex`).option(`--skip-git-repo-check`,`Allow running Codex outside a Git repository`).option(`--output-schema <file>`,`Path to a JSON Schema file describing the model's final response shape`).option(`--color <color>`,`Color setting for output`,e=>{if(![`always`,`never`,`auto`].includes(e))throw new n(`Color must be one of always, never, auto`);return e}).option(`--include-plan-tool`,`Include the plan tool in the conversation`).option(`-o, --output-last-message <file>`,`File where the last message from the agent should be written`).action(async(e,t,n)=>{await J(`codex`,e)}),e),Nt=e=>(e.argument(`[session_id]`,`Conversation or session id. Combine with --last to resume the most recent session when omitted`).argument(`[prompt]`,`Prompt to send after resuming the session. Use "-" to read from stdin`).option(`-c, --config <key=value>`,`Override configuration values loaded from ~/.codex/config.toml`,Z,[]).option(`--last`,`Resume the most recent recorded session without specifying an id`).action(async(e,t,n)=>{await J(`codex`,t)}),e),Pt=Mt(new t(`codex`).description(`Start a codex session`).enablePositionalOptions());Nt(Pt.command(`resume`).description(`Resume a Codex session`));const Ft=(e,t=[])=>(e.split(`,`).map(e=>e.trim()).filter(Boolean).forEach(e=>{t.push(e)}),t),It=(e,t=[])=>(t.push(e),t),Q=(e,t,r)=>{if(!t.includes(e))throw new n(`${r} must be one of ${t.map(e=>`"${e}"`).join(`, `)}`);return e},Lt=new t(`claude`).description(`Start a claude session`).argument(`[prompt]`,`Your prompt`).option(`-d, --debug`,`Enable debug mode`).option(`--verbose`,`Override verbose mode setting from config`).option(`-p, --print`,`Print response and exit (useful for pipes)`).option(`--output-format <format>`,`Output format (requires --print)`,e=>Q(e,[`text`,`json`,`stream-json`],`Output format`)).option(`--input-format <format>`,`Input format (requires --print)`,e=>Q(e,[`text`,`stream-json`],`Input format`)).option(`--mcp-debug`,`[DEPRECATED] Enable MCP debug mode`).option(`--dangerously-skip-permissions`,`Bypass all permission checks. Recommended only for isolated sandboxes`).option(`--allowedTools <tools...>`,`Comma or space-separated list of tool names to allow`,Ft,[]).option(`--disallowedTools <tools...>`,`Comma or space-separated list of tool names to deny`,Ft,[]).option(`--mcp-config <configs...>`,`Load MCP servers from JSON files or strings (space-separated)`,It,[]).option(`--append-system-prompt <prompt>`,`Append a system prompt to the default system prompt`).option(`--permission-mode <mode>`,`Permission mode to use for the session`,e=>Q(e,[`acceptEdits`,`bypassPermissions`,`default`,`plan`],`Permission mode`)).option(`-c, --continue`,`Continue the most recent conversation`).option(`-r, --resume [sessionId]`,`Resume a conversation by id or select interactively when omitted`).option(`--model <model>`,`Model for the current session (alias like 'sonnet' or fully-qualified name)`).option(`--fallback-model <model>`,`Enable automatic fallback to specified model when default model is overloaded`).option(`--settings <file-or-json>`,`Load additional settings from a JSON file or string`).option(`--add-dir <directories...>`,`Additional directories to allow tool access to`,It,[]).option(`--strict-mcp-config`,`Only use MCP servers from --mcp-config, ignoring all other MCP configurations`).option(`--session-id <uuid>`,`Use a specific session ID for the conversation`).action(async function(e){this.optsWithGlobals(),await J(`claude`,e)});function Rt(){let e=process.env.NODE_ENV||`development`,t=process.cwd(),n;n=e===`production`?`.env`:`.env.dev`,Se({path:c.join(t,n)}),Pe()}Rt();const $=new t;$.name(`lody`).description(`Lody Agent CLI tool for managing remote command execution`).version(w,`-v, --version`,`display version number`).enablePositionalOptions(),$.addCommand(At),$.addCommand(jt),$.addCommand(Y),$.addCommand(Pt),$.addCommand(Lt),$.configureHelp({sortSubcommands:!0,subcommandTerm:e=>e.name()+` `+e.usage()}),process.on(`uncaughtException`,e=>{console.error(r.red(`Uncaught Exception:`),e.message),process.exit(1)}),process.on(`unhandledRejection`,(e,t)=>{console.error(r.red(`Unhandled Rejection at:`),t,`reason:`,e),process.exit(1)});try{$.parse(process.argv),process.argv.slice(2).length||($.outputHelp(),process.exit(0))}catch(e){e instanceof Error&&e.name===`CommanderError`||console.error(r.red(`CLI Error:`),e instanceof Error?e.message:`Unknown error`),process.exit(1)}export{};
package/package.json CHANGED
@@ -1,11 +1,18 @@
1
1
  {
2
2
  "name": "lody",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "Lody Agent CLI tool for managing remote command execution",
5
5
  "type": "module",
6
- "main": "dist/index.js",
6
+ "main": "dist/index.cjs",
7
+ "module": "dist/index.mjs",
8
+ "exports": {
9
+ ".": {
10
+ "import": "./dist/index.mjs",
11
+ "require": "./dist/index.cjs"
12
+ }
13
+ },
7
14
  "bin": {
8
- "lody": "dist/index.js"
15
+ "lody": "dist/index.cjs"
9
16
  },
10
17
  "keywords": [
11
18
  "cli",
@@ -19,7 +26,7 @@
19
26
  "engines": {
20
27
  "node": ">=18.0.0"
21
28
  },
22
- "devDependencies": {
29
+ "dependencies": {
23
30
  "@agentclientprotocol/sdk": "^0.5.1",
24
31
  "@modelcontextprotocol/sdk": "^1.22.0",
25
32
  "@types/dockerode": "^3.3.47",
@@ -37,7 +44,6 @@
37
44
  "convex": "1.25.4",
38
45
  "dockerode": "^4.0.9",
39
46
  "dotenv": "^16.6.1",
40
- "esbuild": "^0.24.2",
41
47
  "eventemitter3": "^5.0.1",
42
48
  "express": "^4.21.2",
43
49
  "inquirer": "^10.2.2",
@@ -53,28 +59,25 @@
53
59
  "rimraf": "^6.1.2",
54
60
  "ts-jest": "^29.4.5",
55
61
  "ts-node": "^10.9.2",
62
+ "tsdown": "^0.14.2",
56
63
  "tsx": "^4.20.6",
57
64
  "typescript": "^5.9.3",
58
65
  "uuid": "^13.0.0",
59
66
  "winston": "^3.18.3",
60
67
  "winston-daily-rotate-file": "^5.0.0",
61
68
  "ws": "^8.18.3",
62
- "@lody/shared": "0.0.1",
63
- "@lody/convex": "0.0.1"
69
+ "throttle-debounce": "^5.0.2"
64
70
  },
65
71
  "files": [
66
72
  "dist",
67
73
  "README.md",
68
74
  "LICENSE"
69
75
  ],
70
- "dependencies": {
71
- "throttle-debounce": "^5.0.2"
72
- },
73
76
  "scripts": {
74
77
  "dev": "tsx src/index.ts",
75
78
  "build": "pnpm run clean && pnpm run typecheck && pnpm run build:bundle && pnpm run copy:wasm",
76
79
  "build:watch": "pnpm run build:bundle -- --watch",
77
- "build:bundle": "esbuild src/index.ts --bundle --platform=node --target=node18 --format=esm --outfile=dist/index.js --minify --legal-comments=none --external:cpu-features --external:*.node",
80
+ "build:bundle": "tsdown",
78
81
  "copy:wasm": "node scripts/copy-loro-wasm.js",
79
82
  "clean": "rimraf dist",
80
83
  "format": "prettier --write src/**/*.ts",