pi-straw-boss 0.7.3 → 0.9.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/README.md CHANGED
@@ -14,11 +14,12 @@ Run a few agents on the same project and they start stepping on each other: two
14
14
 
15
15
  - **Everyone joins.** Each pi in Herdr joins its tab's net at start as a member; nothing to ask or remember.
16
16
  - **A main agent per tab, by promotion.** An agent whose work grows calls `boss_promote`, or is promoted when it first dispatches a subagent. A tab has one main, and nobody takes it over except you with `/boss`.
17
- - **A tower per workspace.** It opens with the workspace's first main in its own tab, pinned first, and coordinates the main agents and the other towers. You don't need to talk to it.
17
+ - **A tower per workspace.** It opens with the workspace's first main in its own tab, pinned first, and coordinates the main agents and the other towers, and opens a tab with a main agent for work no session owns. You don't need to talk to it.
18
18
  - **A live dashboard** in the tower: who joined, and what they and their subagents are doing.
19
19
  - **A browser view** of the whole net, `/boss gui`: every workspace as a base with its tower, agents, and subagents, animated and read-only. Click a base to zoom in, click a unit to read its chat as a live comms log; units that wait on you queue up as alerts.
20
20
  - **Broadcast** to a tab, a workspace, every tower, or every session.
21
21
  - **Tidy tabs.** When a pi starts in a tab or quits out of it, the tab's panes are made equal again, so workers split off one by one don't leave a chain of halves.
