@oxgeneral/orch 0.2.3 → 0.3.0
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/App-TW35IULR.js +18 -0
- package/dist/agent-FRQKL7YI.js +9 -0
- package/dist/{orchestrator-VGYKSOZJ.js → chunk-2UC4SVJB.js} +236 -71
- package/dist/chunk-2UC4SVJB.js.map +1 -0
- package/dist/chunk-5AJ4LYO5.js +8 -0
- package/dist/{chunk-45K2XID7.js → chunk-6DWHQPTE.js} +2 -1
- package/dist/chunk-6DWHQPTE.js.map +1 -0
- package/dist/{chunk-POUC4CPC.js → chunk-6MJ7V6VY.js} +2 -2
- package/dist/{chunk-HNKJ4IF7.js → chunk-B4JQM4NU.js} +34 -10
- package/dist/chunk-B4JQM4NU.js.map +1 -0
- package/dist/{chunk-6HENRUYZ.js → chunk-CDFA4IIQ.js} +2 -2
- package/dist/chunk-CHRW4CLD.js +2 -0
- package/dist/{chunk-ZU6AY2VU.js → chunk-GZ2Q56YZ.js} +2 -2
- package/dist/chunk-HMMPM7MF.js +3 -0
- package/dist/{chunk-AELEEEV3.js → chunk-HSBYJ5C5.js} +27 -7
- package/dist/chunk-HXOMNULD.js +2 -0
- package/dist/{chunk-O5AO5QIR.js → chunk-IQXRQBUK.js} +9 -2
- package/dist/chunk-IQXRQBUK.js.map +1 -0
- package/dist/chunk-L26TK7Y5.js +2 -0
- package/dist/chunk-L3FYR45M.js +2 -0
- package/dist/chunk-LXNRCJ22.js +2 -0
- package/dist/{chunk-TX7WOFCW.js → chunk-MGFMVPRD.js} +4 -7
- package/dist/chunk-MGFMVPRD.js.map +1 -0
- package/dist/chunk-MNXU3KCD.js +2 -0
- package/dist/{chunk-CHIP7O6V.js → chunk-O2MSGW3V.js} +3 -1
- package/dist/chunk-O2MSGW3V.js.map +1 -0
- package/dist/chunk-PJ5DKXGR.js +2 -0
- package/dist/{chunk-VTA74YWX.js → chunk-QEEM67OA.js} +11 -17
- package/dist/chunk-QEEM67OA.js.map +1 -0
- package/dist/chunk-UMZEA3JT.js +5 -0
- package/dist/{shell-OGTSH4RJ.js → chunk-UW6GUUE6.js} +3 -3
- package/dist/chunk-XDVMX2FO.js +8 -0
- package/dist/chunk-XDVMX2FO.js.map +1 -0
- package/dist/chunk-ZA5Z33GO.js +11 -0
- package/dist/claude-E36EGXUV.js +2 -0
- package/dist/{chunk-IRN2U2NE.js → claude-RIB3RQS5.js} +5 -2
- package/dist/claude-RIB3RQS5.js.map +1 -0
- package/dist/cli.js +1 -199
- package/dist/clipboard-service-PDTSZIR5.js +25 -0
- package/dist/codex-OTZKVESD.js +2 -0
- package/dist/{codex-U7LTJTX6.js → codex-VBUSA2GJ.js} +5 -3
- package/dist/codex-VBUSA2GJ.js.map +1 -0
- package/dist/config-CCSS2P7R.js +2 -0
- package/dist/container-OIXLFSX2.js +6 -0
- package/dist/context-GSMQHQES.js +7 -0
- package/dist/cursor-3DJA6LWS.js +2 -0
- package/dist/{cursor-3DI5GKRF.js → cursor-4QIOTDBW.js} +5 -3
- package/dist/cursor-4QIOTDBW.js.map +1 -0
- package/dist/doctor-KBK5JZBZ.js +2 -0
- package/dist/doctor-service-F2SXDWHS.js +91 -0
- package/dist/doctor-service-F2SXDWHS.js.map +1 -0
- package/dist/doctor-service-PB7YBH3F.js +2 -0
- package/dist/goal-RFKFPR7M.js +8 -0
- package/dist/index.d.ts +124 -46
- package/dist/index.js +1817 -5
- package/dist/index.js.map +1 -1
- package/dist/init-WRDFAFS2.js +53 -0
- package/dist/logs-5QHJWMEG.js +12 -0
- package/dist/msg-4SCLBO4K.js +9 -0
- package/dist/orchestrator-FGGXK3N3.js +5 -0
- package/dist/{orchestrator-TAFBYQQ5.js.map → orchestrator-FGGXK3N3.js.map} +1 -1
- package/dist/orchestrator-R7IWZUT6.js +13 -0
- package/dist/process-manager-33H27MQF.js +2 -0
- package/dist/process-manager-A36Y7LHP.js +3 -0
- package/dist/{process-manager-TLZOTO4Y.js.map → process-manager-A36Y7LHP.js.map} +1 -1
- package/dist/registry-BO2PPRNG.js +2 -0
- package/dist/registry-JXXRLJ5J.js +3 -0
- package/dist/{registry-UQAHK77P.js.map → registry-JXXRLJ5J.js.map} +1 -1
- package/dist/run-HSHRELOP.js +3 -0
- package/dist/shell-EOJBDWTH.js +2 -0
- package/dist/{chunk-CIIE6LNG.js → shell-IH2MMTVP.js} +3 -2
- package/dist/shell-IH2MMTVP.js.map +1 -0
- package/dist/status-DLBNWSWM.js +2 -0
- package/dist/task-J6ZN7ALI.js +20 -0
- package/dist/team-MSIBKOQC.js +4 -0
- package/dist/template-engine-MFL5B677.js +3 -0
- package/dist/{template-engine-322SCRR6.js.map → template-engine-MFL5B677.js.map} +1 -1
- package/dist/template-engine-ONIDVD4F.js +2 -0
- package/dist/tui-G4XUFAIP.js +2 -0
- package/dist/update-PC2ENCKU.js +2 -0
- package/dist/update-check-HGMBDYHL.js +2 -0
- package/dist/workspace-manager-KOOYTO7E.js +3 -0
- package/dist/{workspace-manager-47KI7B27.js → workspace-manager-T6AXG7XL.js} +40 -3
- package/dist/workspace-manager-T6AXG7XL.js.map +1 -0
- package/package.json +2 -1
- package/readme.md +5 -4
- package/scripts/benchmark.ts +304 -0
- package/dist/App-KDZSTAMR.js +0 -4864
- package/dist/agent-V5M2C3OC.js +0 -157
- package/dist/chunk-2B32FPEB.js +0 -11
- package/dist/chunk-2B32FPEB.js.map +0 -1
- package/dist/chunk-33QNTNR6.js +0 -46
- package/dist/chunk-6GFVB6EK.js +0 -101
- package/dist/chunk-6HENRUYZ.js.map +0 -1
- package/dist/chunk-AELEEEV3.js.map +0 -1
- package/dist/chunk-E3TCKHU6.js +0 -13
- package/dist/chunk-E3TCKHU6.js.map +0 -1
- package/dist/chunk-ED47GL3F.js +0 -29
- package/dist/chunk-HXYAZGLP.js +0 -15
- package/dist/chunk-I5WEMARW.js +0 -166
- package/dist/chunk-IZYSGYXG.js +0 -2
- package/dist/chunk-IZYSGYXG.js.map +0 -1
- package/dist/chunk-P6ATSXGL.js +0 -107
- package/dist/chunk-PBFE5V3G.js +0 -2
- package/dist/chunk-PBFE5V3G.js.map +0 -1
- package/dist/chunk-PNE6LQRF.js +0 -5
- package/dist/chunk-POUC4CPC.js.map +0 -1
- package/dist/chunk-XI4TU6VU.js +0 -50
- package/dist/chunk-ZU6AY2VU.js.map +0 -1
- package/dist/claude-GH6P2DC5.js +0 -4
- package/dist/claude-S47YTIHU.js +0 -2
- package/dist/claude-S47YTIHU.js.map +0 -1
- package/dist/codex-2CH57B7G.js +0 -2
- package/dist/codex-2CH57B7G.js.map +0 -1
- package/dist/config-LJFM55LN.js +0 -75
- package/dist/container-JV7TAUP5.js +0 -1532
- package/dist/context-EPSDCJTU.js +0 -83
- package/dist/cursor-QFUNKPCQ.js +0 -2
- package/dist/cursor-QFUNKPCQ.js.map +0 -1
- package/dist/doctor-IO4PV4D6.js +0 -67
- package/dist/doctor-service-A34DHPKI.js +0 -2
- package/dist/doctor-service-NTWBWOM2.js +0 -2
- package/dist/doctor-service-NTWBWOM2.js.map +0 -1
- package/dist/goal-I56QP7HS.js +0 -110
- package/dist/init-BE5VKWOM.js +0 -149
- package/dist/logs-IAUAS5TX.js +0 -207
- package/dist/msg-SQWQLJP6.js +0 -95
- package/dist/orchestrator-TAFBYQQ5.js +0 -2
- package/dist/process-manager-HUVNAPQV.js +0 -2
- package/dist/process-manager-TLZOTO4Y.js +0 -2
- package/dist/registry-PQWRVNF2.js +0 -2
- package/dist/registry-UQAHK77P.js +0 -2
- package/dist/run-PSZURVVL.js +0 -95
- package/dist/shell-5ZNXFGXV.js +0 -3
- package/dist/shell-OGTSH4RJ.js.map +0 -1
- package/dist/status-DTF7D3DV.js +0 -56
- package/dist/task-5OJTXW27.js +0 -209
- package/dist/team-AISPLEJB.js +0 -97
- package/dist/template-engine-322SCRR6.js +0 -2
- package/dist/template-engine-3CDRZNMJ.js +0 -3
- package/dist/tui-XDJE3IUA.js +0 -225
- package/dist/update-72GZMF65.js +0 -64
- package/dist/update-check-4RV7Z6WT.js +0 -2
- package/dist/workspace-manager-7M46ESUL.js +0 -2
- package/dist/workspace-manager-7M46ESUL.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,6 +1,1818 @@
|
|
|
1
|
-
import {k,a as a$1,b as b$2,h,j as j$2,d,c as c$1,e,f as f$1,g as g$1,i as i$1}from'./chunk-2B32FPEB.js';import {a,b as b$1}from'./chunk-E3TCKHU6.js';export{f as Orchestrator,a as canTransition,d as isBlocked,c as isDispatchable,b as isTerminal,e as resolveFailureStatus}from'./chunk-E3TCKHU6.js';import {c,f,i,g,k as k$1,j as j$1}from'./chunk-ZU6AY2VU.js';export{g as AgentNotFoundError,b as NotInitializedError,a as OrchestryError,f as TaskNotFoundError}from'./chunk-ZU6AY2VU.js';import {a as a$2}from'./chunk-AELEEEV3.js';export{a as AdapterRegistry}from'./chunk-6HENRUYZ.js';export{a as createTokenUsage}from'./chunk-PBFE5V3G.js';import {b as b$3}from'./chunk-POUC4CPC.js';import {nanoid}from'nanoid';import Rt,{mkdir}from'fs/promises';import {createReadStream}from'fs';import ct from'path';import {homedir}from'os';var P=class{handlers=new Map;wildcardHandlers=new Set;maxListeners=10;warnedTypes=new Set;setMaxListeners(t){this.maxListeners=t;}getMaxListeners(){return this.maxListeners}listenerCount(t){return this.handlers.get(t)?.size??0}on(t,e){this.handlers.has(t)||this.handlers.set(t,new Set);let s=this.handlers.get(t);return s.add(e),this.maxListeners>0&&s.size>this.maxListeners&&!this.warnedTypes.has(t)&&(this.warnedTypes.add(t),console.warn(`EventBus: possible memory leak detected. ${s.size} listeners added for "${t}". Use setMaxListeners() to increase limit if this is intentional.`)),()=>this.off(t,e)}once(t,e){let s=r=>{this.off(t,s),e(r);};return this.on(t,s)}off(t,e){this.handlers.get(t)?.delete(e);}emit(t){let e=this.handlers.get(t.type);e&&this.dispatchToSet(e,t,"handler"),this.dispatchToSet(this.wildcardHandlers,t,"wildcard handler");}dispatchToSet(t,e,s){for(let r of t)try{r(e);}catch(a){console.error(`EventBus ${s} error for "${e.type}":`,a);}}onAny(t){return this.wildcardHandlers.add(t),this.maxListeners>0&&this.wildcardHandlers.size>this.maxListeners&&!this.warnedTypes.has("*")&&(this.warnedTypes.add("*"),console.warn(`EventBus: possible memory leak detected. ${this.wildcardHandlers.size} wildcard listeners added. Use setMaxListeners() to increase limit if this is intentional.`)),()=>{this.wildcardHandlers.delete(t);}}clear(){this.handlers.clear(),this.wildcardHandlers.clear(),this.warnedTypes.clear();}};var b=class{constructor(t,e,s){this.taskStore=t;this.eventBus=e;this.config=s;}async create(t){if(!t.title.trim())throw new c("Task title is required");let e=t.priority??this.config.defaults.task.priority;if(!Number.isInteger(e)||e<1||e>4)throw new c("Priority must be an integer between 1 and 4");if(t.depends_on?.length){let n=(await Promise.all(t.depends_on.map(async i=>({depId:i,exists:!!await this.taskStore.get(i)})))).filter(i=>!i.exists).map(i=>i.depId);if(n.length>0)throw new c(`Unknown depends_on task ID(s): ${n.join(", ")}`)}let s=new Date().toISOString(),r={id:`tsk_${nanoid(7)}`,title:t.title.trim(),description:t.description?.trim()??"",status:"todo",priority:e,assignee:t.assignee,labels:t.labels??[],depends_on:t.depends_on??[],created_at:s,updated_at:s,attempts:0,max_attempts:t.max_attempts??this.config.defaults.task.max_attempts,workspace_mode:t.workspace_mode,review_criteria:t.review_criteria,scope:t.scope};return await this.taskStore.save(r),this.eventBus.emit({type:"task:created",task:r}),r}async list(t){return this.taskStore.list(t)}async get(t){let e=await this.taskStore.get(t);if(!e)throw new f(t);return e}async updateStatus(t,e){let s=await this.get(t),r=s.status;if(!a(r,e))throw new i(t,r,e);return s.status=e,s.updated_at=new Date().toISOString(),await this.taskStore.save(s),this.eventBus.emit({type:"task:status_changed",taskId:t,from:r,to:e}),s}async assign(t,e){let s=await this.get(t);return s.assignee=e,s.updated_at=new Date().toISOString(),await this.taskStore.save(s),this.eventBus.emit({type:"task:assigned",taskId:t,agentId:e}),s}async cancel(t){let e=await this.get(t);if(b$1(e.status))throw new i(t,e.status,"cancelled");return this.updateStatus(t,"cancelled")}async retry(t){let e=await this.get(t);if(e.status!=="failed"&&e.status!=="cancelled")throw new i(t,e.status,"todo");let s=e.status;return e.status="todo",e.attempts=0,e.updated_at=new Date().toISOString(),await this.taskStore.save(e),this.eventBus.emit({type:"task:status_changed",taskId:t,from:s,to:"todo"}),e}async reject(t,e){let s=await this.get(t);if(s.status!=="review")throw new i(t,s.status,"todo");let r=s.status;return s.status="todo",s.attempts=0,s.feedback=e,s.updated_at=new Date().toISOString(),await this.taskStore.save(s),this.eventBus.emit({type:"task:status_changed",taskId:t,from:r,to:"todo"}),s}async update(t,e){let s=await this.get(t);if(e.title!==void 0){if(!e.title.trim())throw new c("Task title cannot be empty");s.title=e.title.trim();}if(e.description!==void 0&&(s.description=e.description.trim()),e.priority!==void 0){if(!Number.isInteger(e.priority)||e.priority<1||e.priority>4)throw new c("Priority must be an integer between 1 and 4");s.priority=e.priority;}return e.labels!==void 0&&(s.labels=e.labels),s.updated_at=new Date().toISOString(),await this.taskStore.save(s),s}async delete(t){if((await this.get(t)).status==="in_progress")throw new c("Cannot delete a running task. Cancel it first.");await this.taskStore.delete(t);}async incrementAttempts(t){let e=await this.get(t);return e.attempts+=1,e.updated_at=new Date().toISOString(),await this.taskStore.save(e),e}};var A=class{constructor(t,e,s,r){this.agentStore=t;this.stateStore=e;this.eventBus=s;this.config=r;}async create(t){if(!t.name.trim())throw new c("Agent name is required");if(await this.agentStore.getByName(t.name))throw new c(`Agent "${t.name}" already exists`);let s={id:`agt_${nanoid(7)}`,name:t.name.trim(),adapter:t.adapter||this.config.defaults.agent.adapter,role:t.role,config:{command:t.command,model:t.model,approval_policy:t.approval_policy??this.config.defaults.agent.approval_policy,max_turns:t.max_turns??this.config.defaults.agent.max_turns,timeout_ms:t.timeout_ms??this.config.defaults.agent.timeout_ms,stall_timeout_ms:t.stall_timeout_ms??this.config.defaults.agent.stall_timeout_ms,env:t.env,system_prompt:t.system_prompt,workspace_mode:t.workspace_mode,skills:t.skills},status:"idle",stats:{tasks_completed:0,tasks_failed:0,total_runs:0,total_runtime_ms:0}};return await this.agentStore.save(s),s}async list(){return this.agentStore.list()}async get(t){let e=await this.agentStore.get(t);if(!e)throw new g(t);return e}async remove(t){let e=await this.get(t);if(e.status==="running"){let s=await this.stateStore.read();if(Object.values(s.running).some(a=>a.agent_id===t))throw new c("Cannot remove a running agent. Stop it first.");e.status="idle",await this.agentStore.save(e);}await this.agentStore.delete(t);}async update(t,e){let s=await this.get(t);if(e.name!==void 0){if(!e.name.trim())throw new c("Agent name cannot be empty");let r=await this.agentStore.getByName(e.name.trim());if(r&&r.id!==t)throw new c(`Agent "${e.name}" already exists`);s.name=e.name.trim();}return e.role!==void 0&&(s.role=e.role||void 0),e.model!==void 0&&(s.config.model=e.model||void 0),e.approval_policy!==void 0&&(s.config.approval_policy=e.approval_policy),await this.agentStore.save(s),s}async disable(t){return this.setStatus(t,"disabled")}async enable(t){return this.setStatus(t,"idle")}async setAutonomous(t,e){let s=await this.get(t);return s.autonomous=e,await this.agentStore.save(s),this.eventBus.emit({type:"agent:autonomous_toggled",agentId:t,autonomous:e}),s}async setStatus(t,e){let s=await this.get(t);return s.status=e,await this.agentStore.save(s),s}async updateStats(t,e){let s=await this.get(t);return Object.assign(s.stats,e),await this.agentStore.save(s),s}async findBestAgent(t){let e=await this.agentStore.list(),s=e.filter(a=>a.status==="idle");if(s.length===0)return null;if(t.assignee){let a=e.find(n=>n.id===t.assignee);return a&&a.status==="idle"?a:null}let r=s.map(a=>{let n=0;if(t.labels?.length>0&&a.config.skills?.length)for(let o of t.labels){let c=o.toLowerCase();a.config.skills.some(d=>d.toLowerCase()===c)&&(n+=50);}if(t.labels?.length>0&&a.role){let o=a.role.toLowerCase();t.labels.some(c=>o.includes(c.toLowerCase()))&&(n+=30);}a.status==="idle"&&(n+=20);let i=a.stats.tasks_completed+a.stats.tasks_failed;return i>0&&(n+=Math.round(a.stats.tasks_completed/i*10)),{agent:a,score:n}});return r.sort((a,n)=>n.score-a.score),r[0]?.agent??null}};var x=class{constructor(t,e){this.runStore=t;this.eventBus=e;}async create(t){let e={id:`run_${nanoid(7)}`,task_id:t.taskId,agent_id:t.agentId,attempt:t.attempt,status:"preparing",started_at:new Date().toISOString(),workspace_path:t.workspacePath,prompt:t.prompt};return await this.runStore.save(e),e}async get(t){return this.runStore.get(t)}async start(t,e){let s=await this.runStore.get(t);if(!s)throw new Error(`Run not found: ${t}`);return s.status="running",s.pid=e,await this.runStore.save(s),this.eventBus.emit({type:"agent:started",agentId:s.agent_id,taskId:s.task_id,runId:t}),s}async finish(t,e,s,r){let a=await this.runStore.get(t);if(!a)throw new Error(`Run not found: ${t}`);return a.status=e,a.finished_at=new Date().toISOString(),a.tokens=s,a.error=r,await this.runStore.save(a),this.eventBus.emit({type:"agent:completed",runId:t,agentId:a.agent_id,success:e==="succeeded"}),a}async appendEvent(t,e){await this.runStore.appendEvent(t,e);}async listAll(){return this.runStore.listAll()}async listForTask(t){return this.runStore.listForTask(t)}async listForAgent(t){return this.runStore.listForAgent(t)}async readEvents(t){return this.runStore.readEvents(t)}async readEventsTail(t,e){return this.runStore.readEventsTail(t,e)}async getLastFailedRunContext(t,e=50){let r=(await this.runStore.listForTask(t)).filter(i=>i.status==="failed").sort((i,o)=>(o.finished_at??"").localeCompare(i.finished_at??""))[0];if(!r)return null;let a=r.error??"Unknown error",n="";try{n=(await this.runStore.readEventsTail(r.id,e*2)).filter(c=>c.type==="agent_output"||c.type==="error").map(c=>typeof c.data=="string"?c.data:JSON.stringify(c.data)).join(`
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
`);}}}finally{a.destroy();}}async listFiltered(t){await h(this.paths.runsDir);let e=await j$2(this.paths.runsDir,".json"),s=64,r=[];for(let a=0;a<e.length;a+=s){let n=e.slice(a,a+s),i=await Promise.all(n.map(o=>{let c=o.endsWith(".json")?o.slice(0,-5):o;return c$1(this.paths.runPath(c))}));for(let o of i)o!==null&&t(o)&&r.push(o);}return r.sort((a,n)=>new Date(n.started_at).getTime()-new Date(a.started_at).getTime())}};var W={version:1,running:{},claimed:[],retry_queue:[],stats:{total_runs:0,total_tasks_completed:0,total_tasks_failed:0,total_tokens:{input:0,output:0,total:0},total_runtime_ms:0}};var R=class{constructor(t){this.paths=t;}async read(){let t=await c$1(this.paths.statePath);if(!t)return structuredClone(W);let e=structuredClone(W);return {version:t.version??e.version,pid:t.pid,started_at:t.started_at,running:t.running&&typeof t.running=="object"?t.running:e.running,claimed:Array.isArray(t.claimed)?t.claimed:e.claimed,retry_queue:Array.isArray(t.retry_queue)?t.retry_queue:e.retry_queue,stats:{total_runs:t.stats?.total_runs??e.stats.total_runs,total_tasks_completed:t.stats?.total_tasks_completed??e.stats.total_tasks_completed,total_tasks_failed:t.stats?.total_tasks_failed??e.stats.total_tasks_failed,total_tokens:t.stats?.total_tokens??e.stats.total_tokens,total_runtime_ms:t.stats?.total_runtime_ms??e.stats.total_runtime_ms}}}async write(t){await d(this.paths.statePath,t);}};var ot={project:{name:"my-project"},defaults:{agent:{adapter:"claude",approval_policy:"auto",max_turns:50,timeout_ms:36e5,stall_timeout_ms:3e5,workspace_mode:"worktree"},task:{max_attempts:3,priority:3}},scheduling:{poll_interval_ms:3e4,max_concurrent_agents:6,retry_base_delay_ms:1e4,retry_max_delay_ms:3e5}};var O=class{constructor(t){this.paths=t;}async read(){let t=await a$1(this.paths.configPath);return mt(ot,t??{})}async write(t){await b$2(this.paths.configPath,t);}async get(t){let e=await this.read();return Mt(e,t)}async set(t,e){let s=await this.read();Gt(s,t,e),await this.write(s);}};function Mt(m,t){let e=t.split("."),s=m;for(let r of e){if(s==null||typeof s!="object")return;s=s[r];}return s}function Gt(m,t,e){let s=t.split("."),r=m;for(let n=0;n<s.length-1;n++){let i=s[n];(typeof r[i]!="object"||r[i]===null)&&(r[i]={}),r=r[i];}let a=s[s.length-1];r[a]=e;}function mt(m,t){let e={...m};for(let s of Object.keys(t)){let r=t[s],a=e[s];r!=null&&typeof r=="object"&&!Array.isArray(r)&&typeof a=="object"&&a!==null&&!Array.isArray(a)?e[s]=mt(a,r):e[s]=r;}return e}var z={tui:{activity_filter:"all"}};var dt=ct.join(homedir(),".orchestry"),lt=ct.join(dt,"global.yml"),D=class{async read(){let t=await a$1(lt);return t?{tui:{activity_filter:t.tui?.activity_filter??z.tui.activity_filter}}:{...z}}async write(t){await mkdir(dt,{recursive:true}),await b$2(lt,t);}async set(t,e){let s=await this.read();s.tui[t]=e,await this.write(s);}};var M=class m{constructor(t){this.paths=t;}async get(t){let e=await c$1(this.paths.contextPath(t));return e?gt(e)?(await this.delete(t),null):e:null}static MAX_TTL_MS=720*60*60*1e3;async set(t,e,s){if(s!==void 0&&(!Number.isFinite(s)||s<=0||s>m.MAX_TTL_MS))throw new Error(`TTL must be a positive number up to ${m.MAX_TTL_MS}ms (30 days)`);await h(this.paths.contextDir);let r=new Date().toISOString(),a=await c$1(this.paths.contextPath(t)),n={key:t,value:e,created_at:a?.created_at??r,updated_at:r,ttl_ms:s,expires_at:s?new Date(Date.now()+s).toISOString():void 0};await d(this.paths.contextPath(t),n);}async delete(t){try{await Rt.unlink(this.paths.contextPath(t));}catch(e){if(e.code!=="ENOENT")throw e}}async list(){await h(this.paths.contextDir);let t=await j$2(this.paths.contextDir,".json"),e=await Promise.all(t.map(r=>{let a=r.replace(".json","");return c$1(this.paths.contextPath(a))})),s=[];for(let r of e)if(r){if(gt(r)){await this.delete(r.key);continue}s.push(r);}return s.sort((r,a)=>r.key.localeCompare(a.key))}async getAll(){let t=await this.list(),e={};for(let s of t)e[s.key]=s.value;return e}};function gt(m){return m.expires_at?new Date(m.expires_at).getTime()<Date.now():false}var G=class{constructor(t){this.paths=t;}async save(t){await h(this.paths.messagesDir),await d(this.paths.messagePath(t.id),t);}async get(t){return c$1(this.paths.messagePath(t))}async list(){let t=await j$2(this.paths.messagesDir,".json");return (await Promise.all(t.map(s=>c$1(this.paths.messagePath(s.replace(".json","")))))).filter(s=>s!==null).sort((s,r)=>s.created_at.localeCompare(r.created_at))}async listPending(t){let e=await this.list(),s=Date.now();return e.filter(r=>r.status!=="pending"||r.expires_at&&new Date(r.expires_at).getTime()<s?false:r.to_agent_id===t)}async markDelivered(t){let e=await this.get(t);e&&(e.status="delivered",e.delivered_at=new Date().toISOString(),await d(this.paths.messagePath(t),e));}async delete(t){try{await Rt.unlink(this.paths.messagePath(t));}catch(e){if(e.code!=="ENOENT")throw e}}async purgeExpired(){let t=await this.list(),e=Date.now(),s=t.filter(r=>{let a=r.expires_at&&new Date(r.expires_at).getTime()<e,n=r.delivered_at&&e-new Date(r.delivered_at).getTime()>36e5;return a||n});return await Promise.all(s.map(r=>this.delete(r.id))),s.length}};var $t=new Set(["achieved","abandoned"]);function ut(m){return $t.has(m)}var X={active:0,paused:1,achieved:2,abandoned:3};var L=class{constructor(t){this.paths=t;}async list(t){let e=await j$2(this.paths.goalsDir,".yml");return (await Promise.all(e.map(a=>{let n=a.replace(".yml","");return a$1(this.paths.goalPath(n))}))).filter(a=>a!==null&&(!t?.status||a.status===t.status)).sort((a,n)=>{let i=X[a.status]-X[n.status];if(i!==0)return i;let o=n.updated_at??"",c=a.updated_at??"";return o<c?-1:o>c?1:0})}async get(t){return a$1(this.paths.goalPath(t))}async save(t){await b$2(this.paths.goalPath(t.id),t);}async delete(t){try{await Rt.unlink(this.paths.goalPath(t));}catch(e){if(e.code!=="ENOENT")throw e}}};var B=class{constructor(t){this.paths=t;}async save(t){await h(this.paths.teamsDir),await b$2(this.paths.teamPath(t.id),t);}async get(t){return a$1(this.paths.teamPath(t))}async getByName(t){return (await this.list()).find(s=>s.name===t)??null}async list(){await h(this.paths.teamsDir);let t=await j$2(this.paths.teamsDir,".yml");return (await Promise.all(t.map(s=>a$1(this.paths.teamPath(s.replace(".yml","")))))).filter(s=>s!==null)}async delete(t){try{await Rt.unlink(this.paths.teamPath(t));}catch(e){if(e.code!=="ENOENT")throw e}}};var F=class{constructor(t,e,s,r){this.messageStore=t;this.agentStore=e;this.teamStore=s;this.eventBus=r;}async send(t){if(!t.body.trim())throw new c("Message body is required");let e=t.ttl_ms??864e5;if(e<=0||e>6048e5)throw new c(`TTL must be between 1ms and ${6048e5}ms`);if(!await this.agentStore.get(t.from_agent_id)&&t.from_agent_id!=="cli")throw new c(`Sender agent not found: ${t.from_agent_id}`);let r=new Date,a={channel:t.channel,from_agent_id:t.from_agent_id,subject:(t.subject||"(no subject)").slice(0,200),body:t.body.slice(0,4e3),created_at:r.toISOString(),expires_at:new Date(r.getTime()+e).toISOString(),status:"pending",team_id:t.team_id,reply_to:t.reply_to},n=[];if(t.channel==="broadcast"){let i=await this.agentStore.list();if(t.team_id){let d=await this.teamStore.get(t.team_id);if(d){let v=new Set(d.members.map(p=>p.agent_id));i=i.filter(p=>v.has(p.id));}}let c=i.filter(d=>d.id!==t.from_agent_id&&d.status!=="disabled").map(d=>({...a,id:`msg_${nanoid(7)}`,to_agent_id:d.id}));await Promise.all(c.map(d=>this.messageStore.save(d)));for(let d of c)n.push(d),this.emitSent(d);}else if(t.channel==="lead"){if(!t.team_id)throw new c("team_id is required for lead channel");let i=await this.teamStore.get(t.team_id);if(!i)throw new c(`Team not found: ${t.team_id}`);let o={...a,id:`msg_${nanoid(7)}`,to_agent_id:i.lead_agent_id};await this.messageStore.save(o),n.push(o),this.emitSent(o);}else {if(!t.to_agent_id)throw new c("to_agent_id is required for direct messages");if(!await this.agentStore.get(t.to_agent_id))throw new c(`Recipient agent not found: ${t.to_agent_id}`);let o={...a,id:`msg_${nanoid(7)}`,to_agent_id:t.to_agent_id};await this.messageStore.save(o),n.push(o),this.emitSent(o);}return n}async drainMailbox(t,e){let s=await this.messageStore.listPending(t);await Promise.all(s.map(r=>this.messageStore.markDelivered(r.id)));for(let r of s)this.eventBus.emit({type:"message:delivered",messageId:r.id,toAgentId:t,taskId:e});return s}async listAll(){return this.messageStore.list()}async listPendingForAgent(t){return this.messageStore.listPending(t)}async listForAgent(t){return (await this.messageStore.list()).filter(s=>s.to_agent_id===t||s.from_agent_id===t)}async purgeExpired(){return this.messageStore.purgeExpired()}emitSent(t){this.eventBus.emit({type:"message:sent",messageId:t.id,fromAgentId:t.from_agent_id,toAgentId:t.to_agent_id,channel:t.channel});}};var Yt={active:["paused","achieved","abandoned"],paused:["active","abandoned"],achieved:[],abandoned:[]},N=class{constructor(t,e,s,r){this.goalStore=t;this.eventBus=e;this.agentService=s;this.taskService=r;}async create(t){if(!t.title.trim())throw new c("Goal title is required");let e=new Date().toISOString(),s={id:`goal_${nanoid(7)}`,title:t.title.trim(),description:t.description?.trim()??"",status:"active",assignee:t.assignee,created_at:e,updated_at:e};return await this.goalStore.save(s),this.eventBus.emit({type:"goal:created",goalId:s.id,title:s.title}),s.assignee&&await this.enableAutonomous(s.assignee),s}async list(t){return this.goalStore.list(t)}async get(t){let e=await this.goalStore.get(t);if(!e)throw new j$1(t);return e}async updateStatus(t,e){let s=await this.get(t),r=s.status;if(!Yt[r].includes(e))throw new c(`Cannot transition goal from '${r}' to '${e}'`);return s.status=e,s.updated_at=new Date().toISOString(),await this.goalStore.save(s),this.eventBus.emit({type:"goal:status_changed",goalId:t,from:r,to:e}),s.assignee&&(e==="paused"?(await this.maybeDisableAutonomous(s.assignee),await this.cancelPendingAutonomousTasks(s.assignee)):e==="active"&&r==="paused"?await this.enableAutonomous(s.assignee):ut(e)&&await this.maybeDisableAutonomous(s.assignee)),s}async update(t,e){let s=await this.get(t),r=s.assignee;if(e.title!==void 0){if(!e.title.trim())throw new c("Goal title cannot be empty");s.title=e.title.trim();}e.description!==void 0&&(s.description=e.description.trim()),e.assignee!==void 0&&(s.assignee=e.assignee||void 0),s.updated_at=new Date().toISOString(),await this.goalStore.save(s),this.eventBus.emit({type:"goal:updated",goalId:t});let a=s.assignee;if(a!==r){let n=[];a&&n.push(this.enableAutonomous(a)),r&&n.push(this.maybeDisableAutonomous(r)),await Promise.all(n);}return s}async delete(t){let e=await this.get(t),{assignee:s}=e;await this.goalStore.delete(t),this.eventBus.emit({type:"goal:deleted",goalId:t}),s&&await this.maybeDisableAutonomous(s);}async enableAutonomous(t){if(this.agentService)try{await this.agentService.setAutonomous(t,!0);}catch{}}async hasActiveGoalsForAgent(t){return (await this.goalStore.list({status:"active"})).some(s=>s.assignee===t)}async cancelPendingAutonomousTasks(t){if(this.taskService)try{let[e,s]=await Promise.all([this.taskService.list({status:"todo"}),this.taskService.list({status:"retrying"})]),r=[...e,...s].filter(a=>a.assignee===t&&a.labels?.includes(a$2));await Promise.all(r.map(a=>this.taskService.cancel(a.id).catch(()=>{})));}catch{}}async maybeDisableAutonomous(t){if(this.agentService)try{await this.hasActiveGoalsForAgent(t)||await this.agentService.setAutonomous(t,!1);}catch{}}};var ht={auto_claim:true,message_ttl_ms:864e5};var $=class{constructor(t,e,s,r){this.teamStore=t;this.agentStore=e;this.taskStore=s;this.eventBus=r;}async create(t){if(!t.name.trim())throw new c("Team name is required");if(!await this.agentStore.get(t.lead_agent_id))throw new c(`Lead agent not found: ${t.lead_agent_id}`);if(await this.teamStore.getByName(t.name.trim()))throw new c(`Team "${t.name}" already exists`);let r=new Date().toISOString(),a={agent_id:t.lead_agent_id,role:"lead",joined_at:r},n=[];for(let o of t.member_agent_ids??[]){if(o===t.lead_agent_id)continue;if(!await this.agentStore.get(o))throw new c(`Member agent not found: ${o}`);n.push({agent_id:o,role:"member",joined_at:r});}let i={id:`team_${nanoid(7)}`,name:t.name.trim(),description:t.description,status:"active",members:[a,...n],task_pool:[],lead_agent_id:t.lead_agent_id,created_at:r,updated_at:r,config:{...ht,...t.config??{}}};await this.teamStore.save(i),this.eventBus.emit({type:"team:created",teamId:i.id,name:i.name,leadAgentId:i.lead_agent_id});for(let o of n)this.eventBus.emit({type:"team:member_joined",teamId:i.id,agentId:o.agent_id});return i}async get(t){let e=await this.teamStore.get(t);if(!e)throw new k$1(t);return e}async list(){return this.teamStore.list()}async join(t,e){let s=await this.get(t);if(s.members.some(a=>a.agent_id===e))throw new c(`Agent ${e} is already a member of team ${t}`);if(!await this.agentStore.get(e))throw new c(`Agent not found: ${e}`);return s.members.push({agent_id:e,role:"member",joined_at:new Date().toISOString()}),s.updated_at=new Date().toISOString(),await this.teamStore.save(s),this.eventBus.emit({type:"team:member_joined",teamId:t,agentId:e}),s}async leave(t,e){let s=await this.get(t);if(e===s.lead_agent_id)throw new c("Lead cannot leave team. Disband the team or transfer lead first.");return s.members=s.members.filter(r=>r.agent_id!==e),s.updated_at=new Date().toISOString(),await this.teamStore.save(s),this.eventBus.emit({type:"team:member_left",teamId:t,agentId:e}),s}async addTask(t,e){let s=await this.get(t);if(!await this.taskStore.get(e))throw new c(`Task not found: ${e}`);return s.task_pool.includes(e)||(s.task_pool.push(e),s.updated_at=new Date().toISOString(),await this.teamStore.save(s),this.eventBus.emit({type:"team:task_added",teamId:t,taskId:e})),s}async removeTask(t,e){let s=await this.get(t);return s.task_pool=s.task_pool.filter(r=>r!==e),s.updated_at=new Date().toISOString(),await this.teamStore.save(s),s}async setLead(t,e){let s=await this.get(t),r=s.members.find(n=>n.agent_id===e);if(!r)throw new c(`Agent ${e} is not a member of team ${t}`);let a=s.members.find(n=>n.agent_id===s.lead_agent_id);return a&&(a.role="member"),r.role="lead",s.lead_agent_id=e,s.updated_at=new Date().toISOString(),await this.teamStore.save(s),s}async disband(t){let e=await this.get(t);e.status="disbanded",e.updated_at=new Date().toISOString(),await this.teamStore.save(e),this.eventBus.emit({type:"team:disbanded",teamId:t});}async findTeamForAgent(t){return (await this.teamStore.list()).find(s=>s.status==="active"&&s.members.some(r=>r.agent_id===t))??null}};async function ft(m){let t=new k(m.projectRoot);await t.requireInit();let e=new O(t),s=new D,[r,a]=await Promise.all([e.read(),s.read()]),n=new I(t),i=new C(t),o=new j(t),c=new R(t),d=new M(t),v=new G(t),p=new L(t),k$1=new B(t),w=new P,_=new b(n,w,r),E=new A(i,c,w,r),U=new x(o,w),wt=new F(v,i,k$1,w),_t=new N(p,w,E,_),St=new $(k$1,i,n,w);return {context:m,paths:t,config:r,taskStore:n,agentStore:i,runStore:o,stateStore:c,configStore:e,globalConfigStore:s,globalConfig:a,contextStore:d,messageStore:v,goalStore:p,teamStore:k$1,eventBus:w,taskService:_,agentService:E,runService:U,messageService:wt,goalService:_t,teamService:St}}async function yt(m){let t=await ft(m),[{ProcessManager:e},{AdapterRegistry:s},{ClaudeAdapter:r},{CodexAdapter:a},{CursorAdapter:n},{ShellAdapter:i},{WorkspaceManager:o},{LiquidTemplateEngine:c},{Orchestrator:d},{DoctorService:v}]=await Promise.all([import('./process-manager-TLZOTO4Y.js'),import('./registry-UQAHK77P.js'),import('./claude-S47YTIHU.js'),import('./codex-2CH57B7G.js'),import('./cursor-QFUNKPCQ.js'),import('./shell-OGTSH4RJ.js'),import('./workspace-manager-7M46ESUL.js'),import('./template-engine-322SCRR6.js'),import('./orchestrator-TAFBYQQ5.js'),import('./doctor-service-NTWBWOM2.js')]),p=new e,k=new c,w=new o(m.projectRoot,t.paths.root,p),_=new s;_.register(new r(p)),_.register(new a(p)),_.register(new n(p)),_.register(new i(p));let E=new v(_,p),U=new d({taskStore:t.taskStore,agentStore:t.agentStore,runStore:t.runStore,stateStore:t.stateStore,adapterRegistry:_,workspaceManager:w,templateEngine:k,processManager:p,eventBus:t.eventBus,taskService:t.taskService,agentService:t.agentService,runService:t.runService,contextStore:t.contextStore,messageService:t.messageService,goalStore:t.goalStore,config:t.config,projectRoot:m.projectRoot,lockPath:t.paths.lockPath});return {...t,processManager:p,adapterRegistry:_,workspaceManager:w,templateEngine:k,doctorService:E,orchestrator:U}}async function zt(m){return yt(m)}export{A as AgentService,P as EventBus,x as RunService,b as TaskService,zt as buildContainer,yt as buildFullContainer,ft as buildLightContainer};//# sourceMappingURL=index.js.map
|
|
1
|
+
import { ensureDir, Paths, readYaml, writeYaml, listFiles, writeJson, readJson, appendJsonl, readJsonl, readJsonlTail, pathExists } from './chunk-QEEM67OA.js';
|
|
2
|
+
import { canTransition, isTerminal } from './chunk-2UC4SVJB.js';
|
|
3
|
+
export { Orchestrator, canTransition, isBlocked, isDispatchable, isTerminal, resolveFailureStatus } from './chunk-2UC4SVJB.js';
|
|
4
|
+
import { InvalidArgumentsError, TaskNotFoundError, InvalidTransitionError, AgentNotFoundError, OrchestryError, TeamNotFoundError, GoalNotFoundError } from './chunk-IQXRQBUK.js';
|
|
5
|
+
export { AgentNotFoundError, NotInitializedError, OrchestryError, TaskNotFoundError, WorkspaceError } from './chunk-IQXRQBUK.js';
|
|
6
|
+
import { AUTONOMOUS_LABEL } from './chunk-B4JQM4NU.js';
|
|
7
|
+
export { AdapterRegistry } from './chunk-6DWHQPTE.js';
|
|
8
|
+
export { createTokenUsage } from './chunk-XDVMX2FO.js';
|
|
9
|
+
import fs, { mkdtemp, readFile, unlink, rm, mkdir } from 'fs/promises';
|
|
10
|
+
import path, { join } from 'path';
|
|
11
|
+
import { nanoid } from 'nanoid';
|
|
12
|
+
import { execFile as execFile$1, execFileSync } from 'child_process';
|
|
13
|
+
import { promisify } from 'util';
|
|
14
|
+
import { homedir, tmpdir } from 'os';
|
|
15
|
+
import { createReadStream } from 'fs';
|
|
16
|
+
|
|
17
|
+
// src/application/event-bus.ts
|
|
18
|
+
var EventBus = class {
|
|
19
|
+
handlers = /* @__PURE__ */ new Map();
|
|
20
|
+
wildcardHandlers = /* @__PURE__ */ new Set();
|
|
21
|
+
maxListeners = 10;
|
|
22
|
+
warnedTypes = /* @__PURE__ */ new Set();
|
|
23
|
+
/**
|
|
24
|
+
* Set the maximum number of listeners per event type before a warning is emitted.
|
|
25
|
+
* Helps detect memory leaks from repeated subscriptions in watch mode.
|
|
26
|
+
*/
|
|
27
|
+
setMaxListeners(n) {
|
|
28
|
+
this.maxListeners = n;
|
|
29
|
+
}
|
|
30
|
+
getMaxListeners() {
|
|
31
|
+
return this.maxListeners;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Get the number of listeners for a specific event type.
|
|
35
|
+
*/
|
|
36
|
+
listenerCount(type) {
|
|
37
|
+
return this.handlers.get(type)?.size ?? 0;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Subscribe to events of a specific type.
|
|
41
|
+
* Returns an unsubscribe function.
|
|
42
|
+
*/
|
|
43
|
+
on(type, handler) {
|
|
44
|
+
if (!this.handlers.has(type)) {
|
|
45
|
+
this.handlers.set(type, /* @__PURE__ */ new Set());
|
|
46
|
+
}
|
|
47
|
+
const set = this.handlers.get(type);
|
|
48
|
+
set.add(handler);
|
|
49
|
+
if (this.maxListeners > 0 && set.size > this.maxListeners && !this.warnedTypes.has(type)) {
|
|
50
|
+
this.warnedTypes.add(type);
|
|
51
|
+
console.warn(
|
|
52
|
+
`EventBus: possible memory leak detected. ${set.size} listeners added for "${type}". Use setMaxListeners() to increase limit if this is intentional.`
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
return () => this.off(type, handler);
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Subscribe to an event type, auto-unsubscribe after first call.
|
|
59
|
+
*/
|
|
60
|
+
once(type, handler) {
|
|
61
|
+
const wrapper = (event) => {
|
|
62
|
+
this.off(type, wrapper);
|
|
63
|
+
handler(event);
|
|
64
|
+
};
|
|
65
|
+
return this.on(type, wrapper);
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Unsubscribe a handler from an event type.
|
|
69
|
+
*/
|
|
70
|
+
off(type, handler) {
|
|
71
|
+
this.handlers.get(type)?.delete(handler);
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Emit an event synchronously to all subscribed handlers.
|
|
75
|
+
*/
|
|
76
|
+
emit(event) {
|
|
77
|
+
const typed = this.handlers.get(event.type);
|
|
78
|
+
if (typed) this.dispatchToSet(typed, event, "handler");
|
|
79
|
+
this.dispatchToSet(this.wildcardHandlers, event, "wildcard handler");
|
|
80
|
+
}
|
|
81
|
+
dispatchToSet(handlers, event, label) {
|
|
82
|
+
for (const handler of handlers) {
|
|
83
|
+
try {
|
|
84
|
+
handler(event);
|
|
85
|
+
} catch (err) {
|
|
86
|
+
console.error(`EventBus ${label} error for "${event.type}":`, err);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Subscribe to ALL events regardless of type.
|
|
92
|
+
*/
|
|
93
|
+
onAny(handler) {
|
|
94
|
+
this.wildcardHandlers.add(handler);
|
|
95
|
+
if (this.maxListeners > 0 && this.wildcardHandlers.size > this.maxListeners && !this.warnedTypes.has("*")) {
|
|
96
|
+
this.warnedTypes.add("*");
|
|
97
|
+
console.warn(
|
|
98
|
+
`EventBus: possible memory leak detected. ${this.wildcardHandlers.size} wildcard listeners added. Use setMaxListeners() to increase limit if this is intentional.`
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
return () => {
|
|
102
|
+
this.wildcardHandlers.delete(handler);
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Remove all handlers.
|
|
107
|
+
*/
|
|
108
|
+
clear() {
|
|
109
|
+
this.handlers.clear();
|
|
110
|
+
this.wildcardHandlers.clear();
|
|
111
|
+
this.warnedTypes.clear();
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
var TaskService = class {
|
|
115
|
+
constructor(taskStore, eventBus, config, paths) {
|
|
116
|
+
this.taskStore = taskStore;
|
|
117
|
+
this.eventBus = eventBus;
|
|
118
|
+
this.config = config;
|
|
119
|
+
this.paths = paths;
|
|
120
|
+
}
|
|
121
|
+
async create(input) {
|
|
122
|
+
if (!input.title.trim()) {
|
|
123
|
+
throw new InvalidArgumentsError("Task title is required");
|
|
124
|
+
}
|
|
125
|
+
const priority = input.priority ?? this.config.defaults.task.priority;
|
|
126
|
+
if (!Number.isInteger(priority) || priority < 1 || priority > 4) {
|
|
127
|
+
throw new InvalidArgumentsError("Priority must be an integer between 1 and 4");
|
|
128
|
+
}
|
|
129
|
+
if (input.depends_on?.length) {
|
|
130
|
+
const results = await Promise.all(
|
|
131
|
+
input.depends_on.map(async (depId) => ({ depId, exists: !!await this.taskStore.get(depId) }))
|
|
132
|
+
);
|
|
133
|
+
const missing = results.filter((r) => !r.exists).map((r) => r.depId);
|
|
134
|
+
if (missing.length > 0) {
|
|
135
|
+
throw new InvalidArgumentsError(
|
|
136
|
+
`Unknown depends_on task ID(s): ${missing.join(", ")}`
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
141
|
+
const task = {
|
|
142
|
+
id: `tsk_${nanoid(7)}`,
|
|
143
|
+
title: input.title.trim(),
|
|
144
|
+
description: input.description?.trim() ?? "",
|
|
145
|
+
status: "todo",
|
|
146
|
+
priority,
|
|
147
|
+
assignee: input.assignee,
|
|
148
|
+
labels: input.labels ?? [],
|
|
149
|
+
depends_on: input.depends_on ?? [],
|
|
150
|
+
created_at: now,
|
|
151
|
+
updated_at: now,
|
|
152
|
+
attempts: 0,
|
|
153
|
+
max_attempts: input.max_attempts ?? this.config.defaults.task.max_attempts,
|
|
154
|
+
workspace_mode: input.workspace_mode,
|
|
155
|
+
review_criteria: input.review_criteria,
|
|
156
|
+
scope: input.scope,
|
|
157
|
+
goalId: input.goalId
|
|
158
|
+
};
|
|
159
|
+
if (input.attachments?.length && this.paths) {
|
|
160
|
+
const attachmentNames = await this.copyAttachments(task.id, input.attachments);
|
|
161
|
+
task.attachments = attachmentNames;
|
|
162
|
+
}
|
|
163
|
+
await this.taskStore.save(task);
|
|
164
|
+
this.eventBus.emit({ type: "task:created", task });
|
|
165
|
+
return task;
|
|
166
|
+
}
|
|
167
|
+
async list(filter) {
|
|
168
|
+
return this.taskStore.list(filter);
|
|
169
|
+
}
|
|
170
|
+
async get(id) {
|
|
171
|
+
const task = await this.taskStore.get(id);
|
|
172
|
+
if (!task) throw new TaskNotFoundError(id);
|
|
173
|
+
return task;
|
|
174
|
+
}
|
|
175
|
+
async updateStatus(id, newStatus) {
|
|
176
|
+
const task = await this.get(id);
|
|
177
|
+
const oldStatus = task.status;
|
|
178
|
+
if (!canTransition(oldStatus, newStatus)) {
|
|
179
|
+
throw new InvalidTransitionError(id, oldStatus, newStatus);
|
|
180
|
+
}
|
|
181
|
+
task.status = newStatus;
|
|
182
|
+
task.updated_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
183
|
+
await this.taskStore.save(task);
|
|
184
|
+
this.eventBus.emit({
|
|
185
|
+
type: "task:status_changed",
|
|
186
|
+
taskId: id,
|
|
187
|
+
from: oldStatus,
|
|
188
|
+
to: newStatus
|
|
189
|
+
});
|
|
190
|
+
return task;
|
|
191
|
+
}
|
|
192
|
+
async assign(taskId, agentId) {
|
|
193
|
+
const task = await this.get(taskId);
|
|
194
|
+
task.assignee = agentId;
|
|
195
|
+
task.updated_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
196
|
+
await this.taskStore.save(task);
|
|
197
|
+
this.eventBus.emit({
|
|
198
|
+
type: "task:assigned",
|
|
199
|
+
taskId,
|
|
200
|
+
agentId
|
|
201
|
+
});
|
|
202
|
+
return task;
|
|
203
|
+
}
|
|
204
|
+
async cancel(id) {
|
|
205
|
+
const task = await this.get(id);
|
|
206
|
+
if (isTerminal(task.status)) {
|
|
207
|
+
throw new InvalidTransitionError(id, task.status, "cancelled");
|
|
208
|
+
}
|
|
209
|
+
return this.updateStatus(id, "cancelled");
|
|
210
|
+
}
|
|
211
|
+
async retry(id) {
|
|
212
|
+
const task = await this.get(id);
|
|
213
|
+
if (task.status !== "failed" && task.status !== "cancelled") {
|
|
214
|
+
throw new InvalidTransitionError(id, task.status, "todo");
|
|
215
|
+
}
|
|
216
|
+
const oldStatus = task.status;
|
|
217
|
+
task.status = "todo";
|
|
218
|
+
task.attempts = 0;
|
|
219
|
+
task.updated_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
220
|
+
await this.taskStore.save(task);
|
|
221
|
+
this.eventBus.emit({
|
|
222
|
+
type: "task:status_changed",
|
|
223
|
+
taskId: id,
|
|
224
|
+
from: oldStatus,
|
|
225
|
+
to: "todo"
|
|
226
|
+
});
|
|
227
|
+
return task;
|
|
228
|
+
}
|
|
229
|
+
async reject(id, feedback) {
|
|
230
|
+
const task = await this.get(id);
|
|
231
|
+
if (task.status !== "review") {
|
|
232
|
+
throw new InvalidTransitionError(id, task.status, "todo");
|
|
233
|
+
}
|
|
234
|
+
const oldStatus = task.status;
|
|
235
|
+
task.status = "todo";
|
|
236
|
+
task.attempts = 0;
|
|
237
|
+
task.feedback = feedback;
|
|
238
|
+
task.updated_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
239
|
+
await this.taskStore.save(task);
|
|
240
|
+
this.eventBus.emit({
|
|
241
|
+
type: "task:status_changed",
|
|
242
|
+
taskId: id,
|
|
243
|
+
from: oldStatus,
|
|
244
|
+
to: "todo"
|
|
245
|
+
});
|
|
246
|
+
return task;
|
|
247
|
+
}
|
|
248
|
+
async update(id, fields) {
|
|
249
|
+
const task = await this.get(id);
|
|
250
|
+
if (fields.title !== void 0) {
|
|
251
|
+
if (!fields.title.trim()) throw new InvalidArgumentsError("Task title cannot be empty");
|
|
252
|
+
task.title = fields.title.trim();
|
|
253
|
+
}
|
|
254
|
+
if (fields.description !== void 0) task.description = fields.description.trim();
|
|
255
|
+
if (fields.priority !== void 0) {
|
|
256
|
+
if (!Number.isInteger(fields.priority) || fields.priority < 1 || fields.priority > 4) {
|
|
257
|
+
throw new InvalidArgumentsError("Priority must be an integer between 1 and 4");
|
|
258
|
+
}
|
|
259
|
+
task.priority = fields.priority;
|
|
260
|
+
}
|
|
261
|
+
if (fields.labels !== void 0) task.labels = fields.labels;
|
|
262
|
+
if (fields.attachments?.length && this.paths) {
|
|
263
|
+
const attachmentNames = await this.copyAttachments(id, fields.attachments);
|
|
264
|
+
task.attachments = [...task.attachments ?? [], ...attachmentNames];
|
|
265
|
+
}
|
|
266
|
+
task.updated_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
267
|
+
await this.taskStore.save(task);
|
|
268
|
+
return task;
|
|
269
|
+
}
|
|
270
|
+
async delete(id) {
|
|
271
|
+
const task = await this.get(id);
|
|
272
|
+
if (task.status === "in_progress") {
|
|
273
|
+
throw new InvalidArgumentsError("Cannot delete a running task. Cancel it first.");
|
|
274
|
+
}
|
|
275
|
+
await this.taskStore.delete(id);
|
|
276
|
+
if (this.paths) {
|
|
277
|
+
const dir = this.paths.taskAttachmentsDir(id);
|
|
278
|
+
await fs.rm(dir, { recursive: true, force: true });
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
getAttachmentPath(taskId, filename) {
|
|
282
|
+
if (!this.paths) {
|
|
283
|
+
throw new InvalidArgumentsError("Paths not configured");
|
|
284
|
+
}
|
|
285
|
+
return path.join(this.paths.taskAttachmentsDir(taskId), filename);
|
|
286
|
+
}
|
|
287
|
+
async copyAttachments(taskId, sourcePaths) {
|
|
288
|
+
if (!this.paths) return [];
|
|
289
|
+
const dir = this.paths.taskAttachmentsDir(taskId);
|
|
290
|
+
await ensureDir(dir);
|
|
291
|
+
await Promise.all(
|
|
292
|
+
sourcePaths.map(async (srcPath) => {
|
|
293
|
+
try {
|
|
294
|
+
await fs.access(srcPath);
|
|
295
|
+
} catch {
|
|
296
|
+
throw new InvalidArgumentsError(`Attachment file not found: ${srcPath}`);
|
|
297
|
+
}
|
|
298
|
+
})
|
|
299
|
+
);
|
|
300
|
+
const names = await Promise.all(
|
|
301
|
+
sourcePaths.map(async (srcPath) => {
|
|
302
|
+
const basename = path.basename(srcPath);
|
|
303
|
+
await fs.copyFile(srcPath, path.join(dir, basename));
|
|
304
|
+
return basename;
|
|
305
|
+
})
|
|
306
|
+
);
|
|
307
|
+
return names;
|
|
308
|
+
}
|
|
309
|
+
async incrementAttempts(id) {
|
|
310
|
+
const task = await this.get(id);
|
|
311
|
+
task.attempts += 1;
|
|
312
|
+
task.updated_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
313
|
+
await this.taskStore.save(task);
|
|
314
|
+
return task;
|
|
315
|
+
}
|
|
316
|
+
};
|
|
317
|
+
var AgentService = class {
|
|
318
|
+
constructor(agentStore, stateStore, eventBus, config) {
|
|
319
|
+
this.agentStore = agentStore;
|
|
320
|
+
this.stateStore = stateStore;
|
|
321
|
+
this.eventBus = eventBus;
|
|
322
|
+
this.config = config;
|
|
323
|
+
}
|
|
324
|
+
async create(input) {
|
|
325
|
+
if (!input.name.trim()) {
|
|
326
|
+
throw new InvalidArgumentsError("Agent name is required");
|
|
327
|
+
}
|
|
328
|
+
const existing = await this.agentStore.getByName(input.name);
|
|
329
|
+
if (existing) {
|
|
330
|
+
throw new InvalidArgumentsError(`Agent "${input.name}" already exists`);
|
|
331
|
+
}
|
|
332
|
+
const agent = {
|
|
333
|
+
id: `agt_${nanoid(7)}`,
|
|
334
|
+
name: input.name.trim(),
|
|
335
|
+
adapter: input.adapter || this.config.defaults.agent.adapter,
|
|
336
|
+
role: input.role,
|
|
337
|
+
config: {
|
|
338
|
+
command: input.command,
|
|
339
|
+
model: input.model,
|
|
340
|
+
approval_policy: input.approval_policy ?? this.config.defaults.agent.approval_policy,
|
|
341
|
+
max_turns: input.max_turns ?? this.config.defaults.agent.max_turns,
|
|
342
|
+
timeout_ms: input.timeout_ms ?? this.config.defaults.agent.timeout_ms,
|
|
343
|
+
stall_timeout_ms: input.stall_timeout_ms ?? this.config.defaults.agent.stall_timeout_ms,
|
|
344
|
+
env: input.env,
|
|
345
|
+
system_prompt: input.system_prompt,
|
|
346
|
+
workspace_mode: input.workspace_mode,
|
|
347
|
+
skills: input.skills
|
|
348
|
+
},
|
|
349
|
+
status: "idle",
|
|
350
|
+
stats: {
|
|
351
|
+
tasks_completed: 0,
|
|
352
|
+
tasks_failed: 0,
|
|
353
|
+
total_runs: 0,
|
|
354
|
+
total_runtime_ms: 0
|
|
355
|
+
}
|
|
356
|
+
};
|
|
357
|
+
await this.agentStore.save(agent);
|
|
358
|
+
return agent;
|
|
359
|
+
}
|
|
360
|
+
async list() {
|
|
361
|
+
return this.agentStore.list();
|
|
362
|
+
}
|
|
363
|
+
async get(id) {
|
|
364
|
+
const agent = await this.agentStore.get(id);
|
|
365
|
+
if (!agent) throw new AgentNotFoundError(id);
|
|
366
|
+
return agent;
|
|
367
|
+
}
|
|
368
|
+
async remove(id) {
|
|
369
|
+
const agent = await this.get(id);
|
|
370
|
+
if (agent.status === "running") {
|
|
371
|
+
const state = await this.stateStore.read();
|
|
372
|
+
const isActuallyRunning = Object.values(state.running).some((e) => e.agent_id === id);
|
|
373
|
+
if (isActuallyRunning) {
|
|
374
|
+
throw new InvalidArgumentsError("Cannot remove a running agent. Stop it first.");
|
|
375
|
+
}
|
|
376
|
+
agent.status = "idle";
|
|
377
|
+
await this.agentStore.save(agent);
|
|
378
|
+
}
|
|
379
|
+
await this.agentStore.delete(id);
|
|
380
|
+
}
|
|
381
|
+
async update(id, fields) {
|
|
382
|
+
const agent = await this.get(id);
|
|
383
|
+
if (fields.name !== void 0) {
|
|
384
|
+
if (!fields.name.trim()) throw new InvalidArgumentsError("Agent name cannot be empty");
|
|
385
|
+
const existing = await this.agentStore.getByName(fields.name.trim());
|
|
386
|
+
if (existing && existing.id !== id) {
|
|
387
|
+
throw new InvalidArgumentsError(`Agent "${fields.name}" already exists`);
|
|
388
|
+
}
|
|
389
|
+
agent.name = fields.name.trim();
|
|
390
|
+
}
|
|
391
|
+
if (fields.role !== void 0) agent.role = fields.role || void 0;
|
|
392
|
+
if (fields.model !== void 0) agent.config.model = fields.model || void 0;
|
|
393
|
+
if (fields.approval_policy !== void 0) agent.config.approval_policy = fields.approval_policy;
|
|
394
|
+
await this.agentStore.save(agent);
|
|
395
|
+
return agent;
|
|
396
|
+
}
|
|
397
|
+
async disable(id) {
|
|
398
|
+
return this.setStatus(id, "disabled");
|
|
399
|
+
}
|
|
400
|
+
async enable(id) {
|
|
401
|
+
return this.setStatus(id, "idle");
|
|
402
|
+
}
|
|
403
|
+
async setAutonomous(id, enabled) {
|
|
404
|
+
const agent = await this.get(id);
|
|
405
|
+
agent.autonomous = enabled;
|
|
406
|
+
await this.agentStore.save(agent);
|
|
407
|
+
this.eventBus.emit({ type: "agent:autonomous_toggled", agentId: id, autonomous: enabled });
|
|
408
|
+
return agent;
|
|
409
|
+
}
|
|
410
|
+
async setStatus(id, status) {
|
|
411
|
+
const agent = await this.get(id);
|
|
412
|
+
agent.status = status;
|
|
413
|
+
await this.agentStore.save(agent);
|
|
414
|
+
return agent;
|
|
415
|
+
}
|
|
416
|
+
async updateStats(id, update) {
|
|
417
|
+
const agent = await this.get(id);
|
|
418
|
+
Object.assign(agent.stats, update);
|
|
419
|
+
await this.agentStore.save(agent);
|
|
420
|
+
return agent;
|
|
421
|
+
}
|
|
422
|
+
/**
|
|
423
|
+
* Find the best available agent for a task using scoring.
|
|
424
|
+
*
|
|
425
|
+
* Scoring:
|
|
426
|
+
* - Explicit assignee match = 100
|
|
427
|
+
* - Skill match with task labels = 50 per match
|
|
428
|
+
* - Role match with task labels = 30
|
|
429
|
+
* - Idle status bonus = 20
|
|
430
|
+
* - Success rate bonus = 0–10 (scaled by completed / total)
|
|
431
|
+
*/
|
|
432
|
+
async findBestAgent(task) {
|
|
433
|
+
const agents = await this.agentStore.list();
|
|
434
|
+
const available = agents.filter(
|
|
435
|
+
(a) => a.status === "idle"
|
|
436
|
+
);
|
|
437
|
+
if (available.length === 0) return null;
|
|
438
|
+
if (task.assignee) {
|
|
439
|
+
const assigned = agents.find((a) => a.id === task.assignee);
|
|
440
|
+
if (assigned && assigned.status === "idle") return assigned;
|
|
441
|
+
return null;
|
|
442
|
+
}
|
|
443
|
+
const lowerLabels = task.labels?.length ? task.labels.map((l) => l.toLowerCase()) : void 0;
|
|
444
|
+
const scored = available.map((agent) => {
|
|
445
|
+
let score = 0;
|
|
446
|
+
if (lowerLabels && agent.config.skills?.length) {
|
|
447
|
+
const skillSet = new Set(agent.config.skills.map((s) => s.toLowerCase()));
|
|
448
|
+
for (const label of lowerLabels) {
|
|
449
|
+
if (skillSet.has(label)) {
|
|
450
|
+
score += 50;
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
if (lowerLabels && agent.role) {
|
|
455
|
+
const lowerRole = agent.role.toLowerCase();
|
|
456
|
+
if (lowerLabels.some((l) => lowerRole.includes(l))) {
|
|
457
|
+
score += 30;
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
if (agent.status === "idle") {
|
|
461
|
+
score += 20;
|
|
462
|
+
}
|
|
463
|
+
const totalTasks = agent.stats.tasks_completed + agent.stats.tasks_failed;
|
|
464
|
+
if (totalTasks > 0) {
|
|
465
|
+
score += Math.round(agent.stats.tasks_completed / totalTasks * 10);
|
|
466
|
+
}
|
|
467
|
+
return { agent, score };
|
|
468
|
+
});
|
|
469
|
+
scored.sort((a, b) => b.score - a.score);
|
|
470
|
+
return scored[0]?.agent ?? null;
|
|
471
|
+
}
|
|
472
|
+
};
|
|
473
|
+
var RunService = class {
|
|
474
|
+
constructor(runStore, eventBus) {
|
|
475
|
+
this.runStore = runStore;
|
|
476
|
+
this.eventBus = eventBus;
|
|
477
|
+
}
|
|
478
|
+
async create(params) {
|
|
479
|
+
const run = {
|
|
480
|
+
id: `run_${nanoid(7)}`,
|
|
481
|
+
task_id: params.taskId,
|
|
482
|
+
agent_id: params.agentId,
|
|
483
|
+
attempt: params.attempt,
|
|
484
|
+
status: "preparing",
|
|
485
|
+
started_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
486
|
+
workspace_path: params.workspacePath,
|
|
487
|
+
prompt: params.prompt
|
|
488
|
+
};
|
|
489
|
+
await this.runStore.save(run);
|
|
490
|
+
return run;
|
|
491
|
+
}
|
|
492
|
+
async get(id) {
|
|
493
|
+
return this.runStore.get(id);
|
|
494
|
+
}
|
|
495
|
+
async start(id, pid) {
|
|
496
|
+
const run = await this.runStore.get(id);
|
|
497
|
+
if (!run) throw new Error(`Run not found: ${id}`);
|
|
498
|
+
run.status = "running";
|
|
499
|
+
run.pid = pid;
|
|
500
|
+
await this.runStore.save(run);
|
|
501
|
+
this.eventBus.emit({
|
|
502
|
+
type: "agent:started",
|
|
503
|
+
agentId: run.agent_id,
|
|
504
|
+
taskId: run.task_id,
|
|
505
|
+
runId: id
|
|
506
|
+
});
|
|
507
|
+
return run;
|
|
508
|
+
}
|
|
509
|
+
async finish(id, status, tokens, error) {
|
|
510
|
+
const run = await this.runStore.get(id);
|
|
511
|
+
if (!run) throw new Error(`Run not found: ${id}`);
|
|
512
|
+
run.status = status;
|
|
513
|
+
run.finished_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
514
|
+
run.tokens = tokens;
|
|
515
|
+
run.error = error;
|
|
516
|
+
await this.runStore.save(run);
|
|
517
|
+
this.eventBus.emit({
|
|
518
|
+
type: "agent:completed",
|
|
519
|
+
runId: id,
|
|
520
|
+
agentId: run.agent_id,
|
|
521
|
+
success: status === "succeeded"
|
|
522
|
+
});
|
|
523
|
+
return run;
|
|
524
|
+
}
|
|
525
|
+
async appendEvent(runId, event) {
|
|
526
|
+
await this.runStore.appendEvent(runId, event);
|
|
527
|
+
}
|
|
528
|
+
async listAll() {
|
|
529
|
+
return this.runStore.listAll();
|
|
530
|
+
}
|
|
531
|
+
async listForTask(taskId) {
|
|
532
|
+
return this.runStore.listForTask(taskId);
|
|
533
|
+
}
|
|
534
|
+
async listForAgent(agentId) {
|
|
535
|
+
return this.runStore.listForAgent(agentId);
|
|
536
|
+
}
|
|
537
|
+
async readEvents(runId) {
|
|
538
|
+
return this.runStore.readEvents(runId);
|
|
539
|
+
}
|
|
540
|
+
async readEventsTail(runId, count) {
|
|
541
|
+
return this.runStore.readEventsTail(runId, count);
|
|
542
|
+
}
|
|
543
|
+
/**
|
|
544
|
+
* Get error and last N lines of output from the most recent failed run for a task.
|
|
545
|
+
* Used to provide retry context so agents can learn from previous failures.
|
|
546
|
+
*/
|
|
547
|
+
async getLastFailedRunContext(taskId, maxOutputLines = 50) {
|
|
548
|
+
const runs = await this.runStore.listForTask(taskId);
|
|
549
|
+
const failedRun = runs.filter((r) => r.status === "failed").sort((a, b) => (b.finished_at ?? "").localeCompare(a.finished_at ?? ""))[0];
|
|
550
|
+
if (!failedRun) return null;
|
|
551
|
+
const error = failedRun.error ?? "Unknown error";
|
|
552
|
+
let output = "";
|
|
553
|
+
try {
|
|
554
|
+
const events = await this.runStore.readEventsTail(failedRun.id, maxOutputLines * 2);
|
|
555
|
+
const outputLines = events.filter((e) => e.type === "agent_output" || e.type === "error").map((e) => typeof e.data === "string" ? e.data : JSON.stringify(e.data)).join("\n").split("\n");
|
|
556
|
+
output = outputLines.slice(-maxOutputLines).join("\n");
|
|
557
|
+
} catch {
|
|
558
|
+
}
|
|
559
|
+
return { error, output };
|
|
560
|
+
}
|
|
561
|
+
};
|
|
562
|
+
var execFile = promisify(execFile$1);
|
|
563
|
+
var EXEC_TIMEOUT_MS = 3e3;
|
|
564
|
+
function isClipboardToolAvailable() {
|
|
565
|
+
const platform = process.platform;
|
|
566
|
+
if (platform === "darwin") {
|
|
567
|
+
return true;
|
|
568
|
+
}
|
|
569
|
+
if (platform === "linux") {
|
|
570
|
+
try {
|
|
571
|
+
execFileSync("which", ["xclip"], { timeout: EXEC_TIMEOUT_MS, stdio: "ignore" });
|
|
572
|
+
return true;
|
|
573
|
+
} catch {
|
|
574
|
+
return false;
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
if (platform === "win32") {
|
|
578
|
+
return true;
|
|
579
|
+
}
|
|
580
|
+
return false;
|
|
581
|
+
}
|
|
582
|
+
async function detectClipboardType() {
|
|
583
|
+
const platform = process.platform;
|
|
584
|
+
if (platform === "darwin") {
|
|
585
|
+
return detectMacOS();
|
|
586
|
+
}
|
|
587
|
+
if (platform === "linux") {
|
|
588
|
+
return detectLinux();
|
|
589
|
+
}
|
|
590
|
+
if (platform === "win32") {
|
|
591
|
+
return detectWindows();
|
|
592
|
+
}
|
|
593
|
+
throw new OrchestryError(
|
|
594
|
+
`Unsupported platform for clipboard: ${platform}`,
|
|
595
|
+
1,
|
|
596
|
+
"Supported: macOS, Linux, Windows"
|
|
597
|
+
);
|
|
598
|
+
}
|
|
599
|
+
async function getClipboardImage() {
|
|
600
|
+
const type = await detectClipboardType();
|
|
601
|
+
if (type !== "image") return null;
|
|
602
|
+
const platform = process.platform;
|
|
603
|
+
if (platform === "darwin") {
|
|
604
|
+
return getImageMacOS();
|
|
605
|
+
}
|
|
606
|
+
if (platform === "linux") {
|
|
607
|
+
return getImageLinux();
|
|
608
|
+
}
|
|
609
|
+
if (platform === "win32") {
|
|
610
|
+
return getImageWindows();
|
|
611
|
+
}
|
|
612
|
+
return null;
|
|
613
|
+
}
|
|
614
|
+
async function detectMacOS() {
|
|
615
|
+
try {
|
|
616
|
+
const { stdout } = await execFile("osascript", ["-e", "clipboard info"], {
|
|
617
|
+
timeout: EXEC_TIMEOUT_MS
|
|
618
|
+
});
|
|
619
|
+
if (stdout.includes("\xABclass PNGf\xBB") || stdout.includes("\xABclass TIFF\xBB")) {
|
|
620
|
+
return "image";
|
|
621
|
+
}
|
|
622
|
+
if (stdout.includes("\xABclass ut16\xBB") || stdout.includes("\xABclass utf8\xBB")) {
|
|
623
|
+
return "text";
|
|
624
|
+
}
|
|
625
|
+
return stdout.trim().length > 0 ? "text" : "empty";
|
|
626
|
+
} catch {
|
|
627
|
+
return "empty";
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
async function getImageMacOS() {
|
|
631
|
+
const dir = await mkdtemp(join(tmpdir(), "orch-clip-"));
|
|
632
|
+
const filePath = join(dir, "clipboard.png");
|
|
633
|
+
try {
|
|
634
|
+
const script = `
|
|
635
|
+
set theFile to POSIX file "${filePath}"
|
|
636
|
+
try
|
|
637
|
+
set imgData to the clipboard as \xABclass PNGf\xBB
|
|
638
|
+
set fRef to open for access theFile with write permission
|
|
639
|
+
write imgData to fRef
|
|
640
|
+
close access fRef
|
|
641
|
+
return "ok"
|
|
642
|
+
on error
|
|
643
|
+
try
|
|
644
|
+
close access theFile
|
|
645
|
+
end try
|
|
646
|
+
return "error"
|
|
647
|
+
end try
|
|
648
|
+
`;
|
|
649
|
+
const { stdout } = await execFile("osascript", ["-e", script], {
|
|
650
|
+
timeout: EXEC_TIMEOUT_MS
|
|
651
|
+
});
|
|
652
|
+
if (stdout.trim() !== "ok") return null;
|
|
653
|
+
const data = await readFile(filePath);
|
|
654
|
+
return { data, ext: "png" };
|
|
655
|
+
} catch {
|
|
656
|
+
return null;
|
|
657
|
+
} finally {
|
|
658
|
+
try {
|
|
659
|
+
await unlink(filePath);
|
|
660
|
+
} catch {
|
|
661
|
+
}
|
|
662
|
+
try {
|
|
663
|
+
await rm(dir, { recursive: true });
|
|
664
|
+
} catch {
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
async function detectLinux() {
|
|
669
|
+
try {
|
|
670
|
+
const { stdout } = await execFile(
|
|
671
|
+
"xclip",
|
|
672
|
+
["-selection", "clipboard", "-t", "TARGETS", "-o"],
|
|
673
|
+
{ timeout: EXEC_TIMEOUT_MS }
|
|
674
|
+
);
|
|
675
|
+
const targets = stdout.toLowerCase();
|
|
676
|
+
if (targets.includes("image/png") || targets.includes("image/tiff") || targets.includes("image/jpeg")) {
|
|
677
|
+
return "image";
|
|
678
|
+
}
|
|
679
|
+
if (targets.includes("text/plain") || targets.includes("utf8_string") || targets.includes("string")) {
|
|
680
|
+
return "text";
|
|
681
|
+
}
|
|
682
|
+
return targets.trim().length > 0 ? "text" : "empty";
|
|
683
|
+
} catch {
|
|
684
|
+
return "empty";
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
async function getImageLinux() {
|
|
688
|
+
try {
|
|
689
|
+
const { stdout } = await execFile(
|
|
690
|
+
"xclip",
|
|
691
|
+
["-selection", "clipboard", "-t", "image/png", "-o"],
|
|
692
|
+
{ timeout: EXEC_TIMEOUT_MS, encoding: "buffer", maxBuffer: 50 * 1024 * 1024 }
|
|
693
|
+
);
|
|
694
|
+
const data = Buffer.isBuffer(stdout) ? stdout : Buffer.from(stdout, "binary");
|
|
695
|
+
if (data.length === 0) return null;
|
|
696
|
+
return { data, ext: "png" };
|
|
697
|
+
} catch {
|
|
698
|
+
return null;
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
async function detectWindows() {
|
|
702
|
+
try {
|
|
703
|
+
const { stdout: imgCheck } = await execFile(
|
|
704
|
+
"powershell",
|
|
705
|
+
["-NoProfile", "-Command", 'if (Get-Clipboard -Format Image) { "image" } else { "none" }'],
|
|
706
|
+
{ timeout: EXEC_TIMEOUT_MS }
|
|
707
|
+
);
|
|
708
|
+
if (imgCheck.trim() === "image") return "image";
|
|
709
|
+
const { stdout: textCheck } = await execFile(
|
|
710
|
+
"powershell",
|
|
711
|
+
["-NoProfile", "-Command", 'if (Get-Clipboard) { "text" } else { "empty" }'],
|
|
712
|
+
{ timeout: EXEC_TIMEOUT_MS }
|
|
713
|
+
);
|
|
714
|
+
return textCheck.trim() === "text" ? "text" : "empty";
|
|
715
|
+
} catch {
|
|
716
|
+
return "empty";
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
async function getImageWindows() {
|
|
720
|
+
const dir = await mkdtemp(join(tmpdir(), "orch-clip-"));
|
|
721
|
+
const filePath = join(dir, "clipboard.png");
|
|
722
|
+
try {
|
|
723
|
+
const script = `
|
|
724
|
+
Add-Type -AssemblyName System.Windows.Forms
|
|
725
|
+
$img = [System.Windows.Forms.Clipboard]::GetImage()
|
|
726
|
+
if ($img) {
|
|
727
|
+
$img.Save('${filePath.replace(/\\/g, "\\\\")}', [System.Drawing.Imaging.ImageFormat]::Png)
|
|
728
|
+
Write-Output 'ok'
|
|
729
|
+
} else {
|
|
730
|
+
Write-Output 'error'
|
|
731
|
+
}
|
|
732
|
+
`;
|
|
733
|
+
const { stdout } = await execFile("powershell", ["-NoProfile", "-Command", script], {
|
|
734
|
+
timeout: EXEC_TIMEOUT_MS
|
|
735
|
+
});
|
|
736
|
+
if (stdout.trim() !== "ok") return null;
|
|
737
|
+
const data = await readFile(filePath);
|
|
738
|
+
return { data, ext: "png" };
|
|
739
|
+
} catch {
|
|
740
|
+
return null;
|
|
741
|
+
} finally {
|
|
742
|
+
try {
|
|
743
|
+
await unlink(filePath);
|
|
744
|
+
} catch {
|
|
745
|
+
}
|
|
746
|
+
try {
|
|
747
|
+
await rm(dir, { recursive: true });
|
|
748
|
+
} catch {
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
var TaskStore = class {
|
|
753
|
+
constructor(paths) {
|
|
754
|
+
this.paths = paths;
|
|
755
|
+
}
|
|
756
|
+
async list(filter) {
|
|
757
|
+
await ensureDir(this.paths.tasksDir);
|
|
758
|
+
const files = await listFiles(this.paths.tasksDir, ".yml");
|
|
759
|
+
const tasksResults = await Promise.all(
|
|
760
|
+
files.map((file) => {
|
|
761
|
+
const id = file.replace(".yml", "");
|
|
762
|
+
return readYaml(this.paths.taskPath(id));
|
|
763
|
+
})
|
|
764
|
+
);
|
|
765
|
+
const tasks = tasksResults.filter(
|
|
766
|
+
(task) => task !== null && (!filter?.status || task.status === filter.status) && (!filter?.goalId || task.goalId === filter.goalId)
|
|
767
|
+
);
|
|
768
|
+
return tasks.sort((a, b) => {
|
|
769
|
+
const statusOrder = statusPriority(a.status) - statusPriority(b.status);
|
|
770
|
+
if (statusOrder !== 0) return statusOrder;
|
|
771
|
+
const bTime = b.updated_at ?? "";
|
|
772
|
+
const aTime = a.updated_at ?? "";
|
|
773
|
+
return bTime < aTime ? -1 : bTime > aTime ? 1 : 0;
|
|
774
|
+
});
|
|
775
|
+
}
|
|
776
|
+
async get(id) {
|
|
777
|
+
return readYaml(this.paths.taskPath(id));
|
|
778
|
+
}
|
|
779
|
+
async save(task) {
|
|
780
|
+
await ensureDir(this.paths.tasksDir);
|
|
781
|
+
await writeYaml(this.paths.taskPath(task.id), task);
|
|
782
|
+
}
|
|
783
|
+
async delete(id) {
|
|
784
|
+
try {
|
|
785
|
+
await fs.unlink(this.paths.taskPath(id));
|
|
786
|
+
} catch (err) {
|
|
787
|
+
if (err.code !== "ENOENT") throw err;
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
};
|
|
791
|
+
function statusPriority(status) {
|
|
792
|
+
const order = {
|
|
793
|
+
in_progress: 0,
|
|
794
|
+
retrying: 1,
|
|
795
|
+
review: 2,
|
|
796
|
+
todo: 3,
|
|
797
|
+
done: 4,
|
|
798
|
+
failed: 5,
|
|
799
|
+
cancelled: 6
|
|
800
|
+
};
|
|
801
|
+
return order[status];
|
|
802
|
+
}
|
|
803
|
+
var AgentStore = class {
|
|
804
|
+
constructor(paths) {
|
|
805
|
+
this.paths = paths;
|
|
806
|
+
}
|
|
807
|
+
async list() {
|
|
808
|
+
await ensureDir(this.paths.agentsDir);
|
|
809
|
+
const files = await listFiles(this.paths.agentsDir, ".yml");
|
|
810
|
+
const results = await Promise.all(
|
|
811
|
+
files.map((file) => {
|
|
812
|
+
const id = file.replace(".yml", "");
|
|
813
|
+
return readYaml(this.paths.agentPath(id));
|
|
814
|
+
})
|
|
815
|
+
);
|
|
816
|
+
return results.filter((agent) => agent !== null);
|
|
817
|
+
}
|
|
818
|
+
async get(id) {
|
|
819
|
+
return readYaml(this.paths.agentPath(id));
|
|
820
|
+
}
|
|
821
|
+
async getByName(name) {
|
|
822
|
+
const agents = await this.list();
|
|
823
|
+
return agents.find((a) => a.name === name) ?? null;
|
|
824
|
+
}
|
|
825
|
+
async save(agent) {
|
|
826
|
+
await ensureDir(this.paths.agentsDir);
|
|
827
|
+
await writeYaml(this.paths.agentPath(agent.id), agent);
|
|
828
|
+
}
|
|
829
|
+
async delete(id) {
|
|
830
|
+
try {
|
|
831
|
+
await fs.unlink(this.paths.agentPath(id));
|
|
832
|
+
} catch (err) {
|
|
833
|
+
if (err.code !== "ENOENT") throw err;
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
};
|
|
837
|
+
var RunStore = class {
|
|
838
|
+
constructor(paths) {
|
|
839
|
+
this.paths = paths;
|
|
840
|
+
}
|
|
841
|
+
async save(run) {
|
|
842
|
+
await ensureDir(this.paths.runsDir);
|
|
843
|
+
await writeJson(this.paths.runPath(run.id), run);
|
|
844
|
+
}
|
|
845
|
+
async get(id) {
|
|
846
|
+
return readJson(this.paths.runPath(id));
|
|
847
|
+
}
|
|
848
|
+
async listAll() {
|
|
849
|
+
return this.listFiltered(() => true);
|
|
850
|
+
}
|
|
851
|
+
async listForTask(taskId) {
|
|
852
|
+
return this.listFiltered((run) => run.task_id === taskId);
|
|
853
|
+
}
|
|
854
|
+
async listForAgent(agentId) {
|
|
855
|
+
return this.listFiltered((run) => run.agent_id === agentId);
|
|
856
|
+
}
|
|
857
|
+
async appendEvent(runId, event) {
|
|
858
|
+
await ensureDir(this.paths.runsDir);
|
|
859
|
+
await appendJsonl(this.paths.runEventsPath(runId), event);
|
|
860
|
+
}
|
|
861
|
+
async readEvents(runId) {
|
|
862
|
+
return readJsonl(this.paths.runEventsPath(runId));
|
|
863
|
+
}
|
|
864
|
+
/**
|
|
865
|
+
* Read the last N events for a run without loading the entire JSONL file.
|
|
866
|
+
*/
|
|
867
|
+
async readEventsTail(runId, count) {
|
|
868
|
+
return readJsonlTail(this.paths.runEventsPath(runId), count);
|
|
869
|
+
}
|
|
870
|
+
async *streamEvents(runId, signal) {
|
|
871
|
+
const filePath = this.paths.runEventsPath(runId);
|
|
872
|
+
const deadline = Date.now() + 3e4;
|
|
873
|
+
while (!signal?.aborted && Date.now() < deadline) {
|
|
874
|
+
if (await pathExists(filePath)) break;
|
|
875
|
+
await new Promise((r) => setTimeout(r, 100));
|
|
876
|
+
}
|
|
877
|
+
if (signal?.aborted || Date.now() >= deadline) return;
|
|
878
|
+
const stream = createReadStream(filePath);
|
|
879
|
+
const { readLines } = await import('./process-manager-A36Y7LHP.js');
|
|
880
|
+
try {
|
|
881
|
+
for await (const line of readLines(stream)) {
|
|
882
|
+
if (signal?.aborted) break;
|
|
883
|
+
if (line.trim()) {
|
|
884
|
+
try {
|
|
885
|
+
yield JSON.parse(line);
|
|
886
|
+
} catch {
|
|
887
|
+
process.stderr.write(`[RunStore] skipping corrupt JSONL line: ${line.slice(0, 200)}
|
|
888
|
+
`);
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
} finally {
|
|
893
|
+
stream.destroy();
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
async listFiltered(predicate) {
|
|
897
|
+
await ensureDir(this.paths.runsDir);
|
|
898
|
+
const files = await listFiles(this.paths.runsDir, ".json");
|
|
899
|
+
const BATCH = 64;
|
|
900
|
+
const all = [];
|
|
901
|
+
for (let i = 0; i < files.length; i += BATCH) {
|
|
902
|
+
const batch = files.slice(i, i + BATCH);
|
|
903
|
+
const results = await Promise.all(
|
|
904
|
+
batch.map((file) => {
|
|
905
|
+
const id = file.endsWith(".json") ? file.slice(0, -5) : file;
|
|
906
|
+
return readJson(this.paths.runPath(id));
|
|
907
|
+
})
|
|
908
|
+
);
|
|
909
|
+
for (const run of results) {
|
|
910
|
+
if (run !== null && predicate(run)) all.push(run);
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
return all.sort(
|
|
914
|
+
(a, b) => new Date(b.started_at).getTime() - new Date(a.started_at).getTime()
|
|
915
|
+
);
|
|
916
|
+
}
|
|
917
|
+
};
|
|
918
|
+
|
|
919
|
+
// src/domain/state.ts
|
|
920
|
+
var DEFAULT_STATE = {
|
|
921
|
+
version: 1,
|
|
922
|
+
running: {},
|
|
923
|
+
claimed: /* @__PURE__ */ new Set(),
|
|
924
|
+
retry_queue: [],
|
|
925
|
+
stats: {
|
|
926
|
+
total_runs: 0,
|
|
927
|
+
total_tasks_completed: 0,
|
|
928
|
+
total_tasks_failed: 0,
|
|
929
|
+
total_tokens: { input: 0, output: 0, total: 0 },
|
|
930
|
+
total_runtime_ms: 0
|
|
931
|
+
}
|
|
932
|
+
};
|
|
933
|
+
|
|
934
|
+
// src/infrastructure/storage/state-store.ts
|
|
935
|
+
var StateStore = class {
|
|
936
|
+
constructor(paths) {
|
|
937
|
+
this.paths = paths;
|
|
938
|
+
}
|
|
939
|
+
async read() {
|
|
940
|
+
const raw = await readJson(this.paths.statePath);
|
|
941
|
+
if (!raw) return structuredClone(DEFAULT_STATE);
|
|
942
|
+
const defaults = structuredClone(DEFAULT_STATE);
|
|
943
|
+
return {
|
|
944
|
+
version: raw.version ?? defaults.version,
|
|
945
|
+
pid: raw.pid,
|
|
946
|
+
started_at: raw.started_at,
|
|
947
|
+
running: raw.running && typeof raw.running === "object" ? raw.running : defaults.running,
|
|
948
|
+
claimed: Array.isArray(raw.claimed) ? new Set(raw.claimed) : new Set(defaults.claimed),
|
|
949
|
+
retry_queue: Array.isArray(raw.retry_queue) ? raw.retry_queue : defaults.retry_queue,
|
|
950
|
+
stats: {
|
|
951
|
+
total_runs: raw.stats?.total_runs ?? defaults.stats.total_runs,
|
|
952
|
+
total_tasks_completed: raw.stats?.total_tasks_completed ?? defaults.stats.total_tasks_completed,
|
|
953
|
+
total_tasks_failed: raw.stats?.total_tasks_failed ?? defaults.stats.total_tasks_failed,
|
|
954
|
+
total_tokens: raw.stats?.total_tokens ?? defaults.stats.total_tokens,
|
|
955
|
+
total_runtime_ms: raw.stats?.total_runtime_ms ?? defaults.stats.total_runtime_ms
|
|
956
|
+
}
|
|
957
|
+
};
|
|
958
|
+
}
|
|
959
|
+
async write(state) {
|
|
960
|
+
const serializable = { ...state, claimed: Array.from(state.claimed) };
|
|
961
|
+
await writeJson(this.paths.statePath, serializable);
|
|
962
|
+
}
|
|
963
|
+
};
|
|
964
|
+
|
|
965
|
+
// src/domain/config.ts
|
|
966
|
+
var DEFAULT_CONFIG = {
|
|
967
|
+
project: {
|
|
968
|
+
name: "my-project"
|
|
969
|
+
},
|
|
970
|
+
defaults: {
|
|
971
|
+
agent: {
|
|
972
|
+
adapter: "claude",
|
|
973
|
+
approval_policy: "auto",
|
|
974
|
+
max_turns: 50,
|
|
975
|
+
timeout_ms: 36e5,
|
|
976
|
+
stall_timeout_ms: 3e5,
|
|
977
|
+
workspace_mode: "worktree"
|
|
978
|
+
},
|
|
979
|
+
task: {
|
|
980
|
+
max_attempts: 3,
|
|
981
|
+
priority: 3
|
|
982
|
+
}
|
|
983
|
+
},
|
|
984
|
+
scheduling: {
|
|
985
|
+
poll_interval_ms: 1e4,
|
|
986
|
+
max_concurrent_agents: 6,
|
|
987
|
+
retry_base_delay_ms: 1e4,
|
|
988
|
+
retry_max_delay_ms: 3e5
|
|
989
|
+
}
|
|
990
|
+
};
|
|
991
|
+
|
|
992
|
+
// src/infrastructure/storage/config-store.ts
|
|
993
|
+
var ConfigStore = class {
|
|
994
|
+
constructor(paths) {
|
|
995
|
+
this.paths = paths;
|
|
996
|
+
}
|
|
997
|
+
async read() {
|
|
998
|
+
const config = await readYaml(this.paths.configPath);
|
|
999
|
+
return deepMerge(
|
|
1000
|
+
DEFAULT_CONFIG,
|
|
1001
|
+
config ?? {}
|
|
1002
|
+
);
|
|
1003
|
+
}
|
|
1004
|
+
async write(config) {
|
|
1005
|
+
await writeYaml(this.paths.configPath, config);
|
|
1006
|
+
}
|
|
1007
|
+
async get(keyPath) {
|
|
1008
|
+
const config = await this.read();
|
|
1009
|
+
return getByPath(config, keyPath);
|
|
1010
|
+
}
|
|
1011
|
+
async set(keyPath, value) {
|
|
1012
|
+
const config = await this.read();
|
|
1013
|
+
setByPath(config, keyPath, value);
|
|
1014
|
+
await this.write(config);
|
|
1015
|
+
}
|
|
1016
|
+
};
|
|
1017
|
+
function getByPath(obj, keyPath) {
|
|
1018
|
+
const keys = keyPath.split(".");
|
|
1019
|
+
let current = obj;
|
|
1020
|
+
for (const key of keys) {
|
|
1021
|
+
if (current === null || current === void 0 || typeof current !== "object") {
|
|
1022
|
+
return void 0;
|
|
1023
|
+
}
|
|
1024
|
+
current = current[key];
|
|
1025
|
+
}
|
|
1026
|
+
return current;
|
|
1027
|
+
}
|
|
1028
|
+
function setByPath(obj, keyPath, value) {
|
|
1029
|
+
const keys = keyPath.split(".");
|
|
1030
|
+
let current = obj;
|
|
1031
|
+
for (let i = 0; i < keys.length - 1; i++) {
|
|
1032
|
+
const key = keys[i];
|
|
1033
|
+
if (typeof current[key] !== "object" || current[key] === null) {
|
|
1034
|
+
current[key] = {};
|
|
1035
|
+
}
|
|
1036
|
+
current = current[key];
|
|
1037
|
+
}
|
|
1038
|
+
const lastKey = keys[keys.length - 1];
|
|
1039
|
+
current[lastKey] = value;
|
|
1040
|
+
}
|
|
1041
|
+
function deepMerge(target, source) {
|
|
1042
|
+
const result = { ...target };
|
|
1043
|
+
for (const key of Object.keys(source)) {
|
|
1044
|
+
const sourceVal = source[key];
|
|
1045
|
+
const targetVal = result[key];
|
|
1046
|
+
if (sourceVal !== null && sourceVal !== void 0 && typeof sourceVal === "object" && !Array.isArray(sourceVal) && typeof targetVal === "object" && targetVal !== null && !Array.isArray(targetVal)) {
|
|
1047
|
+
result[key] = deepMerge(
|
|
1048
|
+
targetVal,
|
|
1049
|
+
sourceVal
|
|
1050
|
+
);
|
|
1051
|
+
} else {
|
|
1052
|
+
result[key] = sourceVal;
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
return result;
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
// src/domain/global-config.ts
|
|
1059
|
+
var DEFAULT_GLOBAL_CONFIG = {
|
|
1060
|
+
tui: {
|
|
1061
|
+
activity_filter: "all"
|
|
1062
|
+
}
|
|
1063
|
+
};
|
|
1064
|
+
|
|
1065
|
+
// src/infrastructure/storage/global-config-store.ts
|
|
1066
|
+
var GLOBAL_DIR = path.join(homedir(), ".orchestry");
|
|
1067
|
+
var GLOBAL_CONFIG_PATH = path.join(GLOBAL_DIR, "global.yml");
|
|
1068
|
+
var GlobalConfigStore = class {
|
|
1069
|
+
async read() {
|
|
1070
|
+
const data = await readYaml(GLOBAL_CONFIG_PATH);
|
|
1071
|
+
if (!data) return { ...DEFAULT_GLOBAL_CONFIG };
|
|
1072
|
+
return {
|
|
1073
|
+
tui: {
|
|
1074
|
+
activity_filter: data.tui?.activity_filter ?? DEFAULT_GLOBAL_CONFIG.tui.activity_filter
|
|
1075
|
+
}
|
|
1076
|
+
};
|
|
1077
|
+
}
|
|
1078
|
+
async write(config) {
|
|
1079
|
+
await mkdir(GLOBAL_DIR, { recursive: true });
|
|
1080
|
+
await writeYaml(GLOBAL_CONFIG_PATH, config);
|
|
1081
|
+
}
|
|
1082
|
+
async set(key, value) {
|
|
1083
|
+
const config = await this.read();
|
|
1084
|
+
config.tui[key] = value;
|
|
1085
|
+
await this.write(config);
|
|
1086
|
+
}
|
|
1087
|
+
};
|
|
1088
|
+
var ContextStore = class _ContextStore {
|
|
1089
|
+
constructor(paths) {
|
|
1090
|
+
this.paths = paths;
|
|
1091
|
+
}
|
|
1092
|
+
async get(key) {
|
|
1093
|
+
const entry = await readJson(this.paths.contextPath(key));
|
|
1094
|
+
if (!entry) return null;
|
|
1095
|
+
if (isExpired(entry)) {
|
|
1096
|
+
await this.delete(key);
|
|
1097
|
+
return null;
|
|
1098
|
+
}
|
|
1099
|
+
return entry;
|
|
1100
|
+
}
|
|
1101
|
+
/** Max TTL: 30 days in milliseconds */
|
|
1102
|
+
static MAX_TTL_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
1103
|
+
async set(key, value, ttlMs) {
|
|
1104
|
+
if (ttlMs !== void 0) {
|
|
1105
|
+
if (!Number.isFinite(ttlMs) || ttlMs <= 0 || ttlMs > _ContextStore.MAX_TTL_MS) {
|
|
1106
|
+
throw new Error(`TTL must be a positive number up to ${_ContextStore.MAX_TTL_MS}ms (30 days)`);
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
await ensureDir(this.paths.contextDir);
|
|
1110
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1111
|
+
const existing = await readJson(this.paths.contextPath(key));
|
|
1112
|
+
const entry = {
|
|
1113
|
+
key,
|
|
1114
|
+
value,
|
|
1115
|
+
created_at: existing?.created_at ?? now,
|
|
1116
|
+
updated_at: now,
|
|
1117
|
+
ttl_ms: ttlMs,
|
|
1118
|
+
expires_at: ttlMs ? new Date(Date.now() + ttlMs).toISOString() : void 0
|
|
1119
|
+
};
|
|
1120
|
+
await writeJson(this.paths.contextPath(key), entry);
|
|
1121
|
+
}
|
|
1122
|
+
async delete(key) {
|
|
1123
|
+
try {
|
|
1124
|
+
await fs.unlink(this.paths.contextPath(key));
|
|
1125
|
+
} catch (err) {
|
|
1126
|
+
if (err.code !== "ENOENT") throw err;
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
async list() {
|
|
1130
|
+
await ensureDir(this.paths.contextDir);
|
|
1131
|
+
const files = await listFiles(this.paths.contextDir, ".json");
|
|
1132
|
+
const results = await Promise.all(
|
|
1133
|
+
files.map((file) => {
|
|
1134
|
+
const key = file.replace(".json", "");
|
|
1135
|
+
return readJson(this.paths.contextPath(key));
|
|
1136
|
+
})
|
|
1137
|
+
);
|
|
1138
|
+
const entries = [];
|
|
1139
|
+
for (const entry of results) {
|
|
1140
|
+
if (!entry) continue;
|
|
1141
|
+
if (isExpired(entry)) {
|
|
1142
|
+
await this.delete(entry.key);
|
|
1143
|
+
continue;
|
|
1144
|
+
}
|
|
1145
|
+
entries.push(entry);
|
|
1146
|
+
}
|
|
1147
|
+
return entries.sort((a, b) => a.key.localeCompare(b.key));
|
|
1148
|
+
}
|
|
1149
|
+
async getAll() {
|
|
1150
|
+
const entries = await this.list();
|
|
1151
|
+
const result = {};
|
|
1152
|
+
for (const entry of entries) {
|
|
1153
|
+
result[entry.key] = entry.value;
|
|
1154
|
+
}
|
|
1155
|
+
return result;
|
|
1156
|
+
}
|
|
1157
|
+
};
|
|
1158
|
+
function isExpired(entry) {
|
|
1159
|
+
if (!entry.expires_at) return false;
|
|
1160
|
+
return new Date(entry.expires_at).getTime() < Date.now();
|
|
1161
|
+
}
|
|
1162
|
+
var MessageStore = class {
|
|
1163
|
+
constructor(paths) {
|
|
1164
|
+
this.paths = paths;
|
|
1165
|
+
}
|
|
1166
|
+
async save(message) {
|
|
1167
|
+
await ensureDir(this.paths.messagesDir);
|
|
1168
|
+
await writeJson(this.paths.messagePath(message.id), message);
|
|
1169
|
+
}
|
|
1170
|
+
async get(id) {
|
|
1171
|
+
return readJson(this.paths.messagePath(id));
|
|
1172
|
+
}
|
|
1173
|
+
async list() {
|
|
1174
|
+
const files = await listFiles(this.paths.messagesDir, ".json");
|
|
1175
|
+
const results = await Promise.all(
|
|
1176
|
+
files.map((f) => readJson(this.paths.messagePath(f.replace(".json", ""))))
|
|
1177
|
+
);
|
|
1178
|
+
return results.filter((m) => m !== null).sort((a, b) => a.created_at.localeCompare(b.created_at));
|
|
1179
|
+
}
|
|
1180
|
+
async listPending(agentId) {
|
|
1181
|
+
const all = await this.list();
|
|
1182
|
+
const now = Date.now();
|
|
1183
|
+
return all.filter((m) => {
|
|
1184
|
+
if (m.status !== "pending") return false;
|
|
1185
|
+
if (m.expires_at && new Date(m.expires_at).getTime() < now) return false;
|
|
1186
|
+
return m.to_agent_id === agentId;
|
|
1187
|
+
});
|
|
1188
|
+
}
|
|
1189
|
+
async markDelivered(id) {
|
|
1190
|
+
const msg = await this.get(id);
|
|
1191
|
+
if (!msg) return;
|
|
1192
|
+
msg.status = "delivered";
|
|
1193
|
+
msg.delivered_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
1194
|
+
await writeJson(this.paths.messagePath(id), msg);
|
|
1195
|
+
}
|
|
1196
|
+
async delete(id) {
|
|
1197
|
+
try {
|
|
1198
|
+
await fs.unlink(this.paths.messagePath(id));
|
|
1199
|
+
} catch (err) {
|
|
1200
|
+
if (err.code !== "ENOENT") throw err;
|
|
1201
|
+
}
|
|
1202
|
+
}
|
|
1203
|
+
async purgeExpired() {
|
|
1204
|
+
const all = await this.list();
|
|
1205
|
+
const now = Date.now();
|
|
1206
|
+
const toDelete = all.filter((m) => {
|
|
1207
|
+
const isExpired2 = m.expires_at && new Date(m.expires_at).getTime() < now;
|
|
1208
|
+
const isOldDelivered = m.delivered_at && now - new Date(m.delivered_at).getTime() > 36e5;
|
|
1209
|
+
return isExpired2 || isOldDelivered;
|
|
1210
|
+
});
|
|
1211
|
+
await Promise.all(toDelete.map((m) => this.delete(m.id)));
|
|
1212
|
+
return toDelete.length;
|
|
1213
|
+
}
|
|
1214
|
+
};
|
|
1215
|
+
|
|
1216
|
+
// src/domain/goal.ts
|
|
1217
|
+
var TERMINAL_GOAL_STATUSES = /* @__PURE__ */ new Set(["achieved", "abandoned"]);
|
|
1218
|
+
function isGoalTerminal(status) {
|
|
1219
|
+
return TERMINAL_GOAL_STATUSES.has(status);
|
|
1220
|
+
}
|
|
1221
|
+
var GOAL_STATUS_ORDER = {
|
|
1222
|
+
active: 0,
|
|
1223
|
+
paused: 1,
|
|
1224
|
+
achieved: 2,
|
|
1225
|
+
abandoned: 3
|
|
1226
|
+
};
|
|
1227
|
+
var GoalStore = class {
|
|
1228
|
+
constructor(paths) {
|
|
1229
|
+
this.paths = paths;
|
|
1230
|
+
}
|
|
1231
|
+
async list(filter) {
|
|
1232
|
+
const files = await listFiles(this.paths.goalsDir, ".yml");
|
|
1233
|
+
const results = await Promise.all(
|
|
1234
|
+
files.map((file) => {
|
|
1235
|
+
const id = file.replace(".yml", "");
|
|
1236
|
+
return readYaml(this.paths.goalPath(id));
|
|
1237
|
+
})
|
|
1238
|
+
);
|
|
1239
|
+
const goals = results.filter(
|
|
1240
|
+
(goal) => goal !== null && (!filter?.status || goal.status === filter.status)
|
|
1241
|
+
);
|
|
1242
|
+
return goals.sort((a, b) => {
|
|
1243
|
+
const statusOrder = GOAL_STATUS_ORDER[a.status] - GOAL_STATUS_ORDER[b.status];
|
|
1244
|
+
if (statusOrder !== 0) return statusOrder;
|
|
1245
|
+
const bTime = b.updated_at ?? "";
|
|
1246
|
+
const aTime = a.updated_at ?? "";
|
|
1247
|
+
return bTime < aTime ? -1 : bTime > aTime ? 1 : 0;
|
|
1248
|
+
});
|
|
1249
|
+
}
|
|
1250
|
+
async get(id) {
|
|
1251
|
+
return readYaml(this.paths.goalPath(id));
|
|
1252
|
+
}
|
|
1253
|
+
async save(goal) {
|
|
1254
|
+
await writeYaml(this.paths.goalPath(goal.id), goal);
|
|
1255
|
+
}
|
|
1256
|
+
async delete(id) {
|
|
1257
|
+
try {
|
|
1258
|
+
await fs.unlink(this.paths.goalPath(id));
|
|
1259
|
+
} catch (err) {
|
|
1260
|
+
if (err.code !== "ENOENT") throw err;
|
|
1261
|
+
}
|
|
1262
|
+
}
|
|
1263
|
+
};
|
|
1264
|
+
var TeamStore = class {
|
|
1265
|
+
constructor(paths) {
|
|
1266
|
+
this.paths = paths;
|
|
1267
|
+
}
|
|
1268
|
+
async save(team) {
|
|
1269
|
+
await ensureDir(this.paths.teamsDir);
|
|
1270
|
+
await writeYaml(this.paths.teamPath(team.id), team);
|
|
1271
|
+
}
|
|
1272
|
+
async get(id) {
|
|
1273
|
+
return readYaml(this.paths.teamPath(id));
|
|
1274
|
+
}
|
|
1275
|
+
async getByName(name) {
|
|
1276
|
+
const teams = await this.list();
|
|
1277
|
+
return teams.find((t) => t.name === name) ?? null;
|
|
1278
|
+
}
|
|
1279
|
+
async list() {
|
|
1280
|
+
await ensureDir(this.paths.teamsDir);
|
|
1281
|
+
const files = await listFiles(this.paths.teamsDir, ".yml");
|
|
1282
|
+
const results = await Promise.all(
|
|
1283
|
+
files.map((f) => readYaml(this.paths.teamPath(f.replace(".yml", ""))))
|
|
1284
|
+
);
|
|
1285
|
+
return results.filter((t) => t !== null);
|
|
1286
|
+
}
|
|
1287
|
+
async delete(id) {
|
|
1288
|
+
try {
|
|
1289
|
+
await fs.unlink(this.paths.teamPath(id));
|
|
1290
|
+
} catch (err) {
|
|
1291
|
+
if (err.code !== "ENOENT") throw err;
|
|
1292
|
+
}
|
|
1293
|
+
}
|
|
1294
|
+
};
|
|
1295
|
+
|
|
1296
|
+
// src/domain/message.ts
|
|
1297
|
+
var MAX_MESSAGE_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
1298
|
+
var DEFAULT_MESSAGE_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
1299
|
+
|
|
1300
|
+
// src/application/message-service.ts
|
|
1301
|
+
var MessageService = class {
|
|
1302
|
+
constructor(messageStore, agentStore, teamStore, eventBus) {
|
|
1303
|
+
this.messageStore = messageStore;
|
|
1304
|
+
this.agentStore = agentStore;
|
|
1305
|
+
this.teamStore = teamStore;
|
|
1306
|
+
this.eventBus = eventBus;
|
|
1307
|
+
}
|
|
1308
|
+
/**
|
|
1309
|
+
* Send a message. For broadcast, creates one message per recipient agent.
|
|
1310
|
+
* For 'lead' channel, resolves team lead and sends direct.
|
|
1311
|
+
*/
|
|
1312
|
+
async send(input) {
|
|
1313
|
+
if (!input.body.trim()) throw new InvalidArgumentsError("Message body is required");
|
|
1314
|
+
const ttlMs = input.ttl_ms ?? DEFAULT_MESSAGE_TTL_MS;
|
|
1315
|
+
if (ttlMs <= 0 || ttlMs > MAX_MESSAGE_TTL_MS) {
|
|
1316
|
+
throw new InvalidArgumentsError(`TTL must be between 1ms and ${MAX_MESSAGE_TTL_MS}ms`);
|
|
1317
|
+
}
|
|
1318
|
+
const sender = await this.agentStore.get(input.from_agent_id);
|
|
1319
|
+
if (!sender && input.from_agent_id !== "cli") {
|
|
1320
|
+
throw new InvalidArgumentsError(`Sender agent not found: ${input.from_agent_id}`);
|
|
1321
|
+
}
|
|
1322
|
+
const now = /* @__PURE__ */ new Date();
|
|
1323
|
+
const baseMessage = {
|
|
1324
|
+
channel: input.channel,
|
|
1325
|
+
from_agent_id: input.from_agent_id,
|
|
1326
|
+
subject: (input.subject || "(no subject)").slice(0, 200),
|
|
1327
|
+
body: input.body.slice(0, 4e3),
|
|
1328
|
+
created_at: now.toISOString(),
|
|
1329
|
+
expires_at: new Date(now.getTime() + ttlMs).toISOString(),
|
|
1330
|
+
status: "pending",
|
|
1331
|
+
team_id: input.team_id,
|
|
1332
|
+
reply_to: input.reply_to
|
|
1333
|
+
};
|
|
1334
|
+
const messages = [];
|
|
1335
|
+
if (input.channel === "broadcast") {
|
|
1336
|
+
let agents = await this.agentStore.list();
|
|
1337
|
+
if (input.team_id) {
|
|
1338
|
+
const team = await this.teamStore.get(input.team_id);
|
|
1339
|
+
if (team) {
|
|
1340
|
+
const memberIds = new Set(team.members.map((m) => m.agent_id));
|
|
1341
|
+
agents = agents.filter((a) => memberIds.has(a.id));
|
|
1342
|
+
}
|
|
1343
|
+
}
|
|
1344
|
+
const recipients = agents.filter((a) => a.id !== input.from_agent_id && a.status !== "disabled");
|
|
1345
|
+
const broadcastMsgs = recipients.map((agent) => ({
|
|
1346
|
+
...baseMessage,
|
|
1347
|
+
id: `msg_${nanoid(7)}`,
|
|
1348
|
+
to_agent_id: agent.id
|
|
1349
|
+
}));
|
|
1350
|
+
await Promise.all(broadcastMsgs.map((msg) => this.messageStore.save(msg)));
|
|
1351
|
+
for (const msg of broadcastMsgs) {
|
|
1352
|
+
messages.push(msg);
|
|
1353
|
+
this.emitSent(msg);
|
|
1354
|
+
}
|
|
1355
|
+
} else if (input.channel === "lead") {
|
|
1356
|
+
if (!input.team_id) throw new InvalidArgumentsError("team_id is required for lead channel");
|
|
1357
|
+
const team = await this.teamStore.get(input.team_id);
|
|
1358
|
+
if (!team) throw new InvalidArgumentsError(`Team not found: ${input.team_id}`);
|
|
1359
|
+
const msg = {
|
|
1360
|
+
...baseMessage,
|
|
1361
|
+
id: `msg_${nanoid(7)}`,
|
|
1362
|
+
to_agent_id: team.lead_agent_id
|
|
1363
|
+
};
|
|
1364
|
+
await this.messageStore.save(msg);
|
|
1365
|
+
messages.push(msg);
|
|
1366
|
+
this.emitSent(msg);
|
|
1367
|
+
} else {
|
|
1368
|
+
if (!input.to_agent_id) throw new InvalidArgumentsError("to_agent_id is required for direct messages");
|
|
1369
|
+
const recipient = await this.agentStore.get(input.to_agent_id);
|
|
1370
|
+
if (!recipient) throw new InvalidArgumentsError(`Recipient agent not found: ${input.to_agent_id}`);
|
|
1371
|
+
const msg = {
|
|
1372
|
+
...baseMessage,
|
|
1373
|
+
id: `msg_${nanoid(7)}`,
|
|
1374
|
+
to_agent_id: input.to_agent_id
|
|
1375
|
+
};
|
|
1376
|
+
await this.messageStore.save(msg);
|
|
1377
|
+
messages.push(msg);
|
|
1378
|
+
this.emitSent(msg);
|
|
1379
|
+
}
|
|
1380
|
+
return messages;
|
|
1381
|
+
}
|
|
1382
|
+
/**
|
|
1383
|
+
* Drain mailbox: fetch pending messages for an agent and mark them delivered.
|
|
1384
|
+
* Called by the orchestrator during dispatchTask.
|
|
1385
|
+
*/
|
|
1386
|
+
async drainMailbox(agentId, taskId) {
|
|
1387
|
+
const pending = await this.messageStore.listPending(agentId);
|
|
1388
|
+
await Promise.all(pending.map((msg) => this.messageStore.markDelivered(msg.id)));
|
|
1389
|
+
for (const msg of pending) {
|
|
1390
|
+
this.eventBus.emit({
|
|
1391
|
+
type: "message:delivered",
|
|
1392
|
+
messageId: msg.id,
|
|
1393
|
+
toAgentId: agentId,
|
|
1394
|
+
taskId
|
|
1395
|
+
});
|
|
1396
|
+
}
|
|
1397
|
+
return pending;
|
|
1398
|
+
}
|
|
1399
|
+
async listAll() {
|
|
1400
|
+
return this.messageStore.list();
|
|
1401
|
+
}
|
|
1402
|
+
async listPendingForAgent(agentId) {
|
|
1403
|
+
return this.messageStore.listPending(agentId);
|
|
1404
|
+
}
|
|
1405
|
+
async listForAgent(agentId) {
|
|
1406
|
+
const all = await this.messageStore.list();
|
|
1407
|
+
return all.filter((m) => m.to_agent_id === agentId || m.from_agent_id === agentId);
|
|
1408
|
+
}
|
|
1409
|
+
async purgeExpired() {
|
|
1410
|
+
return this.messageStore.purgeExpired();
|
|
1411
|
+
}
|
|
1412
|
+
emitSent(msg) {
|
|
1413
|
+
this.eventBus.emit({
|
|
1414
|
+
type: "message:sent",
|
|
1415
|
+
messageId: msg.id,
|
|
1416
|
+
fromAgentId: msg.from_agent_id,
|
|
1417
|
+
toAgentId: msg.to_agent_id,
|
|
1418
|
+
channel: msg.channel
|
|
1419
|
+
});
|
|
1420
|
+
}
|
|
1421
|
+
};
|
|
1422
|
+
var VALID_TRANSITIONS = {
|
|
1423
|
+
active: ["paused", "achieved", "abandoned"],
|
|
1424
|
+
paused: ["active", "abandoned"],
|
|
1425
|
+
achieved: [],
|
|
1426
|
+
abandoned: []
|
|
1427
|
+
};
|
|
1428
|
+
var GoalService = class {
|
|
1429
|
+
constructor(goalStore, eventBus, agentService, taskService, contextStore) {
|
|
1430
|
+
this.goalStore = goalStore;
|
|
1431
|
+
this.eventBus = eventBus;
|
|
1432
|
+
this.agentService = agentService;
|
|
1433
|
+
this.taskService = taskService;
|
|
1434
|
+
this.contextStore = contextStore;
|
|
1435
|
+
}
|
|
1436
|
+
async create(input) {
|
|
1437
|
+
if (!input.title.trim()) {
|
|
1438
|
+
throw new InvalidArgumentsError("Goal title is required");
|
|
1439
|
+
}
|
|
1440
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1441
|
+
const goal = {
|
|
1442
|
+
id: `goal_${nanoid(7)}`,
|
|
1443
|
+
title: input.title.trim(),
|
|
1444
|
+
description: input.description?.trim() ?? "",
|
|
1445
|
+
status: "active",
|
|
1446
|
+
assignee: input.assignee,
|
|
1447
|
+
created_at: now,
|
|
1448
|
+
updated_at: now
|
|
1449
|
+
};
|
|
1450
|
+
await this.goalStore.save(goal);
|
|
1451
|
+
this.eventBus.emit({ type: "goal:created", goalId: goal.id, title: goal.title });
|
|
1452
|
+
if (goal.assignee) {
|
|
1453
|
+
await this.enableAutonomous(goal.assignee);
|
|
1454
|
+
}
|
|
1455
|
+
return goal;
|
|
1456
|
+
}
|
|
1457
|
+
async list(filter) {
|
|
1458
|
+
return this.goalStore.list(filter);
|
|
1459
|
+
}
|
|
1460
|
+
async get(id) {
|
|
1461
|
+
const goal = await this.goalStore.get(id);
|
|
1462
|
+
if (!goal) throw new GoalNotFoundError(id);
|
|
1463
|
+
return goal;
|
|
1464
|
+
}
|
|
1465
|
+
async updateStatus(id, newStatus) {
|
|
1466
|
+
const goal = await this.get(id);
|
|
1467
|
+
const oldStatus = goal.status;
|
|
1468
|
+
if (!VALID_TRANSITIONS[oldStatus].includes(newStatus)) {
|
|
1469
|
+
throw new InvalidArgumentsError(
|
|
1470
|
+
`Cannot transition goal from '${oldStatus}' to '${newStatus}'`
|
|
1471
|
+
);
|
|
1472
|
+
}
|
|
1473
|
+
goal.status = newStatus;
|
|
1474
|
+
goal.updated_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
1475
|
+
await this.goalStore.save(goal);
|
|
1476
|
+
this.eventBus.emit({ type: "goal:status_changed", goalId: id, from: oldStatus, to: newStatus });
|
|
1477
|
+
if (goal.assignee) {
|
|
1478
|
+
if (newStatus === "paused") {
|
|
1479
|
+
await this.maybeDisableAutonomous(goal.assignee);
|
|
1480
|
+
await this.cancelPendingAutonomousTasks(goal.assignee);
|
|
1481
|
+
} else if (newStatus === "active" && oldStatus === "paused") {
|
|
1482
|
+
await this.enableAutonomous(goal.assignee);
|
|
1483
|
+
} else if (isGoalTerminal(newStatus)) {
|
|
1484
|
+
await this.maybeDisableAutonomous(goal.assignee);
|
|
1485
|
+
}
|
|
1486
|
+
}
|
|
1487
|
+
return goal;
|
|
1488
|
+
}
|
|
1489
|
+
async update(id, fields) {
|
|
1490
|
+
const goal = await this.get(id);
|
|
1491
|
+
const oldAssignee = goal.assignee;
|
|
1492
|
+
if (fields.title !== void 0) {
|
|
1493
|
+
if (!fields.title.trim()) throw new InvalidArgumentsError("Goal title cannot be empty");
|
|
1494
|
+
goal.title = fields.title.trim();
|
|
1495
|
+
}
|
|
1496
|
+
if (fields.description !== void 0) goal.description = fields.description.trim();
|
|
1497
|
+
if (fields.assignee !== void 0) goal.assignee = fields.assignee || void 0;
|
|
1498
|
+
goal.updated_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
1499
|
+
await this.goalStore.save(goal);
|
|
1500
|
+
this.eventBus.emit({ type: "goal:updated", goalId: id });
|
|
1501
|
+
const newAssignee = goal.assignee;
|
|
1502
|
+
if (newAssignee !== oldAssignee) {
|
|
1503
|
+
const ops = [];
|
|
1504
|
+
if (newAssignee) ops.push(this.enableAutonomous(newAssignee));
|
|
1505
|
+
if (oldAssignee) ops.push(this.maybeDisableAutonomous(oldAssignee));
|
|
1506
|
+
await Promise.all(ops);
|
|
1507
|
+
}
|
|
1508
|
+
return goal;
|
|
1509
|
+
}
|
|
1510
|
+
async delete(id) {
|
|
1511
|
+
const goal = await this.get(id);
|
|
1512
|
+
const { assignee } = goal;
|
|
1513
|
+
await this.goalStore.delete(id);
|
|
1514
|
+
this.eventBus.emit({ type: "goal:deleted", goalId: id });
|
|
1515
|
+
if (assignee) {
|
|
1516
|
+
await this.maybeDisableAutonomous(assignee);
|
|
1517
|
+
}
|
|
1518
|
+
}
|
|
1519
|
+
async listTasksForGoal(goalId) {
|
|
1520
|
+
return this.taskService?.list({ goalId }) ?? [];
|
|
1521
|
+
}
|
|
1522
|
+
async getProgressReport(goalId) {
|
|
1523
|
+
if (!this.contextStore) return void 0;
|
|
1524
|
+
const entry = await this.contextStore.get(`${goalId}-progress`);
|
|
1525
|
+
return entry?.value;
|
|
1526
|
+
}
|
|
1527
|
+
/** Enable autonomous mode on an agent. */
|
|
1528
|
+
async enableAutonomous(agentId) {
|
|
1529
|
+
if (!this.agentService) return;
|
|
1530
|
+
try {
|
|
1531
|
+
await this.agentService.setAutonomous(agentId, true);
|
|
1532
|
+
} catch {
|
|
1533
|
+
}
|
|
1534
|
+
}
|
|
1535
|
+
/** Check if an agent has at least one active goal. */
|
|
1536
|
+
async hasActiveGoalsForAgent(agentId) {
|
|
1537
|
+
const activeGoals = await this.goalStore.list({ status: "active" });
|
|
1538
|
+
return activeGoals.some((g) => g.assignee === agentId);
|
|
1539
|
+
}
|
|
1540
|
+
/** Cancel dispatchable (todo/retrying) autonomous tasks assigned to the agent. */
|
|
1541
|
+
async cancelPendingAutonomousTasks(agentId) {
|
|
1542
|
+
if (!this.taskService) return;
|
|
1543
|
+
try {
|
|
1544
|
+
const [todos, retrying] = await Promise.all([
|
|
1545
|
+
this.taskService.list({ status: "todo" }),
|
|
1546
|
+
this.taskService.list({ status: "retrying" })
|
|
1547
|
+
]);
|
|
1548
|
+
const pending = [...todos, ...retrying].filter(
|
|
1549
|
+
(t) => t.assignee === agentId && t.labels?.includes(AUTONOMOUS_LABEL)
|
|
1550
|
+
);
|
|
1551
|
+
await Promise.all(pending.map((t) => this.taskService.cancel(t.id).catch(() => {
|
|
1552
|
+
})));
|
|
1553
|
+
} catch {
|
|
1554
|
+
}
|
|
1555
|
+
}
|
|
1556
|
+
/** Disable autonomous if agent has no other active goals. */
|
|
1557
|
+
async maybeDisableAutonomous(agentId) {
|
|
1558
|
+
if (!this.agentService) return;
|
|
1559
|
+
try {
|
|
1560
|
+
if (!await this.hasActiveGoalsForAgent(agentId)) {
|
|
1561
|
+
await this.agentService.setAutonomous(agentId, false);
|
|
1562
|
+
}
|
|
1563
|
+
} catch {
|
|
1564
|
+
}
|
|
1565
|
+
}
|
|
1566
|
+
};
|
|
1567
|
+
|
|
1568
|
+
// src/domain/team.ts
|
|
1569
|
+
var DEFAULT_TEAM_CONFIG = {
|
|
1570
|
+
auto_claim: true,
|
|
1571
|
+
message_ttl_ms: 24 * 60 * 60 * 1e3
|
|
1572
|
+
};
|
|
1573
|
+
|
|
1574
|
+
// src/application/team-service.ts
|
|
1575
|
+
var TeamService = class {
|
|
1576
|
+
constructor(teamStore, agentStore, taskStore, eventBus) {
|
|
1577
|
+
this.teamStore = teamStore;
|
|
1578
|
+
this.agentStore = agentStore;
|
|
1579
|
+
this.taskStore = taskStore;
|
|
1580
|
+
this.eventBus = eventBus;
|
|
1581
|
+
}
|
|
1582
|
+
async create(input) {
|
|
1583
|
+
if (!input.name.trim()) throw new InvalidArgumentsError("Team name is required");
|
|
1584
|
+
const lead = await this.agentStore.get(input.lead_agent_id);
|
|
1585
|
+
if (!lead) throw new InvalidArgumentsError(`Lead agent not found: ${input.lead_agent_id}`);
|
|
1586
|
+
const existing = await this.teamStore.getByName(input.name.trim());
|
|
1587
|
+
if (existing) throw new InvalidArgumentsError(`Team "${input.name}" already exists`);
|
|
1588
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1589
|
+
const leadMember = { agent_id: input.lead_agent_id, role: "lead", joined_at: now };
|
|
1590
|
+
const additionalMembers = [];
|
|
1591
|
+
for (const agentId of input.member_agent_ids ?? []) {
|
|
1592
|
+
if (agentId === input.lead_agent_id) continue;
|
|
1593
|
+
const agent = await this.agentStore.get(agentId);
|
|
1594
|
+
if (!agent) throw new InvalidArgumentsError(`Member agent not found: ${agentId}`);
|
|
1595
|
+
additionalMembers.push({ agent_id: agentId, role: "member", joined_at: now });
|
|
1596
|
+
}
|
|
1597
|
+
const team = {
|
|
1598
|
+
id: `team_${nanoid(7)}`,
|
|
1599
|
+
name: input.name.trim(),
|
|
1600
|
+
description: input.description,
|
|
1601
|
+
status: "active",
|
|
1602
|
+
members: [leadMember, ...additionalMembers],
|
|
1603
|
+
task_pool: [],
|
|
1604
|
+
lead_agent_id: input.lead_agent_id,
|
|
1605
|
+
created_at: now,
|
|
1606
|
+
updated_at: now,
|
|
1607
|
+
config: { ...DEFAULT_TEAM_CONFIG, ...input.config ?? {} }
|
|
1608
|
+
};
|
|
1609
|
+
await this.teamStore.save(team);
|
|
1610
|
+
this.eventBus.emit({ type: "team:created", teamId: team.id, name: team.name, leadAgentId: team.lead_agent_id });
|
|
1611
|
+
for (const member of additionalMembers) {
|
|
1612
|
+
this.eventBus.emit({ type: "team:member_joined", teamId: team.id, agentId: member.agent_id });
|
|
1613
|
+
}
|
|
1614
|
+
return team;
|
|
1615
|
+
}
|
|
1616
|
+
async get(id) {
|
|
1617
|
+
const team = await this.teamStore.get(id);
|
|
1618
|
+
if (!team) throw new TeamNotFoundError(id);
|
|
1619
|
+
return team;
|
|
1620
|
+
}
|
|
1621
|
+
async list() {
|
|
1622
|
+
return this.teamStore.list();
|
|
1623
|
+
}
|
|
1624
|
+
async join(teamId, agentId) {
|
|
1625
|
+
const team = await this.get(teamId);
|
|
1626
|
+
if (team.members.some((m) => m.agent_id === agentId)) {
|
|
1627
|
+
throw new InvalidArgumentsError(`Agent ${agentId} is already a member of team ${teamId}`);
|
|
1628
|
+
}
|
|
1629
|
+
const agent = await this.agentStore.get(agentId);
|
|
1630
|
+
if (!agent) throw new InvalidArgumentsError(`Agent not found: ${agentId}`);
|
|
1631
|
+
team.members.push({ agent_id: agentId, role: "member", joined_at: (/* @__PURE__ */ new Date()).toISOString() });
|
|
1632
|
+
team.updated_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
1633
|
+
await this.teamStore.save(team);
|
|
1634
|
+
this.eventBus.emit({ type: "team:member_joined", teamId, agentId });
|
|
1635
|
+
return team;
|
|
1636
|
+
}
|
|
1637
|
+
async leave(teamId, agentId) {
|
|
1638
|
+
const team = await this.get(teamId);
|
|
1639
|
+
if (agentId === team.lead_agent_id) {
|
|
1640
|
+
throw new InvalidArgumentsError("Lead cannot leave team. Disband the team or transfer lead first.");
|
|
1641
|
+
}
|
|
1642
|
+
team.members = team.members.filter((m) => m.agent_id !== agentId);
|
|
1643
|
+
team.updated_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
1644
|
+
await this.teamStore.save(team);
|
|
1645
|
+
this.eventBus.emit({ type: "team:member_left", teamId, agentId });
|
|
1646
|
+
return team;
|
|
1647
|
+
}
|
|
1648
|
+
async addTask(teamId, taskId) {
|
|
1649
|
+
const team = await this.get(teamId);
|
|
1650
|
+
const task = await this.taskStore.get(taskId);
|
|
1651
|
+
if (!task) throw new InvalidArgumentsError(`Task not found: ${taskId}`);
|
|
1652
|
+
if (!team.task_pool.includes(taskId)) {
|
|
1653
|
+
team.task_pool.push(taskId);
|
|
1654
|
+
team.updated_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
1655
|
+
await this.teamStore.save(team);
|
|
1656
|
+
this.eventBus.emit({ type: "team:task_added", teamId, taskId });
|
|
1657
|
+
}
|
|
1658
|
+
return team;
|
|
1659
|
+
}
|
|
1660
|
+
async removeTask(teamId, taskId) {
|
|
1661
|
+
const team = await this.get(teamId);
|
|
1662
|
+
team.task_pool = team.task_pool.filter((id) => id !== taskId);
|
|
1663
|
+
team.updated_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
1664
|
+
await this.teamStore.save(team);
|
|
1665
|
+
return team;
|
|
1666
|
+
}
|
|
1667
|
+
async setLead(teamId, agentId) {
|
|
1668
|
+
const team = await this.get(teamId);
|
|
1669
|
+
const member = team.members.find((m) => m.agent_id === agentId);
|
|
1670
|
+
if (!member) throw new InvalidArgumentsError(`Agent ${agentId} is not a member of team ${teamId}`);
|
|
1671
|
+
const currentLead = team.members.find((m) => m.agent_id === team.lead_agent_id);
|
|
1672
|
+
if (currentLead) currentLead.role = "member";
|
|
1673
|
+
member.role = "lead";
|
|
1674
|
+
team.lead_agent_id = agentId;
|
|
1675
|
+
team.updated_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
1676
|
+
await this.teamStore.save(team);
|
|
1677
|
+
return team;
|
|
1678
|
+
}
|
|
1679
|
+
async disband(teamId) {
|
|
1680
|
+
const team = await this.get(teamId);
|
|
1681
|
+
team.status = "disbanded";
|
|
1682
|
+
team.updated_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
1683
|
+
await this.teamStore.save(team);
|
|
1684
|
+
this.eventBus.emit({ type: "team:disbanded", teamId });
|
|
1685
|
+
}
|
|
1686
|
+
/**
|
|
1687
|
+
* Find the team an agent belongs to (if any).
|
|
1688
|
+
*/
|
|
1689
|
+
async findTeamForAgent(agentId) {
|
|
1690
|
+
const teams = await this.teamStore.list();
|
|
1691
|
+
return teams.find((t) => t.status === "active" && t.members.some((m) => m.agent_id === agentId)) ?? null;
|
|
1692
|
+
}
|
|
1693
|
+
};
|
|
1694
|
+
|
|
1695
|
+
// src/container.ts
|
|
1696
|
+
async function buildLightContainer(context) {
|
|
1697
|
+
const paths = new Paths(context.projectRoot);
|
|
1698
|
+
await paths.requireInit();
|
|
1699
|
+
const configStore = new ConfigStore(paths);
|
|
1700
|
+
const globalConfigStore = new GlobalConfigStore();
|
|
1701
|
+
const [config, globalConfig] = await Promise.all([
|
|
1702
|
+
configStore.read(),
|
|
1703
|
+
globalConfigStore.read()
|
|
1704
|
+
]);
|
|
1705
|
+
const taskStore = new TaskStore(paths);
|
|
1706
|
+
const agentStore = new AgentStore(paths);
|
|
1707
|
+
const runStore = new RunStore(paths);
|
|
1708
|
+
const stateStore = new StateStore(paths);
|
|
1709
|
+
const contextStore = new ContextStore(paths);
|
|
1710
|
+
const messageStore = new MessageStore(paths);
|
|
1711
|
+
const goalStore = new GoalStore(paths);
|
|
1712
|
+
const teamStore = new TeamStore(paths);
|
|
1713
|
+
const eventBus = new EventBus();
|
|
1714
|
+
const taskService = new TaskService(taskStore, eventBus, config, paths);
|
|
1715
|
+
const agentService = new AgentService(agentStore, stateStore, eventBus, config);
|
|
1716
|
+
const runService = new RunService(runStore, eventBus);
|
|
1717
|
+
const messageService = new MessageService(messageStore, agentStore, teamStore, eventBus);
|
|
1718
|
+
const goalService = new GoalService(goalStore, eventBus, agentService, taskService, contextStore);
|
|
1719
|
+
const teamService = new TeamService(teamStore, agentStore, taskStore, eventBus);
|
|
1720
|
+
return {
|
|
1721
|
+
context,
|
|
1722
|
+
paths,
|
|
1723
|
+
config,
|
|
1724
|
+
taskStore,
|
|
1725
|
+
agentStore,
|
|
1726
|
+
runStore,
|
|
1727
|
+
stateStore,
|
|
1728
|
+
configStore,
|
|
1729
|
+
globalConfigStore,
|
|
1730
|
+
globalConfig,
|
|
1731
|
+
contextStore,
|
|
1732
|
+
messageStore,
|
|
1733
|
+
goalStore,
|
|
1734
|
+
teamStore,
|
|
1735
|
+
eventBus,
|
|
1736
|
+
taskService,
|
|
1737
|
+
agentService,
|
|
1738
|
+
runService,
|
|
1739
|
+
messageService,
|
|
1740
|
+
goalService,
|
|
1741
|
+
teamService
|
|
1742
|
+
};
|
|
1743
|
+
}
|
|
1744
|
+
async function buildFullContainer(context) {
|
|
1745
|
+
const light = await buildLightContainer(context);
|
|
1746
|
+
const [
|
|
1747
|
+
{ ProcessManager },
|
|
1748
|
+
{ AdapterRegistry: AdapterRegistry2 },
|
|
1749
|
+
{ ClaudeAdapter },
|
|
1750
|
+
{ CodexAdapter },
|
|
1751
|
+
{ CursorAdapter },
|
|
1752
|
+
{ ShellAdapter },
|
|
1753
|
+
{ WorkspaceManager },
|
|
1754
|
+
{ LiquidTemplateEngine },
|
|
1755
|
+
{ Orchestrator: Orchestrator2 },
|
|
1756
|
+
{ DoctorService }
|
|
1757
|
+
] = await Promise.all([
|
|
1758
|
+
import('./process-manager-A36Y7LHP.js'),
|
|
1759
|
+
import('./registry-JXXRLJ5J.js'),
|
|
1760
|
+
import('./claude-RIB3RQS5.js'),
|
|
1761
|
+
import('./codex-VBUSA2GJ.js'),
|
|
1762
|
+
import('./cursor-4QIOTDBW.js'),
|
|
1763
|
+
import('./shell-IH2MMTVP.js'),
|
|
1764
|
+
import('./workspace-manager-T6AXG7XL.js'),
|
|
1765
|
+
import('./template-engine-MFL5B677.js'),
|
|
1766
|
+
import('./orchestrator-FGGXK3N3.js'),
|
|
1767
|
+
import('./doctor-service-F2SXDWHS.js')
|
|
1768
|
+
]);
|
|
1769
|
+
const processManager = new ProcessManager();
|
|
1770
|
+
const templateEngine = new LiquidTemplateEngine();
|
|
1771
|
+
const workspaceManager = new WorkspaceManager(
|
|
1772
|
+
context.projectRoot,
|
|
1773
|
+
light.paths.root,
|
|
1774
|
+
processManager
|
|
1775
|
+
);
|
|
1776
|
+
const adapterRegistry = new AdapterRegistry2();
|
|
1777
|
+
adapterRegistry.register(new ClaudeAdapter(processManager));
|
|
1778
|
+
adapterRegistry.register(new CodexAdapter(processManager));
|
|
1779
|
+
adapterRegistry.register(new CursorAdapter(processManager));
|
|
1780
|
+
adapterRegistry.register(new ShellAdapter(processManager));
|
|
1781
|
+
const doctorService = new DoctorService(adapterRegistry, processManager, context.projectRoot);
|
|
1782
|
+
const orchestrator = new Orchestrator2({
|
|
1783
|
+
taskStore: light.taskStore,
|
|
1784
|
+
agentStore: light.agentStore,
|
|
1785
|
+
runStore: light.runStore,
|
|
1786
|
+
stateStore: light.stateStore,
|
|
1787
|
+
adapterRegistry,
|
|
1788
|
+
workspaceManager,
|
|
1789
|
+
templateEngine,
|
|
1790
|
+
processManager,
|
|
1791
|
+
eventBus: light.eventBus,
|
|
1792
|
+
taskService: light.taskService,
|
|
1793
|
+
agentService: light.agentService,
|
|
1794
|
+
runService: light.runService,
|
|
1795
|
+
contextStore: light.contextStore,
|
|
1796
|
+
messageService: light.messageService,
|
|
1797
|
+
goalStore: light.goalStore,
|
|
1798
|
+
config: light.config,
|
|
1799
|
+
projectRoot: context.projectRoot,
|
|
1800
|
+
lockPath: light.paths.lockPath
|
|
1801
|
+
});
|
|
1802
|
+
return {
|
|
1803
|
+
...light,
|
|
1804
|
+
processManager,
|
|
1805
|
+
adapterRegistry,
|
|
1806
|
+
workspaceManager,
|
|
1807
|
+
templateEngine,
|
|
1808
|
+
doctorService,
|
|
1809
|
+
orchestrator
|
|
1810
|
+
};
|
|
1811
|
+
}
|
|
1812
|
+
async function buildContainer(context) {
|
|
1813
|
+
return buildFullContainer(context);
|
|
1814
|
+
}
|
|
1815
|
+
|
|
1816
|
+
export { AgentService, EventBus, RunService, TaskService, buildContainer, buildFullContainer, buildLightContainer, detectClipboardType, getClipboardImage, isClipboardToolAvailable };
|
|
1817
|
+
//# sourceMappingURL=index.js.map
|
|
6
1818
|
//# sourceMappingURL=index.js.map
|