aipm-worker-supervisor 1.0.0 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/openclaw-bridge.cjs +2 -0
- package/dist/openclaw.cjs +13 -0
- package/dist/supervisor.cjs +7 -7
- package/dist/worker.cjs +5 -5
- package/package.json +4 -2
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
var E=require("node:http"),b=require("node:crypto");var S={debug:10,info:20,warn:30,error:40},y=class n{constructor(e,t={}){this.minLevel=e;this.ctx=t}child(e){return new n(this.minLevel,{...this.ctx,...e})}debug(e,t){this.log("debug",e,t)}info(e,t){this.log("info",e,t)}warn(e,t){this.log("warn",e,t)}error(e,t){this.log("error",e,t)}log(e,t,r){if(S[e]<S[this.minLevel])return;let o={ts:new Date().toISOString(),level:e,message:t,...this.ctx,...r||{}},c=JSON.stringify(o);e==="error"?console.error(c):e==="warn"?console.warn(c):console.log(c)}};var _=require("node:fs"),k=require("node:path");function v(n=".env",e=process.env){try{let t=(0,_.readFileSync)((0,k.resolve)(n),"utf8");for(let r of t.split(/\r?\n/)){let o=r.trim();if(!o||o.startsWith("#"))continue;let c=o.indexOf("=");if(c<=0)continue;let m=o.slice(0,c).trim(),f=o.slice(c+1).trim();e[m]||(e[m]=f)}}catch{}}function d(n,e,t){n.statusCode=e,n.setHeader("Content-Type","application/json"),n.end(JSON.stringify(t))}function O(n){return new Promise((e,t)=>{let r="";n.on("data",o=>{r+=String(o)}),n.on("end",()=>{if(!r.trim())return e({});try{e(JSON.parse(r))}catch(o){t(o)}}),n.on("error",t)})}async function L(n){let e=await fetch(`${n.gatewayHttpBase.replace(/\/+$/,"")}/tools/invoke`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${n.gatewayToken}`},body:JSON.stringify(n.payload)}),t=await e.text();if(!e.ok)throw new Error(`gateway_tools_invoke_failed:${e.status}:${t}`);return JSON.parse(t)}function T(n,e){return{session_key:`agent:${n}:runtime:${e}`,session_id:`runtime-${n.slice(0,8)}-${(0,b.randomUUID)().slice(0,8)}`,metadata:{synthetic:!0,source:"bridge-fallback",note:"No gateway spawn capability configured",createdAt:new Date().toISOString()}}}async function I(){v(process.env.WORKER_ENV_FILE||".env");let n=new y(String(process.env.LOG_LEVEL||"info").toLowerCase(),{app:"aipm-openclaw-session-bridge"}),e=String(process.env.OPENCLAW_SESSION_API_TOKEN||"").trim(),t=Number(process.env.OPENCLAW_SESSION_API_PORT||8791),r=String(process.env.OPENCLAW_GATEWAY_HTTP_BASE||"").trim(),o=String(process.env.OPENCLAW_GATEWAY_TOKEN||"").trim(),c=String(process.env.OPENCLAW_SPAWN_AGENT_ID||"main").trim(),m=s=>{let i=String(s.headers.authorization||"");return e&&i===`Bearer ${e}`};(0,E.createServer)(async(s,i)=>{try{if(!s.url||!s.method)return d(i,404,{error:"not_found"});if(!m(s))return d(i,401,{error:"unauthorized"});if(s.method==="GET"&&s.url==="/health")return d(i,200,{ok:!0,mode:r&&o?"gateway-tools-invoke":"synthetic-fallback"});if(s.method==="POST"&&s.url==="/runtime/openclaw/start"){let l=await O(s),a=String(l.agent_id||"").trim(),u=String(l.runtime_name||"openclaw-runtime").trim();if(!a)return d(i,400,{error:"agent_id_required"});try{if(r&&o){let g=[`You are launching a persistent employee runtime session for HQ agent ${a}.`,"Immediately acknowledge readiness in one line, then stay available.","Do not run destructive actions."].join(" "),w=await L({gatewayHttpBase:r,gatewayToken:o,payload:{tool:"sessions_spawn",args:{task:g,runtime:"subagent",mode:"session",thread:!0,cleanup:"keep",label:`hq-openclaw-${a.slice(0,8)}`,agentId:c},sessionKey:"main"}}),p=w.result||{},h=String(p.childSessionKey||p.sessionKey||"").trim();if(!h)throw new Error(`spawn_missing_session_key:${JSON.stringify(w)}`);return d(i,200,{ok:!0,session_key:h,session_id:String(p.sessionId||"")||null,runtime_name:u,host_id:l.host_id||null,metadata:{synthetic:!1,source:"gateway-tools-invoke",runId:p.runId||null}})}}catch(g){n.warn("Gateway spawn failed, falling back to synthetic",{error:g instanceof Error?g.message:String(g),agentId:a})}let N=T(a,u);return d(i,200,{ok:!0,...N,runtime_name:u,host_id:l.host_id||null})}if(s.method==="POST"&&s.url==="/runtime/openclaw/stop"){let l=await O(s),a=String(l.session_key||"").trim();if(r&&o&&a)try{await fetch(`${r.replace(/\/+$/,"")}/chat/abort`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${o}`},body:JSON.stringify({sessionKey:a})})}catch(u){n.warn("Gateway stop call failed (ignored)",{error:u instanceof Error?u.message:String(u),sessionKey:a})}return d(i,200,{ok:!0,stopped:!0,session_key:a||null})}return d(i,404,{error:"not_found"})}catch(l){return d(i,500,{error:l instanceof Error?l.message:"internal_error"})}}).listen(t,()=>{n.info("OpenClaw session bridge listening",{port:t,mode:r&&o?"gateway-tools-invoke":"synthetic-fallback"})})}I().catch(n=>{console.error(JSON.stringify({ts:new Date().toISOString(),level:"error",message:"OpenClaw session bridge crashed",error:n instanceof Error?n.message:String(n)})),process.exit(1)});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
var j=require("node:fs"),U=require("node:path");function W(t=".env",e=process.env){try{let r=(0,j.readFileSync)((0,U.resolve)(t),"utf8");for(let s of r.split(/\r?\n/)){let o=s.trim();if(!o||o.startsWith("#"))continue;let n=o.indexOf("=");if(n<=0)continue;let i=o.slice(0,n).trim(),l=o.slice(n+1).trim();e[i]||(e[i]=l)}}catch{}}var K=require("node:fs"),E=require("node:path");function f(t,e){let r=Number(t);return!Number.isFinite(r)||r<=0?e:Math.floor(r)}function ce(t){let e=t.trim().replace(/\/+$/,"");if(!e)throw new Error("AIPM_BASE_URL is required");return e}function le(t=process.env){let e=ce(t.AIPM_BASE_URL||""),r=String(t.AGENT_API_KEY||t.AIPM_API_KEY||"").trim();if(!r)throw new Error("AGENT_API_KEY (or AIPM_API_KEY) is required");let s=(0,E.resolve)(t.STATE_FILE_PATH||`.worker-state/${r.slice(0,12)}.json`),o=(0,E.resolve)(s,"..");(0,K.existsSync)(o);let n=String(t.LOG_LEVEL||"info").toLowerCase(),i=["debug","info","warn","error"].includes(n)?n:"info",l=f(t.MAX_CONCURRENCY,2),a=String(t.MODEL_PROVIDER||"anthropic").toLowerCase();return{aipmBaseUrl:e,agentApiKey:r,pollIntervalMs:f(t.POLL_INTERVAL_MS,5e3),maxConcurrency:l,inboxConcurrency:f(t.INBOX_CONCURRENCY,1),taskConcurrency:f(t.TASK_CONCURRENCY,l),logLevel:i,leaseTtlMs:f(t.LEASE_TTL_MS,6e4),stateFilePath:s,heartbeatIntervalMs:f(t.HEARTBEAT_INTERVAL_MS,15e3),maxRetries:f(t.MAX_RETRIES,4),retryBaseMs:f(t.RETRY_BASE_MS,500),retryMaxMs:f(t.RETRY_MAX_MS,8e3),circuitFailureThreshold:f(t.CIRCUIT_FAILURE_THRESHOLD,5),circuitCooldownMs:f(t.CIRCUIT_COOLDOWN_MS,3e4),modelProvider:a==="openai"?"openai":"anthropic",modelApiKey:String(t.MODEL_API_KEY||"").trim()||null,modelTokenUrl:String(t.MODEL_TOKEN_URL||"").trim()||null,modelName:String(t.MODEL_NAME||"claude-sonnet-4-6").trim(),modelBaseUrl:String(t.MODEL_BASE_URL||"").trim()||null,modelMaxTokens:f(t.MODEL_MAX_TOKENS,4096),modelSystemPrompt:t.MODEL_SYSTEM_PROMPT?String(t.MODEL_SYSTEM_PROMPT):null}}function F(t=process.env){let e=le(t),r=String(t.AGENT_API_KEYS||"").trim(),s=String(t.AGENT_NAMES||"").trim();if(!r)return[e];let o=r.split(",").map(a=>a.trim()).filter(Boolean);if(o.length===0)return[e];let n=s?s.split(",").map(a=>a.trim()).filter(Boolean):[],i=String(t.MODEL_NAMES||"").trim(),l=i?i.split(",").map(a=>a.trim()).filter(Boolean):[];return o.map((a,d)=>{let p=n[d]||`agent-${d+1}`,m=(0,E.resolve)(t[`STATE_FILE_PATH_${d+1}`]||`.worker-state/${p}.json`);return{...e,agentApiKey:a,stateFilePath:m,workerName:p,...l[d]?{modelName:l[d]}:{}}})}var q={debug:10,info:20,warn:30,error:40},y=class t{constructor(e,r={}){this.minLevel=e;this.ctx=r}child(e){return new t(this.minLevel,{...this.ctx,...e})}debug(e,r){this.log("debug",e,r)}info(e,r){this.log("info",e,r)}warn(e,r){this.log("warn",e,r)}error(e,r){this.log("error",e,r)}log(e,r,s){if(q[e]<q[this.minLevel])return;let o={ts:new Date().toISOString(),level:e,message:r,...this.ctx,...s||{}},n=JSON.stringify(o);e==="error"?console.error(n):e==="warn"?console.warn(n):console.log(n)}};var R=class{constructor(e,r){this.baseUrl=e;this.apiKey=r}async call(e,r){let s=await fetch(`${this.baseUrl}${e}`,{...r,headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.apiKey}`,...r?.headers||{}}});if(!s.ok){let o="";try{let n=await s.json();o=n?.error||n?.code||""}catch{}throw new Error(`AIPM ${e} -> ${s.status}${o?` (${o})`:""}`)}return await s.json()}bootstrap(){return this.call("/agent-bootstrap",{method:"GET"})}updateStatus(e){return this.call("/api-agent-status",{method:"POST",body:JSON.stringify(e)})}async listInboxUnread(){return(await this.call("/agent-inbox?unread=true",{method:"GET"})).inbox||[]}markInboxRead(e){return this.call(`/agent-inbox/${e}/read`,{method:"POST",body:JSON.stringify({})})}sendInboxMessage(e){return this.call("/agent-inbox",{method:"POST",body:JSON.stringify(e)})}listMyTasks(e){let r=e?`?status=${encodeURIComponent(e)}`:"";return this.call(`/agent-tasks${r}`,{method:"GET"})}async listMyActiveTasks(){let[e,r]=await Promise.all([this.listMyTasks("pending"),this.listMyTasks("in_progress")]);return{tasks:[...e.tasks||[],...r.tasks||[]]}}createTask(e){return this.call("/agent-tasks",{method:"POST",body:JSON.stringify(e)})}getTask(e){return this.call(`/agent-task-state/task/${e}`,{method:"GET"})}taskStatus(e,r,s){return this.call("/agent-task-state/status",{method:"POST",body:JSON.stringify({task_id:e,status:r,reason:s})})}taskLog(e,r,s="info"){return this.call("/agent-task-state/log",{method:"POST",body:JSON.stringify({task_id:e,message:r,level:s})})}};function de(t,e=140){let r=t.replace(/\s+/g," ").trim();return r.length<=e?r:`${r.slice(0,e-1)}...`}function ue(t){return(t||"").trim().toLowerCase().startsWith("[ack]")||(t||"").trim().toLowerCase().startsWith("ack:")}function B(t){return t.match(/agent_id\s*:\s*([0-9a-fA-F-]{36})/)?.[1]||null}function ge(t){let e=(t.subject||"").toLowerCase(),r=(t.body||"").toLowerCase();return t.taskRef||B(t.body)?!1:e.includes("live")||e.includes("check")||e.includes("ping")||r.includes("are you live")||r.includes("quick live check")||r.includes("reply with")}async function G(t,e){let{client:r,logger:s,agentId:o}=e,n=s.child({inboxMessageId:t.id});if(t.type==="urgent"&&n.info("Urgent inbox item observed"),t.from_agent_id===o||ue(t.subject)){await r.markInboxRead(t.id),n.debug("Ignored self/ack inbox message");return}let i=B(t.body||"");if(!t.taskRef&&i&&t.from_agent_id){let a=await r.sendInboxMessage({to_agent_id:i,subject:`Hello from ${o}`,message:`Hi \u2014 quick live check from peer worker ${o}. Are you live?`});await r.sendInboxMessage({to_agent_id:t.from_agent_id,subject:"Peer message sent",message:`Executed peer message command. target_agent_id=${i}, outbound_message_id=${a.message?.id??"unknown"}`}),await r.markInboxRead(t.id),n.info("Peer message command executed",{peerAgentId:i,outboundMessageId:a.message?.id});return}if(ge(t)&&t.from_agent_id){await r.sendInboxMessage({to_agent_id:t.from_agent_id,subject:`Ack: ${t.subject||"message received"}`,message:`Hi from worker ${o}. I'm live at ${new Date().toISOString()}.`,task_ref:t.taskRef||void 0}),await r.markInboxRead(t.id),n.info("Inbox ping acknowledged without task creation");return}let l=t.taskRef||null;if(!l){let a=t.subject?.trim()?`Inbox follow-up: ${t.subject}`:`Inbox follow-up: ${de(t.body,64)}`;l=(await r.createTask({title:a,description:t.body,assigned_to:o,priority:"medium"})).task_id,n.info("Created task from inbox",{taskId:l})}t.from_agent_id&&await r.sendInboxMessage({to_agent_id:t.from_agent_id,subject:`[Ack] ${t.subject||"Message received"}`,message:`Received message ${t.id}. Linked task: ${l||"none"}.`,task_ref:l||void 0}),await r.markInboxRead(t.id),n.info("Inbox message processed",{taskId:l})}var I=null;async function Y(t){if(!t.tokenUrl)return t.apiKey;let e=Date.now();if(I&&I.expiresAt>e+6e4)return I.token;let r=await fetch(t.tokenUrl,{method:"GET"});if(!r.ok){let o=await r.text();throw new Error(`Token provider returned ${r.status}: ${o}`)}let s=await r.json();if(!s.token)throw new Error("Token provider response missing 'token' field");return I={token:s.token,expiresAt:e+(s.expires_in??3600)*1e3},I.token}var H=20,J="You are an autonomous AI agent. Complete the assigned task using the available tools. When done, call complete_task with a summary. If you cannot complete it, call fail_task with the reason.",X=[{name:"log_progress",description:"Log a progress note to the task. Use for intermediate updates.",parameters:{type:"object",properties:{message:{type:"string",description:"Progress message"}},required:["message"]}},{name:"complete_task",description:"Mark the task as complete. Call this when work is done.",parameters:{type:"object",properties:{summary:{type:"string",description:"Summary of what was accomplished"}},required:["summary"]}},{name:"fail_task",description:"Mark the task as failed. Call this if you cannot complete it.",parameters:{type:"object",properties:{reason:{type:"string",description:"Reason for failure"}},required:["reason"]}},{name:"create_subtask",description:"Create a subtask to delegate work to another agent.",parameters:{type:"object",properties:{title:{type:"string",description:"Subtask title"},description:{type:"string",description:"Subtask description"},assigned_to:{type:"string",description:"Agent ID or name (optional)"},priority:{type:"string",enum:["low","medium","high","urgent"]}},required:["title","description"]}},{name:"send_message",description:"Send an inbox message to another agent.",parameters:{type:"object",properties:{to_agent_id:{type:"string",description:"Target agent ID"},subject:{type:"string",description:"Message subject"},message:{type:"string",description:"Message body"}},required:["to_agent_id","subject","message"]}}],pe=X.map(t=>({name:t.name,description:t.description,input_schema:t.parameters})),me=X.map(t=>({type:"function",function:{name:t.name,description:t.description,parameters:t.parameters}}));async function V(t,e,r){let{client:s,taskId:o}=r;switch(t){case"log_progress":return await s.taskLog(o,String(e.message||""),"info"),{result:"Logged.",done:!1,failed:!1,summary:""};case"complete_task":{let n=String(e.summary||"Task completed.");return{result:n,done:!0,failed:!1,summary:n}}case"fail_task":{let n=String(e.reason||"Unknown failure.");return{result:n,done:!0,failed:!0,summary:n}}case"create_subtask":return{result:`Subtask created: ${(await s.createTask({title:String(e.title),description:String(e.description||""),assigned_to:e.assigned_to?String(e.assigned_to):void 0,priority:e.priority?String(e.priority):"medium"})).task_id}`,done:!1,failed:!1,summary:""};case"send_message":return{result:`Message sent: ${(await s.sendInboxMessage({to_agent_id:String(e.to_agent_id),subject:String(e.subject||""),message:String(e.message||""),task_ref:o})).message?.id??"unknown"}`,done:!1,failed:!1,summary:""};default:return{result:`Unknown tool: ${t}`,done:!1,failed:!1,summary:""}}}async function he(t,e,r){let s=await Y(e),o=(e.baseUrl||"https://api.anthropic.com").replace(/\/+$/,""),n=e.systemPrompt||J,i=[{role:"user",content:`Task: ${t.title}
|
|
3
|
+
|
|
4
|
+
Description:
|
|
5
|
+
${t.description||"(no description provided)"}`}];for(let l=0;l<H;l++){let a=await fetch(`${o}/v1/messages`,{method:"POST",headers:{"x-api-key":s,"anthropic-version":"2023-06-01","content-type":"application/json"},body:JSON.stringify({model:e.model,max_tokens:e.maxTokens,system:n,messages:i,tools:pe})});if(!a.ok){let c=await a.text();throw new Error(`Anthropic API ${a.status}: ${c}`)}let d=await a.json();i.push({role:"assistant",content:d.content});let p=d.content.filter(c=>c.type==="tool_use");if(p.length===0)return{summary:d.content.find(u=>u.type==="text")?.text||"Task processed.",status:"done"};let m=[];for(let c of p){let u=await V(c.name,c.input,r);if(m.push({type:"tool_result",tool_use_id:c.id,content:u.result}),u.done)return{summary:u.summary||u.result,status:u.failed?"failed":"done"}}i.push({role:"user",content:m})}return{summary:"Max iterations reached.",status:"done"}}async function fe(t,e,r){let s=await Y(e),o=(e.baseUrl||"https://api.openai.com").replace(/\/+$/,""),i=[{role:"system",content:e.systemPrompt||J},{role:"user",content:`Task: ${t.title}
|
|
6
|
+
|
|
7
|
+
Description:
|
|
8
|
+
${t.description||"(no description provided)"}`}];for(let l=0;l<H;l++){let a=await fetch(`${o}/v1/chat/completions`,{method:"POST",headers:{Authorization:`Bearer ${s}`,"content-type":"application/json"},body:JSON.stringify({model:e.model,max_tokens:e.maxTokens,messages:i,tools:me,tool_choice:"auto"})});if(!a.ok){let c=await a.text();throw new Error(`OpenAI API ${a.status}: ${c}`)}let p=(await a.json()).choices[0];if(!p)throw new Error("Empty choices in model response");i.push(p.message);let m=p.message.tool_calls;if(!m||m.length===0)return{summary:p.message.content||"Task processed.",status:"done"};for(let c of m){let u={};try{u=JSON.parse(c.function.arguments)}catch{}let _=await V(c.function.name,u,r);if(i.push({role:"tool",tool_call_id:c.id,name:c.function.name,content:_.result}),_.done)return{summary:_.summary||_.result,status:_.failed?"failed":"done"}}}return{summary:"Max iterations reached.",status:"done"}}async function z(t,e,r){return e.provider==="anthropic"?he(t,e,r):fe(t,e,r)}async function Q(t,e){let{client:r,logger:s}=e,o=s.child({taskId:t.id});if(t.status==="done"||t.status==="cancelled"){o.debug("Skipping terminal task");return}if(t.status!=="in_progress"&&await r.taskStatus(t.id,"in_progress","worker_claimed_task"),e.aiRunner){o.info("AI runner invoked",{provider:e.aiRunner.provider,model:e.aiRunner.model});let i=await z(t,e.aiRunner,{client:r,taskId:t.id,logger:o,agentId:e.agentId});await r.taskLog(t.id,`AI execution complete: ${i.summary}`,i.status==="failed"?"warn":"info");let l=null;t.assigned_by&&t.assigned_by!==e.agentId&&(l=(await r.sendInboxMessage({to_agent_id:t.assigned_by,subject:`[Handoff] ${t.title}`,message:`Task ${t.id} processed.
|
|
9
|
+
Title: ${t.title}
|
|
10
|
+
Status: ${i.status}
|
|
11
|
+
Summary: ${i.summary}`,task_ref:t.id})).message?.id||null,l||o.warn("Handoff sent but response carried no message id",{assignedBy:t.assigned_by}));let a=i.status==="failed"?"failed":"done",d=i.status==="failed"?"ai_runner_failed":"ai_runner_completed";await r.taskStatus(t.id,a,d),o.info("Task completed via AI runner",{status:a,handoffMessageId:l});return}await r.taskLog(t.id,"Resident worker processed task and persisted execution note.");let n=null;t.assigned_by&&t.assigned_by!==e.agentId&&(n=(await r.sendInboxMessage({to_agent_id:t.assigned_by,subject:`[Handoff] ${t.title}`,message:`Task ${t.id} processed by resident worker.
|
|
12
|
+
Title: ${t.title}
|
|
13
|
+
Status: ready_for_review`,task_ref:t.id})).message?.id||null,n||o.warn("Handoff sent but response carried no message id",{assignedBy:t.assigned_by})),await r.taskStatus(t.id,"done","worker_completed"),o.info("Task completed",{handoffMessageId:n})}function we(t,e,r,s=.2){let o=Math.min(r,e*Math.pow(2,Math.max(0,t-1))),n=o*s*Math.random();return Math.floor(o+n)}async function Z(t,e){let r;for(let s=1;s<=e.maxAttempts;s++)try{return await t(s)}catch(o){if(r=o,s>=e.maxAttempts||e.shouldRetry&&!e.shouldRetry(o,s))break;let n=we(s,e.baseMs,e.maxMs,e.jitterRatio);await new Promise(i=>setTimeout(i,n))}throw r instanceof Error?r:new Error("retry_failed")}var k=require("node:fs/promises"),L=require("node:path"),ee={processed:{},leases:{},checkpoints:{workerState:"idle",lastHeartbeatAt:null,lastError:null,queueDepth:0,metrics:{processedCount:0,failedCount:0,retryCount:0,avgProcessingLatencyMs:0}}};function O(t,e,r){return`${t}:${e}:${r}`}var b=class{constructor(e){this.filePath=e}state=structuredClone(ee);loaded=!1;async load(){if(!this.loaded){try{let e=await(0,k.readFile)(this.filePath,"utf8");this.state={...ee,...JSON.parse(e)}}catch{await(0,k.mkdir)((0,L.dirname)(this.filePath),{recursive:!0}),await this.flush()}this.loaded=!0,this.cleanupExpiredLeases()}}cleanupExpiredLeases(){let e=Date.now();for(let[r,s]of Object.entries(this.state.leases))s.expiresAt<=e&&delete this.state.leases[r]}async flush(){await(0,k.mkdir)((0,L.dirname)(this.filePath),{recursive:!0}),await(0,k.writeFile)(this.filePath,JSON.stringify(this.state,null,2),"utf8")}async isProcessed(e){return await this.load(),!!this.state.processed[e]}async markProcessed(e){await this.load(),this.state.processed[e]={processedAt:new Date().toISOString()},await this.flush()}async tryAcquireLease(e,r,s){return await this.load(),this.cleanupExpiredLeases(),this.state.leases[e]?!1:(this.state.leases[e]={owner:r,expiresAt:Date.now()+s},await this.flush(),!0)}async releaseLease(e,r){await this.load(),this.state.leases[e]?.owner===r&&(delete this.state.leases[e],await this.flush())}async setWorkerState(e,r){await this.load(),this.state.checkpoints.workerState=e,r!==void 0&&(this.state.checkpoints.lastError=r),await this.flush()}async heartbeat(){await this.load(),this.state.checkpoints.lastHeartbeatAt=new Date().toISOString(),await this.flush()}async setQueueDepth(e){await this.load(),this.state.checkpoints.queueDepth=e,await this.flush()}async addMetric(e,r=1){await this.load(),this.state.checkpoints.metrics[e]+=r,await this.flush()}async updateProcessingLatency(e){await this.load();let r=this.state.checkpoints.metrics,s=Math.max(1,r.processedCount);r.avgProcessingLatencyMs=(r.avgProcessingLatencyMs*(s-1)+e)/s,await this.flush()}async getDiagnostics(){return await this.load(),this.cleanupExpiredLeases(),{...this.state.checkpoints,leaseCount:Object.keys(this.state.leases).length,processedKeys:Object.keys(this.state.processed).length}}};var te={urgent:4,high:3,medium:2,low:1},ye=3e4,T=class{constructor(e){this.deps=e;this.logger=e.logger||new y(e.config.logLevel,{scope:"resident-worker"}),this.client=e.client||new R(e.config.aipmBaseUrl,e.config.agentApiKey),this.store=e.stateStore||new b(e.config.stateFilePath),this.currentPollMs=e.config.pollIntervalMs}logger;client;store;running=!1;stopRequested=!1;agentId="";projectId="";consecutiveFailures=0;currentPollMs;leaseOwner=`worker-${process.pid}-${Math.random().toString(16).slice(2,8)}`;async start(){if(this.running)return;this.running=!0,this.stopRequested=!1,await this.store.load();let e=await this.client.bootstrap();for(this.agentId=e.agent.id,this.projectId=e.projectId,this.logger.info("Worker bootstrap complete",{agentId:this.agentId,role:e.agent.role}),await this.client.updateStatus({status:"idle",currentTask:"resident_loop_online"}),await this.store.setWorkerState("idle",null);!this.stopRequested;){let r=Date.now();try{await this.store.heartbeat(),await this.tick(),this.consecutiveFailures=0}catch(n){this.consecutiveFailures+=1;let i=n instanceof Error?n.message:"loop_error";if(this.logger.error("Loop iteration failed",{error:i,consecutiveFailures:this.consecutiveFailures}),await this.store.addMetric("failedCount",1),await this.store.setWorkerState("error",i),this.consecutiveFailures>=this.deps.config.circuitFailureThreshold){await this.client.updateStatus({status:"blocked",blockedReason:`Circuit open after ${this.consecutiveFailures} failures`,blockedBy:"resident_worker_loop"}),await this.store.setWorkerState("blocked",i),await this.sleep(this.deps.config.circuitCooldownMs),this.consecutiveFailures=0;continue}}let s=Date.now()-r,o=Math.max(250,this.currentPollMs-s);await this.sleep(o)}await this.client.updateStatus({status:"idle",currentTask:"resident_loop_stopped"}),await this.store.setWorkerState("stopped",null),this.running=!1}async stop(){this.stopRequested=!0}async diagnostics(){let e=await this.store.getDiagnostics();return{agentId:this.agentId||null,projectId:this.projectId||null,running:this.running,stopRequested:this.stopRequested,...e}}async tick(){await this.client.updateStatus({status:"idle",currentTask:"polling_inbox_and_tasks"});let[e,r]=await Promise.all([this.client.listInboxUnread(),this.client.listMyActiveTasks().then(c=>c.tasks||[])]),s=r.filter(c=>c.status==="pending"),o=r.filter(c=>c.status==="in_progress"),n=e.length+r.length;if(this.logger.debug("Fetched work queues",{inboxCount:e.length,pendingTaskCount:s.length,activeTaskCount:o.length,queueDepth:n}),await this.store.setQueueDepth(n),n===0?(this.currentPollMs=Math.min(this.currentPollMs*1.5,ye),this.logger.debug("Queue empty, backing off poll",{nextPollMs:Math.round(this.currentPollMs)})):this.currentPollMs=this.deps.config.pollIntervalMs,n===0)return;let i=[...e].sort((c,u)=>c.type==="urgent"&&u.type!=="urgent"?-1:u.type==="urgent"&&c.type!=="urgent"?1:0),l=[...s,...o].sort((c,u)=>(te[u.priority]??0)-(te[c.priority]??0)),a=`inbox:${i.length},tasks:${l.length}`;await this.client.updateStatus({status:"working",currentTask:a}),await this.store.setWorkerState("working",null);let d=this.deps.config.modelApiKey||this.deps.config.modelTokenUrl?{provider:this.deps.config.modelProvider,apiKey:this.deps.config.modelApiKey??"",tokenUrl:this.deps.config.modelTokenUrl,model:this.deps.config.modelName,baseUrl:this.deps.config.modelBaseUrl,maxTokens:this.deps.config.modelMaxTokens,systemPrompt:this.deps.config.modelSystemPrompt}:void 0,p=i.map(c=>()=>this.processInbox(c.id,()=>G(c,{client:this.client,logger:this.logger,agentId:this.agentId}))),m=l.map(c=>()=>this.processTask(c.id,()=>Q(c,{client:this.client,logger:this.logger,agentId:this.agentId,aiRunner:d})));await Promise.all([this.runWithConcurrency(p,this.deps.config.inboxConcurrency),this.runWithConcurrency(m,this.deps.config.taskConcurrency)])}async processInbox(e,r){let s=O(this.agentId,e,"inbox");return this.processWorkItem(s,`inbox:${e}`,r)}async processTask(e,r){let s=O(this.agentId,e,"task");return this.processWorkItem(s,`task:${e}`,r)}async processWorkItem(e,r,s){if(await this.store.isProcessed(e)||!await this.store.tryAcquireLease(e,this.leaseOwner,this.deps.config.leaseTtlMs))return;let n=Date.now();this.logger.debug("Processing work item",{currentTask:r});try{await Z(async i=>{i>1&&await this.store.addMetric("retryCount",1),await s()},{maxAttempts:this.deps.config.maxRetries,baseMs:this.deps.config.retryBaseMs,maxMs:this.deps.config.retryMaxMs}),await this.store.markProcessed(e),await this.store.addMetric("processedCount",1),await this.store.updateProcessingLatency(Date.now()-n)}finally{await this.store.releaseLease(e,this.leaseOwner)}}async runWithConcurrency(e,r){let s=[...e],o=Array.from({length:Math.max(1,r)}).map(async()=>{for(;s.length>0;){let n=s.shift();if(!n)return;await n()}});await Promise.all(o)}async sleep(e){await new Promise(r=>setTimeout(r,e))}};var A=class{logger;stopRequested=!1;entries;restartDelayMs;constructor(e,r){this.logger=r?.logger||new y("info",{app:"aipm-supervisor"}),this.restartDelayMs=r?.restartDelayMs??3e3,this.entries=e.map((s,o)=>{let n=s.workerName||`worker-${o+1}`,i=this.logger.child({worker:n});return{name:n,config:s,worker:new T({config:s,logger:i}),runPromise:null,restartCount:0,forceRestart:!1}})}async start(){this.stopRequested=!1,this.logger.info("Starting multi-agent supervisor",{workerCount:this.entries.length}),await Promise.all(this.entries.map(e=>this.runWorker(e)))}async stop(){this.stopRequested=!0,this.logger.info("Stopping multi-agent supervisor"),await Promise.all(this.entries.map(e=>e.worker.stop())),await Promise.all(this.entries.map(async e=>{if(e.runPromise)try{await e.runPromise}catch{}}))}async diagnostics(){let e=await Promise.all(this.entries.map(async r=>({name:r.name,restarts:r.restartCount,...await r.worker.diagnostics()})));return{running:!this.stopRequested,workerCount:this.entries.length,workers:e}}async requestRestart(e){let r=this.entries.find(s=>s.name===e);if(!r)throw new Error(`worker_not_found:${e}`);return r.forceRestart=!0,await r.worker.stop(),{ok:!0,worker:r.name}}listWorkerNames(){return this.entries.map(e=>e.name)}async snapshotForControlPlane(){return await Promise.all(this.entries.map(async r=>({name:r.name,restartCount:r.restartCount,diagnostics:await r.worker.diagnostics()})))}async applyCommand(e){if(e.command_type==="sync_workers")return{ok:!0,action:"sync_workers"};if(e.command_type==="restart_worker"){let r=String(e.payload?.worker_name||"").trim();if(r)return await this.requestRestart(r),{ok:!0,action:"restart_worker",target:r};let s=String(e.target_worker_agent_id||"").trim();if(!s)throw new Error("restart_worker target missing");let n=(await this.snapshotForControlPlane()).find(i=>String(i.diagnostics.agentId||"")===s);if(!n)throw new Error("restart_worker target agent not found");return await this.requestRestart(n.name),{ok:!0,action:"restart_worker",target:n.name}}if(e.command_type==="stop_supervisor")return await this.stop(),{ok:!0,action:"stop_supervisor"};if(e.command_type==="start_supervisor")return{ok:!0,action:"start_supervisor_noop_when_running"};throw new Error(`unsupported command_type: ${e.command_type}`)}async persistedStatus(){let e=await Promise.all(this.entries.map(async r=>{let o=await new b(r.config.stateFilePath).getDiagnostics();return{name:r.name,stateFilePath:r.config.stateFilePath,...o}}));return{ok:!0,workerCount:e.length,workers:e}}async runWorker(e){for(;!this.stopRequested;)try{if(e.runPromise=e.worker.start(),await e.runPromise,e.runPromise=null,this.stopRequested)return;if(e.forceRestart){e.forceRestart=!1,e.worker=new T({config:e.config,logger:this.logger.child({worker:e.name})}),this.logger.info("Worker restart requested",{worker:e.name});continue}throw new Error("worker_exited_unexpectedly")}catch(r){if(e.restartCount+=1,this.logger.error("Worker crashed, scheduling restart",{worker:e.name,restartCount:e.restartCount,error:r instanceof Error?r.message:String(r)}),this.stopRequested)return;await new Promise(s=>setTimeout(s,this.restartDelayMs))}}};var se=require("node:os");function _e(t){switch(t){case"idle":return"idle";case"working":return"working";case"blocked":return"blocked";case"stopped":return"stopped";case"error":return"error";default:return"running"}}async function re(t,e,r){let s=await fetch(t,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${e}`},body:JSON.stringify(r)});if(!s.ok){let o=await s.text();throw new Error(`control_post_failed:${s.status}:${o}`)}return s.json()}async function ke(){W(process.env.WORKER_ENV_FILE||".env");let t=String(process.env.OPENCLAW_AGENT_KEYS||process.env.AGENT_API_KEYS||"").trim(),e=String(process.env.OPENCLAW_AGENT_NAMES||process.env.AGENT_NAMES||"").trim(),r=t.split(",")[0]?.trim()||String(process.env.AGENT_API_KEY||"").trim();if(!r)throw new Error("No agent API keys configured. Set OPENCLAW_AGENT_KEYS (comma-separated) or AGENT_API_KEY.");let o=String(process.env.OPENCLAW_MODEL_NAMES||process.env.MODEL_NAMES||"").trim()||(e?e.split(",").map(g=>`openclaw:${g.trim()}`).join(","):"openclaw:main"),n={...process.env,AGENT_API_KEY:r,AGENT_API_KEYS:t,AGENT_NAMES:e,MODEL_NAMES:o},i=F(n),l=new y(i[0]?.logLevel||"info",{app:"aipm-openclaw-runtime-main"});for(let g of i)l.info("worker model assignment",{worker:g.workerName,model:g.modelName,baseUrl:g.modelBaseUrl});let a=new A(i,{logger:l}),d=(process.argv[2]||"start").toLowerCase();if(d==="status"){let g=await a.persistedStatus();console.log(JSON.stringify(g,null,2));return}d!=="start"&&(console.error(`Unknown command: ${d}`),process.exit(1));let p=!1,m=async g=>{l.info("OpenClaw runtime shutdown signal",{signal:g}),p=!0,await a.stop()};process.on("SIGINT",()=>void m("SIGINT")),process.on("SIGTERM",()=>void m("SIGTERM"));let c=String(process.env.AIPM_BASE_URL||"").replace(/\/+$/,""),u=String(process.env.WORKER_RUNTIME_CONTROL_TOKEN||"").trim(),_=String(process.env.WORKER_RUNTIME_PROJECT_ID||"").trim(),M=String(process.env.OPENCLAW_RUNTIME_NAME||process.env.WORKER_RUNTIME_NAME||"openclaw-runtime").trim(),C=String(process.env.WORKER_RUNTIME_HOST_ID||(0,se.hostname)()||"localhost").trim(),ne=Math.max(1e3,Number(process.env.WORKER_RUNTIME_HEARTBEAT_MS||5e3));if(!c||!u)throw new Error("AIPM_BASE_URL and WORKER_RUNTIME_CONTROL_TOKEN are required");let N=`${c}/worker-runtime-control`,oe=a.start();for(l.info("OpenClaw runtime started",{runtimeName:M,hostId:C,workerCount:i.length});!p;){try{let g=await a.snapshotForControlPlane(),x=_||String(g[0]?.diagnostics?.projectId||"");if(!x)throw new Error("worker_runtime_project_id_missing");let ie=g.map(w=>({agent_id:String(w.diagnostics.agentId||""),runtime_name:M,host_id:C,state:_e(String(w.diagnostics.workerState||"running")),last_error:w.diagnostics.lastError||null,metadata:{workerName:w.name,restartCount:w.restartCount,queueDepth:Number(w.diagnostics.queueDepth??0),metrics:w.diagnostics.metrics||{}}})),$=await re(N,u,{action:"heartbeat",project_id:x,runtime_name:M,host_id:C,mode:"openclaw",status:"running",workers:[],openclaw_bindings:ie}),ae=Array.isArray($.commands)?$.commands:[];for(let w of ae){let h=w,D="done",S={},P=null;try{h.command_type==="sync_openclaw"?S={ok:!0,action:"sync_openclaw",workers:i.length}:h.command_type==="start_openclaw"?S={ok:!0,action:"start_openclaw_noop_when_running"}:h.command_type==="restart_openclaw"?S=await a.applyCommand({id:h.id,command_type:"restart_worker",target_worker_agent_id:h.target_worker_agent_id,payload:h.payload}):h.command_type==="stop_openclaw"&&(S=await a.applyCommand({id:h.id,command_type:"restart_worker",target_worker_agent_id:h.target_worker_agent_id,payload:h.payload}))}catch(v){D="failed",P=v instanceof Error?v.message:String(v),l.error("OpenClaw command failed",{command_type:h.command_type,id:h.id,error:P})}await re(N,u,{action:"command_result",project_id:x,command_id:h.id,status:D,result:S,error_message:P})}}catch(g){l.error("OpenClaw managed heartbeat failed",{error:g instanceof Error?g.message:String(g)})}await new Promise(g=>setTimeout(g,ne))}await oe}ke().catch(t=>{console.error(JSON.stringify({ts:new Date().toISOString(),level:"error",message:"OpenClaw runtime crashed",error:t instanceof Error?t.message:String(t)})),process.exit(1)});
|
package/dist/supervisor.cjs
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
var O=require("node:fs"),$=require("node:path");function N(s=".env",e=process.env){try{let t=(0,O.readFileSync)((0,$.resolve)(s),"utf8");for(let r of t.split(/\r?\n/)){let o=r.trim();if(!o||o.startsWith("#"))continue;let n=o.indexOf("=");if(n<=0)continue;let i=o.slice(0,n).trim(),a=o.slice(n+1).trim();e[i]||(e[i]=a)}}catch{}}var D=require("node:fs"),b=require("node:path");function p(s,e){let t=Number(s);return!Number.isFinite(t)||t<=0?e:Math.floor(t)}function ee(s){let e=s.trim().replace(/\/+$/,"");if(!e)throw new Error("AIPM_BASE_URL is required");return e}function te(s=process.env){let e=ee(s.AIPM_BASE_URL||""),t=String(s.AGENT_API_KEY||s.AIPM_API_KEY||"").trim();if(!t)throw new Error("AGENT_API_KEY (or AIPM_API_KEY) is required");let r=(0,b.resolve)(s.STATE_FILE_PATH||`.worker-state/${t.slice(0,12)}.json`),o=(0,b.resolve)(r,"..");(0,D.existsSync)(o);let n=String(s.LOG_LEVEL||"info").toLowerCase(),i=["debug","info","warn","error"].includes(n)?n:"info",a=p(s.MAX_CONCURRENCY,2),l=String(s.MODEL_PROVIDER||"anthropic").toLowerCase();return{aipmBaseUrl:e,agentApiKey:t,pollIntervalMs:p(s.POLL_INTERVAL_MS,5e3),maxConcurrency:a,inboxConcurrency:p(s.INBOX_CONCURRENCY,1),taskConcurrency:p(s.TASK_CONCURRENCY,a),logLevel:i,leaseTtlMs:p(s.LEASE_TTL_MS,6e4),stateFilePath:r,heartbeatIntervalMs:p(s.HEARTBEAT_INTERVAL_MS,15e3),maxRetries:p(s.MAX_RETRIES,4),retryBaseMs:p(s.RETRY_BASE_MS,500),retryMaxMs:p(s.RETRY_MAX_MS,8e3),circuitFailureThreshold:p(s.CIRCUIT_FAILURE_THRESHOLD,5),circuitCooldownMs:p(s.CIRCUIT_COOLDOWN_MS,3e4),modelProvider:l==="openai"?"openai":"anthropic",modelApiKey:String(s.MODEL_API_KEY||"").trim()||null,modelName:String(s.MODEL_NAME||"claude-sonnet-4-6").trim(),modelBaseUrl:String(s.MODEL_BASE_URL||"").trim()||null,modelMaxTokens:p(s.MODEL_MAX_TOKENS,4096),modelSystemPrompt:s.MODEL_SYSTEM_PROMPT?String(s.MODEL_SYSTEM_PROMPT):null}}function j(s=process.env){let e=te(s),t=String(s.AGENT_API_KEYS||"").trim(),r=String(s.AGENT_NAMES||"").trim();if(!t)return[e];let o=t.split(",").map(i=>i.trim()).filter(Boolean);if(o.length===0)return[e];let n=r?r.split(",").map(i=>i.trim()).filter(Boolean):[];return o.map((i,a)=>{let l=n[a]||`agent-${a+1}`,d=(0,b.resolve)(s[`STATE_FILE_PATH_${a+1}`]||`.worker-state/${l}.json`);return{...e,agentApiKey:i,stateFilePath:d,workerName:l}})}var W={debug:10,info:20,warn:30,error:40},w=class s{constructor(e,t={}){this.minLevel=e;this.ctx=t}child(e){return new s(this.minLevel,{...this.ctx,...e})}debug(e,t){this.log("debug",e,t)}info(e,t){this.log("info",e,t)}warn(e,t){this.log("warn",e,t)}error(e,t){this.log("error",e,t)}log(e,t,r){if(W[e]<W[this.minLevel])return;let o={ts:new Date().toISOString(),level:e,message:t,...this.ctx,...r||{}},n=JSON.stringify(o);e==="error"?console.error(n):e==="warn"?console.warn(n):console.log(n)}};var I=class{constructor(e,t){this.baseUrl=e;this.apiKey=t}async call(e,t){let r=await fetch(`${this.baseUrl}${e}`,{...t,headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.apiKey}`,...t?.headers||{}}});if(!r.ok){let o="";try{let n=await r.json();o=n?.error||n?.code||""}catch{}throw new Error(`AIPM ${e} -> ${r.status}${o?` (${o})`:""}`)}return await r.json()}bootstrap(){return this.call("/agent-bootstrap",{method:"GET"})}updateStatus(e){return this.call("/api-agent-status",{method:"POST",body:JSON.stringify(e)})}async listInboxUnread(){return(await this.call("/agent-inbox?unread=true",{method:"GET"})).inbox||[]}markInboxRead(e){return this.call(`/agent-inbox/${e}/read`,{method:"POST",body:JSON.stringify({})})}sendInboxMessage(e){return this.call("/agent-inbox",{method:"POST",body:JSON.stringify(e)})}listMyTasks(e){let t=e?`?status=${encodeURIComponent(e)}`:"";return this.call(`/agent-tasks${t}`,{method:"GET"})}listMyActiveTasks(){return this.call("/agent-tasks?status=pending,in_progress",{method:"GET"})}createTask(e){return this.call("/agent-tasks",{method:"POST",body:JSON.stringify(e)})}getTask(e){return this.call(`/agent-task-state/task/${e}`,{method:"GET"})}taskStatus(e,t,r){return this.call("/agent-task-state/status",{method:"POST",body:JSON.stringify({task_id:e,status:t,reason:r})})}taskLog(e,t,r="info"){return this.call("/agent-task-state/log",{method:"POST",body:JSON.stringify({task_id:e,message:t,level:r})})}};function se(s,e=140){let t=s.replace(/\s+/g," ").trim();return t.length<=e?t:`${t.slice(0,e-1)}...`}function re(s){return(s||"").trim().toLowerCase().startsWith("[ack]")||(s||"").trim().toLowerCase().startsWith("ack:")}function U(s){return s.match(/agent_id\s*:\s*([0-9a-fA-F-]{36})/)?.[1]||null}function ne(s){let e=(s.subject||"").toLowerCase(),t=(s.body||"").toLowerCase();return s.taskRef||U(s.body)?!1:e.includes("live")||e.includes("check")||e.includes("ping")||t.includes("are you live")||t.includes("quick live check")||t.includes("reply with")}async function F(s,e){let{client:t,logger:r,agentId:o}=e,n=r.child({inboxMessageId:s.id});if(s.type==="urgent"&&n.info("Urgent inbox item observed"),s.from_agent_id===o||re(s.subject)){await t.markInboxRead(s.id),n.debug("Ignored self/ack inbox message");return}let i=U(s.body||"");if(!s.taskRef&&i&&s.from_agent_id){let l=await t.sendInboxMessage({to_agent_id:i,subject:`Hello from ${o}`,message:`Hi \u2014 quick live check from peer worker ${o}. Are you live?`});await t.sendInboxMessage({to_agent_id:s.from_agent_id,subject:"Peer message sent",message:`Executed peer message command. target_agent_id=${i}, outbound_message_id=${l.message?.id??"unknown"}`}),await t.markInboxRead(s.id),n.info("Peer message command executed",{peerAgentId:i,outboundMessageId:l.message?.id});return}if(ne(s)&&s.from_agent_id){await t.sendInboxMessage({to_agent_id:s.from_agent_id,subject:`Ack: ${s.subject||"message received"}`,message:`Hi from worker ${o}. I'm live at ${new Date().toISOString()}.`,task_ref:s.taskRef||void 0}),await t.markInboxRead(s.id),n.info("Inbox ping acknowledged without task creation");return}let a=s.taskRef||null;if(!a){let l=s.subject?.trim()?`Inbox follow-up: ${s.subject}`:`Inbox follow-up: ${se(s.body,64)}`;a=(await t.createTask({title:l,description:s.body,assigned_to:o,priority:"medium"})).task_id,n.info("Created task from inbox",{taskId:a})}s.from_agent_id&&await t.sendInboxMessage({to_agent_id:s.from_agent_id,subject:`[Ack] ${s.subject||"Message received"}`,message:`Received message ${s.id}. Linked task: ${a||"none"}.`,task_ref:a||void 0}),await t.markInboxRead(s.id),n.info("Inbox message processed",{taskId:a})}var q="You are an autonomous AI agent. Complete the assigned task using the available tools. When done, call complete_task with a summary. If you cannot complete it, call fail_task with the reason.",K=[{name:"log_progress",description:"Log a progress note to the task. Use for intermediate updates.",parameters:{type:"object",properties:{message:{type:"string",description:"Progress message"}},required:["message"]}},{name:"complete_task",description:"Mark the task as complete. Call this when work is done.",parameters:{type:"object",properties:{summary:{type:"string",description:"Summary of what was accomplished"}},required:["summary"]}},{name:"fail_task",description:"Mark the task as failed. Call this if you cannot complete it.",parameters:{type:"object",properties:{reason:{type:"string",description:"Reason for failure"}},required:["reason"]}},{name:"create_subtask",description:"Create a subtask to delegate work to another agent.",parameters:{type:"object",properties:{title:{type:"string",description:"Subtask title"},description:{type:"string",description:"Subtask description"},assigned_to:{type:"string",description:"Agent ID or name (optional)"},priority:{type:"string",enum:["low","medium","high","urgent"]}},required:["title","description"]}},{name:"send_message",description:"Send an inbox message to another agent.",parameters:{type:"object",properties:{to_agent_id:{type:"string",description:"Target agent ID"},subject:{type:"string",description:"Message subject"},message:{type:"string",description:"Message body"}},required:["to_agent_id","subject","message"]}}],oe=K.map(s=>({name:s.name,description:s.description,input_schema:s.parameters})),ie=K.map(s=>({type:"function",function:{name:s.name,description:s.description,parameters:s.parameters}}));async function B(s,e,t){let{client:r,taskId:o}=t;switch(s){case"log_progress":return await r.taskLog(o,String(e.message||""),"info"),{result:"Logged.",done:!1,failed:!1,summary:""};case"complete_task":{let n=String(e.summary||"Task completed.");return{result:n,done:!0,failed:!1,summary:n}}case"fail_task":{let n=String(e.reason||"Unknown failure.");return{result:n,done:!0,failed:!0,summary:n}}case"create_subtask":return{result:`Subtask created: ${(await r.createTask({title:String(e.title),description:String(e.description||""),assigned_to:e.assigned_to?String(e.assigned_to):void 0,priority:e.priority?String(e.priority):"medium"})).task_id}`,done:!1,failed:!1,summary:""};case"send_message":return{result:`Message sent: ${(await r.sendInboxMessage({to_agent_id:String(e.to_agent_id),subject:String(e.subject||""),message:String(e.message||""),task_ref:o})).message?.id??"unknown"}`,done:!1,failed:!1,summary:""};default:return{result:`Unknown tool: ${s}`,done:!1,failed:!1,summary:""}}}async function ae(s,e,t){let r=(e.baseUrl||"https://api.anthropic.com").replace(/\/+$/,""),o=e.systemPrompt||q,n=[{role:"user",content:`Task: ${s.title}
|
|
2
|
+
var $=require("node:fs"),N=require("node:path");function D(r=".env",e=process.env){try{let t=(0,$.readFileSync)((0,N.resolve)(r),"utf8");for(let s of t.split(/\r?\n/)){let o=s.trim();if(!o||o.startsWith("#"))continue;let n=o.indexOf("=");if(n<=0)continue;let i=o.slice(0,n).trim(),l=o.slice(n+1).trim();e[i]||(e[i]=l)}}catch{}}var j=require("node:fs"),R=require("node:path");function m(r,e){let t=Number(r);return!Number.isFinite(t)||t<=0?e:Math.floor(t)}function se(r){let e=r.trim().replace(/\/+$/,"");if(!e)throw new Error("AIPM_BASE_URL is required");return e}function ne(r=process.env){let e=se(r.AIPM_BASE_URL||""),t=String(r.AGENT_API_KEY||r.AIPM_API_KEY||"").trim();if(!t)throw new Error("AGENT_API_KEY (or AIPM_API_KEY) is required");let s=(0,R.resolve)(r.STATE_FILE_PATH||`.worker-state/${t.slice(0,12)}.json`),o=(0,R.resolve)(s,"..");(0,j.existsSync)(o);let n=String(r.LOG_LEVEL||"info").toLowerCase(),i=["debug","info","warn","error"].includes(n)?n:"info",l=m(r.MAX_CONCURRENCY,2),a=String(r.MODEL_PROVIDER||"anthropic").toLowerCase();return{aipmBaseUrl:e,agentApiKey:t,pollIntervalMs:m(r.POLL_INTERVAL_MS,5e3),maxConcurrency:l,inboxConcurrency:m(r.INBOX_CONCURRENCY,1),taskConcurrency:m(r.TASK_CONCURRENCY,l),logLevel:i,leaseTtlMs:m(r.LEASE_TTL_MS,6e4),stateFilePath:s,heartbeatIntervalMs:m(r.HEARTBEAT_INTERVAL_MS,15e3),maxRetries:m(r.MAX_RETRIES,4),retryBaseMs:m(r.RETRY_BASE_MS,500),retryMaxMs:m(r.RETRY_MAX_MS,8e3),circuitFailureThreshold:m(r.CIRCUIT_FAILURE_THRESHOLD,5),circuitCooldownMs:m(r.CIRCUIT_COOLDOWN_MS,3e4),modelProvider:a==="openai"?"openai":"anthropic",modelApiKey:String(r.MODEL_API_KEY||"").trim()||null,modelTokenUrl:String(r.MODEL_TOKEN_URL||"").trim()||null,modelName:String(r.MODEL_NAME||"claude-sonnet-4-6").trim(),modelBaseUrl:String(r.MODEL_BASE_URL||"").trim()||null,modelMaxTokens:m(r.MODEL_MAX_TOKENS,4096),modelSystemPrompt:r.MODEL_SYSTEM_PROMPT?String(r.MODEL_SYSTEM_PROMPT):null}}function U(r=process.env){let e=ne(r),t=String(r.AGENT_API_KEYS||"").trim(),s=String(r.AGENT_NAMES||"").trim();if(!t)return[e];let o=t.split(",").map(a=>a.trim()).filter(Boolean);if(o.length===0)return[e];let n=s?s.split(",").map(a=>a.trim()).filter(Boolean):[],i=String(r.MODEL_NAMES||"").trim(),l=i?i.split(",").map(a=>a.trim()).filter(Boolean):[];return o.map((a,u)=>{let g=n[u]||`agent-${u+1}`,p=(0,R.resolve)(r[`STATE_FILE_PATH_${u+1}`]||`.worker-state/${g}.json`);return{...e,agentApiKey:a,stateFilePath:p,workerName:g,...l[u]?{modelName:l[u]}:{}}})}var W={debug:10,info:20,warn:30,error:40},y=class r{constructor(e,t={}){this.minLevel=e;this.ctx=t}child(e){return new r(this.minLevel,{...this.ctx,...e})}debug(e,t){this.log("debug",e,t)}info(e,t){this.log("info",e,t)}warn(e,t){this.log("warn",e,t)}error(e,t){this.log("error",e,t)}log(e,t,s){if(W[e]<W[this.minLevel])return;let o={ts:new Date().toISOString(),level:e,message:t,...this.ctx,...s||{}},n=JSON.stringify(o);e==="error"?console.error(n):e==="warn"?console.warn(n):console.log(n)}};var I=class{constructor(e,t){this.baseUrl=e;this.apiKey=t}async call(e,t){let s=await fetch(`${this.baseUrl}${e}`,{...t,headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.apiKey}`,...t?.headers||{}}});if(!s.ok){let o="";try{let n=await s.json();o=n?.error||n?.code||""}catch{}throw new Error(`AIPM ${e} -> ${s.status}${o?` (${o})`:""}`)}return await s.json()}bootstrap(){return this.call("/agent-bootstrap",{method:"GET"})}updateStatus(e){return this.call("/api-agent-status",{method:"POST",body:JSON.stringify(e)})}async listInboxUnread(){return(await this.call("/agent-inbox?unread=true",{method:"GET"})).inbox||[]}markInboxRead(e){return this.call(`/agent-inbox/${e}/read`,{method:"POST",body:JSON.stringify({})})}sendInboxMessage(e){return this.call("/agent-inbox",{method:"POST",body:JSON.stringify(e)})}listMyTasks(e){let t=e?`?status=${encodeURIComponent(e)}`:"";return this.call(`/agent-tasks${t}`,{method:"GET"})}async listMyActiveTasks(){let[e,t]=await Promise.all([this.listMyTasks("pending"),this.listMyTasks("in_progress")]);return{tasks:[...e.tasks||[],...t.tasks||[]]}}createTask(e){return this.call("/agent-tasks",{method:"POST",body:JSON.stringify(e)})}getTask(e){return this.call(`/agent-task-state/task/${e}`,{method:"GET"})}taskStatus(e,t,s){return this.call("/agent-task-state/status",{method:"POST",body:JSON.stringify({task_id:e,status:t,reason:s})})}taskLog(e,t,s="info"){return this.call("/agent-task-state/log",{method:"POST",body:JSON.stringify({task_id:e,message:t,level:s})})}};function oe(r,e=140){let t=r.replace(/\s+/g," ").trim();return t.length<=e?t:`${t.slice(0,e-1)}...`}function ie(r){return(r||"").trim().toLowerCase().startsWith("[ack]")||(r||"").trim().toLowerCase().startsWith("ack:")}function F(r){return r.match(/agent_id\s*:\s*([0-9a-fA-F-]{36})/)?.[1]||null}function ae(r){let e=(r.subject||"").toLowerCase(),t=(r.body||"").toLowerCase();return r.taskRef||F(r.body)?!1:e.includes("live")||e.includes("check")||e.includes("ping")||t.includes("are you live")||t.includes("quick live check")||t.includes("reply with")}async function q(r,e){let{client:t,logger:s,agentId:o}=e,n=s.child({inboxMessageId:r.id});if(r.type==="urgent"&&n.info("Urgent inbox item observed"),r.from_agent_id===o||ie(r.subject)){await t.markInboxRead(r.id),n.debug("Ignored self/ack inbox message");return}let i=F(r.body||"");if(!r.taskRef&&i&&r.from_agent_id){let a=await t.sendInboxMessage({to_agent_id:i,subject:`Hello from ${o}`,message:`Hi \u2014 quick live check from peer worker ${o}. Are you live?`});await t.sendInboxMessage({to_agent_id:r.from_agent_id,subject:"Peer message sent",message:`Executed peer message command. target_agent_id=${i}, outbound_message_id=${a.message?.id??"unknown"}`}),await t.markInboxRead(r.id),n.info("Peer message command executed",{peerAgentId:i,outboundMessageId:a.message?.id});return}if(ae(r)&&r.from_agent_id){await t.sendInboxMessage({to_agent_id:r.from_agent_id,subject:`Ack: ${r.subject||"message received"}`,message:`Hi from worker ${o}. I'm live at ${new Date().toISOString()}.`,task_ref:r.taskRef||void 0}),await t.markInboxRead(r.id),n.info("Inbox ping acknowledged without task creation");return}let l=r.taskRef||null;if(!l){let a=r.subject?.trim()?`Inbox follow-up: ${r.subject}`:`Inbox follow-up: ${oe(r.body,64)}`;l=(await t.createTask({title:a,description:r.body,assigned_to:o,priority:"medium"})).task_id,n.info("Created task from inbox",{taskId:l})}r.from_agent_id&&await t.sendInboxMessage({to_agent_id:r.from_agent_id,subject:`[Ack] ${r.subject||"Message received"}`,message:`Received message ${r.id}. Linked task: ${l||"none"}.`,task_ref:l||void 0}),await t.markInboxRead(r.id),n.info("Inbox message processed",{taskId:l})}var b=null;async function K(r){if(!r.tokenUrl)return r.apiKey;let e=Date.now();if(b&&b.expiresAt>e+6e4)return b.token;let t=await fetch(r.tokenUrl,{method:"GET"});if(!t.ok){let o=await t.text();throw new Error(`Token provider returned ${t.status}: ${o}`)}let s=await t.json();if(!s.token)throw new Error("Token provider response missing 'token' field");return b={token:s.token,expiresAt:e+(s.expires_in??3600)*1e3},b.token}var B=20,H="You are an autonomous AI agent. Complete the assigned task using the available tools. When done, call complete_task with a summary. If you cannot complete it, call fail_task with the reason.",J=[{name:"log_progress",description:"Log a progress note to the task. Use for intermediate updates.",parameters:{type:"object",properties:{message:{type:"string",description:"Progress message"}},required:["message"]}},{name:"complete_task",description:"Mark the task as complete. Call this when work is done.",parameters:{type:"object",properties:{summary:{type:"string",description:"Summary of what was accomplished"}},required:["summary"]}},{name:"fail_task",description:"Mark the task as failed. Call this if you cannot complete it.",parameters:{type:"object",properties:{reason:{type:"string",description:"Reason for failure"}},required:["reason"]}},{name:"create_subtask",description:"Create a subtask to delegate work to another agent.",parameters:{type:"object",properties:{title:{type:"string",description:"Subtask title"},description:{type:"string",description:"Subtask description"},assigned_to:{type:"string",description:"Agent ID or name (optional)"},priority:{type:"string",enum:["low","medium","high","urgent"]}},required:["title","description"]}},{name:"send_message",description:"Send an inbox message to another agent.",parameters:{type:"object",properties:{to_agent_id:{type:"string",description:"Target agent ID"},subject:{type:"string",description:"Message subject"},message:{type:"string",description:"Message body"}},required:["to_agent_id","subject","message"]}}],ce=J.map(r=>({name:r.name,description:r.description,input_schema:r.parameters})),le=J.map(r=>({type:"function",function:{name:r.name,description:r.description,parameters:r.parameters}}));async function G(r,e,t){let{client:s,taskId:o}=t;switch(r){case"log_progress":return await s.taskLog(o,String(e.message||""),"info"),{result:"Logged.",done:!1,failed:!1,summary:""};case"complete_task":{let n=String(e.summary||"Task completed.");return{result:n,done:!0,failed:!1,summary:n}}case"fail_task":{let n=String(e.reason||"Unknown failure.");return{result:n,done:!0,failed:!0,summary:n}}case"create_subtask":return{result:`Subtask created: ${(await s.createTask({title:String(e.title),description:String(e.description||""),assigned_to:e.assigned_to?String(e.assigned_to):void 0,priority:e.priority?String(e.priority):"medium"})).task_id}`,done:!1,failed:!1,summary:""};case"send_message":return{result:`Message sent: ${(await s.sendInboxMessage({to_agent_id:String(e.to_agent_id),subject:String(e.subject||""),message:String(e.message||""),task_ref:o})).message?.id??"unknown"}`,done:!1,failed:!1,summary:""};default:return{result:`Unknown tool: ${r}`,done:!1,failed:!1,summary:""}}}async function ue(r,e,t){let s=await K(e),o=(e.baseUrl||"https://api.anthropic.com").replace(/\/+$/,""),n=e.systemPrompt||H,i=[{role:"user",content:`Task: ${r.title}
|
|
3
3
|
|
|
4
4
|
Description:
|
|
5
|
-
${
|
|
5
|
+
${r.description||"(no description provided)"}`}];for(let l=0;l<B;l++){let a=await fetch(`${o}/v1/messages`,{method:"POST",headers:{"x-api-key":s,"anthropic-version":"2023-06-01","content-type":"application/json"},body:JSON.stringify({model:e.model,max_tokens:e.maxTokens,system:n,messages:i,tools:ce})});if(!a.ok){let c=await a.text();throw new Error(`Anthropic API ${a.status}: ${c}`)}let u=await a.json();i.push({role:"assistant",content:u.content});let g=u.content.filter(c=>c.type==="tool_use");if(g.length===0)return{summary:u.content.find(d=>d.type==="text")?.text||"Task processed.",status:"done"};let p=[];for(let c of g){let d=await G(c.name,c.input,t);if(p.push({type:"tool_result",tool_use_id:c.id,content:d.result}),d.done)return{summary:d.summary||d.result,status:d.failed?"failed":"done"}}i.push({role:"user",content:p})}return{summary:"Max iterations reached.",status:"done"}}async function de(r,e,t){let s=await K(e),o=(e.baseUrl||"https://api.openai.com").replace(/\/+$/,""),i=[{role:"system",content:e.systemPrompt||H},{role:"user",content:`Task: ${r.title}
|
|
6
6
|
|
|
7
7
|
Description:
|
|
8
|
-
${
|
|
9
|
-
Title: ${
|
|
8
|
+
${r.description||"(no description provided)"}`}];for(let l=0;l<B;l++){let a=await fetch(`${o}/v1/chat/completions`,{method:"POST",headers:{Authorization:`Bearer ${s}`,"content-type":"application/json"},body:JSON.stringify({model:e.model,max_tokens:e.maxTokens,messages:i,tools:le,tool_choice:"auto"})});if(!a.ok){let c=await a.text();throw new Error(`OpenAI API ${a.status}: ${c}`)}let g=(await a.json()).choices[0];if(!g)throw new Error("Empty choices in model response");i.push(g.message);let p=g.message.tool_calls;if(!p||p.length===0)return{summary:g.message.content||"Task processed.",status:"done"};for(let c of p){let d={};try{d=JSON.parse(c.function.arguments)}catch{}let f=await G(c.function.name,d,t);if(i.push({role:"tool",tool_call_id:c.id,name:c.function.name,content:f.result}),f.done)return{summary:f.summary||f.result,status:f.failed?"failed":"done"}}}return{summary:"Max iterations reached.",status:"done"}}async function Y(r,e,t){return e.provider==="anthropic"?ue(r,e,t):de(r,e,t)}async function X(r,e){let{client:t,logger:s}=e,o=s.child({taskId:r.id});if(r.status==="done"||r.status==="cancelled"){o.debug("Skipping terminal task");return}if(r.status!=="in_progress"&&await t.taskStatus(r.id,"in_progress","worker_claimed_task"),e.aiRunner){o.info("AI runner invoked",{provider:e.aiRunner.provider,model:e.aiRunner.model});let i=await Y(r,e.aiRunner,{client:t,taskId:r.id,logger:o,agentId:e.agentId});await t.taskLog(r.id,`AI execution complete: ${i.summary}`,i.status==="failed"?"warn":"info");let l=null;r.assigned_by&&r.assigned_by!==e.agentId&&(l=(await t.sendInboxMessage({to_agent_id:r.assigned_by,subject:`[Handoff] ${r.title}`,message:`Task ${r.id} processed.
|
|
9
|
+
Title: ${r.title}
|
|
10
10
|
Status: ${i.status}
|
|
11
|
-
Summary: ${i.summary}`,task_ref:
|
|
12
|
-
Title: ${
|
|
13
|
-
Status: ready_for_review`,task_ref:s.id})).message?.id||null,n||o.warn("Handoff sent but response carried no message id",{assignedBy:s.assigned_by})),await t.taskStatus(s.id,"done","worker_completed"),o.info("Task completed",{handoffMessageId:n})}function le(s,e,t,r=.2){let o=Math.min(t,e*Math.pow(2,Math.max(0,s-1))),n=o*r*Math.random();return Math.floor(o+n)}async function G(s,e){let t;for(let r=1;r<=e.maxAttempts;r++)try{return await s(r)}catch(o){if(t=o,r>=e.maxAttempts||e.shouldRetry&&!e.shouldRetry(o,r))break;let n=le(r,e.baseMs,e.maxMs,e.jitterRatio);await new Promise(i=>setTimeout(i,n))}throw t instanceof Error?t:new Error("retry_failed")}var y=require("node:fs/promises"),M=require("node:path"),Y={processed:{},leases:{},checkpoints:{workerState:"idle",lastHeartbeatAt:null,lastError:null,queueDepth:0,metrics:{processedCount:0,failedCount:0,retryCount:0,avgProcessingLatencyMs:0}}};function x(s,e,t){return`${s}:${e}:${t}`}var k=class{constructor(e){this.filePath=e}state=structuredClone(Y);loaded=!1;async load(){if(!this.loaded){try{let e=await(0,y.readFile)(this.filePath,"utf8");this.state={...Y,...JSON.parse(e)}}catch{await(0,y.mkdir)((0,M.dirname)(this.filePath),{recursive:!0}),await this.flush()}this.loaded=!0,this.cleanupExpiredLeases()}}cleanupExpiredLeases(){let e=Date.now();for(let[t,r]of Object.entries(this.state.leases))r.expiresAt<=e&&delete this.state.leases[t]}async flush(){await(0,y.mkdir)((0,M.dirname)(this.filePath),{recursive:!0}),await(0,y.writeFile)(this.filePath,JSON.stringify(this.state,null,2),"utf8")}async isProcessed(e){return await this.load(),!!this.state.processed[e]}async markProcessed(e){await this.load(),this.state.processed[e]={processedAt:new Date().toISOString()},await this.flush()}async tryAcquireLease(e,t,r){return await this.load(),this.cleanupExpiredLeases(),this.state.leases[e]?!1:(this.state.leases[e]={owner:t,expiresAt:Date.now()+r},await this.flush(),!0)}async releaseLease(e,t){await this.load(),this.state.leases[e]?.owner===t&&(delete this.state.leases[e],await this.flush())}async setWorkerState(e,t){await this.load(),this.state.checkpoints.workerState=e,t!==void 0&&(this.state.checkpoints.lastError=t),await this.flush()}async heartbeat(){await this.load(),this.state.checkpoints.lastHeartbeatAt=new Date().toISOString(),await this.flush()}async setQueueDepth(e){await this.load(),this.state.checkpoints.queueDepth=e,await this.flush()}async addMetric(e,t=1){await this.load(),this.state.checkpoints.metrics[e]+=t,await this.flush()}async updateProcessingLatency(e){await this.load();let t=this.state.checkpoints.metrics,r=Math.max(1,t.processedCount);t.avgProcessingLatencyMs=(t.avgProcessingLatencyMs*(r-1)+e)/r,await this.flush()}async getDiagnostics(){return await this.load(),this.cleanupExpiredLeases(),{...this.state.checkpoints,leaseCount:Object.keys(this.state.leases).length,processedKeys:Object.keys(this.state.processed).length}}};var X={urgent:4,high:3,medium:2,low:1},ue=3e4,_=class{constructor(e){this.deps=e;this.logger=e.logger||new w(e.config.logLevel,{scope:"resident-worker"}),this.client=e.client||new I(e.config.aipmBaseUrl,e.config.agentApiKey),this.store=e.stateStore||new k(e.config.stateFilePath),this.currentPollMs=e.config.pollIntervalMs}logger;client;store;running=!1;stopRequested=!1;agentId="";projectId="";consecutiveFailures=0;currentPollMs;leaseOwner=`worker-${process.pid}-${Math.random().toString(16).slice(2,8)}`;async start(){if(this.running)return;this.running=!0,this.stopRequested=!1,await this.store.load();let e=await this.client.bootstrap();for(this.agentId=e.agent.id,this.projectId=e.projectId,this.logger.info("Worker bootstrap complete",{agentId:this.agentId,role:e.agent.role}),await this.client.updateStatus({status:"idle",currentTask:"resident_loop_online"}),await this.store.setWorkerState("idle",null);!this.stopRequested;){let t=Date.now();try{await this.store.heartbeat(),await this.tick(),this.consecutiveFailures=0}catch(n){this.consecutiveFailures+=1;let i=n instanceof Error?n.message:"loop_error";if(this.logger.error("Loop iteration failed",{error:i,consecutiveFailures:this.consecutiveFailures}),await this.store.addMetric("failedCount",1),await this.store.setWorkerState("error",i),this.consecutiveFailures>=this.deps.config.circuitFailureThreshold){await this.client.updateStatus({status:"blocked",blockedReason:`Circuit open after ${this.consecutiveFailures} failures`,blockedBy:"resident_worker_loop"}),await this.store.setWorkerState("blocked",i),await this.sleep(this.deps.config.circuitCooldownMs),this.consecutiveFailures=0;continue}}let r=Date.now()-t,o=Math.max(250,this.currentPollMs-r);await this.sleep(o)}await this.client.updateStatus({status:"idle",currentTask:"resident_loop_stopped"}),await this.store.setWorkerState("stopped",null),this.running=!1}async stop(){this.stopRequested=!0}async diagnostics(){let e=await this.store.getDiagnostics();return{agentId:this.agentId||null,projectId:this.projectId||null,running:this.running,stopRequested:this.stopRequested,...e}}async tick(){await this.client.updateStatus({status:"idle",currentTask:"polling_inbox_and_tasks"});let[e,t]=await Promise.all([this.client.listInboxUnread(),this.client.listMyActiveTasks().then(c=>c.tasks||[])]),r=t.filter(c=>c.status==="pending"),o=t.filter(c=>c.status==="in_progress"),n=e.length+t.length;if(this.logger.debug("Fetched work queues",{inboxCount:e.length,pendingTaskCount:r.length,activeTaskCount:o.length,queueDepth:n}),await this.store.setQueueDepth(n),n===0?(this.currentPollMs=Math.min(this.currentPollMs*1.5,ue),this.logger.debug("Queue empty, backing off poll",{nextPollMs:Math.round(this.currentPollMs)})):this.currentPollMs=this.deps.config.pollIntervalMs,n===0)return;let i=[...e].sort((c,g)=>c.type==="urgent"&&g.type!=="urgent"?-1:g.type==="urgent"&&c.type!=="urgent"?1:0),a=[...r,...o].sort((c,g)=>(X[g.priority]??0)-(X[c.priority]??0)),l=`inbox:${i.length},tasks:${a.length}`;await this.client.updateStatus({status:"working",currentTask:l}),await this.store.setWorkerState("working",null);let d=this.deps.config.modelApiKey?{provider:this.deps.config.modelProvider,apiKey:this.deps.config.modelApiKey,model:this.deps.config.modelName,baseUrl:this.deps.config.modelBaseUrl,maxTokens:this.deps.config.modelMaxTokens,systemPrompt:this.deps.config.modelSystemPrompt}:void 0,m=i.map(c=>()=>this.processInbox(c.id,()=>F(c,{client:this.client,logger:this.logger,agentId:this.agentId}))),u=a.map(c=>()=>this.processTask(c.id,()=>J(c,{client:this.client,logger:this.logger,agentId:this.agentId,aiRunner:d})));await Promise.all([this.runWithConcurrency(m,this.deps.config.inboxConcurrency),this.runWithConcurrency(u,this.deps.config.taskConcurrency)])}async processInbox(e,t){let r=x(this.agentId,e,"inbox");return this.processWorkItem(r,`inbox:${e}`,t)}async processTask(e,t){let r=x(this.agentId,e,"task");return this.processWorkItem(r,`task:${e}`,t)}async processWorkItem(e,t,r){if(await this.store.isProcessed(e)||!await this.store.tryAcquireLease(e,this.leaseOwner,this.deps.config.leaseTtlMs))return;let n=Date.now();this.logger.debug("Processing work item",{currentTask:t});try{await G(async i=>{i>1&&await this.store.addMetric("retryCount",1),await r()},{maxAttempts:this.deps.config.maxRetries,baseMs:this.deps.config.retryBaseMs,maxMs:this.deps.config.retryMaxMs}),await this.store.markProcessed(e),await this.store.addMetric("processedCount",1),await this.store.updateProcessingLatency(Date.now()-n)}finally{await this.store.releaseLease(e,this.leaseOwner)}}async runWithConcurrency(e,t){let r=[...e],o=Array.from({length:Math.max(1,t)}).map(async()=>{for(;r.length>0;){let n=r.shift();if(!n)return;await n()}});await Promise.all(o)}async sleep(e){await new Promise(t=>setTimeout(t,e))}};var R=class{logger;stopRequested=!1;entries;restartDelayMs;constructor(e,t){this.logger=t?.logger||new w("info",{app:"aipm-supervisor"}),this.restartDelayMs=t?.restartDelayMs??3e3,this.entries=e.map((r,o)=>{let n=r.workerName||`worker-${o+1}`,i=this.logger.child({worker:n});return{name:n,config:r,worker:new _({config:r,logger:i}),runPromise:null,restartCount:0,forceRestart:!1}})}async start(){this.stopRequested=!1,this.logger.info("Starting multi-agent supervisor",{workerCount:this.entries.length}),await Promise.all(this.entries.map(e=>this.runWorker(e)))}async stop(){this.stopRequested=!0,this.logger.info("Stopping multi-agent supervisor"),await Promise.all(this.entries.map(e=>e.worker.stop())),await Promise.all(this.entries.map(async e=>{if(e.runPromise)try{await e.runPromise}catch{}}))}async diagnostics(){let e=await Promise.all(this.entries.map(async t=>({name:t.name,restarts:t.restartCount,...await t.worker.diagnostics()})));return{running:!this.stopRequested,workerCount:this.entries.length,workers:e}}async requestRestart(e){let t=this.entries.find(r=>r.name===e);if(!t)throw new Error(`worker_not_found:${e}`);return t.forceRestart=!0,await t.worker.stop(),{ok:!0,worker:t.name}}listWorkerNames(){return this.entries.map(e=>e.name)}async snapshotForControlPlane(){return await Promise.all(this.entries.map(async t=>({name:t.name,restartCount:t.restartCount,diagnostics:await t.worker.diagnostics()})))}async applyCommand(e){if(e.command_type==="sync_workers")return{ok:!0,action:"sync_workers"};if(e.command_type==="restart_worker"){let t=String(e.payload?.worker_name||"").trim();if(t)return await this.requestRestart(t),{ok:!0,action:"restart_worker",target:t};let r=String(e.target_worker_agent_id||"").trim();if(!r)throw new Error("restart_worker target missing");let n=(await this.snapshotForControlPlane()).find(i=>String(i.diagnostics.agentId||"")===r);if(!n)throw new Error("restart_worker target agent not found");return await this.requestRestart(n.name),{ok:!0,action:"restart_worker",target:n.name}}if(e.command_type==="stop_supervisor")return await this.stop(),{ok:!0,action:"stop_supervisor"};if(e.command_type==="start_supervisor")return{ok:!0,action:"start_supervisor_noop_when_running"};throw new Error(`unsupported command_type: ${e.command_type}`)}async persistedStatus(){let e=await Promise.all(this.entries.map(async t=>{let o=await new k(t.config.stateFilePath).getDiagnostics();return{name:t.name,stateFilePath:t.config.stateFilePath,...o}}));return{ok:!0,workerCount:e.length,workers:e}}async runWorker(e){for(;!this.stopRequested;)try{if(e.runPromise=e.worker.start(),await e.runPromise,e.runPromise=null,this.stopRequested)return;if(e.forceRestart){e.forceRestart=!1,e.worker=new _({config:e.config,logger:this.logger.child({worker:e.name})}),this.logger.info("Worker restart requested",{worker:e.name});continue}throw new Error("worker_exited_unexpectedly")}catch(t){if(e.restartCount+=1,this.logger.error("Worker crashed, scheduling restart",{worker:e.name,restartCount:e.restartCount,error:t instanceof Error?t.message:String(t)}),this.stopRequested)return;await new Promise(r=>setTimeout(r,this.restartDelayMs))}}};var z=require("node:os");async function V(s,e,t){let r=await fetch(s,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${e}`},body:JSON.stringify(t)});if(!r.ok){let o=await r.text();throw new Error(`control_post_failed:${r.status}:${o}`)}return r.json()}async function de(){N(process.env.WORKER_ENV_FILE||".env");let s=j(process.env),e=new w(s[0]?.logLevel||"info",{app:"aipm-supervisor-main"}),t=new R(s,{logger:e}),r=(process.argv[2]||"start").toLowerCase();if(r==="status"){let l=await t.persistedStatus();console.log(JSON.stringify(l,null,2));return}r!=="start"&&(console.error(`Unknown command: ${r}`),process.exit(1));let o=!1,n=async l=>{e.info("Supervisor shutdown signal",{signal:l}),o=!0,await t.stop()};process.on("SIGINT",()=>void n("SIGINT")),process.on("SIGTERM",()=>void n("SIGTERM"));let i=String(process.env.WORKER_RUNTIME_MANAGED||"false").toLowerCase()==="true",a=t.start();if(i){let l=String(process.env.AIPM_BASE_URL||"").replace(/\/+$/,""),d=String(process.env.WORKER_RUNTIME_CONTROL_TOKEN||"").trim(),m=String(process.env.WORKER_RUNTIME_PROJECT_ID||"").trim(),u=String(process.env.WORKER_RUNTIME_NAME||"local-supervisor").trim(),c=String(process.env.WORKER_RUNTIME_HOST_ID||(0,z.hostname)()||"localhost").trim(),g=Math.max(1e3,Number(process.env.WORKER_RUNTIME_HEARTBEAT_MS||5e3));if(!l||!d)throw new Error("Managed mode requires AIPM_BASE_URL and WORKER_RUNTIME_CONTROL_TOKEN");let A=`${l}/worker-runtime-control`;for(;!o;){try{let f=await t.snapshotForControlPlane(),S=m||String(f[0]?.diagnostics?.projectId||"");if(!S)throw new Error("worker_runtime_project_id_missing");let Q=f.map(h=>({worker_name:h.name,agent_id:String(h.diagnostics.agentId||""),state:String(h.diagnostics.workerState||"idle"),queue_depth:Number(h.diagnostics.queueDepth??0),restart_count:Number(h.restartCount||0),metrics:h.diagnostics.metrics||{},last_error:h.diagnostics.lastError||null})),E=await V(A,d,{action:"heartbeat",project_id:S,runtime_name:u,host_id:c,mode:"supervisor",status:"running",workers:Q}),Z=Array.isArray(E.commands)?E.commands:[];for(let h of Z){let C=h,P="done",v={},L=null;try{v=await t.applyCommand(C)}catch(T){P="failed",L=T instanceof Error?T.message:String(T)}await V(A,d,{action:"command_result",project_id:S,command_id:C.id,status:P,result:v,error_message:L})}}catch(f){e.error("Managed heartbeat failed",{error:f instanceof Error?f.message:String(f)})}await new Promise(f=>setTimeout(f,g))}}await a}de().catch(s=>{console.error(JSON.stringify({ts:new Date().toISOString(),level:"error",message:"Supervisor crashed",error:s instanceof Error?s.message:String(s)})),process.exit(1)});
|
|
11
|
+
Summary: ${i.summary}`,task_ref:r.id})).message?.id||null,l||o.warn("Handoff sent but response carried no message id",{assignedBy:r.assigned_by}));let a=i.status==="failed"?"failed":"done",u=i.status==="failed"?"ai_runner_failed":"ai_runner_completed";await t.taskStatus(r.id,a,u),o.info("Task completed via AI runner",{status:a,handoffMessageId:l});return}await t.taskLog(r.id,"Resident worker processed task and persisted execution note.");let n=null;r.assigned_by&&r.assigned_by!==e.agentId&&(n=(await t.sendInboxMessage({to_agent_id:r.assigned_by,subject:`[Handoff] ${r.title}`,message:`Task ${r.id} processed by resident worker.
|
|
12
|
+
Title: ${r.title}
|
|
13
|
+
Status: ready_for_review`,task_ref:r.id})).message?.id||null,n||o.warn("Handoff sent but response carried no message id",{assignedBy:r.assigned_by})),await t.taskStatus(r.id,"done","worker_completed"),o.info("Task completed",{handoffMessageId:n})}function ge(r,e,t,s=.2){let o=Math.min(t,e*Math.pow(2,Math.max(0,r-1))),n=o*s*Math.random();return Math.floor(o+n)}async function V(r,e){let t;for(let s=1;s<=e.maxAttempts;s++)try{return await r(s)}catch(o){if(t=o,s>=e.maxAttempts||e.shouldRetry&&!e.shouldRetry(o,s))break;let n=ge(s,e.baseMs,e.maxMs,e.jitterRatio);await new Promise(i=>setTimeout(i,n))}throw t instanceof Error?t:new Error("retry_failed")}var k=require("node:fs/promises"),A=require("node:path"),z={processed:{},leases:{},checkpoints:{workerState:"idle",lastHeartbeatAt:null,lastError:null,queueDepth:0,metrics:{processedCount:0,failedCount:0,retryCount:0,avgProcessingLatencyMs:0}}};function E(r,e,t){return`${r}:${e}:${t}`}var _=class{constructor(e){this.filePath=e}state=structuredClone(z);loaded=!1;async load(){if(!this.loaded){try{let e=await(0,k.readFile)(this.filePath,"utf8");this.state={...z,...JSON.parse(e)}}catch{await(0,k.mkdir)((0,A.dirname)(this.filePath),{recursive:!0}),await this.flush()}this.loaded=!0,this.cleanupExpiredLeases()}}cleanupExpiredLeases(){let e=Date.now();for(let[t,s]of Object.entries(this.state.leases))s.expiresAt<=e&&delete this.state.leases[t]}async flush(){await(0,k.mkdir)((0,A.dirname)(this.filePath),{recursive:!0}),await(0,k.writeFile)(this.filePath,JSON.stringify(this.state,null,2),"utf8")}async isProcessed(e){return await this.load(),!!this.state.processed[e]}async markProcessed(e){await this.load(),this.state.processed[e]={processedAt:new Date().toISOString()},await this.flush()}async tryAcquireLease(e,t,s){return await this.load(),this.cleanupExpiredLeases(),this.state.leases[e]?!1:(this.state.leases[e]={owner:t,expiresAt:Date.now()+s},await this.flush(),!0)}async releaseLease(e,t){await this.load(),this.state.leases[e]?.owner===t&&(delete this.state.leases[e],await this.flush())}async setWorkerState(e,t){await this.load(),this.state.checkpoints.workerState=e,t!==void 0&&(this.state.checkpoints.lastError=t),await this.flush()}async heartbeat(){await this.load(),this.state.checkpoints.lastHeartbeatAt=new Date().toISOString(),await this.flush()}async setQueueDepth(e){await this.load(),this.state.checkpoints.queueDepth=e,await this.flush()}async addMetric(e,t=1){await this.load(),this.state.checkpoints.metrics[e]+=t,await this.flush()}async updateProcessingLatency(e){await this.load();let t=this.state.checkpoints.metrics,s=Math.max(1,t.processedCount);t.avgProcessingLatencyMs=(t.avgProcessingLatencyMs*(s-1)+e)/s,await this.flush()}async getDiagnostics(){return await this.load(),this.cleanupExpiredLeases(),{...this.state.checkpoints,leaseCount:Object.keys(this.state.leases).length,processedKeys:Object.keys(this.state.processed).length}}};var Q={urgent:4,high:3,medium:2,low:1},pe=3e4,S=class{constructor(e){this.deps=e;this.logger=e.logger||new y(e.config.logLevel,{scope:"resident-worker"}),this.client=e.client||new I(e.config.aipmBaseUrl,e.config.agentApiKey),this.store=e.stateStore||new _(e.config.stateFilePath),this.currentPollMs=e.config.pollIntervalMs}logger;client;store;running=!1;stopRequested=!1;agentId="";projectId="";consecutiveFailures=0;currentPollMs;leaseOwner=`worker-${process.pid}-${Math.random().toString(16).slice(2,8)}`;async start(){if(this.running)return;this.running=!0,this.stopRequested=!1,await this.store.load();let e=await this.client.bootstrap();for(this.agentId=e.agent.id,this.projectId=e.projectId,this.logger.info("Worker bootstrap complete",{agentId:this.agentId,role:e.agent.role}),await this.client.updateStatus({status:"idle",currentTask:"resident_loop_online"}),await this.store.setWorkerState("idle",null);!this.stopRequested;){let t=Date.now();try{await this.store.heartbeat(),await this.tick(),this.consecutiveFailures=0}catch(n){this.consecutiveFailures+=1;let i=n instanceof Error?n.message:"loop_error";if(this.logger.error("Loop iteration failed",{error:i,consecutiveFailures:this.consecutiveFailures}),await this.store.addMetric("failedCount",1),await this.store.setWorkerState("error",i),this.consecutiveFailures>=this.deps.config.circuitFailureThreshold){await this.client.updateStatus({status:"blocked",blockedReason:`Circuit open after ${this.consecutiveFailures} failures`,blockedBy:"resident_worker_loop"}),await this.store.setWorkerState("blocked",i),await this.sleep(this.deps.config.circuitCooldownMs),this.consecutiveFailures=0;continue}}let s=Date.now()-t,o=Math.max(250,this.currentPollMs-s);await this.sleep(o)}await this.client.updateStatus({status:"idle",currentTask:"resident_loop_stopped"}),await this.store.setWorkerState("stopped",null),this.running=!1}async stop(){this.stopRequested=!0}async diagnostics(){let e=await this.store.getDiagnostics();return{agentId:this.agentId||null,projectId:this.projectId||null,running:this.running,stopRequested:this.stopRequested,...e}}async tick(){await this.client.updateStatus({status:"idle",currentTask:"polling_inbox_and_tasks"});let[e,t]=await Promise.all([this.client.listInboxUnread(),this.client.listMyActiveTasks().then(c=>c.tasks||[])]),s=t.filter(c=>c.status==="pending"),o=t.filter(c=>c.status==="in_progress"),n=e.length+t.length;if(this.logger.debug("Fetched work queues",{inboxCount:e.length,pendingTaskCount:s.length,activeTaskCount:o.length,queueDepth:n}),await this.store.setQueueDepth(n),n===0?(this.currentPollMs=Math.min(this.currentPollMs*1.5,pe),this.logger.debug("Queue empty, backing off poll",{nextPollMs:Math.round(this.currentPollMs)})):this.currentPollMs=this.deps.config.pollIntervalMs,n===0)return;let i=[...e].sort((c,d)=>c.type==="urgent"&&d.type!=="urgent"?-1:d.type==="urgent"&&c.type!=="urgent"?1:0),l=[...s,...o].sort((c,d)=>(Q[d.priority]??0)-(Q[c.priority]??0)),a=`inbox:${i.length},tasks:${l.length}`;await this.client.updateStatus({status:"working",currentTask:a}),await this.store.setWorkerState("working",null);let u=this.deps.config.modelApiKey||this.deps.config.modelTokenUrl?{provider:this.deps.config.modelProvider,apiKey:this.deps.config.modelApiKey??"",tokenUrl:this.deps.config.modelTokenUrl,model:this.deps.config.modelName,baseUrl:this.deps.config.modelBaseUrl,maxTokens:this.deps.config.modelMaxTokens,systemPrompt:this.deps.config.modelSystemPrompt}:void 0,g=i.map(c=>()=>this.processInbox(c.id,()=>q(c,{client:this.client,logger:this.logger,agentId:this.agentId}))),p=l.map(c=>()=>this.processTask(c.id,()=>X(c,{client:this.client,logger:this.logger,agentId:this.agentId,aiRunner:u})));await Promise.all([this.runWithConcurrency(g,this.deps.config.inboxConcurrency),this.runWithConcurrency(p,this.deps.config.taskConcurrency)])}async processInbox(e,t){let s=E(this.agentId,e,"inbox");return this.processWorkItem(s,`inbox:${e}`,t)}async processTask(e,t){let s=E(this.agentId,e,"task");return this.processWorkItem(s,`task:${e}`,t)}async processWorkItem(e,t,s){if(await this.store.isProcessed(e)||!await this.store.tryAcquireLease(e,this.leaseOwner,this.deps.config.leaseTtlMs))return;let n=Date.now();this.logger.debug("Processing work item",{currentTask:t});try{await V(async i=>{i>1&&await this.store.addMetric("retryCount",1),await s()},{maxAttempts:this.deps.config.maxRetries,baseMs:this.deps.config.retryBaseMs,maxMs:this.deps.config.retryMaxMs}),await this.store.markProcessed(e),await this.store.addMetric("processedCount",1),await this.store.updateProcessingLatency(Date.now()-n)}finally{await this.store.releaseLease(e,this.leaseOwner)}}async runWithConcurrency(e,t){let s=[...e],o=Array.from({length:Math.max(1,t)}).map(async()=>{for(;s.length>0;){let n=s.shift();if(!n)return;await n()}});await Promise.all(o)}async sleep(e){await new Promise(t=>setTimeout(t,e))}};var T=class{logger;stopRequested=!1;entries;restartDelayMs;constructor(e,t){this.logger=t?.logger||new y("info",{app:"aipm-supervisor"}),this.restartDelayMs=t?.restartDelayMs??3e3,this.entries=e.map((s,o)=>{let n=s.workerName||`worker-${o+1}`,i=this.logger.child({worker:n});return{name:n,config:s,worker:new S({config:s,logger:i}),runPromise:null,restartCount:0,forceRestart:!1}})}async start(){this.stopRequested=!1,this.logger.info("Starting multi-agent supervisor",{workerCount:this.entries.length}),await Promise.all(this.entries.map(e=>this.runWorker(e)))}async stop(){this.stopRequested=!0,this.logger.info("Stopping multi-agent supervisor"),await Promise.all(this.entries.map(e=>e.worker.stop())),await Promise.all(this.entries.map(async e=>{if(e.runPromise)try{await e.runPromise}catch{}}))}async diagnostics(){let e=await Promise.all(this.entries.map(async t=>({name:t.name,restarts:t.restartCount,...await t.worker.diagnostics()})));return{running:!this.stopRequested,workerCount:this.entries.length,workers:e}}async requestRestart(e){let t=this.entries.find(s=>s.name===e);if(!t)throw new Error(`worker_not_found:${e}`);return t.forceRestart=!0,await t.worker.stop(),{ok:!0,worker:t.name}}listWorkerNames(){return this.entries.map(e=>e.name)}async snapshotForControlPlane(){return await Promise.all(this.entries.map(async t=>({name:t.name,restartCount:t.restartCount,diagnostics:await t.worker.diagnostics()})))}async applyCommand(e){if(e.command_type==="sync_workers")return{ok:!0,action:"sync_workers"};if(e.command_type==="restart_worker"){let t=String(e.payload?.worker_name||"").trim();if(t)return await this.requestRestart(t),{ok:!0,action:"restart_worker",target:t};let s=String(e.target_worker_agent_id||"").trim();if(!s)throw new Error("restart_worker target missing");let n=(await this.snapshotForControlPlane()).find(i=>String(i.diagnostics.agentId||"")===s);if(!n)throw new Error("restart_worker target agent not found");return await this.requestRestart(n.name),{ok:!0,action:"restart_worker",target:n.name}}if(e.command_type==="stop_supervisor")return await this.stop(),{ok:!0,action:"stop_supervisor"};if(e.command_type==="start_supervisor")return{ok:!0,action:"start_supervisor_noop_when_running"};throw new Error(`unsupported command_type: ${e.command_type}`)}async persistedStatus(){let e=await Promise.all(this.entries.map(async t=>{let o=await new _(t.config.stateFilePath).getDiagnostics();return{name:t.name,stateFilePath:t.config.stateFilePath,...o}}));return{ok:!0,workerCount:e.length,workers:e}}async runWorker(e){for(;!this.stopRequested;)try{if(e.runPromise=e.worker.start(),await e.runPromise,e.runPromise=null,this.stopRequested)return;if(e.forceRestart){e.forceRestart=!1,e.worker=new S({config:e.config,logger:this.logger.child({worker:e.name})}),this.logger.info("Worker restart requested",{worker:e.name});continue}throw new Error("worker_exited_unexpectedly")}catch(t){if(e.restartCount+=1,this.logger.error("Worker crashed, scheduling restart",{worker:e.name,restartCount:e.restartCount,error:t instanceof Error?t.message:String(t)}),this.stopRequested)return;await new Promise(s=>setTimeout(s,this.restartDelayMs))}}};var ee=require("node:os");async function Z(r,e,t){let s=await fetch(r,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${e}`},body:JSON.stringify(t)});if(!s.ok){let o=await s.text();throw new Error(`control_post_failed:${s.status}:${o}`)}return s.json()}async function me(){D(process.env.WORKER_ENV_FILE||".env");let r=U(process.env),e=new y(r[0]?.logLevel||"info",{app:"aipm-supervisor-main"}),t=new T(r,{logger:e}),s=(process.argv[2]||"start").toLowerCase();if(s==="status"){let a=await t.persistedStatus();console.log(JSON.stringify(a,null,2));return}s!=="start"&&(console.error(`Unknown command: ${s}`),process.exit(1));let o=!1,n=async a=>{e.info("Supervisor shutdown signal",{signal:a}),o=!0,await t.stop()};process.on("SIGINT",()=>void n("SIGINT")),process.on("SIGTERM",()=>void n("SIGTERM"));let i=String(process.env.WORKER_RUNTIME_MANAGED||"false").toLowerCase()==="true",l=t.start();if(i){let a=String(process.env.AIPM_BASE_URL||"").replace(/\/+$/,""),u=String(process.env.WORKER_RUNTIME_CONTROL_TOKEN||"").trim(),g=String(process.env.WORKER_RUNTIME_PROJECT_ID||"").trim(),p=String(process.env.WORKER_RUNTIME_NAME||"local-supervisor").trim(),c=String(process.env.WORKER_RUNTIME_HOST_ID||(0,ee.hostname)()||"localhost").trim(),d=Math.max(1e3,Number(process.env.WORKER_RUNTIME_HEARTBEAT_MS||5e3));if(!a||!u)throw new Error("Managed mode requires AIPM_BASE_URL and WORKER_RUNTIME_CONTROL_TOKEN");let f=`${a}/worker-runtime-control`;for(;!o;){try{let w=await t.snapshotForControlPlane(),M=g||String(w[0]?.diagnostics?.projectId||"");if(!M)throw new Error("worker_runtime_project_id_missing");let te=w.map(h=>({worker_name:h.name,agent_id:String(h.diagnostics.agentId||""),state:String(h.diagnostics.workerState||"idle"),queue_depth:Number(h.diagnostics.queueDepth??0),restart_count:Number(h.restartCount||0),metrics:h.diagnostics.metrics||{},last_error:h.diagnostics.lastError||null})),C=await Z(f,u,{action:"heartbeat",project_id:M,runtime_name:p,host_id:c,mode:"supervisor",status:"running",workers:te}),re=Array.isArray(C.commands)?C.commands:[];for(let h of re){let P=h,v="done",L={},O=null;try{L=await t.applyCommand(P)}catch(x){v="failed",O=x instanceof Error?x.message:String(x)}await Z(f,u,{action:"command_result",project_id:M,command_id:P.id,status:v,result:L,error_message:O})}}catch(w){e.error("Managed heartbeat failed",{error:w instanceof Error?w.message:String(w)})}await new Promise(w=>setTimeout(w,d))}}await l}me().catch(r=>{console.error(JSON.stringify({ts:new Date().toISOString(),level:"error",message:"Supervisor crashed",error:r instanceof Error?r.message:String(r)})),process.exit(1)});
|
package/dist/worker.cjs
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
var S=require("node:fs"),
|
|
2
|
+
var S=require("node:fs"),I=require("node:path");function u(t,e){let s=Number(t);return!Number.isFinite(s)||s<=0?e:Math.floor(s)}function K(t){let e=t.trim().replace(/\/+$/,"");if(!e)throw new Error("AIPM_BASE_URL is required");return e}function M(t=process.env){let e=K(t.AIPM_BASE_URL||""),s=String(t.AGENT_API_KEY||t.AIPM_API_KEY||"").trim();if(!s)throw new Error("AGENT_API_KEY (or AIPM_API_KEY) is required");let r=(0,I.resolve)(t.STATE_FILE_PATH||`.worker-state/${s.slice(0,12)}.json`),i=(0,I.resolve)(r,"..");(0,S.existsSync)(i);let n=String(t.LOG_LEVEL||"info").toLowerCase(),o=["debug","info","warn","error"].includes(n)?n:"info",l=u(t.MAX_CONCURRENCY,2),c=String(t.MODEL_PROVIDER||"anthropic").toLowerCase();return{aipmBaseUrl:e,agentApiKey:s,pollIntervalMs:u(t.POLL_INTERVAL_MS,5e3),maxConcurrency:l,inboxConcurrency:u(t.INBOX_CONCURRENCY,1),taskConcurrency:u(t.TASK_CONCURRENCY,l),logLevel:o,leaseTtlMs:u(t.LEASE_TTL_MS,6e4),stateFilePath:r,heartbeatIntervalMs:u(t.HEARTBEAT_INTERVAL_MS,15e3),maxRetries:u(t.MAX_RETRIES,4),retryBaseMs:u(t.RETRY_BASE_MS,500),retryMaxMs:u(t.RETRY_MAX_MS,8e3),circuitFailureThreshold:u(t.CIRCUIT_FAILURE_THRESHOLD,5),circuitCooldownMs:u(t.CIRCUIT_COOLDOWN_MS,3e4),modelProvider:c==="openai"?"openai":"anthropic",modelApiKey:String(t.MODEL_API_KEY||"").trim()||null,modelTokenUrl:String(t.MODEL_TOKEN_URL||"").trim()||null,modelName:String(t.MODEL_NAME||"claude-sonnet-4-6").trim(),modelBaseUrl:String(t.MODEL_BASE_URL||"").trim()||null,modelMaxTokens:u(t.MODEL_MAX_TOKENS,4096),modelSystemPrompt:t.MODEL_SYSTEM_PROMPT?String(t.MODEL_SYSTEM_PROMPT):null}}var A=require("node:fs"),R=require("node:path");function C(t=".env",e=process.env){try{let s=(0,A.readFileSync)((0,R.resolve)(t),"utf8");for(let r of s.split(/\r?\n/)){let i=r.trim();if(!i||i.startsWith("#"))continue;let n=i.indexOf("=");if(n<=0)continue;let o=i.slice(0,n).trim(),l=i.slice(n+1).trim();e[o]||(e[o]=l)}}catch{}}var w=class{constructor(e,s){this.baseUrl=e;this.apiKey=s}async call(e,s){let r=await fetch(`${this.baseUrl}${e}`,{...s,headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.apiKey}`,...s?.headers||{}}});if(!r.ok){let i="";try{let n=await r.json();i=n?.error||n?.code||""}catch{}throw new Error(`AIPM ${e} -> ${r.status}${i?` (${i})`:""}`)}return await r.json()}bootstrap(){return this.call("/agent-bootstrap",{method:"GET"})}updateStatus(e){return this.call("/api-agent-status",{method:"POST",body:JSON.stringify(e)})}async listInboxUnread(){return(await this.call("/agent-inbox?unread=true",{method:"GET"})).inbox||[]}markInboxRead(e){return this.call(`/agent-inbox/${e}/read`,{method:"POST",body:JSON.stringify({})})}sendInboxMessage(e){return this.call("/agent-inbox",{method:"POST",body:JSON.stringify(e)})}listMyTasks(e){let s=e?`?status=${encodeURIComponent(e)}`:"";return this.call(`/agent-tasks${s}`,{method:"GET"})}async listMyActiveTasks(){let[e,s]=await Promise.all([this.listMyTasks("pending"),this.listMyTasks("in_progress")]);return{tasks:[...e.tasks||[],...s.tasks||[]]}}createTask(e){return this.call("/agent-tasks",{method:"POST",body:JSON.stringify(e)})}getTask(e){return this.call(`/agent-task-state/task/${e}`,{method:"GET"})}taskStatus(e,s,r){return this.call("/agent-task-state/status",{method:"POST",body:JSON.stringify({task_id:e,status:s,reason:r})})}taskLog(e,s,r="info"){return this.call("/agent-task-state/log",{method:"POST",body:JSON.stringify({task_id:e,message:s,level:r})})}};function q(t,e=140){let s=t.replace(/\s+/g," ").trim();return s.length<=e?s:`${s.slice(0,e-1)}...`}function H(t){return(t||"").trim().toLowerCase().startsWith("[ack]")||(t||"").trim().toLowerCase().startsWith("ack:")}function E(t){return t.match(/agent_id\s*:\s*([0-9a-fA-F-]{36})/)?.[1]||null}function Y(t){let e=(t.subject||"").toLowerCase(),s=(t.body||"").toLowerCase();return t.taskRef||E(t.body)?!1:e.includes("live")||e.includes("check")||e.includes("ping")||s.includes("are you live")||s.includes("quick live check")||s.includes("reply with")}async function L(t,e){let{client:s,logger:r,agentId:i}=e,n=r.child({inboxMessageId:t.id});if(t.type==="urgent"&&n.info("Urgent inbox item observed"),t.from_agent_id===i||H(t.subject)){await s.markInboxRead(t.id),n.debug("Ignored self/ack inbox message");return}let o=E(t.body||"");if(!t.taskRef&&o&&t.from_agent_id){let c=await s.sendInboxMessage({to_agent_id:o,subject:`Hello from ${i}`,message:`Hi \u2014 quick live check from peer worker ${i}. Are you live?`});await s.sendInboxMessage({to_agent_id:t.from_agent_id,subject:"Peer message sent",message:`Executed peer message command. target_agent_id=${o}, outbound_message_id=${c.message?.id??"unknown"}`}),await s.markInboxRead(t.id),n.info("Peer message command executed",{peerAgentId:o,outboundMessageId:c.message?.id});return}if(Y(t)&&t.from_agent_id){await s.sendInboxMessage({to_agent_id:t.from_agent_id,subject:`Ack: ${t.subject||"message received"}`,message:`Hi from worker ${i}. I'm live at ${new Date().toISOString()}.`,task_ref:t.taskRef||void 0}),await s.markInboxRead(t.id),n.info("Inbox ping acknowledged without task creation");return}let l=t.taskRef||null;if(!l){let c=t.subject?.trim()?`Inbox follow-up: ${t.subject}`:`Inbox follow-up: ${q(t.body,64)}`;l=(await s.createTask({title:c,description:t.body,assigned_to:i,priority:"medium"})).task_id,n.info("Created task from inbox",{taskId:l})}t.from_agent_id&&await s.sendInboxMessage({to_agent_id:t.from_agent_id,subject:`[Ack] ${t.subject||"Message received"}`,message:`Received message ${t.id}. Linked task: ${l||"none"}.`,task_ref:l||void 0}),await s.markInboxRead(t.id),n.info("Inbox message processed",{taskId:l})}var k=null;async function P(t){if(!t.tokenUrl)return t.apiKey;let e=Date.now();if(k&&k.expiresAt>e+6e4)return k.token;let s=await fetch(t.tokenUrl,{method:"GET"});if(!s.ok){let i=await s.text();throw new Error(`Token provider returned ${s.status}: ${i}`)}let r=await s.json();if(!r.token)throw new Error("Token provider response missing 'token' field");return k={token:r.token,expiresAt:e+(r.expires_in??3600)*1e3},k.token}var v=20,O="You are an autonomous AI agent. Complete the assigned task using the available tools. When done, call complete_task with a summary. If you cannot complete it, call fail_task with the reason.",$=[{name:"log_progress",description:"Log a progress note to the task. Use for intermediate updates.",parameters:{type:"object",properties:{message:{type:"string",description:"Progress message"}},required:["message"]}},{name:"complete_task",description:"Mark the task as complete. Call this when work is done.",parameters:{type:"object",properties:{summary:{type:"string",description:"Summary of what was accomplished"}},required:["summary"]}},{name:"fail_task",description:"Mark the task as failed. Call this if you cannot complete it.",parameters:{type:"object",properties:{reason:{type:"string",description:"Reason for failure"}},required:["reason"]}},{name:"create_subtask",description:"Create a subtask to delegate work to another agent.",parameters:{type:"object",properties:{title:{type:"string",description:"Subtask title"},description:{type:"string",description:"Subtask description"},assigned_to:{type:"string",description:"Agent ID or name (optional)"},priority:{type:"string",enum:["low","medium","high","urgent"]}},required:["title","description"]}},{name:"send_message",description:"Send an inbox message to another agent.",parameters:{type:"object",properties:{to_agent_id:{type:"string",description:"Target agent ID"},subject:{type:"string",description:"Message subject"},message:{type:"string",description:"Message body"}},required:["to_agent_id","subject","message"]}}],G=$.map(t=>({name:t.name,description:t.description,input_schema:t.parameters})),J=$.map(t=>({type:"function",function:{name:t.name,description:t.description,parameters:t.parameters}}));async function N(t,e,s){let{client:r,taskId:i}=s;switch(t){case"log_progress":return await r.taskLog(i,String(e.message||""),"info"),{result:"Logged.",done:!1,failed:!1,summary:""};case"complete_task":{let n=String(e.summary||"Task completed.");return{result:n,done:!0,failed:!1,summary:n}}case"fail_task":{let n=String(e.reason||"Unknown failure.");return{result:n,done:!0,failed:!0,summary:n}}case"create_subtask":return{result:`Subtask created: ${(await r.createTask({title:String(e.title),description:String(e.description||""),assigned_to:e.assigned_to?String(e.assigned_to):void 0,priority:e.priority?String(e.priority):"medium"})).task_id}`,done:!1,failed:!1,summary:""};case"send_message":return{result:`Message sent: ${(await r.sendInboxMessage({to_agent_id:String(e.to_agent_id),subject:String(e.subject||""),message:String(e.message||""),task_ref:i})).message?.id??"unknown"}`,done:!1,failed:!1,summary:""};default:return{result:`Unknown tool: ${t}`,done:!1,failed:!1,summary:""}}}async function X(t,e,s){let r=await P(e),i=(e.baseUrl||"https://api.anthropic.com").replace(/\/+$/,""),n=e.systemPrompt||O,o=[{role:"user",content:`Task: ${t.title}
|
|
3
3
|
|
|
4
4
|
Description:
|
|
5
|
-
${t.description||"(no description provided)"}`}];for(let
|
|
5
|
+
${t.description||"(no description provided)"}`}];for(let l=0;l<v;l++){let c=await fetch(`${i}/v1/messages`,{method:"POST",headers:{"x-api-key":r,"anthropic-version":"2023-06-01","content-type":"application/json"},body:JSON.stringify({model:e.model,max_tokens:e.maxTokens,system:n,messages:o,tools:G})});if(!c.ok){let a=await c.text();throw new Error(`Anthropic API ${c.status}: ${a}`)}let p=await c.json();o.push({role:"assistant",content:p.content});let g=p.content.filter(a=>a.type==="tool_use");if(g.length===0)return{summary:p.content.find(d=>d.type==="text")?.text||"Task processed.",status:"done"};let m=[];for(let a of g){let d=await N(a.name,a.input,s);if(m.push({type:"tool_result",tool_use_id:a.id,content:d.result}),d.done)return{summary:d.summary||d.result,status:d.failed?"failed":"done"}}o.push({role:"user",content:m})}return{summary:"Max iterations reached.",status:"done"}}async function V(t,e,s){let r=await P(e),i=(e.baseUrl||"https://api.openai.com").replace(/\/+$/,""),o=[{role:"system",content:e.systemPrompt||O},{role:"user",content:`Task: ${t.title}
|
|
6
6
|
|
|
7
7
|
Description:
|
|
8
|
-
${t.description||"(no description provided)"}`}];for(let
|
|
8
|
+
${t.description||"(no description provided)"}`}];for(let l=0;l<v;l++){let c=await fetch(`${i}/v1/chat/completions`,{method:"POST",headers:{Authorization:`Bearer ${r}`,"content-type":"application/json"},body:JSON.stringify({model:e.model,max_tokens:e.maxTokens,messages:o,tools:J,tool_choice:"auto"})});if(!c.ok){let a=await c.text();throw new Error(`OpenAI API ${c.status}: ${a}`)}let g=(await c.json()).choices[0];if(!g)throw new Error("Empty choices in model response");o.push(g.message);let m=g.message.tool_calls;if(!m||m.length===0)return{summary:g.message.content||"Task processed.",status:"done"};for(let a of m){let d={};try{d=JSON.parse(a.function.arguments)}catch{}let y=await N(a.function.name,d,s);if(o.push({role:"tool",tool_call_id:a.id,name:a.function.name,content:y.result}),y.done)return{summary:y.summary||y.result,status:y.failed?"failed":"done"}}}return{summary:"Max iterations reached.",status:"done"}}async function j(t,e,s){return e.provider==="anthropic"?X(t,e,s):V(t,e,s)}async function D(t,e){let{client:s,logger:r}=e,i=r.child({taskId:t.id});if(t.status==="done"||t.status==="cancelled"){i.debug("Skipping terminal task");return}if(t.status!=="in_progress"&&await s.taskStatus(t.id,"in_progress","worker_claimed_task"),e.aiRunner){i.info("AI runner invoked",{provider:e.aiRunner.provider,model:e.aiRunner.model});let o=await j(t,e.aiRunner,{client:s,taskId:t.id,logger:i,agentId:e.agentId});await s.taskLog(t.id,`AI execution complete: ${o.summary}`,o.status==="failed"?"warn":"info");let l=null;t.assigned_by&&t.assigned_by!==e.agentId&&(l=(await s.sendInboxMessage({to_agent_id:t.assigned_by,subject:`[Handoff] ${t.title}`,message:`Task ${t.id} processed.
|
|
9
9
|
Title: ${t.title}
|
|
10
10
|
Status: ${o.status}
|
|
11
|
-
Summary: ${o.summary}`,task_ref:t.id})).message?.id||null,
|
|
11
|
+
Summary: ${o.summary}`,task_ref:t.id})).message?.id||null,l||i.warn("Handoff sent but response carried no message id",{assignedBy:t.assigned_by}));let c=o.status==="failed"?"failed":"done",p=o.status==="failed"?"ai_runner_failed":"ai_runner_completed";await s.taskStatus(t.id,c,p),i.info("Task completed via AI runner",{status:c,handoffMessageId:l});return}await s.taskLog(t.id,"Resident worker processed task and persisted execution note.");let n=null;t.assigned_by&&t.assigned_by!==e.agentId&&(n=(await s.sendInboxMessage({to_agent_id:t.assigned_by,subject:`[Handoff] ${t.title}`,message:`Task ${t.id} processed by resident worker.
|
|
12
12
|
Title: ${t.title}
|
|
13
|
-
Status: ready_for_review`,task_ref:t.id})).message?.id||null,n||i.warn("Handoff sent but response carried no message id",{assignedBy:t.assigned_by})),await s.taskStatus(t.id,"done","worker_completed"),i.info("Task completed",{handoffMessageId:n})}var
|
|
13
|
+
Status: ready_for_review`,task_ref:t.id})).message?.id||null,n||i.warn("Handoff sent but response carried no message id",{assignedBy:t.assigned_by})),await s.taskStatus(t.id,"done","worker_completed"),i.info("Task completed",{handoffMessageId:n})}var U={debug:10,info:20,warn:30,error:40},f=class t{constructor(e,s={}){this.minLevel=e;this.ctx=s}child(e){return new t(this.minLevel,{...this.ctx,...e})}debug(e,s){this.log("debug",e,s)}info(e,s){this.log("info",e,s)}warn(e,s){this.log("warn",e,s)}error(e,s){this.log("error",e,s)}log(e,s,r){if(U[e]<U[this.minLevel])return;let i={ts:new Date().toISOString(),level:e,message:s,...this.ctx,...r||{}},n=JSON.stringify(i);e==="error"?console.error(n):e==="warn"?console.warn(n):console.log(n)}};function z(t,e,s,r=.2){let i=Math.min(s,e*Math.pow(2,Math.max(0,t-1))),n=i*r*Math.random();return Math.floor(i+n)}async function F(t,e){let s;for(let r=1;r<=e.maxAttempts;r++)try{return await t(r)}catch(i){if(s=i,r>=e.maxAttempts||e.shouldRetry&&!e.shouldRetry(i,r))break;let n=z(r,e.baseMs,e.maxMs,e.jitterRatio);await new Promise(o=>setTimeout(o,n))}throw s instanceof Error?s:new Error("retry_failed")}var h=require("node:fs/promises"),x=require("node:path"),B={processed:{},leases:{},checkpoints:{workerState:"idle",lastHeartbeatAt:null,lastError:null,queueDepth:0,metrics:{processedCount:0,failedCount:0,retryCount:0,avgProcessingLatencyMs:0}}};function T(t,e,s){return`${t}:${e}:${s}`}var b=class{constructor(e){this.filePath=e}state=structuredClone(B);loaded=!1;async load(){if(!this.loaded){try{let e=await(0,h.readFile)(this.filePath,"utf8");this.state={...B,...JSON.parse(e)}}catch{await(0,h.mkdir)((0,x.dirname)(this.filePath),{recursive:!0}),await this.flush()}this.loaded=!0,this.cleanupExpiredLeases()}}cleanupExpiredLeases(){let e=Date.now();for(let[s,r]of Object.entries(this.state.leases))r.expiresAt<=e&&delete this.state.leases[s]}async flush(){await(0,h.mkdir)((0,x.dirname)(this.filePath),{recursive:!0}),await(0,h.writeFile)(this.filePath,JSON.stringify(this.state,null,2),"utf8")}async isProcessed(e){return await this.load(),!!this.state.processed[e]}async markProcessed(e){await this.load(),this.state.processed[e]={processedAt:new Date().toISOString()},await this.flush()}async tryAcquireLease(e,s,r){return await this.load(),this.cleanupExpiredLeases(),this.state.leases[e]?!1:(this.state.leases[e]={owner:s,expiresAt:Date.now()+r},await this.flush(),!0)}async releaseLease(e,s){await this.load(),this.state.leases[e]?.owner===s&&(delete this.state.leases[e],await this.flush())}async setWorkerState(e,s){await this.load(),this.state.checkpoints.workerState=e,s!==void 0&&(this.state.checkpoints.lastError=s),await this.flush()}async heartbeat(){await this.load(),this.state.checkpoints.lastHeartbeatAt=new Date().toISOString(),await this.flush()}async setQueueDepth(e){await this.load(),this.state.checkpoints.queueDepth=e,await this.flush()}async addMetric(e,s=1){await this.load(),this.state.checkpoints.metrics[e]+=s,await this.flush()}async updateProcessingLatency(e){await this.load();let s=this.state.checkpoints.metrics,r=Math.max(1,s.processedCount);s.avgProcessingLatencyMs=(s.avgProcessingLatencyMs*(r-1)+e)/r,await this.flush()}async getDiagnostics(){return await this.load(),this.cleanupExpiredLeases(),{...this.state.checkpoints,leaseCount:Object.keys(this.state.leases).length,processedKeys:Object.keys(this.state.processed).length}}};var W={urgent:4,high:3,medium:2,low:1},Q=3e4,_=class{constructor(e){this.deps=e;this.logger=e.logger||new f(e.config.logLevel,{scope:"resident-worker"}),this.client=e.client||new w(e.config.aipmBaseUrl,e.config.agentApiKey),this.store=e.stateStore||new b(e.config.stateFilePath),this.currentPollMs=e.config.pollIntervalMs}logger;client;store;running=!1;stopRequested=!1;agentId="";projectId="";consecutiveFailures=0;currentPollMs;leaseOwner=`worker-${process.pid}-${Math.random().toString(16).slice(2,8)}`;async start(){if(this.running)return;this.running=!0,this.stopRequested=!1,await this.store.load();let e=await this.client.bootstrap();for(this.agentId=e.agent.id,this.projectId=e.projectId,this.logger.info("Worker bootstrap complete",{agentId:this.agentId,role:e.agent.role}),await this.client.updateStatus({status:"idle",currentTask:"resident_loop_online"}),await this.store.setWorkerState("idle",null);!this.stopRequested;){let s=Date.now();try{await this.store.heartbeat(),await this.tick(),this.consecutiveFailures=0}catch(n){this.consecutiveFailures+=1;let o=n instanceof Error?n.message:"loop_error";if(this.logger.error("Loop iteration failed",{error:o,consecutiveFailures:this.consecutiveFailures}),await this.store.addMetric("failedCount",1),await this.store.setWorkerState("error",o),this.consecutiveFailures>=this.deps.config.circuitFailureThreshold){await this.client.updateStatus({status:"blocked",blockedReason:`Circuit open after ${this.consecutiveFailures} failures`,blockedBy:"resident_worker_loop"}),await this.store.setWorkerState("blocked",o),await this.sleep(this.deps.config.circuitCooldownMs),this.consecutiveFailures=0;continue}}let r=Date.now()-s,i=Math.max(250,this.currentPollMs-r);await this.sleep(i)}await this.client.updateStatus({status:"idle",currentTask:"resident_loop_stopped"}),await this.store.setWorkerState("stopped",null),this.running=!1}async stop(){this.stopRequested=!0}async diagnostics(){let e=await this.store.getDiagnostics();return{agentId:this.agentId||null,projectId:this.projectId||null,running:this.running,stopRequested:this.stopRequested,...e}}async tick(){await this.client.updateStatus({status:"idle",currentTask:"polling_inbox_and_tasks"});let[e,s]=await Promise.all([this.client.listInboxUnread(),this.client.listMyActiveTasks().then(a=>a.tasks||[])]),r=s.filter(a=>a.status==="pending"),i=s.filter(a=>a.status==="in_progress"),n=e.length+s.length;if(this.logger.debug("Fetched work queues",{inboxCount:e.length,pendingTaskCount:r.length,activeTaskCount:i.length,queueDepth:n}),await this.store.setQueueDepth(n),n===0?(this.currentPollMs=Math.min(this.currentPollMs*1.5,Q),this.logger.debug("Queue empty, backing off poll",{nextPollMs:Math.round(this.currentPollMs)})):this.currentPollMs=this.deps.config.pollIntervalMs,n===0)return;let o=[...e].sort((a,d)=>a.type==="urgent"&&d.type!=="urgent"?-1:d.type==="urgent"&&a.type!=="urgent"?1:0),l=[...r,...i].sort((a,d)=>(W[d.priority]??0)-(W[a.priority]??0)),c=`inbox:${o.length},tasks:${l.length}`;await this.client.updateStatus({status:"working",currentTask:c}),await this.store.setWorkerState("working",null);let p=this.deps.config.modelApiKey||this.deps.config.modelTokenUrl?{provider:this.deps.config.modelProvider,apiKey:this.deps.config.modelApiKey??"",tokenUrl:this.deps.config.modelTokenUrl,model:this.deps.config.modelName,baseUrl:this.deps.config.modelBaseUrl,maxTokens:this.deps.config.modelMaxTokens,systemPrompt:this.deps.config.modelSystemPrompt}:void 0,g=o.map(a=>()=>this.processInbox(a.id,()=>L(a,{client:this.client,logger:this.logger,agentId:this.agentId}))),m=l.map(a=>()=>this.processTask(a.id,()=>D(a,{client:this.client,logger:this.logger,agentId:this.agentId,aiRunner:p})));await Promise.all([this.runWithConcurrency(g,this.deps.config.inboxConcurrency),this.runWithConcurrency(m,this.deps.config.taskConcurrency)])}async processInbox(e,s){let r=T(this.agentId,e,"inbox");return this.processWorkItem(r,`inbox:${e}`,s)}async processTask(e,s){let r=T(this.agentId,e,"task");return this.processWorkItem(r,`task:${e}`,s)}async processWorkItem(e,s,r){if(await this.store.isProcessed(e)||!await this.store.tryAcquireLease(e,this.leaseOwner,this.deps.config.leaseTtlMs))return;let n=Date.now();this.logger.debug("Processing work item",{currentTask:s});try{await F(async o=>{o>1&&await this.store.addMetric("retryCount",1),await r()},{maxAttempts:this.deps.config.maxRetries,baseMs:this.deps.config.retryBaseMs,maxMs:this.deps.config.retryMaxMs}),await this.store.markProcessed(e),await this.store.addMetric("processedCount",1),await this.store.updateProcessingLatency(Date.now()-n)}finally{await this.store.releaseLease(e,this.leaseOwner)}}async runWithConcurrency(e,s){let r=[...e],i=Array.from({length:Math.max(1,s)}).map(async()=>{for(;r.length>0;){let n=r.shift();if(!n)return;await n()}});await Promise.all(i)}async sleep(e){await new Promise(s=>setTimeout(s,e))}};async function Z(){C(process.env.WORKER_ENV_FILE||".env");let t=M(process.env),e=new f(t.logLevel,{app:"aipm-worker"}),s=new _({config:t,logger:e}),r=(process.argv[2]||"start").toLowerCase();if(r==="status"){let n=await s.diagnostics();console.log(JSON.stringify({ok:!0,diagnostics:n},null,2)),process.exit(0)}r!=="start"&&(console.error(`Unknown command: ${r}`),process.exit(1));let i=async n=>{e.info("Shutdown signal received",{signal:n}),await s.stop()};process.on("SIGINT",()=>void i("SIGINT")),process.on("SIGTERM",()=>void i("SIGTERM")),e.info("Starting resident worker"),await s.start(),e.info("Resident worker exited")}Z().catch(t=>{console.error(JSON.stringify({ts:new Date().toISOString(),level:"error",message:"Worker crashed",error:t instanceof Error?t.message:String(t)})),process.exit(1)});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "aipm-worker-supervisor",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "AIPM always-on worker and supervisor runtime",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"engines": {
|
|
@@ -8,7 +8,9 @@
|
|
|
8
8
|
},
|
|
9
9
|
"bin": {
|
|
10
10
|
"aipm-worker": "./dist/worker.cjs",
|
|
11
|
-
"aipm-supervisor": "./dist/supervisor.cjs"
|
|
11
|
+
"aipm-supervisor": "./dist/supervisor.cjs",
|
|
12
|
+
"aipm-openclaw-runtime": "./dist/openclaw.cjs",
|
|
13
|
+
"aipm-openclaw-bridge": "./dist/openclaw-bridge.cjs"
|
|
12
14
|
},
|
|
13
15
|
"files": [
|
|
14
16
|
"dist/"
|