pi-straw-boss 0.3.2 → 0.4.1

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
@@ -12,11 +12,11 @@
12
12
 
13
13
  Run a few agents on the same project and they start stepping on each other: two dev servers on one port, two branches editing the same migration, and no agent knows the others exist. pi-straw-boss lets them find each other and coordinate, without a scheduler or a new service.
14
14
 
15
- - **Opt-in net.** Each session is asked once whether its work shares resources or needs to collaborate. Only those that say yes join.
16
- - **A main agent per tab.** The first to join a tab coordinates it; `/boss` takes over.
17
- - **A tower per workspace.** It opens on demand in its own tab, pinned first, and coordinates the main agents and the other towers. You don't need to talk to it.
15
+ - **Everyone joins.** Each pi in Herdr joins its tab's net at start as a member; nothing to ask or remember.
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.
18
18
  - **A live dashboard** in the tower: who joined, and what they and their subagents are doing.
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.
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
22
 
@@ -30,7 +30,7 @@ You need pi running inside Herdr 0.9.4 or newer, and [pi-intercom](https://www.n
30
30
 
31
31
  ## Quick start
32
32
 
33
- 1. Open pi in a Herdr tab and give it work. When the work needs to share or coordinate, the agent joins the net. You can also type `/boss`.
33
+ 1. Open pi in a Herdr tab and give it work. It joins the net by itself; when its work grows it promotes itself to the tab's main agent, which also opens the workspace tower. You can take over with `/boss`.
34
34
  2. Type `/boss status` to see each tab's main agent and the towers.
35
35
  3. Type `/boss tower` to open the workspace's tower and watch its dashboard. Agents open it themselves when they call it.
36
36
  4. Type `/boss gui` from any session to see the whole net in your browser.
@@ -39,13 +39,13 @@ You need pi running inside Herdr 0.9.4 or newer, and [pi-intercom](https://www.n
39
39
 
40
40
  | Name | What it does |
41
41
  |---|---|
42
- | `boss_join` | Join this tab's net. |
42
+ | `boss_promote` | Become this tab's main agent (refused while it has one). |
43
43
  | `boss_status` | Main agents, this workspace's tower, and the other towers. |
44
44
  | `call_tower` | Message this workspace's tower, or another workspace's. |
45
45
  | `boss_broadcast` | Message a tab, a workspace, all towers, or every session. |
46
- | `/boss` | Join and become this tab's main agent. |
46
+ | `/boss` | Take over as this tab's main agent. |
47
47
  | `/boss status`, `/boss tower` | Show the status; open and focus the tower. |
48
- | `/boss gui`, `/boss gui stop` | Open the net in the browser (any session, joins nothing); stop its server. |
48
+ | `/boss gui`, `/boss gui stop` | Open the net in the browser (any session); stop its server. |
49
49
 
50
50
  Subagents and sessions in Herdr worktree workspaces never join: the session that launched them speaks for them. Outside Herdr the extension does nothing.
51
51
 
@@ -0,0 +1,7 @@
1
+ import{l as e,n as t}from"./index-DWQG2ADm.js";var n=(...e)=>e.filter(e=>e!==!1&&e!=null);function r(e,t,...r){let i=document.createElement(e);for(let[e,n]of Object.entries(t??{}))i.setAttribute(e,n);return i.append(...n(...r)),i}function i(){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 a(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)}var o=(e,t,n)=>({tag:e,kids:t,attrs:n}),s=/^(?:https?:\/\/|mailto:)[^\s\u0000-\u001f]+$/i,c=(e,t)=>o(`a`,t,{href:e,rel:`noopener noreferrer`,target:`_blank`}),l=/(`+)([^`]|[^`][^]{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 u(e){let t=[],n=0;for(let r of e.matchAll(l))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(o(`em`,u(r[5]??r[6]))):r[7]===void 0?t.push(c(r[9],[r[9]])):t.push(s.test(r[8])?c(r[8],u(r[7])):r[0]):t.push(o(`del`,u(r[4]))):t.push(o(`strong`,u(r[3]))):t.push(o(`code`,[r[2]]));return n<e.length&&t.push(e.slice(n)),t}var d=/^(\s*)([-*+]|\d+[.)])\s+(.*)$/,f=/^\s*([-*_])(\s*\1){2,}\s*$/,p=/^\s*\|?\s*:?-+:?\s*(\|\s*:?-+:?\s*)*\|?\s*$/,m=e=>e.trim().replace(/^\|/,``).replace(/\|$/,``).split(`|`).map(e=>e.trim()),h=(e,t)=>/^\s*(`{3,}|~{3,}|#{1,6}\s|>)/.test(e)||f.test(e)||d.test(e)||e.includes(`|`)&&t!==void 0&&p.test(t)&&t.includes(`-`);function g(e,t=0){if(t>=16)return[o(`p`,[e])];let n=e.replace(/\r\n?/g,`
2
+ `).split(`
3
+ `),r=[];for(let e=0;e<n.length;){let i=n[e],a=n[e+1],s=i.match(/^\s*(`{3,}|~{3,})\s*([\w+-]*)/),c=i.match(/^\s*(#{1,6})\s+(.*?)\s*#*\s*$/),l=i.match(d);if(!i.trim())e++;else if(s){let t=[];for(e++;e<n.length&&!n[e].trim().startsWith(s[1]);e++)t.push(n[e]);e++,r.push(o(`pre`,[o(`code`,[t.join(`
4
+ `)],s[2]?{"data-lang":s[2]}:void 0)]))}else if(c)r.push(o(`h${c[1].length}`,u(c[2]))),e++;else if(f.test(i))r.push(o(`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(o(`blockquote`,g(i.join(`
5
+ `),t+1)))}else if(i.includes(`|`)&&a!==void 0&&p.test(a)&&a.includes(`-`)){let t=[];for(e+=2;e<n.length&&n[e].trim()&&n[e].includes(`|`);e++)t.push(m(n[e]));r.push(o(`table`,[o(`thead`,[o(`tr`,m(i).map(e=>o(`th`,u(e))))]),o(`tbody`,t.map(e=>o(`tr`,e.map(e=>o(`td`,u(e))))))]))}else if(l){let i=l[1].length,a=[];for(;e<n.length;){let r=n[e].match(d);if(!r||r[1].length!==i||/\d/.test(r[2])!==/\d/.test(l[2]))break;let s=[r[3]],c=r[3].match(/^\[([ xX])\]\s+(.*)$/);for(c&&(s[0]=c[2]),e++;e<n.length&&n[e].trim()&&n[e].search(/\S/)>i;e++)s.push(n[e].replace(RegExp(`^ {0,${i+2}}`),``));let u=g(s.join(`
6
+ `),t+1),f=u[0],p=c?.[1]!==` `;c&&(typeof f==`object`&&f.tag===`p`?f.kids:u).unshift(o(`i`,p?[`✓`]:[],{class:`check`,"data-done":String(p),"aria-label":p?`done`:`not done`})),a.push(o(`li`,u,c?{class:`task`}:void 0)),!n[e]?.trim()&&n[e+1]?.match(d)?.[1]?.length===i&&e++}r.push(o(/\d/.test(l[2])?`ol`:`ul`,a))}else{let t=[i];for(e++;e<n.length&&n[e].trim()&&!h(n[e],n[e+1]);e++)t.push(n[e]);r.push(o(`p`,u(t.join(`
7
+ `))))}}return r}function _(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}function v(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 y=e=>e?new Date(e).toLocaleTimeString([],{hour:`2-digit`,minute:`2-digit`,hour12:!1}):``,b=e=>e>=1024?`${(e/1024).toFixed(e>=10240?0:1)} KB`:`${e} B`,x={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`},S=n=>{let r=e(`<span class="ava"><svg><use/></svg></span>`);return r.querySelector(`use`).setAttribute(`href`,`#i-${t[n]??n}`),r};function C(e){let t=[],n=0;for(let i of e.matchAll(/https?:\/\/[^\s<>)\]]+/g))t.push(e.slice(n,i.index),r(`a`,{href:i[0],rel:`noopener noreferrer`,target:`_blank`},i[0])),n=i.index+i[0].length;return[...t,e.slice(n)]}function w(e,t,{who:n,link:o,openTile:s}){let c=v(),l=new Map,u=r(`div`,{class:`chat-list`}),d=r(`div`,{class:`chat-top`}),f=r(`div`,{class:`chat-live`}),p=r(`button`,{class:`chat-pill`,type:`button`,hidden:``}),m=r(`p`,{class:`chat-empty`},`No transmissions yet.`),h=r(`div`,{class:`chat-scroll`},d,u,f);e.replaceChildren(h,p);let w=i(),T=!1,E=!1,D,O,k=0,A=()=>h.scrollHeight-h.scrollTop-h.clientHeight<80,j=(e=w.button(c.detached))=>(p.hidden=!e,p.textContent=e??``),M=()=>(w.jump(),O=void 0,h.scrollTop=h.scrollHeight,j());async function N(e){T=!0;let n=await a(c,t,e,e=>(z(e),B(e))).finally(()=>T=!1),r=l.get(e);if(D===e){if(!n||!r)return M();w.hold(),O=r,r.scrollIntoView({block:`center`}),r.classList.remove(`lit`),r.offsetWidth,r.classList.add(`lit`),clearTimeout(k),k=window.setTimeout(()=>(O=void 0,r.classList.remove(`lit`)),2e3),j()}}let P=()=>D?void N(D):M(),F=(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`)},I=e=>s({id:`doc:${e}`,title:e.split(`/`).pop()||e,body:()=>{let n=r(`div`,{class:`md`},r(`p`,{class:`chat-empty`},`Loading…`));return t.doc(e).then(e=>n.replaceChildren(e===void 0?`Unavailable.`:_(g(e)))),n}});function L(e){let n=c.result(e.id),i=r(`pre`,{class:`out`},n?.text??``),a=n?.more?r(`button`,{class:`more`,type:`button`},`show all (${b(n.bytes??0)})`):null;a?.addEventListener(`click`,()=>F(a,i,n.id,e=>e.text));let o=n?n.isError?`error`:`done`:`working`,s=e.name===`show_doc`&&t.doc?r(`button`,{class:`more open`,type:`button`},`open beside`):null;return s?.addEventListener(`click`,t=>(t.preventDefault(),e.summary.split(`, `).forEach(I))),r(`details`,{class:`act`},r(`summary`,{"data-mark":o},r(`b`,{class:`badge`}),r(`code`,null,e.name),r(`span`,{class:`sum`},e.summary),s),r(`pre`,{class:`args`},e.args??``),n&&r(`div`,{class:`result`},n.images?r(`em`,null,`${n.images} image(s)`):null,i,a))}function R(e){let t=r(`time`,null,y(e.at));switch(e.kind){case`user`:return r(`article`,{class:`tx you`},r(`header`,null,t,r(`b`,null,`you`),S(`user`)),r(`div`,{class:`bubble`},...C(e.text??``),e.images?r(`em`,null,` · ${e.images} image(s)`):null));case`assistant`:{let i=n(),a=r(`pre`,{class:`thought-text`}),o=e.thought?r(`button`,{class:`thought`,type:`button`},e.thought<0?`◇ thought · redacted`:`◇ thought · ${e.thought.toLocaleString()} chars`):null;return e.thought&&e.thought>0&&o?.addEventListener(`click`,()=>F(o,a,e.id,e=>e.thoughtText)),r(`article`,{class:`tx in`},S(i.role),r(`div`,{class:`tx-body`},r(`header`,null,r(`b`,null,i.name),e.model?r(`span`,null,e.model):null,t,e.stop?r(`span`,{class:`stop`,"data-mark":`error`},e.stop):null),o,a,e.text?r(`div`,{class:`md`},_(g(e.text))):null,e.error?r(`p`,{class:`fault`},e.error):null,...(e.tools??[]).map(L)))}case`bash`:return r(`article`,{class:`tx sys`},r(`details`,{class:`act`},r(`summary`,{"data-mark":e.exit?`error`:`done`},r(`b`,{class:`badge`}),r(`code`,null,`shell`),r(`span`,{class:`sum`},`! ${e.command}`),e.exit?r(`span`,{class:`stop`,"data-mark":`error`},`exit ${e.exit}`):null),e.text?r(`pre`,{class:`out`},e.text):null));case`notice`:return r(`article`,{class:`tx sys`},r(`details`,{class:`notice`},r(`summary`,null,S(`comms`),r(`em`,null,`incoming`),x[e.type??``]??e.type??`notice`,` `,e.from&&o(e.from)||(e.from??``),t),r(`pre`,null,e.text??``)));case`divider`:return r(`article`,{class:`tx rule`},r(`details`,null,r(`summary`,null,e.type===`compaction`?`context compacted · ${(e.tokens??0).toLocaleString()} tokens summarized`:`branch summary`),r(`pre`,null,e.text??``)));default:return r(`article`,{class:`tx note`},e.text??``,` `,t)}}function z(e){e.rebuild&&(l.clear(),u.replaceChildren());for(let t of e.dropped)l.get(t)?.remove(),l.delete(t);let t=e.added.map(e=>(l.set(e.id,R(e)),l.get(e.id)));e.front?u.prepend(...t):u.append(...t),m.remove(),c.items.length||u.append(m),d.hidden=!c.before}let B=e=>e.touched.forEach(e=>{let t=l.get(e),n=c.items.find(t=>t.id===e);if(t&&n){let r=R(n);t.replaceWith(r),l.set(e,r)}}),V=async()=>{if(!T&&c.before&&E){T=!0;try{let e=h.scrollHeight,n=c.prepend(await t.older(c.before));z(n),B(n),h.scrollTop+=h.scrollHeight-e}finally{T=!1}c.before&&h.clientHeight&&d.getBoundingClientRect().bottom>=h.getBoundingClientRect().top&&V()}};new IntersectionObserver(e=>e.some(e=>e.isIntersecting)&&void V(),{root:h}).observe(d),p.addEventListener(`click`,()=>{D=void 0,c.detached?t.latest().then(e=>(z(c.reset(e)),M())):M()}),h.addEventListener(`scroll`,()=>(w.scrolled(A()),j()),{passive:!0});for(let e of[`wheel`,`touchstart`,`pointerdown`,`keydown`])h.addEventListener(e,()=>O=D=void 0,{passive:!0});let H=new ResizeObserver(()=>w.pinned&&!c.detached?h.scrollTop=h.scrollHeight:O?.scrollIntoView({block:`center`}));return H.observe(u),H.observe(h),{apply(e){if(e.reset)z(c.reset({items:e.items??[],before:e.before})),E=!0,P();else if(e.items?.length){let t=c.append(e.items);z(t),B(t),w.arrived(t.added.length)&&!c.detached?M():j()}},reveal(e){D=e,E&&P()},setLive(e,t){f.dataset.mark=t,f.lastChild?.textContent!==e&&f.replaceChildren(...e?[r(`b`,{class:`badge`}),r(`span`,null,e)]:[])}}}export{w as mountChat};
@@ -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){let e=(e,i,a,o,s)=>n.set(e,{key:e,ws:r.id,name:i,role:a,mark:o,note:s,at:t});r.tower&&e(r.tower.key,r.tower.name,`tower`,r.tower.mark);for(let t of[...r.tabs.flatMap(e=>e.units),...r.outside])for(let n of[t,...t.subs])e(n.key,n.name,n.role,n.mark,n.note)}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){v(e,n);let r=e.querySelector(`use`),i=`#i-${f[t]??`member`}`;r.getAttribute(`href`)!==i&&r.setAttribute(`href`,i)}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),_(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 E=45e3,ae=1e3,oe=3e4,se=(e,t)=>Math.min(oe,ae*2**(e-1))*(.8+.4*t);function D(e){try{return JSON.parse(e)}catch{return}}function ce(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(),E),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,se(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=D(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:D(e)??`the server reported a problem the page cannot read`}),l())}),r=e.setTimeout(()=>u(),E)};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 le=(function(){let e=typeof document<`u`&&document.createElement(`link`).relList;return e&&e.supports&&e.supports(`modulepreload`)?`modulepreload`:`preload`})(),ue=function(e,t){return new URL(e,t).href},de={},fe=function(e){return e.pathname.endsWith(`.css`)},pe=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},O=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=ue(t,n);let r=s(t),i=fe(r);return pe(de,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`:le,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)})},k=ie(),A=e=>document.getElementById(e),me=e=>e?new Date(e).toLocaleTimeString([],{hour12:!1}):`never`,j,M,N,P,F={state:`connecting`,attempt:0},I={view:`map`},L=!1,R,z,B,V=e=>z?e(z):void B?.then(e);function he(){B??=O(async()=>{let{mountFocus:e}=await import(`./views-Dh8gbtFc.js`);return{mountFocus:e}},[],import.meta.url).then(({mountFocus:e})=>(z=e({data:ve,restart:()=>Q.restart(),openTile:ge}),z.show(I,I),N&&z.net(N),z))}var H,U,W=e=>{if(H)return e(H);U??=O(async()=>{let{mountPanels:e}=await import(`./layer-D7Fuxmih.js`);return{mountPanels:e}},[],import.meta.url).then(({mountPanels:e})=>H=e({label:e=>N?.bases.find(t=>t.id===e)?.label??e,recent:async e=>(await(Z?Z.data(e).latest():X(e,`?limit=12`))).items})),U.then(e)},ge=(e,t)=>W(n=>n.tiles.open(e,t));function G(e){if(N=P=e,document.hidden)return;k.stats(e);let t=i(e,Date.now()),r=a(R,t);(r.length||H)&&W(e=>e.alerts(r,t)),R=t,k.log(n(M,e)),M=e,V(t=>t.net(e)),I.view===`map`&&(k.show(e),j=e,P=void 0)}function K(e){let t=I,n=e.view===`workspace`&&t.view===`map`?document.querySelector(`#bases [data-key="${CSS.escape(e.ws)}"]`)?.getBoundingClientRect():void 0;I=e,e.view!==`map`&&he(),A(`map`).hidden=e.view!==`map`,A(`legend`).hidden=e.view===`session`,k.still(e.view!==`map`),V(r=>r.show(e,t,n)),e.view===`map`&&N&&N!==j&&(G(N),requestAnimationFrame(k.layout)),L&&d(e)!==d(t)&&Q.restart()}function q(e){F=e;let t=e.state===`reconnecting`||e.state===`offline`;document.body.dataset.stale=A(`stage`).dataset.stale=String(t),A(`link`).dataset.state=e.state,A(`link-text`).textContent={connecting:`connecting`,live:`live`,reconnecting:`reconnecting`,offline:`offline`}[e.state];let n=A(`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=me(e.lastContact),n.replaceChildren(`${r} Last update `,i,`.`)}function J(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 Y=e=>fetch(`api/${e}`,{cache:`no-store`}).then(e=>e.ok?e.json():Promise.reject(Error(String(e.status)))),X=(e,t=``)=>Y(`history/${encodeURIComponent(e)}${t}`),Z=void 0,ve=e=>({older:t=>X(e,`?before=${encodeURIComponent(t)}`),latest:()=>X(e),entry:t=>Y(`entry/${encodeURIComponent(e)}/${encodeURIComponent(t)}`).catch(()=>void 0)}),ye=()=>{let e=_e(d(I));return e.listen(`history`,e=>J(`history`,e)),e.listen(`cards`,e=>J(`cards`,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=R?.get(n.key);r&&(e.preventDefault(),W(e=>e.stack.show(r)))},!0);var be=ee(e=>K(e)),Q=ce({open:ye,now:Date.now,setTimeout:(e,t)=>window.setTimeout(e,t),clearTimeout:e=>window.clearTimeout(e),random:Math.random,hidden:()=>document.hidden,onNet:G,onStatus:q}),$=()=>Q.resume();document.addEventListener(`visibilitychange`,()=>{if(k.pause(document.hidden),document.hidden)return Q.hide();P&&G(P),V(e=>e.wake()),$()}),addEventListener(`keydown`,e=>{e.key!==`Escape`||e.defaultPrevented||(e.preventDefault(),!H?.stack.close()&&I.view!==`map`&&be.go(u(I)))});for(let e of[`focus`,`pageshow`,`online`])window.addEventListener(e,()=>!document.hidden&&$());q(F),K(be.route()),Q.start(),L=!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,O as t,v as u};
@@ -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)}.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(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}.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,.specs dt,.stop,.dialog .tag,.intel h4,.pending b,.rx 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,.rx,.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{grid-template-rows:auto minmax(0,1fr) auto;height:calc(100vh - 190px);min-height:380px;display:grid;overflow:hidden}.comms-head,.tile>header,.rx{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}.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}.rx{border-top:1px solid var(--line)}.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,.rx svg{width:22px;height:22px;color:var(--gold)}.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}.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>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{align-items:center;gap:10px;margin-top:14px;display:flex}.go,.later,.chat-pill{font:600 13px var(--sans);cursor:pointer;border-radius:10px;padding:7px 13px;text-decoration:none}.go,.chat-pill{background:var(--c);color:#06101e}.later{border:1px solid var(--line);color:var(--text);background:0 0}.queued{margin-left:auto}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}}@media (width<=900px){.work{flex-direction:column;align-items:stretch}.tile{position:static}.keep{flex-wrap:wrap}}@media (prefers-reduced-motion:reduce){*,:before,:after{transition:none!important;animation:none!important}}
@@ -0,0 +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-DWQG2ADm.js";var u=(e,t)=>o(e.mark)-o(t.mark)||e.at-t.at;function d(e){let t=[],n=[],r=()=>e.render(t.map(e=>e.alert),n),i=(i,a)=>{let o=t.findIndex(e=>e.alert.key===i.key),s=o>=0?t.splice(o,1)[0]:{alert:i,back:a,raised:i.mark};t.push(s),n=n.filter(e=>e.key!==i.key),r(),e.enter(s.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(u)}r()},prune(e){n=n.filter(t=>e(t.key)?.mark===t.mark);for(let n of t)n.alert={...n.alert,...e(n.alert.key),at:n.alert.at};r()},show:t=>i(t,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(u)),!c&&l?(i(l,s.back),!0):(r(),!e.restore(s.back)&&c&&e.enter(c.alert),!0)}}}function f(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(-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=t.filter(t=>t.id!==e),n===e&&(n=(t[i]??t[i-1])?.id),r())},activate(e){n=e,r()}}}function p(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
+ `).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 m=()=>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><footer><a class="go">Open comms</a><button class="later" type="button">Later</button><span class="queued"></span></footer></section>`),h=()=>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 g(o){let[u,g,_,v,y]=[s(`dock`,HTMLElement),s(`work`,HTMLElement),s(`tiles`,HTMLElement),s(`tile-tabs`,HTMLElement),s(`alerts`,HTMLButtonElement)],b=new Map,x=(t,n,i=b.get(n.key))=>{r(t,`.said`,i?.failed?`History unavailable.`:i?.said??`No messages yet.`),t.querySelector(`.pending`).hidden=!i?.pending,r(t,`.pending span`,i?.pending??``),e(t.querySelector(`.go`),{view:`session`,ws:n.ws,key:n.key,...i?.at&&{at:i.at}})},S=(e,t={said:`…`})=>{b.set(e.key,t),o.recent(e.key).then(p,()=>({failed:!0})).then(n=>{let r=b.get(e.key)===t&&(b.set(e.key,n),u.querySelector(`.dialog[data-key="${CSS.escape(e.key)}"]`));r&&x(r,e)})},C=(e,n,i,a)=>{l(e,n.mark),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`,c(n.note,n.mark)),r(e,`.where`,o.label(n.ws)),r(e,`.tag`,{waiting:`question`,error:`error`,done:`finished`}[n.mark]??`update`),r(e,`.queued`,a?`+${a} queued`:``),b.has(n.key)||S(n),x(e,n)},w=d({render(e,o){n(u,e.map(e=>({...e,id:e.key})),m,(t,n)=>C(t,n,e.length-1-e.findIndex(e=>e.key===n.key),o.length));let s=o[0];y.disabled=!s,y.dataset.mark=s?.mark??`unknown`,r(y,`#alert-count`,String(o.length)),r(y,`#alert-text`,s?`${s.name} · ${c(s.note,s.mark)}`:`no alerts`),n(y.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(),1))});y.addEventListener(`click`,()=>w.next()),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,E=()=>_.classList.toggle(`tabbed`,D.tiles.length>1&&g.clientWidth<460+D.tiles.length*400),D=f({render(e,t){_.hidden=!e.length,n(_,e,e=>{let t=h();return t.querySelector(`.tile-body`).append(e.body()),t.querySelector(`.x`).addEventListener(`click`,()=>D.close(e.id)),t},(e,n)=>{r(e,`header b`,n.title),e.classList.toggle(`active`,n.id===t)}),n(v,e,()=>a(`<button type="button" class="tab-btn"></button>`),(e,n)=>{e.textContent=n.title,e.classList.toggle(`active`,n.id===t),e.onclick=()=>D.activate(n.id)}),_.prepend(v),e.length!==T&&(T=e.length,E())},enter:e=>requestAnimationFrame(()=>_.querySelector(`.tile[data-key="${CSS.escape(e.id)}"]`)?.focus())});return new ResizeObserver(E).observe(g),{tiles:D,stack:w,alerts(e,t){for(let t of e)S(t),w.arrive(t);w.prune(e=>t.get(e))}}}export{g as mountPanels};
@@ -0,0 +1,2 @@
1
+ const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["./index-xkoHrvS5.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-DWQG2ADm.js";var d=200;function f(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,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 p=()=>a(`<a class="token">${i}<span class="name"></span><span class="line"><em class="role"></em><span class="note"></span></span></a>`);function m(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),r(n,`.name`,i.name),r(n,`.role`,i.role===`outside`?`unaligned`:i.role),r(n,`.note`,c(i.note,i.mark))}var h=()=>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>`),g=`<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>`,_=`<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><div class="d-drones" hidden><h4>Dispatched</h4><div class="formation"></div></div></div>`,v=`<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><p class="rx"><svg><use href="#i-comms"/></svg><b>receive only</b><span>Replying from the browser comes in a later version. For now, reply in this unit's terminal pane in Herdr.</span></p></div>`;function y({data:i,restart:y,openTile:b}){let x=e=>document.getElementById(e),[S,C,w]=[`ws-view`,`session-view`,`dossier`].map(x),T={view:`map`},E,D={},O,k=[],A=!1,j,M,N,P=0,F=()=>T.view===`map`?``:T.ws,I=(t,n)=>{let r=document.createElement(`a`);return e(r,t),r.textContent=n,r};function L(){let t=(t,n,r)=>{let i=document.createElement(t);return i.textContent=n,r&&e(i,r),i},n=x(`crumbs`),r=T;if(r.view===`map`)return n.replaceChildren(t(`b`,`Straw Net`),t(`span`,`all bases`));let i=E?.bases.find(e=>e.id===r.ws)?.label??r.ws,a=r.view===`session`?f(E,r.key)?.u.name??j?.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 R(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=x({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(){S.firstElementChild||S.append(a(g));let t=S.firstElementChild,i=E?.bases.find(e=>e.id===F()),c=t.querySelector(`.lost`);if(c.hidden=!!i,c.textContent=E?`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 _=(e,t)=>{u(e,t.mark),m(e.querySelector(`.token`),t,i.id);let a=e.querySelector(`.say`),o=D[t.key]??``;a.hidden=!o,r(a,`p`,o),n(e.querySelector(`.drones`),t.subs,p,(e,t)=>(e.classList.add(`drone`),m(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,h,_)});let v=(e,r)=>{let a=t.querySelector(`[data-group="${e}"]`);a.hidden=!r.length,n(a.querySelector(`.formation`),r,p,(e,t)=>m(e,t,i.id))};v(`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}))),v(`outside`,i.outside)}function B(){if(T.view!==`session`)return;w.firstElementChild||w.append(a(_));let e=w.firstElementChild,i=f(E,T.key),l=i?.u,d=l?.mark??`unknown`;u(e,d),t(e.querySelector(`.plate`),l?.role??j?.role??`member`,d),r(e,`h3`,l?.name??j?.name??T.key),r(e,`.role`,l?.role===`outside`?`unaligned`:l?.role??j?.role??`session`),r(e,`.state`,l?c(l.note,d):`not in the net`);let h=e.querySelector(`.d-note`),g=j?.state;h.hidden=g!==`recent`&&g!==`unknown`,h.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.`),j?.successor&&h.append(` Now: `,I({view:`session`,ws:F(),key:j.successor},f(E,j.successor)?.u.name??`the new session`));let v=[[`model`,M],[`context`,N?`${N.toLocaleString()} tokens`:void 0],[`directory`,j?.header?.cwd],[`base`,i&&I({view:`workspace`,ws:i.base.id},i.base.label)],[`squad`,i?.squad],[`launcher`,i?.launcher&&I({view:`session`,ws:i.base.id,key:i.launcher.key},i.launcher.name)],[`unreadable`,j?.unreadable?`${j.unreadable} entr${j.unreadable===1?`y`:`ies`}`:void 0]];e.querySelector(`.specs`).replaceChildren(...v.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 y=e.querySelector(`.d-drones`);y.hidden=!l?.subs.length,n(y.querySelector(`.formation`),(l?.subs??[]).map(e=>({...e,id:e.key})),p,(e,t)=>(e.classList.add(`drone`),m(e,t,F())));let b=C.querySelector(`.comms-head`);if(b){r(b,`h2 span`,l?.name??j?.name??``),r(b,`p`,[i?.base.label,i?.squad&&`squad ${i.squad}`].filter(Boolean).join(` · `));let e=b.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`)}O?.setLive(d===`working`?`${c(l.note,d)}`:o(d)?s[d]:``,d)}async function V(e){let t=++P,{mountChat:n}=await l(async()=>{let{mountChat:e}=await import(`./chat-B8KUfo8d.js`);return{mountChat:e}},__vite__mapDeps([0]),import.meta.url);t===P&&T.view===`session`&&(C.replaceChildren(a(v)),O=n(C.querySelector(`.chat-root`),i(e),{who:()=>f(E,e)?.u??{key:e,name:j?.name??e,role:j?.role??`member`,mark:`unknown`},link:t=>{let n=f(E,e)?.u.subs.find(e=>e.name===t)??E?.bases.flatMap(e=>[...e.tabs.flatMap(e=>e.units),...e.outside]).find(e=>e.name===t);return n&&I({view:`session`,ws:f(E,n.key)?.base.id??F(),key:n.key},t)},openTile:b}),O.reveal(T.at),k.splice(0).forEach(H),B())}function H(e){O?.apply(e);for(let t of e.items??[])t.kind===`assistant`&&([M,N]=[t.model??M,t.ctx??N])}let U=()=>(L(),T.view===`workspace`?z():B());return{locate:e=>f(E,e),show(e,t,n){let[r,i]=T.view===`session`?[T.key,T.at]:[];(e.view!==`workspace`||T.view!==`workspace`||e.ws!==T.ws)&&(D={}),T=e,S.hidden=e.view!==`workspace`,C.hidden=w.hidden=e.view!==`session`,e.view===`session`&&e.key!==r?([O,j,M,N,k,A]=[void 0,void 0,void 0,void 0,[],!1],C.replaceChildren(a(`<p class="lost">Opening the channel…</p>`)),V(e.key)):e.view===`session`&&e.at!==i?O?.reveal(e.at):e.view!==`session`&&(P++,O=void 0),U(),e!==t&&R(e,t,n)},net(e){E=e,T.view!==`map`&&U()},cards(e){D={...D,...e},T.view===`workspace`&&z()},history(e){if(T.view===`session`&&e.key===T.key){if(j={...j,...e,items:void 0},e.reset&&([M,N]=[void 0,void 0]),document.hidden)return k.length<d?void k.push(e):void(A=!0);O?H(e):k.push(e),B()}},wake(){if(A)return A=!1,k=[],y();k.splice(0).forEach(e=>O&&H(e)),U()}}}export{y as mountFocus};
@@ -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-CH3266oL.js"></script>
10
- <link rel="stylesheet" crossorigin href="./assets/index-BP87OlaT.css">
9
+ <script type="module" crossorigin src="./assets/index-DWQG2ADm.js"></script>
10
+ <link rel="stylesheet" crossorigin href="./assets/index-xkoHrvS5.css">
11
11
  </head>
12
12
  <body>
13
13
  <!-- Icons: game-icons.net, CC BY 3.0 (Lorc, Delapouite); see gui/ICONS.md -->
@@ -18,31 +18,43 @@
18
18
  <symbol id="i-member" viewBox="0 0 512 512" fill="currentColor"><path d="M255.875 19.47c-33.142 0-59.844 26.822-59.844 60.186 0 33.364 26.703 60.156 59.845 60.156 33.142 0 59.875-26.792 59.875-60.156S289.017 19.47 255.875 19.47zm-50.688 120.343c-2.908 1.23-5.658 2.53-8.187 3.937-14.467 8.046-21.47 17.86-21.47 27.094 0 9.234 7.003 19.08 21.47 27.125 14.467 8.044 35.51 13.436 58.875 13.436 23.365 0 44.408-5.392 58.875-13.437 14.467-8.047 21.47-17.892 21.47-27.126 0-9.234-7.003-19.048-21.47-27.094-2.53-1.406-5.28-2.708-8.188-3.938-13.696 11.647-31.392 18.688-50.687 18.688-19.3 0-36.996-7.034-50.688-18.688zm78.875 87.906c-8.948 1.54-18.394 2.374-28.187 2.374-9.315 0-18.316-.758-26.875-2.156 2.69 6.923 4.36 14.186 4.906 21.656 2.456 33.554-17.04 69.573-58.47 93.594l-.155.093-.155.095c-20.062 10.653-30.28 24.056-30.28 36.97 0 12.9 10.28 26.46 30.343 37.217 20.062 10.76 48.86 17.844 80.75 17.844s60.687-7.085 80.75-17.844c20.062-10.758 30.343-24.318 30.343-37.218 0-13.127-10.773-26.656-31.655-37.406l-.22-.125-.186-.094c-40.344-23.394-58.705-59.676-55.908-93.22.626-7.497 2.31-14.813 5-21.78zM128.845 395.655c-5.592 3.72-10.256 7.61-13.875 11.53-6.9 7.48-9.94 14.64-9.94 21.845 0 7.206 3.04 14.397 9.94 21.876 6.898 7.48 17.6 14.852 31.28 21.125 27.36 12.547 66.42 20.69 109.625 20.69 43.206 0 82.295-8.143 109.656-20.69 13.682-6.27 24.352-13.644 31.25-21.124 6.9-7.48 9.97-14.67 9.97-21.875 0-7.204-3.07-14.363-9.97-21.842-3.597-3.902-8.238-7.767-13.78-11.47-5.638 15.6-19.584 28.706-37.5 38.313-23.533 12.62-54.947 20.095-89.563 20.095-34.615 0-66.06-7.474-89.593-20.094-17.94-9.62-31.887-22.747-37.5-38.374z"/></symbol>
19
19
  <symbol id="i-subagent" viewBox="0 0 512 512" fill="currentColor"><path d="M407 24.98v14.04h16V25zM88.99 25v14.03H105V25zM487 57H344.9v15.96H487zm-320 0H25v15.98h142zm256 33.93h-16.1v27.97l13.7.1h2.4zm-318 .1H88.97v28.07H105zm272 45.87l-.1 32.1h-78.5l-16-32h-52.8l-16 32.1-78.6-.1v-32H55.03l-12.04 48L199 214.4V208c.1-31.4 25.7-56.9 57.1-57 31.3 0 57 25.6 56.9 57 0 4.1.1 6.4.1 6.4L469 185.1 457 137zM256.1 169c-21.6 0-39 17.4-39.1 39.1.1 21.5 17.5 38.9 39.1 38.9s39-17.4 38.9-39c.1-21.5-17.3-39-38.9-39zm-.2 14c13.8 0 25 11.3 25.1 25 0 13.7-11.3 25.1-25 25-13.7 0-25.1-11.3-25-24.9 0-13.8 11.3-25 24.9-25.1zm-54.7 40.5L215 279h82l13.9-55.4c-6.8 23.9-28.8 41.4-54.8 41.4-26.1 0-48.1-17.6-54.9-41.5zm-35.8 4.2L60.35 321.1l83.85 107.7c5-5 11.3-8.8 18.2-11.1L100.2 318l87.9-73-3.4-13.4zm181.3.2l-19.4 3.8-3.3 13.4 87.8 73-62.2 99.6c7 2.3 13.2 6.2 18.3 11.3l83.8-107.8zM176 433.6c-15 0-26.9 11.9-26.9 26.7 0 14.9 11.9 26.7 27 26.8 14.9-.1 26.9-12 26.9-26.8-.1-14.8-11.9-26.7-27-26.7zm159.9 0c-14.9.1-27 11.9-26.9 26.8 0 14.7 11.9 26.6 27 26.6 15 0 27-11.8 26.9-26.7.1-14.8-11.9-26.7-27-26.7z"/></symbol>
20
20
  <symbol id="i-outpost" viewBox="0 0 512 512" fill="currentColor"><path d="M361.155 91.245l-18 .193.42 38.98c-45.773 13.285-108.533 19.738-166.474 23.573 35.097 96.284 99.357 173.77 157.845 257.13 20.718-19.655 51.11-31.983 83.46-36.01-20.8-18.109-36.634-27.966-58.833-70.438 31.27 37.085 52.579 48.467 77.623 62.006 3.263-13.094 8.938-24.638 18.721-32.674 8.667-7.12 20.026-10.654 33.53-10.344-46.874-59.763-101.67-117.054-127.83-189.435l-.462-42.98zM163.25 102.92l-17.998.244s.25 18.34.56 36.97c.156 9.316.325 18.703.489 25.929.06 2.636.117 4.58.174 6.542-34.378 83.733-69.154 160.993-123.92 233.442 33.635-1.387 66.326-1.203 98.552-.041 22.263-62.617 23.346-134.855 35.627-202.006 11.417 68.562 10.566 139.445 33.483 205.83 42.962 3.082 85.69 7.198 129.35 10.926-55.67-79.151-118.213-155.037-155.118-249.365-.05-1.782-.1-3.396-.152-5.737-.162-7.156-.333-16.523-.488-25.82-.31-18.594-.559-36.914-.559-36.914z"/></symbol>
21
+ <symbol id="i-user" viewBox="0 0 512 512" fill="currentColor"><path d="M250.882 22.802c-23.366 3.035-44.553 30.444-44.553 65.935 0 19.558 6.771 36.856 16.695 48.815l11.84 14.263-18.217 3.424c-12.9 2.425-22.358 9.24-30.443 20.336-8.085 11.097-14.266 26.558-18.598 44.375-7.843 32.28-9.568 71.693-9.842 106.436h42.868l11.771 157.836c29.894 6.748 61.811 6.51 90.602.025l10.414-157.86h40.816c-.027-35.169-.477-75.126-7.584-107.65-3.918-17.934-9.858-33.372-18.04-44.343-8.185-10.97-18.08-17.745-32.563-19.989l-18.592-2.88 11.736-14.704c9.495-11.897 15.932-28.997 15.932-48.082 0-37.838-23.655-65.844-49.399-65.844z"/></symbol>
22
+ <symbol id="i-doc" viewBox="0 0 512 512" fill="currentColor"><path d="M103.432 17.844a86.782 86.782 0 0 0-3.348.08c-2.547.11-5.083.334-7.604.678-20.167 2.747-39.158 13.667-52.324 33.67-24.613 37.4 2.194 98.025 56.625 98.025.536 0 1.058-.012 1.583-.022v.704h60.565c-10.758 31.994-30.298 66.596-52.448 101.43a283.192 283.192 0 0 0-6.29 10.406l34.878 35.733-56.263 9.423c-32.728 85.966-27.42 182.074 48.277 182.074v-.002l9.31.066c23.83-.57 46.732-4.298 61.325-12.887 4.174-2.458 7.63-5.237 10.467-8.42h-32.446c-20.33 5.95-40.8-6.94-47.396-25.922-8.956-25.77 7.52-52.36 31.867-60.452a55.552 55.552 0 0 1 17.565-2.834v-.406h178.33c-.57-44.403 16.35-90.125 49.184-126 23.955-26.176 42.03-60.624 51.3-94.846l-41.225-24.932 38.272-6.906-43.37-25.807h-.005l.002-.002.002.002 52.127-8.85c-5.232-39.134-28.84-68.113-77.37-68.113C341.14 32.26 222.11 35.29 149.34 28.496c-14.888-6.763-30.547-10.723-45.908-10.652zm.464 18.703c13.137.043 27.407 3.804 41.247 10.63l.033-.07c4.667 4.735 8.542 9.737 11.68 14.985H82.92l10.574 14.78c10.608 14.83 19.803 31.99 21.09 42.024.643 5.017-.11 7.167-1.814 8.836-1.705 1.67-6.228 3.875-15.99 3.875-40.587 0-56.878-44.952-41.012-69.06C66.238 46.64 79.582 39.22 95.002 37.12a64.146 64.146 0 0 1 8.894-.573zM118.5 80.78h46.28c4.275 15.734 3.656 33.07-.544 51.51H131.52c1.9-5.027 2.268-10.574 1.6-15.77-1.527-11.913-7.405-24.065-14.62-35.74zm101.553 317.095c6.44 6.84 11.192 15.31 13.37 24.914 3.797 16.736 3.092 31.208-1.767 43.204-4.526 11.175-12.576 19.79-22.29 26h237.19c14.448 0 24.887-5.678 32.2-14.318 7.312-8.64 11.2-20.514 10.705-32.352a47.733 47.733 0 0 0-2.407-13.18l-69.91-8.205 42.017-20.528c-8.32-3.442-18.64-5.537-31.375-5.537H220.053zm-42.668.506a36.999 36.999 0 0 0-3.457.153 34.825 34.825 0 0 0-7.824 1.63c-15.11 5.02-25.338 21.54-20.11 36.583 3.673 10.57 15.347 17.71 25.654 13.938l1.555-.57h43.354c.946-6.36.754-13.882-1.358-23.192-3.71-16.358-20.543-28.483-37.815-28.54z"/></symbol>
23
+ <symbol id="i-comms" viewBox="0 0 512 512" fill="currentColor"><path d="M71 32v135h18V32H71zm256 0v135h18V32h-18zm-208 95.604V167h18v-39.396h-18zm256 0V167h18v-39.396h-18zM57 185v99.27l16 16V487h110V300.27l16-16V185H57zm256 0v99.27l16 16V487h110V300.27l16-16V185H313zm-96 7v80h16.432v-80H217zm256 0v80h16.432v-80H473zm-393 7h96v18H80v-18zm256 0h96v18h-96v-18zM80 231h96v18H80v-18zm256 0h96v18h-96v-18zM80 263h96v18H80v-18zm256 0h96v18h-96v-18zm-224 41a24 24 0 0 1 24 24 24 24 0 0 1-24 24 24 24 0 0 1-24-24 24 24 0 0 1 24-24zm256 0a24 24 0 0 1 24 24 24 24 0 0 1-24 24 24 24 0 0 1-24-24 24 24 0 0 1 24-24z"/></symbol>
24
+ <symbol id="i-outside" viewBox="0 0 512 512" fill="currentColor"><path d="M257.375 32.03C139.957 87.197 42.343 247.886 17.5 367.75c71.742 0 124.22 22.845 162.094 63.03l10.47-11.436c-28.06-28.873-64.935-52.446-113.564-75.906 26.142-65.033 66.028-163.458 116.72-169.188 19.835-2.243 41.05 9.735 64.155 43.438 72.33-120.27 141.014 38.54 180.875 125.75-47.687 25.854-84.5 49.463-112.97 78.718l9.25 10.406c37.335-39.758 89.657-64.812 162.72-64.812C467.784 249.384 377.24 90.37 257.375 32.03zm-79.72 243.314c-15.3-.083-26.405 6.436-26.405 16.656 0 12.58 16.834 26.038 37.594 30.063 20.76 4.024 37.594-2.92 37.594-15.5s-16.835-26.038-37.594-30.063c-3.893-.755-7.657-1.137-11.188-1.156zm156.345 0c-3.53.02-7.295.4-11.188 1.156-20.76 4.025-37.593 17.483-37.593 30.063 0 12.58 16.833 19.524 37.592 15.5 20.76-4.025 37.594-17.484 37.594-30.063 0-10.22-11.105-16.74-26.406-16.656z"/></symbol>
21
25
  </defs></svg>
22
26
  <div class="shell">
23
27
  <header class="hud">
24
- <div class="brand">
28
+ <a class="brand" href="./" data-route="" aria-label="Straw Net: all bases">
25
29
  <span class="logo"><svg><use href="#i-tower"/></svg></span>
26
30
  <div><h1>Straw Net</h1><p>read-only live view</p></div>
27
- </div>
31
+ </a>
28
32
  <dl class="stats" aria-label="Net totals">
29
33
  <div><dt>Bases</dt><dd id="s-bases">0</dd></div>
30
34
  <div><dt>Agents</dt><dd id="s-agents">0</dd></div>
31
35
  <div data-mark="working"><dt>Working</dt><dd id="s-working">0</dd></div>
32
36
  <div data-mark="waiting"><dt>Waiting</dt><dd id="s-waiting">0</dd></div>
33
37
  </dl>
38
+ <button class="alerts" id="alerts" type="button" data-mark="unknown" aria-label="Alerts" disabled><span class="faces" id="alert-faces"></span><span class="count"><b id="alert-count">0</b><small id="alert-text">no alerts</small></span></button>
34
39
  <div class="link" id="link" data-state="connecting"><i></i><span id="link-text">connecting</span></div>
35
40
  </header>
36
41
  <div class="banner" id="banner" role="status" hidden></div>
42
+ <nav class="crumbs" id="crumbs" aria-label="Location"><b>Straw Net</b><span>all bases</span></nav>
37
43
  <div class="stage" id="stage">
38
- <main class="map" id="map">
39
- <svg class="links" id="links" aria-hidden="true"></svg>
40
- <div class="bases" id="bases"></div>
41
- <p class="empty" id="empty" hidden>No workspaces yet. Open a pi session in Herdr and the net appears here.</p>
42
- </main>
44
+ <div class="work" id="work">
45
+ <main class="map view" id="map">
46
+ <svg class="links" id="links" aria-hidden="true"></svg>
47
+ <div class="bases" id="bases"></div>
48
+ <p class="empty" id="empty" hidden>No workspaces yet. Open a pi session in Herdr and the net appears here.</p>
49
+ </main>
50
+ <section class="zoom view" id="ws-view" hidden></section>
51
+ <section class="comms view" id="session-view" hidden></section>
52
+ <div class="tiles" id="tiles" hidden><nav class="tile-tabs" id="tile-tabs" aria-label="Documents"></nav></div>
53
+ </div>
43
54
  <aside class="side">
55
+ <section class="panel dossier" id="dossier" hidden></section>
44
56
  <section class="panel"><h2>Net log</h2><ol class="log" id="log"></ol></section>
45
- <section class="panel"><h2>States</h2>
57
+ <section class="panel" id="legend"><h2>States</h2>
46
58
  <ul class="legend">
47
59
  <li data-mark="working"><b class="badge"></b>working</li>
48
60
  <li data-mark="waiting"><b class="badge"></b>waiting on a question</li>
@@ -55,5 +67,6 @@
55
67
  </aside>
56
68
  </div>
57
69
  </div>
70
+ <div class="dock" id="dock" aria-label="Alerts" role="region"></div>
58
71
  </body>
59
72
  </html>
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pi-straw-boss",
3
- "version": "0.3.2",
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",
3
+ "version": "0.4.1",
4
+ "description": "Straw Boss coordination for pi in Herdr: every pi in Herdr joins its tab's net, a session promotes itself to main agent when its work grows, the workspace tower opens with the first main and has a live monitoring dashboard, and boss_broadcast reaches the rest",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "author": "wayne930242",
@@ -0,0 +1 @@
1
+ Straw Boss: you are a member of tab {{tab}}, which has no main agent yet. When your work grows, meaning several steps, dispatching workers, or touching more than one app, call `boss_promote` to become the tab's main agent; dispatching a subagent promotes you automatically. Report coordination friction, such as an agent, endpoint, or shared resource you cannot reach, with call_tower.
package/prompts/member.md CHANGED
@@ -1 +1 @@
1
- Straw Boss: you joined the net of tab {{tab}}, whose main agent is {{main}}. The tower of workspace {{workspace}} is {{tower}}. Take shared-resource and collaboration questions to the main agent through intercom. Report coordination friction, such as an agent, endpoint, or shared resource you cannot reach, with call_tower.
1
+ Straw Boss: you are a member of tab {{tab}}, whose main agent is {{main}}. The tower of workspace {{workspace}} is {{tower}}. Before you dispatch a subagent, tell {{main}} through intercom what you will do and which resources (ports, databases, files, branches) it uses; take other shared-resource questions there too. Report coordination friction, such as an agent, endpoint, or shared resource you cannot reach, with call_tower.
package/src/dashboard.ts CHANGED
@@ -17,7 +17,7 @@ import { headOf, type HerdrRun, isSub, type Pane, panes, parentOf, type Session,
17
17
 
18
18
  export type Mark = "working" | "waiting" | "idle" | "done" | "error" | "unknown";
19
19
  /** One line of the tree. `net` rows are tab (0) > session (1) > subagent (2); `towers` rows are flat. */
20
- export interface Row { group: "net" | "towers"; depth: number; mark: Mark; text: string; kind?: string; note?: string; workspace?: string }
20
+ export interface Row { group: "net" | "towers"; depth: number; mark: Mark; text: string; kind?: string; note?: string; workspace?: string; id?: string; file?: string; pane?: string }
21
21
  export interface Snapshot { title: string; mark: Mark; head: boolean; rows: Row[] }
22
22
  export type Tone = "accent" | "warning" | "muted" | "success" | "dim" | "error" | "border" | "title";
23
23
  export type Paint = (tone: Tone, text: string) => string;
@@ -93,13 +93,15 @@ export function subagents(parent: Pane | undefined, all: Pane[], now: number): R
93
93
  const activity = readJson(path);
94
94
  // A child whose pane is gone, or back at a shell prompt, has finished.
95
95
  const mark: Mark = pane?.agent === "pi" ? markOf(pane.agent_status) : "done";
96
- let script: string | undefined;
96
+ // Only a pi pane's session file is the child's transcript; a reused pane running another agent is not read.
97
+ let script: string | undefined, file = pane?.agent === "pi" ? pane.agent_session?.value : undefined;
97
98
  try {
98
99
  script = readdirSync(join(dir, "subagent-scripts")).find((f) => f.endsWith(`-${id}.sh`));
99
- } catch {} // the launch script is only a fallback source for the name
100
+ file ??= readFileSync(join(dir, "subagent-scripts", script!), "utf8").match(/PI_SUBAGENT_SESSION='([^']+)'/)?.[1];
101
+ } catch {} // the launch script is only a fallback source for the name and the session file
100
102
  const name = pane?.label ?? script?.slice(0, -`-${id}.sh`.length) ?? id.slice(0, 8);
101
103
  const doing = activity.toolActive ? String(activity.toolName) : String(activity.phase ?? "");
102
- return { group: "net" as const, depth: 2, mark, text: name, kind: "subagent", workspace: pane?.workspace_id, note: { working: doing, waiting: "waiting on a question", done: "finished" }[mark as "working"] };
104
+ return { group: "net" as const, depth: 2, mark, text: name, kind: "subagent", workspace: pane?.workspace_id, id, file, pane: pane?.agent === "pi" ? pane.pane_id : undefined, note: { working: doing, waiting: "waiting on a question", done: "finished" }[mark as "working"] };
103
105
  })
