@north-light/cli 0.3.426 → 0.3.428

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.
@@ -1 +1 @@
1
- var l=Object.defineProperty;var a=(r,t)=>l(r,"name",{value:t,configurable:!0});import{EventEmitter as d}from"node:events";import{WebSocket as h}from"ws";import{classify as m}from"../fault-classifier.js";import{BrokerUnavailableError as f}from"./transport.js";function u(r,t){if("socketPath"in r)return`ws+unix://${r.socketPath}:/v1/nodes/${encodeURIComponent(t)}/attach?revive=0`;const e=new URL(r.previewEndpoint);return e.protocol=e.protocol==="http:"?"ws:":"wss:",e.pathname=`/v1/nodes/${encodeURIComponent(t)}/attach`,e.search="?wakeParked=1",e.hash="",e.toString()}a(u,"relayUrl");class k extends d{static{a(this,"RelayTransport")}nodeId;target;ws;closeEmitted=!1;constructor(t,e){super(),this.nodeId=t,this.target=e}get url(){return u(this.target,this.nodeId)}dial(){const t=new h(this.url,{..."relayToken"in this.target?{headers:{authorization:`Bearer ${this.target.relayToken}`}}:{},perMessageDeflate:!1});return this.ws=t,t}connect(){const t=this.dial();t.on("open",()=>{this.emit("connect")}),t.on("message",e=>this.onMessage(e)),t.on("error",e=>this.onError(e)),t.on("close",(e,n)=>this.onClose(e,n.toString()))}redial(t){return this.destroy(),this.closeEmitted=!1,new Promise((e,n)=>{const o=this.dial();let i=!1;o.on("open",()=>{i||(i=!0,this.emit("connect"),e())}),o.on("message",s=>this.onMessage(s)),o.on("error",s=>{if(i){this.onError(s);return}i=!0;try{o.terminate()}catch{}n(s)}),o.on("close",(s,c)=>{i&&this.onClose(s,c.toString())})})}send(t){const e=this.ws;if(!(e===void 0||e.readyState!==h.OPEN))try{e.send(JSON.stringify(t))}catch{}}destroy(){const t=this.ws;if(t!==void 0&&t.readyState<=h.OPEN)try{t.close()}catch{}}onMessage(t){let e;try{e=JSON.parse(t.toString())}catch{return}this.emit("frame",e)}onError(t){this.emitError(new Error(`relay transport error: ${t.message}`))}onClose(t,e){if(this.closeEmitted)return;this.closeEmitted=!0;const n=m("viewer\u2194broker",{code:t,reason:e});(t===1008||n.kind==="protocol"||t===1011&&(/^no running broker/.test(e)||/^no node /.test(e)))&&this.emitError(new f(this.nodeId)),this.emit("close")}emitError(t){this.listenerCount("error")>0&&this.emit("error",t)}}export{k as RelayTransport};
1
+ var l=Object.defineProperty;var a=(r,t)=>l(r,"name",{value:t,configurable:!0});import{EventEmitter as d}from"node:events";import{WebSocket as h}from"ws";import{classify as m}from"../fault-classifier.js";import{BrokerUnavailableError as f}from"./transport.js";function u(r,t){if("socketPath"in r)return`ws+unix://${r.socketPath}:/v1/nodes/${encodeURIComponent(t)}/attach?revive=0`;const e=new URL(r.previewEndpoint);return e.protocol=e.protocol==="http:"?"ws:":"wss:",e.pathname=`/v1/nodes/${encodeURIComponent(t)}/attach`,e.search="?wakeParked=1",e.hash="",e.toString()}a(u,"relayUrl");class k extends d{static{a(this,"RelayTransport")}nodeId;target;ws;closeEmitted=!1;constructor(t,e){super(),this.nodeId=t,this.target=e}get url(){return u(this.target,this.nodeId)}dial(){const t=new h(this.url,{..."relayToken"in this.target?{headers:{authorization:`Bearer ${this.target.relayToken}`}}:{},perMessageDeflate:!1});return this.ws=t,t}connect(){const t=this.dial();t.on("open",()=>{this.emit("connect")}),t.on("message",e=>this.onMessage(e)),t.on("error",e=>this.onError(e)),t.on("close",(e,n)=>this.onClose(e,n.toString()))}redial(t){return this.destroy(),this.closeEmitted=!1,new Promise((e,n)=>{const o=this.dial();let i=!1;o.on("open",()=>{i||(i=!0,this.emit("connect"),e())}),o.on("message",s=>this.onMessage(s)),o.on("error",s=>{if(i){this.onError(s);return}i=!0;try{o.terminate()}catch{}n(s)}),o.on("close",(s,c)=>{i&&this.onClose(s,c.toString())})})}send(t){const e=this.ws;if(!(e===void 0||e.readyState!==h.OPEN))try{e.send(JSON.stringify(t))}catch{}}destroy(){const t=this.ws;if(t!==void 0&&t.readyState<=h.OPEN)try{t.close(1e3)}catch{}}onMessage(t){let e;try{e=JSON.parse(t.toString())}catch{return}this.emit("frame",e)}onError(t){this.emitError(new Error(`relay transport error: ${t.message}`))}onClose(t,e){if(this.closeEmitted)return;this.closeEmitted=!0;const n=m("viewer\u2194broker",{code:t,reason:e});(t===1008||n.kind==="protocol"||t===1011&&(/^no running broker/.test(e)||/^no node /.test(e)))&&this.emitError(new f(this.nodeId)),this.emit("close")}emitError(t){this.listenerCount("error")>0&&this.emit("error",t)}}export{k as RelayTransport};
@@ -1,2 +1,2 @@
1
- var k=Object.defineProperty;var s=(l,w)=>k(l,"name",{value:w,configurable:!0});import{MessageChannel as E,Worker as S,receiveMessageOnPort as b}from"node:worker_threads";import{ConversationNotFoundError as g}from"./index.js";function N(l,w){const u=new S(new URL(import.meta.url.endsWith(".ts")?"./client-worker.ts":"./client-worker.js",import.meta.url),{workerData:{address:l,token:w}});u.unref();let c;u.on("error",e=>{c=e}),u.on("exit",e=>{c=new Error(`conversation store worker exited with ${e}`)});function m(e,r,t){if(!t)throw new Error("conversation store worker returned no reply");if(t.error)throw new Error(t.error);if(t.status===404)throw new g(r.split("/")[2]??r);if(!t.status||t.status>=400)throw new Error(`conversation store ${e} ${r} failed (${t.status}): ${t.body}`);return{body:t.body??"",...t.path?{path:t.path}:{},...t.tail?{tail:Number(t.tail)}:{}}}s(m,"settle");function n(e,r,t){if(c)throw c;const o=new SharedArrayBuffer(4),{port1:i,port2:d}=new E;try{if(u.postMessage({method:e,path:r,data:t,signal:o,reply:d},[d]),Atomics.wait(new Int32Array(o),0,0,3e4)==="timed-out")throw i.postMessage("cancel"),new Error(`conversation store request timed out: ${e} ${r}`);return m(e,r,b(i)?.message)}finally{i.close()}}s(n,"call");function p(e,r){if(c)return Promise.reject(c);const{port1:t,port2:o}=new E;return new Promise((i,d)=>{const f=setTimeout(()=>{t.postMessage("cancel"),t.close(),d(new Error(`conversation store request timed out: ${e} ${r}`))},3e4);f.unref(),t.once("message",v=>{clearTimeout(f),t.close();try{i(m(e,r,v))}catch(T){d(T)}}),u.postMessage({method:e,path:r,reply:o},[o])})}s(p,"callAsync");const a=s(e=>`/conversations/${encodeURIComponent(e)}`,"url"),$=s(e=>{const[r,...t]=e.body.trimEnd().split(`
2
- `).map(i=>JSON.parse(i)),o=t;return e.tail===void 0?{header:r,entries:o,path:e.path??""}:{header:r,entries:o.slice(0,e.tail-1),tail:o.slice(e.tail-1),path:e.path??""}},"parseRead"),h=s((e,r)=>r?`${a(e)}?head=${r.head}&tail=${r.tail}`:a(e),"readPath");return{create(e,r,t,o){return JSON.parse(n("POST","/conversations",{node_id:e,header:r,entries:t,path:o}).body).conversation_id},append(e,r){n("POST",`${a(e)}/entries`,{entries:r})},replace(e,r,t){n("PUT",a(e),{header:r,entries:t})},read(e,r){return $(n("GET",h(e,r)))},async readAsync(e,r){return $(await p("GET",h(e,r)))},header(e){return JSON.parse(n("GET",`${a(e)}/header`).body)},fork(e,r,t,o){return JSON.parse(n("POST",`${a(e)}/fork`,{node_id:r,leaf_id:t,cwd:o}).body).conversation_id},list(e){const r=e===void 0?"":`?node_id=${encodeURIComponent(e)}`;return JSON.parse(n("GET",`/conversations${r}`).body).conversations},delete(e){n("DELETE",a(e))}}}s(N,"createStoreClient");export{N as createStoreClient};
1
+ var k=Object.defineProperty;var s=(l,w)=>k(l,"name",{value:w,configurable:!0});import{MessageChannel as p,Worker as S,receiveMessageOnPort as b}from"node:worker_threads";import{ConversationNotFoundError as g}from"./index.js";function N(l,w){const u=new S(new URL(import.meta.url.endsWith(".ts")?"./client-worker.ts":"./client-worker.js",import.meta.url),{workerData:{address:l,token:w}});u.unref();let c;u.on("error",e=>{c=e}),u.on("exit",e=>{c=new Error(`conversation store worker exited with ${e}`)});function m(e,r,t){if(!t)throw new Error("conversation store worker returned no reply");if(t.error)throw new Error(t.error);if(t.status===404)throw new g(r.split("/")[2]??r);if(!t.status||t.status>=400)throw new Error(`conversation store ${e} ${r} failed (${t.status}): ${t.body}`);return{body:t.body??"",...t.path?{path:t.path}:{},...t.tail?{tail:Number(t.tail)}:{}}}s(m,"settle");function n(e,r,t){if(c)throw c;const o=new SharedArrayBuffer(4),{port1:i,port2:d}=new p;try{if(u.postMessage({method:e,path:r,data:t,signal:o,reply:d},[d]),Atomics.wait(new Int32Array(o),0,0,3e4)==="timed-out")throw i.postMessage("cancel"),new Error(`conversation store request timed out: ${e} ${r}`);return m(e,r,b(i)?.message)}finally{i.close()}}s(n,"call");function E(e,r){if(c)return Promise.reject(c);const{port1:t,port2:o}=new p;return new Promise((i,d)=>{const f=setTimeout(()=>{t.postMessage("cancel"),t.close(),d(new Error(`conversation store request timed out: ${e} ${r}`))},3e4);f.unref(),t.once("message",v=>{clearTimeout(f),t.close();try{i(m(e,r,v))}catch(T){d(T)}}),u.postMessage({method:e,path:r,reply:o},[o])})}s(E,"callAsync");const a=s(e=>`/conversations/${encodeURIComponent(e)}`,"url"),$=s(e=>{const[r,...t]=e.body.trimEnd().split(`
2
+ `).map(i=>JSON.parse(i)),o=t;return e.tail===void 0?{header:r,entries:o,path:e.path??""}:{header:r,entries:o.slice(0,e.tail-1),tail:o.slice(e.tail-1),path:e.path??""}},"parseRead"),h=s((e,r)=>r?`${a(e)}?head=${r.head}&tail=${r.tail}`:a(e),"readPath");return{create(e,r,t,o){return JSON.parse(n("POST","/conversations",{node_id:e,header:r,entries:t,path:o}).body).conversation_id},append(e,r,t){n("POST",`${a(e)}/entries`,{entries:r,...t?.dedupe?{dedupe:!0}:{}})},replace(e,r,t){n("PUT",a(e),{header:r,entries:t})},read(e,r){return $(n("GET",h(e,r)))},async readAsync(e,r){return $(await E("GET",h(e,r)))},header(e){return JSON.parse(n("GET",`${a(e)}/header`).body)},fork(e,r,t,o){return JSON.parse(n("POST",`${a(e)}/fork`,{node_id:r,leaf_id:t,cwd:o}).body).conversation_id},list(e){const r=e===void 0?"":`?node_id=${encodeURIComponent(e)}`;return JSON.parse(n("GET",`/conversations${r}`).body).conversations},delete(e){n("DELETE",a(e))}}}s(N,"createStoreClient");export{N as createStoreClient};
@@ -1,9 +1,9 @@
1
- var G=Object.defineProperty;var u=(a,s)=>G(a,"name",{value:s,configurable:!0});import{randomUUID as J}from"node:crypto";import{closeSync as v,createReadStream as K,existsSync as $,fsyncSync as k,mkdirSync as L,openSync as z,readFileSync as P,readSync as M,renameSync as Q,statSync as S,unlinkSync as O,writeFileSync as j}from"node:fs";import{createInterface as T}from"node:readline";import{dirname as V,join as F,relative as X,resolve as b}from"node:path";import{SessionManager as Y}from"@earendil-works/pi-coding-agent";import{ConversationNotFoundError as Z}from"./index.js";function x(a){const s=[];for(const p of a.split(`
2
- `))if(p.trim())try{s.push(JSON.parse(p))}catch{}return s}u(x,"parseLines");function I(a){const s=z(a,"r");try{const p=Buffer.allocUnsafe(4096);let c="";for(;;){const w=M(s,p,0,p.length,null);c+=p.toString("utf8",0,w);let m=c.indexOf(`
3
- `);for(;m!==-1||w===0;){const y=m===-1?c:c.slice(0,m),[h]=x(y);if(h!==void 0)return h;if(m===-1)return;c=c.slice(m+1),m=c.indexOf(`
4
- `)}}}finally{v(s)}}u(I,"firstLine");function ee(a,s){const p=z(a,"r");try{const c=Buffer.alloc(1);return M(p,c,0,1,s-1),c[0]}finally{v(p)}}u(ee,"lastByte");function C(a,s){return[a,...s].map(p=>`${JSON.stringify(p)}
5
- `).join("")}u(C,"lines");function W(a,s){const p=`--${b(s).replace(/^[/\\]/,"").replace(/[/\\:]/g,"-")}--`;return F(a,p)}u(W,"sessionDirectory");function A(a){return`${a.timestamp.replace(/[:.]/g,"-")}_${a.id}.jsonl`}u(A,"fileName");function ae(a,s){const{appendSection:p}=s,c=new Map,w=new Map;L(a,{recursive:!0,mode:448});const m=new Set;function y(e){if(m.has(e))return;const t=h(e);if(s.repair?.(e,t),!$(t)){c.delete(e),w.delete(e);return}m.add(e)}u(y,"repair");function h(e){const t=c.get(e)??s.pathFor?.(e);if(!t)throw new Z(e);return c.set(e,t),t}u(h,"pathFor");function E(e){y(e);const t=h(e),r=x(P(t,"utf8")),n=r.shift();if(n?.type!=="session"||n.id!==e)throw new Error(`invalid conversation: ${e}`);return{header:n,entries:r,path:t}}u(E,"read");function B(e,t){const r=I(t);return r?.type==="session"&&r.id===e?r:void 0}u(B,"validHeader");function N(e){const t=h(e),r=B(e,t);if(!r)throw new Error(`invalid conversation: ${e}`);return{header:r,path:t}}u(N,"headerOf");function H(e,t){y(e);const{header:r,path:n}=N(e),i=S(n).size;if(i<=t.head+t.tail)return E(e);const f=z(n,"r");try{const l=u((R,_)=>{if(_<=0)return[];const U=Buffer.allocUnsafe(_),q=M(f,U,0,_,R);return U.subarray(0,q).toString("utf8").split(`
6
- `)},"slice"),o=l(0,t.head),d=l(i-t.tail,t.tail);o.pop(),d.shift();const g=x(o.join(`
7
- `));return g[0]?.type==="session"&&g.shift(),{header:r,entries:g,tail:x(d.join(`
8
- `)),path:n}}finally{v(f)}}u(H,"readWindow");function D(e,t,r,n,i){if(c.has(t.id))throw new Error(`conversation already exists: ${t.id}`);const f=X(a,n);if(f.startsWith("..")||f===""||b(n)===b(a))throw new Error("conversation path outside store root");if(L(V(n),{recursive:!0,mode:448}),$(n))throw new Error(`conversation file already exists: ${n}`);let l=!1;try{const o=u(()=>{const d=z(n,"wx",384);l=!0;try{return j(d,C(t,r)),k(d),l=!1,S(n).size}finally{v(d)}},"write");return e===void 0?o():p(t.id,{kind:"create",nodeId:e,path:n,entries:r,...i?{sourceId:i}:{}},o),c.set(t.id,n),t.id}catch(o){throw l||!$(n)?($(n)&&O(n),s.abortCreate?.(t.id)):c.set(t.id,n),o}}return u(D,"createAt"),{recover(e){for(const t of e){if(!t.transcript_path)continue;const r=c.get(t.session_id);if(r&&r!==t.transcript_path)throw new Error(`conflicting conversation paths: ${t.session_id}`);c.set(t.session_id,t.transcript_path)}},repair:y,readLines(e){y(e);const t=h(e),r=T({input:K(t),crlfDelay:1/0});return{path:t,lines:r}},create(e,t,r,n){return D(e,t,r,s.createPath?.(e,t)??n??F(W(a,t.cwd),A(t)))},append(e,t){if(t.length===0)return;y(e);const r=h(e),n=S(r).size;if(n>0&&ee(r,n)!==10)throw new Error(`incomplete conversation tail: ${e}`);try{p(e,{kind:"append",entries:t},()=>{const i=z(r,"a");try{return j(i,t.map(f=>`${JSON.stringify(f)}
9
- `).join("")),k(i),S(r).size}finally{v(i)}})}catch(i){throw m.delete(e),i}},replace(e,t,r){y(e);const n=h(e);if(t.id!==e)throw new Error("replacement header id differs from conversation id");const i=P(n),f=u(o=>{const d=`${n}.${J()}.tmp`;try{j(d,o,{flag:"wx",mode:384});const g=z(d,"r");try{k(g)}finally{v(g)}Q(d,n)}finally{$(d)&&O(d)}},"swap");let l=!1;try{p(e,{kind:"replace"},()=>(f(C(t,r)),l=!0,S(n).size))}catch(o){throw l&&f(i),o}finally{w.delete(e)}},read(e,t){return t?H(e,t):E(e)},header(e){y(e);const{header:t,path:r}=N(e),n=S(r);return{header:t,path:r,mtime:n.mtimeMs,size:n.size}},fork(e,t,r,n){const i=E(e);let f=i.entries;if(r!==void 0){const d=Y.inMemory(i.header.cwd,void 0,[i.header,...f]);d.createBranchedSession(r),f=d.getEntries()}const l=new Date().toISOString(),o={...i.header,id:J(),timestamp:l,cwd:n??i.header.cwd,parentSession:i.path};return D(t,o,f,t&&s.createPath?s.createPath(t,o):F(W(a,o.cwd),A(o)),e)},list(e){const t=[],r=s.owners();for(const n of new Set([...c.keys(),...r.keys()])){const i=r.get(n);if(e!==void 0&&i!==e||(y(n),!c.has(n)))continue;const f=h(n),l=S(f);let o=w.get(n);(!o||o.mtime!==l.mtimeMs||o.size!==l.size)&&(o={mtime:l.mtimeMs,size:l.size,header:B(n,f)},w.set(n,o)),o.header&&t.push({conversation_id:n,...i?{node_id:i}:{},header:o.header,mtime:l.mtimeMs,size:l.size,path:f})}return t.sort((n,i)=>i.mtime-n.mtime)},delete(e){O(h(e)),s.delete?.(e),c.delete(e),m.delete(e),w.delete(e)}}}u(ae,"createFileStore");export{ae as createFileStore,W as sessionDirectory};
1
+ var G=Object.defineProperty;var u=(l,c)=>G(l,"name",{value:c,configurable:!0});import{randomUUID as J}from"node:crypto";import{closeSync as v,createReadStream as K,existsSync as $,fsyncSync as k,mkdirSync as L,openSync as z,readFileSync as P,readSync as M,renameSync as Q,statSync as S,unlinkSync as O,writeFileSync as j}from"node:fs";import{createInterface as T}from"node:readline";import{dirname as V,join as F,relative as X,resolve as b}from"node:path";import{SessionManager as Y}from"@earendil-works/pi-coding-agent";import{ConversationNotFoundError as Z}from"./index.js";function E(l){const c=[];for(const p of l.split(`
2
+ `))if(p.trim())try{c.push(JSON.parse(p))}catch{}return c}u(E,"parseLines");function I(l){const c=z(l,"r");try{const p=Buffer.allocUnsafe(4096);let f="";for(;;){const w=M(c,p,0,p.length,null);f+=p.toString("utf8",0,w);let m=f.indexOf(`
3
+ `);for(;m!==-1||w===0;){const y=m===-1?f:f.slice(0,m),[h]=E(y);if(h!==void 0)return h;if(m===-1)return;f=f.slice(m+1),m=f.indexOf(`
4
+ `)}}}finally{v(c)}}u(I,"firstLine");function ee(l,c){const p=z(l,"r");try{const f=Buffer.alloc(1);return M(p,f,0,1,c-1),f[0]}finally{v(p)}}u(ee,"lastByte");function C(l,c){return[l,...c].map(p=>`${JSON.stringify(p)}
5
+ `).join("")}u(C,"lines");function W(l,c){const p=`--${b(c).replace(/^[/\\]/,"").replace(/[/\\:]/g,"-")}--`;return F(l,p)}u(W,"sessionDirectory");function A(l){return`${l.timestamp.replace(/[:.]/g,"-")}_${l.id}.jsonl`}u(A,"fileName");function ae(l,c){const{appendSection:p}=c,f=new Map,w=new Map;L(l,{recursive:!0,mode:448});const m=new Set;function y(e){if(m.has(e))return;const t=h(e);if(c.repair?.(e,t),!$(t)){f.delete(e),w.delete(e);return}m.add(e)}u(y,"repair");function h(e){const t=f.get(e)??c.pathFor?.(e);if(!t)throw new Z(e);return f.set(e,t),t}u(h,"pathFor");function x(e){y(e);const t=h(e),r=E(P(t,"utf8")),n=r.shift();if(n?.type!=="session"||n.id!==e)throw new Error(`invalid conversation: ${e}`);return{header:n,entries:r,path:t}}u(x,"read");function B(e,t){const r=I(t);return r?.type==="session"&&r.id===e?r:void 0}u(B,"validHeader");function N(e){const t=h(e),r=B(e,t);if(!r)throw new Error(`invalid conversation: ${e}`);return{header:r,path:t}}u(N,"headerOf");function H(e,t){y(e);const{header:r,path:n}=N(e),a=S(n).size;if(a<=t.head+t.tail)return x(e);const i=z(n,"r");try{const o=u((R,_)=>{if(_<=0)return[];const U=Buffer.allocUnsafe(_),q=M(i,U,0,_,R);return U.subarray(0,q).toString("utf8").split(`
6
+ `)},"slice"),s=o(0,t.head),d=o(a-t.tail,t.tail);s.pop(),d.shift();const g=E(s.join(`
7
+ `));return g[0]?.type==="session"&&g.shift(),{header:r,entries:g,tail:E(d.join(`
8
+ `)),path:n}}finally{v(i)}}u(H,"readWindow");function D(e,t,r,n,a){if(f.has(t.id))throw new Error(`conversation already exists: ${t.id}`);const i=X(l,n);if(i.startsWith("..")||i===""||b(n)===b(l))throw new Error("conversation path outside store root");if(L(V(n),{recursive:!0,mode:448}),$(n))throw new Error(`conversation file already exists: ${n}`);let o=!1;try{const s=u(()=>{const d=z(n,"wx",384);o=!0;try{return j(d,C(t,r)),k(d),o=!1,S(n).size}finally{v(d)}},"write");return e===void 0?s():p(t.id,{kind:"create",nodeId:e,path:n,entries:r,...a?{sourceId:a}:{}},s),f.set(t.id,n),t.id}catch(s){throw o||!$(n)?($(n)&&O(n),c.abortCreate?.(t.id)):f.set(t.id,n),s}}return u(D,"createAt"),{recover(e){for(const t of e){if(!t.transcript_path)continue;const r=f.get(t.session_id);if(r&&r!==t.transcript_path)throw new Error(`conflicting conversation paths: ${t.session_id}`);f.set(t.session_id,t.transcript_path)}},repair:y,readLines(e){y(e);const t=h(e),r=T({input:K(t),crlfDelay:1/0});return{path:t,lines:r}},create(e,t,r,n){return D(e,t,r,c.createPath?.(e,t)??n??F(W(l,t.cwd),A(t)))},append(e,t,r){if(t.length===0)return;y(e);const n=h(e);if(r?.dedupe){const i=new Set(x(e).entries.map(o=>o.id));if(t=t.filter(o=>!i.has(o.id)),t.length===0)return}const a=S(n).size;if(a>0&&ee(n,a)!==10)throw new Error(`incomplete conversation tail: ${e}`);try{p(e,{kind:"append",entries:t},()=>{const i=z(n,"a");try{return j(i,t.map(o=>`${JSON.stringify(o)}
9
+ `).join("")),k(i),S(n).size}finally{v(i)}})}catch(i){throw m.delete(e),i}},replace(e,t,r){y(e);const n=h(e);if(t.id!==e)throw new Error("replacement header id differs from conversation id");const a=P(n),i=u(s=>{const d=`${n}.${J()}.tmp`;try{j(d,s,{flag:"wx",mode:384});const g=z(d,"r");try{k(g)}finally{v(g)}Q(d,n)}finally{$(d)&&O(d)}},"swap");let o=!1;try{p(e,{kind:"replace"},()=>(i(C(t,r)),o=!0,S(n).size))}catch(s){throw o&&i(a),s}finally{w.delete(e)}},read(e,t){return t?H(e,t):x(e)},header(e){y(e);const{header:t,path:r}=N(e),n=S(r);return{header:t,path:r,mtime:n.mtimeMs,size:n.size}},fork(e,t,r,n){const a=x(e);let i=a.entries;if(r!==void 0){const d=Y.inMemory(a.header.cwd,void 0,[a.header,...i]);d.createBranchedSession(r),i=d.getEntries()}const o=new Date().toISOString(),s={...a.header,id:J(),timestamp:o,cwd:n??a.header.cwd,parentSession:a.path};return D(t,s,i,t&&c.createPath?c.createPath(t,s):F(W(l,s.cwd),A(s)),e)},list(e){const t=[],r=c.owners();for(const n of new Set([...f.keys(),...r.keys()])){const a=r.get(n);if(e!==void 0&&a!==e||(y(n),!f.has(n)))continue;const i=h(n),o=S(i);let s=w.get(n);(!s||s.mtime!==o.mtimeMs||s.size!==o.size)&&(s={mtime:o.mtimeMs,size:o.size,header:B(n,i)},w.set(n,s)),s.header&&t.push({conversation_id:n,...a?{node_id:a}:{},header:s.header,mtime:o.mtimeMs,size:o.size,path:i})}return t.sort((n,a)=>a.mtime-n.mtime)},delete(e){O(h(e)),c.delete?.(e),f.delete(e),m.delete(e),w.delete(e)}}}u(ae,"createFileStore");export{ae as createFileStore,W as sessionDirectory};
@@ -20,7 +20,12 @@ export interface ConversationWindow {
20
20
  }