22
+ - **Stranded subagents are asked to report.** A subagent that stays open after finishing, while the pi that launched it was restarted and no longer receives its result, is asked once over intercom to send its final report there ([how it works](docs/how-it-works.md#orphaned-subagents)).
22
23
  - **A net view in Herdr's sidebar.** An optional plugin turns the Agents list into the net, towers first and each subagent under its launcher, on by default and switched by one key.
23
24
 
24
25
  ## Install
@@ -45,10 +46,11 @@ The tower only routes and coordinates, so it can run a lighter model: set `tower
45
46
  | `boss_promote` | Become this tab's main agent (refused while it has one). |
46
47
  | `boss_status` | Main agents, this workspace's tower, and the other towers. |
47
48
  | `call_tower` | Message this workspace's tower, or another workspace's. |
49
+ | `boss_open_main` | Tower only: open a tab with a new main agent for a request no session owns. |
48
50
  | `boss_broadcast` | Message a tab, a workspace, all towers, or every session. |
49
51
  | `/boss` | Take over as this tab's main agent. |
50
52
  | `/boss status`, `/boss tower` | Show the status; open and focus the tower. |
51
- | `/boss gui`, `/boss gui stop` | Open the net in the browser (any session, joins nothing); stop its server. Type into a session from its comms view, or work its terminal with the key pad under the terminal tile; answer a waiting `robot_hand` selector or `ask_user_question` questionnaire there or in its alert (other dialogs show read-only, to answer in the terminal; `secret_drop` stays terminal-only). |
53
+ | `/boss gui`, `/boss gui stop` | Open the net in the browser (any session, joins nothing); stop its server. Type into a session from its comms view, or work its terminal with the key pad under the terminal tile; answer a waiting `robot_hand` selector or `ask_user_question` questionnaire there or in its alert (other dialogs show read-only, to answer in the terminal; `secret_drop` stays terminal-only). Documents a session shows with [pi-herdr-doc-review](https://www.npmjs.com/package/pi-herdr-doc-review) 0.4.0 or newer open beside its comms: HTML in a sandboxed frame whose form answers the agent, text read-only. |
52
54
  | `/boss gui remote`, `/boss gui remote off` | Reach the GUI from your phone over your tailnet (`tailscale serve`, never Funnel): pair by QR code; off removes only its own serve entry. |
53
55
 
54
56
  Subagents and sessions in Herdr worktree workspaces never join: the session that launched them speaks for them. Outside Herdr the extension does nothing.
@@ -0,0 +1,2 @@
1
+ import{l as e,n as t}from"./index-F5nF0TfD.js";import{n}from"./post-B39ywXKd.js";import{n as r,t as i}from"./views-D6rBFuay.js";function a(){let e=!0,t=0;return{get pinned(){return e},scrolled(n){e=n,n&&(t=0)},hold:()=>void(e=!1),arrived(n){return e||(t+=n),e},jump:()=>void(e=!0,t=0),button:n=>e&&!n?void 0:t&&!n?`${t} new`:`jump to latest`}}async function o(e,t,n,r){let i=()=>e.items.some(e=>e.id===n);for(;!i()&&e.before;)r(e.prepend(await t.older(e.before)));return i()?!0:(r(e.reset(await t.latest())),!1)}function s(e=600){let t=[],n,r=!1,i=new Set,a=new Map,o=new Map,s=e=>e.forEach(e=>(i.delete(e.id),e.tools?.forEach(e=>(o.delete(e.id),a.delete(e.id)))));function c(e,t){let n=[];for(let r of e)i.has(r.id)||(i.add(r.id),r.kind===`result`&&r.callId?(a.set(r.callId,r),o.has(r.callId)&&t.add(o.get(r.callId))):(r.tools?.forEach(e=>o.set(e.id,r.id)),n.push(r)));return n}let l=e=>({rebuild:!1,added:[],touched:[],dropped:[],front:!1,...e});return{get items(){return t},get before(){return n},get detached(){return r},result:e=>a.get(e),reset(e){return[t,n,r]=[[],e.before,!1],i.clear(),a.clear(),o.clear(),t=c(e.items,new Set),l({rebuild:!0,added:t})},append(i){if(r)return l({});let a=new Set,o=c(i,a);t.push(...o);let u=t.length>e?t.splice(0,t.length-e):[];return s(u),u.length&&(n=t[0].id),l({added:o,touched:[...a],dropped:u.map(e=>e.id)})},prepend(i){let a=new Set,o=c(i.items,a);t.unshift(...o),n=i.before;let u=t.length>e?t.splice(e):[];return s(u),u.length&&(r=!0),l({added:o,touched:[...a],dropped:u.map(e=>e.id),front:!0})}}}var c=e=>e?new Date(e).toLocaleTimeString([],{hour:`2-digit`,minute:`2-digit`,hour12:!1}):``,l=e=>e>=1024?`${(e/1024).toFixed(e>=10240?0:1)} KB`:`${e} B`,u={intercom_message:`message from`,subagent_result:`report from`,subagent_ping:`question from`,subagent_status:`status of`,"pi-loop-monitor":`monitor`,"memory-context":`memory`,"doc-review":`form answers`},d=n=>{let r=e(`<span class="ava"><svg><use/></svg></span>`);return r.querySelector(`use`).setAttribute(`href`,`#i-${t[n]??n}`),r};function f(e){let t=[],r=0;for(let i of e.matchAll(/https?:\/\/[^\s<>)\]]+/g))t.push(e.slice(r,i.index),n(`a`,{href:i[0],rel:`noopener noreferrer`,target:`_blank`},i[0])),r=i.index+i[0].length;return[...t,e.slice(r)]}function p(e,t,{who:p,link:m,docFor:h}){let g=s(),_=new Map,v=new Set,y=()=>v.forEach(e=>e.button.isConnected?(e.shown=!0,e.button.hidden=!e.paths.some(e=>h?.(e))):e.shown&&v.delete(e)),b=n(`div`,{class:`chat-list`}),x=n(`div`,{class:`chat-top`}),S=n(`div`,{class:`chat-live`}),C=n(`button`,{class:`chat-pill`,type:`button`,hidden:``}),w=n(`p`,{class:`chat-empty`},`No transmissions yet.`),T=n(`div`,{class:`chat-scroll`,tabindex:`-1`,"aria-label":`Comms log`},x,b,S);e.replaceChildren(T,C);let E=a(),D=!1,O=!1,k,A,j=0,M=()=>T.scrollHeight-T.scrollTop-T.clientHeight<80,N=(e=E.button(g.detached))=>(C.hidden=!e,C.textContent=e??``),P=()=>(E.jump(),A=void 0,T.scrollTop=T.scrollHeight,N());async function F(e){D=!0;let n=await o(g,t,e,e=>(B(e),V(e))).finally(()=>D=!1),r=_.get(e);if(k===e){if(!n||!r)return P();E.hold(),A=r,r.scrollIntoView({block:`center`}),r.classList.remove(`lit`),r.offsetWidth,r.classList.add(`lit`),clearTimeout(j),j=window.setTimeout(()=>(A=void 0,r.classList.remove(`lit`)),2e3),N()}}let I=()=>k?void F(k):P(),L=(e,n,r,i)=>{e.disabled=!0,t.entry(r).then(t=>{let r=t&&i(t);if(r===void 0)return void(e.textContent=`unavailable`);n.textContent=r,e.remove()},()=>e.textContent=`unavailable`)};function R(e,t){let r=g.result(e.id),i=n(`pre`,{class:`out`},r?.text??``),a=n(`pre`,{class:`args`},e.args??``),o=e.cut?n(`button`,{class:`more`,type:`button`},`show all arguments`):null;o?.addEventListener(`click`,()=>L(o,a,t,t=>(e=>e&&`${e.args??``}${e.cut?`
2
+ … (cut at 1 MB)`:``}`)(t.tools?.find(t=>t.id===e.id))));let s=r?.more?n(`button`,{class:`more`,type:`button`},`show all (${l(r.bytes??0)})`):null;s?.addEventListener(`click`,()=>L(s,i,r.id,e=>e.text));let c=e.name===`show_doc`&&h?n(`button`,{class:`more open`,type:`button`,hidden:``},`open beside`):null;return c?.addEventListener(`click`,t=>(t.preventDefault(),e.summary.split(`, `).forEach(e=>h?.(e)?.()))),c&&v.add({paths:e.summary.split(`, `),button:c}),c&&queueMicrotask(y),n(`details`,{class:`act`},n(`summary`,{"data-mark":r?r.isError?`error`:`done`:`working`},n(`b`,{class:`badge`}),n(`code`,null,e.name),n(`span`,{class:`sum`},e.summary),c),a,o,r&&n(`div`,{class:`result`},r.images?n(`em`,null,`${r.images} image(s)`):null,i,s))}function z(e){let t=n(`time`,null,c(e.at));switch(e.kind){case`user`:return n(`article`,{class:`tx you`},n(`header`,null,t,n(`b`,null,`you`),d(`user`)),n(`div`,{class:`bubble`},...f(e.text??``),e.images?n(`em`,null,` · ${e.images} image(s)`):null));case`assistant`:{let a=p(),o=n(`pre`,{class:`thought-text`}),s=e.thought?n(`button`,{class:`thought`,type:`button`},e.thought<0?`◇ thought · redacted`:`◇ thought · ${e.thought.toLocaleString()} chars`):null;return e.thought&&e.thought>0&&s?.addEventListener(`click`,()=>L(s,o,e.id,e=>e.thoughtText)),n(`article`,{class:`tx in`},d(a.role),n(`div`,{class:`tx-body`},n(`header`,null,n(`b`,null,a.name),e.model?n(`span`,null,e.model):null,t,e.stop?n(`span`,{class:`stop`,"data-mark":`error`},e.stop):null),s,o,e.text?n(`div`,{class:`md`},i(r(e.text))):null,e.error?n(`p`,{class:`fault`},e.error):null,...(e.tools??[]).map(t=>R(t,e.id))))}case`bash`:return n(`article`,{class:`tx sys`},n(`details`,{class:`act`},n(`summary`,{"data-mark":e.exit?`error`:`done`},n(`b`,{class:`badge`}),n(`code`,null,`shell`),n(`span`,{class:`sum`},`! ${e.command}`),e.exit?n(`span`,{class:`stop`,"data-mark":`error`},`exit ${e.exit}`):null),e.text?n(`pre`,{class:`out`},e.text):null));case`notice`:return n(`article`,{class:`tx sys`},n(`details`,{class:`notice`},n(`summary`,null,d(`comms`),n(`em`,null,`incoming`),u[e.type??``]??e.type??`notice`,` `,e.from&&m(e.from)||(e.from??``),t),n(`pre`,null,e.text??``)));case`divider`:return n(`article`,{class:`tx rule`},n(`details`,null,n(`summary`,null,e.type===`compaction`?`context compacted · ${(e.tokens??0).toLocaleString()} tokens summarized`:`branch summary`),n(`pre`,null,e.text??``)));default:return n(`article`,{class:`tx note`},e.text??``,` `,t)}}function B(e){e.rebuild&&(_.clear(),b.replaceChildren());for(let t of e.dropped)_.get(t)?.remove(),_.delete(t);let t=e.added.map(e=>(_.set(e.id,z(e)),_.get(e.id)));e.front?b.prepend(...t):b.append(...t),w.remove(),g.items.length||b.append(w),x.hidden=!g.before}let V=e=>e.touched.forEach(e=>{let t=_.get(e),n=g.items.find(t=>t.id===e);if(t&&n){let r=z(n);t.replaceWith(r),_.set(e,r)}}),H=async()=>{if(!D&&g.before&&O){D=!0;try{let e=T.scrollHeight,n=g.prepend(await t.older(g.before));B(n),V(n),T.scrollTop+=T.scrollHeight-e}finally{D=!1}g.before&&T.clientHeight&&x.getBoundingClientRect().bottom>=T.getBoundingClientRect().top&&H()}};new IntersectionObserver(e=>e.some(e=>e.isIntersecting)&&void H(),{root:T}).observe(x),C.addEventListener(`click`,()=>{k=void 0,g.detached?t.latest().then(e=>(B(g.reset(e)),P())):P()}),T.addEventListener(`scroll`,()=>(E.scrolled(M()),N()),{passive:!0});for(let e of[`wheel`,`touchstart`,`pointerdown`,`keydown`])T.addEventListener(e,()=>A=k=void 0,{passive:!0});let U=new ResizeObserver(()=>E.pinned&&!g.detached?T.scrollTop=T.scrollHeight:A?.scrollIntoView({block:`center`}));return U.observe(b),U.observe(T),{log:T,docs:y,apply(e){if(e.reset)B(g.reset({items:e.items??[],before:e.before})),O=!0,I();else if(e.items?.length){let t=g.append(e.items);B(t),V(t),E.arrived(t.added.length)&&!g.detached?P():N()}},reveal(e){k=e,O&&I()},setLive(e,t){S.dataset.mark=t,S.lastChild?.textContent!==e&&S.replaceChildren(...e?[n(`b`,{class:`badge`}),n(`span`,null,e)]:[])}}}export{p as mountChat};
@@ -0,0 +1 @@
1
+ :root{--bg:#060a14;--panel:#0e1628e0;--line:#7a98e033;--text:#dbe6ff;--dim:#8b99ba;--gold:#ffd166;--grid:linear-gradient(#82a0ff0d 1px, transparent 1px) 0 0 / 44px 44px, linear-gradient(90deg, #82a0ff0d 1px, transparent 1px) 0 0 / 44px 44px;--mono:ui-monospace, "SF Mono", Menlo, Consolas, monospace;--sans:ui-rounded, "Avenir Next", "Trebuchet MS", system-ui, -apple-system, "Segoe UI", sans-serif}*{box-sizing:border-box;margin:0}html{--lightningcss-light: ;--lightningcss-dark:initial;color-scheme:dark}body{min-height:100vh;color:var(--text);font:14px/1.4 var(--sans);background:radial-gradient(1100px 640px at 12% -10%, #38e0ff1c, transparent 62%), radial-gradient(900px 600px at 98% 8%, #966eff1c, transparent 60%), linear-gradient(var(--bg), #0a1020);background-attachment:fixed}svg{display:block}[data-mark=working]{--c:#38e0ff}[data-mark=waiting]{--c:#ffb02e}[data-mark=idle]{--c:#7184ad}[data-mark=done]{--c:#3ddc97}[data-mark=error]{--c:#ff5470}[data-mark=unknown]{--c:#4f5c7c}[data-mark]{--c1:color-mix(in srgb, var(--c) 13%, transparent);--c4:color-mix(in srgb, var(--c) 45%, transparent)}:focus-visible{outline-offset:2px;outline:2px solid #38e0ff}.shell{max-width:1480px;margin:0 auto;padding:18px 22px 40px}.hud{border:1px solid var(--line);background:var(--panel);-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);z-index:5;border-radius:18px;align-items:center;gap:24px;padding:12px 18px;display:flex;position:sticky;top:12px}.brand{align-items:center;gap:12px;margin-right:auto;display:flex}.logo{color:#38e0ff;border:1px solid var(--line);background:radial-gradient(circle at 30% 25%,#38e0ff4d,#0a1020e6);border-radius:14px;place-items:center;width:44px;height:44px;display:grid}.logo svg{filter:drop-shadow(0 0 6px #38e0ff);width:28px;height:28px}.brand h1{letter-spacing:.14em;text-transform:uppercase;font-size:20px}.brand p{color:var(--dim);font-size:12px}.stats{gap:10px;display:flex}.stats div,.tally div,.alerts{background:#ffffff0a;border:1px solid #ffffff0f;border-radius:12px;min-width:78px;padding:6px 12px}.stats dt,.tally dt{color:var(--dim);letter-spacing:.08em;text-transform:uppercase;font-size:11px}.stats dd,.tally dd{font:600 20px/1.1 var(--mono)}.stats [data-mark] dd,.tally [data-mark] dd,.count b{color:var(--c)}.pair-btn{border:1px solid var(--line);color:var(--dim);font:600 12px var(--sans);letter-spacing:.08em;text-transform:uppercase;cursor:pointer;background:0 0;border-radius:12px;padding:6px 12px}.pair{text-align:center;color:var(--dim);justify-items:center;gap:12px;font-size:13px;display:grid}.qr{shape-rendering:crispedges;border-radius:12px;width:min(100%,300px)}.link{color:var(--dim);text-transform:uppercase;letter-spacing:.08em;align-items:center;gap:8px;font-size:12px;display:flex}.link i{background:#4f5c7c;border-radius:50%;width:9px;height:9px}.link[data-state=live] i{background:#3ddc97;animation:2.4s ease-in-out infinite blink}.link[data-state=live]{color:#3ddc97}.link[data-state=reconnecting] i,.link[data-state=connecting] i{background:#ffb02e;animation:1s ease-in-out infinite blink}.link[data-state=offline] i{background:#ff5470}.banner{color:#c9d6f5;background:#7184ad29;border-radius:14px;margin:14px 0 0;padding:10px 16px;font-size:13px}.banner[hidden]{display:none}.banner b{font-family:var(--mono);font-weight:600}.stage{grid-template-columns:minmax(0,1fr) 300px;align-items:start;gap:20px;margin-top:18px;transition:opacity .4s;display:grid}.stage[data-stale=true] .map,body[data-stale=true] .stats{opacity:.4}.stage[data-stale=true] *{animation-play-state:paused!important}.map{border:1px solid var(--line);background:var(--grid), #080d1a8c;border-radius:22px;min-height:420px;padding:26px;transition:opacity .4s;position:relative}.links{pointer-events:none;width:100%;height:100%;position:absolute;inset:0}.wire{fill:none;stroke:#38e0ff4d;stroke-width:1.5px;stroke-dasharray:3 7;stroke-linecap:round}.packet{fill:#38e0ff;opacity:.9}.bases{grid-template-columns:repeat(auto-fill,minmax(min(100%,300px),1fr));align-items:start;gap:30px;display:grid;position:relative}.empty{color:var(--dim);text-align:center;padding:120px 0;position:relative}.base{border:1px solid var(--line);background:linear-gradient(#16213c,#0c1324);border-radius:18px;padding:16px;position:relative;box-shadow:0 18px 40px #00000059}.base.head{background:linear-gradient(#ffd1661a,#0000 38%),linear-gradient(#16213c,#0c1324)}.base[data-mark=waiting]{box-shadow:0 18px 40px #00000059,inset 0 0 0 999px #ffb02e0b}.base>header{align-items:center;gap:14px;margin-bottom:14px;display:flex}.title{justify-items:start;min-width:0;margin-right:auto;display:grid}.title h2{white-space:nowrap;text-overflow:ellipsis;max-width:100%;font-size:17px;overflow:hidden}.title p{max-width:100%;color:var(--dim);white-space:nowrap;text-overflow:ellipsis;font-size:12px;overflow:hidden}.crown{letter-spacing:.12em;text-transform:uppercase;color:var(--gold);background:#ffd1661f;border-radius:999px;align-items:center;gap:5px;margin-bottom:3px;padding:2px 9px 2px 6px;font-size:10px;display:inline-flex}.crown svg{width:14px;height:14px}.base:not(.head) .crown{display:none}.tower{width:80px;height:80px;color:var(--c);flex:none;place-items:center;display:grid;position:relative}.tower:before{content:"";background:radial-gradient(circle at 35% 30%, color-mix(in srgb, var(--c) 24%, #101a2e), #0b1222 70%);border:1px solid color-mix(in srgb, var(--c) 45%, transparent);border-radius:50%;position:absolute;inset:0}.tower svg{width:48px;height:48px;filter:drop-shadow(0 0 6px color-mix(in srgb, var(--c) 70%, transparent));position:relative}.tower.ghost{opacity:.45}.tower.ghost:before{border-style:dashed}.sweep,.pulse{opacity:0;pointer-events:none;border-radius:50%;position:absolute;inset:0}.sweep{background:conic-gradient(from 0deg, transparent 0 70%, color-mix(in srgb, var(--c) 55%, transparent) 100%)}.pulse{border:2px solid var(--c)}.tower[data-mark=working] .sweep{opacity:1;animation:3.2s linear infinite spin}.tower[data-mark=waiting] .pulse{animation:1.5s ease-out infinite ping}.tower>.badge{--s:40px;position:absolute;bottom:-2px;right:-2px}.tabs{gap:12px;display:grid}.tab{background:#ffffff06;border:1px solid #ffffff0d;border-radius:14px;padding:10px}.tab h3{color:var(--dim);letter-spacing:.12em;text-transform:uppercase;margin-bottom:8px;font-size:11px;font-weight:600}.units,.subs{gap:8px;display:grid}.outposts{gap:8px;margin-top:12px;display:grid}.outposts:empty{display:none}.outposts:before{content:"Outposts";color:var(--dim);letter-spacing:.12em;text-transform:uppercase;font-size:11px}.card{background:#ffffff09;border:1px solid #ffffff0f;border-radius:12px;align-items:center;gap:10px;padding:8px 10px;transition:background-color .4s;display:flex;position:relative}.unit[data-mark=waiting]>.card{background:#ffb02e1c}.unit[data-mark=error]>.card{background:#ff54701c}.unit[data-mark=working]>.card{background:#38e0ff12}.plate{width:var(--s,40px);height:var(--s,40px);color:var(--c);background:radial-gradient(circle at 35% 30%, color-mix(in srgb, var(--c) 26%, #101a2e), #0b1222 72%);border-radius:50%;flex:none;place-items:center;transition:color .4s;display:grid;position:relative}.plate svg{width:calc(var(--s,40px) * .55);height:calc(var(--s,40px) * .55)}.ring,.plate:after{pointer-events:none;border-radius:50%;position:absolute;inset:-3px}.ring{opacity:0;background:conic-gradient(from 0deg, transparent 0 55%, var(--c));-webkit-mask:radial-gradient(farthest-side,#0000 calc(100% - 3px),#000 calc(100% - 2px));mask:radial-gradient(farthest-side,#0000 calc(100% - 3px),#000 calc(100% - 2px))}.plate[data-mark=working]>.ring{opacity:1;animation:1.1s linear infinite spin}.plate:after{content:"";border:2px solid var(--c);opacity:0}.plate[data-mark=waiting]:after{animation:1.5s ease-out infinite ping}.badge{width:clamp(16px, calc(var(--s,40px) * .3), 24px);height:clamp(16px, calc(var(--s,40px) * .3), 24px);font:700 10px/1 var(--mono);color:#06101e;background:var(--c);border-radius:50%;flex:none;place-items:center;transition:background-color .4s;display:grid}.plate>.badge{position:absolute;bottom:-4px;right:-4px}[data-mark=waiting]>.badge:after{content:"?"}[data-mark=idle]>.badge:after{content:"z"}[data-mark=done]>.badge:after{content:"✓"}[data-mark=error]>.badge:after{content:"!"}[data-mark=unknown]>.badge:after{content:"·"}[data-mark=working]>.badge:after{content:"";border:2px solid #06101e;border-right-color:#0000;border-radius:50%;width:8px;height:8px;animation:.8s linear infinite spin}[data-mark=waiting]>.badge{animation:1.2s ease-in-out infinite hop}.meta{min-width:0;display:grid}.name{white-space:nowrap;text-overflow:ellipsis;font-weight:600;overflow:hidden}.line{color:var(--dim);align-items:baseline;gap:8px;min-width:0;font-size:12px;display:flex}.role{letter-spacing:.1em;text-transform:uppercase;color:color-mix(in srgb, var(--c) 70%, var(--dim));font-size:10px;font-style:normal}.note{white-space:nowrap;text-overflow:ellipsis;overflow:hidden}.unit[data-mark=idle]>.card{opacity:.85}.subs{margin:6px 0 0 22px;padding-left:18px;position:relative}.subs:empty{display:none}.subs:before{content:"";background:repeating-linear-gradient(#7a98e080 0 4px,#0000 4px 8px);width:1px;position:absolute;top:-4px;bottom:22px;left:3px}.subs>.unit>.card:before{content:"";background:repeating-linear-gradient(90deg,#7a98e080 0 4px,#0000 4px 8px);width:15px;height:1px;position:absolute;top:50%;left:-15px}.subs>.unit{position:relative}.subs .plate{--s:30px}.subs .name{font-size:13px}.side{gap:16px;display:grid;position:sticky;top:96px}.panel{border:1px solid var(--line);background:var(--panel);border-radius:18px;padding:14px 16px}.panel h2{color:var(--dim);letter-spacing:.14em;text-transform:uppercase;margin-bottom:10px;font-size:11px}.net-log summary{cursor:pointer;justify-content:space-between;align-items:center;list-style:none;display:flex}.net-log summary::-webkit-details-marker{display:none}.net-log summary:after{content:"▾";color:var(--dim);transition:transform .2s}.net-log:not([open]) summary:after{transform:rotate(-90deg)}.net-log:not([open]) h2{margin-bottom:0}.log{gap:7px;max-height:360px;padding:0;list-style:none;display:grid;overflow:hidden}.log:empty:before{content:"Waiting for changes…";color:var(--dim);font-size:12px}.log li{grid-template-columns:auto 1fr;gap:2px 8px;font-size:12px;animation:.4s ease-out both slide;display:grid}.log li:before{content:"";background:var(--c);border-radius:50%;width:8px;height:8px;margin-top:5px}.log time{font:11px var(--mono);color:var(--dim);grid-column:2}.credit{color:var(--dim);padding:0 6px;font-size:11px}.legend{gap:8px;padding:0;font-size:12px;list-style:none;display:grid}.legend li{align-items:center;gap:9px;display:flex}.enter{animation:.5s cubic-bezier(.2,.9,.3,1.15) both pop}.leaving{pointer-events:none;animation:.35s ease-in both out}.flash>.card>.plate,.flash.tower{animation:.6s ease-out flash}@keyframes spin{to{transform:rotate(360deg)}}@keyframes ping{0%{opacity:.6;transform:scale(1)}to{opacity:0;transform:scale(1.7)}}@keyframes hop{50%{transform:translateY(-3px)}}@keyframes blink{50%{opacity:.35}}@keyframes pop{0%{opacity:0;transform:scale(.92)translateY(8px)}}@keyframes out{to{opacity:0;transform:scale(.94)}}@keyframes flash{40%{transform:scale(1.12)}}@keyframes slide{0%{opacity:0;transform:translate(10px)}}@keyframes zoom{0%{opacity:0;transform:scale(.45)}}[hidden]{display:none!important}a{color:inherit}.crumbs,.tally dt,.comms-head small,.tx.you>header,.notice em,.rule summary,.dossier-in>.tag,.d-drones h4,.d-docs h4,.specs dt,.stop,.dialog .tag,.intel h4,.pending b{color:var(--dim);letter-spacing:.14em;text-transform:uppercase;font-size:11px;font-style:normal}.token .name,.drone .note,.count small,.sum,.where,.tile b{white-space:nowrap;text-overflow:ellipsis;max-width:100%;overflow:hidden}.md a,.bubble a,.notice a,.d-note a,.specs a,.crumbs a{color:#38e0ff}.crumbs{gap:10px;margin:14px 6px 0;display:flex}.crumbs>*+:before{content:"›";opacity:.5;margin-right:10px}.crumbs a,.brand{text-decoration:none}.work{align-items:flex-start;gap:20px;min-width:0;display:flex}.view{flex:1 1 0;min-width:0}body[data-stale=true] .work{opacity:.45}.enter-zoom{transform-origin:var(--ox,50%) var(--oy,0);animation:.5s ease-out both zoom}.enter-slide,.dossier{animation:.4s ease-out both slide}.enter-back{animation:.4s ease-out both pop}.hit,.enter-base:after{z-index:1;border-radius:inherit;position:absolute;inset:0}.enter-base:after{content:"";z-index:0}.hit:hover{box-shadow:inset 0 0 0 1px var(--c)}.base>header{position:relative}.enter-base{text-decoration:none}.base>header:hover h2{color:#38e0ff}.alerts{width:230px;color:inherit;font:inherit;text-align:left;cursor:pointer;align-items:center;gap:10px;display:flex;position:relative}.alerts:disabled{cursor:default;opacity:.6}.alerts:not(:disabled){border-color:var(--c4);background:var(--c1)}.faces{flex:none;padding-left:10px;display:flex}.faces .plate{--s:28px;margin-left:-10px;box-shadow:0 0 0 2px #0e1628}.faces .badge{display:none}.count{min-width:0;display:grid}.count b{font:600 18px/1.1 var(--mono)}.count small,.comms-head p,.cmp-note,.where,.queued,.d-line,.who p{color:var(--dim);font-size:12px}.token{border:1px solid #0000;border-radius:16px;justify-items:center;gap:3px;width:100%;padding:10px 8px 9px;text-decoration:none;display:grid;position:relative}a.token:hover{border-color:var(--c4)}.token[data-mark=waiting]{background:#ffb02e1c}.token[data-mark=error]{background:#ff54701c}.token .plate{--s:64px;margin-bottom:4px}.token .name{font-weight:600}.token .line{justify-items:center;max-width:100%;display:grid}.drone{width:94px;padding:6px;font-size:12px}.drone .plate{--s:44px}.drone .role{display:none}.flash.token>.plate{animation:.6s ease-out flash}.zoom-in,.comms-in{border:1px solid var(--line);background:var(--grid), #080d1a8c;border-radius:22px;position:relative}.zoom-in{min-height:420px;padding:24px}.zoom-in.void>:not(.lost){display:none}.lost,.chat-empty{color:var(--dim);text-align:center;padding:60px 0}.keep{align-items:center;gap:20px;padding:18px 22px;display:flex}.keep h2{font-size:24px}.tower.big{width:120px;height:120px}.tower.big svg{width:72px;height:72px}.tower.big>.badge{--s:96px;bottom:4px;right:4px}a.tower:hover:before{border-color:var(--c)}.tally{gap:10px;display:flex}.squads,.camps{grid-template-columns:repeat(auto-fit,minmax(min(100%,430px),1fr));align-items:start;gap:18px;margin-top:20px;display:grid}.squad{background:#0c1324c7;padding:14px 16px 18px}.squad[data-mark=waiting]{background:#262121d9}.squad h3{align-items:center;gap:10px;margin-bottom:14px;display:flex}.squad h3 b,.crumbs b{color:var(--text)}.squad h3 small{margin-left:auto}.squad h3 svg{width:18px;height:18px}.flag{background:var(--c);width:10px;height:10px;box-shadow:0 0 8px var(--c);border-radius:2px;transform:rotate(45deg)}.formation{flex-wrap:wrap;align-items:flex-start;gap:16px 20px;display:flex}.col{justify-items:center;gap:8px;width:196px;display:grid}.say{width:100%;color:var(--dim);background:#111a2f;border-radius:12px;margin-top:4px;padding:7px 10px;font-size:12px;line-height:1.35}.say p{overflow-wrap:anywhere;max-height:4.05em;overflow:hidden}.drones{flex-wrap:wrap;justify-content:center;gap:8px;width:100%;padding-top:16px;display:flex;position:relative}.drones:empty{display:none}.drones:before{content:"";background:repeating-linear-gradient(#7a98e099 0 4px,#0000 4px 8px);width:1px;height:14px;position:absolute;top:0;left:50%}.camp .token{width:156px}[data-group=outside] .plate{filter:saturate(.3);outline-offset:4px;outline:1px dashed #8b99ba8c}.comms-in,.tile{height:calc(var(--vvh,100vh) - 190px);grid-template-rows:auto minmax(0,1fr) auto;min-height:380px;display:grid;overflow:hidden}.comms-head,.tile>header{background:var(--panel);align-items:center;gap:12px;padding:12px 18px;display:flex}.ava{color:#38e0ff;background:radial-gradient(circle at 35% 30%,#38e0ff47,#0b1222 72%);border-radius:50%;flex:none;place-items:center;width:30px;height:30px;display:grid}.ava svg{width:58%;height:58%}.comms-head h2{font-size:16px}.chan{margin-left:auto}.chat-root{min-height:0;display:grid;position:relative}.comms-in .ask{max-height:calc((var(--vvh,100vh) - 190px) * .55);overscroll-behavior:contain;overflow-y:auto}.chat-scroll{overflow-anchor:none;padding:16px 20px;overflow-y:auto}.chat-list{gap:14px;display:grid}.chat-top{height:1px}.chat-live{color:var(--c);align-items:center;gap:10px;margin-top:14px;padding-left:44px;font-size:12px;display:flex}.chat-pill{--c:#38e0ff;border:0;position:absolute;bottom:14px;left:50%;transform:translate(-50%)}.lit:after{content:"";background:#38e0ff2e;border-radius:14px;animation:1s 1s forwards out;position:absolute;inset:0}.composer{border-top:1px solid var(--line);background:var(--panel);gap:8px;padding:12px 18px 14px;display:grid}.cmp-row,.cmp-acts,.cmp-foot,.cmp-note{align-items:center;gap:8px;display:flex}.cmp-row{align-items:flex-end;gap:10px}.composer textarea{resize:none;border:1px solid var(--line);min-width:0;color:var(--text);font:14px/1.4 var(--sans);background:#070c18;border-radius:12px;flex:1;padding:9px 12px}.composer textarea:focus{border-color:#38e0ff80;outline:none;box-shadow:0 0 0 3px #38e0ff1f}.composer button:disabled{opacity:.45;cursor:default}.cmp-note{flex-wrap:wrap;flex:1;min-height:18px}.cmp-note[data-tone=error],.cmp-alt.cmp-stop{color:#ff8a9c}.cmp-note[data-tone=ok]{color:#3ddc97}.cmp-note[data-tone=ask]{color:var(--gold)}.cmp-link{color:#38e0ff;font:12px var(--sans);cursor:pointer;background:0 0;border:0;padding:0}.cmp-yes{background:var(--gold);color:#06101e;font:600 12px var(--sans);cursor:pointer;border:0;border-radius:8px;padding:3px 10px}.tx{content-visibility:auto;contain-intrinsic-size:auto 90px;min-width:0}.tx time,.tx.note{color:var(--dim);font:11px var(--mono)}.tx.you{justify-items:end;gap:5px;display:grid}.tx.you>header{color:var(--gold);align-items:center;gap:8px;display:flex}.tx.you .ava{color:var(--gold)}.bubble{white-space:pre-wrap;overflow-wrap:anywhere;background:#ffd16614;border:1px solid #ffd16638;border-radius:14px;max-width:80%;padding:9px 13px}.tx.in{grid-template-columns:34px minmax(0,1fr);align-items:start;gap:10px;display:grid}.tx-body{border:1px solid var(--line);background:#0e1628d9;border-radius:14px;gap:8px;padding:10px 14px;display:grid}.tx-body,.tx-body>*{min-width:0}.tx-body>header{color:var(--dim);font:11px var(--mono);align-items:baseline;gap:10px;display:flex}.tx-body>header b{color:#38e0ff;font:600 13px var(--sans)}.stop,.dialog .tag{background:var(--c1);color:var(--c);border-radius:999px;padding:1px 8px;font-size:10px}.fault{color:#ff8a9c;font-size:13px}.thought,.more,.tab-btn{border:1px solid var(--line);color:var(--dim);font:11px var(--sans);cursor:pointer;background:0 0;border-radius:999px;justify-self:start;padding:2px 10px}.thought-text:empty{display:none}.thought-text,.out,.args,.notice pre,.rule pre{white-space:pre-wrap;overflow-wrap:anywhere;color:var(--dim);font:12px/1.4 var(--mono)}.act,.notice{background:#ffffff08;border:1px solid #ffffff12;border-radius:10px}.act>summary,.notice>summary,.rule summary{cursor:pointer;align-items:center;gap:9px;min-width:0;padding:6px 10px;font-size:12.5px;list-style:none;display:flex}summary::-webkit-details-marker{display:none}.act code{color:var(--c);font:600 11px var(--mono);letter-spacing:.06em;text-transform:uppercase}.sum{min-width:0;color:var(--dim)}.act[open] .sum{white-space:normal}.open{flex:none;margin-left:auto}.act>pre,.result{margin:0 10px 8px}.result{gap:6px;display:grid}.tx.sys{padding-left:44px}.notice{background:#966eff12;border-color:#966eff33}.notice .ava{width:22px;height:22px}.notice pre,.rule pre{padding:0 10px 8px}.rule summary{justify-content:center}.rule summary:before,.rule summary:after{content:"";background:var(--line);flex:1;height:1px}.tx.note{text-align:center}.dossier-in{text-align:center;justify-items:center;gap:6px;display:grid}.dossier-in>.tag{text-align:left;justify-self:stretch}.portrait{background:radial-gradient(var(--c1), transparent 70%);border-radius:50%;padding:8px}.portrait .plate{--s:96px}.dossier-in h3{overflow-wrap:anywhere;margin-top:4px;font-size:18px}.d-line,.who p{align-items:baseline;gap:8px;display:flex}.d-note{background:#ffb02e1f;border-radius:10px;padding:8px 10px;font-size:12px}.specs{text-align:left;justify-self:stretch;gap:2px;margin-top:10px;display:grid}.specs div{background:#ffffff09;grid-template-columns:76px minmax(0,1fr);gap:8px;padding:7px 10px;display:grid}.specs dt{font-size:10px}.specs dd{overflow-wrap:anywhere;font-size:12.5px}.d-drones{margin-top:8px}.d-drones .formation{justify-content:center;gap:6px}.md{overflow-wrap:anywhere;line-height:1.5}.md p,.md ul,.md ol,.md pre,.md table,.md blockquote{margin:0 0 10px}.md>:last-child{margin-bottom:0}.md :is(h1,h2,h3,h4,h5,h6){margin:14px 0 6px;font-size:15px}.md>:first-child{margin-top:0}.md h1{font-size:19px}.md ul,.md ol{padding-left:22px}.md li>p{margin:0}.md li.task{margin-left:-18px;list-style:none}.check{vertical-align:-2px;text-align:center;width:14px;height:14px;font:700 10px/12px var(--mono);border:1px solid;border-radius:4px;margin-right:7px;display:inline-block}.check[data-done=true]{color:#06101e;background:#3ddc97}.md code{font:12.5px var(--mono);background:#ffffff14;border-radius:5px;padding:1px 5px}.md pre{border:1px solid var(--line);background:#070c18;border-radius:10px;padding:10px 12px;overflow-x:auto}.md pre code{background:0 0;padding:0}.md blockquote{color:var(--dim);background:#ffffff0a;padding:6px 12px}.md table{border-collapse:collapse;display:block;overflow-x:auto}.md th,.md td{border:1px solid var(--line);padding:4px 10px}.tiles{flex:1 1 0;gap:20px;min-width:0;display:flex}.tile-tabs{display:none}.tile{border:1px solid var(--line);background:var(--panel);border-radius:22px;flex:1 1 0;min-width:0;position:sticky;top:96px}.tile>header svg{width:22px;height:22px;color:var(--gold)}.tile{grid-template-columns:minmax(0,1fr)}.tile>header b{flex:1;min-width:0}.tile-body{padding:16px 20px;overflow:auto}.tiles.tabbed{flex-direction:column;gap:8px}.tiles.tabbed .tile-tabs{gap:6px;display:flex}.tiles.tabbed .tile:not(.active){display:none}.tiles.tabbed .tile{flex:none}.tab-btn.active,.open{color:var(--gold);border-color:#ffd16666}.dock{z-index:20;pointer-events:none;width:min(300px,100vw - 44px);display:grid;position:fixed;top:108px;right:max(22px,50vw - 718px)}.dialog{pointer-events:auto;border:1px solid var(--c4);background:linear-gradient(var(--c1), #0c1324 70%), #121a30;transform-origin:top;border-radius:18px;grid-area:1/1;padding:14px 16px 16px;transition:transform .3s,opacity .3s;position:relative;box-shadow:0 24px 60px #000c}.dialog:focus{outline:none}.dialog{overscroll-behavior:contain;max-height:calc(100dvh - 128px);overflow-y:auto}.dialog>header{align-items:center;gap:8px;min-width:0;margin-bottom:12px;display:flex}.where{min-width:0}.x{color:var(--dim);cursor:pointer;background:0 0;border:0;margin-left:auto;font-size:20px}.who{align-items:center;gap:14px;display:flex}.who .plate{--s:56px}.who h3{font-size:17px}.intel{background:#060a148c;border-radius:12px;margin-top:12px;padding:10px 12px}.intel h4{margin-bottom:4px}.said{overflow-wrap:anywhere;font-size:13px;line-height:1.45}.pending{color:var(--c);font:12px var(--mono);overflow-wrap:anywhere;margin-top:8px}.dialog footer{flex-wrap:wrap;align-items:center;gap:10px;margin-top:14px;display:flex}.ask{background:#ffb02e0f;border:1px solid #ffb02e59;border-radius:14px;gap:8px;margin:10px 18px 0;padding:12px 14px;display:grid}.dialog .ask{margin:10px 0 0}.ask-one{gap:8px;display:grid}.ask-one+.ask-one{margin-top:8px}.ask-head{color:var(--gold);letter-spacing:.06em;text-transform:uppercase;background:#ffb02e24;border-radius:999px;justify-self:start;padding:1px 9px;font-size:11px}.ask-how{color:var(--dim);font-size:12px}.ask-q{white-space:pre-wrap;overflow-wrap:anywhere;font-weight:600}.ask-ctx,.ask-why{color:var(--dim);white-space:pre-wrap;overflow-wrap:anywhere;max-height:9em;font-size:13px;overflow:auto}.ask-opts{counter-reset:opt;gap:6px;max-height:16em;margin:0;padding:0;list-style:none;display:grid;overflow:auto}.ask-opt{border:1px solid var(--line);overflow-wrap:anywhere;counter-increment:opt;background:#070c18;border-radius:10px;gap:2px;padding:7px 12px;font-size:13px;display:grid}.ask-opt b:before{content:counter(opt) ". ";color:var(--dim)}.ask-opt b[data-n]:before{content:attr(data-n) ". "}.ask-opt small{color:var(--dim);font-size:12px}.ask-acts{flex-wrap:wrap;align-items:center;gap:6px 14px;display:flex}.ask-acts>:last-child{margin-left:auto}.ask-term{color:var(--gold);font-size:12px}.ask button:disabled{opacity:.45;cursor:default}.ask-note:empty{display:none}.ask-note{color:var(--dim);flex-wrap:wrap;align-items:center;gap:4px 10px;font-size:12px;display:flex}.ask-note[data-tone=error]{color:#ff8a9c}.ask-note[data-tone=ok]{color:#3ddc97}.ask-title{font:12.5px/1.45 var(--mono);white-space:pre-wrap;overflow-wrap:anywhere;max-height:12em;margin:0;overflow:auto}div.ask-opts{max-height:none}button.ask-opt{text-align:left;color:var(--text);font:13px var(--sans);cursor:pointer}.ask-opt[aria-checked=true]{background:#ffb02e1f;border-color:#ffb02e}.ask-own{border:1px solid var(--line);min-width:0;color:var(--text);font:13px var(--sans);background:#070c18;border-radius:10px;padding:7px 12px}.ask-memo{font-size:12px}.ask input:disabled{opacity:.45}.ask-send{color:#06101e;font:600 13px var(--sans);cursor:pointer;background:#ffb02e;border:0;border-radius:10px;padding:7px 13px}.go,.later,.chat-pill,.cmp-send,.cmp-alt{font:600 13px var(--sans);cursor:pointer;border-radius:10px;padding:7px 13px;text-decoration:none}.go,.chat-pill{background:var(--c);color:#06101e}.cmp-send{color:#06101e;background:#38e0ff;border:0}.later,.cmp-alt{border:1px solid var(--line);color:var(--text);background:0 0}.queued{color:var(--dim);flex:1 0 100%;font-size:12px}.queued:empty{display:none}html.paused *,html.paused :before,html.paused :after{animation-play-state:paused!important}@media (width<=1100px){.stage{grid-template-columns:1fr}.side{position:static}.stats{display:none}}.term-tile{grid-template-rows:auto minmax(0,1fr) auto;gap:10px;height:100%;display:grid}.tile-body:has(.term-tile){padding:12px 14px;overflow:hidden}.term-bar{justify-content:space-between;align-items:center;gap:12px;font-size:12px;display:flex}.term-state{color:var(--dim)}.term-state[data-tone=ok]{color:#3ddc97}.term-state[data-tone=error]{color:#ff8a9c}.term-scroll{overscroll-behavior:contain;border:1px solid var(--line);background:#090d18;border-radius:14px;padding:8px 10px;overflow:auto}.term{color:#cdd6f4;width:max-content;min-width:100%;font:12px/1.3 var(--mono);white-space:pre;transition:opacity .2s}.term .tr{min-height:1.3em}.term.stale{opacity:.5}.term.hidden{display:none}.d-term{margin-top:12px;font-size:13px}.plate[data-docs]:before{content:attr(data-docs);z-index:1;color:#3b2a06;min-width:10px;height:16px;font:700 9px/16px var(--mono);text-align:center;background:linear-gradient(#f6e6b8,#d8bd78);border-radius:3px 6px;padding:0 3px;position:absolute;top:-5px;left:-5px;box-shadow:0 0 0 2px #0b1222,2px 2px 0 1px #8a6f33}.d-docs{text-align:left;margin-top:12px}.d-docs h4{text-align:center;margin:0 0 6px}.d-doc-list{gap:6px;display:grid}.d-doc{width:100%;color:var(--text);font:13px var(--sans);text-align:left;cursor:pointer;background:#d8bd7812 linear-gradient(#f6e6b8,#d8bd78) 9px/13px 16px no-repeat;border:1px solid #d8bd7840;border-radius:10px;gap:1px;padding:7px 10px 7px 30px;display:grid}.d-doc:hover{border-color:#d8bd78aa}.d-doc b{overflow-wrap:anywhere;font-weight:600}.d-doc small{color:var(--dim);font-size:11px}.d-docs-cut{color:var(--dim);margin:6px 0 0;font-size:12px}.doc-tile{grid-template-rows:auto minmax(0,1fr);gap:10px;height:100%;display:grid}.tile-body:has(.doc-tile){padding:12px 14px;overflow:hidden}.doc-bar{flex-wrap:wrap;justify-content:flex-start}.doc-why{min-width:0;color:var(--text);flex:160px}.doc-off{color:var(--gold);font-style:normal}.doc-acts{gap:14px;display:flex}.doc-frame{background:#fff;border:1px solid #d8bd7855;border-radius:12px;width:100%;height:100%;min-height:240px}.doc-text{background:#0b1222;border:1px solid #d8bd7840;border-radius:12px;padding:14px 18px;overflow:auto}.doc-pre{white-space:pre-wrap;font:12.5px/1.45 var(--mono);margin:0}.pad{gap:6px;display:grid}.pad-row{grid-template-columns:repeat(auto-fit,minmax(48px,1fr));gap:6px;display:grid}.pad-key{border:1px solid var(--line);min-height:34px;color:var(--text);font:600 12px var(--mono);cursor:pointer;background:#0b1224;border-bottom-width:3px;border-radius:9px;padding:4px 6px}.pad-key:active:not(:disabled){background:#13203e;border-bottom-width:1px;transform:translateY(2px)}.pad-key:disabled{opacity:.35;cursor:default}.pad-key[data-key=enter]{color:#38e0ff}.pad-key[data-key=esc]{color:var(--gold)}.pad-key:is([data-key=up],[data-key=down],[data-key=left],[data-key=right],[data-key=backspace]){font-size:16px}.pad-digits summary{color:var(--dim);cursor:pointer;padding:2px 0;font-size:12px}.pad-note:empty{display:none}.pad-note{color:var(--dim);flex-wrap:wrap;align-items:center;gap:6px 12px;margin:0;font-size:12px;display:flex}.pad-note[data-tone=error]{color:#ff8a9c}.pad-note[data-tone=ask]{color:var(--text);background:#ffb02e14;border:1px solid #ffb02e66;border-radius:10px;padding:8px 10px}@media (width<=640px){.pad-key,.pad-yes{min-height:44px}.pad-digits summary{padding:14px 0}}.pad-yes{color:#06101e;font:600 12px var(--sans);cursor:pointer;background:#ffb02e;border:0;border-radius:9px;padding:5px 11px}.c0{color:#45475a}.cz{color:#090d18}.c1{color:#f38ba8}.c2{color:#a6e3a1}.c3{color:#f9e2af}.c4{color:#89b4fa}.c5{color:#f5c2e7}.c6{color:#94e2d5}.c7{color:#bac2de}.c8{color:#585b70}.c9{color:#f37799}.c10{color:#89d88b}.c11{color:#ebd391}.c12{color:#74a8fc}.c13{color:#f2aede}.c14{color:#6bd7ca}.c15{color:#a6adc8}.k0{background:#090d18}.k1{background:#f38ba8}.k2{background:#a6e3a1}.k3{background:#f9e2af}.k4{background:#89b4fa}.k5{background:#f5c2e7}.k6{background:#94e2d5}.k7,.kz{background:#bac2de}.k8{background:#585b70}.k9{background:#f37799}.k10{background:#89d88b}.k11{background:#ebd391}.k12{background:#74a8fc}.k13{background:#f2aede}.k14{background:#6bd7ca}.k15{background:#a6adc8}.tb{font-weight:700}.td{opacity:.6}.ti{font-style:italic}.tu{text-decoration:underline}.ts{text-decoration:line-through}.tu.ts{text-decoration:underline line-through}@media (width<=900px){.work{flex-direction:column;align-items:stretch}.view,.tiles{flex:none}.tile{position:static}.keep{flex-wrap:wrap}}@media (width<=640px){.shell{padding:max(8px, env(safe-area-inset-top)) max(8px, env(safe-area-inset-right)) calc(24px + env(safe-area-inset-bottom)) max(8px, env(safe-area-inset-left))}.hud{flex-wrap:wrap;gap:10px;padding:10px 12px;position:static}.brand p,.link span{display:none}.alerts{flex:100%;order:3;width:auto}.map,.zoom-in{padding:14px}.bases{gap:18px}.keep{gap:12px;padding:6px}.tower.big{width:84px;height:84px}.tally{flex-wrap:wrap}.col{width:100%}.comms-in,.tile{height:calc(var(--vvh,100dvh) - 16px);border-radius:16px;min-height:300px}.chat-scroll{padding:12px}.bubble{max-width:92%}.composer{padding:10px 12px calc(10px + env(safe-area-inset-bottom))}.cmp-row{flex-wrap:wrap}.composer textarea{flex:100%;min-height:64px}.cmp-acts{margin-left:auto}.log{max-height:none}.log li:nth-child(n+3){display:none}.dock{top:auto;right:10px;bottom:calc(10px + env(safe-area-inset-bottom));width:calc(100vw - 20px)}}@media (pointer:coarse){.composer textarea,.ask input{font-size:16px}.composer button,.go,.later,.x,.chat-pill,.pair-btn,.alerts,.ask button,.term-bar button,.d-term,.d-doc{min-height:40px}.pad-key,.pad-yes{min-height:44px}.pad-digits summary{padding:14px 0}}@media (prefers-reduced-motion:reduce){*,:before,:after{transition:none!important;animation:none!important}}
@@ -0,0 +1 @@
1
+ var e={working:`started working`,waiting:`is waiting on a question`,idle:`went idle`,done:`finished`,error:`hit an error`,unknown:`lost its status`};function t(e){let t=new Map;for(let n of e.bases){n.tower&&t.set(`tower:${n.id}`,{name:`${n.label} tower`,mark:n.tower.mark});for(let e of n.tabs.flatMap(e=>e.units))for(let n of[e,...e.subs])t.set(n.id,{name:n.name,mark:n.mark});for(let e of n.outposts)t.set(e.id,{name:e.label,mark:e.mark})}return t}function n(n,r){if(!n)return[];let[i,a]=[t(n),t(r)],o=[];for(let[t,n]of a){let r=i.get(t);r?r.mark!==n.mark&&o.push({text:`${n.name} ${e[n.mark]}`,mark:n.mark}):o.push({text:`${n.name} joined`,mark:n.mark})}for(let[e,t]of i)a.has(e)||o.push({text:`${t.name} left`,mark:`idle`});return o}var r=e=>e===`waiting`||e===`error`?0:1;function i(e,t){let n=new Map;for(let r of e.bases)for(let e of[...r.tower?[{...r.tower,role:`tower`}]:[],...[...r.tabs.flatMap(e=>e.units),...r.outside].flatMap(e=>[e,...e.subs])])n.set(e.key,{key:e.key,ws:r.id,name:e.name,role:e.role,mark:e.mark,note:e.note,at:t,pending:e.pending});return n}function a(e,t){return[...t.values()].filter(t=>e?.get(t.key)?.mark!==t.mark&&(r(t.mark)===0||e!==void 0&&t.mark===`done`))}var o=e=>{try{return decodeURIComponent(e)}catch{return e}},s=e=>/^\.+$/.test(e)?e.replaceAll(`.`,`%2E`):encodeURIComponent(e);function c(e){let[t,n]=e.split(`#`)[0].split(`?`),[r,i,a,s]=t.replace(/^\/+|\/+$/g,``).split(`/`).map(o);if(r!==`w`||!i)return{view:`map`};if(a!==`s`||!s)return{view:`workspace`,ws:i};let c=new URLSearchParams(n).get(`at`);return c?{view:`session`,ws:i,key:s,at:c}:{view:`session`,ws:i,key:s}}var l=e=>e.view===`map`?``:e.view===`workspace`?`w/${s(e.ws)}`:`w/${s(e.ws)}/s/${s(e.key)}${e.at?`?at=${encodeURIComponent(e.at)}`:``}`,u=e=>e.view===`session`?{view:`workspace`,ws:e.ws}:{view:`map`},d=e=>e.view===`session`?e.key:e.view===`workspace`?`w:${e.ws}`:null;function ee(e,t=new URL(document.baseURI).pathname){let n=()=>c(location.pathname.slice(t.length)+location.search),r={route:n,href:e=>l(e)||`./`,go(n){let r=t+l(n);location.pathname+location.search!==r&&history.pushState(null,``,r),e(n)}};return addEventListener(`popstate`,()=>e(n())),document.addEventListener(`click`,e=>{let t=e.target?.closest?.(`a[data-route]`);!t||e.defaultPrevented||e.button!==0||e.metaKey||e.ctrlKey||e.shiftKey||e.altKey||(e.preventDefault(),r.go(c(t.getAttribute(`data-route`))))}),r}var f={main:`main`,member:`member`,subagent:`subagent`,outpost:`outpost`,outside:`outside`,tower:`tower`},p={working:`working`,waiting:`waiting on a question`,idle:`idle`,done:`finished`,error:`error`,unknown:`no status`},m=e=>e===`waiting`||e===`error`,h=e=>{let t=new DOMParser().parseFromString(e.trim(),`text/html`).body.firstElementChild;if(!(t instanceof HTMLElement))throw Error(`bad template`);return document.importNode(t,!0)},g=(e,t)=>{let n=document.getElementById(e);if(!(n instanceof t))throw Error(`#${e} is missing from the page`);return n},_=(e,t,n)=>{let r=e.querySelector(t);r.textContent!==n&&(r.textContent=n)},v=(e,t)=>{if(e.dataset.mark===t)return;let n=e.dataset.mark!==void 0;e.dataset.mark=t,n&&(e.classList.remove(`flash`),e.offsetWidth,e.classList.add(`flash`))},y=(e,t)=>e?e.replace(/^tool:/,`using `):p[t],b=(e,t)=>{e.toggleAttribute(`hidden`,!t),t&&(e.setAttribute(`href`,l(t)||`./`),e.setAttribute(`data-route`,l(t)))},x=`<div class="plate"><i class="ring"></i><svg><use href="#i-main"/></svg><b class="badge"></b></div>`;function S(e,t,n,r){v(e,n);let i=e.querySelector(`use`),a=`#i-${f[t]??`member`}`,o=r?.filter(e=>e.onScreen).length;i.getAttribute(`href`)!==a&&i.setAttribute(`href`,a),o?e.dataset.docs=String(o):delete e.dataset.docs}function C(e,t,n,r){let i=new Map;for(let t of e.children)!t.classList.contains(`leaving`)&&t.dataset.key&&i.set(t.dataset.key,t);let a=e.firstElementChild;for(let o of t){let t=i.get(o.id);for(t||(t=n(o),t.dataset.key=o.id,t.classList.add(`enter`),setTimeout(()=>t.classList.remove(`enter`),600)),i.delete(o.id);a?.classList.contains(`leaving`);)a=a.nextElementSibling;t===a?a=a.nextElementSibling:e.insertBefore(t,a),r(t,o)}for(let e of i.values())e.classList.add(`leaving`),setTimeout(()=>e.remove(),400)}var w=()=>h(`<div class="unit"><div class="card"><a class="hit"></a>${x}<div class="meta"><span class="name"></span><span class="line"><em class="role"></em><span class="note"></span></span></div></div><div class="subs"></div></div>`);function T(e,t){let n=e.closest(`.base`)?.dataset.key,r=e.querySelector(`.hit`);b(r,t.key&&n?{view:`session`,ws:n,key:t.key}:void 0),r.setAttribute(`aria-label`,`${t.name}: ${y(t.note,t.mark)}`),r.toggleAttribute(`data-alert`,m(t.mark)),v(e,t.mark),S(e.querySelector(`.plate`),t.role,t.mark,t.docs),_(e,`.name`,t.name),_(e,`.role`,t.role),_(e,`.note`,y(t.note,t.mark)),C(e.querySelector(`.subs`),t.subs??[],w,T)}var te=()=>h(`<section class="base"><header><div class="tower"><i class="sweep"></i><i class="pulse"></i><svg><use href="#i-tower"/></svg><b class="badge"></b></div><div class="title"><span class="crown"><svg><use href="#i-crown"/></svg>head tower</span><h2><a class="enter-base"></a></h2><p></p></div></header><div class="tabs"></div><div class="outposts"></div></section>`);function ne(e,t){v(e,t.mark),e.classList.toggle(`head`,t.head);let n=e.querySelector(`.tower`);n.classList.toggle(`ghost`,!t.tower),v(n,t.tower?.mark??`unknown`),_(e,`h2 a`,t.label),b(e.querySelector(`h2 a`),{view:`workspace`,ws:t.id}),_(e,`.title p`,t.tower?`control tower · ${p[t.tower.mark]}`:`no tower yet`),C(e.querySelector(`.tabs`),t.tabs,()=>h(`<div class="tab"><h3></h3><div class="units"></div></div>`),(e,t)=>{_(e,`h3`,`tab ${t.label}`),C(e.querySelector(`.units`),t.units,w,T)}),C(e.querySelector(`.outposts`),t.outposts.map(e=>({id:e.id,name:e.label,role:`outpost`,mark:e.mark,note:e.mark===`unknown`?`no agent running`:void 0})),w,T)}function re(e,t,n){let r=[...e.querySelectorAll(`.base:not(.leaving) .tower`)],i=e.querySelector(`.base.head:not(.leaving) .tower`),a=e.getBoundingClientRect(),o=e=>{let t=e.getBoundingClientRect();return[Math.round(t.x+t.width/2-a.x),Math.round(t.y+t.height/2-a.y)]},s=i?r.filter(e=>e!==i).map(e=>{let[[t,n],[r,a]]=[o(i),o(e)],s=Math.round((t+r)/2);return`M${t} ${n}C${s} ${n} ${s} ${a} ${r} ${a}`}):[],c=s.join(`|`);if(t.dataset.sig===c)return;t.dataset.sig=c;let l=(e,t,...n)=>{let r=document.createElementNS(`http://www.w3.org/2000/svg`,e);for(let[e,n]of Object.entries(t))r.setAttribute(e,n);return r.append(...n),r};t.replaceChildren(...s.flatMap(e=>[l(`path`,{class:`wire`,d:e}),...n?[]:[l(`circle`,{class:`packet`,r:`3`},l(`animateMotion`,{dur:`5s`,repeatCount:`indefinite`,path:e}))]]))}function ie(){let[e,t,n,r]=[g(`map`,HTMLElement),g(`bases`,HTMLElement),g(`links`,SVGSVGElement),g(`log`,HTMLElement)],i=matchMedia(`(prefers-reduced-motion: reduce)`).matches,a=()=>!e.hidden&&re(e,n,i);return new ResizeObserver(a).observe(e),{layout:a,stats(e){let t=e.bases.flatMap(e=>e.tabs.flatMap(e=>e.units.flatMap(e=>[e,...e.subs]))),n=e=>String(t.filter(t=>!e||t.mark===e).length);for(let[t,r]of[[`s-bases`,String(e.bases.length)],[`s-agents`,n()],[`s-working`,n(`working`)],[`s-waiting`,n(`waiting`)]])g(t,HTMLElement).textContent=r},show(e){C(t,e.bases,te,ne),g(`empty`,HTMLElement).hidden=e.bases.length>0,requestAnimationFrame(a)},log(e){let t=new Date().toLocaleTimeString([],{hour12:!1});for(let n of e){let e=h(`<li><span></span><time></time></li>`);e.dataset.mark=n.mark,_(e,`span`,n.text),_(e,`time`,t),r.prepend(e)}for(;r.children.length>12;)r.lastElementChild.remove()},pause(e){document.documentElement.classList.toggle(`paused`,e)},still(e){e?n.pauseAnimations():n.unpauseAnimations()}}}var ae=45e3,oe=1e3,se=3e4,ce=(e,t)=>Math.min(se,oe*2**(e-1))*(.8+.4*t);function E(e){try{return JSON.parse(e)}catch{return}}function le(e){let t,n,r,i=0,a={state:`connecting`,attempt:0},o=t=>e.onStatus(a={...a,...t}),s=()=>(e.clearTimeout(n),e.clearTimeout(r),n=r=void 0),c=()=>{let e=t;t=void 0,e?.close()},l=()=>{e.clearTimeout(r),r=e.setTimeout(()=>u(),ae),o({state:`live`,attempt:0,lastContact:e.now()})},u=()=>{c(),s();let t=a.attempt+1;if(t>8)return o({state:`offline`,attempt:t});o({state:`reconnecting`,attempt:t}),e.hidden()||(n=e.setTimeout(d,ce(t,e.random())))},d=()=>{c(),s(),i=e.now();let n=t=e.open();n.onError(()=>t===n&&u()),n.listen(`message`,r=>{if(t!==n)return;let i=E(r);i&&e.onNet(i),o({problem:i?void 0:`the server sent a snapshot the page cannot read`}),l()}),n.listen(`ping`,()=>t===n&&l()),n.listen(`problem`,e=>{t===n&&(o({problem:E(e)??`the server reported a problem the page cannot read`}),l())}),r=e.setTimeout(()=>u(),ae)};return{start(){o({state:`connecting`,attempt:0}),d()},resume(){let n=e.now()-i<2e3||a.lastContact!==void 0&&e.now()-a.lastContact<=2e4;t&&n||(o({attempt:0,state:a.state===`live`?`reconnecting`:a.state}),d())},restart:d,hide(){e.clearTimeout(n),n=void 0},stop(){c(),s()}}}var ue=(function(){let e=typeof document<`u`&&document.createElement(`link`).relList;return e&&e.supports&&e.supports(`modulepreload`)?`modulepreload`:`preload`})(),de=function(e,t){return new URL(e,t).href},fe={},pe=function(e){return e.pathname.endsWith(`.css`)},me=function(e,t,n){if(t in e)return e[t];let r=n();if(!r){e[t]=void 0;return}let i=r.then(()=>{e[t]=void 0},n=>{throw e[t]=void 0,n});return e[t]=i,i},D=function(e,t,n){let r=Promise.resolve();if(t&&t.length>0){let e,i=document.querySelector(`meta[property=csp-nonce]`),a=i?.nonce||i?.getAttribute(`nonce`);function o(e){return Promise.all(e.map(e=>Promise.resolve(e).then(e=>({status:`fulfilled`,value:e}),e=>({status:`rejected`,reason:e}))))}function s(e){return import.meta.resolve?new URL(import.meta.resolve(e)):new URL(e,import.meta.url)}r=o(t.map(t=>{t=de(t,n);let r=s(t),i=pe(r);return me(fe,r.href,()=>{if(e===void 0){e={all:new Set,styles:new Set};let t=document.getElementsByTagName(`link`);for(let n=t.length-1;n>=0;n--){let r=t[n];e.all.add(r.href),r.rel===`stylesheet`&&e.styles.add(r.href)}}if((i?e.styles:e.all).has(r.href))return;let t=document.createElement(`link`);if(t.rel=i?`stylesheet`:ue,i||(t.as=`script`),t.crossOrigin=``,t.href=r.href,a&&t.setAttribute(`nonce`,a),document.head.appendChild(t),i)return new Promise((e,n)=>{t.addEventListener(`load`,e),t.addEventListener(`error`,()=>n(Error(`Unable to preload CSS for ${r}`)))})})}).filter(e=>e!==void 0))}function i(e){let t=new Event(`vite:preloadError`,{cancelable:!0});if(t.payload=e,window.dispatchEvent(t),!t.defaultPrevented)throw e}return r.then(t=>{for(let e of t||[])e.status===`rejected`&&i(e.reason);return e().catch(i)})},O=ie(),k=e=>document.getElementById(e),he=e=>e?new Date(e).toLocaleTimeString([],{hour12:!1}):`never`,A,j,M,N,P={state:`connecting`,attempt:0},F={view:`map`},I=!1,L,R=``,z,B,V=e=>z?e(z):void B?.then(e);function ge(){B??=D(async()=>{let{mountFocus:e}=await import(`./views-D6rBFuay.js`);return{mountFocus:e}},[],import.meta.url).then(({mountFocus:e})=>(z=e({data:ye,restart:()=>Q.restart(),openTile:G,openTerminal:(e,t)=>W(n=>n.terminal.open(e,t)),online:()=>P.state===`live`}),z.show(F,F),M&&z.net(M),z))}var H,U,W=e=>{if(H)return e(H);U??=D(async()=>{let{mountPanels:e}=await import(`./layer-CEI0ft9k.js`);return{mountPanels:e}},[],import.meta.url).then(({mountPanels:e})=>H=e({label:e=>M?.bases.find(t=>t.id===e)?.label??e,recent:async e=>(await(Z?Z.data(e).latest():X(e,`?limit=12`))).items,post:e=>Z&&((t,n)=>Z.data(e).post(t,n)),stream:()=>R})),U.then(e)},G=(e,t)=>W(n=>n.tiles.open(e,t));function K(e){if(M=N=e,document.hidden)return;O.stats(e);let t=i(e,Date.now()),r=a(L,t);(r.length||H)&&W(e=>e.alerts(r,t)),L=t,O.log(n(j,e)),j=e,V(t=>t.net(e)),F.view===`map`&&(O.show(e),A=e,N=void 0)}function q(e){let t=F,n=e.view===`workspace`&&t.view===`map`?document.querySelector(`#bases [data-key="${CSS.escape(e.ws)}"]`)?.getBoundingClientRect():void 0;F=e,e.view!==`map`&&ge(),k(`map`).hidden=e.view!==`map`,k(`legend`).hidden=e.view===`session`,O.still(e.view!==`map`),V(r=>r.show(e,t,n)),e.view===`map`&&M&&M!==A&&(K(M),requestAnimationFrame(O.layout)),I&&d(e)!==d(t)&&Q.restart()}function J(e){P=e;let t=e.state===`reconnecting`||e.state===`offline`;document.body.dataset.stale=k(`stage`).dataset.stale=String(t),k(`link`).dataset.state=e.state,k(`link-text`).textContent={connecting:`connecting`,live:`live`,reconnecting:`reconnecting`,offline:`offline`}[e.state],z&&M&&z.net(M),H?.stack.redraw();let n=k(`banner`);n.hidden=!t&&!e.problem;let r=e.state===`offline`?`Disconnected. Gave up after ${e.attempt-1} tries; it reconnects when you return to this tab.`:e.state===`reconnecting`?`Disconnected from the straw net. Reconnecting${e.attempt?` (try ${e.attempt})`:``}… The view shows the last known state.`:`The server is up but cannot read the net: ${e.problem}`,i=document.createElement(`b`);i.textContent=he(e.lastContact),n.replaceChildren(`${r} Last update `,i,`.`)}function Y(e,t){try{let n=JSON.parse(t);V(t=>e===`history`?t.history(n):t.cards(n))}catch{}}function _e(e){let t=new EventSource(e?`events?watch=${encodeURIComponent(e)}`:`events`);return{onError:e=>t.onerror=e,listen:(e,n)=>t.addEventListener(e,e=>n(e.data)),close:()=>t.close()}}var ve=e=>fetch(`api/${e}`,{cache:`no-store`}).then(e=>e.ok?e.json():Promise.reject(Error(String(e.status)))),X=(e,t=``)=>ve(`history/${encodeURIComponent(e)}${t}`),Z=void 0,ye=e=>({older:t=>X(e,`?before=${encodeURIComponent(t)}`),latest:()=>X(e),entry:t=>ve(`entry/${encodeURIComponent(e)}/${encodeURIComponent(t)}`).catch(()=>void 0)}),be=()=>{let e=_e(d(F));return e.listen(`history`,e=>Y(`history`,e)),e.listen(`cards`,e=>Y(`cards`,e)),e.listen(`hello`,e=>(R=e,H?.terminal.hello())),e.listen(`mirror`,e=>H?.terminal.frame(e)),e};document.addEventListener(`click`,e=>{let t=e.target?.closest?.(`a[data-alert]`),n=t&&c(t.getAttribute(`data-route`)??``);if(!n||n.view!==`session`||e.button!==0||e.metaKey||e.ctrlKey||e.shiftKey||e.altKey)return;let r=L?.get(n.key);r&&(e.preventDefault(),W(e=>e.stack.show(r)))},!0);var xe=ee(e=>q(e)),Q=le({open:be,now:Date.now,setTimeout:(e,t)=>window.setTimeout(e,t),clearTimeout:e=>window.clearTimeout(e),random:Math.random,hidden:()=>document.hidden,onNet:K,onStatus:J}),$=()=>Q.resume();document.addEventListener(`visibilitychange`,()=>{if(O.pause(document.hidden),document.hidden)return Q.hide();N&&K(N),V(e=>e.wake()),$()}),addEventListener(`keydown`,e=>{e.key!==`Escape`||e.defaultPrevented||(e.preventDefault(),!H?.stack.close()&&F.view!==`map`&&xe.go(u(F)))}),k(`pair`).addEventListener(`click`,()=>void D(async()=>{let{pairTile:e}=await import(`./pair-DDTg3MTR.js`);return{pairTile:e}},[],import.meta.url).then(({pairTile:e})=>G(e(),!0)));for(let e of[`focus`,`pageshow`,`online`])window.addEventListener(e,()=>!document.hidden&&$());J(P),q(xe.route()),Q.start(),I=!0;export{b as a,S as c,C as d,_ as f,x as i,h as l,r as m,f as n,g as o,m as p,p as r,y as s,D as t,v as u};
@@ -1,2 +1,2 @@
1
- import{a as e,c as t,d as n,f as r,i,l as a,m as o,o as s,s as c,u as l}from"./index-CWA0eWOk.js";import{n as u,t as d}from"./post-B39ywXKd.js";import{t as f}from"./ask-CVw44ZU3.js";var p=[[[`esc`,`esc`,`Escape`],[`tab`,`tab`,`Tab`],[`up`,`↑`,`Up`],[`enter`,`enter`,`Enter`]],[[`shift+tab`,`⇧tab`,`Shift+Tab`],[`left`,`←`,`Left`],[`down`,`↓`,`Down`],[`right`,`→`,`Right`]],[[`space`,`space`,`Space`],[`backspace`,`⌫`,`Backspace`],[`home`,`home`,`Home`],[`end`,`end`,`End`],[`pageup`,`pgup`,`Page up`],[`pagedown`,`pgdn`,`Page down`]]],m=`123456789`.split(``);function h({name:e,state:t,post:n,stream:r}){let i=[],a=u(`p`,{class:`pad-note`,role:`status`}),o=!1,s=([e,t,n])=>{let r=u(`button`,{type:`button`,class:`pad-key`,"data-key":e,"aria-label":n},t);return r.onclick=t=>t.isTrusted&&!o&&void h(e),i.push(r),r},c=u(`details`,{class:`pad-digits`},u(`summary`,null,`1–9`),u(`div`,{class:`pad-row`},...m.map(e=>s([e,e,e])))),l=u(`div`,{class:`pad`,role:`group`,"aria-label":`Keys for ${e}`},...p.map(e=>u(`div`,{class:`pad-row`},...e.map(s))),c,a),d=(e,t=``)=>(a.dataset.tone=t,a.replaceChildren(e)),f=(e,t,n)=>{let r=u(`button`,{type:`button`,class:`pad-yes`},e===`enter`?`Send Enter`:`Send Escape`),i=u(`button`,{type:`button`,class:`cmp-link`},`Cancel`);r.onclick=t=>t.isTrusted&&!o&&void h(e,n),i.onclick=()=>d(``),a.dataset.tone=`ask`,a.replaceChildren(u(`span`,null,t),r,i),i.focus()};async function h(e,t){o=!0,g(),d(``);try{let i=await n(`keys`,{key:e,stream:r(),...t?{confirmed:t}:{}});i.status===409&&i.body.error===`confirm`&&typeof i.body.confirm==`string`?f(e,String(i.body.reason??``),i.body.confirm):i.ok||d(i.body.reason??`Refused (${i.status}).`,`error`)}catch{d(`The server could not be reached.`,`error`)}finally{o=!1,g()}}function g(){let e=t();for(let t of i)t.disabled=o||e===`off`||e===`hidden`&&t.dataset.key!==`esc`}return g(),{el:l,update:g}}var g=[0,95,135,175,215,255],_=120,v=5e3,y=3e3,b=e=>Number.isInteger(e)&&e>=0&&e<=255,x=e=>b(e)?e<16?e:e<232?`rgb(${g[Math.floor((e-16)/36)]},${g[Math.floor((e-16)/6)%6]},${g[(e-16)%6]})`:`rgb(${8+10*(e-232)},${8+10*(e-232)},${8+10*(e-232)})`:void 0,S=([e,t,n])=>[e,t,n].every(e=>b(e))?`rgb(${e},${t},${n})`:void 0;function C(e,t){let n={...e},r=t.split(`;`);for(let e=0;e<r.length;e++){let t=r[e].split(`:`).map(e=>e===``?0:Number(e)),i=t[0];if(i===38||i===48||i===58){let a=t.length>1?t[1]:Number(r[e+1]),o=t.length>1?t.slice(2):r.slice(e+2,e+(a===5?3:5)).map(Number);t.length===1&&(e+=a===5?2:a===2?4:1);let s=a===5?x(o[0]):a===2?S(o.slice(-3)):void 0;s!==void 0&&i!==58&&(n[i===38?`fg`:`bg`]=s)}else if(i===0)for(let e of Object.keys(n))delete n[e];else i===1||i===2?n[i===1?`b`:`d`]=!0:i===22?n.b=n.d=!1:i===3||i===23?n.i=i===3:i===4||i===21||i===24?n.u=i!==24&&t[1]!==0:i===7||i===27?n.v=i===7:i===9||i===29?n.s=i===9:i>=30&&i<=37||i>=90&&i<=97?n.fg=i-(i<90?30:82):i>=40&&i<=47||i>=100&&i<=107?n.bg=i-(i<100?40:92):(i===39||i===49)&&delete n[i===39?`fg`:`bg`]}return n}function w(e){let t=[],n={},r=0,i=e=>{let r=e.replace(/\x1b(\[[0-?]*[ -/]*[@-~]?)?|[\u0000-\u001f\u007f-\u009f]/g,``);if(!r)return;let[i,a]=n.v?[n.bg??`z`,n.fg??`z`]:[n.fg,n.bg],o=[typeof i==`number`||i===`z`?`c${i}`:``,typeof a==`number`||a===`z`?`k${a}`:``,...[`b`,`d`,`i`,`u`,`s`].filter(e=>n[e]).map(e=>`t${e}`)].filter(Boolean).join(` `);t.push({text:r,cls:o,...typeof i==`string`&&i!==`z`&&{fg:i},...typeof a==`string`&&a!==`z`&&{bg:a}})};for(let t of e.matchAll(/\x1b\[([0-9;:]{0,64})m/g))i(e.slice(r,t.index)),n=C(n,t[1]),r=t.index+t[0].length;return i(e.slice(r)),t}function T(e,t){let n=Math.min(Math.max(0,Number(t.rows)||0),_);for(;e.children.length<n;)e.append(u(`div`,{class:`tr`}));for(;e.children.length>n;)e.lastElementChild.remove();for(let[r,i]of Array.isArray(t.set)?t.set:[])!Number.isInteger(r)||r<0||r>=n||typeof i!=`string`||e.children[r].replaceChildren(...w(i).map(e=>{if(!e.cls&&!e.fg&&!e.bg)return e.text;let t=u(`span`,e.cls?{class:e.cls}:null,e.text);return e.fg&&(t.style.color=e.fg),e.bg&&(t.style.backgroundColor=e.bg),t}))}function E({tiles:e,post:t,stream:n,pad:r}){let i=new Map,a,o=()=>{try{return String(JSON.parse(n()).stream??``)}catch{return``}},s=(e,t,n=``)=>(e.pad?.update(),(e.state.textContent!==t||e.state.dataset.tone!==n)&&(e.state.dataset.tone=n,e.state.textContent=t)),c=(e,t)=>(e.ended=!0,s(e,`${t} Open the terminal again to retry.`,`error`)),l=(e,t,n=o())=>n&&void(e.queue=e.queue.then(()=>e.post(`mirror`,{stream:n,on:t}).then(n=>t&&!n.ok&&c(e,n.body.reason??`Refused (${n.status}).`),()=>t&&c(e,`The server could not be reached.`)))),d=(e,t=Math.max(e.cols,...[...e.box.children].map(e=>e.textContent?.length??0)))=>e.box.style.fontSize=e.fit.getAttribute(`aria-pressed`)===`true`&&t?`${Math.max(6,Math.floor((e.scroll.clientWidth-20)/(t*.6)))}px`:``,f=e=>Date.now()-e.seen>y,p=()=>(clearInterval(a),a=i.size&&!document.hidden?setInterval(()=>i.forEach(e=>(e.box.classList.toggle(`stale`,f(e)),e.pad?.update())),1e3):void 0);document.addEventListener(`visibilitychange`,()=>(p(),i.forEach(e=>{clearTimeout(e.away),document.hidden?e.away=setTimeout(()=>(e.parked=!0,l(e,!1),s(e,`Paused while this tab is hidden.`)),v):e.parked&&(e.parked=!1,l(e,!0))})));let m=(e,n)=>{let a={key:e,box:u(`div`,{class:`term`,role:`group`,"aria-label":`Terminal of ${n}, read only`}),scroll:u(`div`,{class:`term-scroll`,tabindex:`0`}),state:u(`p`,{class:`term-state`,role:`status`},`Connecting…`),fit:u(`button`,{type:`button`,class:`cmp-link`,"aria-pressed":`false`},`Fit width`),post:t(e),cols:0,seen:0,parked:!1,first:!0,ended:!1,queue:Promise.resolve()};return a.fit.onclick=()=>(a.fit.setAttribute(`aria-pressed`,String(a.fit.getAttribute(`aria-pressed`)!==`true`)),d(a)),a.scroll.append(a.box),new ResizeObserver(()=>d(a)).observe(a.scroll),i.set(e,a),l(a,!0),p(),a.pad=r?.({name:n,state:()=>a.parked||a.ended||f(a)?`off`:a.box.classList.contains(`hidden`)?`hidden`:`live`,post:a.post,stream:o}),a.tile={id:`term:${e}`,title:`Terminal · ${n}`,icon:`i-comms`,body:()=>u(`div`,{class:`term-tile`},u(`div`,{class:`term-bar`},a.state,a.fit),a.scroll,a.pad?.el),closed:()=>(clearTimeout(a.away),l(a,!1),i.delete(e),p())}};return{open(t,n){let r=i.get(t);r?.ended&&(r.ended=!1,s(r,`Connecting…`),l(r,!0)),e.open(r?.tile??m(t,n),!0)},hello:()=>i.forEach(e=>!e.parked&&!e.ended&&l(e,!0)),frame(e){let t;try{t=JSON.parse(e)}catch{return}let n=i.get(t.key);if(n){if(n.box.classList.toggle(`hidden`,!!(t.hidden||t.ended)),t.ended)return c(n,`The mirror ended: ${t.ended}`);if([n.seen,n.ended]=[Date.now(),!1],n.box.classList.remove(`stale`),t.hidden)return T(n.box,{key:n.key,rows:0}),s(n,`Hidden while it waits for a secret; type it in the terminal.`);T(n.box,t),(t.whole||t.cols&&t.cols!==n.cols)&&(n.cols=t.cols||n.cols,d(n)),s(n,`Live · ${n.cols}×${n.box.children.length}`,`ok`),n.first&&t.whole&&(n.first=!1,requestAnimationFrame(()=>n.scroll.scrollTop=n.scroll.scrollHeight))}}}}var D=(e,t)=>o(e.mark)-o(t.mark)||e.at-t.at,O=e=>{let t=e.filter(e=>o(e.mark)===0).length,n=e.length-t;return[t&&`${t} need${t===1?`s`:``} you`,n&&`${n} finished`].filter(Boolean).join(` · `)};function k(e){let t=[],n=[],r=()=>e.render(t.map(e=>e.alert),n),i=(i,a,o=!0)=>{let s=t.findIndex(e=>e.alert.key===i.key);if(s>=0&&!o)return;let c=s>=0?t.splice(s,1)[0]:{alert:i,back:a,raised:i.mark};!o&&t.length?t.splice(t.length-1,0,c):t.push(c),n=n.filter(e=>e.key!==i.key),r(),o&&e.enter(c.alert)};return{get open(){return t.map(e=>e.alert)},get queue(){return n},arrive(e){let i=t.find(t=>t.alert.key===e.key);if(i)[i.alert,i.raised]=[{...e,at:i.alert.at},e.mark];else{let t=n.find(t=>t.key===e.key);n=[...n.filter(t=>t.key!==e.key),t&&t.mark===e.mark?t:e].sort(D)}r()},prune(e){n=n.filter(t=>e(t.key)?.mark===t.mark);for(let n of t){let t=e(n.alert.key);n.alert=t?{...n.alert,...t,gone:void 0,at:n.alert.at}:{...n.alert,mark:`unknown`,note:void 0,pending:void 0,gone:!0}}r()},redraw:r,show:(t,n=!0)=>i(t,e.focused(),n),raise(n){let r=t.find(e=>e.alert.key===n);r&&r!==t.at(-1)&&i(r.alert,e.focused())},next(){n[0]&&i(n[0],e.focused())},close(a=`close`){let s=t.pop();if(!s)return!1;let c=t.at(-1),l=a===`close`?n.find(e=>o(e.mark)===0):void 0;return a===`later`&&s.alert.mark===s.raised&&(n=[...n,s.alert].sort(D)),!c&&l?(i(l,s.back),!0):(r(),!e.restore(s.back)&&c&&e.enter(c.alert),!0)}}}var A=`a, button, input, textarea, select, summary, label, [role=button]`;function j(e){let t=[],n,r=()=>e.render(t,n);return{get tiles(){return t},get active(){return n},open(i,a=!0){t.some(e=>e.id===i.id)||(t=[...t,i]).slice(0,-4).forEach(e=>e.closed?.()),t=t.slice(-4),(a||!t.some(e=>e.id===n))&&(n=i.id),r(),a&&e.enter(t.find(e=>e.id===i.id))},close(e){let i=t.findIndex(t=>t.id===e);i<0||(t[i].closed?.(),t=t.filter(t=>t.id!==e),n===e&&(n=(t[i]??t[i-1])?.id),r())},activate(e){n=e,r()}}}function M(e){let t=new Set(e.flatMap(e=>e.kind===`result`&&e.callId?[e.callId]:[])),n=e.filter(e=>e.kind===`assistant`),r=n.at(-1),i=r?.tools?.find(e=>!t.has(e.id));return{said:(n.findLast(e=>e.text?.trim())?.text?.trim().split(`
1
+ import{a as e,c as t,d as n,f as r,i,l as a,m as o,o as s,s as c,u as l}from"./index-F5nF0TfD.js";import{n as u,t as d}from"./post-B39ywXKd.js";import{t as f}from"./ask-CVw44ZU3.js";var p=[[[`esc`,`esc`,`Escape`],[`tab`,`tab`,`Tab`],[`up`,`↑`,`Up`],[`enter`,`enter`,`Enter`]],[[`shift+tab`,`⇧tab`,`Shift+Tab`],[`left`,`←`,`Left`],[`down`,`↓`,`Down`],[`right`,`→`,`Right`]],[[`space`,`space`,`Space`],[`backspace`,`⌫`,`Backspace`],[`home`,`home`,`Home`],[`end`,`end`,`End`],[`pageup`,`pgup`,`Page up`],[`pagedown`,`pgdn`,`Page down`]]],m=`123456789`.split(``);function h({name:e,state:t,post:n,stream:r}){let i=[],a=u(`p`,{class:`pad-note`,role:`status`}),o=!1,s=([e,t,n])=>{let r=u(`button`,{type:`button`,class:`pad-key`,"data-key":e,"aria-label":n},t);return r.onclick=t=>t.isTrusted&&!o&&void h(e),i.push(r),r},c=u(`details`,{class:`pad-digits`},u(`summary`,null,`1–9`),u(`div`,{class:`pad-row`},...m.map(e=>s([e,e,e])))),l=u(`div`,{class:`pad`,role:`group`,"aria-label":`Keys for ${e}`},...p.map(e=>u(`div`,{class:`pad-row`},...e.map(s))),c,a),d=(e,t=``)=>(a.dataset.tone=t,a.replaceChildren(e)),f=(e,t,n)=>{let r=u(`button`,{type:`button`,class:`pad-yes`},e===`enter`?`Send Enter`:`Send Escape`),i=u(`button`,{type:`button`,class:`cmp-link`},`Cancel`);r.onclick=t=>t.isTrusted&&!o&&void h(e,n),i.onclick=()=>d(``),a.dataset.tone=`ask`,a.replaceChildren(u(`span`,null,t),r,i),i.focus()};async function h(e,t){o=!0,g(),d(``);try{let i=await n(`keys`,{key:e,stream:r(),...t?{confirmed:t}:{}});i.status===409&&i.body.error===`confirm`&&typeof i.body.confirm==`string`?f(e,String(i.body.reason??``),i.body.confirm):i.ok||d(i.body.reason??`Refused (${i.status}).`,`error`)}catch{d(`The server could not be reached.`,`error`)}finally{o=!1,g()}}function g(){let e=t();for(let t of i)t.disabled=o||e===`off`||e===`hidden`&&t.dataset.key!==`esc`}return g(),{el:l,update:g}}var g=[0,95,135,175,215,255],_=120,v=5e3,y=3e3,b=e=>Number.isInteger(e)&&e>=0&&e<=255,x=e=>b(e)?e<16?e:e<232?`rgb(${g[Math.floor((e-16)/36)]},${g[Math.floor((e-16)/6)%6]},${g[(e-16)%6]})`:`rgb(${8+10*(e-232)},${8+10*(e-232)},${8+10*(e-232)})`:void 0,S=([e,t,n])=>[e,t,n].every(e=>b(e))?`rgb(${e},${t},${n})`:void 0;function C(e,t){let n={...e},r=t.split(`;`);for(let e=0;e<r.length;e++){let t=r[e].split(`:`).map(e=>e===``?0:Number(e)),i=t[0];if(i===38||i===48||i===58){let a=t.length>1?t[1]:Number(r[e+1]),o=t.length>1?t.slice(2):r.slice(e+2,e+(a===5?3:5)).map(Number);t.length===1&&(e+=a===5?2:a===2?4:1);let s=a===5?x(o[0]):a===2?S(o.slice(-3)):void 0;s!==void 0&&i!==58&&(n[i===38?`fg`:`bg`]=s)}else if(i===0)for(let e of Object.keys(n))delete n[e];else i===1||i===2?n[i===1?`b`:`d`]=!0:i===22?n.b=n.d=!1:i===3||i===23?n.i=i===3:i===4||i===21||i===24?n.u=i!==24&&t[1]!==0:i===7||i===27?n.v=i===7:i===9||i===29?n.s=i===9:i>=30&&i<=37||i>=90&&i<=97?n.fg=i-(i<90?30:82):i>=40&&i<=47||i>=100&&i<=107?n.bg=i-(i<100?40:92):(i===39||i===49)&&delete n[i===39?`fg`:`bg`]}return n}function w(e){let t=[],n={},r=0,i=e=>{let r=e.replace(/\x1b(\[[0-?]*[ -/]*[@-~]?)?|[\u0000-\u001f\u007f-\u009f]/g,``);if(!r)return;let[i,a]=n.v?[n.bg??`z`,n.fg??`z`]:[n.fg,n.bg],o=[typeof i==`number`||i===`z`?`c${i}`:``,typeof a==`number`||a===`z`?`k${a}`:``,...[`b`,`d`,`i`,`u`,`s`].filter(e=>n[e]).map(e=>`t${e}`)].filter(Boolean).join(` `);t.push({text:r,cls:o,...typeof i==`string`&&i!==`z`&&{fg:i},...typeof a==`string`&&a!==`z`&&{bg:a}})};for(let t of e.matchAll(/\x1b\[([0-9;:]{0,64})m/g))i(e.slice(r,t.index)),n=C(n,t[1]),r=t.index+t[0].length;return i(e.slice(r)),t}function T(e,t){let n=Math.min(Math.max(0,Number(t.rows)||0),_);for(;e.children.length<n;)e.append(u(`div`,{class:`tr`}));for(;e.children.length>n;)e.lastElementChild.remove();for(let[r,i]of Array.isArray(t.set)?t.set:[])!Number.isInteger(r)||r<0||r>=n||typeof i!=`string`||e.children[r].replaceChildren(...w(i).map(e=>{if(!e.cls&&!e.fg&&!e.bg)return e.text;let t=u(`span`,e.cls?{class:e.cls}:null,e.text);return e.fg&&(t.style.color=e.fg),e.bg&&(t.style.backgroundColor=e.bg),t}))}function E({tiles:e,post:t,stream:n,pad:r}){let i=new Map,a,o=()=>{try{return String(JSON.parse(n()).stream??``)}catch{return``}},s=(e,t,n=``)=>(e.pad?.update(),(e.state.textContent!==t||e.state.dataset.tone!==n)&&(e.state.dataset.tone=n,e.state.textContent=t)),c=(e,t)=>(e.ended=!0,s(e,`${t} Open the terminal again to retry.`,`error`)),l=(e,t,n=o())=>n&&void(e.queue=e.queue.then(()=>e.post(`mirror`,{stream:n,on:t}).then(n=>t&&!n.ok&&c(e,n.body.reason??`Refused (${n.status}).`),()=>t&&c(e,`The server could not be reached.`)))),d=(e,t=Math.max(e.cols,...[...e.box.children].map(e=>e.textContent?.length??0)))=>e.box.style.fontSize=e.fit.getAttribute(`aria-pressed`)===`true`&&t?`${Math.max(6,Math.floor((e.scroll.clientWidth-20)/(t*.6)))}px`:``,f=e=>Date.now()-e.seen>y,p=()=>(clearInterval(a),a=i.size&&!document.hidden?setInterval(()=>i.forEach(e=>(e.box.classList.toggle(`stale`,f(e)),e.pad?.update())),1e3):void 0);document.addEventListener(`visibilitychange`,()=>(p(),i.forEach(e=>{clearTimeout(e.away),document.hidden?e.away=setTimeout(()=>(e.parked=!0,l(e,!1),s(e,`Paused while this tab is hidden.`)),v):e.parked&&(e.parked=!1,l(e,!0))})));let m=(e,n)=>{let a={key:e,box:u(`div`,{class:`term`,role:`group`,"aria-label":`Terminal of ${n}, read only`}),scroll:u(`div`,{class:`term-scroll`,tabindex:`0`}),state:u(`p`,{class:`term-state`,role:`status`},`Connecting…`),fit:u(`button`,{type:`button`,class:`cmp-link`,"aria-pressed":`false`},`Fit width`),post:t(e),cols:0,seen:0,parked:!1,first:!0,ended:!1,queue:Promise.resolve()};return a.fit.onclick=()=>(a.fit.setAttribute(`aria-pressed`,String(a.fit.getAttribute(`aria-pressed`)!==`true`)),d(a)),a.scroll.append(a.box),new ResizeObserver(()=>d(a)).observe(a.scroll),i.set(e,a),l(a,!0),p(),a.pad=r?.({name:n,state:()=>a.parked||a.ended||f(a)?`off`:a.box.classList.contains(`hidden`)?`hidden`:`live`,post:a.post,stream:o}),a.tile={id:`term:${e}`,title:`Terminal · ${n}`,icon:`i-comms`,body:()=>u(`div`,{class:`term-tile`},u(`div`,{class:`term-bar`},a.state,a.fit),a.scroll,a.pad?.el),closed:()=>(clearTimeout(a.away),l(a,!1),i.delete(e),p())}};return{open(t,n){let r=i.get(t);r?.ended&&(r.ended=!1,s(r,`Connecting…`),l(r,!0)),e.open(r?.tile??m(t,n),!0)},hello:()=>i.forEach(e=>!e.parked&&!e.ended&&l(e,!0)),frame(e){let t;try{t=JSON.parse(e)}catch{return}let n=i.get(t.key);if(n){if(n.box.classList.toggle(`hidden`,!!(t.hidden||t.ended)),t.ended)return c(n,`The mirror ended: ${t.ended}`);if([n.seen,n.ended]=[Date.now(),!1],n.box.classList.remove(`stale`),t.hidden)return T(n.box,{key:n.key,rows:0}),s(n,`Hidden while it waits for a secret; type it in the terminal.`);T(n.box,t),(t.whole||t.cols&&t.cols!==n.cols)&&(n.cols=t.cols||n.cols,d(n)),s(n,`Live · ${n.cols}×${n.box.children.length}`,`ok`),n.first&&t.whole&&(n.first=!1,requestAnimationFrame(()=>n.scroll.scrollTop=n.scroll.scrollHeight))}}}}var D=(e,t)=>o(e.mark)-o(t.mark)||e.at-t.at,O=e=>{let t=e.filter(e=>o(e.mark)===0).length,n=e.length-t;return[t&&`${t} need${t===1?`s`:``} you`,n&&`${n} finished`].filter(Boolean).join(` · `)};function k(e){let t=[],n=[],r=()=>e.render(t.map(e=>e.alert),n),i=(i,a,o=!0)=>{let s=t.findIndex(e=>e.alert.key===i.key);if(s>=0&&!o)return;let c=s>=0?t.splice(s,1)[0]:{alert:i,back:a,raised:i.mark};!o&&t.length?t.splice(t.length-1,0,c):t.push(c),n=n.filter(e=>e.key!==i.key),r(),o&&e.enter(c.alert)};return{get open(){return t.map(e=>e.alert)},get queue(){return n},arrive(e){let i=t.find(t=>t.alert.key===e.key);if(i)[i.alert,i.raised]=[{...e,at:i.alert.at},e.mark];else{let t=n.find(t=>t.key===e.key);n=[...n.filter(t=>t.key!==e.key),t&&t.mark===e.mark?t:e].sort(D)}r()},prune(e){n=n.filter(t=>e(t.key)?.mark===t.mark);for(let n of t){let t=e(n.alert.key);n.alert=t?{...n.alert,...t,gone:void 0,at:n.alert.at}:{...n.alert,mark:`unknown`,note:void 0,pending:void 0,gone:!0}}r()},redraw:r,show:(t,n=!0)=>i(t,e.focused(),n),raise(n){let r=t.find(e=>e.alert.key===n);r&&r!==t.at(-1)&&i(r.alert,e.focused())},next(){n[0]&&i(n[0],e.focused())},close(a=`close`){let s=t.pop();if(!s)return!1;let c=t.at(-1),l=a===`close`?n.find(e=>o(e.mark)===0):void 0;return a===`later`&&s.alert.mark===s.raised&&(n=[...n,s.alert].sort(D)),!c&&l?(i(l,s.back),!0):(r(),!e.restore(s.back)&&c&&e.enter(c.alert),!0)}}}var A=`a, button, input, textarea, select, summary, label, [role=button]`;function j(e){let t=[],n,r=()=>e.render(t,n);return{get tiles(){return t},get active(){return n},open(i,a=!0){if(!a&&t.length>=4&&!t.some(e=>e.id===i.id))return void i.closed?.();t.some(e=>e.id===i.id)||(t=[...t,i]).slice(0,-4).forEach(e=>e.closed?.()),t=t.slice(-4),(a||!t.some(e=>e.id===n))&&(n=i.id),r(),a&&e.enter(t.find(e=>e.id===i.id))},close(e){let i=t.findIndex(t=>t.id===e);i<0||(t[i].closed?.(),t=t.filter(t=>t.id!==e),n===e&&(n=(t[i]??t[i-1])?.id),r())},activate(e){n=e,r()}}}function M(e){let t=new Set(e.flatMap(e=>e.kind===`result`&&e.callId?[e.callId]:[])),n=e.filter(e=>e.kind===`assistant`),r=n.at(-1),i=r?.tools?.find(e=>!t.has(e.id));return{said:(n.findLast(e=>e.text?.trim())?.text?.trim().split(`
2
2
  `).filter(Boolean).at(-1))?.slice(0,280),pending:i&&`${i.name} · ${i.summary}`.slice(0,200),at:(r??e.findLast(e=>e.kind!==`result`))?.id}}var N=()=>a(`<section class="dialog" role="dialog" aria-modal="false" tabindex="-1"><header><b class="tag">alert</b><span class="where"></span><button class="x" type="button" aria-label="Close">×</button></header><div class="who">${i}<div><h3></h3><p><em class="role"></em> <span class="state"></span></p></div></div><div class="intel"><h4>Last transmission</h4><p class="said">…</p><p class="pending" hidden><b>waiting on</b> <span></span></p></div><div class="ask-root"></div><footer><a class="go">Open comms</a><button class="later" type="button">Later</button><span class="queued"></span></footer></section>`),P=()=>a(`<section class="tile" tabindex="-1"><header><svg><use href="#i-doc"/></svg><b></b><button class="x" type="button" aria-label="Close document">×</button></header><div class="tile-body"></div></section>`);function F(o){let[u,p,m,g,_]=[s(`dock`,HTMLElement),s(`work`,HTMLElement),s(`tiles`,HTMLElement),s(`tile-tabs`,HTMLElement),s(`alerts`,HTMLButtonElement)],v=new Map,y=(t,n,i=v.get(n.key))=>{let a=t.dataset.gone===`true`;r(t,`.said`,i?.failed?`History unavailable.`:i?.said??`No messages yet.`),t.querySelector(`.pending`).hidden=!i?.pending||a,r(t,`.pending span`,i?.pending??``),e(t.querySelector(`.go`),a?void 0:{view:`session`,ws:n.ws,key:n.key,...i?.at&&{at:i.at}})},b=(e,t={said:`…`})=>{v.set(e.key,t),o.recent(e.key).then(M,()=>({failed:!0})).then(n=>{let r=v.get(e.key)===t&&(v.set(e.key,n),u.querySelector(`.dialog[data-key="${CSS.escape(e.key)}"]`));r&&y(r,e)})},x=(e,n,i,a)=>{l(e,n.mark),e.dataset.gone=String(!!n.gone),e.style.transform=i?`translateY(${-12*i}px) scale(${1-.06*i})`:``,e.style.opacity=String(Math.max(0,1-.3*i)),e.toggleAttribute(`inert`,i>0),e.setAttribute(`aria-label`,`${n.name}: ${c(n.note,n.mark)}`),t(e.querySelector(`.plate`),n.role,n.mark),r(e,`h3`,n.name),r(e,`.role`,n.role===`outside`?`unaligned`:n.role),r(e,`.state`,n.gone?`left the net`:c(n.note,n.mark)),r(e,`.where`,o.label(n.ws)),r(e,`.tag`,n.gone?`gone`:{waiting:`question`,error:`error`,done:`finished`}[n.mark]??`update`),r(e,`.queued`,a.length?`Also queued: ${O(a)}`:``),v.has(n.key)||b(n),y(e,n);let s=S.get(e);s||S.set(e,s=f(e.querySelector(`.ask-root`),C(n.key),void 0,()=>I.open(n.key,n.name))),s.update(n.pending,n.mark===`waiting`&&document.body.dataset.stale!==`true`)},S=new WeakMap,C=e=>o.post?.(e)??((t,n)=>d(`sessions/${encodeURIComponent(e)}/${t}`,n)),w=k({render(e,o){n(u,e.map(e=>({...e,id:e.key})),N,(t,n)=>x(t,n,e.length-1-e.findIndex(e=>e.key===n.key),o));let s=o[0];_.disabled=!s,_.dataset.mark=s?.mark??`unknown`,r(_,`#alert-count`,String(o.length)),_.title=o.length?`Alerts: ${O(o)}`:``,r(_,`#alert-text`,s?`${s.name} · ${c(s.note,s.mark)}`:`no alerts`),n(_.querySelector(`.faces`),o.slice(0,3).map(e=>({...e,id:e.key})),()=>a(i),(e,n)=>t(e,n.role,n.mark))},enter:e=>u.querySelector(`[data-key="${CSS.escape(e.key)}"] .go`)?.focus(),focused:()=>document.activeElement instanceof HTMLElement&&document.activeElement!==document.body?document.activeElement:void 0,restore:e=>!!(e?.isConnected&&!e.closest(`[inert]`)&&(e.focus(),document.activeElement===e))});_.addEventListener(`click`,()=>w.next()),document.addEventListener(`click`,e=>{let t=t=>{let n=t.getBoundingClientRect();return e.clientX>=n.left&&e.clientX<=n.right&&e.clientY>=n.top&&e.clientY<=n.bottom},n=[...u.querySelectorAll(`.dialog:not(.leaving)`)],r=n.slice(0,-1).reverse().find(t);e.defaultPrevented||!r||!n.length||t(n.at(-1))||e.target?.closest?.(A)||(e.preventDefault(),e.stopPropagation(),w.raise(r.dataset.key))},!0),u.addEventListener(`click`,e=>{let t=e.target;t.closest(`.x`)?w.close():t.closest(`.later`)?w.close(`later`):t.closest(`.go`)&&queueMicrotask(()=>void w.close(`go`))});let T=0,D=()=>m.classList.toggle(`tabbed`,F.tiles.length>1&&p.clientWidth<460+F.tiles.length*400),F=j({render(e,t){m.hidden=!e.length,n(m,e,e=>{let t=P();return t.querySelector(`use`).setAttribute(`href`,`#${e.icon??`i-doc`}`),t.querySelector(`.x`).setAttribute(`aria-label`,`Close ${e.title}`),t.querySelector(`.tile-body`).append(e.body()),t.querySelector(`.x`).addEventListener(`click`,()=>F.close(e.id)),t},(e,n)=>{r(e,`header b`,n.title),e.classList.toggle(`active`,n.id===t)}),n(g,e,()=>a(`<button type="button" class="tab-btn"></button>`),(e,n)=>{e.textContent=n.title,e.classList.toggle(`active`,n.id===t),e.onclick=()=>F.activate(n.id)}),m.prepend(g),e.length!==T&&(T=e.length,D())},enter:e=>requestAnimationFrame(()=>m.querySelector(`.tile[data-key="${CSS.escape(e.id)}"]`)?.focus())});new ResizeObserver(D).observe(p);let I=E({tiles:F,post:C,stream:o.stream,pad:h});return{tiles:F,stack:w,terminal:I,alerts(e,t){for(let t of e)b(t),w.arrive(t);w.prune(e=>t.get(e))}}}export{F as mountPanels};
@@ -0,0 +1,8 @@
1
+ const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["./index-DoXkXGW_.css"])))=>i.map(i=>d[i]);
2
+ import{a as e,c as t,d as n,f as r,i,l as a,p as o,r as s,s as c,t as l,u}from"./index-F5nF0TfD.js";import{n as d,t as f}from"./post-B39ywXKd.js";import{t as p}from"./ask-CVw44ZU3.js";var m=(e,t,n)=>({tag:e,kids:t,attrs:n}),h=/^(?:https?:\/\/|mailto:)[^\s\u0000-\u001f]+$/i,g=(e,t)=>m(`a`,t,{href:e,rel:`noopener noreferrer`,target:`_blank`}),_=/(`+)([^`]|[^`][^]{0,1000}?[^`])\1(?!`)|\*\*([^*][^]{0,1000}?)\*\*|~~([^~][^]{0,1000}?)~~|\*([^\s*][^*]{0,500}?)\*|(?<![\w])_([^\s_][^_]{0,500}?)_(?![\w])|\[([^\]]{1,500})\]\(([^)\s]{1,2000})(?:\s+"[^"]*")?\)|(https?:\/\/[^\s<>)\]]+)/g;function v(e){let t=[],n=0;for(let r of e.matchAll(_))r.index>n&&t.push(e.slice(n,r.index)),n=r.index+r[0].length,r[2]===void 0?r[3]===void 0?r[4]===void 0?r[5]!==void 0||r[6]!==void 0?t.push(m(`em`,v(r[5]??r[6]))):r[7]===void 0?t.push(g(r[9],[r[9]])):t.push(h.test(r[8])?g(r[8],v(r[7])):r[0]):t.push(m(`del`,v(r[4]))):t.push(m(`strong`,v(r[3]))):t.push(m(`code`,[r[2]]));return n<e.length&&t.push(e.slice(n)),t}var y=/^(\s*)([-*+]|\d+[.)])\s+(.*)$/,b=/^\s*([-*_])(\s*\1){2,}\s*$/,x=/^\s*\|?\s*:?-+:?\s*(\|\s*:?-+:?\s*)*\|?\s*$/,S=e=>e.trim().replace(/^\|/,``).replace(/\|$/,``).split(`|`).map(e=>e.trim()),C=(e,t)=>/^\s*(`{3,}|~{3,}|#{1,6}\s|>)/.test(e)||b.test(e)||y.test(e)||e.includes(`|`)&&t!==void 0&&x.test(t)&&t.includes(`-`);function w(e,t=0){if(t>=16)return[m(`p`,[e])];let n=e.replace(/\r\n?/g,`
3
+ `).split(`
4
+ `),r=[];for(let e=0;e<n.length;){let i=n[e],a=n[e+1],o=i.match(/^\s*(`{3,}|~{3,})\s*([\w+-]*)/),s=i.match(/^\s*(#{1,6})\s+(.*?)\s*#*\s*$/),c=i.match(y);if(!i.trim())e++;else if(o){let t=[];for(e++;e<n.length&&!n[e].trim().startsWith(o[1]);e++)t.push(n[e]);e++,r.push(m(`pre`,[m(`code`,[t.join(`
5
+ `)],o[2]?{"data-lang":o[2]}:void 0)]))}else if(s)r.push(m(`h${s[1].length}`,v(s[2]))),e++;else if(b.test(i))r.push(m(`hr`,[])),e++;else if(/^\s*>/.test(i)){let i=[];for(;e<n.length&&/^\s*>/.test(n[e]);e++)i.push(n[e].replace(/^\s*> ?/,``));r.push(m(`blockquote`,w(i.join(`
6
+ `),t+1)))}else if(i.includes(`|`)&&a!==void 0&&x.test(a)&&a.includes(`-`)){let t=[];for(e+=2;e<n.length&&n[e].trim()&&n[e].includes(`|`);e++)t.push(S(n[e]));r.push(m(`table`,[m(`thead`,[m(`tr`,S(i).map(e=>m(`th`,v(e))))]),m(`tbody`,t.map(e=>m(`tr`,e.map(e=>m(`td`,v(e))))))]))}else if(c){let i=c[1].length,a=[];for(;e<n.length;){let r=n[e].match(y);if(!r||r[1].length!==i||/\d/.test(r[2])!==/\d/.test(c[2]))break;let o=[r[3]],s=r[3].match(/^\[([ xX])\]\s+(.*)$/);for(s&&(o[0]=s[2]),e++;e<n.length&&n[e].trim()&&n[e].search(/\S/)>i;e++)o.push(n[e].replace(RegExp(`^ {0,${i+2}}`),``));let l=w(o.join(`
7
+ `),t+1),u=l[0],d=s?.[1]!==` `;s&&(typeof u==`object`&&u.tag===`p`?u.kids:l).unshift(m(`i`,d?[`✓`]:[],{class:`check`,"data-done":String(d),"aria-label":d?`done`:`not done`})),a.push(m(`li`,l,s?{class:`task`}:void 0)),!n[e]?.trim()&&n[e+1]?.match(y)?.[1]?.length===i&&e++}r.push(m(/\d/.test(c[2])?`ol`:`ul`,a))}else{let t=[i];for(e++;e<n.length&&n[e].trim()&&!C(n[e],n[e+1]);e++)t.push(n[e]);r.push(m(`p`,v(t.join(`
8
+ `))))}}return r}function T(e){let t=e=>{if(typeof e==`string`)return document.createTextNode(e);let n=document.createElement(e.tag);for(let[t,r]of Object.entries(e.attrs??{}))n.setAttribute(t,r);return n.append(...e.kids.map(t)),n},n=document.createDocumentFragment();return n.append(...e.map(t)),n}var E=16384,D=`Not confirmed. Check the terminal before sending again.`,O=`The original already went to the session; what is here is your edited copy, unsent.`,k=/^\s*\/(quit|exit|new|resume|fork|clone|import|reload)(?=\s|$)/,A=new Map,j=e=>(t,n)=>f(`sessions/${encodeURIComponent(e)}/${t}`,n);function M(e,t,n){if(!e.ok)return[`error`,e.status===504?D:e.body.reason??`Refused (${e.status}).`];if(t&&!t.startsWith(n))return[`ok`,O]}var N=e=>{try{return e?.startsWith(`[`)?JSON.parse(e):e?[`error`,e]:void 0}catch{return[`error`,e]}},P=(e,t)=>e!==``&&!e.startsWith(t)?`Sent as it was; your edits stayed here, unsent.`:`Sent. It shows in the log once pi writes it.`,F=(e,t)=>({hidden:!e.live||!e.control||!!e.asking,disabled:t||!e.online}),I=(e,t)=>e.startsWith(t)?e.slice(t.length).replace(/^\n/,``):e;function L(e){if(!e.online)return`The stream is down; sending waits for it to come back.`;if(!e.live)return`This session is no longer live; its history stays readable.`;if(!e.control)return`No live pi pane backs this unit, so there is nothing to type into.`;if(e.mark===`waiting`)return e.answerable?`It waits on a dialog: answer it above, or in its terminal.`:`It waits on a dialog; answer it in its terminal.`}function R(e,t,n){let r=`sb-draft:${t}`,i=matchMedia(`(pointer: coarse)`).matches,a=d(`textarea`,{rows:`1`,maxlength:String(E),"aria-label":`Message`,enterkeyhint:i?`enter`:`send`}),o=d(`button`,{type:`submit`,class:`cmp-send`},`Send`),s=d(`button`,{type:`button`,class:`cmp-alt`,title:`Alt+Enter`},`Follow-up`),c=d(`button`,{type:`button`,class:`cmp-alt cmp-stop`},`Stop`),l=d(`p`,{class:`cmp-note`,role:`status`,"aria-live":`polite`}),u=d(`button`,{type:`button`,class:`cmp-link`},`Focus terminal`),f=d(`form`,{class:`composer`},d(`div`,{class:`cmp-row`},a,d(`div`,{class:`cmp-acts`},o,s,c)),d(`div`,{class:`cmp-foot`},l,u));e.replaceChildren(f);let p={name:``,control:!1,live:!1,mark:`unknown`,online:!1},m=!1,h=!1,g=e=>{try{e()}catch{}};g(()=>a.value=sessionStorage.getItem(r)??``);let _=()=>g(()=>a.value?sessionStorage.setItem(r,a.value):sessionStorage.removeItem(r)),v=()=>(a.style.height=`auto`,a.style.height=`${Math.min(a.scrollHeight,220)}px`),y=(e,t=``,...n)=>(l.dataset.tone=t,l.replaceChildren(e,...n)),b=(e,t,n)=>{let r=d(`button`,{type:`button`,class:`cmp-yes`},t),i=d(`button`,{type:`button`,class:`cmp-link`},`Cancel`);h=!0,r.onclick=()=>(h=!1,n()),i.onclick=()=>(h=!1,y(``),a.focus()),y(e,`ask`,r,i),r.focus()},[x,S]=[`${r}:note`,()=>{try{return sessionStorage.getItem(r)??``}catch{return}}],C=e=>g(()=>e?sessionStorage.setItem(x,JSON.stringify(e)):sessionStorage.removeItem(x));function w(e,i,a){let o={sent:a,answer:n(e,i)};return a!==void 0&&(C([`error`,D]),A.set(t,o),o.answer.then(e=>{let t=S();e.ok&&t!==void 0&&g(()=>sessionStorage.setItem(r,I(t,a))),C(M(e,t,a))},()=>C([`error`,D]))),T(o)}async function T(e,{sent:n}=e){m=!0,h=!1,j(),y(n===void 0?``:`Sending…`);try{let t=await e.answer;if(!f.isConnected)return;if(!t.ok)return t.status===504||C(),y(t.body.reason??`Refused (${t.status}).`,`error`);if(n===void 0)return y(``);C();let r=P(a.value,n);a.value=I(a.value,n),_(),v(),y(r,`ok`)}catch{y(D,`error`)}finally{A.get(t)===e&&A.delete(t),m=!1,j()}}function O(e,t){let n=a.value,r=L(p),i=t===n;if(m||h||!n.trim())return;if(r)return y(r,`error`);if(new TextEncoder().encode(n).length>16384)return y(`The text is longer than 16 KB; shorten it.`,`error`);let o=!i&&n.match(k)?.[0].trim();if(o)return b(`${o} ends this session or switches its pane to another one.`,`Send ${o}`,()=>O(e,n));w(`input`,{text:n,as:e,confirmed:i},n)}function j(){let e=L(p),t=p.mark===`working`;a.placeholder=e??`Message ${p.name} · ${i?``:`Enter sends, Shift+Enter adds a line · `}lands after any unsent text in its terminal`,o.disabled=m||!!e,s.hidden=c.hidden=!t||!!e,s.disabled=c.disabled=m,Object.assign(u,F(p,m))}f.addEventListener(`submit`,e=>(e.preventDefault(),O(`send`))),s.onclick=()=>O(`followUp`),c.onclick=()=>b(`Stop the current task? Queued messages go back to its terminal editor.`,`Stop`,()=>void(m||w(`keys`,{keys:[`esc`]}))),u.onclick=()=>void(m||w(`focus`,{})),a.addEventListener(`input`,()=>(_(),v())),f.addEventListener(`keydown`,e=>{e.key===`Escape`&&!e.isComposing&&(h||e.target===a&&a.value)&&(e.preventDefault(),h&&(h=!1,y(``),a.focus()))}),a.addEventListener(`keydown`,e=>{e.isComposing||e.keyCode===229||e.key!==`Enter`||e.shiftKey||i&&!e.altKey||(e.preventDefault(),O(e.altKey?`followUp`:`send`))});let R=window.visualViewport,z=()=>document.documentElement.style.setProperty(`--vvh`,`${R.height}px`);return R&&(R.addEventListener(`resize`,z),z()),v(),A.has(t)?T(A.get(t)):g(()=>{let e=N(sessionStorage.getItem(x));sessionStorage.removeItem(x),e&&y(e[1],e[0])}),{box:a,update:e=>(p=e,j()),ready:()=>!L(p)}}var z=`allow-forms allow-popups allow-popups-to-escape-sandbox`,B={},V=new Map;function H(e,t=``){let n=[];for(let r of(e.startsWith(`/`)?e:`${t}/${e}`).split(`/`))r===`..`?n.pop():r&&r!==`.`&&n.push(r);return`/${n.join(`/`)}`}var U=(e,t,n)=>e?.find(e=>H(e.display,t)===H(n,t));function W(e,t,n=[]){let r=e.get(t);return e.set(t,new Set(n.map(e=>e.display))),r?n.filter(e=>!r.has(e.display)):[]}var G=(e,t,n=``)=>(e.state.dataset.tone=n,e.state.textContent=t),K=(e,t)=>(e.failed=!0,G(e,t,`error`)),q=e=>B.src?.(e)??`doc/${encodeURIComponent(e)}/`;function J(e){if([e.loaded,e.failed]=[e.card.revision,!1],G(e,``),e.frame)return void(e.frame.src=q(e.card.cap));let t=e.card.cap,n=/\.(md|markdown)$/i.test(e.card.display),r=++e.seq;(B.load?.(t)??fetch(`api/doc/${encodeURIComponent(t)}`,{cache:`no-store`}).then(e=>e.ok?e.json():void 0)).then(t=>e.seq!==r||(t?e.body.replaceChildren(n?d(`div`,{class:`md`},T(w(t.text))):d(`pre`,{class:`doc-pre`},t.text)):K(e,`Unavailable: no longer open in its session.`)),()=>e.seq!==r||K(e,`The server could not be reached.`))}function Y(e,t){let n=V.get(t.cap);if(n)return n.tile;let r=t.display.split(`/`).pop()||t.display,i=t.kind===`html`?d(`iframe`,{class:`doc-frame`,sandbox:z,referrerpolicy:`no-referrer`,title:`${t.display}, shown by ${e}`}):void 0,a=d(`button`,{type:`button`,class:`cmp-link`},`Reload`),o=i&&d(`a`,{class:`cmp-link`,href:q(t.cap),target:`_blank`,rel:`noopener noreferrer`},`Open in a new tab`),s=i??d(`div`,{class:`doc-text`},d(`p`,{class:`chat-empty`},`Loading…`)),c=d(`span`,{class:`term-state`,role:`status`}),l=d(`em`,{class:`doc-off`},`off screen in the terminal`),u=d(`div`,{class:`doc-tile`},d(`div`,{class:`term-bar doc-bar`},d(`span`,{class:`doc-why`},t.reason),l,c,d(`span`,{class:`doc-acts`},a,o)),s),f=new IntersectionObserver(([e])=>(m.shown=!!e?.isIntersecting,m.shown&&!m.gone&&(m.failed||m.loaded!==m.card.revision)&&J(m))),p={id:`doc:${t.cap}`,title:`${r} · ${e}`,icon:`i-doc`,body:()=>u,closed:()=>(V.delete(t.cap),f.disconnect())},m={card:t,tile:p,loaded:0,seq:0,shown:!1,gone:!1,body:s,state:c,off:l,...i&&{frame:i}};return a.onclick=()=>!m.gone&&J(m),l.hidden=t.onScreen,f.observe(u),J(m),V.set(t.cap,m),p}function X(e,t,n,r){let i=new Map,a=new Set;for(let o of e.bases)for(let e of[...o.tower?[o.tower]:[],...[...o.tabs.flatMap(e=>e.units),...o.outside].flatMap(e=>[e,...e.subs])]){for(let t of e.docs??[])i.set(t.cap,t);for(let i of W(t,e.key,e.docs))e.key===n&&r(e.name,i);a.add(e.key)}for(let e of t.keys())a.has(e)||t.delete(e);ee(i)}function ee(e){for(let t of V.values()){let n=e.get(t.card.cap);if(!n){t.gone||G(t,`No longer listed: closed in its session, or the session ended.`,`error`),t.gone=!0;continue}let r=t.gone&&(G(t,``),t.failed);[t.card,t.gone,t.off.hidden]=[n,!1,n.onScreen],(n.revision!==t.loaded||r)&&(t.shown?J(t):n.revision!==t.loaded&&G(t,`Updated: the new version loads when this tile shows.`))}}function te(e,t,n,r){e.hidden=!t?.length&&!n,e.querySelector(`.d-doc-list`).replaceChildren(...(t??[]).map(e=>{let t=d(`button`,{type:`button`,class:`d-doc`,title:e.reason},d(`b`,null,e.display),d(`small`,null,e.onScreen?e.reason:`${e.reason} · off screen`));return t.onclick=()=>r(e),t})),e.querySelector(`.d-docs-cut`).hidden=!n}var ne=200;function Z(e,t){for(let n of e?.bases??[]){if(n.tower?.key===t)return{u:{key:t,name:n.tower.name,role:`tower`,mark:n.tower.mark,control:n.tower.control,pending:n.tower.pending,docs:n.tower.docs,docsCut:n.tower.docsCut,subs:[]},base:n};for(let[e,r]of[...n.tabs.map(e=>[e.label,e.units]),[void 0,n.outside]])for(let i of r){if(i.key===t)return{u:i,base:n,squad:e};let r=i.subs.find(e=>e.key===t);if(r)return{u:r,base:n,squad:e,launcher:i}}}}var Q=()=>a(`<a class="token">${i}<span class="name"></span><span class="line"><em class="role"></em><span class="note"></span></span></a>`);function $(n,i,a){e(n,i.key?{view:`session`,ws:a,key:i.key}:void 0),n.toggleAttribute(`hidden`,!1),n.toggleAttribute(`data-alert`,!!i.key&&o(i.mark)),u(n,i.mark),t(n.querySelector(`.plate`),i.role,i.mark,i.docs),r(n,`.name`,i.name),r(n,`.role`,i.role===`outside`?`unaligned`:i.role),r(n,`.note`,c(i.note,i.mark))}var re=()=>a(`<div class="col"><a class="token">${i}<span class="name"></span><span class="line"><em class="role"></em><span class="note"></span></span></a><div class="say"><p></p></div><div class="drones"></div></div>`),ie=`<div class="zoom-in"><header class="base keep"><a class="tower big"><i class="sweep"></i><i class="pulse"></i><svg><use href="#i-tower"/></svg><b class="badge"></b></a><div class="title"><span class="crown"><svg><use href="#i-crown"/></svg>head tower</span><h2></h2><p></p></div><dl class="tally"><div><dt>Squads</dt><dd></dd></div><div><dt>Units</dt><dd></dd></div><div data-mark="working"><dt>Working</dt><dd></dd></div><div data-mark="waiting"><dt>Waiting</dt><dd></dd></div></dl></header><div class="squads"></div><div class="camps"><section class="tab squad camp" data-group="outposts"><h3><svg><use href="#i-outpost"/></svg><b>Outposts</b><small></small></h3><div class="formation"></div></section><section class="tab squad camp" data-group="outside"><h3><svg><use href="#i-outside"/></svg><b>Outside the net</b><small>unaligned pi panes</small></h3><div class="formation"></div></section></div><p class="lost" hidden></p></div>`,ae=`<div class="dossier-in"><span class="tag">unit dossier</span><div class="portrait">${i}</div><h3></h3><p class="d-line"><em class="role"></em> <span class="state"></span></p><p class="d-note" hidden></p><dl class="specs"></dl><button class="cmp-link d-term" type="button" hidden>Terminal</button><div class="d-docs" hidden><h4>Documents</h4><div class="d-doc-list"></div><p class="d-docs-cut" hidden>More than the relay carries: read them in the terminal.</p></div><div class="d-drones" hidden><h4>Dispatched</h4><div class="formation"></div></div></div>`,oe=`<div class="comms-in"><header class="comms-head"><span class="logo"><svg><use href="#i-comms"/></svg></span><div><h2><small>comms</small> <span></span></h2><p></p></div><span class="chan link" data-state="live"><i></i><b>channel open</b></span></header><div class="chat-root"></div><div class="ask-root"></div><div class="composer-root"></div></div>`;function se({data:i,restart:d,openTile:f,openTerminal:m,online:h}){let g=e=>document.getElementById(e),[_,v,y]=[`ws-view`,`session-view`,`dossier`].map(g),b={view:`map`},x,S={},C,w=[],T=!1,E=new Map,D,O,k,A=0,M,N,P=()=>b.view===`map`?``:b.ws,F=(t,n)=>{let r=document.createElement(`a`);return e(r,t),r.textContent=n,r};function I(){let t=(t,n,r)=>{let i=document.createElement(t);return i.textContent=n,r&&e(i,r),i},n=g(`crumbs`),r=b;if(r.view===`map`)return n.replaceChildren(t(`b`,`Straw Net`),t(`span`,`all bases`));let i=x?.bases.find(e=>e.id===r.ws)?.label??r.ws,a=r.view===`session`?Z(x,r.key)?.u.name??D?.name??r.key:``;n.replaceChildren(t(`a`,`Straw Net`,{view:`map`}),r.view===`workspace`?t(`b`,i):t(`a`,i,{view:`workspace`,ws:r.ws}),...a?[t(`b`,a)]:[])}function L(e,t,n){let r=e=>[`map`,`workspace`,`session`].indexOf(e.view),i=r(e)<r(t)?`enter-back`:e.view===`workspace`&&n?`enter-zoom`:e.view===`session`&&t.view!==`session`?`enter-slide`:``,a=g({map:`map`,workspace:`ws-view`,session:`session-view`}[e.view]);if(i){if(n){let e=a.getBoundingClientRect();a.style.setProperty(`--ox`,`${Math.round(n.x+n.width/2-e.x)}px`),a.style.setProperty(`--oy`,`${Math.round(n.y+n.height/2-e.y)}px`)}a.classList.remove(`enter-zoom`,`enter-slide`,`enter-back`),a.offsetWidth,a.classList.add(i),setTimeout(()=>a.classList.remove(i),700)}}function z(){_.firstElementChild||_.append(a(ie));let t=_.firstElementChild,i=x?.bases.find(e=>e.id===P()),c=t.querySelector(`.lost`);if(c.hidden=!!i,c.textContent=x?`This workspace is not part of the net.`:`Waiting for the net…`,t.classList.toggle(`void`,!i),!i)return;let l=t.querySelector(`.keep`);u(l,i.mark),l.classList.toggle(`head`,i.head);let d=t.querySelector(`.tower`);d.classList.toggle(`ghost`,!i.tower),u(d,i.tower?.mark??`unknown`),e(d,i.tower?{view:`session`,ws:i.id,key:i.tower.key}:void 0),d.toggleAttribute(`hidden`,!1),d.toggleAttribute(`data-alert`,!!(i.tower&&o(i.tower.mark))),r(t,`h2`,i.label),r(t,`.title p`,i.tower?`control tower · ${s[i.tower.mark]}`:`no tower yet`);let f=i.tabs.flatMap(e=>e.units.flatMap(e=>[e,...e.subs]));[i.tabs.length,f.length,f.filter(e=>e.mark===`working`).length,f.filter(e=>e.mark===`waiting`).length].forEach((e,n)=>t.querySelectorAll(`dd`)[n].textContent=String(e));let p=(e,t)=>{u(e,t.mark),$(e.querySelector(`.token`),t,i.id);let a=e.querySelector(`.say`),o=S[t.key]??``;a.hidden=!o,r(a,`p`,o),n(e.querySelector(`.drones`),t.subs,Q,(e,t)=>(e.classList.add(`drone`),$(e,t,i.id)))};n(t.querySelector(`.squads`),i.tabs,()=>a(`<section class="tab squad"><h3><i class="flag"></i><b></b><small></small></h3><div class="formation"></div></section>`),(e,t)=>{r(e,`h3 b`,`squad ${t.label}`);let i=t.units.flatMap(e=>[e,...e.subs]);r(e,`h3 small`,`${i.length} unit${i.length===1?``:`s`}`),u(e,[`waiting`,`error`,`working`].find(e=>i.some(t=>t.mark===e))??`idle`),n(e.querySelector(`.formation`),t.units,re,p)});let m=(e,r)=>{let a=t.querySelector(`[data-group="${e}"]`);a.hidden=!r.length,n(a.querySelector(`.formation`),r,Q,(e,t)=>$(e,t,i.id))};m(`outposts`,i.outposts.map(e=>({id:e.id,name:e.label,role:`outpost`,mark:e.mark,note:e.mark===`unknown`?`no agent running`:void 0}))),m(`outside`,i.outside)}function B(){if(b.view!==`session`)return;y.firstElementChild||y.append(a(ae));let e=y.firstElementChild,i=Z(x,b.key),l=i?.u,d=l?.mark??`unknown`;u(e,d),t(e.querySelector(`.plate`),l?.role??D?.role??`member`,d),r(e,`h3`,l?.name??D?.name??b.key),r(e,`.role`,l?.role===`outside`?`unaligned`:l?.role??D?.role??`session`),r(e,`.state`,l?c(l.note,d):`not in the net`);let p=e.querySelector(`.d-note`),g=D?.state;p.hidden=g!==`recent`&&g!==`unknown`,p.replaceChildren(g===`unknown`?`Not in the net (it may have ended more than ten minutes ago).`:`No longer live; its history stays readable for ten minutes.`),D?.successor&&p.append(` Now: `,F({view:`session`,ws:P(),key:D.successor},Z(x,D.successor)?.u.name??`the new session`));let _=[[`model`,O],[`context`,k?`${k.toLocaleString()} tokens`:void 0],[`directory`,D?.header?.cwd],[`base`,i&&F({view:`workspace`,ws:i.base.id},i.base.label)],[`squad`,i?.squad],[`launcher`,i?.launcher&&F({view:`session`,ws:i.base.id,key:i.launcher.key},i.launcher.name)],[`unreadable`,D?.unreadable?`${D.unreadable} entr${D.unreadable===1?`y`:`ies`}`:void 0]];e.querySelector(`.specs`).replaceChildren(..._.filter(([,e])=>e).map(([e,t])=>{let n=a(`<div><dt></dt><dd></dd></div>`);return n.firstElementChild.textContent=e,n.lastElementChild.append(t),n}));let S=e.querySelector(`.d-term`),w=b.key,T=l?.name??D?.name??w;[S.hidden,S.onclick]=[!l?.control,()=>m(w,T)],te(e.querySelector(`.d-docs`),l?.docs,l?.docsCut,e=>f(Y(T,e))),C?.docs();let E=e.querySelector(`.d-drones`);E.hidden=!l?.subs.length,n(E.querySelector(`.formation`),(l?.subs??[]).map(e=>({...e,id:e.key})),Q,(e,t)=>(e.classList.add(`drone`),$(e,t,P())));let A=v.querySelector(`.comms-head`);if(A){r(A,`h2 span`,l?.name??D?.name??``),r(A,`p`,[i?.base.label,i?.squad&&`squad ${i.squad}`].filter(Boolean).join(` · `));let e=A.querySelector(`.chan`),t=g===`live`||!g&&l;e.dataset.state=t?`live`:`closed`,r(e,`b`,t?`channel open`:g===`recent`?`channel closed`:`no channel`)}C?.setLive(d===`working`?`${c(l.note,d)}`:o(d)?s[d]:``,d),M?.update({name:l?.name??D?.name??b.key,control:!!l?.control,live:g?g===`live`:!!l,mark:d,online:h(),asking:!!l?.pending,answerable:!!l?.pending?.answer}),N?.update(l?.pending,d===`waiting`&&!!l?.control&&h())}let V=()=>setTimeout(()=>b.view===`session`&&(document.activeElement??document.body)===document.body&&(matchMedia(`(pointer: fine)`).matches&&M?.ready()?M.box:C?.log)?.focus({preventScroll:!0}));async function H(e){let t=++A,{mountChat:n}=await l(async()=>{let{mountChat:e}=await import(`./chat-C9mKEHEC.js`);return{mountChat:e}},__vite__mapDeps([0]),import.meta.url);if(t!==A||b.view!==`session`)return;v.replaceChildren(a(oe));let r=()=>Z(x,e)?.u??{key:e,name:D?.name??e,role:D?.role??`member`,mark:`unknown`};C=n(v.querySelector(`.chat-root`),i(e),{who:r,link:t=>{let n=Z(x,e)?.u.subs.find(e=>e.name===t)??x?.bases.flatMap(e=>[...e.tabs.flatMap(e=>e.units),...e.outside]).find(e=>e.name===t);return n&&F({view:`session`,ws:Z(x,n.key)?.base.id??P(),key:n.key},t)},docFor:(t,n=U(Z(x,e)?.u.docs,D?.header?.cwd,t))=>n&&(()=>f(Y(r().name,n)))});let o=i(e).post??j(e);[M,N]=[R(v.querySelector(`.composer-root`),e,o),p(v.querySelector(`.ask-root`),o,void 0,()=>m(e,r().name))],C.reveal(b.at),w.splice(0).forEach(W),B(),V()}function W(e){C?.apply(e);for(let t of e.items??[])t.kind===`assistant`&&([O,k]=[t.model??O,t.ctx??k])}let G=()=>(I(),b.view===`workspace`?z():B());return{locate:e=>Z(x,e),show(e,t,n){let[r,i]=b.view===`session`?[b.key,b.at]:[];(e.view!==`workspace`||b.view!==`workspace`||e.ws!==b.ws)&&(S={}),b=e,_.hidden=e.view!==`workspace`,v.hidden=y.hidden=e.view!==`session`,e.view===`session`&&e.key!==r?([C,M,N,D,O,k,w,T]=[void 0,void 0,void 0,void 0,void 0,void 0,[],!1],v.replaceChildren(a(`<p class="lost">Opening the channel…</p>`)),H(e.key)):e.view===`session`&&e.at!==i?(C?.reveal(e.at),V()):e.view!==`session`&&(A++,C=M=N=void 0),G(),e!==t&&L(e,t,n)},net(e){x=e,X(e,E,b.view===`session`?b.key:void 0,(e,t)=>f(Y(e,t),!1)),b.view!==`map`&&G()},cards(e){S={...S,...e},b.view===`workspace`&&z()},history(e){if(b.view===`session`&&e.key===b.key){if(D={...D,...e,items:void 0},e.reset&&([O,k]=[void 0,void 0]),document.hidden)return w.length<ne?void w.push(e):void(T=!0);C?W(e):w.push(e),B()}},wake(){if(T)return T=!1,w=[],d();w.splice(0).forEach(e=>C&&W(e)),G()}}}export{se as mountFocus,w as n,T as t};
@@ -6,8 +6,8 @@
6
6
  <meta name="referrer" content="no-referrer">
7
7
  <title>Straw Net</title>
8
8
  <link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Crect width='32' height='32' rx='8' fill='%230b1220'/%3E%3Cpath d='M16 5l9 7h-4v13h-10V12H7z' fill='%2338e0ff'/%3E%3C/svg%3E">
9
- <script type="module" crossorigin src="./assets/index-CWA0eWOk.js"></script>
10
- <link rel="stylesheet" crossorigin href="./assets/index-CHtZ727l.css">
9
+ <script type="module" crossorigin src="./assets/index-F5nF0TfD.js"></script>
10
+ <link rel="stylesheet" crossorigin href="./assets/index-DoXkXGW_.css">
11
11
  </head>
12
12
  <body>
13
13
  <!-- Icons: game-icons.net, CC BY 3.0 (Lorc, Delapouite); see gui/ICONS.md -->
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-straw-boss",
3
- "version": "0.7.3",
3
+ "version": "0.9.0",
4
4
  "description": "Straw Boss coordination for pi in Herdr: sessions join a tab's net by choice, the first joiner is its main agent, a workspace tower opens on demand with a live monitoring dashboard, and boss_broadcast reaches the rest",
5
5
  "type": "module",
6
6
  "license": "MIT",
package/prompts/tower.md CHANGED
@@ -2,7 +2,7 @@ You are the tower of Herdr workspace {{workspace}}; agents talk to you, the boss
2
2
 
3
3
  Agents and towers message you through intercom; answer with intercom send.
4
4
 
5
- - Route each request to the main agent that owns it; when its tab has no main, to the member session doing that work; when no session owns it, back to the sender. Relay with boss_status, call_tower, and boss_broadcast; towers coordinate through the head tower, the tower of workspace {{head}}.
5
+ - Route each request to the main agent that owns it; when its tab has no main, to the member session doing that work; when no session owns it, open a main with boss_open_main without asking the boss, once, passing the request and the boss's decisions verbatim and the sender, and tell the sender if it fails. Other workspaces' work goes to their towers. Relay with boss_status, call_tower, and boss_broadcast; towers coordinate through the head tower, the tower of workspace {{head}}.
6
6
  - Friction reports: find the owner and the broken relationship (registration, endpoint, dependency, shared resource, cleanup); have the owner repair it. Return a Straw Boss defect as a finding with evidence.
7
7
  - Never ask the boss (no ask_user, robot_hand, focus changes); boss decisions go to the owning main agent.
8
8
  - Do no work yourself: no commands, process signals, or edits; even a one-step fix goes to its owner.
package/src/ask.ts CHANGED
@@ -103,15 +103,16 @@ export async function pendingOf(file: string): Promise<Pending | undefined> {
103
103
  /** Whether one of the file's open calls is secret_drop (or the file could not be read through; a missing one, a new session, is not): the terminal mirror hides, and the key pad sends only Escape, while it is. */
104
104
  export const waitsForSecret = async (file: string) => (await pendingOf(file), CACHE.get(file)?.secret !== false); // dropped from the cache meanwhile: hidden until the next look
105
105
 
106
- /** The dialogs bridges relay, by unit key, and how they join what the file says (dialogs.ts). */
107
- export interface Relayed { dialogsOf(key: string): Dialog[] | undefined; merge(key: string, file: Pending | undefined, dialogs: Dialog[], readable: boolean): Pending }
106
+ /** The dialogs bridges relay, by unit key, and how they join what the file says (dialogs.ts); `docsOf`, the documents they list. */
107
+ export interface Relayed { dialogsOf(key: string): Dialog[] | undefined; merge(key: string, file: Pending | undefined, dialogs: Dialog[], readable: boolean): Pending; docsOf?(key: string): Pick<Unit, "docs" | "docsCut"> | undefined }
108
108
 
109
- /** Adds what each waiting unit waits on to a snapshot. A unit with a relayed dialog waits even before Herdr reports its pane blocked. */
109
+ /** Adds what each waiting unit waits on, and the documents each unit shows, to a snapshot. A unit with a relayed dialog waits even before Herdr reports its pane blocked. */
110
110
  export async function withPending(net: Net, fileOf: (key: string) => string | undefined, relayed?: Relayed): Promise<Net> {
111
111
  const jobs: Promise<unknown>[] = [];
112
112
  for (const b of net.bases)
113
113
  for (const u of [...(b.tower ? [b.tower] : []), ...[...b.tabs.flatMap((t) => t.units), ...b.outside].flatMap((u) => [u, ...u.subs])]) {
114
114
  const dialogs = relayed?.dialogsOf(u.key);
115
+ Object.assign(u, relayed?.docsOf?.(u.key));
115
116
  if (dialogs?.length) [u.mark, b.mark] = ["waiting", "waiting"];
116
117
  if (dialogs?.length && u !== b.tower) (u as Unit).note = "waiting on a question";
117
118
  const file = u.mark === "waiting" ? fileOf(u.key) : undefined;
package/src/blocked.ts CHANGED
@@ -1,11 +1,20 @@
1
- import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
1
+ import type { ExtensionAPI, UIPromptEndEvent, UIPromptStartEvent } from "@earendil-works/pi-coding-agent";
2
2
 
3
3
  // rpiv-ask-user-question reports its wait as `rpiv:ask-user:blocked`, while Herdr's pi integration and the GUI listen for
4
4
  // `herdr:blocked`. This relays one into the other, once per open questionnaire, so a session waiting on it shows as blocked.
5
+ // Pi's own `ui_prompt_start` / `ui_prompt_end` (one pair per outermost ctx.ui dialog, whichever extension opened it) get the same
6
+ // treatment for select, confirm, input and editor; `custom` stays out, as pi itself does not count it as blocking and the custom dialogs that do block signal it.
5
7
  // Every pi does this, joined or not, subagents included.
6
8
 
7
9
  export const RPIV_BLOCKED = "rpiv:ask-user:blocked";
8
10
  const LABEL = "Waiting for an answer to ask_user_question";
11
+ const DIALOGS = new Set(["select", "confirm", "input", "editor"]);
12
+ // The title is extension-supplied text for the sidebar: control and format characters become spaces, and it is cut to 80 characters.
13
+ const labelOf = ({ kind, title }: UIPromptStartEvent | UIPromptEndEvent) => {
14
+ const text = [...String(title ?? "").replace(/[\p{Cc}\p{Cf}\s]+/gu, " ").trim()];
15
+ const shown = text.slice(0, 80).join("") + (text.length > 80 ? "…" : "");
16
+ return `Waiting for your answer: ${shown || `${kind} dialog`}`;
17
+ };
9
18
 
10
19
  export default function blockedRelay(pi: ExtensionAPI): void {
11
20
  const open = new Set<string>();
@@ -16,4 +25,18 @@ export default function blockedRelay(pi: ExtensionAPI): void {
16
25
  else open.delete(toolCallId);
17
26
  pi.events.emit("herdr:blocked", active ? { active, label: LABEL } : { active });
18
27
  });
28
+
29
+ // Pi emits both events unawaited and walks the extensions one after another, so behind a slow earlier handler this one can see a dialog's end before its start:
30
+ // the count may dip below zero and comes back, and blocked is reported only while it is above zero, so the pairs stay balanced in any arrival order.
31
+ let depth = 0;
32
+ const dialog = (by: 1 | -1) => (event: UIPromptStartEvent | UIPromptEndEvent) => {
33
+ if (!DIALOGS.has(event.kind)) return;
34
+ const was = depth > 0;
35
+ depth += by;
36
+ if (was === depth > 0) return;
37
+ // An end that arrives after a reload or new session retired this runtime cannot be relayed; that runtime has no dialog left to report.
38
+ try { pi.events.emit("herdr:blocked", depth > 0 ? { active: true, label: labelOf(event) } : { active: false }); } catch {}
39
+ };
40
+ pi.on("ui_prompt_start", dialog(1));
41
+ pi.on("ui_prompt_end", dialog(-1));
19
42
  }
package/src/bridge.ts CHANGED
@@ -2,6 +2,7 @@ import { createHash, randomUUID } from "node:crypto";
2
2
  import { readFileSync, statSync } from "node:fs";
3
3
  import { join } from "node:path";
4
4
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
5
+ import { type Doc, type Fields, fieldsOk, listenDocs, submitForm } from "./documents.ts";
5
6
  import { type Answer, type Bus, type Dialog, OWNERS, type OwnerAdapter, type Verdict } from "./owners.ts";
6
7
  import { type Editor, probeEditor } from "./probe.ts";
7
8
 
@@ -13,7 +14,8 @@ import { type Editor, probeEditor } from "./probe.ts";
13
14
  // Per dialog: open → settling (an answer is with the owner) → open again if refused, or closed by the owner's `closed`, the end of its tool
14
15
  // call, or the session's end. An answer whose verdict does not come within 2 s leaves it `unknown`: later answers get `busy` until it closes.
15
16
  // The bridge follows gui.json from the session's start (a stat every 2 s), so it is on G's channel whenever a server runs: the key pad's
16
- // Enter guard asks it what the editor would submit (`probe`, probe.ts), and it answers with a class and at most a command name, never the draft.
17
+ // Enter guard asks it what the editor would submit (`probe`, probe.ts), and it answers with a class and at most a command name, never the draft. It relays
18
+ // the documents the session shows with its dialogs (documents.ts) and hands a form answer for one of them to their owner.
17
19
 
18
20
  export const REGISTER = "intercom:extension-register";
19
21
  const VERDICT_MS = 2000, POLL_MS = 2000, KEEP_MS = 120_000, KEEP = 16, MAX_GUIS = 8, MAX_MESSAGE = 15 * 1024;
@@ -21,15 +23,18 @@ export const guiNamespace = (g: string) => `straw-boss/gui/${createHash("sha256"
21
23
  export type Outcome = "accepted" | "rejected" | "stale" | "busy" | "unknown";
22
24
  export interface Ack { version: 1; type: "ack"; bridgeId: string; id: string; answerId: string; outcome: Outcome; reason?: string; by?: string }
23
25
  /** `closed`: dialogs closed in the last 2 minutes (at most 16), so an answer that comes late hears who closed it, and a resent one that won, that it won. */
24
- export interface Dialogs { version: 1; type: "dialogs"; bridgeId: string; pane: string; file: string | null; dialogs: Dialog[]; closed: Closed[] }
26
+ /** `documents`: what the document owner lists now; `docsCut` when they did not fit the message (they are dropped first). */
27
+ export interface Dialogs { version: 1; type: "dialogs"; bridgeId: string; pane: string; file: string | null; dialogs: Dialog[]; closed: Closed[]; documents?: Doc[]; docsCut?: true }
25
28
  export interface Closed { id: string; by: string; answerId?: string }
26
29
  export interface ProbeResult extends Editor { version: 1; type: "probe-result"; bridgeId: string; probeId: string }
27
- export type ToHub = Dialogs | Ack | ProbeResult;
30
+ export interface FormReply { version: 1; type: "form-result"; bridgeId: string; id: string; answerId: string; accepted: boolean; reason?: string }
31
+ export type ToHub = Dialogs | Ack | ProbeResult | FormReply;
32
+ export interface FormMessage { version: 1; type: "form"; to: string; id: string; answerId: string; fields: Fields }
28
33
  export interface AnswerMessage extends Answer { version: 1; type: "answer"; to: string }
29
34
  /** The hub when it runs in this session: it takes what a remote bridge would publish, from this session's id. */
30
35
  export interface LocalHub { receive(from: string, msg: ToHub): void }
31
36
  /** What the hub calls on the bridge of its own session. */
32
- export interface Bridge { deliver(msg: AnswerMessage, reply: (ack: Ack) => void): void; sync(): void; probe(): Editor }
37
+ export interface Bridge { deliver(msg: AnswerMessage, reply: (ack: Ack) => void): void; sync(): void; probe(): Editor; form(msg: FormMessage, reply: (r: FormReply) => void): void }
33
38
  interface Channel { snapshot(): { connected: boolean; owner?: { sessionId: string; epoch: string } }; publish(payload: unknown, options?: { audience?: "owner" | "capable" }): void }
34
39
  interface Rec { adapter: OwnerAdapter; d: Dialog; state: "open" | "settling" | "unknown" | "closed"; answerId?: string; accepted?: boolean; by?: string; at?: number; last?: Ack; waiting: ((ack: Ack) => void)[] }
35
40
  type Event = { type: string; fromSessionId?: string; owner?: { sessionId: string; epoch: string }; payload?: Record<string, unknown> };
@@ -38,7 +43,7 @@ export function startBridge(pi: ExtensionAPI, { env, dir, local }: { env: NodeJS
38
43
  const bus = pi.events as Bus, bridgeId = randomUUID(), recs = new Map<string, Rec>(), calls = new Map<string, { tool: string; signal?: AbortSignal; stop?: () => void }>();
39
44
  const chans = new Map<string, Channel>(), me = () => env.PI_INTERCOM_SESSION_ID ?? "";
40
45
  let g: string | undefined, gpid = 0, seen: number | undefined, poll: ReturnType<typeof setInterval> | undefined, offs: (() => void)[] = [], warned = false;
41
- let file: () => string | undefined = () => undefined, tell = (_text: string) => {}, editor = (): Editor => ({ editor: "unknown" });
46
+ let file: () => string | undefined = () => undefined, tell = (_text: string) => {}, editor = (): Editor => ({ editor: "unknown" }), docs: Doc[] = [];
42
47
  const open = () => [...recs.values()].filter((r) => r.state !== "closed");
43
48
  const alive = () => { try { return gpid > 0 && (process.kill(gpid, 0), true); } catch (e) { return (e as NodeJS.ErrnoException).code === "EPERM"; } }; // a server that died without removing gui.json gets no probe answers
44
49
 
@@ -53,8 +58,10 @@ export function startBridge(pi: ExtensionAPI, { env, dir, local }: { env: NodeJS
53
58
  if (!target()) return;
54
59
  const dialogs = final ? [] : open().map((r) => ({ ...r.d, ...(r.state !== "open" ? { busy: true as const } : {}) }));
55
60
  const closed = final ? [] : [...recs.values()].filter((r) => r.state === "closed").map((r) => ({ id: r.d.id, by: r.by!, ...(r.by === "remote" && r.answerId ? { answerId: r.answerId } : {}) }));
56
- let msg: Dialogs = { version: 1, type: "dialogs", bridgeId, pane: env.HERDR_PANE_ID!, file: file() ?? null, dialogs, closed };
57
- if (JSON.stringify(msg).length > MAX_MESSAGE) msg = { ...msg, dialogs: dialogs.map(({ select, questions, ...d }) => ({ ...d, big: true as const })) }; // shown read-only: too long to show
61
+ let msg: Dialogs = { version: 1, type: "dialogs", bridgeId, pane: env.HERDR_PANE_ID!, file: file() ?? null, dialogs, closed, documents: final ? [] : docs };
62
+ const big = () => Buffer.byteLength(JSON.stringify(msg)) > MAX_MESSAGE; // in bytes, as the broker counts
63
+ if (big()) msg = { ...msg, documents: [], docsCut: true }; // the documents go first: they stay in the terminal, and the unit says so
64
+ if (big()) msg = { ...msg, dialogs: dialogs.map(({ select, questions, ...d }) => ({ ...d, big: true as const })) }; // shown read-only: too long to show
58
65
  send(target(), msg);
59
66
  };
60
67
  // gui.json names the server's session; it is read at the session's start and then on every mtime change, checked every 2 s (unref'd).
@@ -87,7 +94,8 @@ export function startBridge(pi: ExtensionAPI, { env, dir, local }: { env: NodeJS
87
94
  // Only G's own session, as the broker's current owner of this channel and the server gui.json names now, may answer, probe, or ask for a sync.
88
95
  if (e.type !== "message" || p?.version !== 1 || e.fromSessionId !== from || owner?.sessionId !== from || e.owner?.epoch !== owner.epoch) return;
89
96
  if (p.type === "sync" && from === g) push();
90
- // SAFETY: deliver checks the fields it uses before it hands anything to an owner.
97
+ // SAFETY: deliver and form check the fields they use before they hand anything to an owner.
98
+ if (p.type === "form" && p.to === bridgeId && from === g) form(p as unknown as FormMessage, (r) => send(from, r));
91
99
  if (p.type === "answer" && p.to === bridgeId && from === g) deliver(p as unknown as AnswerMessage, (ack) => send(from, ack));
92
100
  if (p.type === "probe" && p.to === bridgeId && from === g && alive() && typeof p.probeId === "string" && p.probeId.length <= 64) send(from, { version: 1, type: "probe-result", bridgeId, probeId: p.probeId, ...editor() });
93
101
  };
@@ -144,12 +152,20 @@ export function startBridge(pi: ExtensionAPI, { env, dir, local }: { env: NodeJS
144
152
  push();
145
153
  }
146
154
 
155
+ /** A form answer for a listed HTML document goes to its owner, which delivers it as a Chawan submission and replays a repeated answerId. */
156
+ function form(m: FormMessage, reply: (r: FormReply) => void) {
157
+ const out = (r: { accepted: boolean; reason?: string }) => reply({ version: 1, type: "form-result", bridgeId, id: m.id, answerId: m.answerId, ...r });
158
+ if (typeof m.id !== "string" || typeof m.answerId !== "string" || m.answerId.length > 64 || !fieldsOk(m.fields)) return;
159
+ if (docs.some((d) => d.id === m.id && d.kind === "html")) submitForm(bus, m.id, m.answerId, m.fields, out);
160
+ else out({ accepted: false, reason: "closed" });
161
+ }
147
162
  pi.on("session_start", (_e, ctx) => {
148
163
  offs.forEach((off) => off());
164
+ docs = [];
149
165
  file = () => ctx.sessionManager.getSessionFile();
150
166
  tell = (text) => ctx.hasUI && ctx.ui.notify(text, "warning");
151
167
  editor = () => probeEditor(ctx);
152
- offs = OWNERS.map((o) => o.listen(bus, report));
168
+ offs = [...OWNERS.map((o) => o.listen(bus, report)), listenDocs(bus, (d) => ((docs = d), changed()))];
153
169
  seen = -1; // a new session file (/new, /resume) is told to the server at once
154
170
  follow();
155
171
  if (!poll) (poll = setInterval(follow, POLL_MS)).unref?.();
@@ -166,6 +182,7 @@ export function startBridge(pi: ExtensionAPI, { env, dir, local }: { env: NodeJS
166
182
  open().filter((r) => r.d.toolCallId === e.toolCallId).forEach((r) => report.closed(r.d.id, r.accepted ? "remote" : "error")); // the owner failed to say so
167
183
  });
168
184
  pi.on("session_shutdown", () => {
185
+ docs = [];
169
186
  open().forEach((r) => report.closed(r.d.id, "error"));
170
187
  push(true);
171
188
  [...calls.values()].forEach((c) => c.stop?.());
@@ -173,5 +190,5 @@ export function startBridge(pi: ExtensionAPI, { env, dir, local }: { env: NodeJS
173
190
  offs.splice(0).forEach((off) => off());
174
191
  (clearInterval(poll), (poll = undefined));
175
192
  });
176
- return { deliver, sync: () => ((seen = -1), follow()), probe: () => editor() };
193
+ return { deliver, sync: () => ((seen = -1), follow()), probe: () => editor(), form };
177
194
  }
package/src/dashboard.ts CHANGED
@@ -71,9 +71,12 @@ export function render({ title, mark, head, rows, gui }: Snapshot, frame: number
71
71
  }
72
72
 
73
73
  /** Whether a session file is the child `id`'s: pi-herdr-agents names it `<timestamp>_<id>-<hex>-<hex>-<hex>.jsonl`, so `kid` does not match `kid-2`'s or `kidney`'s. */
74
- const ownFile = (file: string | undefined, id: string) => new RegExp(`_${id.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(-[0-9a-f]{1,8}-[0-9a-f]{1,8}-[0-9a-f]{1,4})?\\.jsonl$`).test(file ?? "");
74
+ export const ownFile = (file: string | undefined, id: string) => new RegExp(`_${id.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(-[0-9a-f]{1,8}-[0-9a-f]{1,8}-[0-9a-f]{1,4})?\\.jsonl$`).test(file ?? "");
75
75
 
76
- function readJson(path: string): Record<string, unknown> {
76
+ /** The artifacts directory pi-herdr-agents keeps for the session file `file`: its launch records and the activity of the children it launched. */
77
+ export const artifacts = (file: string) => join(dirname(file), "artifacts", basename(file, ".jsonl").split("_").pop()!);
78
+
79
+ export function readJson(path: string): Record<string, unknown> {
77
80
  try {
78
81
  return JSON.parse(readFileSync(path, "utf8"));
79
82
  } catch {
@@ -85,7 +88,7 @@ function readJson(path: string): Record<string, unknown> {
85
88
  export function subagents(parent: Pane | undefined, all: Pane[], now: number, limit = MAX_CHILDREN): Row[] {
86
89
  const file = parent?.agent_session?.value;
87
90
  if (!file) return [];
88
- const dir = join(dirname(file), "artifacts", basename(file, ".jsonl").split("_").pop()!);
91
+ const dir = artifacts(file);
89
92
  let files: string[] = [];
90
93
  try {
91
94
  files = readdirSync(join(dir, "subagent-activity"));