@lmctl-ai/lmctl 0.1.226 → 0.1.227
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/cli/index.js +1 -1
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -2528,7 +2528,7 @@ The session marker may be stale or the team may not be registered in this lmctl
|
|
|
2528
2528
|
Consulted ${hs(ne())}
|
|
2529
2529
|
To experiment with it manually: https://lmctl.com/lmctl/docs/manual-invocation`}function gS(e){return`unknown option --from
|
|
2530
2530
|
${tm(e)}`}function yS(e,t){let r=cn(e,t);return r.ok?{ok:!0,identity:r.identity,source:"session"}:r}function cn(e,t){let r=e[Rs];if(r===void 0||r.length===0)return{ok:!1,reason:"unset"};let n=[];for(let i of t.listRootTeams()){let o;try{o=Ve(i.teamfile_path,t)}catch{continue}for(let a of o.members)a.teamfile===void 0&&a.session===r&&n.push(`${i.teamfile_path}:${a.alias}`)}let s=[...new Set(n)];if(s.length===0)return{ok:!1,reason:"not-found"};if(s.length>1)throw new Error("LMCTL_SELF_SESSIONID matches multiple team members; the team registry is ambiguous");return{ok:!0,identity:s[0]}}var gi=w(()=>{"use strict";yt();ze();fr()});function De(e){let t=ln(e);if(t===null)throw new Error(`invalid agent identity: ${e}`);return{teamfile:t.teamfile,alias:t.alias,value:e}}var hl=w(()=>{"use strict";at()});async function El(e){let t=e.onEvent??(()=>{}),r=bq(e.io,t),n=null,s=null,i=null,o,a,l=null,d=!1,c=null,u=e.prompt;try{let f=await O(r,R=>Ne(e.teamfile,e.alias,R)),m=f.teamfile,p=await O(r,R=>Lq(R,e.env??process.env)),h=p.sender,_=await Oq(r,{explicitRoot:e.rootFlag,sender:h,receiverTeamfile:m,operatorOrigin:p.origin==="operator"}),g=h===null?null:await Cq(r,{rootTeamfile:_,sender:h,receiverTeamfile:m,receiverAlias:f.alias});g!==null&&!g.allowed&&t({kind:"warning",message:`warning: out-of-policy chat send: ${g.reason}
|
|
2531
|
-
`}),i=await Iq(r,{receiverTeamfile:m,receiverAlias:f.alias,sender:h,rootTeamfile:_,provider:f.provider,model:f.model,sessionId:f.sessionid,cwd:f.sessiondir,prompt:e.prompt,origin:p.origin}),o=xq(r,i);try{as(r,ot(m),m)}catch{}n={root_teamfile:_,sender_teamfile:h?.teamfile??null,sender_alias:h?.alias??null,receiver_teamfile:m,receiver_alias:f.alias,receiver_provider:f.provider,receiver_session_id:f.sessionid,origin:p.origin,policy:g};let y=await bS(r,m,f.alias);if(y!==null){let R=Qf(y);if(h!==null){let A=await rm(r,{sender:h,receiverTeamfile:m,receiverAlias:f.alias,receiverProvider:f.provider,receiverSessionId:f.sessionid,rootTeamfile:_,payload:e.prompt});return c=gl(`enqueued mailbox message ${A.id}`,null),nm(A.id,h,m,f.alias)}return ds(i,n,e.prompt,R,"unavailable"),c=sm(R,null),{status:"busy",message:R,teamChatLogId:null}}let E=await Sq(r,m,f.alias);if(E.busy){if(h!==null){let A=await rm(r,{sender:h,receiverTeamfile:m,receiverAlias:f.alias,receiverProvider:f.provider,receiverSessionId:f.sessionid,rootTeamfile:_,payload:e.prompt});return c=gl(`enqueued mailbox message ${A.id}`,null),nm(A.id,h,m,f.alias)}let R=await kq(r,m,f.alias);return ds(i,n,e.prompt,R,"unavailable"),c=sm(R,null),{status:"busy",message:R,teamChatLogId:null}}if(h!==null&&h.teamfile!==m){let R=await O(r,A=>dv(A,{root:_,sender:h,receiverTeamfile:m,receiverAlias:f.alias,now:new Date}));if(R.stop){let A=uv(R.chain);return ds(i,n,e.prompt,A,"cycle"),c=yl(A,null),{status:"error",errorKind:"cycle",message:A,teamChatLogId:null,messageId:null}}}let v=await Dq(r,{receiver_teamfile:m,receiver_alias:f.alias,sender_teamfile:h?.teamfile??m,sender_alias:h?.alias??"operator",...E.idleReclaim?{force:!0,reclaimReason:"session-idle"}:{}});if(!v.acquired){let R=SS(m,f.alias,v.current);if(h!==null){let A=await rm(r,{sender:h,receiverTeamfile:m,receiverAlias:f.alias,receiverProvider:f.provider,receiverSessionId:f.sessionid,rootTeamfile:_,payload:e.prompt});return c=gl(`enqueued mailbox message ${A.id}`,null),nm(A.id,h,m,f.alias)}return ds(i,n,e.prompt,R,"unavailable"),c=sm(R,null),{status:"busy",message:R,teamChatLogId:null}}s={row:v.lease,leaseId:v.leaseId},a=Er(R=>O(r,A=>A.heartbeatAgentInflight(s.row,s.leaseId,R)),{intervalMs:vS,onError:R=>{ue()&&Pe(JSON.stringify({event:"agent_inflight_heartbeat_error",receiver:{teamfile:s.row.receiver_teamfile,alias:s.row.receiver_alias},error:R instanceof Error?R.message:String(R)}))}});let S=await es({sender:h??{teamfile:m,alias:"operator"},receiver:{teamfile:m,alias:f.alias,provider:f.provider,sessionId:f.sessionid,cwd:f.sessiondir,...f.model!==void 0?{model:f.model}:{},...f.effort!==void 0?{effort:f.effort}:{},...f.isLead!==void 0?{isLead:f.isLead}:{}},newMessage:e.prompt,newMessageFrom:h===null?"operator":`${h.teamfile}:${h.alias}`,io:r,permissionMode:e.permissionMode,env:e.env??process.env,...e.idleTimeoutMs!==void 0?{idleTimeoutMs:e.idleTimeoutMs}:{},onDelta:R=>t({kind:"delta",chunk:R}),onProgress:R=>t({kind:"progress",alias:f.alias,event:R}),onPromptBuilt:async R=>{u=R,l===null&&(l=await Pq(r,{...n,message:R}),l!==null&&e.onAccepted?.(Gr(l))),i!==null&&await Aq(r,i,R)}});u=S.prompt;let x=S.status==="stalled",H=null,F=x?`provider stalled${S.stalledAfterMs!==void 0?` after ${S.stalledAfterMs}ms`:""}`:S.status==="error"?S.error??S.receipt:n.policy?.outOfPolicy===!0?n.policy.reason:null,$=x||S.status==="error"?"error":n.policy?.outOfPolicy===!0?"out_of_policy":"ok";if(l!==null?(await TS(r,l,{...n,message:u,response:S.receipt,status:$,error_detail:F}),d=!0,H=l):H=await Uq(r,{...n,message:u,response:S.receipt,status:$,error_detail:F}),x){let R=F;ds(i,n,u,R,"error"),c=yl(R,H,S.receipt)}else c=gl(S.receipt,H);return{status:x?"stalled":"ok",replyText:S.receipt,wroteDelta:S.wroteDelta,teamChatLogId:H,messageId:H===null?null:Gr(H),durationMs:S.durationMs}}catch(f){let m=f instanceof Error?f.message:String(f);return l!==null&&!d&&n!==null&&(await TS(r,l,{...n,message:u,response:null,status:"error",error_detail:m}),d=!0),ds(i,n,u,m,"error"),c=yl(m,l),{status:"error",errorKind:f instanceof ke?"target":"provider",message:m,teamChatLogId:l,messageId:l===null?null:Gr(l)}}finally{o!==void 0&&clearInterval(o),a!==void 0&&clearInterval(a),l!==null&&await Fq(r,n,Gr(l)),s!==null&&await jq(r,s.row,s.leaseId),i!==null&&await Nq(r,i,c??yl("chat turn exited without a terminal outcome",null))}}function bq(e,t){return{...e,stdout:{write:()=>{}},stderr:{write:r=>{t({kind:"warning",message:r})}}}}async function Sq(e,t,r){let n=await O(e,i=>(Wa(i,t,r),_t(i,t,r)));if(!n.busy)return{busy:!1,idleReclaim:!1};let s=await O(e,i=>ns(i,n,t,r));return{busy:!s,idleReclaim:s}}async function kq(e,t,r){let n=await bS(e,t,r);if(n!==null)return Qf(n);let s=await O(e,i=>i.getAgentInflight(t,r));return s!==null?SS(t,r,s):`${r} is busy; wait and retry`}async function rm(e,t){return await Xa(e,e.env??process.env),await O(e,r=>{let n=Ne(t.sender.teamfile,t.sender.alias,r);return r.enqueueMail({sender_teamfile:t.sender.teamfile,sender_alias:t.sender.alias,sender_provider:n.provider,sender_session_id:n.sessionid,receiver_teamfile:t.receiverTeamfile,receiver_alias:t.receiverAlias,receiver_provider:t.receiverProvider,receiver_session_id:t.receiverSessionId,root_teamfile:t.rootTeamfile,payload:t.payload})})}function nm(e,t,r,n){let s=`${r}:${n}
|
|
2531
|
+
`}),i=await Iq(r,{receiverTeamfile:m,receiverAlias:f.alias,sender:h,rootTeamfile:_,provider:f.provider,model:f.model,sessionId:f.sessionid,cwd:f.sessiondir,prompt:e.prompt,origin:p.origin}),o=xq(r,i);try{as(r,ot(m),m)}catch{}n={root_teamfile:_,sender_teamfile:h?.teamfile??null,sender_alias:h?.alias??null,receiver_teamfile:m,receiver_alias:f.alias,receiver_provider:f.provider,receiver_session_id:f.sessionid,origin:p.origin,policy:g};let y=await bS(r,m,f.alias);if(y!==null){let R=Qf(y);if(h!==null){let A=await rm(r,{sender:h,receiverTeamfile:m,receiverAlias:f.alias,receiverProvider:f.provider,receiverSessionId:f.sessionid,rootTeamfile:_,payload:e.prompt});return c=gl(`enqueued mailbox message ${A.id}`,null),nm(A.id,h,m,f.alias)}return ds(i,n,e.prompt,R,"unavailable"),c=sm(R,null),{status:"busy",message:R,teamChatLogId:null}}let E=await Sq(r,m,f.alias);if(E.busy){if(h!==null){let A=await rm(r,{sender:h,receiverTeamfile:m,receiverAlias:f.alias,receiverProvider:f.provider,receiverSessionId:f.sessionid,rootTeamfile:_,payload:e.prompt});return c=gl(`enqueued mailbox message ${A.id}`,null),nm(A.id,h,m,f.alias)}let R=await kq(r,m,f.alias);return ds(i,n,e.prompt,R,"unavailable"),c=sm(R,null),{status:"busy",message:R,teamChatLogId:null}}if(h!==null&&h.teamfile!==m){let R=await O(r,A=>dv(A,{root:_,sender:h,receiverTeamfile:m,receiverAlias:f.alias,now:new Date}));if(R.stop){let A=uv(R.chain);return ds(i,n,e.prompt,A,"cycle"),c=yl(A,null),{status:"error",errorKind:"cycle",message:A,teamChatLogId:null,messageId:null}}}let v=await Dq(r,{receiver_teamfile:m,receiver_alias:f.alias,sender_teamfile:h?.teamfile??m,sender_alias:h?.alias??"operator",...E.idleReclaim?{force:!0,reclaimReason:"session-idle"}:{}});if(!v.acquired){let R=SS(m,f.alias,v.current);if(h!==null){let A=await rm(r,{sender:h,receiverTeamfile:m,receiverAlias:f.alias,receiverProvider:f.provider,receiverSessionId:f.sessionid,rootTeamfile:_,payload:e.prompt});return c=gl(`enqueued mailbox message ${A.id}`,null),nm(A.id,h,m,f.alias)}return ds(i,n,e.prompt,R,"unavailable"),c=sm(R,null),{status:"busy",message:R,teamChatLogId:null}}s={row:v.lease,leaseId:v.leaseId},a=Er(R=>O(r,A=>A.heartbeatAgentInflight(s.row,s.leaseId,R)),{intervalMs:vS,onError:R=>{ue()&&Pe(JSON.stringify({event:"agent_inflight_heartbeat_error",receiver:{teamfile:s.row.receiver_teamfile,alias:s.row.receiver_alias},error:R instanceof Error?R.message:String(R)}))}});let S=await es({sender:h??{teamfile:m,alias:"operator"},receiver:{teamfile:m,alias:f.alias,provider:f.provider,sessionId:f.sessionid,cwd:f.sessiondir,...f.model!==void 0?{model:f.model}:{},...f.effort!==void 0?{effort:f.effort}:{},...f.isLead!==void 0?{isLead:f.isLead}:{}},newMessage:e.prompt,newMessageFrom:h===null?"operator":`${h.teamfile}:${h.alias}`,io:r,permissionMode:e.permissionMode,env:e.env??process.env,...e.idleTimeoutMs!==void 0?{idleTimeoutMs:e.idleTimeoutMs}:{},onDelta:R=>t({kind:"delta",chunk:R}),onProgress:R=>t({kind:"progress",alias:f.alias,event:R}),onPromptBuilt:async R=>{u=R,l===null&&(l=await Pq(r,{...n,message:R}),l!==null&&e.onAccepted?.(Gr(l))),i!==null&&await Aq(r,i,R)}});u=S.prompt;let x=S.status==="stalled",H=null,F=x?`provider stalled${S.stalledAfterMs!==void 0?` after ${S.stalledAfterMs}ms`:""}`:S.status==="error"?S.error??S.receipt:n.policy?.outOfPolicy===!0?n.policy.reason:null,$=x||S.status==="error"?"error":n.policy?.outOfPolicy===!0?"out_of_policy":"ok";if(l!==null?(await TS(r,l,{...n,message:u,response:S.receipt,status:$,error_detail:F}),d=!0,H=l):H=await Uq(r,{...n,message:u,response:S.receipt,status:$,error_detail:F}),x){let R=F;ds(i,n,u,R,"error"),c=yl(R,H,S.receipt)}else c=gl(S.receipt,H);return{status:x?"stalled":"ok",replyText:S.receipt,wroteDelta:S.wroteDelta,teamChatLogId:H,messageId:H===null?null:Gr(H),durationMs:S.durationMs}}catch(f){let m=f instanceof Error?f.message:String(f);return l!==null&&!d&&n!==null&&(await TS(r,l,{...n,message:u,response:null,status:"error",error_detail:m}),d=!0),ds(i,n,u,m,"error"),c=yl(m,l),{status:"error",errorKind:f instanceof ke?"target":"provider",message:m,teamChatLogId:l,messageId:l===null?null:Gr(l)}}finally{o!==void 0&&clearInterval(o),a!==void 0&&clearInterval(a),l!==null&&await Fq(r,n,Gr(l)),s!==null&&await jq(r,s.row,s.leaseId),i!==null&&await Nq(r,i,c??yl("chat turn exited without a terminal outcome",null))}}function bq(e,t){return{...e,stdout:{write:()=>{}},stderr:{write:r=>{t({kind:"warning",message:r})}}}}async function Sq(e,t,r){let n=await O(e,i=>(Wa(i,t,r),_t(i,t,r)));if(!n.busy)return{busy:!1,idleReclaim:!1};let s=await O(e,i=>ns(i,n,t,r));return{busy:!s,idleReclaim:s}}async function kq(e,t,r){let n=await bS(e,t,r);if(n!==null)return Qf(n);let s=await O(e,i=>i.getAgentInflight(t,r));return s!==null?SS(t,r,s):`${r} is busy; wait and retry`}async function rm(e,t){return await Xa(e,e.env??process.env),await O(e,r=>{let n=Ne(t.sender.teamfile,t.sender.alias,r);return r.enqueueMail({sender_teamfile:t.sender.teamfile,sender_alias:t.sender.alias,sender_provider:n.provider,sender_session_id:n.sessionid,receiver_teamfile:t.receiverTeamfile,receiver_alias:t.receiverAlias,receiver_provider:t.receiverProvider,receiver_session_id:t.receiverSessionId,root_teamfile:t.rootTeamfile,payload:t.payload})})}function nm(e,t,r,n){let s=`${r}:${n}`,i=$e(e);return{status:"enqueued",id:e,message:`mailbox message ${i} enqueued to ${s}`,teamChatLogId:null,messageId:i,sender:`${t.teamfile}:${t.alias}`,receiver:s}}async function Iq(e,t){try{return(await O(e,n=>n.startTrackedInvocation({kind:"chat",origin:t.origin,scope_teamfile:t.receiverTeamfile,from_identity:t.sender===null?"operator":`${t.sender.teamfile}:${t.sender.alias}`,target_teamfile:t.receiverTeamfile,target_alias:t.receiverAlias,root_teamfile:t.rootTeamfile,cwd:t.cwd??null,command:Rq(t.receiverTeamfile,t.receiverAlias,t.prompt),holder_pid:process.pid,request_payload:t.prompt,meta:JSON.stringify({provider:t.provider,model:t.model??null,sessionid:t.sessionId??null})}))).id}catch(r){return ue()&&Pe(JSON.stringify({event:"tracked_invocation_start_error",error:r instanceof Error?r.message:String(r)})),null}}async function Nq(e,t,r){try{await O(e,n=>{n.finishTrackedInvocation(t,r)})}catch(n){ue()&&Pe(JSON.stringify({event:"tracked_invocation_finish_error",id:t,error:n instanceof Error?n.message:String(n)}))}}async function Aq(e,t,r){try{await O(e,n=>{n.updateTrackedInvocationRequestPayload(t,r)})}catch(n){ue()&&Pe(JSON.stringify({event:"tracked_invocation_request_payload_error",id:t,error:n instanceof Error?n.message:String(n)}))}}function xq(e,t){if(t===null)return;let r=setInterval(()=>{O(e,n=>n.heartbeatTrackedInvocation(t)).catch(n=>{ue()&&Pe(JSON.stringify({event:"tracked_invocation_heartbeat_error",id:t,error:n instanceof Error?n.message:String(n)}))})},vS);return r.unref?.(),r}function gl(e,t){return{status:"done",result_summary:yi(e),error:null,team_chat_log_id:t}}function sm(e,t){return{status:"busy",result_summary:yi(e),error:e,team_chat_log_id:t}}function yl(e,t,r){return{status:"error",result_summary:yi(r===void 0?e:r),error:e,team_chat_log_id:t}}function yi(e){let t=e.replace(/\s+/gu," ").trim();return t.length<=500?t:`${t.slice(0,497)}...`}function Rq(e,t,r){return`lmctl chat ${JSON.stringify(e)} ${t} ${JSON.stringify(r)}`}function Lq(e,t){let r=yS(t,e);if(r.ok)return{sender:De(r.identity),origin:"agent"};if(r.reason==="unset")return{sender:null,origin:"operator"};throw new Error(mr("chat"))}async function Oq(e,t){if(t.explicitRoot!==void 0)return await O(e,n=>Se(t.explicitRoot,n));if(t.operatorOrigin||t.sender===null)return t.receiverTeamfile;let r=t.sender;return await O(e,n=>cv(n,{sender:r,now:new Date})??t.receiverTeamfile)}async function Cq(e,t){return await O(e,r=>uS({rootTeamfile:t.rootTeamfile,fromTeamfile:t.sender.teamfile,fromAlias:t.sender.alias,toTeamfile:t.receiverTeamfile,toAlias:t.receiverAlias,registry:$q(r,t.sender.teamfile,t.receiverTeamfile)}))}function $q(e,t,r){return{teams:[t,r].map(s=>Mq(s,e))}}function Mq(e,t){let r=Ve(e,t),n=Ht(r);return{teamfile:e,aliases:r.members.map(s=>s.alias),leadAlias:n?.alias??null}}async function Dq(e,t){return await O(e,r=>{let n=ue(),s=Lt(r,{receiver_teamfile:t.receiver_teamfile,receiver_alias:t.receiver_alias,sender_teamfile:t.sender_teamfile,sender_alias:t.sender_alias,ttlMs:qt(),...t.force===!0?{force:!0}:{},...t.reclaimReason===void 0?{}:{reclaimReason:t.reclaimReason}});return s.acquired?(n&&Pe(JSON.stringify({event:"inflight_acquire",receiver:{teamfile:t.receiver_teamfile,alias:t.receiver_alias},sender:{teamfile:t.sender_teamfile,alias:t.sender_alias},result:s.reclaimReason===null?"acquired":"reclaimed",...s.reclaimReason===null?{}:{reclaim_reason:s.reclaimReason,holder:wS(s.previous)}})),{acquired:!0,lease:s.lease,leaseId:s.leaseId}):(n&&Pe(JSON.stringify({event:"inflight_acquire",receiver:{teamfile:t.receiver_teamfile,alias:t.receiver_alias},sender:{teamfile:t.sender_teamfile,alias:t.sender_alias},result:"rejected",holder:wS(s.current)})),{acquired:!1,current:s.current})})}async function bS(e,t,r){return await O(e,n=>{for(let i=0;i<2;i+=1){let o=n.getTerminalLock(t,r);if(o===null)return null;if(Zf(o).live)return o;let l=n.getAgentInflight(t,r);if(n.clearTerminalLock(t,r,o.lock_id))return l!==null&&l.sender_teamfile===t&&l.sender_alias==="terminal"&&n.clearObservedTerminalAgentInflight(t,r,{sender_teamfile:l.sender_teamfile,sender_alias:l.sender_alias,started_at:l.started_at,holder_pid:l.holder_pid}),null}let s=n.getTerminalLock(t,r);return s===null?null:Zf(s).live?s:null})}function wS(e){return e===null?null:{sender_teamfile:e.sender_teamfile,sender_alias:e.sender_alias,holder_pid:e.holder_pid,started_at:e.started_at}}async function jq(e,t,r){try{await O(e,n=>{n.releaseAgentInflight(t,r)})}catch(n){e.stderr.write(`warning: failed to release chat in-flight slot: ${n instanceof Error?n.message:String(n)}
|
|
2532
2532
|
`)}}function SS(e,t,r){return`${t} is servicing ${r.sender_alias} (${r.sender_teamfile}) since ${r.started_at} \u2014 wait and retry, or inspect without waking it: lmctl tail "${e}" ${t}`}async function Pq(e,t){let r=Ei(t.message);try{return await O(e,n=>n.beginTeamChatLog({root_teamfile:t.root_teamfile,sender_teamfile:t.sender_teamfile,sender_alias:t.sender_alias,receiver_teamfile:t.receiver_teamfile,receiver_alias:t.receiver_alias,receiver_provider:t.receiver_provider,receiver_session_id:t.receiver_session_id,provider_prompt:t.message,message:r.text,response:null,origin:t.origin,status:"running",error_detail:null,truncated:r.truncated?1:0,created_at:new Date().toISOString()}))}catch(n){return e.stderr.write(`warning: failed to begin chat log: ${n instanceof Error?n.message:String(n)}
|
|
2533
2533
|
`),null}}async function TS(e,t,r){let n=Ei(r.message),s=r.response===null?null:Ei(r.response);try{await O(e,i=>i.finishTeamChatLog(t,{root_teamfile:r.root_teamfile,sender_teamfile:r.sender_teamfile,sender_alias:r.sender_alias,receiver_teamfile:r.receiver_teamfile,receiver_alias:r.receiver_alias,receiver_provider:r.receiver_provider,receiver_session_id:r.receiver_session_id,provider_prompt:r.message,message:n.text,response:s?.text??null,origin:r.origin,status:r.status,error_detail:r.error_detail,truncated:n.truncated||s?.truncated?1:0,created_at:new Date().toISOString()}))}catch(i){e.stderr.write(`warning: failed to finish chat log ${t}: ${i instanceof Error?i.message:String(i)}
|
|
2534
2534
|
`)}}async function Fq(e,t,r){if(t!==null)try{await O(e,n=>{n.clearCurrentlyHandlingMessage(t.receiver_teamfile,t.receiver_alias,r)})}catch(n){e.stderr.write(`warning: failed to clear chat causal pointer: ${n instanceof Error?n.message:String(n)}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lmctl-ai/lmctl",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.227",
|
|
4
4
|
"description": "A provider-agnostic control plane for teams of AI coding agents — across providers, with independent review and durable memory.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"homepage": "https://lmctl.com",
|