21
21
  export interface ConversationStore {
22
22
  create(nodeId: string, header: SessionHeader, entries: SessionEntry[], path?: string): string;
23
- append(id: string, entries: SessionEntry[]): void;
23
+ /** `dedupe` makes the append idempotent by entry id: an entry already in the file is skipped.
24
+ * A caller sets it when an earlier attempt failed ambiguously (the connection ended before a
25
+ * response), because the store may have written the entries anyway. */
26
+ append(id: string, entries: SessionEntry[], options?: {
27
+ dedupe?: boolean;
28
+ }): void;
24
29
  replace(id: string, header: SessionHeader, entries: SessionEntry[]): void;
25
30
  /** Given a window and a file larger than head + tail bytes, `entries` holds the entries wholly
26
31
  * inside the first `head` bytes and `tail` those wholly inside the last `tail` bytes.
@@ -58,3 +63,4 @@ export declare function conversationStore(): ConversationStore;
58
63
  export declare function readConversationAsync(id: string, window?: ConversationWindow): Promise<ReturnType<ConversationStore['read']>>;
59
64
  export { createFileStore, sessionDirectory, type AppendSection, type ConversationCanvas, type ConversationWrite } from './file-store.js';
60
65
  export { installSessionManagerStorePatch } from './patch.js';
66
+ export { findOrphanedParents, relinkOrphanedParents, repairConversationFile, type FileRepair, type OrphanRelink } from './repair.js';
@@ -1 +1 @@
1
- var c=Object.defineProperty;var r=(o,t)=>c(o,"name",{value:t,configurable:!0});import{appsRoot as a,socketDir as i}from"../layout.js";import{join as u}from"node:path";import{createStoreClient as f}from"./client.js";import{currentNodeToken as p}from"../runtime/broker/node-token.js";class d extends Error{static{r(this,"ConversationNotFoundError")}code="not_found";constructor(t){super(`conversation not found: ${t}`)}}function y(o){return o instanceof d}r(y,"isConversationNotFound");function A(o){return/(?:^|\/)[^/]+_([0-9a-f-]{36})\.jsonl$/i.exec(o)?.[1]??null}r(A,"conversationIdFromPath");function h(){return a()}r(h,"defaultConversationRoot");function l(){return process.env.CRTR_STORE_ADDRESS||u(i(),"store.sock")}r(l,"storeAddress");let n,s;function D(o){n=o}r(D,"installConversationStore");function m(){if(n)return n;const o=l();return s?.address!==o&&(s={address:o,store:f(o,p())}),s.store}r(m,"conversationStore");async function E(o,t){const e=m();return e.readAsync?e.readAsync(o,t):e.read(o,t)}r(E,"readConversationAsync");import{createFileStore as k,sessionDirectory as N}from"./file-store.js";import{installSessionManagerStorePatch as j}from"./patch.js";export{d as ConversationNotFoundError,A as conversationIdFromPath,m as conversationStore,k as createFileStore,h as defaultConversationRoot,D as installConversationStore,j as installSessionManagerStorePatch,y as isConversationNotFound,E as readConversationAsync,N as sessionDirectory,l as storeAddress};
1
+ var a=Object.defineProperty;var r=(o,e)=>a(o,"name",{value:e,configurable:!0});import{appsRoot as i,socketDir as c}from"../layout.js";import{join as u}from"node:path";import{createStoreClient as f}from"./client.js";import{currentNodeToken as p}from"../runtime/broker/node-token.js";class d extends Error{static{r(this,"ConversationNotFoundError")}code="not_found";constructor(e){super(`conversation not found: ${e}`)}}function R(o){return o instanceof d}r(R,"isConversationNotFound");function y(o){return/(?:^|\/)[^/]+_([0-9a-f-]{36})\.jsonl$/i.exec(o)?.[1]??null}r(y,"conversationIdFromPath");function A(){return i()}r(A,"defaultConversationRoot");function l(){return process.env.CRTR_STORE_ADDRESS||u(c(),"store.sock")}r(l,"storeAddress");let n,s;function F(o){n=o}r(F,"installConversationStore");function m(){if(n)return n;const o=l();return s?.address!==o&&(s={address:o,store:f(o,p())}),s.store}r(m,"conversationStore");async function k(o,e){const t=m();return t.readAsync?t.readAsync(o,e):t.read(o,e)}r(k,"readConversationAsync");import{createFileStore as P,sessionDirectory as _}from"./file-store.js";import{installSessionManagerStorePatch as O}from"./patch.js";import{findOrphanedParents as j,relinkOrphanedParents as $,repairConversationFile as g}from"./repair.js";export{d as ConversationNotFoundError,y as conversationIdFromPath,m as conversationStore,P as createFileStore,A as defaultConversationRoot,j as findOrphanedParents,F as installConversationStore,O as installSessionManagerStorePatch,R as isConversationNotFound,k as readConversationAsync,$ as relinkOrphanedParents,g as repairConversationFile,_ as sessionDirectory,l as storeAddress};
@@ -1,8 +1,8 @@
1
1
  var O=Object.defineProperty;var v=(r,t)=>O(r,"name",{value:t,configurable:!0});import{createServer as _}from"node:http";import{chmodSync as k,existsSync as N,unlinkSync as T}from"node:fs";import{isConversationNotFound as b}from"./index.js";import{resolveNodeToken as j}from"../canvas/node-tokens.js";import{conversationOwner as H}from"../canvas/sessions.js";import{peerUid as J}from"../../native/linux.js";async function x(r){const t=[];for await(const s of r)t.push(Buffer.from(s));return JSON.parse(Buffer.concat(t).toString("utf8"))}v(x,"body");function l(r,t,s){r.writeHead(t,{"content-type":"application/json"}),r.end(JSON.stringify(s))}v(l,"json");async function P(r,t,s){if(!t.readLines){const f=t.read(s);r.writeHead(200,{"content-type":"application/x-ndjson","x-conversation-path":f.path}),r.end([f.header,...f.entries].map(d=>JSON.stringify(d)).join(`
2
2
  `)+`
3
- `);return}const{path:o,lines:e}=t.readLines(s);let a=!1,c=0;for await(const f of e){if(!f.trim())continue;let d;try{d=JSON.parse(f)}catch{continue}if(!a){if(d.type!=="session"||d.id!==s)throw new Error(`invalid conversation: ${s}`);r.writeHead(200,{"content-type":"application/x-ndjson","x-conversation-path":o}),a=!0}const S=JSON.stringify(d)+`
4
- `;c+=Buffer.byteLength(S),c>=256*1024?(c=0,await new Promise((p,u)=>r.write(S,m=>m?u(m):p()))):r.write(S)}if(!a)throw new Error(`invalid conversation: ${s}`);r.end()}v(P,"fullRead");async function C(r,t){const s=_(async(o,e)=>{try{const a=o.headers.authorization,c=typeof a=="string"&&a.startsWith("Bearer ")?j(a.slice(7)):null,f=new URL(o.url??"/","http://store"),d=f.pathname.split("/").filter(Boolean);if(a===void 0&&process.platform!=="linux"&&o.method==="GET"&&d[0]==="conversations"&&(d.length===1||d.length===2||d.length===3&&d[2]==="header")){const n=d[1];if(!n)l(e,200,{conversations:t.list(f.searchParams.get("node_id")??void 0)});else if(d[2]==="header")l(e,200,t.header(n));else{const y=f.searchParams.get("head"),h=f.searchParams.get("tail");if(y===null||h===null)await P(e,t,n);else{const g=t.read(n,{head:Number(y),tail:Number(h)});e.writeHead(200,{"content-type":"application/x-ndjson","x-conversation-path":g.path,...g.tail?{"x-conversation-tail":String(1+g.entries.length)}:{}}),e.end([g.header,...g.entries,...g.tail??[]].map(E=>JSON.stringify(E)).join(`
3
+ `);return}const{path:i,lines:e}=t.readLines(s);let a=!1,c=0;for await(const f of e){if(!f.trim())continue;let d;try{d=JSON.parse(f)}catch{continue}if(!a){if(d.type!=="session"||d.id!==s)throw new Error(`invalid conversation: ${s}`);r.writeHead(200,{"content-type":"application/x-ndjson","x-conversation-path":i}),a=!0}const S=JSON.stringify(d)+`
4
+ `;c+=Buffer.byteLength(S),c>=256*1024?(c=0,await new Promise((p,u)=>r.write(S,m=>m?u(m):p()))):r.write(S)}if(!a)throw new Error(`invalid conversation: ${s}`);r.end()}v(P,"fullRead");async function C(r,t){const s=_(async(i,e)=>{try{const a=i.headers.authorization,c=typeof a=="string"&&a.startsWith("Bearer ")?j(a.slice(7)):null,f=new URL(i.url??"/","http://store"),d=f.pathname.split("/").filter(Boolean);if(a===void 0&&process.platform!=="linux"&&i.method==="GET"&&d[0]==="conversations"&&(d.length===1||d.length===2||d.length===3&&d[2]==="header")){const n=d[1];if(!n)l(e,200,{conversations:t.list(f.searchParams.get("node_id")??void 0)});else if(d[2]==="header")l(e,200,t.header(n));else{const y=f.searchParams.get("head"),h=f.searchParams.get("tail");if(y===null||h===null)await P(e,t,n);else{const g=t.read(n,{head:Number(y),tail:Number(h)});e.writeHead(200,{"content-type":"application/x-ndjson","x-conversation-path":g.path,...g.tail?{"x-conversation-tail":String(1+g.entries.length)}:{}}),e.end([g.header,...g.entries,...g.tail??[]].map(E=>JSON.stringify(E)).join(`
5
5
  `)+`
6
- `)}}return}if(!c){l(e,401,{error:"unauthorized"});return}if(process.platform==="linux"){let n;try{n=J(o.socket)}catch{l(e,401,{error:"unauthorized"});return}if(n!==c.uid){l(e,401,{error:"unauthorized"});return}}const p=v(n=>H(n)===c.node,"owns"),u=v(()=>l(e,404,{error:"not_found"}),"notFound"),m=new URL(o.url??"/","http://store"),w=m.pathname.split("/").filter(Boolean);if(w[0]!=="conversations"){l(e,404,{error:"not_found"});return}const i=w[1];if(!i&&o.method==="POST"){const n=await x(o);if(n.node_id!==c.node){u();return}l(e,201,{conversation_id:t.create(n.node_id,n.header,n.entries,n.path)})}else if(!i&&o.method==="GET"){if(m.searchParams.has("node_id")&&m.searchParams.get("node_id")!==c.node){u();return}l(e,200,{conversations:t.list(c.node)})}else if(i&&w[2]==="entries"&&o.method==="POST"){if(!p(i)){u();return}const n=await x(o);t.append(i,n.entries),e.writeHead(204).end()}else if(i&&w[2]==="header"&&o.method==="GET"){if(!p(i)){u();return}l(e,200,t.header(i))}else if(i&&w[2]==="fork"&&o.method==="POST"){if(!p(i)){u();return}const n=await x(o);if(n.node_id!==c.node){u();return}l(e,201,{conversation_id:t.fork(i,c.node,n.leaf_id,n.cwd)})}else if(i&&w.length===2&&o.method==="GET"){if(!p(i)){u();return}const n=m.searchParams.get("head"),y=m.searchParams.get("tail");if(n===null||y===null)await P(e,t,i);else{const h=t.read(i,{head:Number(n),tail:Number(y)});e.writeHead(200,{"content-type":"application/x-ndjson","x-conversation-path":h.path,...h.tail?{"x-conversation-tail":String(1+h.entries.length)}:{}}),e.end([h.header,...h.entries,...h.tail??[]].map(g=>JSON.stringify(g)).join(`
6
+ `)}}return}if(!c){l(e,401,{error:"unauthorized"});return}if(process.platform==="linux"){let n;try{n=J(i.socket)}catch{l(e,401,{error:"unauthorized"});return}if(n!==c.uid){l(e,401,{error:"unauthorized"});return}}const p=v(n=>H(n)===c.node,"owns"),u=v(()=>l(e,404,{error:"not_found"}),"notFound"),m=new URL(i.url??"/","http://store"),w=m.pathname.split("/").filter(Boolean);if(w[0]!=="conversations"){l(e,404,{error:"not_found"});return}const o=w[1];if(!o&&i.method==="POST"){const n=await x(i);if(n.node_id!==c.node){u();return}l(e,201,{conversation_id:t.create(n.node_id,n.header,n.entries,n.path)})}else if(!o&&i.method==="GET"){if(m.searchParams.has("node_id")&&m.searchParams.get("node_id")!==c.node){u();return}l(e,200,{conversations:t.list(c.node)})}else if(o&&w[2]==="entries"&&i.method==="POST"){if(!p(o)){u();return}const n=await x(i);t.append(o,n.entries,n.dedupe===!0?{dedupe:!0}:void 0),e.writeHead(204).end()}else if(o&&w[2]==="header"&&i.method==="GET"){if(!p(o)){u();return}l(e,200,t.header(o))}else if(o&&w[2]==="fork"&&i.method==="POST"){if(!p(o)){u();return}const n=await x(i);if(n.node_id!==c.node){u();return}l(e,201,{conversation_id:t.fork(o,c.node,n.leaf_id,n.cwd)})}else if(o&&w.length===2&&i.method==="GET"){if(!p(o)){u();return}const n=m.searchParams.get("head"),y=m.searchParams.get("tail");if(n===null||y===null)await P(e,t,o);else{const h=t.read(o,{head:Number(n),tail:Number(y)});e.writeHead(200,{"content-type":"application/x-ndjson","x-conversation-path":h.path,...h.tail?{"x-conversation-tail":String(1+h.entries.length)}:{}}),e.end([h.header,...h.entries,...h.tail??[]].map(g=>JSON.stringify(g)).join(`
7
7
  `)+`
8
- `)}}else if(i&&w.length===2&&o.method==="PUT"){if(!p(i)){u();return}const n=await x(o);t.replace(i,n.header,n.entries),e.writeHead(204).end()}else if(i&&w.length===2&&o.method==="DELETE"){if(!p(i)){u();return}t.delete(i),e.writeHead(204).end()}else l(e,404,{error:"not_found"})}catch(a){const c=a instanceof Error?a.message:String(a);e.headersSent?e.destroy(a instanceof Error?a:new Error(c)):l(e,b(a)?404:500,{error:c})}});if(N(r)&&T(r),await new Promise((o,e)=>{s.once("error",e),s.listen(r,()=>{s.off("error",e),o()})}),typeof s.address()!="string")throw s.close(),new Error(`store listener must bind a unix socket: ${JSON.stringify(s.address())}`);return k(r,process.platform==="linux"&&N("/run/crtr/launcher.sock")?438:384),{close:v(()=>new Promise((o,e)=>s.close(a=>a?e(a):o())),"close")}}v(C,"listenConversationStore");export{C as listenConversationStore};
8
+ `)}}else if(o&&w.length===2&&i.method==="PUT"){if(!p(o)){u();return}const n=await x(i);t.replace(o,n.header,n.entries),e.writeHead(204).end()}else if(o&&w.length===2&&i.method==="DELETE"){if(!p(o)){u();return}t.delete(o),e.writeHead(204).end()}else l(e,404,{error:"not_found"})}catch(a){const c=a instanceof Error?a.message:String(a);e.headersSent?e.destroy(a instanceof Error?a:new Error(c)):l(e,b(a)?404:500,{error:c})}});if(N(r)&&T(r),await new Promise((i,e)=>{s.once("error",e),s.listen(r,()=>{s.off("error",e),i()})}),typeof s.address()!="string")throw s.close(),new Error(`store listener must bind a unix socket: ${JSON.stringify(s.address())}`);return k(r,process.platform==="linux"&&N("/run/crtr/launcher.sock")?438:384),{close:v(()=>new Promise((i,e)=>s.close(a=>a?e(a):i())),"close")}}v(C,"listenConversationStore");export{C as listenConversationStore};
@@ -1 +1 @@
1
- var b=Object.defineProperty;var l=(u,a)=>b(u,"name",{value:a,configurable:!0});import{dirname as E,resolve as v}from"node:path";import{SessionManager as m}from"@earendil-works/pi-coding-agent";import{conversationIdFromPath as x,conversationStore as B}from"./index.js";let N=!1;function j(u){if(N)return;const a=m.prototype,h=m;for(const e of["_setSessionFile","_rewriteFile","_persist","createBranchedSession","newSession"])if(typeof a[e]!="function")throw new Error(`pi SessionManager.${e} changed; store patch cannot start`);for(const e of["create","open","continueRecent","forkFrom","findById","list","listAll"])if(typeof h[e]!="function")throw new Error(`pi SessionManager.${e} changed; store patch cannot start`);const D=a.createBranchedSession,w=a.newSession,r=B();function y(e,s){const{header:t,entries:i,path:o}=r.read(e),n=m.inMemory(s??t.cwd);n.sessionFile=o,n.sessionDir=E(o),n._loadEntries([t,...i]),n.persist=!0,n.flushed=!0;const d=n.fileEntries[0];return d?.type==="session"&&d.version!==t.version&&n._rewriteFile(),n}l(y,"fromStore");function p(e){const[s,...t]=e.fileEntries;if(!s||s.type!=="session")throw new Error("conversation has no session header");r.create(u,s,t),e.sessionFile=r.header(s.id).path,e.sessionDir=E(e.sessionFile),e.flushed=!0}l(p,"persistNew");function g(e){const s=x(e);if(!s)throw new Error(`invalid conversation path: ${e}`);return s}l(g,"idOf"),a._setSessionFile=function(e){const{header:s,entries:t}=r.read(g(e));this.sessionFile=r.header(s.id).path,this.sessionDir=E(this.sessionFile),this._loadEntries([s,...t]),this.flushed=!0},a._rewriteFile=function(){if(!this.persist||!this.sessionFile)return;const[e,...s]=this.fileEntries;if(!e||e.type!=="session")throw new Error("conversation has no session header");r.replace(e.id,e,s)},a._persist=function(e){!this.persist||!this.sessionFile||(this.flushed?r.append(this.sessionId,[e]):p(this))},a.newSession=function(e){if(!this.persist)return w.call(this,e);const s=this.flushed&&this.fileEntries.length===1&&this.sessionFile!==void 0?this.sessionId:void 0;if(s!==void 0&&(e?.id===void 0||e.id===s)){const t=this.sessionFile;w.call(this,{...e,id:s});const i=this.fileEntries[0];if(!i||i.type!=="session")throw new Error("conversation has no session header");return r.replace(s,i,[]),this.sessionFile=t,this.flushed=!0,t}return w.call(this,e),p(this),this.sessionFile},a.createBranchedSession=function(e){if(!this.persist)return D.call(this,e);const s=this.sessionFile;this.persist=!1;try{D.call(this,e)}finally{this.persist=!0}const t=this.fileEntries[0];if(!t||t.type!=="session")throw new Error("branch has no header");return this.fileEntries[0]={...t,parentSession:s},p(this),this.sessionFile},h.create=(e,s,t)=>{const i=m.inMemory(e,t);return i.persist=!0,p(i),i},h.open=(e,s,t)=>y(g(e),t),h.continueRecent=e=>{const s=r.list().find(t=>t.header.cwd===v(e));return s?y(s.conversation_id,e):h.create(e)},h.forkFrom=(e,s)=>y(r.fork(g(e),u,void 0,s)),h.findById=(e,s)=>r.list().find(t=>t.conversation_id===s&&t.header.cwd===v(e))?.path;function M(e){return r.list().filter(s=>e===void 0||s.header.cwd===v(e)).map(s=>{let t,i=0,o="",n;const d=[];for(const f of r.read(s.conversation_id).entries){if(f.type==="session_info"&&(t=f.name?.trim()||void 0),f.type!=="message")continue;i++;const c=f.message;if(c.role!=="user"&&c.role!=="assistant")continue;const A=typeof c.timestamp=="number"?c.timestamp:Date.parse(f.timestamp);Number.isNaN(A)||(n=Math.max(n??0,A));const S=typeof c.content=="string"?c.content:Array.isArray(c.content)?c.content.filter(_=>_.type==="text").map(_=>_.text??"").join(" "):"";S&&(d.push(S),!o&&c.role==="user"&&(o=S))}const F=new Date(s.header.timestamp);return{path:s.path,id:s.conversation_id,cwd:s.header.cwd,...t?{name:t}:{},...s.header.parentSession?{parentSessionPath:s.header.parentSession}:{},created:F,modified:n&&n>0?new Date(n):Number.isNaN(F.getTime())?new Date(s.mtime):F,messageCount:i,firstMessage:o||"(no messages)",allMessagesText:d.join(" ")}})}l(M,"sessionInfos"),h.list=async(e,s,t,i)=>{i?.throwIfAborted();const o=M(e);return t?.(o.length,o.length,o),o},h.listAll=async(...e)=>(e.find(t=>t instanceof AbortSignal)?.throwIfAborted(),M()),N=!0}l(j,"installSessionManagerStorePatch");export{j as installSessionManagerStorePatch};
1
+ var P=Object.defineProperty;var h=(a,c)=>P(a,"name",{value:c,configurable:!0});import{dirname as E,resolve as A}from"node:path";import{SessionManager as w}from"@earendil-works/pi-coding-agent";import{conversationIdFromPath as R,conversationStore as O}from"./index.js";import{findOrphanedParents as T}from"./repair.js";import{emitEvent as I}from"../events/emit.js";const f=new WeakMap;function k(a){a>0&&Atomics.wait(new Int32Array(new SharedArrayBuffer(4)),0,0,a)}h(k,"sleep");function C(){const a=Number(process.env.CRTR_CONVERSATION_APPEND_RETRY_MS);return Number.isFinite(a)&&a>=0?a:500}h(C,"retryDelayMs");let x=!1;function z(a){if(x)return;const c=w.prototype,d=w;for(const e of["_setSessionFile","_rewriteFile","_persist","createBranchedSession","newSession"])if(typeof c[e]!="function")throw new Error(`pi SessionManager.${e} changed; store patch cannot start`);for(const e of["create","open","continueRecent","forkFrom","findById","list","listAll"])if(typeof d[e]!="function")throw new Error(`pi SessionManager.${e} changed; store patch cannot start`);const M=c.createBranchedSession,y=c.newSession,o=O();function N(e,s){const t=T(s);t.length!==0&&I({level:"error",event:"conversation.orphaned_parent",fields:{conversation_id:e,orphan_count:t.length,orphans:t.slice(0,20).map(i=>({entry_id:i.entryId,missing_parent_id:i.missingParentId}))}})}h(N,"reportOrphans");function _(e,s){const{header:t,entries:i,path:r}=o.read(e);N(t.id,i);const n=w.inMemory(s??t.cwd);n.sessionFile=r,n.sessionDir=E(r),n._loadEntries([t,...i]),n.persist=!0,n.flushed=!0;const u=n.fileEntries[0];return u?.type==="session"&&u.version!==t.version&&n._rewriteFile(),n}h(_,"fromStore");function m(e){const[s,...t]=e.fileEntries;if(!s||s.type!=="session")throw new Error("conversation has no session header");o.create(a,s,t),e.sessionFile=o.header(s.id).path,e.sessionDir=E(e.sessionFile),e.flushed=!0,f.delete(e)}h(m,"persistNew");function B(e,s){const t=f.get(e)??{entries:[],ambiguous:!1};t.entries.push(s),f.set(e,t);let i;for(let r=0;r<2;r++){r>0&&k(C());try{o.append(e.sessionId,t.entries,t.ambiguous?{dedupe:!0}:void 0),f.delete(e);return}catch(n){i=n,t.ambiguous=!0}}throw I({level:"error",event:"conversation.append_failed",error:i,fields:{conversation_id:e.sessionId,pending_entries:t.entries.length}}),i}h(B,"appendInOrder");function g(e){const s=R(e);if(!s)throw new Error(`invalid conversation path: ${e}`);return s}h(g,"idOf"),c._setSessionFile=function(e){const{header:s,entries:t}=o.read(g(e));this.sessionFile=o.header(s.id).path,this.sessionDir=E(this.sessionFile),N(s.id,t),this._loadEntries([s,...t]),this.flushed=!0,f.delete(this)},c._rewriteFile=function(){if(!this.persist||!this.sessionFile)return;const[e,...s]=this.fileEntries;if(!e||e.type!=="session")throw new Error("conversation has no session header");o.replace(e.id,e,s),f.delete(this)},c._persist=function(e){!this.persist||!this.sessionFile||(this.flushed?B(this,e):m(this))},c.newSession=function(e){if(!this.persist)return y.call(this,e);const s=this.flushed&&this.fileEntries.length===1&&this.sessionFile!==void 0?this.sessionId:void 0;if(s!==void 0&&(e?.id===void 0||e.id===s)){const t=this.sessionFile;y.call(this,{...e,id:s});const i=this.fileEntries[0];if(!i||i.type!=="session")throw new Error("conversation has no session header");return o.replace(s,i,[]),this.sessionFile=t,this.flushed=!0,t}return y.call(this,e),m(this),this.sessionFile},c.createBranchedSession=function(e){if(!this.persist)return M.call(this,e);const s=this.sessionFile;this.persist=!1;try{M.call(this,e)}finally{this.persist=!0}const t=this.fileEntries[0];if(!t||t.type!=="session")throw new Error("branch has no header");return this.fileEntries[0]={...t,parentSession:s},m(this),this.sessionFile},d.create=(e,s,t)=>{const i=w.inMemory(e,t);return i.persist=!0,m(i),i},d.open=(e,s,t)=>_(g(e),t),d.continueRecent=e=>{const s=o.list().find(t=>t.header.cwd===A(e));return s?_(s.conversation_id,e):d.create(e)},d.forkFrom=(e,s)=>_(o.fork(g(e),a,void 0,s)),d.findById=(e,s)=>o.list().find(t=>t.conversation_id===s&&t.header.cwd===A(e))?.path;function b(e){return o.list().filter(s=>e===void 0||s.header.cwd===A(e)).map(s=>{let t,i=0,r="",n;const u=[];for(const p of o.read(s.conversation_id).entries){if(p.type==="session_info"&&(t=p.name?.trim()||void 0),p.type!=="message")continue;i++;const l=p.message;if(l.role!=="user"&&l.role!=="assistant")continue;const D=typeof l.timestamp=="number"?l.timestamp:Date.parse(p.timestamp);Number.isNaN(D)||(n=Math.max(n??0,D));const S=typeof l.content=="string"?l.content:Array.isArray(l.content)?l.content.filter(F=>F.type==="text").map(F=>F.text??"").join(" "):"";S&&(u.push(S),!r&&l.role==="user"&&(r=S))}const v=new Date(s.header.timestamp);return{path:s.path,id:s.conversation_id,cwd:s.header.cwd,...t?{name:t}:{},...s.header.parentSession?{parentSessionPath:s.header.parentSession}:{},created:v,modified:n&&n>0?new Date(n):Number.isNaN(v.getTime())?new Date(s.mtime):v,messageCount:i,firstMessage:r||"(no messages)",allMessagesText:u.join(" ")}})}h(b,"sessionInfos"),d.list=async(e,s,t,i)=>{i?.throwIfAborted();const r=b(e);return t?.(r.length,r.length,r),r},d.listAll=async(...e)=>(e.find(t=>t instanceof AbortSignal)?.throwIfAborted(),b()),x=!0}h(z,"installSessionManagerStorePatch");export{z as installSessionManagerStorePatch};
@@ -0,0 +1,45 @@
1
+ /** Pure helpers for conversations whose entries name a `parentId` that is not in the file.
2
+ * That shape comes from an append that failed after pi had already advanced its in-memory leaf:
3
+ * later entries were written chained onto the entry that never reached the file, and every
4
+ * reload then stops walking the parent chain at the gap. */
5
+ import type { SessionEntry, SessionHeader } from '@earendil-works/pi-coding-agent';
6
+ export interface OrphanedParent {
7
+ /** Index into the entries array (header excluded) of the entry whose parent is missing. */
8
+ index: number;
9
+ entryId: string;
10
+ missingParentId: string;
11
+ }
12
+ /** Entries whose non-null `parentId` is absent from `entries`. */
13
+ export declare function findOrphanedParents(entries: readonly SessionEntry[]): OrphanedParent[];
14
+ export interface OrphanRelink extends OrphanedParent {
15
+ /** The entry it was relinked to: the one before it in file order, or null at the head. */
16
+ relinkedTo: string | null;
17
+ /** What the lost entry's absence leaves behind for the provider transcript. */
18
+ note?: string;
19
+ }
20
+ /** Relink each orphan onto the entry before it in file order — the leaf pi held when the lost
21
+ * entry was appended. A lost toolResult leaves a tool call without a result, which pi-ai's
22
+ * message transform answers with a synthetic "No result provided" result; a lost toolCall
23
+ * would leave a toolResult with no call, which the transform does not fix, so that case is
24
+ * reported in `note` for the caller to resolve. Returns new entry objects; the input is untouched. */
25
+ export declare function relinkOrphanedParents(entries: readonly SessionEntry[]): {
26
+ entries: SessionEntry[];
27
+ relinks: OrphanRelink[];
28
+ };
29
+ /** Serialize a conversation file: header first, one JSON line per entry. */
30
+ export declare function conversationLines(header: SessionHeader, entries: readonly SessionEntry[]): string;
31
+ export interface FileRepair {
32
+ path: string;
33
+ entries: number;
34
+ /** Branch length walked from the last entry, before and after the relink. */
35
+ branchBefore: number;
36
+ branchAfter: number;
37
+ relinks: OrphanRelink[];
38
+ backup?: string;
39
+ }
40
+ /** Relink one conversation file's orphaned entries in place, after copying it to `<path>.bak-orphans`.
41
+ * The caller must have stopped the owning node's broker: a running broker holds the conversation in
42
+ * memory and appends to the file. `write: false` reports what would change and touches nothing. */
43
+ export declare function repairConversationFile(path: string, options: {
44
+ write: boolean;
45
+ }): FileRepair;
@@ -0,0 +1,3 @@
1
+ var f=Object.defineProperty;var i=(e,r)=>f(e,"name",{value:r,configurable:!0});import{copyFileSync as m,existsSync as h,readFileSync as g,renameSync as I,writeFileSync as w}from"node:fs";function y(e){const r=new Set(e.map(t=>t.id)),n=[];return e.forEach((t,o)=>{t.parentId&&!r.has(t.parentId)&&n.push({index:o,entryId:t.id,missingParentId:t.parentId})}),n}i(y,"findOrphanedParents");function k(e){if(e?.type!=="message")return[];const r=e.message;return r.role!=="assistant"||!Array.isArray(r.content)?[]:r.content.filter(n=>n.type==="toolCall"&&n.id).map(n=>n.id)}i(k,"toolCallIds");function x(e){const r=new Map(y(e).map(o=>[o.index,o])),n=[];return{entries:e.map((o,l)=>{const p=r.get(l);if(!p)return o;const a=e[l-1],s={...p,relinkedTo:a?.id??null};if(o.type==="message"&&o.message.role==="toolResult"){const c=o.message.toolCallId;(!c||!k(a).includes(c))&&(s.note=`toolResult ${c??"?"} has no tool call on the entry before it`)}return n.push(s),{...o,parentId:a?.id??null}}),relinks:n}}i(x,"relinkOrphanedParents");function S(e,r){return[e,...r].map(n=>`${JSON.stringify(n)}
2
+ `).join("")}i(S,"conversationLines");function u(e){const r=new Map(e.map(t=>[t.id,t]));let n=0;for(let t=e.at(-1);t;t=t.parentId?r.get(t.parentId):void 0)n++;return n}i(u,"branchLength");function C(e,r){const n=[];for(const d of g(e,"utf8").split(`
3
+ `))if(d.trim())try{n.push(JSON.parse(d))}catch{}const t=n.shift();if(t?.type!=="session")throw new Error(`no session header: ${e}`);const o=n,{entries:l,relinks:p}=x(o),a={path:e,entries:o.length,branchBefore:u(o),branchAfter:u(l),relinks:p};if(p.length===0||!r.write)return a;const s=`${e}.bak-orphans`;if(h(s))throw new Error(`backup already exists: ${s}`);m(e,s);const c=`${e}.${process.pid}.repair.tmp`;return w(c,S(t,l),{mode:384}),I(c,e),{...a,backup:s}}i(C,"repairConversationFile");export{S as conversationLines,y as findOrphanedParents,x as relinkOrphanedParents,C as repairConversationFile};
@@ -41,6 +41,19 @@ export declare function startRemoteAttachHeartbeat(ws: WebSocket, intervalMs?: n
41
41
  * `ErrorBody`, spec §8) to the raw socket and destroy it. Never throws — a
42
42
  * reset peer is swallowed. Shared with server.ts's auth and startup gates. */
43
43
  export declare function refuseUpgrade(socket: Duplex, status: number, statusText: string, code: string, message: string): void;
44
+ /** Relay one remote WS ⇄ one node broker `view.sock`. Opens the socket on
45
+ * connect; forwards each complete frame socket→WS (bounded decode, one WS
46
+ * message per frame) and re-frames each WS message WS→socket (trailing
47
+ * newline). The relay is semantically verbatim — it never INTERPRETS a frame
48
+ * (it only splits/joins the newline framing). Closes the WS on socket
49
+ * gone/no-broker, destroys the socket on WS close; idempotent teardown, no
50
+ * leaks. Validates the node id first (path-safety + existence) as belt-and-
51
+ * suspenders — the upgrade handler already guarded both. */
52
+ /** A viewer that closed its WebSocket on purpose: 1000 normal, 1001 going
53
+ * away, or 1005 — a close frame carrying no status, which is what a bare
54
+ * `ws.close()` sends. None is a link fault; only an abnormal drop (1006) or an
55
+ * error code is. */
56
+ export declare function isCleanViewerClose(code: number): boolean;
44
57
  /** Handle the HTTP upgrade for `GET /v1/nodes/{id}/attach`. A-9 matches the
45
58
  * route, extracts `{id}`, and calls this with the raw upgrade triplet; this
46
59
  * owns the WS handshake (its own noServer `wss` above) AND the spec §5.3
@@ -1,8 +1,8 @@
1
- var E=Object.defineProperty;var s=(t,e)=>E(t,"name",{value:e,configurable:!0});import{WebSocketServer as $}from"ws";import{isSafeNodeId as A}from"../../core/canvas/paths.js";import{getNode as S}from"../../core/canvas/index.js";import{BROKER_READ_CAPS as R,CLIENT_READ_CAPS as T,FrameDecoder as B,FrameOverflowError as x}from"../../core/runtime/broker-protocol.js";import{classify as k}from"../../core/fault-classifier.js";import{clearFault as p,recordFault as g}from"../../core/canvas/recovery.js";import{decideAttachRevive as C}from"../../core/runtime/revive.js";import{connectViewSocket as N,probeViewSocket as O,waitForBrokerViewSocket as P}from"../../core/runtime/view-socket.js";import{isBeneathRefusal as F}from"../../core/spaces/open-beneath.js";import{attachViewer as _,detachViewer as U}from"./viewer-presence.js";const D=new $({noServer:!0,maxPayload:R.maxLineBytes}),L=25e3,H=6e4;function V(t,e=L,i=H){let o=!0,n=!1,a=Date.now();const f=s(()=>{o=!0},"acknowledge"),m=s(()=>{n||(n=!0,clearInterval(h),t.off("pong",f))},"stop");t.on("pong",f);const h=setInterval(()=>{if(Date.now()-a>i){o=!0;return}if(!o){m(),t.terminate();return}o=!1;try{t.ping()}catch{m(),t.terminate()}},e);return h.unref(),{stop:m,noteActivity:s(()=>{a=Date.now()},"noteActivity")}}s(V,"startRemoteAttachHeartbeat");function b(t){const e=Buffer.from(t,"utf8");return e.byteLength<=123?t:e.subarray(0,123).toString("utf8")}s(b,"clampReason");function u(t,e,i,o,n){const a=JSON.stringify({error:{code:o,message:n}}),f=`HTTP/1.1 ${e} ${i}\r
1
+ var $=Object.defineProperty;var s=(r,e)=>$(r,"name",{value:e,configurable:!0});import{WebSocketServer as R}from"ws";import{isSafeNodeId as A}from"../../core/canvas/paths.js";import{getNode as S}from"../../core/canvas/index.js";import{BROKER_READ_CAPS as C,CLIENT_READ_CAPS as T,FrameDecoder as x,FrameOverflowError as B}from"../../core/runtime/broker-protocol.js";import{classify as k}from"../../core/fault-classifier.js";import{clearFault as d,recordFault as g}from"../../core/canvas/recovery.js";import{decideAttachRevive as N}from"../../core/runtime/revive.js";import{connectViewSocket as O,probeViewSocket as P,waitForBrokerViewSocket as F}from"../../core/runtime/view-socket.js";import{isBeneathRefusal as U}from"../../core/spaces/open-beneath.js";import{attachViewer as _,detachViewer as D}from"./viewer-presence.js";const L=new R({noServer:!0,maxPayload:C.maxLineBytes}),V=25e3,H=6e4;function M(r,e=V,i=H){let n=!0,o=!1,a=Date.now();const f=s(()=>{n=!0},"acknowledge"),m=s(()=>{o||(o=!0,clearInterval(h),r.off("pong",f))},"stop");r.on("pong",f);const h=setInterval(()=>{if(Date.now()-a>i){n=!0;return}if(!n){m(),r.terminate();return}n=!1;try{r.ping()}catch{m(),r.terminate()}},e);return h.unref(),{stop:m,noteActivity:s(()=>{a=Date.now()},"noteActivity")}}s(M,"startRemoteAttachHeartbeat");function b(r){const e=Buffer.from(r,"utf8");return e.byteLength<=123?r:e.subarray(0,123).toString("utf8")}s(b,"clampReason");function u(r,e,i,n,o){const a=JSON.stringify({error:{code:n,message:o}}),f=`HTTP/1.1 ${e} ${i}\r
2
2
  content-type: application/json; charset=utf-8\r
3
3
  content-length: ${Buffer.byteLength(a)}\r
4
4
  connection: close\r
5
5
  \r
6
- `+a;try{t.end(f)}catch{try{t.destroy()}catch{}}}s(u,"refuseUpgrade");function M(t){if(new URL(t.url??"/","http://127.0.0.1").searchParams.get("revive")==="0")return!0;const i=t.headers["x-crtr-attach-revive"];return(Array.isArray(i)?i[0]:i)==="0"}s(M,"reviveOptedOut");function J(t){return new URL(t.url??"/","http://127.0.0.1").searchParams.get("wakeParked")==="1"}s(J,"wakeParkedRequested");function W(t,e){if(!A(e)){try{t.close(1008,b(`invalid node id: ${e}`))}catch{}return}if(S(e)===null){try{t.close(1011,b(`no node ${e}`))}catch{}return}p(e,{link:"viewer\u2194crtrd"});const i=N(e),o=new B(T);let n=!1,a=!1,f,m=!1,h=!1,v=!1;i.on("connect",()=>{m=!0,!n&&v&&_(e,t),p(e,{link:"relay\u2194broker"})});const y=s((r,l)=>{if(!n){if(n=!0,U(e,t),f?.stop(),!i.destroyed)try{i.destroy()}catch{}try{t.close(r,b(l))}catch{}}},"teardown");f=V(t),i.on("data",r=>{f?.noteActivity();let l;try{l=o.push(r)}catch(c){const d=c instanceof x?`broker sent an oversized frame (${c.message})`:`failed to decode a broker frame for ${e}`;y(1009,d);return}for(const c of l)t.readyState===t.OPEN&&t.send(JSON.stringify(c))}),i.on("error",r=>{const l=k("relay\u2194broker",r);g(e,{link:"relay\u2194broker",op:"bridge socket",kind:l.kind,retry:{disposition:"manual"},message:r.message});const c=r.code==="ECONNREFUSED"||r.code==="ENOENT"?`no running broker for ${e}`:`view socket error for ${e}: ${r.message}`;y(1011,c)}),i.on("close",r=>{n||r||(p(e,{link:"relay\u2194broker"}),y(1e3,"broker socket closed"))}),t.on("close",(r,l)=>{const c=l.toString("utf8");if(n){r===1e3&&p(e,{link:"viewer\u2194crtrd"});return}if(r===1e3)p(e,{link:"viewer\u2194crtrd"});else if(!a){a=!0;const d=k("viewer\u2194crtrd",{code:r,reason:c});g(e,{link:"viewer\u2194crtrd",op:"remote attach websocket",kind:d.kind,retry:{disposition:"auto"},message:c||`remote attach closed (${r})`})}y(r,c||`remote attach closed (${r})`)}),t.on("error",()=>{if(!a){a=!0;const r=k("viewer\u2194crtrd",{code:1006,reason:"remote attach websocket error"});g(e,{link:"viewer\u2194crtrd",op:"remote attach websocket",kind:r.kind,retry:{disposition:"auto"},message:"remote attach websocket error"})}y(1011,"remote attach websocket error")}),t.on("message",r=>{if(n||i.destroyed)return;f?.noteActivity();const c=(Array.isArray(r)?Buffer.concat(r):Buffer.isBuffer(r)?r:Buffer.from(new Uint8Array(r))).toString("utf8");if(!h)try{const w=JSON.parse(c);w?.type==="hello"&&(h=!0,v=w.attends!==!1,v&&m&&_(e,t))}catch{}const d=c.endsWith(`
6
+ `+a;try{r.end(f)}catch{try{r.destroy()}catch{}}}s(u,"refuseUpgrade");function J(r){if(new URL(r.url??"/","http://127.0.0.1").searchParams.get("revive")==="0")return!0;const i=r.headers["x-crtr-attach-revive"];return(Array.isArray(i)?i[0]:i)==="0"}s(J,"reviveOptedOut");function W(r){return new URL(r.url??"/","http://127.0.0.1").searchParams.get("wakeParked")==="1"}s(W,"wakeParkedRequested");function E(r){return r===1e3||r===1001||r===1005}s(E,"isCleanViewerClose");function j(r,e){if(!A(e)){try{r.close(1008,b(`invalid node id: ${e}`))}catch{}return}if(S(e)===null){try{r.close(1011,b(`no node ${e}`))}catch{}return}d(e,{link:"viewer\u2194crtrd"});const i=O(e),n=new x(T);let o=!1,a=!1,f,m=!1,h=!1,v=!1;i.on("connect",()=>{m=!0,!o&&v&&_(e,r),d(e,{link:"relay\u2194broker"})});const p=s((t,l)=>{if(!o){if(o=!0,D(e,r),f?.stop(),!i.destroyed)try{i.destroy()}catch{}try{r.close(t,b(l))}catch{}}},"teardown");f=M(r),i.on("data",t=>{f?.noteActivity();let l;try{l=n.push(t)}catch(c){const y=c instanceof B?`broker sent an oversized frame (${c.message})`:`failed to decode a broker frame for ${e}`;p(1009,y);return}for(const c of l)r.readyState===r.OPEN&&r.send(JSON.stringify(c))}),i.on("error",t=>{const l=k("relay\u2194broker",t);g(e,{link:"relay\u2194broker",op:"bridge socket",kind:l.kind,retry:{disposition:"manual"},message:t.message});const c=t.code==="ECONNREFUSED"||t.code==="ENOENT"?`no running broker for ${e}`:`view socket error for ${e}: ${t.message}`;p(1011,c)}),i.on("close",t=>{o||t||(d(e,{link:"relay\u2194broker"}),p(1e3,"broker socket closed"))}),r.on("close",(t,l)=>{const c=l.toString("utf8");if(o){E(t)&&d(e,{link:"viewer\u2194crtrd"});return}if(E(t))d(e,{link:"viewer\u2194crtrd"});else if(!a){a=!0;const y=k("viewer\u2194crtrd",{code:t,reason:c});g(e,{link:"viewer\u2194crtrd",op:"remote attach websocket",kind:y.kind,retry:{disposition:"auto"},message:c||`remote attach closed (${t})`})}p(t,c||`remote attach closed (${t})`)}),r.on("error",()=>{if(!a){a=!0;const t=k("viewer\u2194crtrd",{code:1006,reason:"remote attach websocket error"});g(e,{link:"viewer\u2194crtrd",op:"remote attach websocket",kind:t.kind,retry:{disposition:"auto"},message:"remote attach websocket error"})}p(1011,"remote attach websocket error")}),r.on("message",t=>{if(o||i.destroyed)return;f?.noteActivity();const c=(Array.isArray(t)?Buffer.concat(t):Buffer.isBuffer(t)?t:Buffer.from(new Uint8Array(t))).toString("utf8");if(!h)try{const w=JSON.parse(c);w?.type==="hello"&&(h=!0,v=w.attends!==!1,v&&m&&_(e,r))}catch{}const y=c.endsWith(`
7
7
  `)?c:c+`
8
- `;try{i.write(d)}catch{}})}s(W,"bridgeConnection");async function te(t,e,i,o){e.on("error",()=>{});try{if(!A(o)){u(e,400,"Bad Request","invalid_node_id",`invalid node id: ${o}`);return}if(S(o)===null){u(e,404,"Not Found","node_not_found",`no node ${o}`);return}if(M(t)){if(!await O(o,250)){u(e,409,"Conflict","node_dormant",`node ${o} is dormant and revive was opted out (?revive=0)`);return}}else{let n;try{const a=await C(o,{resume:!0,capacity:"refuse"},J(t));if(a.parked){u(e,409,"Conflict","node_dormant",`node ${o} is parked; attach with ?wakeParked=1 to wake it`);return}n=a.result.launch?.exited}catch(a){const f=a instanceof Error?a.message:String(a);u(e,409,"Conflict","attach_revive_failed",`attach revive failed: ${f}`);return}if(!await P(o,n)){u(e,409,"Conflict","broker_not_ready",`broker view socket for ${o} did not become listenable after revive`);return}}if(e.destroyed)return;D.handleUpgrade(t,e,i,n=>W(n,o))}catch(n){const a=n instanceof Error?n.message:String(n);if(F(n)){u(e,400,"Bad Request","invalid_request",a);return}u(e,500,"Internal Server Error","internal",`attach upgrade failed: ${a}`)}}s(te,"handleAttachUpgrade");export{L as REMOTE_ATTACH_HEARTBEAT_MS,H as REMOTE_ATTACH_QUIET_MS,te as handleAttachUpgrade,u as refuseUpgrade,V as startRemoteAttachHeartbeat};
8
+ `;try{i.write(y)}catch{}})}s(j,"bridgeConnection");async function te(r,e,i,n){e.on("error",()=>{});try{if(!A(n)){u(e,400,"Bad Request","invalid_node_id",`invalid node id: ${n}`);return}if(S(n)===null){u(e,404,"Not Found","node_not_found",`no node ${n}`);return}if(J(r)){if(!await P(n,250)){u(e,409,"Conflict","node_dormant",`node ${n} is dormant and revive was opted out (?revive=0)`);return}}else{let o;try{const a=await N(n,{resume:!0,capacity:"refuse"},W(r));if(a.parked){u(e,409,"Conflict","node_dormant",`node ${n} is parked; attach with ?wakeParked=1 to wake it`);return}o=a.result.launch?.exited}catch(a){const f=a instanceof Error?a.message:String(a);u(e,409,"Conflict","attach_revive_failed",`attach revive failed: ${f}`);return}if(!await F(n,o)){u(e,409,"Conflict","broker_not_ready",`broker view socket for ${n} did not become listenable after revive`);return}}if(e.destroyed)return;L.handleUpgrade(r,e,i,o=>j(o,n))}catch(o){const a=o instanceof Error?o.message:String(o);if(U(o)){u(e,400,"Bad Request","invalid_request",a);return}u(e,500,"Internal Server Error","internal",`attach upgrade failed: ${a}`)}}s(te,"handleAttachUpgrade");export{V as REMOTE_ATTACH_HEARTBEAT_MS,H as REMOTE_ATTACH_QUIET_MS,te as handleAttachUpgrade,E as isCleanViewerClose,u as refuseUpgrade,M as startRemoteAttachHeartbeat};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@north-light/cli",
3
- "version": "0.3.426",
3
+ "version": "0.3.428",
4
4
  "description": "crtr — agent runtime with memory, plugins, and marketplaces",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -137,7 +137,7 @@
137
137
  },
138
138
  "license": "UNLICENSED",
139
139
  "dependencies": {
140
- "@north-light/identity": "0.3.426",
140
+ "@north-light/identity": "0.3.428",
141
141
  "@earendil-works/pi-agent-core": "1.1.0",
142
142
  "@earendil-works/pi-ai": "1.1.0",
143
143
  "@earendil-works/pi-coding-agent": "1.1.0",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@north-light/identity",
3
- "version": "0.3.426",
3
+ "version": "0.3.428",
4
4
  "description": "crouter cloud access-token verification and scope rules",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
package/runtime.lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@north-light/cli",
3
- "version": "0.3.426",
3
+ "version": "0.3.428",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@north-light/cli",
9
- "version": "0.3.426",
9
+ "version": "0.3.428",
10
10
  "hasInstallScript": true,
11
11
  "license": "UNLICENSED",
12
12
  "workspaces": [
@@ -18,7 +18,7 @@
18
18
  "@earendil-works/pi-ai": "1.1.0",
19
19
  "@earendil-works/pi-coding-agent": "1.1.0",
20
20
  "@earendil-works/pi-tui": "1.1.0",
21
- "@north-light/identity": "0.3.426",
21
+ "@north-light/identity": "0.3.428",
22
22
  "cron-parser": "^5.6.0",
23
23
  "esbuild": "^0.27.7",
24
24
  "jose": "^6.2.12",
@@ -12095,17 +12095,17 @@
12095
12095
  },
12096
12096
  "packages/crouter-api": {
12097
12097
  "name": "@north-light/api",
12098
- "version": "0.3.426",
12098
+ "version": "0.3.428",
12099
12099
  "license": "UNLICENSED"
12100
12100
  },
12101
12101
  "packages/crouter-env-docker": {
12102
12102
  "name": "@north-light/env-docker",
12103
- "version": "0.3.426",
12103
+ "version": "0.3.428",
12104
12104
  "license": "UNLICENSED"
12105
12105
  },
12106
12106
  "packages/crouter-identity": {
12107
12107
  "name": "@north-light/identity",
12108
- "version": "0.3.426",
12108
+ "version": "0.3.428",
12109
12109
  "license": "UNLICENSED",
12110
12110
  "dependencies": {
12111
12111
  "jose": "^6.2.1"
@@ -12113,20 +12113,20 @@
12113
12113
  },
12114
12114
  "packages/crouter-plugin": {
12115
12115
  "name": "@north-light/plugin",
12116
- "version": "0.3.426",
12116
+ "version": "0.3.428",
12117
12117
  "license": "UNLICENSED",
12118
12118
  "dependencies": {
12119
- "@north-light/api": "0.3.426",
12120
- "@north-light/identity": "0.3.426"
12119
+ "@north-light/api": "0.3.428",
12120
+ "@north-light/identity": "0.3.428"
12121
12121
  }
12122
12122
  },
12123
12123
  "packages/crouter-sdk": {
12124
12124
  "name": "@north-light/sdk",
12125
- "version": "0.3.426",
12125
+ "version": "0.3.428",
12126
12126
  "license": "UNLICENSED",
12127
12127
  "dependencies": {
12128
- "@north-light/api": "0.3.426",
12129
- "@north-light/identity": "0.3.426",
12128
+ "@north-light/api": "0.3.428",
12129
+ "@north-light/identity": "0.3.428",
12130
12130
  "jose": "^6.2.1"
12131
12131
  },
12132
12132
  "bin": {