104
106
  .sort((a, b) => Number(a.mark === "done") - Number(b.mark === "done") || a.text.localeCompare(b.text))
105
107
  .slice(0, MAX_CHILDREN);
package/src/gui.ts CHANGED
@@ -5,12 +5,17 @@ import { createServer, type IncomingMessage, request, type ServerResponse } from
5
5
  import type { AddressInfo } from "node:net";
6
6
  import { extname, join, resolve, sep } from "node:path";
7
7
  import { fileURLToPath } from "node:url";
8
- import type { Net } from "./net.ts";
8
+ import { readers } from "./history.ts";
9
+ import type { Found, Net, Ref } from "./net.ts";
10
+ import { api, type Hub, watchSession, type Watch, watchWorkspace } from "./watch.ts";
9
11
 
10
12
  // The browser view's server: serves the built page and one live stream of the net, read-only, on 127.0.0.1 only.
11
13
  // Every URL starts with a random token (/<token>/index.html, /<token>/events) and a request without it, or with another Host
12
14
  // header, is refused, so other local pages cannot read the net. The stream sends the full snapshot on connect and on every
13
15
  // change, plus a `ping` event every 15 s so a client can tell a silent drop from a quiet net. It polls only while someone listens.
16
+ // The page has routes (/<token>/w/<workspace>[/s/<session>]): any such URL answers the same index.html, which carries a <base href>
17
+ // so its assets and the stream resolve from any depth. `events?watch=<session key>` adds that session's chat history to the stream,
18
+ // `events?watch=w:<workspace id>` the last line of each session there; /api/history and /api/entry page through a session's file.
14
19
 
