kaizen-agent 1.32.0 → 1.33.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/web/notes-lib.ts CHANGED
@@ -8,14 +8,17 @@
8
8
  // item is its continuation, not code.
9
9
  export function uncode(text: string) {
10
10
  const blank = (s: string) => s.replace(/[^\n]/g, " ");
11
- let prev = "", code = false;
11
+ let prev = "",
12
+ code = false;
12
13
  const indented = text
13
14
  .replace(/^(```|~~~)[^\n]*\n[\s\S]*?(?:^\1[^\n]*$|(?![\s\S]))/gm, blank)
14
- .split("\n").map((l) => {
15
+ .split("\n")
16
+ .map((l) => {
15
17
  code = /^( {4}|\t)/.test(l) && l.trim() !== "" ? code || prev.trim() === "" : l.trim() === "" && code;
16
18
  prev = l;
17
19
  return code ? blank(l) : l;
18
- }).join("\n");
20
+ })
21
+ .join("\n");
19
22
  return indented.replace(/(`+)[^`\n]+?\1/g, blank);
20
23
  }
21
24
 
@@ -26,9 +29,12 @@ export function wikilinks(text: string): Wikilink[] {
26
29
  const out: Wikilink[] = [];
27
30
  for (const m of uncode(text).matchAll(WIKI)) {
28
31
  // A heading anchor (`[[Note#Part]]`) still links to the note.
29
- const target = m[1]!.trim(), at = target.indexOf("#");
30
- const name = (at < 0 ? target : target.slice(0, at)).trim(), anchor = at < 0 ? "" : target.slice(at + 1).trim();
31
- if (name) out.push({ from: m.index!, to: m.index! + m[0].length, name, label: (m[2] ?? m[1]!).trim(), target, anchor });
32
+ const target = m[1]!.trim(),
33
+ at = target.indexOf("#");
34
+ const name = (at < 0 ? target : target.slice(0, at)).trim(),
35
+ anchor = at < 0 ? "" : target.slice(at + 1).trim();
36
+ if (name)
37
+ out.push({ from: m.index!, to: m.index! + m[0].length, name, label: (m[2] ?? m[1]!).trim(), target, anchor });
32
38
  }
33
39
  return out;
34
40
  }
@@ -45,22 +51,38 @@ export function boardLink(w: Pick<Wikilink, "name" | "anchor" | "target">): Boar
45
51
  if (idea) return { kind: "idea", text: idea[1]!.trim() };
46
52
  const run = /^run:\s*([\w.-]+)$/i.exec(w.name);
47
53
  if (!run) return null;
48
- const a = w.anchor.toLowerCase(), item = /^(finding|backlog)-(\d+)$/.exec(a);
49
- if (item) return { kind: "run", id: run[1]!, tab: item[1] === "finding" ? "review" : "backlog", item: { type: item[1] as "finding" | "backlog", n: Number(item[2]) } };
50
- return { kind: "run", id: run[1]!, ...((TABS as readonly string[]).includes(a) ? { tab: a as (typeof TABS)[number] } : {}) };
54
+ const a = w.anchor.toLowerCase(),
55
+ item = /^(finding|backlog)-(\d+)$/.exec(a);
56
+ if (item)
57
+ return {
58
+ kind: "run",
59
+ id: run[1]!,
60
+ tab: item[1] === "finding" ? "review" : "backlog",
61
+ item: { type: item[1] as "finding" | "backlog", n: Number(item[2]) },
62
+ };
63
+ return {
64
+ kind: "run",
65
+ id: run[1]!,
66
+ ...((TABS as readonly string[]).includes(a) ? { tab: a as (typeof TABS)[number] } : {}),
67
+ };
51
68
  }
52
69
 
53
70
  // A run id as written: the directory name, or the name without its date, which
54
71
  // means the newest run of that name (ids sort by date).
55
72
  export function resolveRun(id: string, ids: string[]): string | null {
56
73
  if (ids.includes(id)) return id;
57
- return ids.filter((x) => /^\d{4}-\d{2}-\d{2}-/.test(x) && x.slice(11) === id).sort().pop() ?? null;
74
+ return (
75
+ ids
76
+ .filter((x) => /^\d{4}-\d{2}-\d{2}-/.test(x) && x.slice(11) === id)
77
+ .sort()
78
+ .pop() ?? null
79
+ );
58
80
  }
59
81
 
60
82
  // What a chip says after the run's name: `finding 2`, `backlog 3`, `review`.
61
83
  export function anchorWords(l: BoardLink) {
62
84
  if (l.kind !== "run") return "";
63
- return l.item ? `${l.item.type} ${l.item.n}` : l.tab ?? "";
85
+ return l.item ? `${l.item.type} ${l.item.n}` : (l.tab ?? "");
64
86
  }
65
87
 
66
88
  // `#tag` at the start of a line or after whitespace, with at least one letter; not a
@@ -119,17 +141,31 @@ export function tree(names: string[]): Tree {
119
141
  export function freshName(names: string[], folder = "", base = "Untitled") {
120
142
  const taken = new Set(names.map((n) => n.toLowerCase()));
121
143
  const at = (n: string) => (folder ? folder + "/" : "") + n;
122
- for (let i = 1; ; i++) { const n = at(i === 1 ? base : `${base} ${i}`); if (!taken.has(n.toLowerCase())) return n; }
144
+ for (let i = 1; ; i++) {
145
+ const n = at(i === 1 ? base : `${base} ${i}`);
146
+ if (!taken.has(n.toLowerCase())) return n;
147
+ }
123
148
  }
124
149
 
125
150
  // A GFM table's source as cells: the header row, each column's alignment from the
126
151
  // delimiter row, and the body rows padded to the header's width. `\|` stays a pipe.
127
152
  export type Table = { head: string[]; align: ("left" | "center" | "right" | "")[]; rows: string[][] };
128
153
  export function parseTable(src: string): Table {
129
- const cells = (l: string) => l.trim().replace(/^\|/, "").replace(/(?<!\\)\|$/, "").split(/(?<!\\)\|/).map((c) => c.trim().replace(/\\\|/g, "|"));
154
+ const cells = (l: string) =>
155
+ l
156
+ .trim()
157
+ .replace(/^\|/, "")
158
+ .replace(/(?<!\\)\|$/, "")
159
+ .split(/(?<!\\)\|/)
160
+ .map((c) => c.trim().replace(/\\\|/g, "|"));
130
161
  const lines = src.split(/\r?\n/).filter((l) => l.trim());
131
162
  const head = cells(lines[0] ?? "");
132
- const align = cells(lines[1] ?? "").map((d) => d.startsWith(":") && d.endsWith(":") ? "center" : d.endsWith(":") ? "right" : d.startsWith(":") ? "left" : "") as Table["align"];
133
- const rows = lines.slice(2).map((l) => { const r = cells(l); return head.map((_, i) => r[i] ?? ""); });
163
+ const align = cells(lines[1] ?? "").map((d) =>
164
+ d.startsWith(":") && d.endsWith(":") ? "center" : d.endsWith(":") ? "right" : d.startsWith(":") ? "left" : "",
165
+ ) as Table["align"];
166
+ const rows = lines.slice(2).map((l) => {
167
+ const r = cells(l);
168
+ return head.map((_, i) => r[i] ?? "");
169
+ });
134
170
  return { head, align, rows };
135
171
  }
package/web/notes.html CHANGED
@@ -219,9 +219,9 @@ select.sel{cursor:pointer;appearance:none;padding-right:30px;max-width:min(320px
219
219
  <div class="top"><a class="home" href="/" title="Board" aria-label="kaizen board"><pre class="mark"><span>█ █ ▄▀█ █ ▀▀█ █▀▀ █▄ █</span>
220
220
  <span>█▀▄ █▀█ █ ▄▀ ██▄ █ ▀█</span>
221
221
  <span>█ █ █ █ █ █▄▄ █▄▄ █ █</span></pre><span class="mascot"></span></a><button class="ib" id="fold" aria-label="Collapse menu" title="Collapse menu"><svg><use href="#i-side"/></svg></button></div>
222
+ <a class="it" href="/?view=icebox" data-view="sources" title="Icebox: requests gathered from your sources"><svg><use href="#i-inbox"/></svg><span class="lbl">Icebox</span></a>
222
223
  <a class="it" href="/" data-view="board" title="Board"><svg><use href="#i-board"/></svg><span class="lbl">Board</span></a>
223
224
  <a class="it" href="/notebook" data-view="notes" title="Notes"><svg><use href="#i-note"/></svg><span class="lbl">Notes</span></a>
224
- <a class="it" href="/?view=icebox" data-view="sources" title="Icebox: requests gathered from your sources"><svg><use href="#i-inbox"/></svg><span class="lbl">Icebox</span></a>
225
225
  <a class="it" href="/?view=settings" data-view="settings" title="Settings"><svg><use href="#i-gear"/></svg><span class="lbl">Settings</span></a>
226
226
  <span class="end"></span>
227
227
  <div class="theme" role="group" aria-label="Theme"><button data-t="light" title="Light theme"><svg><use href="#i-sun"/></svg><span class="lbl">Light</span></button><button data-t="dark" title="Dark theme"><svg><use href="#i-moon"/></svg><span class="lbl">Dark</span></button></div>
package/web/term.js CHANGED
@@ -1,4 +1,4 @@
1
- /* kaizen terminal fa21dc7abdde023f -- generated by bun run build:web from web/term.ts; do not edit */
1
+ /* kaizen terminal e2aaf41e5c811c66 -- generated by bun run build:web from web/term.ts; do not edit */
2
2
  (()=>{var{defineProperty:fr,getOwnPropertyDescriptor:tn}=Object,sn=(e,t)=>{for(var i in t)fr(e,i,{get:t[i],enumerable:!0})},V=(e,t,i,s)=>{for(var r=s>1?void 0:s?tn(t,i):t,n=e.length-1,o;n>=0;n--)(o=e[n])&&(r=(s?o(t,i,r):o(r))||r);return s&&r&&fr(t,i,r),r},b=(e,t)=>(i,s)=>t(i,s,e),ds="Terminal input",xi={get:()=>ds,set:(e)=>ds=e},_s="Too much output to announce, navigate to rows manually to read",Ei={get:()=>_s,set:(e)=>_s=e};function rn(e){return e.replace(/\r?\n/g,"\r")}function nn(e,t){return t?"\x1B[200~"+e+"\x1B[201~":e}function on(e,t){e.clipboardData&&e.clipboardData.setData("text/plain",t.selectionText),e.preventDefault()}function hn(e,t,i,s){if(e.stopPropagation(),e.clipboardData){let r=e.clipboardData.getData("text/plain");pr(r,t,i,s)}}function pr(e,t,i,s){e=rn(e),e=nn(e,i.decPrivateModes.bracketedPasteMode&&s.rawOptions.ignoreBracketedPasteMode!==!0),i.triggerDataEvent(e,!0),t.value=""}function gr(e,t,i){let s=i.getBoundingClientRect(),r=e.clientX-s.left-10,n=e.clientY-s.top-10;t.style.width="20px",t.style.height="20px",t.style.left=`${r}px`,t.style.top=`${n}px`,t.style.zIndex="1000",t.focus()}function us(e,t,i,s,r){gr(e,t,i),r&&s.rightClickSelect(e),t.value=s.selectionText,t.select()}function Ke(e){return e>65535?(e-=65536,String.fromCharCode((e>>10)+55296)+String.fromCharCode(e%1024+56320)):String.fromCharCode(e)}function ti(e,t=0,i=e.length){let s="";for(let r=t;r<i;++r){let n=e[r];n>65535?(n-=65536,s+=String.fromCharCode((n>>10)+55296)+String.fromCharCode(n%1024+56320)):s+=String.fromCharCode(n)}return s}var an=class{constructor(){this._interim=0}clear(){this._interim=0}decode(e,t){let i=e.length;if(!i)return 0;let s=0,r=0;if(this._interim){let n=e.charCodeAt(r++);56320<=n&&n<=57343?t[s++]=(this._interim-55296)*1024+n-56320+65536:(t[s++]=this._interim,t[s++]=n),this._interim=0}for(let n=r;n<i;++n){let o=e.charCodeAt(n);if(55296<=o&&o<=56319){if(++n>=i)return this._interim=o,s;let h=e.charCodeAt(n);56320<=h&&h<=57343?t[s++]=(o-55296)*1024+h-56320+65536:(t[s++]=o,t[s++]=h);continue}o!==65279&&(t[s++]=o)}return s}},ln=class{constructor(){this.interim=new Uint8Array(3)}clear(){this.interim.fill(0)}decode(e,t){let i=e.length;if(!i)return 0;let s=0,r,n,o,h,l=0,a=0;if(this.interim[0]){let _=!1,f=this.interim[0];f&=(f&224)===192?31:(f&240)===224?15:7;let u=0,p;for(;(p=this.interim[++u]&63)&&u<4;)f<<=6,f|=p;let C=(this.interim[0]&224)===192?2:(this.interim[0]&240)===224?3:4,D=C-u;for(;a<D;){if(a>=i)return 0;if(p=e[a++],(p&192)!==128){a--,_=!0;break}else this.interim[u++]=p,f<<=6,f|=p&63}_||(C===2?f<128?a--:t[s++]=f:C===3?f<2048||f>=55296&&f<=57343||f===65279||(t[s++]=f):f<65536||f>1114111||(t[s++]=f)),this.interim.fill(0)}let d=i-4,c=a;for(;c<i;){for(;c<d&&!((r=e[c])&128)&&!((n=e[c+1])&128)&&!((o=e[c+2])&128)&&!((h=e[c+3])&128);)t[s++]=r,t[s++]=n,t[s++]=o,t[s++]=h,c+=4;if(r=e[c++],r<128)t[s++]=r;else if((r&224)===192){if(c>=i)return this.interim[0]=r,s;if(n=e[c++],(n&192)!==128){c--;continue}if(l=(r&31)<<6|n&63,l<128){c--;continue}t[s++]=l}else if((r&240)===224){if(c>=i)return this.interim[0]=r,s;if(n=e[c++],(n&192)!==128){c--;continue}if(c>=i)return this.interim[0]=r,this.interim[1]=n,s;if(o=e[c++],(o&192)!==128){c--;continue}if(l=(r&15)<<12|(n&63)<<6|o&63,l<2048||l>=55296&&l<=57343||l===65279)continue;t[s++]=l}else if((r&248)===240){if(c>=i)return this.interim[0]=r,s;if(n=e[c++],(n&192)!==128){c--;continue}if(c>=i)return this.interim[0]=r,this.interim[1]=n,s;if(o=e[c++],(o&192)!==128){c--;continue}if(c>=i)return this.interim[0]=r,this.interim[1]=n,this.interim[2]=o,s;if(h=e[c++],(h&192)!==128){c--;continue}if(l=(r&7)<<18|(n&63)<<12|(o&63)<<6|h&63,l<65536||l>1114111)continue;t[s++]=l}}return s}},vr="",Ue=" ",Bt=class e{constructor(){this.fg=0,this.bg=0,this.extended=new Vt}static toColorRGB(t){return[t>>>16&255,t>>>8&255,t&255]}static fromColorRGB(t){return(t[0]&255)<<16|(t[1]&255)<<8|t[2]&255}clone(){let t=new e;return t.fg=this.fg,t.bg=this.bg,t.extended=this.extended.clone(),t}isInverse(){return this.fg&67108864}isBold(){return this.fg&134217728}isUnderline(){return this.hasExtendedAttrs()&&this.extended.underlineStyle!==0?1:this.fg&268435456}isBlink(){return this.fg&536870912}isInvisible(){return this.fg&1073741824}isItalic(){return this.bg&67108864}isDim(){return this.bg&134217728}isStrikethrough(){return this.fg&2147483648}isProtected(){return this.bg&536870912}isOverline(){return this.bg&1073741824}getFgColorMode(){return this.fg&50331648}getBgColorMode(){return this.bg&50331648}isFgRGB(){return(this.fg&50331648)===50331648}isBgRGB(){return(this.bg&50331648)===50331648}isFgPalette(){return(this.fg&50331648)===16777216||(this.fg&50331648)===33554432}isBgPalette(){return(this.bg&50331648)===16777216||(this.bg&50331648)===33554432}isFgDefault(){return(this.fg&50331648)===0}isBgDefault(){return(this.bg&50331648)===0}isAttributeDefault(){return this.fg===0&&this.bg===0}getFgColor(){switch(this.fg&50331648){case 16777216:case 33554432:return this.fg&255;case 50331648:return this.fg&16777215;default:return-1}}getBgColor(){switch(this.bg&50331648){case 16777216:case 33554432:return this.bg&255;case 50331648:return this.bg&16777215;default:return-1}}hasExtendedAttrs(){return this.bg&268435456}updateExtended(){this.extended.isEmpty()?this.bg&=-268435457:this.bg|=268435456}getUnderlineColor(){if(this.bg&268435456&&~this.extended.underlineColor)switch(this.extended.underlineColor&50331648){case 16777216:case 33554432:return this.extended.underlineColor&255;case 50331648:return this.extended.underlineColor&16777215;default:return this.getFgColor()}return this.getFgColor()}getUnderlineColorMode(){return this.bg&268435456&&~this.extended.underlineColor?this.extended.underlineColor&50331648:this.getFgColorMode()}isUnderlineColorRGB(){return this.bg&268435456&&~this.extended.underlineColor?(this.extended.underlineColor&50331648)===50331648:this.isFgRGB()}isUnderlineColorPalette(){return this.bg&268435456&&~this.extended.underlineColor?(this.extended.underlineColor&50331648)===16777216||(this.extended.underlineColor&50331648)===33554432:this.isFgPalette()}isUnderlineColorDefault(){return this.bg&268435456&&~this.extended.underlineColor?(this.extended.underlineColor&50331648)===0:this.isFgDefault()}getUnderlineStyle(){return this.fg&268435456?this.bg&268435456?this.extended.underlineStyle:1:0}getUnderlineVariantOffset(){return this.extended.underlineVariantOffset}},Vt=class e{constructor(t=0,i=0){this._ext=0,this._urlId=0,this._ext=t,this._urlId=i}get ext(){return this._urlId?this._ext&-469762049|this.underlineStyle<<26:this._ext}set ext(t){this._ext=t}get underlineStyle(){return this._urlId?5:(this._ext&469762048)>>26}set underlineStyle(t){this._ext&=-469762049,this._ext|=t<<26&469762048}get underlineColor(){return this._ext&67108863}set underlineColor(t){this._ext&=-67108864,this._ext|=t&67108863}get urlId(){return this._urlId}set urlId(t){this._urlId=t}get underlineVariantOffset(){let t=(this._ext&3758096384)>>29;return t<0?t^4294967288:t}set underlineVariantOffset(t){this._ext&=536870911,this._ext|=t<<29&3758096384}clone(){return new e(this._ext,this._urlId)}isEmpty(){return this.underlineStyle===0&&this._urlId===0}},Se=class e extends Bt{constructor(){super(...arguments);this.content=0,this.fg=0,this.bg=0,this.extended=new Vt,this.combinedData=""}static fromCharData(t){let i=new e;return i.setFromCharData(t),i}isCombined(){return this.content&2097152}getWidth(){return this.content>>22}getChars(){return this.content&2097152?this.combinedData:this.content&2097151?Ke(this.content&2097151):""}getCode(){return this.isCombined()?this.combinedData.charCodeAt(this.combinedData.length-1):this.content&2097151}setFromCharData(t){this.fg=t[0],this.bg=0;let i=!1;if(t[1].length>2)i=!0;else if(t[1].length===2){let s=t[1].charCodeAt(0);if(55296<=s&&s<=56319){let r=t[1].charCodeAt(1);56320<=r&&r<=57343?this.content=(s-55296)*1024+r-56320+65536|t[2]<<22:i=!0}else i=!0}else this.content=t[1].charCodeAt(0)|t[2]<<22;i&&(this.combinedData=t[1],this.content=2097152|t[2]<<22)}getAsCharData(){return[this.fg,this.getChars(),this.getWidth(),this.getCode()]}},fs="di$target",Di="di$dependencies",li=new Map;function cn(e){return e[Di]||[]}function re(e){if(li.has(e))return li.get(e);let t=function(i,s,r){if(arguments.length!==3)throw Error("@IServiceName-decorator can only be used to decorate a parameter");dn(t,i,r)};return t._id=e,li.set(e,t),t}function dn(e,t,i){t[fs]===t?t[Di].push({id:e,index:i}):(t[Di]=[{id:e,index:i}],t[fs]=t)}var ce=re("BufferService"),mr=re("CoreMouseService"),et=re("CoreService"),_n=re("CharsetService"),ts=re("InstantiationService"),Sr=re("LogService"),de=re("OptionsService"),wr=re("OscLinkService"),un=re("UnicodeService"),Rt=re("DecorationService"),Bi=class{constructor(e,t,i){this._bufferService=e,this._optionsService=t,this._oscLinkService=i}provideLinks(e,t){let i=this._bufferService.buffer.lines.get(e-1);if(!i){t(void 0);return}let s=[],r=this._optionsService.rawOptions.linkHandler,n=new Se,o=i.getTrimmedLength(),h=-1,l=-1,a=!1;for(let d=0;d<o;d++)if(!(l===-1&&!i.hasContent(d))){if(i.loadCell(d,n),n.hasExtendedAttrs()&&n.extended.urlId)if(l===-1){l=d,h=n.extended.urlId;continue}else a=n.extended.urlId!==h;else l!==-1&&(a=!0);if(a||l!==-1&&d===o-1){let c=this._oscLinkService.getLinkData(h)?.uri;if(c){let _={start:{x:l+1,y:e},end:{x:d+(!a&&d===o-1?1:0),y:e}},f=!1;if(!r?.allowNonHttpProtocols)try{let u=new URL(c);["http:","https:"].includes(u.protocol)||(f=!0)}catch{f=!0}f||s.push({text:c,range:_,activate:(u,p)=>r?r.activate(u,p,_):fn(u,p),hover:(u,p)=>r?.hover?.(u,p,_),leave:(u,p)=>r?.leave?.(u,p,_)})}a=!1,n.hasExtendedAttrs()&&n.extended.urlId?(l=d,h=n.extended.urlId):(l=-1,h=-1)}}t(s)}};Bi=V([b(0,ce),b(1,de),b(2,wr)],Bi);function fn(e,t){if(confirm(`Do you want to navigate to ${t}?
3
3
 
4
4
  WARNING: This link could potentially be dangerous`)){let i=window.open();if(i){try{i.opener=null}catch{}i.location.href=t}else console.warn("Opening link blocked as opener could not be cleared")}}var ii=re("CharSizeService"),Ae=re("CoreBrowserService"),is=re("MouseService"),Oe=re("RenderService"),pn=re("SelectionService"),br=re("CharacterJoinerService"),rt=re("ThemeService"),yr=re("LinkProviderService"),gn=class{constructor(){this.listeners=[],this.unexpectedErrorHandler=function(e){setTimeout(()=>{throw e.stack?ps.isErrorNoTelemetry(e)?new ps(e.message+`
package/web/term.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  // The in-card terminal: xterm.js on the board's /pty WebSocket. Bundled into
2
- // web/term.js by `bun run build:web`; board.html loads it the first time a card's
2
+ // web/term.js by `bun run build:web`; the board loads it (`termLoad` in web/board/agent-term.ts) the first time a card's
3
3
  // Terminal tab opens and calls window.kzTerm(el, key).
4
4
  import { Terminal } from "@xterm/xterm";
5
5
  import { FitAddon } from "@xterm/addon-fit";
@@ -12,14 +12,23 @@ function mountTerm(el: HTMLElement, key: string, onEnd?: () => void): () => void
12
12
  fontFamily: tok("--mono"),
13
13
  fontSize: 12,
14
14
  scrollback: 5000,
15
- theme: { background: tok("--bg"), foreground: tok("--ink-hi"), cursor: tok("--accent"), selectionBackground: tok("--line") },
15
+ theme: {
16
+ background: tok("--bg"),
17
+ foreground: tok("--ink-hi"),
18
+ cursor: tok("--accent"),
19
+ selectionBackground: tok("--line"),
20
+ },
16
21
  });
17
22
  const fit = new FitAddon();
18
23
  term.loadAddon(fit);
19
24
  term.open(el);
20
25
 
21
- const ws = new WebSocket(`${location.protocol === "https:" ? "wss" : "ws"}://${location.host}/pty?key=${encodeURIComponent(key)}`);
22
- const send = (m: object) => { if (ws.readyState === WebSocket.OPEN) ws.send(JSON.stringify(m)); };
26
+ const ws = new WebSocket(
27
+ `${location.protocol === "https:" ? "wss" : "ws"}://${location.host}/pty?key=${encodeURIComponent(key)}`,
28
+ );
29
+ const send = (m: object) => {
30
+ if (ws.readyState === WebSocket.OPEN) ws.send(JSON.stringify(m));
31
+ };
23
32
  const size = () => {
24
33
  if (!el.offsetWidth) return;
25
34
  fit.fit();
@@ -28,7 +37,10 @@ function mountTerm(el: HTMLElement, key: string, onEnd?: () => void): () => void
28
37
  ws.onopen = size;
29
38
  ws.onmessage = (e) => term.write(String(e.data));
30
39
  // The board closes with "ended" when the agent exits; anything else is the board gone.
31
- ws.onclose = (e) => { term.write(`\r\n\x1b[2m[${e.reason === "ended" ? "agent ended" : "connection to the board lost"}]\x1b[0m\r\n`); onEnd?.(); };
40
+ ws.onclose = (e) => {
41
+ term.write(`\r\n\x1b[2m[${e.reason === "ended" ? "agent ended" : "connection to the board lost"}]\x1b[0m\r\n`);
42
+ onEnd?.();
43
+ };
32
44
  const typed = term.onData((d) => send({ t: "i", d }));
33
45
  const ro = new ResizeObserver(size);
34
46
  ro.observe(el);