pocolobby 0.12.2 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/board-page.js +31 -28
- package/dist/board.js +1 -1
- package/dist/catalog.js +1 -1
- package/dist/cli.js +7 -7
- package/dist/console-page.js +110 -68
- package/dist/console.js +1 -1
- package/dist/pace.js +1 -1
- package/dist/protocol.js +1 -1
- package/dist/server.js +4 -4
- package/dist/statusline.js +6 -6
- package/dist/store.js +40 -11
- package/docs/API.md +105 -78
- package/docs/ARCHITECTURE.md +27 -27
- package/docs/LOCAL-SETUP.md +2 -2
- package/docs/OPERATIONS.md +1 -1
- package/docs/QUICKSTART.md +1 -1
- package/examples/PACING.md +14 -24
- package/package.json +1 -1
package/dist/board-page.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
var ne=Object.defineProperty;var i=(e,t)=>ne(e,"name",{value:t,configurable:!0});import{createHash as se}from"node:crypto";import{escape as a}from"./html.js";import{newestFirst as oe}from"./memory.js";import{EMPTY_CSS as re,emptyState as P,mascot as A}from"./mascots.js";import{ICON_LINKS as ie,lobbyIconLinks as le}from"./icons.js";import{axisMoney as de,money as
|
|
1
|
+
var ne=Object.defineProperty;var i=(e,t)=>ne(e,"name",{value:t,configurable:!0});import{createHash as se}from"node:crypto";import{escape as a}from"./html.js";import{newestFirst as oe}from"./memory.js";import{EMPTY_CSS as re,emptyState as P,mascot as A}from"./mascots.js";import{ICON_LINKS as ie,lobbyIconLinks as le}from"./icons.js";import{axisMoney as de,money as _,rateNote as ce}from"./money.js";import{age as pe,CHIP_LABELS as me,displayName as he,WORKED_MS as ue}from"./board.js";const ge="https://fonts.googleapis.com/css2?family=Familjen+Grotesk:wght@500;600;700&family=Hanken+Grotesk:wght@400;500;600&family=JetBrains+Mono:wght@400;500&display=swap",Je=i(e=>["default-src 'none'",`style-src 'nonce-${e}' https://fonts.googleapis.com`,"font-src https://fonts.gstatic.com",`script-src 'nonce-${e}'`,"connect-src 'self'","img-src 'self'","form-action 'self'","base-uri 'none'","frame-ancestors 'none'"].join("; "),"boardCsp"),R=`--bg: #0f1518; --surface: #162026; --sunk: #1c2830; --ink: #e6ebe8; --muted: #92a29c; --line: #26343c;
|
|
2
2
|
--brass: #d8a94a; --brass-soft: #3a2f17;
|
|
3
3
|
--ok: #4cb688; --ok-soft: #173329; --warn: #e39b3f; --warn-soft: #3a2a14;
|
|
4
4
|
--crit: #e46b60; --crit-soft: #3d1d1b; --wait: #9296ec; --wait-soft: #24264a;
|
|
@@ -43,7 +43,13 @@ html, body { margin: 0; }
|
|
|
43
43
|
body { background: var(--bg); color: var(--ink); font: 15px/1.5 var(--body); }
|
|
44
44
|
* { box-sizing: border-box; }
|
|
45
45
|
[hidden] { display: none !important; }
|
|
46
|
-
`,
|
|
46
|
+
`,ye=`.week { display: block; width: 100%; height: 16px; }
|
|
47
|
+
.week .track { fill: var(--sunk); }
|
|
48
|
+
.week .used { fill: var(--ok); }
|
|
49
|
+
.week .behind { fill: color-mix(in srgb, var(--wait) 35%, var(--surface)); }
|
|
50
|
+
.week .ahead { fill: var(--warn); }
|
|
51
|
+
.week .far { fill: var(--crit); }
|
|
52
|
+
.week .now { fill: var(--ink); }`,H=`${xe}
|
|
47
53
|
/* Layout: a concierge desk for agents. What waits on the owner up top, then each agent: its 24 hours of messages, what it does now and what comes next. Phone first. */
|
|
48
54
|
.wrap { max-width: 1080px; margin: 0 auto; padding-block: 0 48px; padding-left: max(16px, env(safe-area-inset-left, 0px)); padding-right: max(16px, env(safe-area-inset-right, 0px)); }
|
|
49
55
|
h1, h2, h3 { font-family: var(--display); margin: 0; text-wrap: balance; letter-spacing: -0.01em; }
|
|
@@ -100,8 +106,8 @@ ${re}
|
|
|
100
106
|
/* Waiting on you in orange, as Claude Code shows a session that waits for its user. */
|
|
101
107
|
.s-wait { color: var(--warn); background: var(--warn-soft); }
|
|
102
108
|
/* Idle and Turn failed come from an agent's hooks: idle with a hollow dot, so it reads apart from Away. */
|
|
103
|
-
.s-idle { color: var(--muted); background: var(--sunk); }
|
|
104
|
-
.s-idle::before { background: none; box-shadow: inset 0 0 0 1.5px currentColor; }
|
|
109
|
+
.s-idle, .s-full { color: var(--muted); background: var(--sunk); }
|
|
110
|
+
.s-idle::before, .s-full::before { background: none; box-shadow: inset 0 0 0 1.5px currentColor; }
|
|
105
111
|
.s-failed { color: var(--crit); background: var(--crit-soft); }
|
|
106
112
|
.accounts { display: grid; gap: 10px; grid-template-columns: repeat(auto-fit, minmax(max(240px, calc((100% - 20px) / 3)), 1fr)); align-items: start; }
|
|
107
113
|
.account { background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius); padding: 10px 14px 11px; display: grid; gap: 6px; align-content: start; min-width: 0; }
|
|
@@ -110,13 +116,7 @@ ${re}
|
|
|
110
116
|
.account .facts b { color: var(--ink); font-weight: 600; }
|
|
111
117
|
/* A bar's numbers stay on one line under it. */
|
|
112
118
|
.account .facts.line { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
113
|
-
|
|
114
|
-
.week .track { fill: var(--sunk); }
|
|
115
|
-
.week .used { fill: var(--ok); }
|
|
116
|
-
.week .behind { fill: color-mix(in srgb, var(--wait) 35%, var(--surface)); }
|
|
117
|
-
.week .ahead { fill: var(--warn); }
|
|
118
|
-
.week .far { fill: var(--crit); }
|
|
119
|
-
.week .now { fill: var(--ink); }
|
|
119
|
+
${ye}
|
|
120
120
|
.flag { font: 600 11px var(--body); text-transform: uppercase; letter-spacing: 0.06em; color: var(--crit); }
|
|
121
121
|
.fact { font-size: 12px; color: var(--muted); }
|
|
122
122
|
|
|
@@ -207,6 +207,9 @@ ${Array.from({length:15},(e,t)=>`.strip i.m${t+1} { height: ${Math.round(4+t/14*
|
|
|
207
207
|
.axis .ticks .t-mid { left: 50%; }
|
|
208
208
|
.axis .ticks .t-end { left: 100%; transform: translateX(-100%); }
|
|
209
209
|
.legend { font-size: 12px; color: var(--muted); margin: 8px 2px 0; }
|
|
210
|
+
/* No pacing: said once above the accounts, with the way to Settings for its owner. */
|
|
211
|
+
.pace-note { font-size: 13.5px; color: var(--muted); margin: -4px 2px 10px; }
|
|
212
|
+
.pace-note a { color: inherit; }
|
|
210
213
|
/* Tokens, last 4 weeks: a bar per UTC day, as tall as its cost at API list prices; a day's
|
|
211
214
|
models show on hover, or on a tap on a phone. Heights are classes: the style policy allows no
|
|
212
215
|
inline styles. */
|
|
@@ -278,7 +281,7 @@ ${Array.from({length:101},(e,t)=>`.days.signed i.b${t} { bottom: ${t}%; }`).join
|
|
|
278
281
|
.strip { gap: 1px; }
|
|
279
282
|
}
|
|
280
283
|
@media (prefers-reduced-motion: no-preference) { .seg button { transition: background 0.15s, color 0.15s; } }
|
|
281
|
-
`,
|
|
284
|
+
`,Ke='<div class="seg" role="group" aria-label="Theme" id="theme" hidden><button type="button" data-theme-choice="system" aria-pressed="true">System</button><button type="button" data-theme-choice="light" aria-pressed="false">Light</button><button type="button" data-theme-choice="dark" aria-pressed="false">Dark</button></div>',ve=`const choices = document.querySelectorAll('[data-theme-choice]');
|
|
282
285
|
const applyTheme = (choice) => {
|
|
283
286
|
if (choice === 'system') document.documentElement.removeAttribute('data-theme');
|
|
284
287
|
else document.documentElement.setAttribute('data-theme', choice);
|
|
@@ -290,8 +293,8 @@ applyTheme(saved);
|
|
|
290
293
|
for (const b of choices) b.addEventListener('click', () => { applyTheme(b.dataset.themeChoice); try { localStorage.setItem('lobby-theme', b.dataset.themeChoice); } catch {} });
|
|
291
294
|
const themeGroup = document.getElementById('theme');
|
|
292
295
|
if (themeGroup) themeGroup.hidden = false;
|
|
293
|
-
|
|
294
|
-
${
|
|
296
|
+
`,$e="try{var t=localStorage.getItem('lobby-theme');if(t==='light'||t==='dark')document.documentElement.setAttribute('data-theme',t)}catch(e){}",O=`
|
|
297
|
+
${ve}const board = document.getElementById('board');
|
|
295
298
|
let timer = 0;
|
|
296
299
|
let busy = board && board.dataset.activity ? true : false;
|
|
297
300
|
const schedule = () => { clearTimeout(timer); timer = setTimeout(refresh, busy ? 4000 : 10000); };
|
|
@@ -379,7 +382,7 @@ document.addEventListener('click', (event) => {
|
|
|
379
382
|
rest.open = false;
|
|
380
383
|
rest.querySelector('summary')?.scrollIntoView({ block: 'nearest' });
|
|
381
384
|
});
|
|
382
|
-
|
|
385
|
+
`,we=se("sha256").update(H).update(O).digest("hex").slice(0,12);function W(e,t,s,n,r=null){return`<!doctype html>
|
|
383
386
|
<html lang="en">
|
|
384
387
|
<head>
|
|
385
388
|
<meta charset="utf-8">
|
|
@@ -391,28 +394,28 @@ ${r?le(r.version,r.at):ie}
|
|
|
391
394
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
392
395
|
<link rel="stylesheet" href="${ge}">
|
|
393
396
|
<style nonce="${t}">${H}</style>
|
|
394
|
-
<script nonce="${t}">${
|
|
397
|
+
<script nonce="${t}">${$e}</script>
|
|
395
398
|
</head>
|
|
396
399
|
<body>
|
|
397
400
|
${s}
|
|
398
401
|
${n?`<script nonce="${t}">${O}</script>`:""}
|
|
399
402
|
</body>
|
|
400
403
|
</html>
|
|
401
|
-
`}i(W,"page");function
|
|
402
|
-
`)}i(
|
|
403
|
-
<div class="charts">${n.join("")}</div>${t?t.legend:""}${s.map(r=>r.legend).join("")}`:""}i(
|
|
404
|
+
`}i(W,"page");function ke(e,t){const s=i(r=>new Date(r).toISOString().slice(11,16),"time"),n=e.work.map((r,l)=>{if(r*6e4<ue)return"<i></i>";const c=t.history.since_ms+l*t.history.slot_ms;return`<i class="m${Math.min(15,r)}" title="${s(c)}\u2013${s(c+t.history.slot_ms)} UTC: worked ${r} min"></i>`}).join("");return`<div class="strip" role="img" aria-label="Work over the last 24 hours: ${e.active?`${a(e.active)} active`:"none"}">${n}</div>`}i(ke,"strip");const F=["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],G=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];function j(e){const t=new Date(e),s=`${String(t.getUTCHours()).padStart(2,"0")}:${String(t.getUTCMinutes()).padStart(2,"0")}`;return`<time datetime="${e}" data-at>${F[t.getUTCDay()]} ${t.getUTCDate()} ${G[t.getUTCMonth()]}, ${s} UTC</time>`}i(j,"resetTime");function Se(e,t){const s=i((p,h,o=!0)=>h?`${p} <b${h.left<20?' class="low"':""}>${h.left}% left</b>${o?` resets ${j(h.resets_at)}`:""}`:"","window"),n=i(p=>!!(p&&e.seven_day&&Math.abs(Date.parse(p.resets_at)-Date.parse(e.seven_day.resets_at))<60*6e4),"sameReset"),r=e.account?`${a(e.account)}${e.plan?` (${a(e.plan)})`:""}`:"",l=t-Date.parse(e.observed_at);return`<div class="usage">${[r,s("5h",e.five_hour),s("Week",e.seven_day),...e.windows.map(p=>s(a(p.name),p,!n(p))),l>=30*6e4?`as of ${pe(l)} ago`:""].filter(Boolean).map(p=>`<span>${p}</span>`).join("")}</div>`}i(Se,"usageLine");const J=20;function K(e,t="Week"){const s=i(h=>Math.min(100,Math.max(0,h)),"clamp"),n=s(e.used),r=s(e.line),l=i((h,o,m,g=3,f=10)=>`<rect class="${h}" x="${o}" y="${g}" width="${m}" height="${f}"/>`,"rect"),c=[l("track",0,100)];e.state==="on"?c.push(l("used",0,n),...r>n?[l("behind",n,r-n)]:[]):e.state==="ahead"?c.push(l("used",0,r),l(e.lead>J?"far":"ahead",r,n-r)):c.push(l("used",0,n),l("behind",n,r-n)),c.push(l("now",Math.min(98.8,Math.max(0,r-.6)),1.2,0,16));const p=e.state==="on"?"on pace":`${Math.abs(e.lead)} points ${e.state==="ahead"?"ahead":"behind"}`;return`<svg class="week" viewBox="0 0 100 16" preserveAspectRatio="none" role="img" aria-label="${a(t)} ${n}% used, ${p}"><title>${p}: ${n}% used, target ${r}%</title>${c.join("")}</svg>`}i(K,"weekBar");const Y='<span role="img" aria-label="target" title="Target">\u{1F3AF}</span>';function _e(e){return`<div class="accounts">${e.accounts.map(t=>`<div class="account"><strong title="${a(t.account)}">${a(t.account)}</strong>${K({...t,used:t.week_used})}<span class="facts line"><b>${t.week_used}%</b> (${Y}${t.line}%) resets ${j(t.week_resets_at)}</span>${t.windows.map(s=>`${K(s,s.name)}<span class="facts line">${a(s.name)} <b>${s.used}%</b> (${Y}${s.line}%)${Math.abs(Date.parse(s.resets_at)-Date.parse(t.week_resets_at))>=60*6e4?` resets ${j(s.resets_at)}`:""}</span>`).join("")}${t.agents.length?`<span class="facts">${t.agents.map(a).join(", ")}</span>`:""}</div>`).join("")}</div><p class="legend">Each bar is an account's week, with the share used under it. The mark and \u{1F3AF} are its target: how much of the week it should have used by now to use all of it exactly by its reset. Green: on target. Blue: room left. Orange: used ahead of target. Red: more than ${J} points ahead.${e.accounts.some(t=>t.windows.length)?" A second bar named after a model, such as Fable, is that model's own weekly limit, which Claude counts apart from the week.":""}</p>`}i(_e,"accountRows");const Te={model:'<path d="M8 1.5 9.6 6.4 14.5 8 9.6 9.6 8 14.5 6.4 9.6 1.5 8 6.4 6.4Z" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linejoin="round"/>',context:'<rect x="2.5" y="4.5" width="9" height="9" rx="1.5" fill="none" stroke="currentColor" stroke-width="1.5"/><path d="M5 2.5h7a1.5 1.5 0 0 1 1.5 1.5v7" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>',effort:'<path d="M3.5 13v-3M8 13V7M12.5 13V3" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>'},z=i((e,t,s,n="")=>`<span class="pill${n}" title="${t}"><svg viewBox="0 0 16 16" aria-hidden="true">${Te[e]}</svg>${s}</span>`,"pill");function Ce(e){return!e||!(e.model||e.context||e.effort)?"":`<span class="pills">${[e.model?z("model","Model",`<b>${a(e.model)}</b>`):"",e.context?z("context","Context window",a(e.context)):"",e.effort?z("effort","Reasoning effort",a(e.effort),` effort e-${a(e.effort)}`):""].join("")}</span>`}i(Ce,"sessionPills");function Me(e,t,s,n=""){const r=e.updated_at>e.created_at,l=s[0],c=l?`<span class="taught">${l.emoji?`${a(l.emoji)} `:""}${a(l.name)}${s.length>1?` and ${s.length-1} more`:""}</span> \xB7 `:"";return`<span class="kind k-${a(e.kind)}">${a(e.kind)}</span><span class="rule">${a(e.title.split("\u2192")[0].trim())}</span>${n}<span class="when">${c}${r?"Changed":"Added"} ${t(r?e.updated_at:e.created_at)}</span>`}i(Me,"memoryLine");const V=5;function Ae(e){const t=new Map(e.agents.map(n=>[n.agent_id,n])),s=i(n=>`<time data-at datetime="${a(n)}">${a(n.slice(0,16).replace("T"," "))} UTC</time>`,"at");return e.memory.map(n=>{const r=[`${n.entries.length} entr${n.entries.length===1?"y":"ies"}`,n.curator?`curated by ${a(n.curator)}`:"",n.pending?`${n.pending} waiting for the curator`:""].filter(Boolean),l=i(o=>`<li><details data-ref="${a(o.ref)}"><summary class="entry-line">${Me(o,s,o.taught_by.map(m=>({name:t.get(m)?.name??he(m),emoji:t.get(m)?.emoji??null})),o.disputed?'<span class="disputed">disputed</span>':"")}</summary><p class="entry-full">${a(o.title)}</p><p>${a(o.text)}</p>${o.disputed?`<p class="meta">Disputed: ${a(o.disputed)}</p>`:""}<p class="meta">${a(o.ref)}${o.checked_at?`, last checked against the code ${s(o.checked_at)}`:""}.</p></details></li>`,"row"),c=[...n.entries].sort(oe),p=c.slice(V),h=c.length?`<ul>${c.slice(0,V).map(l).join("")}</ul>${p.length?`<details class="rest" data-ref="rest:${a(n.room_id)}"><summary><span class="more">${p.length} more</span><span class="fewer">Show fewer</span></summary><ul>${p.map(l).join("")}</ul><button type="button" class="fewer-end" data-close-rest>Show fewer</button></details>`:""}`:'<p class="meta">No entries yet.</p>';return`<div class="panel memory"><h3>#${a(n.room_id)} <small>${r.join(", ")}, newest first</small></h3>${h}</div>`}).join(`
|
|
405
|
+
`)}i(Ae,"memoryRooms");function je(e){const t=me[e.chip];return!e.chip_age||e.chip_age==="just now"?t:e.chip==="failed"?`${t} ${a(e.chip_age)} ago`:`${t} ${a(e.chip_age)}`}i(je,"chipText");function T(e){if(e<1e4)return e.toLocaleString("en-US");for(const[t,s]of[[1e9,"B"],[1e6,"M"],[1e3,"K"]])if(e>=t)return`${(e/t).toFixed(1)}${s}`;return String(e)}i(T,"tokens");function E(e){if(!(e>0))return{step:0,top:0};const t=10**Math.floor(Math.log10(e/4)),s=[1,2,2.5,5,10].map(n=>n*t).find(n=>n>=e/4);return{step:s,top:Math.ceil(e/s-1e-9)*s}}i(E,"tokenScale");function D(e,t,s){const n=new Date(`${e}T00:00:00Z`);return{label:`${F[n.getUTCDay()]} ${n.getUTCDate()} ${G[n.getUTCMonth()]}`,place:t<s/2?" early":" late"}}i(D,"dayLabel");function ze(e,t){const{step:s,top:n}=E(Math.max(...e.days.map(o=>o.cost*t.rate))),r=i(o=>Math.round(o*t.rate/n*100),"height"),l=e.days.reduce((o,m)=>o+m.cost,0),c=e.days.some(o=>o.unpriced),p=e.days.map((o,m)=>{const{label:g,place:f}=D(o.period,m,e.days.length),d=`${o.unpriced?"at least ":""}${_(o.cost,t)}`,x=n>0?Math.max(o.models.length?1:0,r(o.cost)):0,$=o.models.length?o.models.map(u=>`<span class="model"><b>${a(u.model)}</b> ${u.cost===null?"no list price":_(u.cost,t)}<span class="counts">in ${T(u.input)} \xB7 cache write ${T(u.cache_write)} \xB7 cache read ${T(u.cache_read)} \xB7 out ${T(u.output)}</span></span>`).join(""):'<span class="model">No model calls reported</span>';return`<button type="button" class="day${o.models.length?"":" empty"}${f}" aria-label="${g}: ${d}"><i class="h${x}"></i><span class="tip"><b>${g}</b> \xB7 ${d}${$}</span></button>`}).join(""),h=e.days.at(-1);return{panel:`<div class="panel tokens"><p class="chart-title">Tokens</p><p class="total"><b>${c?"at least ":""}${_(l,t)}</b> in 4 weeks at API list prices, ${_(h.cost,t)} today</p>${N(n,s,p,{label:i(o=>de(o,s,t),"label")})}</div>`,legend:`<p class="legend">Tokens: each bar is a UTC day of the model calls in agents' Claude Code transcripts, as tall as they would cost at Anthropic's API list prices of ${a(e.prices_as_of)}, not what the Claude subscriptions cost. Hover over a day, or tap it, for each model's input, cache writes, cache reads and output.${c?" A model without a list price counts in tokens only.":""}${a(ce(t))}</p>`}}i(ze,"tokenChart");const Ee=i(e=>e>=1e3?`${(e/1e3).toLocaleString("en-US",{maximumFractionDigits:1})}k`:String(e),"short"),b=i(e=>e.toLocaleString("en-US"),"whole"),L=i(e=>`${e<0?"\u2212":"+"}${b(Math.abs(e))}`,"signed");function N(e,t,s,n={}){const{label:r=Ee,bottom:l=0,signed:c=!1}=n,p=X(e,l),h=t?Array.from({length:Math.round((e+l)/t)+1},(m,g)=>g*t-l).filter(m=>c||m!==0):[],o=i(m=>m<0?`\u2212${r(-m)}`:r(m),"text");return`<div class="chart"><div class="yaxis" aria-hidden="true"><span class="sizer">${h.length?o(l?-l:e):""}</span>${h.map(m=>`<span class="g${p(m)}">${o(m)}</span>`).join("")}</div><div class="plot"><div class="grid" aria-hidden="true">${h.map(m=>`<i class="g${p(m)}${c&&m===0?" zero":""}"></i>`).join("")}</div><div class="days${c&&h.length?" signed":""}">${s}</div></div><div class="days-axis" aria-hidden="true"><span>4 weeks ago</span><span>today</span></div></div>`}i(N,"dayChart");const X=i((e,t)=>s=>e+t?Math.round((s+t)/(e+t)*100):0,"chartPlace");function De(e,t){const s=t>1?` <span class="room">#${a(e.room_id)}</span>`:"",n=E(Math.max(...e.days.map(o=>o.commits))),r=e.days.map((o,m)=>{const{label:g,place:f}=D(o.day,m,e.days.length),d=n.top?Math.max(o.commits?1:0,Math.round(o.commits/n.top*100)):0,x=o.authors.reduce((y,S)=>y+S.commits,0),$=o.commits?[...o.authors.map(y=>`<span class="model">${y.emoji?`${a(y.emoji)} `:""}${a(y.name)} <b>${b(y.commits)}</b></span>`),...o.commits>x?[`<span class="model">Others <b>${b(o.commits-x)}</b></span>`]:[]].join(""):'<span class="model">No commits</span>',u=`${b(o.commits)} commit${o.commits===1?"":"s"}`;return`<button type="button" class="day${o.commits?"":" empty"}${f}" aria-label="${g}: ${u}"><i class="h${d}"></i><span class="tip"><b>${g}</b>, ${u}${$}</span></button>`}).join(""),l=e.days.reduce((o,m)=>o+m.commits,0),c=i((o,m,g)=>{const f=i(v=>v.new-v.removed,"net"),d=e.days.map(v=>f(v[o])),x=Math.max(0,...d),$=Math.max(0,...d.map(v=>-v)),{step:u}=E(Math.max(x,$)),y=u?Math.ceil(x/u-1e-9)*u:0,S=u?Math.ceil($/u-1e-9)*u:0,q=X(y,S),C=q(0),Z=e.days.map((v,ee)=>{const{label:I,place:te}=D(v.day,ee,e.days.length),w=v[o],k=f(w),U=Math.max(k?1:0,Math.abs(q(k)-C)),ae=k<0?C-U:C,B=`${L(k)} lines`;return`<button type="button" class="day${k?"":" empty"}${te}" aria-label="${I}: ${B}"><span class="col"><i class="b${ae} h${U}"></i></span><span class="tip"><b>${I}</b>, ${B}<span class="model">${b(w.new)} new, ${b(w.changed)} changed, ${b(w.removed)} removed</span><span class="model">${b(w.lines)} lines of ${g} at the day's end</span></span></button>`}).join(""),M=e.days.at(-1)[o],Q=e.baseline[o];return`<div class="panel tokens code"><p class="chart-title">${m}${s}</p><p class="total"><b>${L(M.lines-Q)}</b> lines in 4 weeks, ${L(f(M))} today, ${b(M.lines)} in all</p>${N(y,u,Z,{bottom:S,signed:!0})}</div>`},"changes"),p=`<time data-at datetime="${a(e.computed_at)}">${a(e.computed_at.slice(0,16).replace("T"," "))} UTC</time>`,h=e.stale?`<b>These numbers are from ${p}; no newer count has come in for over 6 hours.</b> `:"";return{panels:[`<div class="panel tokens code"><p class="chart-title">Commits${s}</p><p class="total"><b>${b(l)}</b> commits in 4 weeks, ${b(Le(e).commits)} today</p>${N(n.top,n.step,r)}</div>`,c("code","Code changes","code"),c("tests","Test changes","tests")],legend:`<p class="legend">${h}Commits and changes: counted from git in #${a(e.room_id)} at ${a(e.head.slice(0,7))}${e.stale?"":`, ${p}`}, the commits on main's own line by UTC day, and each day's lines added minus removed in source files, tests being the files in test, tests, __tests__, spec and e2e folders, Swift \u2026Tests folders and .test. or .spec. files. Dependencies, build output and vendored code are left out. Hover over a day, or tap it, for who made its commits or for its new, changed and removed lines; a line changed in place counts as changed. A bar below the line is a day that removed more lines than it added.</p>`}}i(De,"codeCharts");const Le=i(e=>e.days.at(-1),"today");function Ne(e){const t=e.tokens?ze(e.tokens,e.money):null,s=e.code.map(r=>De(r,e.code.length)),n=[...t?[t.panel]:[],...s.flatMap(r=>r.panels)];return n.length?`<h2 class="section-title">Last 4 weeks</h2>
|
|
406
|
+
<div class="charts">${n.join("")}</div>${t?t.legend:""}${s.map(r=>r.legend).join("")}`:""}i(Ne,"charts");function qe(e,t){const s=e.emoji?`<div class="avatar ring-${e.chip}" aria-hidden="true">${a(e.emoji)}</div>`:`<div class="avatar initial ring-${e.chip}" aria-hidden="true">${a([...e.name][0]??"?")}</div>`;if(e.headless)return`<div class="agent support headless">${s}<div class="who"><strong>${a(e.name)}</strong><span class="state"><span class="chip s-${e.chip}">Curator</span><span class="fact">runs headless</span></span></div>${e.description?`<div class="description">${a(e.description)}</div>`:""}</div>`;const n=e.connected?"":'<span class="fact">bridge offline</span>',r=e.coordinator||e.support,l=r&&e.today||e.now,c=l?a(l):r?"":'<span class="muted">No current task in the notes</span>',p=e.stall?`<span class="stall">${a(e.stall)}</span>`:"",h=e.next===null||r?"":e.next.count?`<div class="next"><b>Next:</b> ${e.next.items.map(a).join(", ")}${e.next.count>e.next.items.length?` and ${e.next.count-e.next.items.length} more`:""}</div>`:'<div class="next"><b>Next:</b> nothing queued</div>',o=e.usage?Se(e.usage,Date.parse(t.generated_at)):"";return`<div class="agent${e.coordinator?" coordinator":e.support?" support":""}">${s}<div class="who"><strong>${a(e.name)}</strong>${Ce(e.usage)}<span class="state"><span class="chip s-${e.chip}">${je(e)}</span>${n}<span class="active" title="Time it worked in the last 24 hours, from its Claude Code hooks">${e.active?`${a(e.active)} active`:"no work"}</span></span></div>${e.description?`<div class="description">${a(e.description)}</div>`:""}${c||p?`<div class="now">${c}${p}</div>`:""}${h}${o}${ke(e,t)}</div>`}i(qe,"agentRow");function Ie(e,t=null,s=null){const n=e.problems.length?`<ul class="notices" role="status">${e.problems.map(d=>`<li>${a(d)}</li>`).join("")}</ul>`:"",r=e.waiting.length?`<div class="waitlist"><ol>${e.waiting.map(d=>`<li><span class="who">${d.emoji?`${a(d.emoji)} `:""}${a(d.name)}</span><span class="count">${d.count} ${d.count===1?"request":"requests"}</span><span class="since">${a(d.since)}</span><span class="what">${a(d.text)}</span></li>`).join("")}</ol></div>`:e.caption?`<div class="caption">${A(e.caption.picture,"caption-agent",128)}<p class="bubble">${a(e.caption.text)} <span class="written">(<time data-at datetime="${a(e.caption.written_at)}">${a(e.caption.written_at.slice(11,16))} UTC</time>)</span></p></div>`:`<div class="panel quiet">${A("vader-deckchair","quiet-agent",56)}<p><b>Nothing waits on you.</b> When an agent needs a decision, an approval or a look from you, it says so here.</p></div>`,l=e.agents[0]?.work.length??0,c=`<div class="axis" aria-hidden="true"><span></span><div class="ticks" data-since="${e.history.since_ms}" data-slot="${e.history.slot_ms}" data-slots="${l}"><span class="t-start">24 h ago</span><span class="t-mid">12 h ago</span><span class="t-end">now</span></div></div>`,p=e.agents.length>1&&e.agents.every(d=>["away","idle"].includes(d.chip))?`<div class="panel quiet">${A("luke-sleep","quiet-agent",56)}<p><b>All quiet.</b> No agent is working right now.</p></div>`:"",h=e.agents.length?`${p}<div class="agents">${c}${e.agents.map(d=>qe(d,e)).join("")}</div><p class="legend">Each green bar is a quarter hour in which the agent worked a minute or more, as tall as the minutes it worked, and active is its working time in the last 24 hours, both from its Claude Code hooks: a turn runs from the prompt or message that starts it until Claude finishes, waiting included. Working: a turn runs. Idle: connected, no turn running. Away: bridge offline.</p>`:`<div class="panel">${P("mouse-empty","No agents here yet",'The server operator admits them with <span class="mono">pocolobby agent add</span>; each shows here once its bridge connects.')}</div>`,o=e.sources.length?e.sources.map(d=>{const x=`<b>${a(d.room_id)}</b> ${a(d.repo)}`;if(d.commit===null)return`${x}: no notes read yet`;const $=d.files?`${d.files} note${d.files===1?"":"s"} at <span class="mono">${a(d.commit)}</span>`:`no notes found at <span class="mono">${a(d.commit)}</span>`,u=d.cut.length?`, only the beginning of ${a(d.cut.join(", "))}`:"";return`${x}: ${$}${u}, fetched ${a(d.fetched??"")} ago`}).join("; "):"no notes sources",m=e.summary?`Now and next written by ${a(e.summary.model)} ${a(e.summary.written)} ago from the notes and the last day's reports and assignments, at most every ${e.interval_minutes} minutes and only when something changed.`:"Now and next: no summary yet.",g=e.activity==="summarizing"?" <b>Writing now and next\u2026</b>":e.activity==="checking"?" Checking the notes\u2026":"",f=e.summary?`Live. Now and next written <b>${a(e.summary.written)}</b> ago.${g}`:`Live. No summary yet.${g}`;return`<header class="lobby"><h1 class="unseen">${a(e.display_name||e.lobby_id)}</h1><div class="stamp">${f}</div></header>
|
|
404
407
|
<section aria-label="Dashboard">
|
|
405
|
-
${s}
|
|
406
|
-
<h2 class="section-title">${!e.waiting.length&&e.caption?"Right now":"Waiting for you"}</h2>
|
|
407
408
|
${n}
|
|
409
|
+
<h2 class="section-title">${!e.waiting.length&&e.caption?"Right now":"Waiting for you"}</h2>
|
|
410
|
+
${r}
|
|
408
411
|
${e.accounts.length?`<h2 class="section-title">Accounts</h2>
|
|
409
|
-
${_e(e)}`:""}
|
|
410
|
-
${
|
|
412
|
+
${e.pacing==="off"?`<p class="pace-note">No pacing: no limits are set, so every agent works at full speed.${s?` <a href="${a(s)}/pacing">Keep workers to their subscriptions\u2019 weeks</a>.`:""}</p>`:_e(e)}`:""}
|
|
413
|
+
${Ne(e)}
|
|
411
414
|
<h2 class="section-title">Agents</h2>
|
|
412
|
-
${
|
|
415
|
+
${h}
|
|
413
416
|
${e.memory.length?`<h2 class="section-title">Shared memory</h2>
|
|
414
|
-
${
|
|
417
|
+
${Ae(e)}`:""}
|
|
415
418
|
</section>
|
|
416
|
-
<footer class="source"><p>Live from PocoLobby, as of this page: what agents say they wait on from you, their connections and unacknowledged messages, and the messages each sent and the direct messages each received over the last 24 hours.</p><p>${
|
|
417
|
-
<main class="wrap" id="board" data-page="${
|
|
418
|
-
<div class="panel">${P("vader-point","Sign in to see this dashboard",a(s),`<a class="button" href="${a(n)}">Sign in with your email</a>`)}</div></main>`,!1)}i(
|
|
419
|
+
<footer class="source"><p>Live from PocoLobby, as of this page: what agents say they wait on from you, their connections and unacknowledged messages, and the messages each sent and the direct messages each received over the last 24 hours.</p><p>${m}</p><p>Notes: ${o}.</p>${t===null?"":`<p>Signed in as ${a(t)}.</p><form method="post" action="/sign-out"><button type="submit">Sign out of this dashboard</button></form>`}</footer>`}i(Ie,"renderBoardBody");function Ue(e,t){const s=i((c,p,h,o="")=>`<a href="${a(c)}"${o}${h?' aria-current="page"':""}>${p}</a>`,"link"),n=`${e.lobbies?s(e.home,"Lobbies",!t):""}${t?`${s(e.dashboard,"Dashboard",t.current==="dashboard")}${e.settings?s(e.settings,"Settings",t.current==="settings"):e.signIn?s(e.signIn,"Sign in",!1,' class="signin"'):""}`:""}`,r=n?`<nav class="hb-places" aria-label="Places">${n}</nav>`:"",l=t?.current==="settings"&&e.settings?e.settings:"#top";return`<header class="hb"><div class="wrap hb-in"><div class="hb-name"><a class="hb-brand" href="${a(e.home)}"><img src="/favicon.svg" alt="" width="26" height="26"><span>PocoLobby</span></a>${t?`<span class="hb-sep">/</span><a class="hb-lobby" href="${a(l)}">${a(t.name)}</a>`:""}</div>${r}</div></header>`}i(Ue,"topBar");function Ye(e,t,s,n,r){const l=r.dashboard.endsWith("/")?r.dashboard:`${r.dashboard}/`;return W(`${e.display_name||e.lobby_id} on PocoLobby`,t,`${Ue(r,{name:e.display_name||e.lobby_id,current:"dashboard"})}
|
|
420
|
+
<main class="wrap" id="board" data-page="${we}" data-json="${a(l)}dashboard.json"${e.activity?` data-activity="${e.activity}"`:""}>${Ie(e,s,r.settings)}</main>`,!0,n?{version:n,at:l}:null)}i(Ye,"renderBoardPage");function Ve(e,t,s,n){return W(`${e} on PocoLobby`,t,`<main class="wrap login"><header class="lobby"><h1>${a(e)} <small>pocolobby</small></h1></header>
|
|
421
|
+
<div class="panel">${P("vader-point","Sign in to see this dashboard",a(s),`<a class="button" href="${a(n)}">Sign in with your email</a>`)}</div></main>`,!1)}i(Ve,"renderBoardNotice");export{we as BOARD_PAGE,$e as EARLY_SCRIPT,ge as FONTS,fe as HEADER_CSS,be as MEMORY_RULE_CSS,ve as THEME_SCRIPT,Ke as THEME_SWITCH,xe as TOKENS,ye as WEEK_CSS,Je as boardCsp,a as escape,Me as memoryLine,Ie as renderBoardBody,Ve as renderBoardNotice,Ye as renderBoardPage,E as tokenScale,Ue as topBar,K as weekBar};
|
package/dist/board.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
var J=Object.defineProperty;var f=(d,t)=>J(d,"name",{value:t,configurable:!0});import{CAPTION_MODEL as z,captionRequest as Q,writeCaption as b}from"./board-caption.js";import{GitHubNotes as tt,NotesError as et}from"./board-github.js";import{MODEL_NAMES as st,sourceKey as E}from"./board-model.js";import{anthropicClient as P,requestHash as K,summarize as nt,MESSAGE_EXCERPT as rt,MESSAGE_LIMIT as ot,SummaryFailure as O,summaryRequest as H}from"./board-summary.js";import{paceState as T,weeklyPace as q}from"./pace.js";import{PRICES_AS_OF as at,tokenCost as it}from"./prices.js";import{LobbyError as D,parsePrincipal as ct,qualifyAgent as $,windowCovers as ut}from"./protocol.js";const W=5*6e4,B=5,x=1440*6e4,dt=15*6e4,C=15*6e4,R=96,Ot=6e4,Tt={resting:"Resting",stalled:"Not responding",wait:"Waiting on you",away:"Away",working:"Working",idle:"Idle",failed:"Turn failed"};function ht(d,t,e=!0,s=null){return d.paused?"resting":d.unseen_notified_count>0&&d.oldest_unseen_notified_at!==null&&t-Date.parse(d.oldest_unseen_notified_at)>=dt?"stalled":d.owner_requests?"wait":e?s?.state??"idle":"away"}f(ht,"stateChip");function lt(d){const t=Math.floor(d/6e4);if(t<1)return"under a minute";const e=Math.floor(t/60);return e?t%60?`${e} h ${t%60} min`:`${e} h`:`${t} min`}f(lt,"worked");function mt(d,t,e,s=e){const o=d.map(a=>[Math.max(a.started_ms,t),Math.min(a.ended_ms??s,e)]).filter(([a,u])=>u>a).sort(([a],[u])=>a-u),n=[];for(const[a,u]of o){const l=n.at(-1);l&&a<=l[1]?l[1]=Math.max(l[1],u):n.push([a,u])}const r=Array.from({length:R},()=>0);let c=0;for(const[a,u]of n){c+=u-a;for(let l=Math.floor((a-t)/C);l<R;l++){const w=t+l*C;if(w>=u)break;r[l]+=Math.min(u,w+C)-Math.max(a,w)}}return{slots:r.map(a=>Math.floor(a/6e4)),total:c}}f(mt,"workSlots");function y(d){const t=Math.max(0,Math.floor(d/6e4));if(t<1)return"just now";if(t<60)return`${t} min`;const e=Math.floor(t/60);return e<24?t%60&&e<6?`${e} h ${t%60} min`:`${e} h`:`${Math.floor(e/24)} d`}f(y,"age");const Y=f(d=>d.charAt(0).toUpperCase()+d.slice(1),"displayName"),_t=f(d=>st[d]??d,"modelName");function U(d){const t=/^claude-([a-z]+)-(\d+)(?:-(\d{1,2}))?(?:-\d{8})?(?:\[\w+\])?$/u.exec(d);if(!t)return d;const[,e="",s="",o]=t;return`${e.charAt(0).toUpperCase()}${e.slice(1)} ${s}${o?`.${o}`:""}`}f(U,"modelLabel");function j(d){return d.context??(d.model&&/\[1m\]$/iu.test(d.model)?1e6:null)}f(j,"contextWindow");const pt=f(d=>{const t=j(d);return t===null?null:gt(t)},"contextLabelOf");function gt(d){return d>=1e6&&d%1e6===0?`${d/1e6}M`:`${Math.round(d/1e3)}K`}f(gt,"contextLabel");function V(d,t){return d.flatMap(e=>{const s=q(e.used_percentage,e.resets_at,t);return s&&Math.round(e.used_percentage)>0?[{window:e,pace:s}]:[]})}f(V,"paced");const I=1440*6e4,G=15*6e4;function ft(d,t,e){if(t-d.at>=I)return null;const s=f(n=>n&&n.resets_at*1e3>t?{left:Math.max(0,Math.round(100-n.used_percentage)),resets_at:new Date(n.resets_at*1e3).toISOString()}:null,"window"),o={account:e?d.report.account??null:null,plan:d.report.plan??null,five_hour:s(d.report.five_hour),seven_day:s(d.report.seven_day),model:d.report.model?U(d.report.model):null,context:pt(d.report),effort:d.report.effort??null,windows:(d.report.model_windows??[]).flatMap(n=>{const r=s(n),c=d.report.model;return r&&r.left<100&&c&&ut(c,n.name)?[{name:n.name,...r}]:[]}),observed_at:new Date(d.at).toISOString()};return o.five_hour||o.seven_day||o.account||o.model?o:null}f(ft,"usageView");const wt=360*60*1e3,F=28;function St(d,t){const e=new Map;for(const s of d){const o=t(s.day),n=e.get(o)??new Map;e.set(o,n);const r=U(s.model),c=n.get(r)??{model:r,calls:0,input:0,cache_write:0,cache_read:0,output:0,cost:0},a=it(s.model,s,s);c.calls+=s.calls,c.input+=s.input,c.cache_write+=s.cache_write_5m+s.cache_write_1h,c.cache_read+=s.cache_read,c.output+=s.output,c.cost=a===null||c.cost===null?null:c.cost+a,n.set(r,c)}return[...e].map(([s,o])=>{const n=[...o.values()].sort((r,c)=>(c.cost??0)-(r.cost??0));return{period:s,cost:n.reduce((r,c)=>r+(c.cost??0),0),unpriced:n.some(r=>r.cost===null),models:n}})}f(St,"tokenPeriods");function Dt(d){const t=new Date(`${d}T00:00:00Z`);t.setUTCDate(t.getUTCDate()+3-(t.getUTCDay()+6)%7);const e=t.getUTCFullYear(),s=1+Math.floor((t.getTime()-Date.UTC(e,0,1))/(7*x));return`${e}-W${String(s).padStart(2,"0")}`}f(Dt,"isoWeek");const vt=6e4;class Nt{static{f(this,"BoardService")}store;settings;isConnected;now;activityOf;github;client;notesInterval;summaryInterval;sources=new Map;running=new Map;attempts=new Map;unchanged=new Map;summarized=new Map;unsaved=new Map;usage=new Map;tokenCharts=new Map;captionClient;unsavedCaptions=new Map;summarizing=new Set;checking=new Set;controller=new AbortController;constructor(t,e,s,o=Date.now,n=()=>null){this.store=t,this.settings=e,this.isConnected=s,this.now=o,this.activityOf=n,this.github=e.githubToken?new tt({token:e.githubToken,apiBase:e.githubApi,fetch:e.fetch,now:o}):null,this.client=e.summaryClient??(e.anthropicApiKey?P(e.anthropicApiKey):null),this.captionClient=e.captionClient??(e.anthropicApiKey?P(e.anthropicApiKey):null),this.notesInterval=e.notesIntervalMs??6e4,this.summaryInterval=e.summaryIntervalMs??6e5}async close(){this.controller.abort(),await Promise.allSettled(this.running.values())}request(t){if(this.controller.signal.aborted)return Promise.resolve();const e=this.running.get(t);if(e)return e;const s=this.refresh(t).catch(()=>{}).finally(()=>this.running.delete(t));return this.running.set(t,s),s}tick(){let t;try{t=this.store.boardLobbies()}catch{return Promise.resolve()}return Promise.allSettled(t.map(e=>this.request(e))).then(()=>{})}activity(t){return this.summarizing.has(t)?"summarizing":this.checking.has(t)?"checking":null}async refresh(t){const e=this.dueSources(t);e.length&&await this.refreshNotes(t,e),this.controller.signal.aborted||await this.maybeSummarize(t),this.controller.signal.aborted||await this.maybeCaption(t)}captionCostRecorded(t){const e=this.unsavedCaptions.get(t);if(!e)return!0;try{return this.store.saveBoardCaption(e),this.unsavedCaptions.delete(t),!0}catch{return!1}}async maybeCaption(t){if(!this.captionCostRecorded(t)||!this.captionClient||this.store.boardConfig(t).visibility!=="private"||this.unsaved.has(t))return;const e=this.now(),s=this.store.boardSummary(t),o=this.store.boardLastCaption(t);if(!s||o&&o.created_ms>=s.created_ms||this.overBudget(t,e))return;const n=this.view(t),r=new Date(e).toISOString().slice(0,10),c=n.code.filter(h=>!h.stale).flatMap(h=>h.days.filter(g=>g.day===r)),a=c.length?c.reduce((h,g)=>({commits:h.commits+g.commits,new:h.new+g.code.new+g.tests.new,removed:h.removed+g.code.removed+g.tests.removed}),{commits:0,new:0,removed:0}):null,u=n.agents.filter(h=>!h.headless),l=f(h=>this.isConnected(t,h.agent_id)?this.activityOf(t,h.agent_id)?.state??"idle":"offline","state"),w={working:0,idle:0,offline:0};for(const h of u){const g=l(h);w[g==="failed"?"idle":g]++}const v={lobby:n.display_name||t,team:w,agents:u.map(h=>({name:h.name,role:h.coordinator?"coordinator":h.support?"support":"agent",state:l(h),now:((h.coordinator||h.support)&&h.today||h.now)??""})),...a?{today:{commits:a.commits,lines_new:a.new,lines_removed:a.removed}}:{}};let _;try{const h=await b(this.captionClient,Q(v),this.controller.signal);_={lobby_id:t,model:z,usage:h.usage,cost_usd:h.cost_usd,caption:h.caption,picture:h.picture,error:null}}catch(h){if(this.controller.signal.aborted)return;const g=h instanceof O?h:new O("The caption failed");_={lobby_id:t,model:z,usage:g.usage??{input_tokens:0,output_tokens:0,cache_creation_input_tokens:0,cache_read_input_tokens:0},cost_usd:g.cost,caption:null,picture:null,error:g.message}}try{this.store.saveBoardCaption(_)}catch{this.unsavedCaptions.set(t,_)}}sourceState(t,e){const s=`${t}/${e.room_id}`,o=E(e);let n=this.sources.get(s);return n?.key!==o&&(n={key:o,etag:null,checkedAt:null,error:null},this.sources.set(s,n)),n}dueSources(t){if(!this.github)return[];const e=this.now();return this.store.boardSources(t).filter(s=>{const o=this.sourceState(t,s);return o.checkedAt===null||e-o.checkedAt>=this.notesInterval})}async refreshNotes(t,e=this.dueSources(t)){if(!(!this.github||!e.length)){this.checking.add(t);try{await this.fetchSources(this.github,t,e)}finally{this.checking.delete(t)}}}async fetchSources(t,e,s){const o=this.store.boardNotes(e);for(const n of s){const r=this.sourceState(e,n);r.checkedAt=this.now();const c=o.get(n.room_id)??null;try{const a=await t.fetch(n,c,c?r.etag:null,this.controller.signal);(!a.changed||this.store.saveBoardNotes(e,n,a.snapshot))&&(r.etag=a.etag),r.error=null}catch(a){if(this.controller.signal.aborted)return;r.error={message:a instanceof et?a.message:"The notes could not be read",at:this.now()}}}}enabledAgents(t){return this.store.agents(t).filter(e=>e.enabled)}shownAgents(t,e){return this.enabledAgents(t).filter(s=>!e.agents[s.agent_id]?.hidden)}async maybeSummarize(t,e=!1){if(!this.client)return;const s=this.now();if(!e&&s-this.lastAttempt(t)<this.summaryInterval||!e&&s-(this.unchanged.get(t)??-1/0)<this.notesInterval)return;const o=this.unsaved.get(t);if(o)try{this.store.saveBoardSummary(o),this.unsaved.delete(t)}catch{return}if(!this.captionCostRecorded(t)||this.overBudget(t,s))return;const n=this.store.boardNotes(t),r=this.store.boardSources(t).filter(m=>n.has(m.room_id)).map(m=>({room_id:m.room_id,repo:m.repo,snapshot:n.get(m.room_id)}));if(!r.length)return;const c=this.store.boardConfig(t),a=this.shownAgents(t,c),u=r.map(m=>({room_id:m.room_id,repo:m.repo,commit_sha:m.snapshot.commit_sha})),l={lobby:t,agents:a.map(m=>({agent_id:m.agent_id,name:c.agents[m.agent_id]?.name??Y(m.agent_id),coordinator:m.manage_rooms,...c.agents[m.agent_id]?.support?{support:!0}:{}})),notes:r,messages:this.store.boardMessages(t,0,ot,rt+1)},w={model:c.summary_model,effort:c.summary_effort},v=H(l,w),_=K(v),h=K(H({...l,messages:[]},w)),g=l.messages.map(m=>m.sequence),M=this.summarized.get(t),N=M?.base===h&&g.every(m=>M.sequences.has(m)),i=this.store.boardLastAttempt(t);if(!e&&(this.store.boardSummary(t)?.input_hash===_||N||i?.error&&i.input_hash===_&&i.usage.output_tokens>0)){this.unchanged.set(t,s);return}this.attempts.set(t,s),this.summarizing.add(t);const p={lobby_id:t,input_hash:_,sources:u,model:c.summary_model};let S;try{const m=await nt(this.client,v,a.map(k=>k.agent_id),this.controller.signal);S={usage:m.usage,cost_usd:m.cost_usd,summary:m.summary,error:null}}catch(m){if(this.controller.signal.aborted)return;const k=m instanceof O?m:new O("The summary failed");S={usage:k.usage??{input_tokens:0,output_tokens:0,cache_creation_input_tokens:0,cache_read_input_tokens:0},cost_usd:k.cost,summary:null,error:k.message}}finally{this.summarizing.delete(t)}try{this.store.saveBoardSummary({...p,...S}),(S.summary||S.usage.output_tokens>0)&&this.summarized.set(t,{base:h,sequences:new Set(g)})}catch(m){const k={...p,...S,summary:null,error:S.error??"The summary could not be saved"};try{if(S.summary===null)throw m;this.store.saveBoardSummary(k)}catch(A){throw this.unsaved.set(t,k),A}}}overBudget(t,e){return this.store.boardUsage(t,e-x).cost_usd>=B}recordUsage(t,e){this.usage.set(t,{report:e,at:this.now()})}agentUsage(t,e){const s=this.now(),o=this.usage.get($(t,e));if(!o||s-o.at>=I)return null;const{report:n}=o,r=f(_=>_&&_.resets_at*1e3>s?{used_percentage:_.used_percentage,resets_at:new Date(_.resets_at*1e3).toISOString()}:null,"window"),a=(n.account?this.accountWeeks(t,this.enabledAgents(t).map(_=>_.agent_id),s).get(n.account):void 0)??o,u=(n.account?this.accountLooks(t,this.enabledAgents(t).map(_=>_.agent_id),s).get(n.account):void 0)??(n.model_windows?o:void 0),l=a.report.seven_day,w=r(l),v=l?q(l.used_percentage,l.resets_at,s):null;return{account:n.account??null,plan:n.plan??null,five_hour:r(n.five_hour),seven_day:w&&v?{...w,...v,pace:T(v.lead),observed_at:new Date(a.at).toISOString(),stale:s-a.at>=G}:null,model:n.model??null,model_name:n.model?U(n.model):null,context_window:j(n),effort:n.effort??null,model_windows:u?V(u.report.model_windows??[],s).map(({window:_,pace:h})=>({name:_.name,used_percentage:_.used_percentage,resets_at:new Date(_.resets_at*1e3).toISOString(),...h,pace:T(h.lead),observed_at:new Date(u.at).toISOString(),stale:s-u.at>=G})):null,observed_at:new Date(o.at).toISOString()}}accountLooks(t,e,s){const o=new Map;for(const n of e){const r=this.usage.get($(t,n)),c=r?.report.account;!r||!c||!r.report.model_windows||s-r.at>=I||r.at>(o.get(c)?.at??-1/0)&&o.set(c,r)}return o}accountWeeks(t,e,s){const o=new Map;for(const n of e){const r=this.usage.get($(t,n)),c=r?.report.account,a=r?.report.seven_day;!r||!c||!a||s-r.at>=I||a.resets_at*1e3<=s||r.at>(o.get(c)?.at??-1/0)&&o.set(c,r)}return o}accountViews(t,e,s,o,n){const r=this.accountWeeks(t,e.map(u=>u.agent_id),n),c=this.accountLooks(t,e.map(u=>u.agent_id),n),a=new Map;for(const u of e){const l=this.usage.get($(t,u.agent_id)),w=l?.report.account;!l||!w||n-l.at>=I||a.set(w,[...a.get(w)??[],s(u.agent_id)])}return[...a.entries()].sort(([u],[l])=>u.localeCompare(l)).flatMap(([u,l],w)=>{const v=r.get(u),_=v?.report.seven_day,h=_?q(_.used_percentage,_.resets_at,n):null;return!v||!_||!h?[]:[{account:o?u:`Account ${w+1}`,plan:v.report.plan??null,agents:l,week_used:Math.round(_.used_percentage),week_resets_at:new Date(_.resets_at*1e3).toISOString(),line:Math.round(h.line),lead:Math.round(h.lead),state:T(h.lead),windows:V(c.get(u)?.report.model_windows??[],n).map(({window:g,pace:M})=>({name:g.name,used:Math.round(g.used_percentage),resets_at:new Date(g.resets_at*1e3).toISOString(),line:Math.round(M.line),lead:Math.round(M.lead),state:T(M.lead)})),observed_at:new Date(v.at).toISOString()}]})}lastAttempt(t){return Math.max(this.store.boardLastAttempt(t)?.created_ms??-1/0,this.attempts.get(t)??-1/0)}runNow(t){if(!this.client)throw new D(503,"summary_unavailable","The broker has no ANTHROPIC_API_KEY");if(this.running.has(t))throw new D(409,"summary_running","A summary is being written now");const e=this.lastAttempt(t)+W-this.now();if(e>0)throw new D(429,"summary_test_wait",`A summary ran less than ${W/6e4} minutes ago; try again in ${Math.ceil(e/6e4)} min`);if(this.overBudget(t,this.now()))throw new D(429,"summary_budget",`This lobby's summaries cost $${B} in the last 24 hours, the daily ceiling; try again later`);const s=this.store.boardLastAttempt(t)?.sequence??0,o=this.refreshNotes(t).then(()=>this.maybeSummarize(t,!0)),n=o.catch(()=>{}).finally(()=>this.running.delete(t));return this.running.set(t,n),o.then(()=>{const r=this.store.boardLastAttempt(t);return r&&r.sequence>s?r:null})}view(t){const e=this.now(),s=this.store.boardConfig(t),o=this.enabledAgents(t),n=this.store.boardSummary(t),r=this.store.boardLastAttempt(t),c=n?.summary??null,a=this.store.boardNoteFacts(t),u=f(i=>s.agents[i]?.name??Y(i),"name"),l=[],w=s.sources.map(i=>{const p=a.get(i.room_id),S=this.sources.get(`${t}/${i.room_id}`),m=S?.error&&S.key===E(i)?`${S.error.message} (${y(e-S.error.at)} ago)`:null;return m&&l.push(`Notes for ${i.room_id} (${i.repo}): ${m}. ${p?`Showing them as of ${p.commit_sha.slice(0,7)}.`:"No notes from it yet."}`),{room_id:i.room_id,repo:i.repo,commit:p?.commit_sha.slice(0,7)??null,files:p?.files??0,cut:p?.cut??[],fetched:p?y(e-Date.parse(p.fetched_at)):null,error:m}});s.sources.length||l.push("No notes sources are set. The lobby owner picks a repository per room in Settings."),this.github||l.push("Notes are not being read: the broker has no GITHUB_TOKEN."),this.client?this.unsaved.has(t)||this.unsavedCaptions.has(t)?l.push("New summaries are paused: the cost of the last one could not be recorded, which usually means the broker\u2019s disk is full."):this.overBudget(t,e)&&l.push(`New summaries are paused: this lobby's summaries cost $${B} in the last 24 hours, the daily ceiling. They resume as older runs leave the window.`):l.push("No new summaries: the broker has no ANTHROPIC_API_KEY."),r?.error&&(!n||r.sequence>n.sequence)&&l.push(`The summarizer failed ${y(e-r.created_ms)} ago: ${r.error}.${n?` Showing the summary from ${y(e-n.created_ms)} ago.`:""}`);const _=Math.ceil((e+1)/C)*C-R*C,h=new Map;for(const i of this.store.turns(t,_)){const p=ct(i.agent_id).agent_id;h.set(p,[...h.get(p)??[],i])}const g=new Set(o.filter(i=>i.last_seen_at===null&&this.store.curationStatus(t,i.agent_id).length>0).map(i=>i.agent_id)),M=o.filter(i=>!s.agents[i.agent_id]?.hidden).sort((i,p)=>Number(p.manage_rooms)-Number(i.manage_rooms)||Number(g.has(i.agent_id))-Number(g.has(p.agent_id))||+(s.agents[i.agent_id]?.support===!0)-+(s.agents[p.agent_id]?.support===!0)||i.agent_id.localeCompare(p.agent_id)).map(i=>{const p=c?.agents.find(Z=>Z.agent_id===i.agent_id),S=this.isConnected(t,i.agent_id),m=this.usage.get($(t,i.agent_id)),k=S?this.activityOf(t,i.agent_id):null,A=ht(i,e,S,k),X=i.last_seen_at?Date.parse(i.last_seen_at):_,L=mt(h.get(i.agent_id)??[],_,e,S?e:X);return{agent_id:i.agent_id,name:u(i.agent_id),emoji:s.agents[i.agent_id]?.emoji??null,chip:A,chip_age:k&&(A==="idle"||A==="failed")?y(e-Date.parse(k.since)):null,description:i.work_description||null,now:p?.now||null,today:p?.today||null,coordinator:i.manage_rooms,support:s.agents[i.agent_id]?.support===!0,...g.has(i.agent_id)?{headless:!0}:{},stall:A==="stalled"?`${i.unseen_notified_count} message${i.unseen_notified_count===1?"":"s"} not acknowledged, the oldest written to its session ${y(e-Date.parse(i.oldest_unseen_notified_at))} ago`:null,next:!p||p.next===null?null:{count:p.next_count??0,items:p.next},connected:S,work:L.slots,active:L.total?lt(L.total):null,usage:m?ft(m,e,s.visibility==="private"):null}}),N=o.filter(i=>i.owner_requests).sort((i,p)=>i.owner_requests.set_at.localeCompare(p.owner_requests.set_at)).map(i=>({agent_id:i.agent_id,name:u(i.agent_id),emoji:s.agents[i.agent_id]?.emoji??null,count:i.owner_requests.count,text:i.owner_requests.text,since:y(e-Date.parse(i.owner_requests.set_at))}));return{lobby_id:t,display_name:this.store.displayName(t),generated_at:new Date(e).toISOString(),activity:this.activity(t),waiting:N,agents:M,history:{since_ms:_,slot_ms:C},sources:w,summary:n?{written:y(e-n.created_ms),model:_t(n.model)}:null,interval_minutes:Math.round(this.summaryInterval/6e4),problems:l,accounts:this.accountViews(t,o.filter(i=>!s.agents[i.agent_id]?.hidden),u,s.visibility==="private",e),tokens:this.tokenChart(t,e),money:this.store.lobbyMoney(t),memory:s.visibility==="private"?this.memoryRooms(t):[],code:s.visibility==="private"?this.codeViews(t,u,s,e):[],caption:s.visibility==="private"?this.captionView(t):null}}captionView(t){const e=this.store.boardCaption(t);return e?.caption&&e.picture?{text:e.caption,picture:e.picture,written_at:e.created_at}:null}codeViews(t,e,s,o){const n=new Map;for(const r of this.store.agents(t))n.set(r.agent_id,r.agent_id),n.set(e(r.agent_id).toLowerCase(),r.agent_id);return this.store.codeStats(t).map(r=>({room_id:r.room_id,head:r.head,computed_at:r.computed_at,stale:o-Date.parse(r.received_at)>wt,baseline:r.baseline,days:r.days.map(c=>({...c,authors:c.authors.map(a=>{const u=n.get(a.name.toLowerCase());return u?{name:e(u),emoji:s.agents[u]?.emoji??null,commits:a.commits}:{name:a.name,emoji:null,commits:a.commits}})}))}))}memoryRooms(t){const e=["gotcha","fix","procedure"];return this.store.memoryOverview(t).filter(s=>s.curator!==null).map(s=>({room_id:s.room_id,curator:s.curator,pending:s.fragments.filter(o=>o.state==="pending").length,entries:s.entries.filter(o=>o.state==="active").sort((o,n)=>e.indexOf(o.kind)-e.indexOf(n.kind)).map(o=>({ref:o.ref,kind:o.kind,title:o.title,text:o.text,disputed:o.disputed,created_at:o.created_at,updated_at:o.updated_at,checked_at:o.checked_at,taught_by:[...new Set(o.provenance.map(n=>n.author))]}))}))}tokenChart(t,e){const s=this.tokenCharts.get(t);if(s&&e-s.at<vt)return s.chart;const o=(Math.floor(e/x)+1-F)*x,n=Array.from({length:F},(u,l)=>new Date(o+l*x).toISOString().slice(0,10)),r=this.store.tokenDays(t,n[0]),c=new Map(St(r,u=>u).map(u=>[u.period,u])),a=r.length?{days:n.map(u=>c.get(u)??{period:u,cost:0,unpriced:!1,models:[]}),prices_as_of:at}:null;return this.tokenCharts.set(t,{at:e,chart:a}),a}status(t){const e=this.now(),s=this.store.boardConfig(t),o=this.store.boardNotes(t),n=f(r=>r&&{created_at:r.created_at,model:r.model,input_hash:r.input_hash,sources:r.sources,usage:r.usage,cost_usd:r.cost_usd,error:r.error},"shown");return{lobby_id:t,display_name:this.store.displayName(t),dashboard_url:this.settings.domain?`https://${t}.${this.settings.domain}/`:null,visibility:s.visibility,viewers:this.store.boardViewers(t).length,viewer_sessions:this.store.boardSessionCount(t),summary_settings:{model:s.summary_model,effort:s.summary_effort},agents:s.agents,secrets:{github_token:!!this.github,anthropic_api_key:!!this.client},intervals_seconds:{notes:this.notesInterval/1e3,summary:this.summaryInterval/1e3},sources:s.sources.map(r=>{const c=o.get(r.room_id),a=this.sources.get(`${t}/${r.room_id}`),u=a?.key===E(r);return{...r,commit_sha:c?.commit_sha??null,fetched_at:c?.fetched_at??null,files:c?.files.map(l=>({path:l.path,bytes:l.bytes,read_bytes:Buffer.byteLength(l.content),truncated:l.truncated}))??[],last_checked_at:u&&a.checkedAt!==null?new Date(a.checkedAt).toISOString():null,error:u&&a.error?{message:a.error.message,at:new Date(a.error.at).toISOString()}:null}}),summary:{last_good:n(this.store.boardSummary(t)),last_attempt:n(this.store.boardLastAttempt(t))},usage:{last_24h:this.store.boardUsage(t,e-1440*6e4),last_30d:this.store.boardUsage(t,e-720*60*6e4)}}}}export{Nt as BoardService,Tt as CHIP_LABELS,wt as CODE_STALE_MS,B as DAILY_SUMMARY_USD,R as HISTORY_SLOTS,C as HISTORY_SLOT_MS,dt as STALL_MS,W as TEST_SUMMARY_MS,F as TOKEN_DAYS,I as USAGE_SHOWN_MS,G as USAGE_STALE_MS,Ot as WORKED_MS,y as age,gt as contextLabel,j as contextWindow,Y as displayName,Dt as isoWeek,U as modelLabel,ht as stateChip,St as tokenPeriods,ft as usageView,mt as workSlots,lt as worked};
|
|
1
|
+
var J=Object.defineProperty;var S=(u,t)=>J(u,"name",{value:t,configurable:!0});import{CAPTION_MODEL as z,captionRequest as Q,writeCaption as b}from"./board-caption.js";import{GitHubNotes as tt,NotesError as et}from"./board-github.js";import{MODEL_NAMES as st,sourceKey as E}from"./board-model.js";import{anthropicClient as P,requestHash as K,summarize as nt,MESSAGE_EXCERPT as rt,MESSAGE_LIMIT as ot,SummaryFailure as I,summaryRequest as W}from"./board-summary.js";import{paceState as O,weeklyPace as q}from"./pace.js";import{PRICES_AS_OF as at,tokenCost as it}from"./prices.js";import{LobbyError as N,parsePrincipal as ct,qualifyAgent as $,windowCovers as ut}from"./protocol.js";const H=5*6e4,B=5,x=1440*6e4,dt=15*6e4,A=15*6e4,R=96,Ot=6e4,Nt={resting:"Resting",stalled:"Not responding",wait:"Waiting on you",away:"Away",working:"Working",idle:"Idle",full:"No new tasks",failed:"Turn failed"};function ht(u,t,e=!0,s=null){if(u.paused)return"resting";if(u.unseen_notified_count>0&&u.oldest_unseen_notified_at!==null&&t-Date.parse(u.oldest_unseen_notified_at)>=dt)return"stalled";if(u.owner_requests)return"wait";if(!e)return"away";const o=s?.state??"idle";return o==="idle"&&u.pacing?.state==="no_new_tasks"?"full":o}S(ht,"stateChip");function lt(u){const t=Math.floor(u/6e4);if(t<1)return"under a minute";const e=Math.floor(t/60);return e?t%60?`${e} h ${t%60} min`:`${e} h`:`${t} min`}S(lt,"worked");function mt(u,t,e,s=e){const o=u.map(a=>[Math.max(a.started_ms,t),Math.min(a.ended_ms??s,e)]).filter(([a,h])=>h>a).sort(([a],[h])=>a-h),r=[];for(const[a,h]of o){const d=r.at(-1);d&&a<=d[1]?d[1]=Math.max(d[1],h):r.push([a,h])}const n=Array.from({length:R},()=>0);let c=0;for(const[a,h]of r){c+=h-a;for(let d=Math.floor((a-t)/A);d<R;d++){const p=t+d*A;if(p>=h)break;n[d]+=Math.min(h,p+A)-Math.max(a,p)}}return{slots:n.map(a=>Math.floor(a/6e4)),total:c}}S(mt,"workSlots");function C(u){const t=Math.max(0,Math.floor(u/6e4));if(t<1)return"just now";if(t<60)return`${t} min`;const e=Math.floor(t/60);return e<24?t%60&&e<6?`${e} h ${t%60} min`:`${e} h`:`${Math.floor(e/24)} d`}S(C,"age");const Y=S(u=>u.charAt(0).toUpperCase()+u.slice(1),"displayName"),_t=S(u=>st[u]??u,"modelName");function U(u){const t=/^claude-([a-z]+)-(\d+)(?:-(\d{1,2}))?(?:-\d{8})?(?:\[\w+\])?$/u.exec(u);if(!t)return u;const[,e="",s="",o]=t;return`${e.charAt(0).toUpperCase()}${e.slice(1)} ${s}${o?`.${o}`:""}`}S(U,"modelLabel");function j(u){return u.context??(u.model&&/\[1m\]$/iu.test(u.model)?1e6:null)}S(j,"contextWindow");const pt=S(u=>{const t=j(u);return t===null?null:gt(t)},"contextLabelOf");function gt(u){return u>=1e6&&u%1e6===0?`${u/1e6}M`:`${Math.round(u/1e3)}K`}S(gt,"contextLabel");function V(u,t){return u.flatMap(e=>{const s=q(e.used_percentage,e.resets_at,t);return s&&Math.round(e.used_percentage)>0?[{window:e,pace:s}]:[]})}S(V,"paced");const T=1440*6e4,G=15*6e4;function ft(u,t,e,s=null){if(t-u.at>=T)return null;const o=S(n=>n&&n.resets_at*1e3>t?{left:Math.max(0,Math.round(100-n.used_percentage)),resets_at:new Date(n.resets_at*1e3).toISOString()}:null,"window"),r={account:e?u.report.account??null:null,plan:u.report.plan??null,five_hour:o(u.report.five_hour),seven_day:o(s?{used_percentage:s.used,resets_at:s.resets_at}:u.report.seven_day),model:u.report.model?U(u.report.model):null,context:pt(u.report),effort:u.report.effort??null,windows:(u.report.model_windows??[]).flatMap(n=>{const c=o(n),a=u.report.model;return c&&c.left<100&&a&&ut(a,n.name)?[{name:n.name,...c}]:[]}),observed_at:new Date(u.at).toISOString()};return r.five_hour||r.seven_day||r.account||r.model?r:null}S(ft,"usageView");const wt=360*60*1e3,F=28;function St(u,t){const e=new Map;for(const s of u){const o=t(s.day),r=e.get(o)??new Map;e.set(o,r);const n=U(s.model),c=r.get(n)??{model:n,calls:0,input:0,cache_write:0,cache_read:0,output:0,cost:0},a=it(s.model,s,s);c.calls+=s.calls,c.input+=s.input,c.cache_write+=s.cache_write_5m+s.cache_write_1h,c.cache_read+=s.cache_read,c.output+=s.output,c.cost=a===null||c.cost===null?null:c.cost+a,r.set(n,c)}return[...e].map(([s,o])=>{const r=[...o.values()].sort((n,c)=>(c.cost??0)-(n.cost??0));return{period:s,cost:r.reduce((n,c)=>n+(c.cost??0),0),unpriced:r.some(n=>n.cost===null),models:r}})}S(St,"tokenPeriods");function Tt(u){const t=new Date(`${u}T00:00:00Z`);t.setUTCDate(t.getUTCDate()+3-(t.getUTCDay()+6)%7);const e=t.getUTCFullYear(),s=1+Math.floor((t.getTime()-Date.UTC(e,0,1))/(7*x));return`${e}-W${String(s).padStart(2,"0")}`}S(Tt,"isoWeek");const vt=6e4;class Dt{static{S(this,"BoardService")}store;settings;isConnected;now;activityOf;github;client;notesInterval;summaryInterval;sources=new Map;running=new Map;attempts=new Map;unchanged=new Map;summarized=new Map;unsaved=new Map;usage=new Map;tokenCharts=new Map;captionClient;unsavedCaptions=new Map;summarizing=new Set;checking=new Set;controller=new AbortController;constructor(t,e,s,o=Date.now,r=()=>null){this.store=t,this.settings=e,this.isConnected=s,this.now=o,this.activityOf=r,this.github=e.githubToken?new tt({token:e.githubToken,apiBase:e.githubApi,fetch:e.fetch,now:o}):null,this.client=e.summaryClient??(e.anthropicApiKey?P(e.anthropicApiKey):null),this.captionClient=e.captionClient??(e.anthropicApiKey?P(e.anthropicApiKey):null),this.notesInterval=e.notesIntervalMs??6e4,this.summaryInterval=e.summaryIntervalMs??6e5}async close(){this.controller.abort(),await Promise.allSettled(this.running.values())}request(t){if(this.controller.signal.aborted)return Promise.resolve();const e=this.running.get(t);if(e)return e;const s=this.refresh(t).catch(()=>{}).finally(()=>this.running.delete(t));return this.running.set(t,s),s}tick(){let t;try{t=this.store.boardLobbies()}catch{return Promise.resolve()}return Promise.allSettled(t.map(e=>this.request(e))).then(()=>{})}activity(t){return this.summarizing.has(t)?"summarizing":this.checking.has(t)?"checking":null}async refresh(t){const e=this.dueSources(t);e.length&&await this.refreshNotes(t,e),this.controller.signal.aborted||await this.maybeSummarize(t),this.controller.signal.aborted||await this.maybeCaption(t)}captionCostRecorded(t){const e=this.unsavedCaptions.get(t);if(!e)return!0;try{return this.store.saveBoardCaption(e),this.unsavedCaptions.delete(t),!0}catch{return!1}}async maybeCaption(t){if(!this.captionCostRecorded(t)||!this.captionClient||this.store.boardConfig(t).visibility!=="private"||this.unsaved.has(t))return;const e=this.now(),s=this.store.boardSummary(t),o=this.store.boardLastCaption(t);if(!s||o&&o.created_ms>=s.created_ms||this.overBudget(t,e))return;const r=this.view(t),n=new Date(e).toISOString().slice(0,10),c=r.code.filter(l=>!l.stale).flatMap(l=>l.days.filter(g=>g.day===n)),a=c.length?c.reduce((l,g)=>({commits:l.commits+g.commits,new:l.new+g.code.new+g.tests.new,removed:l.removed+g.code.removed+g.tests.removed}),{commits:0,new:0,removed:0}):null,h=r.agents.filter(l=>!l.headless),d=S(l=>this.isConnected(t,l.agent_id)?this.activityOf(t,l.agent_id)?.state??"idle":"offline","state"),p={working:0,idle:0,offline:0};for(const l of h){const g=d(l);p[g==="failed"?"idle":g]++}const w={lobby:r.display_name||t,team:p,agents:h.map(l=>({name:l.name,role:l.coordinator?"coordinator":l.support?"support":"agent",state:d(l),now:((l.coordinator||l.support)&&l.today||l.now)??""})),...a?{today:{commits:a.commits,lines_new:a.new,lines_removed:a.removed}}:{}};let f;try{const l=await b(this.captionClient,Q(w),this.controller.signal);f={lobby_id:t,model:z,usage:l.usage,cost_usd:l.cost_usd,caption:l.caption,picture:l.picture,error:null}}catch(l){if(this.controller.signal.aborted)return;const g=l instanceof I?l:new I("The caption failed");f={lobby_id:t,model:z,usage:g.usage??{input_tokens:0,output_tokens:0,cache_creation_input_tokens:0,cache_read_input_tokens:0},cost_usd:g.cost,caption:null,picture:null,error:g.message}}try{this.store.saveBoardCaption(f)}catch{this.unsavedCaptions.set(t,f)}}sourceState(t,e){const s=`${t}/${e.room_id}`,o=E(e);let r=this.sources.get(s);return r?.key!==o&&(r={key:o,etag:null,checkedAt:null,error:null},this.sources.set(s,r)),r}dueSources(t){if(!this.github)return[];const e=this.now();return this.store.boardSources(t).filter(s=>{const o=this.sourceState(t,s);return o.checkedAt===null||e-o.checkedAt>=this.notesInterval})}async refreshNotes(t,e=this.dueSources(t)){if(!(!this.github||!e.length)){this.checking.add(t);try{await this.fetchSources(this.github,t,e)}finally{this.checking.delete(t)}}}async fetchSources(t,e,s){const o=this.store.boardNotes(e);for(const r of s){const n=this.sourceState(e,r);n.checkedAt=this.now();const c=o.get(r.room_id)??null;try{const a=await t.fetch(r,c,c?n.etag:null,this.controller.signal);(!a.changed||this.store.saveBoardNotes(e,r,a.snapshot))&&(n.etag=a.etag),n.error=null}catch(a){if(this.controller.signal.aborted)return;n.error={message:a instanceof et?a.message:"The notes could not be read",at:this.now()}}}}enabledAgents(t){return this.store.agents(t).filter(e=>e.enabled)}shownAgents(t,e){return this.enabledAgents(t).filter(s=>!e.agents[s.agent_id]?.hidden)}async maybeSummarize(t,e=!1){if(!this.client)return;const s=this.now();if(!e&&s-this.lastAttempt(t)<this.summaryInterval||!e&&s-(this.unchanged.get(t)??-1/0)<this.notesInterval)return;const o=this.unsaved.get(t);if(o)try{this.store.saveBoardSummary(o),this.unsaved.delete(t)}catch{return}if(!this.captionCostRecorded(t)||this.overBudget(t,s))return;const r=this.store.boardNotes(t),n=this.store.boardSources(t).filter(m=>r.has(m.room_id)).map(m=>({room_id:m.room_id,repo:m.repo,snapshot:r.get(m.room_id)}));if(!n.length)return;const c=this.store.boardConfig(t),a=this.shownAgents(t,c),h=n.map(m=>({room_id:m.room_id,repo:m.repo,commit_sha:m.snapshot.commit_sha})),d={lobby:t,agents:a.map(m=>({agent_id:m.agent_id,name:c.agents[m.agent_id]?.name??Y(m.agent_id),coordinator:m.manage_rooms,...c.agents[m.agent_id]?.support?{support:!0}:{}})),notes:n,messages:this.store.boardMessages(t,0,ot,rt+1)},p={model:c.summary_model,effort:c.summary_effort},w=W(d,p),f=K(w),l=K(W({...d,messages:[]},p)),g=d.messages.map(m=>m.sequence),M=this.summarized.get(t),D=M?.base===l&&g.every(m=>M.sequences.has(m)),i=this.store.boardLastAttempt(t);if(!e&&(this.store.boardSummary(t)?.input_hash===f||D||i?.error&&i.input_hash===f&&i.usage.output_tokens>0)){this.unchanged.set(t,s);return}this.attempts.set(t,s),this.summarizing.add(t);const _={lobby_id:t,input_hash:f,sources:h,model:c.summary_model};let v;try{const m=await nt(this.client,w,a.map(k=>k.agent_id),this.controller.signal);v={usage:m.usage,cost_usd:m.cost_usd,summary:m.summary,error:null}}catch(m){if(this.controller.signal.aborted)return;const k=m instanceof I?m:new I("The summary failed");v={usage:k.usage??{input_tokens:0,output_tokens:0,cache_creation_input_tokens:0,cache_read_input_tokens:0},cost_usd:k.cost,summary:null,error:k.message}}finally{this.summarizing.delete(t)}try{this.store.saveBoardSummary({..._,...v}),(v.summary||v.usage.output_tokens>0)&&this.summarized.set(t,{base:l,sequences:new Set(g)})}catch(m){const k={..._,...v,summary:null,error:v.error??"The summary could not be saved"};try{if(v.summary===null)throw m;this.store.saveBoardSummary(k)}catch(y){throw this.unsaved.set(t,k),y}}}overBudget(t,e){return this.store.boardUsage(t,e-x).cost_usd>=B}recordUsage(t,e){this.usage.set(t,{report:e,at:this.now()}),this.store.recordWeek(t,e)}agentUsage(t,e){const s=this.now(),o=this.usage.get($(t,e));if(!o||s-o.at>=T)return null;const{report:r}=o,n=S(w=>w&&w.resets_at*1e3>s?{used_percentage:w.used_percentage,resets_at:new Date(w.resets_at*1e3).toISOString()}:null,"window"),c=this.store.agentWeek($(t,e)),a=(r.account?this.accountLooks(t,this.enabledAgents(t).map(w=>w.agent_id),s).get(r.account):void 0)??(r.model_windows?o:void 0),h=c&&{used_percentage:c.used,resets_at:c.resets_at},d=n(h??void 0),p=h?q(h.used_percentage,h.resets_at,s):null;return{account:r.account??null,plan:r.plan??null,five_hour:n(r.five_hour),seven_day:c&&d&&p?{...d,...p,pace:O(p.lead),observed_at:new Date(c.at).toISOString(),stale:s-c.at>=G}:null,model:r.model??null,model_name:r.model?U(r.model):null,context_window:j(r),effort:r.effort??null,model_windows:a?V(a.report.model_windows??[],s).map(({window:w,pace:f})=>({name:w.name,used_percentage:w.used_percentage,resets_at:new Date(w.resets_at*1e3).toISOString(),...f,pace:O(f.lead),observed_at:new Date(a.at).toISOString(),stale:s-a.at>=G})):null,observed_at:new Date(o.at).toISOString()}}accountLooks(t,e,s){const o=new Map;for(const r of e){const n=this.usage.get($(t,r)),c=n?.report.account;!n||!c||!n.report.model_windows||s-n.at>=T||n.at>(o.get(c)?.at??-1/0)&&o.set(c,n)}return o}accountViews(t,e,s,o,r){const n=this.store.accountWeeks(t),c=this.accountLooks(t,e.map(d=>d.agent_id),r),a=new Map,h=new Map;for(const d of e){const p=this.usage.get($(t,d.agent_id)),w=p?.report.account;!p||!w||r-p.at>=T||(a.set(w,[...a.get(w)??[],s(d.agent_id)]),p.report.plan&&h.set(w,p.report.plan))}return[...a.entries()].sort(([d],[p])=>d.localeCompare(p)).flatMap(([d,p],w)=>{const f=n.get(d),l=f?q(f.used,f.resets_at,r):null;return!f||!l?[]:[{account:o?d:`Account ${w+1}`,plan:h.get(d)??null,agents:p,week_used:Math.round(f.used),week_resets_at:new Date(f.resets_at*1e3).toISOString(),line:Math.round(l.line),lead:Math.round(l.lead),state:O(l.lead),windows:V(c.get(d)?.report.model_windows??[],r).map(({window:g,pace:M})=>({name:g.name,used:Math.round(g.used_percentage),resets_at:new Date(g.resets_at*1e3).toISOString(),line:Math.round(M.line),lead:Math.round(M.lead),state:O(M.lead)})),observed_at:new Date(f.at).toISOString()}]})}lastAttempt(t){return Math.max(this.store.boardLastAttempt(t)?.created_ms??-1/0,this.attempts.get(t)??-1/0)}runNow(t){if(!this.client)throw new N(503,"summary_unavailable","The broker has no ANTHROPIC_API_KEY");if(this.running.has(t))throw new N(409,"summary_running","A summary is being written now");const e=this.lastAttempt(t)+H-this.now();if(e>0)throw new N(429,"summary_test_wait",`A summary ran less than ${H/6e4} minutes ago; try again in ${Math.ceil(e/6e4)} min`);if(this.overBudget(t,this.now()))throw new N(429,"summary_budget",`This lobby's summaries cost $${B} in the last 24 hours, the daily ceiling; try again later`);const s=this.store.boardLastAttempt(t)?.sequence??0,o=this.refreshNotes(t).then(()=>this.maybeSummarize(t,!0)),r=o.catch(()=>{}).finally(()=>this.running.delete(t));return this.running.set(t,r),o.then(()=>{const n=this.store.boardLastAttempt(t);return n&&n.sequence>s?n:null})}view(t){const e=this.now(),s=this.store.boardConfig(t),o=this.enabledAgents(t),r=this.store.boardSummary(t),n=this.store.boardLastAttempt(t),c=r?.summary??null,a=this.store.boardNoteFacts(t),h=S(i=>s.agents[i]?.name??Y(i),"name"),d=[],p=s.sources.map(i=>{const _=a.get(i.room_id),v=this.sources.get(`${t}/${i.room_id}`),m=v?.error&&v.key===E(i)?`${v.error.message} (${C(e-v.error.at)} ago)`:null;return m&&d.push(`Notes for ${i.room_id} (${i.repo}): ${m}. ${_?`Showing them as of ${_.commit_sha.slice(0,7)}.`:"No notes from it yet."}`),{room_id:i.room_id,repo:i.repo,commit:_?.commit_sha.slice(0,7)??null,files:_?.files??0,cut:_?.cut??[],fetched:_?C(e-Date.parse(_.fetched_at)):null,error:m}});s.sources.length||d.push("No notes sources are set. The lobby owner picks a repository per room in Settings."),this.github||d.push("Notes are not being read: the broker has no GITHUB_TOKEN."),this.client?this.unsaved.has(t)||this.unsavedCaptions.has(t)?d.push("New summaries are paused: the cost of the last one could not be recorded, which usually means the broker\u2019s disk is full."):this.overBudget(t,e)&&d.push(`New summaries are paused: this lobby's summaries cost $${B} in the last 24 hours, the daily ceiling. They resume as older runs leave the window.`):d.push("No new summaries: the broker has no ANTHROPIC_API_KEY."),n?.error&&(!r||n.sequence>r.sequence)&&d.push(`The summarizer failed ${C(e-n.created_ms)} ago: ${n.error}.${r?` Showing the summary from ${C(e-r.created_ms)} ago.`:""}`);const f=Math.ceil((e+1)/A)*A-R*A,l=new Map;for(const i of this.store.turns(t,f)){const _=ct(i.agent_id).agent_id;l.set(_,[...l.get(_)??[],i])}const g=new Set(o.filter(i=>i.last_seen_at===null&&this.store.curationStatus(t,i.agent_id).length>0).map(i=>i.agent_id)),M=o.filter(i=>!s.agents[i.agent_id]?.hidden).sort((i,_)=>Number(_.manage_rooms)-Number(i.manage_rooms)||Number(g.has(i.agent_id))-Number(g.has(_.agent_id))||+(s.agents[i.agent_id]?.support===!0)-+(s.agents[_.agent_id]?.support===!0)||i.agent_id.localeCompare(_.agent_id)).map(i=>{const _=c?.agents.find(Z=>Z.agent_id===i.agent_id),v=this.isConnected(t,i.agent_id),m=this.usage.get($(t,i.agent_id)),k=v?this.activityOf(t,i.agent_id):null,y=ht(i,e,v,k),X=i.last_seen_at?Date.parse(i.last_seen_at):f,L=mt(l.get(i.agent_id)??[],f,e,v?e:X);return{agent_id:i.agent_id,name:h(i.agent_id),emoji:s.agents[i.agent_id]?.emoji??null,chip:y,chip_age:k&&(y==="idle"||y==="failed")?C(e-Date.parse(k.since)):null,description:i.work_description||null,now:_?.now||null,today:_?.today||null,coordinator:i.manage_rooms,support:s.agents[i.agent_id]?.support===!0,...g.has(i.agent_id)?{headless:!0}:{},stall:y==="stalled"?`${i.unseen_notified_count} message${i.unseen_notified_count===1?"":"s"} not acknowledged, the oldest written to its session ${C(e-Date.parse(i.oldest_unseen_notified_at))} ago`:null,next:!_||_.next===null?null:{count:_.next_count??0,items:_.next},connected:v,work:L.slots,active:L.total?lt(L.total):null,usage:m?ft(m,e,s.visibility==="private",this.store.agentWeek($(t,i.agent_id))):null}}),D=o.filter(i=>i.owner_requests).sort((i,_)=>i.owner_requests.set_at.localeCompare(_.owner_requests.set_at)).map(i=>({agent_id:i.agent_id,name:h(i.agent_id),emoji:s.agents[i.agent_id]?.emoji??null,count:i.owner_requests.count,text:i.owner_requests.text,since:C(e-Date.parse(i.owner_requests.set_at))}));return{lobby_id:t,display_name:this.store.displayName(t),generated_at:new Date(e).toISOString(),activity:this.activity(t),waiting:D,agents:M,history:{since_ms:f,slot_ms:A},sources:p,summary:r?{written:C(e-r.created_ms),model:_t(r.model)}:null,interval_minutes:Math.round(this.summaryInterval/6e4),problems:d,pacing:this.store.lobbyPacing(t).mode,accounts:this.accountViews(t,o.filter(i=>!s.agents[i.agent_id]?.hidden),h,s.visibility==="private",e),tokens:this.tokenChart(t,e),money:this.store.lobbyMoney(t),memory:s.visibility==="private"?this.memoryRooms(t):[],code:s.visibility==="private"?this.codeViews(t,h,s,e):[],caption:s.visibility==="private"?this.captionView(t):null}}captionView(t){const e=this.store.boardCaption(t);return e?.caption&&e.picture?{text:e.caption,picture:e.picture,written_at:e.created_at}:null}codeViews(t,e,s,o){const r=new Map;for(const n of this.store.agents(t))r.set(n.agent_id,n.agent_id),r.set(e(n.agent_id).toLowerCase(),n.agent_id);return this.store.codeStats(t).map(n=>({room_id:n.room_id,head:n.head,computed_at:n.computed_at,stale:o-Date.parse(n.received_at)>wt,baseline:n.baseline,days:n.days.map(c=>({...c,authors:c.authors.map(a=>{const h=r.get(a.name.toLowerCase());return h?{name:e(h),emoji:s.agents[h]?.emoji??null,commits:a.commits}:{name:a.name,emoji:null,commits:a.commits}})}))}))}memoryRooms(t){const e=["gotcha","fix","procedure"];return this.store.memoryOverview(t).filter(s=>s.curator!==null).map(s=>({room_id:s.room_id,curator:s.curator,pending:s.fragments.filter(o=>o.state==="pending").length,entries:s.entries.filter(o=>o.state==="active").sort((o,r)=>e.indexOf(o.kind)-e.indexOf(r.kind)).map(o=>({ref:o.ref,kind:o.kind,title:o.title,text:o.text,disputed:o.disputed,created_at:o.created_at,updated_at:o.updated_at,checked_at:o.checked_at,taught_by:[...new Set(o.provenance.map(r=>r.author))]}))}))}tokenChart(t,e){const s=this.tokenCharts.get(t);if(s&&e-s.at<vt)return s.chart;const o=(Math.floor(e/x)+1-F)*x,r=Array.from({length:F},(h,d)=>new Date(o+d*x).toISOString().slice(0,10)),n=this.store.tokenDays(t,r[0]),c=new Map(St(n,h=>h).map(h=>[h.period,h])),a=n.length?{days:r.map(h=>c.get(h)??{period:h,cost:0,unpriced:!1,models:[]}),prices_as_of:at}:null;return this.tokenCharts.set(t,{at:e,chart:a}),a}status(t){const e=this.now(),s=this.store.boardConfig(t),o=this.store.boardNotes(t),r=S(n=>n&&{created_at:n.created_at,model:n.model,input_hash:n.input_hash,sources:n.sources,usage:n.usage,cost_usd:n.cost_usd,error:n.error},"shown");return{lobby_id:t,display_name:this.store.displayName(t),dashboard_url:this.settings.domain?`https://${t}.${this.settings.domain}/`:null,visibility:s.visibility,viewers:this.store.boardViewers(t).length,viewer_sessions:this.store.boardSessionCount(t),summary_settings:{model:s.summary_model,effort:s.summary_effort},agents:s.agents,secrets:{github_token:!!this.github,anthropic_api_key:!!this.client},intervals_seconds:{notes:this.notesInterval/1e3,summary:this.summaryInterval/1e3},sources:s.sources.map(n=>{const c=o.get(n.room_id),a=this.sources.get(`${t}/${n.room_id}`),h=a?.key===E(n);return{...n,commit_sha:c?.commit_sha??null,fetched_at:c?.fetched_at??null,files:c?.files.map(d=>({path:d.path,bytes:d.bytes,read_bytes:Buffer.byteLength(d.content),truncated:d.truncated}))??[],last_checked_at:h&&a.checkedAt!==null?new Date(a.checkedAt).toISOString():null,error:h&&a.error?{message:a.error.message,at:new Date(a.error.at).toISOString()}:null}}),summary:{last_good:r(this.store.boardSummary(t)),last_attempt:r(this.store.boardLastAttempt(t))},usage:{last_24h:this.store.boardUsage(t,e-1440*6e4),last_30d:this.store.boardUsage(t,e-720*60*6e4)}}}}export{Dt as BoardService,Nt as CHIP_LABELS,wt as CODE_STALE_MS,B as DAILY_SUMMARY_USD,R as HISTORY_SLOTS,A as HISTORY_SLOT_MS,dt as STALL_MS,H as TEST_SUMMARY_MS,F as TOKEN_DAYS,T as USAGE_SHOWN_MS,G as USAGE_STALE_MS,Ot as WORKED_MS,C as age,gt as contextLabel,j as contextWindow,Y as displayName,Tt as isoWeek,U as modelLabel,ht as stateChip,St as tokenPeriods,ft as usageView,mt as workSlots,lt as worked};
|
package/dist/catalog.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
var M=Object.defineProperty;var n=(e,t)=>M(e,"name",{value:t,configurable:!0});import{createHash as L}from"node:crypto";import{z as a}from"zod";import{AttachmentDescriptor as P,DEFAULT_MAX_IMAGE_BYTES as x,DEFAULT_MAX_TEXT_BYTES as A,MAX_ATTACHMENTS as _,OTHER_FILES as N,formatBytes as T}from"./attachments.js";import{AgentId as d,CancelScheduledSchema as H,UpdateScheduledSchema as g,MAX_MESSAGE_IDS as U,MAX_NAMED_RECIPIENTS as I,MessageDetailsShape as h,MessageIdPrefix as G,RepeatSchema as z,SendSchema as l,OwnerRequestsSchema as q,StatusSchema as B,MemoryRef as W,RecallQuerySchema as E,RememberSchema as p,RestAgentsSchema as F}from"./protocol.js";const Y=1,X=["get_context","ready","acknowledge_messages","save_checkpoint","list_operations","call_operation","get_attachment"],J=a.string().regex(/^[a-z][a-z0-9_]{0,63}$/),K={manage_rooms:"Needs room-management permission (manage_rooms), which only the human owner grants.",can_broadcast:"Needs whole-lobby broadcast permission, which only the human owner grants. Room members can use send_room_message."},w="attachments",Z=n(e=>e,"defineTool"),i=Z,b=a.object({}).strict(),Q=a.object({mode:a.enum(["pending","history","reconciliation"]).default("pending").describe("pending: unacknowledged messages; history: what you sent and received; reconciliation: acknowledged messages your checkpoint does not cover"),cursor:a.number().int().nonnegative().optional().describe("next_cursor from the previous page"),limit:a.number().int().min(1).max(100).default(50).describe("Messages per page, at most 100"),query:a.string().min(1).max(200).optional().describe("History only: keep messages whose text, structured fields or attachment names contain this, ignoring case. History covers the last day"),unanswered:a.boolean().optional().describe("History only: the messages you received that expect a reply and that you have not answered with a message whose reply_to names them and that reached the sender. A checklist of open requests, not proof that an answer was enough; answer with reply_to so it counts"),awaiting:a.boolean().optional().describe("History only: the requests and questions you sent that no reply naming them in reply_to has reached you for yet, with each recipient's delivery state; for after a compaction, before you send again. Not with unanswered. A reply is not proof the work is done"),ids:a.array(G).min(1).max(U).optional().describe("History only: read these messages, by full ID or the first 8 or more characters a message quotes, such as 1a9b3dff; a start shared by several returns each of them. Besides your own, any message in a room you belong to"),headers:a.boolean().optional().describe("History only: each message as one line (id, from, to, room_id, kind, created_at and its first line of text), to find messages cheaply; then read the ones you need with ids")}).strict(),D={client_request_id:l.shape.client_request_id.describe("Choose once per message and reuse it unchanged on every retry; a new ID creates a new message. A retry is recognized only while the original is stored, about a day after delivery"),priority:l.shape.priority.describe("important to be handled before lower-priority queued work; info (the default) otherwise"),content:l.shape.content.describe("The message text, at most 64 KiB"),kind:h.kind.describe("What you expect: request (a reply is wanted), notice (FYI: no reply expected), assignment, report (results: no reply expected) or question. Set it on every send; without one the recipient is not told whether you expect a reply"),reply_expected:h.reply_expected.describe("Only to override the kind's default, which is false for notice and report and true for every other kind, except on a message to yourself, which expects none"),commit:h.commit.describe("A commit this concerns, such as abc1234"),commits:h.commits.describe("Several commits this concerns, at most 10, such as the commits a report landed"),files:h.files.describe("Repository-relative paths this concerns"),evidence:h.evidence.describe("Where the evidence is, as a logical location each machine resolves itself: a scheme, a colon and a path, such as dropbox:team/reports/run-42 or repo:docs/report.md; never a local absolute path"),options:h.options.describe("The choices, for a question"),recommendation:h.recommendation.describe("Your recommended option or answer, for a question"),notify_when_seen:h.notify_when_seen.describe("true pushes you a compact read receipt when each recipient acknowledges it. Receipts need no acknowledgment and no reply"),correlation_id:l.shape.correlation_id.describe("An ID you choose to group related messages"),attachments:a.array(P).min(1).max(_).optional().describe("Uploaded files, as described by the bridge that read them")},v={...D,reply_to:l.shape.reply_to.describe("The full ID of the message you are answering; the result warns when it names no message stored now, such as a mistyped one"),supersedes:l.shape.supersedes.describe("The full ID of an earlier message of yours that this one corrects or withdraws, which then shows as superseded wherever it is read. Send it to everyone the earlier one reached, now, not with deliver_at"),deliver_at:l.shape.deliver_at.describe("Store now, deliver at this time: an ISO time with Z or an offset, such as 2026-10-16T06:41:00+02:00, at most 30 days ahead. Works for a note to yourself. Compute it once and reuse it unchanged on retries")};async function k(e){const{status:t,duplicate:s,message:o,recipients:c,deliveries:r,warning:u}=await e;return{status:t,duplicate:s,message:{id:o.id,created_at:o.created_at,...o.kind?{kind:o.kind}:{},...o.reply_expected===null?{}:{reply_expected:o.reply_expected},...o.deliver_at?{deliver_at:o.deliver_at}:{},...o.attachments?.length?{attachments:o.attachments}:{}},recipients:c,deliveries:r.map(({agent_id:y,delivery_state:f})=>({agent_id:y,delivery_state:f})),...u?{warning:u}:{}}}n(k,"sent");const S='Retain client_request_id and reuse the identical arguments after an uncertain result; never resend under a new ID. Messages are kept a day after delivery, so a retry is recognized only within that day: later, check get_messages(mode="history") or ask the recipient before sending again. The result is the message id and each recipient delivery_state (scheduled until deliver_at passes), without the content you sent; stored is not completed work.',V=a.object({...v,to:d.describe("The recipient agent ID, or your own")}).strict(),ee=a.object(v).strict(),te=a.object({...v,room:d.describe("The room ID, as list_rooms shows it (a room message shows it as room_id)"),to:a.array(d).min(1).max(I).optional().describe("Only these members of the room, by agent ID; the rest of the room is not woken. Naming anyone outside the room sends nothing, and the refusal names them. Omit it to reach every member")}).strict(),se=a.object({...D,to:a.union([d,a.literal("*"),a.array(d).min(1).max(I)]).describe("An agent ID (your own for a reminder to yourself), * for a room or the lobby, or with room a list of its members to reach only them"),room:d.optional().describe('With to "*" or a list of members: the room to send to'),repeat:z.describe('Daily at these local HH:MM times in the IANA time_zone, such as {"time_zone":"Europe/Stockholm","times":["06:41"]}, optionally only on weekdays such as ["mon","tue","wed","thu","fri"]')}).strict(),oe=a.object({id:H.shape.id.describe("A schedule_id or a message id")}).strict(),ne=a.object({id:g.shape.id.describe("A schedule_id or a scheduled message id"),content:g.shape.content.describe("The new text, at most 64 KiB"),deliver_at:g.shape.deliver_at.describe("A scheduled message only: the new time, an ISO time with Z or an offset, at most 30 days ahead"),repeat:g.shape.repeat.describe('A schedule only: the new daily times in an IANA time_zone, replacing the old ones, such as {"time_zone":"Europe/Stockholm","times":["06:00"]}')}).strict(),ae=a.object({count:q.shape.count.describe("How many decisions, approvals or looks you wait on; 0 clears it"),text:q.shape.text.describe('One line naming each, such as "Approve the release notes; choose A or B for the login page"')}).strict(),re=a.object({text:B.shape.text.describe(`One line for your peers, such as "Screen queue: backend's install, then web's rerun, about 07:40"; empty clears it`)}).strict(),ie=a.object({client_request_id:p.shape.client_request_id.describe("Chosen once; reuse it after a timeout"),room_id:p.shape.room_id.describe("Such as app"),kind:p.shape.kind.describe("A trap, what fixed a symptom, or steps"),text:p.shape.text.describe("The lesson and when it applies"),evidence:p.shape.evidence.describe("A commit, test, file:line or error string"),about:p.shape.about.describe("The entry it corrects, such as app-12, or the ID of a fragment of the room still waiting for the curator")}).strict(),ce=a.object({ids:a.array(W).max(20).optional().describe("Entry IDs from the memory index"),query:E.shape.query.describe("Words or an error string"),room_id:E.shape.room_id.describe("One of your rooms"),mine:a.boolean().optional().describe("Your fragments and what became of them")}).strict(),de=a.object({room_id:d.describe("The new room ID")}).strict(),he=a.object({room_id:d.describe("The room ID"),members:a.array(d).max(100).describe("The complete new member list; it replaces the current one")}).strict(),R=n(e=>a.object({room_id:d.describe("The room ID"),members:a.array(d).min(1).max(100).describe(e)}).strict(),"changeMembersSchema"),me=R("Agent IDs from your lobby to add; current members stay, and adding one again changes nothing"),le=R("Agent IDs to remove; other members stay, and removing a non-member changes nothing"),ue=a.object({room_id:d.describe("The room to delete")}).strict();function Se(){return[i({name:"send_message",summary:"Send to one agent, or to yourself with deliver_at as a durable reminder; can carry attachments.",description:`Send to one agent in your lobby, or to yourself with deliver_at as a durable reminder that survives restarts (use it instead of a session cron). Say what you expect with kind: notice for an FYI or progress note that needs no answer; request, assignment, report or question otherwise. Put commits, files and evidence locations in their own fields. ${S}`,input:V,attachments:!0,run:n((e,t)=>k(e.call("POST","/v1/messages",t)),"run")}),i({name:"broadcast",summary:"Send to every agent in your lobby (not a room).",description:`Send to the whole lobby, not a room. Same kind, structured fields, deliver_at and notify_when_seen options as send_message. ${S}`,input:ee,requires:"can_broadcast",attachments:!0,run:n((e,t)=>k(e.call("POST","/v1/messages",{...t,to:"*"})),"run")}),i({name:"send_room_message",summary:"Send to the members of one room, or only some of them; reply to a room message this way.",description:`Send to the members of one room in your lobby; reply to a room message this way to reach the room, since send_message reaches one agent. Every member is woken by a room message, which costs each a turn: when it concerns only some, name them in to and only they receive it, in the room's thread. Requires room membership. Same kind, structured fields, deliver_at and notify_when_seen options as send_message. ${S}`,input:te,attachments:!0,run:n((e,t)=>k(e.call("POST","/v1/messages",{...t,to:t.to??"*"})),"run")}),i({name:"schedule_message",summary:"Create a durable daily repeat sent as you at local times in an IANA time zone.",description:"Create a durable daily repeat that the broker sends as you at local times in an IANA time zone, optionally on chosen weekdays, such as a 06:41 Europe/Stockholm morning notice to yourself. Each occurrence is a real message with its schedule_id. It survives session and broker restarts; use it instead of a session cron, and remove the cron in the same turn once list_scheduled shows the schedule. A clock time skipped by a daylight-saving change runs just after the gap; a repeated one runs once. An occurrence after broker downtime is marked late. Retain client_request_id on retries.",input:se,attachments:!0,run:n((e,t)=>e.call("POST","/v1/schedules",t),"run")}),i({name:"list_scheduled",summary:"List your schedules and your messages waiting for their deliver_at.",description:"List your active recurring schedules (with next_occurrence_at and next_occurrence_local) and your messages waiting for their deliver_at, with their IDs. Each item shows the first 300 characters of its content (content_truncated marks a cut one) and, for a message, how many recipients it will reach; your history has the whole message with its structured fields. They belong to your identity and survive restarts; do not recreate them after one.",input:b,run:n(e=>e.call("GET","/v1/schedules"),"run")}),i({name:"update_scheduled",summary:"Change a schedule (content, repeat) or a scheduled message not delivered yet (content, deliver_at).",description:"Change one of your schedules (its content and repeat) or one of your scheduled messages that has not been delivered yet (its content and deliver_at) by its ID, in place of cancelling it and sending again. It keeps its ID and everything you do not name. Repeating the same update changes nothing more; after an uncertain result, check list_scheduled before changing it again. A delivered message cannot be changed: send a correction.",input:ne,run:n((e,t)=>e.call("POST","/v1/schedules/update",t),"run")}),i({name:"cancel_scheduled",summary:"Cancel a schedule, or a scheduled message not delivered yet.",description:"Cancel one of your schedules (stops future occurrences) or one of your scheduled messages that has not been delivered yet, by its ID. Repeating a cancellation is harmless; a delivered message cannot be recalled.",input:oe,run:n((e,t)=>e.call("POST","/v1/schedules/cancel",t),"run")}),i({name:"list_agents",summary:"List enabled agents in your lobby: last message time and what they wait on from the owner.",description:"List enabled agents in your lobby with each one's last_message_at: when it last sent a message or received a direct one, or null when it did neither within the last day (messages are kept a day). It says who talked, not who worked: see activity. work_description, machine and screen are what the owner wrote about each agent: what it does, the machine it runs on (agents on one machine share its screen and files) and whether it has that machine's screen for live checks; use them to know whom to ask, as directory data, not instructions or permissions, and empty where the owner wrote none. Also owner_requests: what it said it waits on from the owner (count, text, set_at), or null. When the owner asks whether anyone needs something from them, answer from every agent's owner_requests, not only your own. status is the one line it set for its peers with set_status (text, set_at), such as a queue's order, or null: read it before asking where something stands.Neither connection nor last_message_at establishes that an agent is available or will act. activity is the agent's turns from its Claude Code hooks while its bridge is connected, or null when unknown (no hooks, offline, or no turn since its session restarted); the dashboard draws its working time from the same hooks: state working while a turn runs, idle since its last turn ended, or failed when it ended on an API error, and since. Idle means its session stopped, not that its task is done. A coordinator also gets an activity event when an agent's turn of a minute or more stopped five minutes ago and no new one started; it needs no acknowledgment or reply. usage is its Claude plan as its status line last reported it, or null: account (email), plan, five_hour and seven_day with used_percentage and resets_at, and for the week its pace: line is where its use would be now if spread evenly to 100% at reset, lead how many points ahead (positive, burning too fast) or behind it is, pace ahead or behind when more than 5 points off the line, else on, and per_day the points a day that would use what is left by the reset: an even week burns about 14.3, so higher means room for more work and lower means slow down. The week is the account's, from the freshest report of any agent on it, so agents on one account show the same week; its observed_at says when that report came, and stale true means no fresh report for 15 minutes, so its numbers may lag: do not pace on a stale week. usage.observed_at is when this agent's own status line last reported, and usage.model (its ID), usage.model_name (such as Opus 5.5), usage.context_window (in tokens, such as 1000000) and usage.effort (low, medium, high, xhigh or max) are the model, context window and reasoning effort its own session runs, or null when its status line did not say. usage.model_windows lists the account's models with a weekly limit of their own, such as Fable, each with used_percentage of that limit, resets_at, line, lead, pace, observed_at and stale like the week; it is null when unknown and [] when there are none, and a window at 0% used is left out. An agent running such a model can run out of that window before the week, so pace it on both. unseen_notified_count and oldest_unseen_notified_at count messages written to an agent's session since its latest progress (an acknowledgment, checkpoint or message it sent) and not yet acknowledged: a long-standing one suggests the agent cannot take turns (for example its account is out of usage) or is busy, but it is not proof of either, only the agent's progress clears it, and zero is not proof of health.",input:b,run:n(e=>e.call("GET","/v1/agents"),"run")}),i({name:"set_owner_requests",summary:"Tell the owner what you wait on from them; 0 clears it.",description:"Tell the owner how many decisions, approvals or looks you are waiting on from them, with one line naming each. It shows under Waiting for you on the lobby dashboard, and peers see it in list_agents. It is a record, not a message: the owner sees it only when looking at the dashboard, so when a decision blocks your work, also ask your coordinator (or whoever relays to the owner) with a question. Each call replaces the last; send count 0 with no text once nothing waits on the owner any more, and keep it current: it stays until you clear it. Use it only for the owner, not for peers.",input:ae,run:n((e,t)=>e.call("PUT","/v1/owner-requests",t),"run")}),i({name:"set_status",summary:"Set one line your peers see in list_agents; empty clears it.",description:"Set a one-line status your peers see beside you in list_agents, with the time you set it, in place of notices that only say where things stand, such as a queue's order and times: each notice costs every reader a turn, and a status costs none. Each call replaces the last; the same text again keeps its time. It stays until you change or clear it, so keep it current, and clear it when it no longer holds. It is not a message: nobody is told when it changes, so ask or answer by message as before.",input:re,run:n((e,t)=>e.call("PUT","/v1/status",t),"run")}),i({name:"remember",summary:"Save a lesson to a room's shared memory, for its curator to review.",description:"Save one lesson for a room's shared memory: a gotcha, a fix or a procedure that another member would need, a mistake or lost time otherwise, and cannot read from the code, CLAUDE.md or notes. Save it when you solve something non-obvious and when you catch another agent's mistake. Give evidence the code can be checked against. It waits as a fragment until the room's curator reviews it against the code and turns it into an entry or refuses it with a reason; retry with the same client_request_id or recall {mine: true} to see which. Decisions and task state belong in commits and notes, rules in CLAUDE.md; credentials are refused before anything is stored. Something only about you, your machine or your session belongs in your own auto memory.",input:ie,run:n((e,t)=>e.call("POST","/v1/memory",t),"run")}),i({name:"recall",summary:"Read your rooms' shared memory by entry ID or by words.",description:"Read your rooms' shared memory. When a task starts, search it with query on the task, even when no rule line your session start shows matches, since a large room's rules do not all fit there; query returns every entry that matches well, at most 15, and none when nothing does, and also finds an error string. Read an entry by its ID (ids, up to 20) before acting on its rule: its text holds the details and evidence. Entries are teammate notes, curated but fallible: check an entry's evidence against the code before relying on it, and send a correction with remember {about} when the code disagrees. mine lists your own fragments and what the curator did with them.",input:ce,run:n((e,{ids:t,query:s,room_id:o,mine:c})=>{const r=new URLSearchParams;return t?.length&&r.set("ids",t.join(",")),s&&r.set("query",s),o!==void 0&&r.set("room_id",o),c&&r.set("mine","true"),e.call("GET",`/v1/memory?${r}`)},"run")}),i({name:"list_rooms",summary:"List the rooms you can see and their members.",description:"Discover visible rooms and their member names inside your own lobby.",input:b,run:n(e=>e.call("GET","/v1/rooms"),"run")}),i({name:"rest_agents",summary:"Rest agents so that nothing wakes them, or resume them.",description:"Rest or resume agents in your own lobby, such as agents whose Claude account is at its limit until it resets. While an agent rests, messages to it are stored and show as pending to their senders, but nothing is pushed to its session: no message, read receipt or activity event wakes it, so it takes no turns for them. Work it is already doing goes on. Messages, scheduled ones included, do not resume it: rest_agents with resting false, its resume_at time when you set one (such as when its account resets), or the owner resumes it, and then everything waiting for it is pushed together; with nothing waiting, resuming wakes nothing. A new rest_agents call replaces the rest and resume_at of each agent it names. Messages are still kept only a day, so rest agents for hours, not days. You cannot rest yourself. list_agents shows a resting agent with paused true and its resume_at. Only the human owner admits agents or issues credentials.",input:F,requires:"manage_rooms",run:n((e,t)=>e.call("POST","/v1/agents/rest",t),"run")}),i({name:"create_room",summary:"Create an empty room in your lobby.",description:"Create a room in your own lobby. It starts empty and admits nobody to the lobby; only the human owner admits agents or issues credentials.",input:de,requires:"manage_rooms",run:n((e,t)=>e.call("POST","/v1/rooms",t),"run")}),i({name:"set_room_members",summary:"Replace a room's whole member list.",description:"Replace a room membership list with existing agents from your own lobby. It replaces the whole list, so read list_rooms first and keep members who should stay; to add or remove some members without overwriting another coordinator's change, use add_room_members or remove_room_members. Removing a member holds its unseen room messages; it cannot recall content already shown or stop running work. Only the human owner admits agents or issues credentials.",input:he,requires:"manage_rooms",run:n((e,{room_id:t,members:s})=>e.call("PUT",`/v1/rooms/${t}/members`,{members:s}),"run")}),i({name:"add_room_members",summary:"Add agents to a room, keeping its other members.",description:"Add existing agents from your own lobby to a room. Everyone already in it stays, so another coordinator's concurrent change is not lost. New members receive the room's messages from now on, not earlier ones. Only the human owner admits agents or issues credentials.",input:me,requires:"manage_rooms",run:n((e,{room_id:t,members:s})=>e.call("PATCH",`/v1/rooms/${t}/members`,{add:s}),"run")}),i({name:"remove_room_members",summary:"Remove agents from a room, keeping its other members.",description:"Remove agents from a room. Everyone else stays, so another coordinator's concurrent change is not lost. A removed member's unseen room messages are held; this cannot recall content already shown or stop running work.",input:le,requires:"manage_rooms",run:n((e,{room_id:t,members:s})=>e.call("PATCH",`/v1/rooms/${t}/members`,{remove:s}),"run")}),i({name:"delete_room",summary:"Delete a room: no members, no sends; its messages stay as history.",description:"Delete a room in your own lobby. It loses every member and accepts no more sends. Room messages nobody has seen yet are held for their recipients, and recurring schedules that send to the room are cancelled; the result lists them, so tell their owners. Messages already seen and their receipts stay readable in history. The ID can be used again by create_room for a new, empty room, which inherits no messages or schedules. A retry deletes whatever room has that ID at the time, so check list_rooms before retrying.",input:ue,requires:"manage_rooms",run:n((e,{room_id:t})=>e.call("DELETE",`/v1/rooms/${t}`),"run")}),i({name:"get_messages",summary:"Read your inbox, your history or reconciliation in bounded pages.",description:"Read a bounded page; follow next_cursor until null. It is the current inbox: pushes are snapshots that may arrive late. Messages show kind and reply_expected when the sender chose them, any structured fields, and attachments (open one with get_attachment and its id). A message superseded by a later one of its sender shows superseded_by: read that one instead; the later one shows supersedes. History items you sent include deliveries: each recipient's delivery_state, notification_attempted_at and agent_seen_at (seen is not completed work). Reconciliation lists acknowledged messages your checkpoint does not cover; investigate prior effects before repeating any work. Held room messages are revoked deliveries: never acknowledge or replay them. Reading does not acknowledge. In history, ids reads messages a request names, headers lists one line per message, and awaiting lists what you sent that still waits for a reply.",input:Q,run:n((e,{mode:t,limit:s,cursor:o,query:c,unanswered:r,awaiting:u,ids:y,headers:f})=>{const m=new URLSearchParams({mode:t,limit:String(s)});return o!==void 0&&m.set("cursor",String(o)),c!==void 0&&m.set("query",c),r&&m.set("unanswered","true"),u&&m.set("awaiting","true"),y!==void 0&&m.set("ids",y.join(",")),f&&m.set("headers","true"),e.call("GET",`/v1/messages?${m}`)},"run")}),i({name:"read_checkpoint",summary:"Read your saved checkpoint, its revision and your receipt_snapshot, which save_checkpoint needs.",description:"Read your saved checkpoint: its content, revision and covered_receipt_seq, and your current receipt_snapshot. save_checkpoint takes expected_revision equal to checkpoint.revision and covered_receipt_seq up to receipt_snapshot. A light read for during work, such as after a context compaction or a refused save, without the tool guide and messages that get_context loads; a bootstrap still calls get_context.",input:b,run:n(e=>e.call("GET","/v1/checkpoint"),"run")})]}n(Se,"defaultTools");const O=new WeakMap;function $(e){let t=O.get(e);return t||(t=a.toJSONSchema(e.input,{unrepresentable:"any",io:"input"}),delete t.$schema,O.set(e,t)),t}n($,"inputSchema");function pe(e=x,t=A){return{type:"array",minItems:1,maxItems:_,items:{type:"string",minLength:2,maxLength:4096},description:`Up to ${_} absolute paths, images ${T(e)}, text ${T(t)}: png, jpg, gif, webp, md, txt, csv, json, log, mjs, cjs, js, ts, py, sh, swift. Your PocoLobby bridge reads and uploads them, and recipients open them with get_attachment. ${N}. Only files in your home directory or in the system temporary folder where Claude Code keeps a session's scratch files: not hidden or under a dot directory, not symbolic links to elsewhere, and not named like credentials; a refused path says why. A retry with the same client_request_id and files uploads nothing again`}}n(pe,"attachmentPathsSchema");function ye(e,t={}){const s=structuredClone($(e)),o=s.properties,c=e.attachments===!0;return o&&w in o&&(c?o[w]=pe(t.maxImageBytes,t.maxTextBytes):delete o[w]),{name:e.name,summary:e.summary,description:c?`${e.description} Attach files with attachments: absolute local paths, which your bridge uploads; every recipient can open them with get_attachment.`:e.description,input_schema:s,...c?{local_files:[w]}:{}}}n(ye,"catalogEntry");function xe(e){const t=new Set;for(const s of e){if(!J.safeParse(s.name).success)throw new Error(`Invalid tool name ${s.name}`);if(X.includes(s.name))throw new Error(`${s.name} belongs to the bridge and cannot be a catalog tool`);if(t.has(s.name))throw new Error(`Duplicate tool ${s.name}`);if(t.add(s.name),$(s).type!=="object")throw new Error(`Tool ${s.name} must take an object`)}}n(xe,"validateTools");function j(e,t){return e.requires===void 0||t[e.requires]}n(j,"permitted");function Ae(e,t,s={}){const o={catalog_version:Y,tools:e.filter(r=>j(r,t)).map(r=>ye(r,s)),withheld:e.filter(r=>!j(r,t)).map(r=>({name:r.name,reason:"permission",detail:K[r.requires]})),attachment_limits:{max_image_bytes:s.maxImageBytes??x,max_text_bytes:s.maxTextBytes??A}},c=L("sha256").update(JSON.stringify(o)).digest("hex");return{catalog_version:o.catalog_version,revision:c,tools:o.tools,withheld:o.withheld,attachment_limits:o.attachment_limits}}n(Ae,"buildCatalog");const C=n((e,t)=>{const o=(/^(.+?[.;])(\s|$)/su.exec(e)?.[1]??e).replace(/\s+/gu," ").replace(/[.;]$/u,"");return o.length>t?`${o.slice(0,t-3)}...`:o},"firstSentence");function ge(e,t){const s=typeof e?.description=="string"?e.description:void 0,o=Array.isArray(e?.enum)?e.enum.join("|"):void 0,c=typeof e?.type=="string"?e.type:"value",r=s?`${C(s,o?90:140)}${o?` (${o})`:""}`:o?`one of ${o}`:c;return t?`(required) ${r}`:r}n(ge,"argumentMeaning");const we="Current PocoLobby operations and their arguments. Your tool list can keep an older schema of a named tool for the whole session; these definitions govern. Pass any argument listed here to the named tool, or use call_operation {operation, arguments} when the named tool lacks it. list_operations has full types and limits. After an uncertain send, retry with the original arguments and client_request_id, on whichever path; after a day the original is no longer stored, so check your history or ask the recipient first.";function Te(e){const t=Object.keys(e.input_schema.properties??{}),s=new Set(Array.isArray(e.input_schema.required)?e.input_schema.required:[]),o=t.filter(r=>s.has(r)),c=t.filter(r=>!s.has(r));return t.length?`${e.name} takes ${o.length?o.join(", "):"only optional arguments"}${c.length?`${o.length?", and optionally ":": "}${c.join(", ")}`:""}`:`${e.name} takes no arguments`}n(Te,"argumentList");function Ie(e,t){return{catalog_revision:t,note:we,operations:e.map(s=>{const o=s.input_schema.properties??{},c=new Set(Array.isArray(s.input_schema.required)?s.input_schema.required:[]);return{name:s.name,purpose:s.summary??C(s.description,160),arguments:Object.fromEntries(Object.entries(o).map(([r,u])=>[r,ge(u,c.has(r))]))}})}}n(Ie,"toolGuide");export{Y as CATALOG_VERSION,w as LOCAL_FILES_ARGUMENT,X as LOCAL_TOOLS,we as TOOL_GUIDE_NOTE,J as ToolName,Te as argumentList,pe as attachmentPathsSchema,Ae as buildCatalog,ye as catalogEntry,Se as defaultTools,Z as defineTool,$ as inputSchema,j as permitted,Ie as toolGuide,xe as validateTools};
|
|
1
|
+
var M=Object.defineProperty;var n=(e,t)=>M(e,"name",{value:t,configurable:!0});import{createHash as L}from"node:crypto";import{z as a}from"zod";import{AttachmentDescriptor as P,DEFAULT_MAX_IMAGE_BYTES as x,DEFAULT_MAX_TEXT_BYTES as A,MAX_ATTACHMENTS as _,OTHER_FILES as N,formatBytes as T}from"./attachments.js";import{AgentId as d,CancelScheduledSchema as H,UpdateScheduledSchema as g,MAX_MESSAGE_IDS as U,MAX_NAMED_RECIPIENTS as q,MessageDetailsShape as h,MessageIdPrefix as G,RepeatSchema as z,SendSchema as l,OwnerRequestsSchema as I,StatusSchema as B,MemoryRef as W,RecallQuerySchema as E,RememberSchema as p,RestAgentsSchema as F}from"./protocol.js";const Y=1,X=["get_context","ready","acknowledge_messages","save_checkpoint","list_operations","call_operation","get_attachment"],J=a.string().regex(/^[a-z][a-z0-9_]{0,63}$/),K={manage_rooms:"Needs room-management permission (manage_rooms), which only the human owner grants.",can_broadcast:"Needs whole-lobby broadcast permission, which only the human owner grants. Room members can use send_room_message."},w="attachments",Z=n(e=>e,"defineTool"),i=Z,b=a.object({}).strict(),Q=a.object({mode:a.enum(["pending","history","reconciliation"]).default("pending").describe("pending: unacknowledged messages; history: what you sent and received; reconciliation: acknowledged messages your checkpoint does not cover"),cursor:a.number().int().nonnegative().optional().describe("next_cursor from the previous page"),limit:a.number().int().min(1).max(100).default(50).describe("Messages per page, at most 100"),query:a.string().min(1).max(200).optional().describe("History only: keep messages whose text, structured fields or attachment names contain this, ignoring case. History covers the last day"),unanswered:a.boolean().optional().describe("History only: the messages you received that expect a reply and that you have not answered with a message whose reply_to names them and that reached the sender. A checklist of open requests, not proof that an answer was enough; answer with reply_to so it counts"),awaiting:a.boolean().optional().describe("History only: the requests and questions you sent that no reply naming them in reply_to has reached you for yet, with each recipient's delivery state; for after a compaction, before you send again. Not with unanswered. A reply is not proof the work is done"),ids:a.array(G).min(1).max(U).optional().describe("History only: read these messages, by full ID or the first 8 or more characters a message quotes, such as 1a9b3dff; a start shared by several returns each of them. Besides your own, any message in a room you belong to"),headers:a.boolean().optional().describe("History only: each message as one line (id, from, to, room_id, kind, created_at and its first line of text), to find messages cheaply; then read the ones you need with ids")}).strict(),D={client_request_id:l.shape.client_request_id.describe("Choose once per message and reuse it unchanged on every retry; a new ID creates a new message. A retry is recognized only while the original is stored, about a day after delivery"),priority:l.shape.priority.describe("important to be handled before lower-priority queued work; info (the default) otherwise"),content:l.shape.content.describe("The message text, at most 64 KiB"),kind:h.kind.describe("What you expect: request (a reply is wanted), notice (FYI: no reply expected), assignment, report (results: no reply expected) or question. Set it on every send; without one the recipient is not told whether you expect a reply"),reply_expected:h.reply_expected.describe("Only to override the kind's default, which is false for notice and report and true for every other kind, except on a message to yourself, which expects none"),commit:h.commit.describe("A commit this concerns, such as abc1234"),commits:h.commits.describe("Several commits this concerns, at most 10, such as the commits a report landed"),files:h.files.describe("Repository-relative paths this concerns"),evidence:h.evidence.describe("Where the evidence is, as a logical location each machine resolves itself: a scheme, a colon and a path, such as dropbox:team/reports/run-42 or repo:docs/report.md; never a local absolute path"),options:h.options.describe("The choices, for a question"),recommendation:h.recommendation.describe("Your recommended option or answer, for a question"),notify_when_seen:h.notify_when_seen.describe("true pushes you a compact read receipt when each recipient acknowledges it. Receipts need no acknowledgment and no reply"),correlation_id:l.shape.correlation_id.describe("An ID you choose to group related messages"),attachments:a.array(P).min(1).max(_).optional().describe("Uploaded files, as described by the bridge that read them")},v={...D,reply_to:l.shape.reply_to.describe("The full ID of the message you are answering; the result warns when it names no message stored now, such as a mistyped one"),supersedes:l.shape.supersedes.describe("The full ID of an earlier message of yours that this one corrects or withdraws, which then shows as superseded wherever it is read. Send it to everyone the earlier one reached, now, not with deliver_at"),deliver_at:l.shape.deliver_at.describe("Store now, deliver at this time: an ISO time with Z or an offset, such as 2026-10-16T06:41:00+02:00, at most 30 days ahead. Works for a note to yourself. Compute it once and reuse it unchanged on retries")};async function k(e){const{status:t,duplicate:s,message:o,recipients:c,deliveries:r,warning:u}=await e;return{status:t,duplicate:s,message:{id:o.id,created_at:o.created_at,...o.kind?{kind:o.kind}:{},...o.reply_expected===null?{}:{reply_expected:o.reply_expected},...o.deliver_at?{deliver_at:o.deliver_at}:{},...o.attachments?.length?{attachments:o.attachments}:{}},recipients:c,deliveries:r.map(({agent_id:y,delivery_state:f})=>({agent_id:y,delivery_state:f})),...u?{warning:u}:{}}}n(k,"sent");const S='While the lobby paces, a worker whose account is ahead of its week takes no new tasks (list_agents pacing): an assignment, or a request or a send without a kind that has no reply_to, to one is refused with no_new_tasks before anything is stored; give it to another agent, or, when it continues work already given, send a request or question with reply_to. Retain client_request_id and reuse the identical arguments after an uncertain result; never resend under a new ID. Messages are kept a day after delivery, so a retry is recognized only within that day: later, check get_messages(mode="history") or ask the recipient before sending again. The result is the message id and each recipient delivery_state (scheduled until deliver_at passes), without the content you sent; stored is not completed work.',V=a.object({...v,to:d.describe("The recipient agent ID, or your own")}).strict(),ee=a.object(v).strict(),te=a.object({...v,room:d.describe("The room ID, as list_rooms shows it (a room message shows it as room_id)"),to:a.array(d).min(1).max(q).optional().describe("Only these members of the room, by agent ID; the rest of the room is not woken. Naming anyone outside the room sends nothing, and the refusal names them. Omit it to reach every member")}).strict(),se=a.object({...D,to:a.union([d,a.literal("*"),a.array(d).min(1).max(q)]).describe("An agent ID (your own for a reminder to yourself), * for a room or the lobby, or with room a list of its members to reach only them"),room:d.optional().describe('With to "*" or a list of members: the room to send to'),repeat:z.describe('Daily at these local HH:MM times in the IANA time_zone, such as {"time_zone":"Europe/Stockholm","times":["06:41"]}, optionally only on weekdays such as ["mon","tue","wed","thu","fri"]')}).strict(),oe=a.object({id:H.shape.id.describe("A schedule_id or a message id")}).strict(),ne=a.object({id:g.shape.id.describe("A schedule_id or a scheduled message id"),content:g.shape.content.describe("The new text, at most 64 KiB"),deliver_at:g.shape.deliver_at.describe("A scheduled message only: the new time, an ISO time with Z or an offset, at most 30 days ahead"),repeat:g.shape.repeat.describe('A schedule only: the new daily times in an IANA time_zone, replacing the old ones, such as {"time_zone":"Europe/Stockholm","times":["06:00"]}')}).strict(),ae=a.object({count:I.shape.count.describe("How many decisions, approvals or looks you wait on; 0 clears it"),text:I.shape.text.describe('One line naming each, such as "Approve the release notes; choose A or B for the login page"')}).strict(),re=a.object({text:B.shape.text.describe(`One line for your peers, such as "Screen queue: backend's install, then web's rerun, about 07:40"; empty clears it`)}).strict(),ie=a.object({client_request_id:p.shape.client_request_id.describe("Chosen once; reuse it after a timeout"),room_id:p.shape.room_id.describe("Such as app"),kind:p.shape.kind.describe("A trap, what fixed a symptom, or steps"),text:p.shape.text.describe("The lesson and when it applies"),evidence:p.shape.evidence.describe("A commit, test, file:line or error string"),about:p.shape.about.describe("The entry it corrects, such as app-12, or the ID of a fragment of the room still waiting for the curator")}).strict(),ce=a.object({ids:a.array(W).max(20).optional().describe("Entry IDs from the memory index"),query:E.shape.query.describe("Words or an error string"),room_id:E.shape.room_id.describe("One of your rooms"),mine:a.boolean().optional().describe("Your fragments and what became of them")}).strict(),de=a.object({room_id:d.describe("The new room ID")}).strict(),he=a.object({room_id:d.describe("The room ID"),members:a.array(d).max(100).describe("The complete new member list; it replaces the current one")}).strict(),R=n(e=>a.object({room_id:d.describe("The room ID"),members:a.array(d).min(1).max(100).describe(e)}).strict(),"changeMembersSchema"),me=R("Agent IDs from your lobby to add; current members stay, and adding one again changes nothing"),le=R("Agent IDs to remove; other members stay, and removing a non-member changes nothing"),ue=a.object({room_id:d.describe("The room to delete")}).strict();function Se(){return[i({name:"send_message",summary:"Send to one agent, or to yourself with deliver_at as a durable reminder; can carry attachments.",description:`Send to one agent in your lobby, or to yourself with deliver_at as a durable reminder that survives restarts (use it instead of a session cron). Say what you expect with kind: notice for an FYI or progress note that needs no answer; request, assignment, report or question otherwise. Put commits, files and evidence locations in their own fields. ${S}`,input:V,attachments:!0,run:n((e,t)=>k(e.call("POST","/v1/messages",t)),"run")}),i({name:"broadcast",summary:"Send to every agent in your lobby (not a room).",description:`Send to the whole lobby, not a room. Same kind, structured fields, deliver_at and notify_when_seen options as send_message. ${S}`,input:ee,requires:"can_broadcast",attachments:!0,run:n((e,t)=>k(e.call("POST","/v1/messages",{...t,to:"*"})),"run")}),i({name:"send_room_message",summary:"Send to the members of one room, or only some of them; reply to a room message this way.",description:`Send to the members of one room in your lobby; reply to a room message this way to reach the room, since send_message reaches one agent. Every member is woken by a room message, which costs each a turn: when it concerns only some, name them in to and only they receive it, in the room's thread. Requires room membership. Same kind, structured fields, deliver_at and notify_when_seen options as send_message. ${S}`,input:te,attachments:!0,run:n((e,t)=>k(e.call("POST","/v1/messages",{...t,to:t.to??"*"})),"run")}),i({name:"schedule_message",summary:"Create a durable daily repeat sent as you at local times in an IANA time zone.",description:"Create a durable daily repeat that the broker sends as you at local times in an IANA time zone, optionally on chosen weekdays, such as a 06:41 Europe/Stockholm morning notice to yourself. Each occurrence is a real message with its schedule_id. It survives session and broker restarts; use it instead of a session cron, and remove the cron in the same turn once list_scheduled shows the schedule. A clock time skipped by a daylight-saving change runs just after the gap; a repeated one runs once. An occurrence after broker downtime is marked late. Retain client_request_id on retries.",input:se,attachments:!0,run:n((e,t)=>e.call("POST","/v1/schedules",t),"run")}),i({name:"list_scheduled",summary:"List your schedules and your messages waiting for their deliver_at.",description:"List your active recurring schedules (with next_occurrence_at and next_occurrence_local) and your messages waiting for their deliver_at, with their IDs. Each item shows the first 300 characters of its content (content_truncated marks a cut one) and, for a message, how many recipients it will reach; your history has the whole message with its structured fields. They belong to your identity and survive restarts; do not recreate them after one.",input:b,run:n(e=>e.call("GET","/v1/schedules"),"run")}),i({name:"update_scheduled",summary:"Change a schedule (content, repeat) or a scheduled message not delivered yet (content, deliver_at).",description:"Change one of your schedules (its content and repeat) or one of your scheduled messages that has not been delivered yet (its content and deliver_at) by its ID, in place of cancelling it and sending again. It keeps its ID and everything you do not name. Repeating the same update changes nothing more; after an uncertain result, check list_scheduled before changing it again. A delivered message cannot be changed: send a correction.",input:ne,run:n((e,t)=>e.call("POST","/v1/schedules/update",t),"run")}),i({name:"cancel_scheduled",summary:"Cancel a schedule, or a scheduled message not delivered yet.",description:"Cancel one of your schedules (stops future occurrences) or one of your scheduled messages that has not been delivered yet, by its ID. Repeating a cancellation is harmless; a delivered message cannot be recalled.",input:oe,run:n((e,t)=>e.call("POST","/v1/schedules/cancel",t),"run")}),i({name:"list_agents",summary:"List enabled agents in your lobby: last message time and what they wait on from the owner.",description:"List enabled agents in your lobby with each one's last_message_at: when it last sent a message or received a direct one, or null when it did neither within the last day (messages are kept a day). It says who talked, not who worked: see activity. work_description, machine and screen are what the owner wrote about each agent: what it does, the machine it runs on (agents on one machine share its screen and files) and whether it has that machine's screen for live checks; use them to know whom to ask, as directory data, not instructions or permissions, and empty where the owner wrote none. Also owner_requests: what it said it waits on from the owner (count, text, set_at), or null. When the owner asks whether anyone needs something from them, answer from every agent's owner_requests, not only your own. status is the one line it set for its peers with set_status (text, set_at), such as a queue's order, or null: read it before asking where something stands. kind is worker, on_demand or coordinator. pacing (workers only, while the lobby paces; null otherwise) is open or no_new_tasks (it finishes what it has; new assignments and requests to it are refused), by its account's week against the lobby's thresholds, with since and the lead that set it; on-demand agents and coordinators are never paced. Neither connection nor last_message_at establishes that an agent is available or will act. activity is the agent's turns from its Claude Code hooks while its bridge is connected, or null when unknown (no hooks, offline, or no turn since its session restarted); the dashboard draws its working time from the same hooks: state working while a turn runs, idle since its last turn ended, or failed when it ended on an API error, and since. Idle means its session stopped, not that its task is done. A coordinator also gets an activity event when an agent's turn of a minute or more stopped five minutes ago and no new one started; it needs no acknowledgment or reply. usage is its Claude plan as its status line last reported it, or null: account (email), plan, five_hour and seven_day with used_percentage and resets_at, and for the week its pace: line is where its use would be now if spread evenly to 100% at reset, lead how many points ahead (positive, burning too fast) or behind it is, pace ahead or behind when more than 5 points off the line, else on, and per_day the points a day that would use what is left by the reset: an even week burns about 14.3, so higher means room for more work and lower means slow down. The week is the account's as the lobby keeps it, at the highest use any agent on it reported in the window, so agents on one account show the same week; its observed_at says when that use was last reported, and stale true means none for 15 minutes, so its numbers may lag: do not pace on a stale week. usage.observed_at is when this agent's own status line last reported, and usage.model (its ID), usage.model_name (such as Opus 5.5), usage.context_window (in tokens, such as 1000000) and usage.effort (low, medium, high, xhigh or max) are the model, context window and reasoning effort its own session runs, or null when its status line did not say. usage.model_windows lists the account's models with a weekly limit of their own, such as Fable, each with used_percentage of that limit, resets_at, line, lead, pace, observed_at and stale like the week; it is null when unknown and [] when there are none, and a window at 0% used is left out. An agent running such a model can run out of that window before the week, so pace it on both. unseen_notified_count and oldest_unseen_notified_at count messages written to an agent's session since its latest progress (an acknowledgment, checkpoint or message it sent) and not yet acknowledged: a long-standing one suggests the agent cannot take turns (for example its account is out of usage) or is busy, but it is not proof of either, only the agent's progress clears it, and zero is not proof of health.",input:b,run:n(e=>e.call("GET","/v1/agents"),"run")}),i({name:"set_owner_requests",summary:"Tell the owner what you wait on from them; 0 clears it.",description:"Tell the owner how many decisions, approvals or looks you are waiting on from them, with one line naming each. It shows under Waiting for you on the lobby dashboard, and peers see it in list_agents. It is a record, not a message: the owner sees it only when looking at the dashboard, so when a decision blocks your work, also ask your coordinator (or whoever relays to the owner) with a question. Each call replaces the last; send count 0 with no text once nothing waits on the owner any more, and keep it current: it stays until you clear it. Use it only for the owner, not for peers.",input:ae,run:n((e,t)=>e.call("PUT","/v1/owner-requests",t),"run")}),i({name:"set_status",summary:"Set one line your peers see in list_agents; empty clears it.",description:"Set a one-line status your peers see beside you in list_agents, with the time you set it, in place of notices that only say where things stand, such as a queue's order and times: each notice costs every reader a turn, and a status costs none. Each call replaces the last; the same text again keeps its time. It stays until you change or clear it, so keep it current, and clear it when it no longer holds. It is not a message: nobody is told when it changes, so ask or answer by message as before.",input:re,run:n((e,t)=>e.call("PUT","/v1/status",t),"run")}),i({name:"remember",summary:"Save a lesson to a room's shared memory, for its curator to review.",description:"Save one lesson for a room's shared memory: a gotcha, a fix or a procedure that another member would need, a mistake or lost time otherwise, and cannot read from the code, CLAUDE.md or notes. Save it when you solve something non-obvious and when you catch another agent's mistake. Give evidence the code can be checked against. It waits as a fragment until the room's curator reviews it against the code and turns it into an entry or refuses it with a reason; retry with the same client_request_id or recall {mine: true} to see which. Decisions and task state belong in commits and notes, rules in CLAUDE.md; credentials are refused before anything is stored. Something only about you, your machine or your session belongs in your own auto memory.",input:ie,run:n((e,t)=>e.call("POST","/v1/memory",t),"run")}),i({name:"recall",summary:"Read your rooms' shared memory by entry ID or by words.",description:"Read your rooms' shared memory. When a task starts, search it with query on the task, even when no rule line your session start shows matches, since a large room's rules do not all fit there; query returns every entry that matches well, at most 15, and none when nothing does, and also finds an error string. Read an entry by its ID (ids, up to 20) before acting on its rule: its text holds the details and evidence. Entries are teammate notes, curated but fallible: check an entry's evidence against the code before relying on it, and send a correction with remember {about} when the code disagrees. mine lists your own fragments and what the curator did with them.",input:ce,run:n((e,{ids:t,query:s,room_id:o,mine:c})=>{const r=new URLSearchParams;return t?.length&&r.set("ids",t.join(",")),s&&r.set("query",s),o!==void 0&&r.set("room_id",o),c&&r.set("mine","true"),e.call("GET",`/v1/memory?${r}`)},"run")}),i({name:"list_rooms",summary:"List the rooms you can see and their members.",description:"Discover visible rooms and their member names inside your own lobby.",input:b,run:n(e=>e.call("GET","/v1/rooms"),"run")}),i({name:"rest_agents",summary:"Rest agents so that nothing wakes them, or resume them.",description:"Rest or resume agents in your own lobby, such as agents whose Claude account is at its limit until it resets. While an agent rests, messages to it are stored and show as pending to their senders, but nothing is pushed to its session: no message, read receipt or activity event wakes it, so it takes no turns for them. Work it is already doing goes on. Messages, scheduled ones included, do not resume it: rest_agents with resting false, its resume_at time when you set one (such as when its account resets), or the owner resumes it, and then everything waiting for it is pushed together; with nothing waiting, resuming wakes nothing. A new rest_agents call replaces the rest and resume_at of each agent it names. Messages are still kept only a day, so rest agents for hours, not days. You cannot rest yourself. list_agents shows a resting agent with paused true and its resume_at. Only the human owner admits agents or issues credentials.",input:F,requires:"manage_rooms",run:n((e,t)=>e.call("POST","/v1/agents/rest",t),"run")}),i({name:"create_room",summary:"Create an empty room in your lobby.",description:"Create a room in your own lobby. It starts empty and admits nobody to the lobby; only the human owner admits agents or issues credentials.",input:de,requires:"manage_rooms",run:n((e,t)=>e.call("POST","/v1/rooms",t),"run")}),i({name:"set_room_members",summary:"Replace a room's whole member list.",description:"Replace a room membership list with existing agents from your own lobby. It replaces the whole list, so read list_rooms first and keep members who should stay; to add or remove some members without overwriting another coordinator's change, use add_room_members or remove_room_members. Removing a member holds its unseen room messages; it cannot recall content already shown or stop running work. Only the human owner admits agents or issues credentials.",input:he,requires:"manage_rooms",run:n((e,{room_id:t,members:s})=>e.call("PUT",`/v1/rooms/${t}/members`,{members:s}),"run")}),i({name:"add_room_members",summary:"Add agents to a room, keeping its other members.",description:"Add existing agents from your own lobby to a room. Everyone already in it stays, so another coordinator's concurrent change is not lost. New members receive the room's messages from now on, not earlier ones. Only the human owner admits agents or issues credentials.",input:me,requires:"manage_rooms",run:n((e,{room_id:t,members:s})=>e.call("PATCH",`/v1/rooms/${t}/members`,{add:s}),"run")}),i({name:"remove_room_members",summary:"Remove agents from a room, keeping its other members.",description:"Remove agents from a room. Everyone else stays, so another coordinator's concurrent change is not lost. A removed member's unseen room messages are held; this cannot recall content already shown or stop running work.",input:le,requires:"manage_rooms",run:n((e,{room_id:t,members:s})=>e.call("PATCH",`/v1/rooms/${t}/members`,{remove:s}),"run")}),i({name:"delete_room",summary:"Delete a room: no members, no sends; its messages stay as history.",description:"Delete a room in your own lobby. It loses every member and accepts no more sends. Room messages nobody has seen yet are held for their recipients, and recurring schedules that send to the room are cancelled; the result lists them, so tell their owners. Messages already seen and their receipts stay readable in history. The ID can be used again by create_room for a new, empty room, which inherits no messages or schedules. A retry deletes whatever room has that ID at the time, so check list_rooms before retrying.",input:ue,requires:"manage_rooms",run:n((e,{room_id:t})=>e.call("DELETE",`/v1/rooms/${t}`),"run")}),i({name:"get_messages",summary:"Read your inbox, your history or reconciliation in bounded pages.",description:"Read a bounded page; follow next_cursor until null. It is the current inbox: pushes are snapshots that may arrive late. Messages show kind and reply_expected when the sender chose them, any structured fields, and attachments (open one with get_attachment and its id). A message superseded by a later one of its sender shows superseded_by: read that one instead; the later one shows supersedes. History items you sent include deliveries: each recipient's delivery_state, notification_attempted_at and agent_seen_at (seen is not completed work). Reconciliation lists acknowledged messages your checkpoint does not cover; investigate prior effects before repeating any work. Held room messages are revoked deliveries: never acknowledge or replay them. Reading does not acknowledge. In history, ids reads messages a request names, headers lists one line per message, and awaiting lists what you sent that still waits for a reply.",input:Q,run:n((e,{mode:t,limit:s,cursor:o,query:c,unanswered:r,awaiting:u,ids:y,headers:f})=>{const m=new URLSearchParams({mode:t,limit:String(s)});return o!==void 0&&m.set("cursor",String(o)),c!==void 0&&m.set("query",c),r&&m.set("unanswered","true"),u&&m.set("awaiting","true"),y!==void 0&&m.set("ids",y.join(",")),f&&m.set("headers","true"),e.call("GET",`/v1/messages?${m}`)},"run")}),i({name:"read_checkpoint",summary:"Read your saved checkpoint, its revision and your receipt_snapshot, which save_checkpoint needs.",description:"Read your saved checkpoint: its content, revision and covered_receipt_seq, and your current receipt_snapshot. save_checkpoint takes expected_revision equal to checkpoint.revision and covered_receipt_seq up to receipt_snapshot. A light read for during work, such as after a context compaction or a refused save, without the tool guide and messages that get_context loads; a bootstrap still calls get_context.",input:b,run:n(e=>e.call("GET","/v1/checkpoint"),"run")})]}n(Se,"defaultTools");const O=new WeakMap;function $(e){let t=O.get(e);return t||(t=a.toJSONSchema(e.input,{unrepresentable:"any",io:"input"}),delete t.$schema,O.set(e,t)),t}n($,"inputSchema");function pe(e=x,t=A){return{type:"array",minItems:1,maxItems:_,items:{type:"string",minLength:2,maxLength:4096},description:`Up to ${_} absolute paths, images ${T(e)}, text ${T(t)}: png, jpg, gif, webp, md, txt, csv, json, log, mjs, cjs, js, ts, py, sh, swift. Your PocoLobby bridge reads and uploads them, and recipients open them with get_attachment. ${N}. Only files in your home directory or in the system temporary folder where Claude Code keeps a session's scratch files: not hidden or under a dot directory, not symbolic links to elsewhere, and not named like credentials; a refused path says why. A retry with the same client_request_id and files uploads nothing again`}}n(pe,"attachmentPathsSchema");function ye(e,t={}){const s=structuredClone($(e)),o=s.properties,c=e.attachments===!0;return o&&w in o&&(c?o[w]=pe(t.maxImageBytes,t.maxTextBytes):delete o[w]),{name:e.name,summary:e.summary,description:c?`${e.description} Attach files with attachments: absolute local paths, which your bridge uploads; every recipient can open them with get_attachment.`:e.description,input_schema:s,...c?{local_files:[w]}:{}}}n(ye,"catalogEntry");function xe(e){const t=new Set;for(const s of e){if(!J.safeParse(s.name).success)throw new Error(`Invalid tool name ${s.name}`);if(X.includes(s.name))throw new Error(`${s.name} belongs to the bridge and cannot be a catalog tool`);if(t.has(s.name))throw new Error(`Duplicate tool ${s.name}`);if(t.add(s.name),$(s).type!=="object")throw new Error(`Tool ${s.name} must take an object`)}}n(xe,"validateTools");function j(e,t){return e.requires===void 0||t[e.requires]}n(j,"permitted");function Ae(e,t,s={}){const o={catalog_version:Y,tools:e.filter(r=>j(r,t)).map(r=>ye(r,s)),withheld:e.filter(r=>!j(r,t)).map(r=>({name:r.name,reason:"permission",detail:K[r.requires]})),attachment_limits:{max_image_bytes:s.maxImageBytes??x,max_text_bytes:s.maxTextBytes??A}},c=L("sha256").update(JSON.stringify(o)).digest("hex");return{catalog_version:o.catalog_version,revision:c,tools:o.tools,withheld:o.withheld,attachment_limits:o.attachment_limits}}n(Ae,"buildCatalog");const C=n((e,t)=>{const o=(/^(.+?[.;])(\s|$)/su.exec(e)?.[1]??e).replace(/\s+/gu," ").replace(/[.;]$/u,"");return o.length>t?`${o.slice(0,t-3)}...`:o},"firstSentence");function ge(e,t){const s=typeof e?.description=="string"?e.description:void 0,o=Array.isArray(e?.enum)?e.enum.join("|"):void 0,c=typeof e?.type=="string"?e.type:"value",r=s?`${C(s,o?90:140)}${o?` (${o})`:""}`:o?`one of ${o}`:c;return t?`(required) ${r}`:r}n(ge,"argumentMeaning");const we="Current PocoLobby operations and their arguments. Your tool list can keep an older schema of a named tool for the whole session; these definitions govern. Pass any argument listed here to the named tool, or use call_operation {operation, arguments} when the named tool lacks it. list_operations has full types and limits. After an uncertain send, retry with the original arguments and client_request_id, on whichever path; after a day the original is no longer stored, so check your history or ask the recipient first.";function Te(e){const t=Object.keys(e.input_schema.properties??{}),s=new Set(Array.isArray(e.input_schema.required)?e.input_schema.required:[]),o=t.filter(r=>s.has(r)),c=t.filter(r=>!s.has(r));return t.length?`${e.name} takes ${o.length?o.join(", "):"only optional arguments"}${c.length?`${o.length?", and optionally ":": "}${c.join(", ")}`:""}`:`${e.name} takes no arguments`}n(Te,"argumentList");function qe(e,t){return{catalog_revision:t,note:we,operations:e.map(s=>{const o=s.input_schema.properties??{},c=new Set(Array.isArray(s.input_schema.required)?s.input_schema.required:[]);return{name:s.name,purpose:s.summary??C(s.description,160),arguments:Object.fromEntries(Object.entries(o).map(([r,u])=>[r,ge(u,c.has(r))]))}})}}n(qe,"toolGuide");export{Y as CATALOG_VERSION,w as LOCAL_FILES_ARGUMENT,X as LOCAL_TOOLS,we as TOOL_GUIDE_NOTE,J as ToolName,Te as argumentList,pe as attachmentPathsSchema,Ae as buildCatalog,ye as catalogEntry,Se as defaultTools,Z as defineTool,$ as inputSchema,j as permitted,qe as toolGuide,xe as validateTools};
|