15
20
  export const BUNDLE = fileURLToPath(new URL("../dist/gui", import.meta.url));
16
21
  const TYPES: Record<string, string> = { ".html": "text/html; charset=utf-8", ".js": "text/javascript; charset=utf-8", ".css": "text/css; charset=utf-8", ".svg": "image/svg+xml", ".json": "application/json" };
@@ -18,38 +23,41 @@ const HEADERS = {
18
23
  "cache-control": "no-store",
19
24
  "referrer-policy": "no-referrer",
20
25
  "x-content-type-options": "nosniff",
21
- "content-security-policy": "default-src 'none'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src 'self'",
26
+ "content-security-policy": "default-src 'none'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src 'self'; base-uri 'self'; frame-ancestors 'none'",
22
27
  };
23
28
 
24
29
  export interface Gui { url: string; port: number; token: string; stop(): Promise<void> }
25
- export interface GuiOptions { read(): Promise<Net>; dir?: string; port?: number; token?: string; pollMs?: number; pingMs?: number }
30
+ /** `net` is the registry behind the net reading (the allow list of session keys); without it no session can be watched. */
31
+ export interface GuiOptions { read(): Promise<Net>; net?: { lookup(key: string): Found | undefined; workspace(id: string): Ref[] }; dir?: string; port?: number; token?: string; pollMs?: number; pingMs?: number }
26
32
 
