@lmctl-ai/lmctl 0.2.46 → 0.2.48
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/build-info.json +1 -1
- package/dist/lmbee.js +14 -15
- package/package.json +1 -1
package/build-info.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"sha": "
|
|
1
|
+
{"sha": "239f51363d608ee489489daddbbd627ea0f20a88", "dirty": true, "builtAt": "2026-10-04T21:50:21.000Z"}
|
package/dist/lmbee.js
CHANGED
|
@@ -127,7 +127,7 @@ Appkey saved to ${p.appkeyPath} (0600)
|
|
|
127
127
|
`),null;for(let[s,i]of[["peer_site",r],["team",n],["alias",o]])try{pn(i,s)}catch(a){return process.stderr.write(`teams-conf: skipping remote _TEAM_ line -- ${a instanceof Error?a.message:String(a)}: ${t}
|
|
128
128
|
`),null}return{channel:"lmmail",name:n,peerSite:r,alias:o}}var WA=/^(`{3,}|~{3,})/;function ec(e){let t=[],r=null;for(let n of e.split(/\r?\n/)){let o=n.trim(),s=WA.exec(o);if(s){let a=s[1];r===null?r={char:a[0],len:a.length}:a[0]===r.char&&a.length>=r.len&&o.slice(a.length).trim()===""&&(r=null);continue}if(r!==null||!qA.test(o))continue;let i=HA(o);i&&t.push(i)}return t}function mm(e){return e.toLowerCase().endsWith(".md")}function GA(e,t){let r=new Set,n=[];for(let o of[e,...t])if(!(!o||r.has(o))&&(r.add(o),!!Za(o))){try{if(!FA(o).isFile())continue}catch{continue}n.push(o)}return n}function VA(e,t=[]){let r=GA(e,t),n=new Map,o=s=>{try{return fm(s,"utf8")}catch{return null}};for(let s of r){if(!mm(s))continue;let i=o(s);if(i!==null)for(let a of ec(i))n.has(a.name)||n.set(a.name,a)}for(let s of r){if(mm(s))continue;let i=o(s);if(i!==null)for(let a of _o(i))n.has(a.name)||n.set(a.name,a)}return[...n.values()]}function xs(e,t,r){e.prepare(`INSERT INTO sources(path, label, added_ts) VALUES (?, ?, ?)
|
|
129
129
|
ON CONFLICT(path) DO UPDATE SET label = excluded.label`).run(t,r,new Date().toISOString())}function tc(e){return e.prepare("SELECT * FROM sources ORDER BY id").all()}function Rs(e,t){e.prepare("DELETE FROM sources WHERE path = ?").run(t)}function JA(e){let t=ks(e);return Za(t)?UA(t):null}function Q(e){let t=JA(dm(e.repoPath,`${e.name}.lmctl`));if(t)return t;throw Za(e.repoPath)?new Error(`resolveTeamfilePath: cannot resolve a teamfile for team "${e.name}" -- expected "${dm(e.repoPath,`${e.name}.lmctl`)}" to exist. teams.conf has no explicit teamfile-path column -- fix by naming the teamfile "<name>.lmctl" under repo_path.`):new Error(`resolveTeamfilePath: repo_path "${e.repoPath}" for team "${e.name}" does not exist`)}var KA=/^_MEMBER_(\s|$)/,zA=/(\w+)=(?:"([^"]*)"|([\S]*))/g;function jt(e,t){return nc(e).find(r=>r.alias===t)?.provider??null}function rc(e){return nc(e).filter(t=>t.sessionid!==void 0&&t.sessionid.length>0).length}function Vr(e){return nc(e).filter(t=>t.alias!==void 0).map(t=>({alias:t.alias,provider:t.provider??null}))}function nc(e){let t;try{t=fm(Q(e),"utf8")}catch{return[]}let r=[];for(let n of t.split(`
|
|
130
|
-
`)){if(!KA.test(n.trim()))continue;let o={};for(let s of n.matchAll(zA)){let i=s[1];o[i]=s[2]!==void 0?s[2]:s[3]}r.push({alias:o.alias,provider:o.provider,sessionid:o.sessionid})}return r}function Jr(e,t){let r=tc(e).map(n=>n.path);return VA(t,r)}function gn(e){if(typeof e!="string")return null;let t=e.lastIndexOf(":");if(t===-1)return null;let r=e.slice(0,t),n=e.slice(t+1);return!r||!n?null:{teamfilePath:r,alias:n}}function _n(e,t){let r=gn(t);if(r===null||r.alias!==e.leadAlias)return!1;let n;try{n=Q(e)}catch{return!1}return n===r.teamfilePath}function Fe(e,t){return t.running.some(r=>_n(e,r.target))}function hm(e,t,r){return r.running.some(n=>{let o=gn(n.target);return o!==null&&o.alias===t&&o.teamfilePath===e})}function YA(e,t,r){let n=new Set(e.filter(u=>Fe(u,t)).map(u=>u.name)),o=new Set(r??[]),s=r===null,i=[...o].filter(u=>!n.has(u)).sort(),a=[...n].filter(u=>!o.has(u)).sort(),c=s||i.length>0,l=s?"first-run":i.length>0?`active->other: ${i.join(", ")}`:"nochange";return{first:s,active:[...n].sort(),wentIdle:i,newlyActive:a,wake:c,why:l}}function Ns(e){let t=e.prepare("SELECT v FROM kv WHERE k = ?").get("active");return t?JSON.parse(t.v):null}function QA(e,t){e.prepare("INSERT INTO kv(k, v) VALUES ('active', ?) ON CONFLICT(k) DO UPDATE SET v = excluded.v").run(JSON.stringify(t))}function Ls(e,t,r){let n=YA(t,r,Ns(e));return QA(e,n.active),n}import{randomUUID as tf}from"node:crypto";import{existsSync as rf,mkdirSync as hk,readFileSync as qs,rmSync as gk,writeFileSync as nf}from"node:fs";import{homedir as _k}from"node:os";import{join as of,resolve as ef,sep as wk}from"node:path";var
|
|
130
|
+
`)){if(!KA.test(n.trim()))continue;let o={};for(let s of n.matchAll(zA)){let i=s[1];o[i]=s[2]!==void 0?s[2]:s[3]}r.push({alias:o.alias,provider:o.provider,sessionid:o.sessionid})}return r}function Jr(e,t){let r=tc(e).map(n=>n.path);return VA(t,r)}function gn(e){if(typeof e!="string")return null;let t=e.lastIndexOf(":");if(t===-1)return null;let r=e.slice(0,t),n=e.slice(t+1);return!r||!n?null:{teamfilePath:r,alias:n}}function _n(e,t){let r=gn(t);if(r===null||r.alias!==e.leadAlias)return!1;let n;try{n=Q(e)}catch{return!1}return n===r.teamfilePath}function Fe(e,t){return t.running.some(r=>_n(e,r.target))}function hm(e,t,r){return r.running.some(n=>{let o=gn(n.target);return o!==null&&o.alias===t&&o.teamfilePath===e})}function YA(e,t,r){let n=new Set(e.filter(u=>Fe(u,t)).map(u=>u.name)),o=new Set(r??[]),s=r===null,i=[...o].filter(u=>!n.has(u)).sort(),a=[...n].filter(u=>!o.has(u)).sort(),c=s||i.length>0,l=s?"first-run":i.length>0?`active->other: ${i.join(", ")}`:"nochange";return{first:s,active:[...n].sort(),wentIdle:i,newlyActive:a,wake:c,why:l}}function Ns(e){let t=e.prepare("SELECT v FROM kv WHERE k = ?").get("active");return t?JSON.parse(t.v):null}function QA(e,t){e.prepare("INSERT INTO kv(k, v) VALUES ('active', ?) ON CONFLICT(k) DO UPDATE SET v = excluded.v").run(JSON.stringify(t))}function Ls(e,t,r){let n=YA(t,r,Ns(e));return QA(e,n.active),n}import{randomUUID as tf}from"node:crypto";import{existsSync as rf,mkdirSync as hk,readFileSync as qs,rmSync as gk,writeFileSync as nf}from"node:fs";import{homedir as _k}from"node:os";import{join as of,resolve as ef,sep as wk}from"node:path";var uj=64*1024;import{readFileSync as Is,writeFileSync as ZA}from"node:fs";function ek(e,t){return e.prepare("UPDATE queue SET status = 'sent', sent_ts = ? WHERE id = ? AND status = 'pending'").run(new Date().toISOString(),t).changes>0}function gm(e,t){let r=[],n=new Map;for(let s of t){if(s.dedupe_key!==null){r.push(s);continue}let i=JSON.stringify([s.from_id,s.to_id,s.escalation_only,s.to_alias]),a=n.get(i);a?a.push(s):n.set(i,[s])}let o=[...r];for(let s of n.values()){if(s.length===1){o.push(s[0]);continue}let[i,...a]=s,c;try{c=Is(i.prompt_file,"utf8")}catch{o.push(...s);continue}let l=[],u=!0;for(let d of a)try{l.push(`
|
|
131
131
|
|
|
132
132
|
--- addendum, ${d.created_ts} ---
|
|
133
133
|
${Is(d.prompt_file,"utf8")}`)}catch{u=!1;break}if(!u){o.push(...s);continue}if(l.length>0){ZA(i.prompt_file,c+l.join(""));let d=e.prepare("UPDATE queue SET status = 'merged' WHERE id = ? AND status = 'pending'");for(let m of a)d.run(m.id)}o.push(i)}return o}function sc(e,t,r){let n=new Map(t.map(c=>[c.name,c])),o=r.teamName?e.prepare("SELECT * FROM queue WHERE status = 'pending' AND escalation_only = 0 AND to_id = ? ORDER BY id").all(r.teamName):e.prepare("SELECT * FROM queue WHERE status = 'pending' AND escalation_only = 0 ORDER BY id").all(),s=gm(e,o),i=new Set,a=0;for(let c of s){let l=c.to_alias===null?c.to_id:`${c.to_id}:${c.to_alias}`;if(i.has(l))continue;i.add(l);let u=n.get(c.to_id);if(!u||r.isTeamBusy(u))continue;if(r.minMemoryPercent!==void 0){let p=r.memoryCheck;if(p&&!p(r.minMemoryPercent))continue}try{Is(c.prompt_file,"utf8")}catch{continue}if(!ek(e,c.id))continue;let d;try{d=Is(c.prompt_file,"utf8")}catch(p){process.stderr.write(`[lmqueue] row #${c.id} claimed 'sent' but its prompt file became unreadable before dispatch -- nothing was actually sent: ${p instanceof Error?p.message:String(p)}
|
|
@@ -689,11 +689,11 @@ Core loop:
|
|
|
689
689
|
(manual nudge and queue delivery are unaffected)
|
|
690
690
|
lmbee auto-nudge-redirect set <team> --companion <team> [--worker-alias A] [--commit-threshold N] [--hours-threshold-ms N]
|
|
691
691
|
lmbee auto-nudge-redirect clear <team> | status [<team>]
|
|
692
|
-
|
|
692
|
+
redirect team's auto-nudge to a cheap companion
|
|
693
693
|
team's Worker (manual dispatch/queue delivery unaffected)
|
|
694
694
|
lmbee provider disable <name> [--reason TEXT] [--expires-at ISO]
|
|
695
695
|
lmbee provider enable <name> | status [<name>]
|
|
696
|
-
|
|
696
|
+
quiet auto-nudge (dispatch and redirect review) for every
|
|
697
697
|
team on a disabled provider (manual dispatch/queue unaffected)
|
|
698
698
|
|
|
699
699
|
Prompts and answers:
|
|
@@ -703,12 +703,11 @@ Prompts and answers:
|
|
|
703
703
|
--in-reply-to: links this prompt to queue prompt <id> -- read/
|
|
704
704
|
unread lifecycle only, never counts as owing a durable answer
|
|
705
705
|
--literal: send an existing-on-disk path as literal TEXT, not
|
|
706
|
-
@file content -- refused by default
|
|
707
|
-
|
|
708
|
-
the filename string, and the report never arrives)
|
|
706
|
+
@file content -- refused by default because a bare path typo
|
|
707
|
+
for @path sends the filename string instead of its contents
|
|
709
708
|
lmbee nudge <team...>
|
|
710
709
|
lmbee prompt <to> <prompt|@file> --from <id> [--new-task "title" [--task-description TEXT] | --task <id>] [--literal]
|
|
711
|
-
|
|
710
|
+
PARALLEL to enqueue/queue --
|
|
712
711
|
writes to the prompt_tasks/task_prompts ledger, never queue.
|
|
713
712
|
Task-linked prompts do not create answers for queue prompts.
|
|
714
713
|
--new-task to start a thread, or --task <id> to continue one --
|
|
@@ -722,7 +721,7 @@ Prompts and answers:
|
|
|
722
721
|
A ruling made here does NOT close a task/epic flag (needs_review,
|
|
723
722
|
blocked, etc.) -- write the closing note on that entity directly
|
|
724
723
|
("lmbee tasks note add <id>"), prompt is never read by task-ledger
|
|
725
|
-
automation and never will be
|
|
724
|
+
automation and never will be.
|
|
726
725
|
lmbee prompt log <task-id> [--json] full prompt history for one prompt task
|
|
727
726
|
lmbee prompt tasks <team> [--json] threads <team> has appeared in (either party), newest-active first --
|
|
728
727
|
lmbee prompt reassign <prompt-id> --task <task-id> --by <id>
|
|
@@ -755,14 +754,14 @@ Prompts and answers:
|
|
|
755
754
|
auto-scan, and escalation from claiming it), waits for its current
|
|
756
755
|
invocation (if any) to end, then hands off an interactive
|
|
757
756
|
lctl chat -- non-destructive cut-in-line, never kills the in-flight
|
|
758
|
-
process
|
|
757
|
+
process. Accepts lctl's own space-separated
|
|
759
758
|
"<teamfile> <alias>" syntax too, .lmctl suffix and all.
|
|
760
759
|
|
|
761
760
|
Teams & history:
|
|
762
761
|
lmbee teams
|
|
763
762
|
lmbee team <name> --timeline [--limit N]
|
|
764
763
|
lmbee team register <name> --teamfile <path> [--alias <alias>]
|
|
765
|
-
|
|
764
|
+
registers a real, existing teamfile
|
|
766
765
|
as queue/chat-addressable without hand-editing teams.conf --
|
|
767
766
|
writes to ~/.lmbee/on-demand-teams.conf, promptName=on-demand
|
|
768
767
|
(queue-addressable, but never gets a standing auto-nudge).
|
|
@@ -773,18 +772,18 @@ Teams & history:
|
|
|
773
772
|
lmbee attention [--team T] [--maintainer M] [--stuck-processing-all]
|
|
774
773
|
lmbee in-flight [--exclude teamfile:alias[,teamfile:alias...]] pre-restart check: any other team's session running?
|
|
775
774
|
lmbee dispatch-status capture --pid N | check --pid N [--start-ticks T] --artifacts P1,P2,...
|
|
776
|
-
|
|
775
|
+
check whether ONE specific dispatch finished, without a live watcher
|
|
777
776
|
lmbee logical-name set <name> --teamfile PATH --alias ALIAS | get <name> | list | clear <name>
|
|
778
|
-
|
|
777
|
+
maps name -> (teamfile, alias); "operator" is reserved
|
|
779
778
|
lmbee rate-limit status | log [--provider NAME] [--limit N]
|
|
780
|
-
|
|
779
|
+
latest lmratelimit readings/forecasts/arm state, and
|
|
781
780
|
the durable armed/disarmed/fired/deferred policy decision log
|
|
782
781
|
lmbee reliability-report [--json]
|
|
783
|
-
|
|
782
|
+
per (provider, model), 7d/30d duration + completion
|
|
784
783
|
rate from tracked_invocation, joined to lmbi token totals --
|
|
785
784
|
RELIABILITY only, never a quality/performance score
|
|
786
785
|
lmbee subscriptions --config <path> [--json]
|
|
787
|
-
|
|
786
|
+
thin read-only passthrough to
|
|
788
787
|
lmbi's own reviewed subscription-value report CLI -- prints
|
|
789
788
|
the report verbatim, no reinterpretation (that schema is
|
|
790
789
|
lmbi's domain). Requires LMBI_SUBSCRIPTION_CLI in the
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lmctl-ai/lmctl",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.48",
|
|
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",
|