27
33
  /** Starts the server. A preferred `port` (and `token`) is reused when free, so a restarted server keeps the URL an open page already has. */
28
- export async function startGui({ read, dir = BUNDLE, port = 0, token = randomBytes(16).toString("hex"), pollMs = 2000, pingMs = 15_000 }: GuiOptions): Promise<Gui> {
34
+ export async function startGui({ read, net, dir = BUNDLE, port = 0, token = randomBytes(16).toString("hex"), pollMs = 2000, pingMs = 15_000 }: GuiOptions): Promise<Gui> {
29
35
  try {
30
36
  if (!statSync(join(dir, "index.html")).isFile()) throw new Error();
31
37
  } catch {
32
38
  throw new Error(`The GUI bundle is missing (${dir}); build it with npm run gui:build`);
33
39
  }
34
- const clients = new Set<ServerResponse>();
35
- let last: string | undefined, problem: string | undefined, busy = false;
40
+ const page = readFileSync(join(dir, "index.html"), "utf8").replace("<head>", `<head>\n<base href="/${token}/">`);
41
+ const hub: Hub = { lookup: (key) => net?.lookup(key), workspace: (id) => net?.workspace(id) ?? [], readers: readers() };
42
+ const clients = new Map<ServerResponse, Watch | undefined>();
43
+ let last: string | undefined, problem: string | undefined, inflight: Promise<void> | undefined;
36
44
  let poll: ReturnType<typeof setInterval> | undefined, beat: ReturnType<typeof setInterval> | undefined;
37
- const push = (data: string, event?: string) => clients.forEach((c) => c.write(`${event ? `event: ${event}\n` : ""}data: ${data}\n\n`));
38
- const tick = async () => {
39
- if (busy) return;
40
- busy = true;
45
+ const send = (to: ServerResponse, data: string, event?: string) => to.write(`${event ? `event: ${event}\n` : ""}data: ${data}\n\n`);
46
+ const push = (data: string, event?: string) => clients.forEach((_, c) => send(c, data, event));
47
+ // One reading at a time; a caller that arrives meanwhile waits for the one in flight.
48
+ const tick = () => (inflight ??= (async () => {
41
49
  try {
42
50
  const json = JSON.stringify(await read());
43
51
  if (json !== last || problem) push((last = json));
44
52
  problem = undefined;
53
+ await Promise.all([...clients.values()].map((w) => w?.refresh().catch(() => {})));
45
54
  } catch (error) {
46
55
  // The stream stays up and says why the net is unreadable, once per distinct reason.
47
56
  const message = (error as Error).message;
48
57
  if (message !== problem) push(JSON.stringify(message), "problem");
49
58
  problem = message;
50
59
  }
51
- busy = false;
52
- };
60
+ })().finally(() => (inflight = undefined)));
53
61
  const listen = (at: number) => new Promise<void>((done, fail) => {
54
62
  server.once("error", fail);
55
63
  server.listen(at, "127.0.0.1", () => (server.off("error", fail), done()));
@@ -58,28 +66,42 @@ export async function startGui({ read, dir = BUNDLE, port = 0, token = randomByt
58
66
  const [a, b] = [Buffer.from(given ?? ""), Buffer.from(token)];
59
67
  return req.method === "GET" && req.headers.host === `127.0.0.1:${(server.address() as AddressInfo).port}` && a.length === b.length && timingSafeEqual(a, b);
60
68
  };
61
- const events = (req: IncomingMessage, res: ServerResponse) => {
69
+ const events = (req: IncomingMessage, res: ServerResponse, target: string | null) => {
62
70
  res.writeHead(200, { ...HEADERS, "content-type": "text/event-stream", connection: "keep-alive" });
63
- clients.add(res);
71
+ const to = (event: string, data: unknown) => send(res, JSON.stringify(data), event);
72
+ const watch = target?.startsWith("w:") ? watchWorkspace(hub, target.slice(2), to) : target ? watchSession(hub, target, to) : undefined;
73
+ clients.set(res, watch);
64
74
  if (last) res.write(`data: ${last}\n\n`);
65
75
  if (clients.size === 1) {
66
76
  poll = setInterval(() => void tick(), pollMs).unref();
67
77
  beat = setInterval(() => push("", "ping"), pingMs).unref();
68
- void tick();
69
78
  }
79
+ // The registry is filled by reading the net, so a watched page starts once a reading exists.
80
+ void (clients.size === 1 || !last ? tick() : watch?.refresh())?.catch(() => {});
70
81
  req.on("close", () => {
71
82
  clients.delete(res);
83
+ watch?.stop();
72
84
  if (!clients.size) (clearInterval(poll), clearInterval(beat));
73
85
  });
74
86
  };
75
87
  const server = createServer((req, res) => {
76
- const pathname = new URL(req.url ?? "/", "http://local").pathname;
88
+ const url = new URL(req.url ?? "/", "http://local"), pathname = url.pathname;
77
89
  const [, given, ...path] = pathname.split("/");
78
90
  const fail = (code: number) => res.writeHead(code, { ...HEADERS, "content-type": "text/plain" }).end(code === 403 ? "forbidden" : "not found");
79
91
  if (!allowed(req, given)) return void fail(403);
80
92
  // The page loads its assets and the stream relative to its own URL, so that URL must end with a slash.
81
93
  if (path.length === 0) return void res.writeHead(308, { ...HEADERS, location: `${pathname}/` }).end();
82
- if (path.join("/") === "events") return events(req, res);
94
+ if (path.join("/") === "events") return events(req, res, url.searchParams.get("watch"));
95
+ if (path[0] === "api") {
96
+ let parts: string[];
97
+ try {
98
+ parts = path.slice(1).map(decodeURIComponent);
99
+ } catch {
100
+ return void fail(404);
101
+ }
102
+ return void api(hub, parts, url.searchParams).then(({ status, body }) => res.writeHead(status, { ...HEADERS, "content-type": TYPES[".json"] }).end(JSON.stringify(body)), () => fail(404));
103
+ }
104
+ if (path[0] === "w") return void res.writeHead(200, { ...HEADERS, "content-type": TYPES[".html"] }).end(page);
83
105
  let file: string;
84
106
  try {
85
107
  file = resolve(dir, decodeURIComponent(path.join("/")) || "index.html");
@@ -87,6 +109,7 @@ export async function startGui({ read, dir = BUNDLE, port = 0, token = randomByt
87
109
  } catch {
88
110
  return void fail(404);
89
111
  }
112
+ if (file === join(resolve(dir), "index.html")) return void res.writeHead(200, { ...HEADERS, "content-type": TYPES[".html"] }).end(page);
90
113
  res.writeHead(200, { ...HEADERS, "content-type": TYPES[extname(file)] ?? "application/octet-stream" });
91
114
  createReadStream(file).pipe(res);
92
115
  });
@@ -102,7 +125,7 @@ export async function startGui({ read, dir = BUNDLE, port = 0, token = randomByt
102
125
  stop: () => new Promise<void>((done) => {
103
126
  clearInterval(poll);
104
127
  clearInterval(beat);
105
- clients.forEach((c) => c.end());
128
+ clients.forEach((_, c) => c.end());
106
129
  server.close(() => done());
107
130
  server.closeAllConnections();
108
131
  }),