pipane 0.1.7 → 0.1.9

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
- import{R as y,l as f,p as h}from"./rendezvous-trust-api-C2Zob5i4.js";import{W as x}from"./webrtc-frame-transport-BijNnA7N.js";const w={loadIdentity:()=>f(),createTrustApi:()=>new y(window.location.origin),createTransport:i=>new x(i)};async function C(i=w){const p=document.getElementById("app")??document.body;p.replaceChildren();const o=document.createElement("main");Object.assign(o.style,{minHeight:"100vh",display:"grid",placeItems:"center",padding:"24px",background:"var(--background, #fff)",color:"var(--foreground, #111827)",fontFamily:"ui-sans-serif, system-ui, sans-serif"});const c=document.createElement("section");Object.assign(c.style,{width:"min(460px, 100%)",border:"1px solid var(--border, #d1d5db)",borderRadius:"12px",padding:"28px",boxShadow:"0 18px 45px rgba(0, 0, 0, 0.10)",background:"var(--card, #fff)"});const s=document.createElement("h1");s.textContent="Pair this browser",Object.assign(s.style,{margin:"0 0 8px",fontSize:"24px"});const d=document.createElement("p");d.textContent="pipane will create a private device key and ask the backend to authorize it.",Object.assign(d.style,{margin:"0 0 20px",lineHeight:"1.5",opacity:"0.75"});const e=document.createElement("p");e.dataset.testid="pairing-status",e.setAttribute("role","status"),Object.assign(e.style,{margin:"0",lineHeight:"1.5",fontWeight:"600"});const t=document.createElement("button");t.type="button",t.textContent="Try again",t.hidden=!0,Object.assign(t.style,{marginTop:"18px",border:"1px solid var(--border, #9ca3af)",borderRadius:"8px",padding:"9px 14px",background:"transparent",color:"inherit",cursor:"pointer"});const n=document.createElement("a");n.textContent="Open backend",n.dataset.testid="pairing-continue",n.hidden=!0,Object.assign(n.style,{display:"inline-block",marginTop:"18px",borderRadius:"8px",padding:"9px 14px",background:"#2563eb",color:"#fff",textDecoration:"none"}),c.append(s,d,e,t,n),o.append(c),p.append(o);let l=!1;const u=async()=>{if(l)return;l=!0,t.hidden=!0,e.textContent="Creating this browser's private device key…",n.hidden=!0;let r;try{const a=h(window.location.href),g=await i.loadIdentity(),m=i.createTrustApi();e.textContent="Contacting the backend…",r=i.createTransport({rendezvousUrl:window.location.origin,backendId:a.backendId,deviceIdentity:g,authorize:async()=>({...await m.createPairingTicket(g,a),pairingSecret:a.secret})}),await r.connect("webrtc"),r.close(1e3,"Pairing complete"),r=void 0;const b=`/backend/${encodeURIComponent(a.backendId)}`;window.history.replaceState(null,"",b),e.textContent="Paired successfully. This browser can now request connections to the backend.",e.style.color="#15803d",n.href=b,n.hidden=!1}catch(a){r?.close(1e3,"Pairing failed"),e.textContent=a instanceof Error?a.message:"Pairing failed",e.style.color="#b91c1c",t.hidden=!1}finally{l=!1}};t.addEventListener("click",()=>{u()}),await u()}export{C as initializePairingPage};
1
+ import{R as y,l as f,p as h}from"./rendezvous-trust-api-C2Zob5i4.js";import{W as x}from"./webrtc-frame-transport-DGX5EO_A.js";const w={loadIdentity:()=>f(),createTrustApi:()=>new y(window.location.origin),createTransport:i=>new x(i)};async function C(i=w){const p=document.getElementById("app")??document.body;p.replaceChildren();const o=document.createElement("main");Object.assign(o.style,{minHeight:"100vh",display:"grid",placeItems:"center",padding:"24px",background:"var(--background, #fff)",color:"var(--foreground, #111827)",fontFamily:"ui-sans-serif, system-ui, sans-serif"});const c=document.createElement("section");Object.assign(c.style,{width:"min(460px, 100%)",border:"1px solid var(--border, #d1d5db)",borderRadius:"12px",padding:"28px",boxShadow:"0 18px 45px rgba(0, 0, 0, 0.10)",background:"var(--card, #fff)"});const s=document.createElement("h1");s.textContent="Pair this browser",Object.assign(s.style,{margin:"0 0 8px",fontSize:"24px"});const d=document.createElement("p");d.textContent="pipane will create a private device key and ask the backend to authorize it.",Object.assign(d.style,{margin:"0 0 20px",lineHeight:"1.5",opacity:"0.75"});const e=document.createElement("p");e.dataset.testid="pairing-status",e.setAttribute("role","status"),Object.assign(e.style,{margin:"0",lineHeight:"1.5",fontWeight:"600"});const t=document.createElement("button");t.type="button",t.textContent="Try again",t.hidden=!0,Object.assign(t.style,{marginTop:"18px",border:"1px solid var(--border, #9ca3af)",borderRadius:"8px",padding:"9px 14px",background:"transparent",color:"inherit",cursor:"pointer"});const n=document.createElement("a");n.textContent="Open backend",n.dataset.testid="pairing-continue",n.hidden=!0,Object.assign(n.style,{display:"inline-block",marginTop:"18px",borderRadius:"8px",padding:"9px 14px",background:"#2563eb",color:"#fff",textDecoration:"none"}),c.append(s,d,e,t,n),o.append(c),p.append(o);let l=!1;const u=async()=>{if(l)return;l=!0,t.hidden=!0,e.textContent="Creating this browser's private device key…",n.hidden=!0;let r;try{const a=h(window.location.href),g=await i.loadIdentity(),m=i.createTrustApi();e.textContent="Contacting the backend…",r=i.createTransport({rendezvousUrl:window.location.origin,backendId:a.backendId,deviceIdentity:g,authorize:async()=>({...await m.createPairingTicket(g,a),pairingSecret:a.secret})}),await r.connect("webrtc"),r.close(1e3,"Pairing complete"),r=void 0;const b=`/backend/${encodeURIComponent(a.backendId)}`;window.history.replaceState(null,"",b),e.textContent="Paired successfully. This browser can now request connections to the backend.",e.style.color="#15803d",n.href=b,n.hidden=!1}catch(a){r?.close(1e3,"Pairing failed"),e.textContent=a instanceof Error?a.message:"Pairing failed",e.style.color="#b91c1c",t.hidden=!1}finally{l=!1}};t.addEventListener("click",()=>{u()}),await u()}export{C as initializePairingPage};
@@ -1 +1 @@
1
- import{B as c,e as f,i as w,d as y}from"./__vite-browser-external-DtLbhLGx.js";import{R as g,l as m}from"./rendezvous-trust-api-C2Zob5i4.js";import{W as v}from"./webrtc-frame-transport-BijNnA7N.js";import{W as k}from"./ws-agent-adapter-BRysf-Y7.js";import"./index-DwbBcYUf.js";const A=3e4,E=10*6e4;class B extends Error{constructor(e){super(e.message),this.payload=e,this.name="DataChannelBackendApiError"}}class q{constructor(e,t,r=A){this.transport=e,this.backendId=t,this.requestTimeoutMs=r,this.requestId=0,this.pending=new Map,e.onFrame(i=>this.handleFrame(i)),e.onConnectionChange(({connected:i,reconnected:o})=>{if(!(!i||!o))for(const d of this.pending.values())try{this.transport.send(d.encodedFrame)}catch{}})}async getCapabilities(){const e=await this.request("backend.capabilities",{});if(!a(e)||e.backendId!==this.backendId||e.semanticProtocolVersion!==c||!S(e.applicationProtocolVersions)||!l(e.features))throw new Error("Backend returned invalid capabilities");return e}async listSessions(){const e=await this.request("sessions.list",{});if(!Array.isArray(e)||!e.every(T))throw new Error("Backend returned an invalid session list");return e.map(t=>({...t,backendId:this.backendId}))}async deleteSession(e){await this.request("sessions.delete",{sessionPath:e})}async listForkMessages(e){const t=await this.request("sessions.forkMessages",{sessionPath:e});if(!Array.isArray(t)||!t.every(r=>a(r)&&s(r.entryId)&&s(r.text)))throw new Error("Backend returned invalid fork messages");return t}async browseDirectory(e){const t=await this.request("host.browse",{path:e});if(!a(t)||!s(t.path)||!Array.isArray(t.dirs)||!t.dirs.every(r=>a(r)&&s(r.name)&&s(r.path)))throw new Error("Backend returned an invalid directory listing");return t}async createDirectory(e,t){const r=await this.request("host.mkdir",{parentPath:e,name:t});if(!a(r)||!s(r.name)||!s(r.path))throw new Error("Backend returned an invalid created folder");return r}async getRawSession(e){const t=await this.request("sessions.raw",{sessionPath:e});if(!s(t))throw new Error("Backend returned invalid raw session content");return t}async getFileContent(e,t){const r=await this.request("files.read",{sessionPath:e,path:t});if(!a(r)||!s(r.path)||!s(r.content))throw new Error("Backend returned invalid file content");return r}async createFileUpload(e){const t=await this.request("files.upload.create",e);if(!a(t)||!s(t.uploadId)||t.uploadId.length===0)throw new Error("Backend returned an invalid file upload session");return t}async appendFileUpload(e){const t=await this.request("files.upload.append",e);if(!a(t)||!Number.isSafeInteger(t.nextOffset)||t.nextOffset<0)throw new Error("Backend returned an invalid file upload offset");return t}async completeFileUpload(e){const t=await this.request("files.upload.complete",{uploadId:e});if(!a(t)||!s(t.path)||!s(t.fileName)||!s(t.mimeType)||!Number.isSafeInteger(t.size)||t.size<0)throw new Error("Backend returned an invalid uploaded file");return t}async getLocalSettings(){const e=await this.request("settings.get",{});if(!a(e)||!s(e.path)||typeof e.exists!="boolean"||!l(e.errors)||!s(e.formatted)||!("settings"in e))throw new Error("Backend returned invalid local settings");return e}validateLocalSettings(e){return this.settingsMutation("settings.validate",{content:e})}patchLocalSettings(e){return this.settingsMutation("settings.patch",{patch:e})}saveLocalSettings(e){return this.settingsMutation("settings.save",{content:e})}async getUpdates(){const e=await this.request("updates.get",{});if(!a(e)||!s(e.checkedAt)||!Array.isArray(e.notices))throw new Error("Backend returned an invalid update snapshot");return e}async runUpdate(e){const t=await this.request("updates.run",{target:e},E);if(!a(t)||!a(t.result)||!a(t.snapshot))throw new Error("Backend returned an invalid update result");return t}async settingsMutation(e,t){const r=await this.request(e,t);if(!a(r)||typeof r.valid!="boolean"||!l(r.errors)||r.formatted!==void 0&&!s(r.formatted))throw new Error("Backend returned an invalid settings response");return r}request(e,t,r=this.requestTimeoutMs){if(!this.transport.isConnected)return Promise.reject(new Error("Backend transport is not connected"));const i=`api_${++this.requestId}_${crypto.randomUUID()}`;return new Promise((o,d)=>{const h=f({v:c,kind:"request",id:i,method:e,params:t}),p=setTimeout(()=>{this.pending.delete(i),d(new Error(`Backend request timed out: ${e}`))},r);this.pending.set(i,{method:e,encodedFrame:h,resolve:o,reject:d,timer:p});try{this.transport.send(h)}catch(u){clearTimeout(p),this.pending.delete(i),d(u instanceof Error?u:new Error(String(u)))}})}handleFrame(e){if(!w(e))return;const t=y(e);if(!t.ok)return;const r=t.value,i=this.pending.get(r.id);if(i){if(clearTimeout(i.timer),this.pending.delete(r.id),r.method!==i.method){i.reject(new Error(`Backend response method mismatch: expected ${i.method}, received ${r.method}`));return}r.success?i.resolve(r.result):i.reject(new B(r.error))}}}function T(n){return a(n)&&s(n.id)&&s(n.path)&&s(n.cwd)&&s(n.created)&&s(n.modified)&&Number.isSafeInteger(n.messageCount)&&s(n.firstMessage)}function a(n){return typeof n=="object"&&n!==null&&!Array.isArray(n)}function s(n){return typeof n=="string"}function l(n){return Array.isArray(n)&&n.every(s)}function S(n){return Array.isArray(n)&&n.every(e=>Number.isSafeInteger(e)&&e>0)}class F{constructor(e,t={loadIdentity:()=>m(),createTrustApi:()=>new g(e)}){this.rendezvousUrl=e,this.dependencies=t,this.descriptors=[],this.clients=new Map}async initialize(){return this.identity??=await this.dependencies.loadIdentity(),this.trustApi??=this.dependencies.createTrustApi(),this.descriptors=await this.trustApi.listAuthorizedBackends(this.identity),this.authorizedBackends}get authorizedBackends(){return this.descriptors.map(e=>({...e,protocolVersions:[...e.protocolVersions]}))}getClient(e){const t=this.clients.get(e);if(t)return t;if(!this.identity||!this.trustApi)throw new Error("Remote backend manager is not initialized");const r=this.descriptors.find(o=>o.backendId===e);if(!r)throw new Error("This browser is not authorized for the requested backend");if(r.online&&!r.protocolVersions.includes(c))throw new Error(`Backend does not support semantic protocol v${c}`);const i=this.dependencies.createClient?this.dependencies.createClient(e,this.identity,this.trustApi):this.createClient(e,this.identity,this.trustApi);return this.clients.set(e,i),i}async revokeBackend(e){if(!this.identity||!this.trustApi)throw new Error("Remote backend manager is not initialized");await this.trustApi.revokeBackend(this.identity,e),this.clients.get(e)?.disconnect(),this.clients.delete(e),this.descriptors=this.descriptors.filter(t=>t.backendId!==e)}disconnectAll(){for(const e of this.clients.values())e.disconnect();this.clients.clear()}createClient(e,t,r){const i=new v({rendezvousUrl:this.rendezvousUrl,backendId:e,deviceIdentity:t,authorize:()=>r.createConnectionTicket(t,e)}),o=new q(i,e);return new k({transport:i,api:o})}}export{F as RemoteBackendManager};
1
+ import{B as c,e as f,i as w,d as y}from"./__vite-browser-external-DtLbhLGx.js";import{R as g,l as m}from"./rendezvous-trust-api-C2Zob5i4.js";import{W as v}from"./webrtc-frame-transport-DGX5EO_A.js";import{W as k}from"./ws-agent-adapter-DpkkZGPl.js";import"./index-C7_1ODks.js";const A=3e4,E=10*6e4;class B extends Error{constructor(e){super(e.message),this.payload=e,this.name="DataChannelBackendApiError"}}class q{constructor(e,t,r=A){this.transport=e,this.backendId=t,this.requestTimeoutMs=r,this.requestId=0,this.pending=new Map,e.onFrame(i=>this.handleFrame(i)),e.onConnectionChange(({connected:i,reconnected:o})=>{if(!(!i||!o))for(const d of this.pending.values())try{this.transport.send(d.encodedFrame)}catch{}})}async getCapabilities(){const e=await this.request("backend.capabilities",{});if(!a(e)||e.backendId!==this.backendId||e.semanticProtocolVersion!==c||!S(e.applicationProtocolVersions)||!l(e.features))throw new Error("Backend returned invalid capabilities");return e}async listSessions(){const e=await this.request("sessions.list",{});if(!Array.isArray(e)||!e.every(T))throw new Error("Backend returned an invalid session list");return e.map(t=>({...t,backendId:this.backendId}))}async deleteSession(e){await this.request("sessions.delete",{sessionPath:e})}async listForkMessages(e){const t=await this.request("sessions.forkMessages",{sessionPath:e});if(!Array.isArray(t)||!t.every(r=>a(r)&&s(r.entryId)&&s(r.text)))throw new Error("Backend returned invalid fork messages");return t}async browseDirectory(e){const t=await this.request("host.browse",{path:e});if(!a(t)||!s(t.path)||!Array.isArray(t.dirs)||!t.dirs.every(r=>a(r)&&s(r.name)&&s(r.path)))throw new Error("Backend returned an invalid directory listing");return t}async createDirectory(e,t){const r=await this.request("host.mkdir",{parentPath:e,name:t});if(!a(r)||!s(r.name)||!s(r.path))throw new Error("Backend returned an invalid created folder");return r}async getRawSession(e){const t=await this.request("sessions.raw",{sessionPath:e});if(!s(t))throw new Error("Backend returned invalid raw session content");return t}async getFileContent(e,t){const r=await this.request("files.read",{sessionPath:e,path:t});if(!a(r)||!s(r.path)||!s(r.content))throw new Error("Backend returned invalid file content");return r}async createFileUpload(e){const t=await this.request("files.upload.create",e);if(!a(t)||!s(t.uploadId)||t.uploadId.length===0)throw new Error("Backend returned an invalid file upload session");return t}async appendFileUpload(e){const t=await this.request("files.upload.append",e);if(!a(t)||!Number.isSafeInteger(t.nextOffset)||t.nextOffset<0)throw new Error("Backend returned an invalid file upload offset");return t}async completeFileUpload(e){const t=await this.request("files.upload.complete",{uploadId:e});if(!a(t)||!s(t.path)||!s(t.fileName)||!s(t.mimeType)||!Number.isSafeInteger(t.size)||t.size<0)throw new Error("Backend returned an invalid uploaded file");return t}async getLocalSettings(){const e=await this.request("settings.get",{});if(!a(e)||!s(e.path)||typeof e.exists!="boolean"||!l(e.errors)||!s(e.formatted)||!("settings"in e))throw new Error("Backend returned invalid local settings");return e}validateLocalSettings(e){return this.settingsMutation("settings.validate",{content:e})}patchLocalSettings(e){return this.settingsMutation("settings.patch",{patch:e})}saveLocalSettings(e){return this.settingsMutation("settings.save",{content:e})}async getUpdates(){const e=await this.request("updates.get",{});if(!a(e)||!s(e.checkedAt)||!Array.isArray(e.notices))throw new Error("Backend returned an invalid update snapshot");return e}async runUpdate(e){const t=await this.request("updates.run",{target:e},E);if(!a(t)||!a(t.result)||!a(t.snapshot))throw new Error("Backend returned an invalid update result");return t}async settingsMutation(e,t){const r=await this.request(e,t);if(!a(r)||typeof r.valid!="boolean"||!l(r.errors)||r.formatted!==void 0&&!s(r.formatted))throw new Error("Backend returned an invalid settings response");return r}request(e,t,r=this.requestTimeoutMs){if(!this.transport.isConnected)return Promise.reject(new Error("Backend transport is not connected"));const i=`api_${++this.requestId}_${crypto.randomUUID()}`;return new Promise((o,d)=>{const h=f({v:c,kind:"request",id:i,method:e,params:t}),p=setTimeout(()=>{this.pending.delete(i),d(new Error(`Backend request timed out: ${e}`))},r);this.pending.set(i,{method:e,encodedFrame:h,resolve:o,reject:d,timer:p});try{this.transport.send(h)}catch(u){clearTimeout(p),this.pending.delete(i),d(u instanceof Error?u:new Error(String(u)))}})}handleFrame(e){if(!w(e))return;const t=y(e);if(!t.ok)return;const r=t.value,i=this.pending.get(r.id);if(i){if(clearTimeout(i.timer),this.pending.delete(r.id),r.method!==i.method){i.reject(new Error(`Backend response method mismatch: expected ${i.method}, received ${r.method}`));return}r.success?i.resolve(r.result):i.reject(new B(r.error))}}}function T(n){return a(n)&&s(n.id)&&s(n.path)&&s(n.cwd)&&s(n.created)&&s(n.modified)&&Number.isSafeInteger(n.messageCount)&&s(n.firstMessage)}function a(n){return typeof n=="object"&&n!==null&&!Array.isArray(n)}function s(n){return typeof n=="string"}function l(n){return Array.isArray(n)&&n.every(s)}function S(n){return Array.isArray(n)&&n.every(e=>Number.isSafeInteger(e)&&e>0)}class F{constructor(e,t={loadIdentity:()=>m(),createTrustApi:()=>new g(e)}){this.rendezvousUrl=e,this.dependencies=t,this.descriptors=[],this.clients=new Map}async initialize(){return this.identity??=await this.dependencies.loadIdentity(),this.trustApi??=this.dependencies.createTrustApi(),this.descriptors=await this.trustApi.listAuthorizedBackends(this.identity),this.authorizedBackends}get authorizedBackends(){return this.descriptors.map(e=>({...e,protocolVersions:[...e.protocolVersions]}))}getClient(e){const t=this.clients.get(e);if(t)return t;if(!this.identity||!this.trustApi)throw new Error("Remote backend manager is not initialized");const r=this.descriptors.find(o=>o.backendId===e);if(!r)throw new Error("This browser is not authorized for the requested backend");if(r.online&&!r.protocolVersions.includes(c))throw new Error(`Backend does not support semantic protocol v${c}`);const i=this.dependencies.createClient?this.dependencies.createClient(e,this.identity,this.trustApi):this.createClient(e,this.identity,this.trustApi);return this.clients.set(e,i),i}async revokeBackend(e){if(!this.identity||!this.trustApi)throw new Error("Remote backend manager is not initialized");await this.trustApi.revokeBackend(this.identity,e),this.clients.get(e)?.disconnect(),this.clients.delete(e),this.descriptors=this.descriptors.filter(t=>t.backendId!==e)}disconnectAll(){for(const e of this.clients.values())e.disconnect();this.clients.clear()}createClient(e,t,r){const i=new v({rendezvousUrl:this.rendezvousUrl,backendId:e,deviceIdentity:t,authorize:()=>r.createConnectionTicket(t,e)}),o=new q(i,e);return new k({transport:i,api:o})}}export{F as RemoteBackendManager};
@@ -0,0 +1 @@
1
+ import{P,b as B,s as O,T as R,v as U,c as W}from"./rendezvous-trust-api-C2Zob5i4.js";const E=2;function N(t,e){const n=new URL(t);if(n.protocol==="http:")n.protocol="ws:";else if(n.protocol==="https:")n.protocol="wss:";else if(n.protocol!=="ws:"&&n.protocol!=="wss:")throw new Error(`Unsupported rendezvous URL protocol: ${n.protocol}`);return n.pathname=`/v${E}/rendezvous/${e}`,n.search="",n.hash="",n.toString()}function F(t){return JSON.stringify(t)}function H(t){return $(t)}function $(t,e){const n=j(t);if(!n.ok)return n;const i=n.value;switch(i.type){case"challenge":return u("invalid backend challenge");case"registered":return u("invalid backend registration");case"pairing_opened":return u("invalid pairing acknowledgement");case"pairing_confirmed":return u("invalid pairing confirmation");case"connection_request":return u("invalid connection request");case"backend_connected":if(!c(i.backendId)||!c(i.connectionId))return u("invalid browser connection acknowledgement");break;case"signal":if(!c(i.connectionId)||!J(i.signal))return u("invalid ICE signal");break;case"connection_binding":if(!c(i.connectionId)||!V(i.binding))return u("invalid backend identity binding");break;case"connection_closed":if(!c(i.connectionId)||!c(i.reason))return u("invalid connection closure");break;case"authorization_revoked":return u("invalid authorization revocation");case"error":if(!q(i.code)||!c(i.message)||!Y(i.connectionId))return u("invalid rendezvous error");break;default:return K(i.type)}return{ok:!0,value:i}}function j(t){let e;try{e=JSON.parse(t)}catch{return{ok:!1,error:{code:"invalid_json",message:"Message is not valid JSON"}}}return!v(e)||!c(e.type)?u("Message must be an object with a type"):e.protocolVersion!==E?{ok:!1,error:{code:"unsupported_version",message:`Unsupported rendezvous protocol version: ${String(e.protocolVersion)}`}}:{ok:!0,value:e}}function J(t){return!v(t)||!c(t.kind)?!1:t.kind==="description"?(t.type==="offer"||t.type==="answer")&&c(t.sdp):t.kind==="candidate"?c(t.candidate)&&(t.sdpMid===null||c(t.sdpMid))&&(t.sdpMLineIndex===null||Number.isSafeInteger(t.sdpMLineIndex)&&t.sdpMLineIndex>=0):!1}function V(t){return v(t)&&t.version===1&&c(t.backendId)&&c(t.publicKey)&&c(t.connectionId)&&c(t.offerSha256)&&c(t.answerSha256)&&c(t.dtlsFingerprint)&&X(t.expiresAt)&&c(t.signature)}function q(t){return t==="invalid_json"||t==="invalid_message"||t==="unsupported_version"||t==="unknown_message"||t==="backend_offline"||t==="unauthorized_connection"||t==="invalid_ticket"||t==="invalid_pairing"}function u(t){return{ok:!1,error:{code:"invalid_message",message:t}}}function K(t){return{ok:!1,error:{code:"unknown_message",message:`Unknown rendezvous message: ${String(t)}`}}}function v(t){return!!t&&typeof t=="object"&&!Array.isArray(t)}function c(t){return typeof t=="string"&&t.length>0}function Y(t){return t===void 0||c(t)}function X(t){return Number.isSafeInteger(t)&&t>0}class G{constructor(e){this.signalListeners=new Set,this.bindingListeners=new Set,this.closedListeners=new Set,this.errorListeners=new Set,this.socket=null,this.endpoint=N(e.url,"browser"),this.backendId=e.backendId,this.ticket=e.ticket,this.createWebSocket=e.createWebSocket??(n=>new WebSocket(n))}get activeConnectionId(){return this.connectionId}connect(){if(this.connectionId)return Promise.resolve(this.connectionId);if(this.connecting)return this.connecting;if(this.connecting=new Promise((n,i)=>{this.resolveConnecting=n,this.rejectConnecting=i}),this.socket?.readyState===WebSocket.OPEN)return this.sendRaw({type:"connect_backend",backendId:this.backendId,ticket:this.ticket}),this.connecting;const e=this.createWebSocket(this.endpoint);return this.socket=e,e.onopen=()=>this.sendRaw({type:"connect_backend",backendId:this.backendId,ticket:this.ticket}),e.onmessage=n=>this.handleMessage(e,String(n.data)),e.onerror=()=>{const n=new Error("Rendezvous WebSocket failed");this.emitError(n),this.failConnection(n)},e.onclose=()=>{if(this.socket!==e)return;this.socket=null;const n=this.connectionId!==void 0;this.connectionId=void 0;const i=new Error("Rendezvous WebSocket closed");if(this.failConnection(i),n)for(const r of this.closedListeners)r(i.message)},this.connecting}onSignal(e){return this.signalListeners.add(e),()=>this.signalListeners.delete(e)}onIdentityBinding(e){return this.bindingListeners.add(e),()=>this.bindingListeners.delete(e)}onConnectionClosed(e){return this.closedListeners.add(e),()=>this.closedListeners.delete(e)}onError(e){return this.errorListeners.add(e),()=>this.errorListeners.delete(e)}sendSignal(e){if(!this.connectionId)throw new Error("Browser rendezvous route is not connected");this.sendRaw({type:"signal",connectionId:this.connectionId,signal:e})}close(e="Browser closed connection"){this.connectionId&&this.socket?.readyState===WebSocket.OPEN&&this.sendRaw({type:"close_connection",connectionId:this.connectionId,reason:e}),this.connectionId=void 0,this.socket?.close(1e3,e),this.socket=null,this.failConnection(new Error(e))}handleMessage(e,n){if(e!==this.socket)return;const i=H(n);if(!i.ok){const o=new Error(i.error.message);this.emitError(o),this.failConnection(o),e.close(1002,"Invalid rendezvous message");return}const r=i.value;switch(r.type){case"backend_connected":if(r.backendId!==this.backendId){const o=new Error("Rendezvous connected an unexpected backend");this.emitError(o),this.failConnection(o),e.close(1002,"Backend identity mismatch");return}this.connectionId=r.connectionId,this.resolveConnecting?.(r.connectionId),this.clearConnecting();break;case"signal":if(r.connectionId!==this.connectionId)return;for(const o of this.signalListeners)o(r.signal);break;case"connection_binding":if(r.connectionId!==this.connectionId)return;for(const o of this.bindingListeners)o(r.binding);break;case"connection_closed":if(r.connectionId!==this.connectionId)return;this.connectionId=void 0;for(const o of this.closedListeners)o(r.reason);break;case"error":this.emitError(r),this.connectionId||this.failConnection(new Error(r.message));break}}sendRaw(e){if(!this.socket||this.socket.readyState!==WebSocket.OPEN)throw new Error("Rendezvous WebSocket is not connected");this.socket.send(F({protocolVersion:E,...e}))}failConnection(e){this.rejectConnecting?.(e),this.clearConnecting()}clearConnecting(){this.connecting=void 0,this.resolveConnecting=void 0,this.rejectConnecting=void 0}emitError(e){for(const n of this.errorListeners)n(e)}}const _=1,Z=`{"__pipaneDataChannelChunk":${_},`,S=12e3,Q=16*1024,A=64*1024*1024,ee=4,ne=96*1024*1024,te=1024*1024,ie=256*1024,D=new TextEncoder,oe=new TextDecoder("utf-8",{fatal:!0}),re=Math.ceil(A/S),x=/^[A-Za-z0-9_-]{1,64}$/u,se=/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/u;function ce(t,e){if(!x.test(e))throw new Error("DataChannel frame id is invalid");const n=D.encode(t);if(n.byteLength>A)throw new Error("DataChannel frame exceeds the reassembly limit");if(n.byteLength<=S)return[t];const i=Math.ceil(n.byteLength/S),r=[];for(let o=0;o<i;o++){const a=o*S,g={__pipaneDataChannelChunk:_,id:e,index:o,total:i,data:z(n.subarray(a,a+S))},k=JSON.stringify(g);if(D.encode(k).byteLength>Q)throw new Error("Encoded DataChannel chunk exceeds the physical message limit");r.push(k)}return r}class ae{constructor(){this.pending=new Map}accept(e){if(!e.startsWith(Z))return e;try{const n=de(e);let i=this.pending.get(n.id);if(!i){if(n.index!==0)throw new Error("DataChannel chunk sequence does not start at zero");if(this.pending.size>=ee)throw new Error("Too many DataChannel frames are pending");i={total:n.total,nextIndex:0,byteLength:0,chunks:[]},this.pending.set(n.id,i)}if(i.total!==n.total||n.index!==i.nextIndex)throw new Error("DataChannel chunks are inconsistent or out of order");const r=he(n.data);if(r.byteLength===0||r.byteLength>S)throw new Error("DataChannel chunk payload size is invalid");if(i.byteLength+=r.byteLength,i.byteLength>A)throw new Error("DataChannel frame exceeds the reassembly limit");if(i.chunks.push(r),i.nextIndex++,i.nextIndex<i.total)return;this.pending.delete(n.id);const o=new Uint8Array(i.byteLength);let a=0;for(const g of i.chunks)o.set(g,a),a+=g.byteLength;return oe.decode(o)}catch(n){throw this.pending.clear(),n}}reset(){this.pending.clear()}}function de(t){let e;try{e=JSON.parse(t)}catch{throw new Error("DataChannel chunk is not valid JSON")}if(!e||typeof e!="object"||Array.isArray(e))throw new Error("DataChannel chunk must be an object");const n=e;if(n.__pipaneDataChannelChunk!==_||typeof n.id!="string"||!x.test(n.id)||!Number.isSafeInteger(n.index)||n.index<0||!Number.isSafeInteger(n.total)||n.total<2||n.total>re||n.index>=n.total||typeof n.data!="string")throw new Error("DataChannel chunk envelope is invalid");return n}function z(t){let e="";for(let n=0;n<t.byteLength;n+=4096)e+=String.fromCharCode(...t.subarray(n,n+4096));return btoa(e)}function he(t){if(!se.test(t))throw new Error("DataChannel chunk payload is not valid base64");let e;try{e=atob(t)}catch{throw new Error("DataChannel chunk payload is not valid base64")}const n=new Uint8Array(e.length);for(let i=0;i<e.length;i++)n[i]=e.charCodeAt(i);if(z(n)!==t)throw new Error("DataChannel chunk payload is not canonical base64");return n}class me{constructor(e){this.frameListeners=new Set,this.connectionListeners=new Set,this.decoder=new ae,this.outgoing=[],this.connected=!1,this.manuallyClosed=!1,this.everConnected=!1,this.reconnectAttempt=0,this.nextFrameId=0,this.queuedBytes=0,this.flushing=!1,this.iceServerUrls=[],this.rendezvousUrl=e.rendezvousUrl,this.backendId=e.backendId,this.deviceIdentity=e.deviceIdentity,this.authorize=e.authorize,this.iceTransportPolicy=e.iceTransportPolicy??"all",this.createPeerConnection=e.createPeerConnection??(n=>new RTCPeerConnection(n)),this.createRendezvousClient=e.createRendezvousClient??(n=>new G({url:this.rendezvousUrl,backendId:this.backendId,ticket:n})),this.reconnectDelayMs=e.reconnectDelayMs??(n=>Math.min(15e3,500*2**Math.min(n,5))),this.schedule=e.schedule??((n,i)=>setTimeout(n,i))}get isConnected(){return this.connected&&this.channel?.readyState==="open"}get isReconnecting(){return this.reconnectTimer!==void 0}onFrame(e){return this.frameListeners.add(e),()=>this.frameListeners.delete(e)}onConnectionChange(e){return this.connectionListeners.add(e),()=>this.connectionListeners.delete(e)}async connect(e){if(this.isConnected)return;this.manuallyClosed=!1;const n=await this.authorize(),i=be(n.ticket);if(this.iceServerUrls=ue(n.iceServers),i.backendId!==this.backendId||i.deviceId!==this.deviceIdentity.deviceId)throw new Error("Connection ticket does not match this browser and backend");const r=this.createRendezvousClient(n.ticket),o=this.createPeerConnection({iceServers:n.iceServers,iceTransportPolicy:this.iceTransportPolicy}),a=o.createDataChannel(P,{ordered:!0,protocol:B});a.bufferedAmountLowThreshold=ie,a.onbufferedamountlow=()=>this.flushOutgoing(),this.rendezvous=r,this.peer=o,this.channel=a,await new Promise(async(g,k)=>{let m=!1,p="",d="",C,y,I=!1;const L=[],l=s=>{m||(m=!0,this.closeInternal(),k(s instanceof Error?s:new Error(String(s))))},T=async()=>{if(!(!C||!y||I)){await U(y,{backendId:this.backendId,connectionId:p,offerSdp:d,answerSdp:C,expiresAt:i.expiresAt}),await o.setRemoteDescription({type:"answer",sdp:C}),I=!0;for(const s of L.splice(0))await o.addIceCandidate(s)}};o.onicecandidate=s=>{if(!s.candidate||!p)return;const w=s.candidate.toJSON();r.sendSignal({kind:"candidate",candidate:w.candidate??"",sdpMid:w.sdpMid??null,sdpMLineIndex:w.sdpMLineIndex??null})},o.onconnectionstatechange=()=>{(o.connectionState==="failed"||o.connectionState==="closed")&&(m?this.handleDisconnected():l(new Error(`WebRTC connection ${o.connectionState}`)))},a.onerror=()=>l(new Error("WebRTC DataChannel failed")),a.onclose=()=>{m?this.handleDisconnected():l(new Error("WebRTC DataChannel closed before authentication"))},a.onopen=()=>{O(this.deviceIdentity,n.ticket,y?.signature??"").then(s=>{if(!y)throw new Error("Backend identity binding is missing");a.send(JSON.stringify({protocolVersion:R,type:"authenticate",ticket:n.ticket,bindingSignature:y.signature,deviceSignature:s,pairingSecret:n.pairingSecret}))}).catch(l)},a.onmessage=s=>{const w=String(s.data);if(!this.connected){let b;try{b=JSON.parse(w)}catch{l(new Error("Backend returned an invalid authentication frame"));return}if(b?.protocolVersion!==R||b?.type!=="authenticated"){l(new Error(b?.message||"Backend rejected DataChannel authentication"));return}if(b.deviceId!==this.deviceIdentity.deviceId){l(new Error("Backend authenticated an unexpected browser device"));return}this.connected=!0,this.reconnectAttempt=0,m=!0,this.emitConnectionChange({connected:!0,reconnected:this.everConnected}),this.everConnected=!0,g();return}try{const b=this.decoder.accept(w);if(b===void 0)return;for(const M of this.frameListeners)M(b)}catch{this.handleDisconnected()}},r.onSignal(s=>{(async()=>{if(s.kind==="description"){if(s.type!=="answer")throw new Error("Browser expected an SDP answer");C=s.sdp,await T();return}const w={candidate:s.candidate,sdpMid:s.sdpMid,sdpMLineIndex:s.sdpMLineIndex};I?await o.addIceCandidate(w):L.push(w)})().catch(l)}),r.onIdentityBinding(s=>{y=s,T().catch(l)}),r.onConnectionClosed(s=>l(new Error(s))),r.onError(s=>l(s instanceof Error?s:new Error(s.message)));try{if(p=await r.connect(),p!==i.connectionId)throw new Error("Rendezvous route does not match the connection ticket");const s=await o.createOffer();if(d=s.sdp??"",!d)throw new Error("Browser WebRTC offer is empty");await o.setLocalDescription(s),r.sendSignal({kind:"description",type:"offer",sdp:d})}catch(s){l(s)}})}send(e){if(!this.isConnected||!this.channel)throw new Error("Backend transport is not connected");const i=ce(e,`b${(++this.nextFrameId).toString(36)}`).map(o=>({message:o,byteLength:le.encode(o).byteLength})),r=i.reduce((o,a)=>o+a.byteLength,0);if(this.queuedBytes+r>ne)throw new Error("DataChannel outgoing frame queue exceeds its limit");this.outgoing.push(...i),this.queuedBytes+=r,this.flushOutgoing()}async getConnectionDiagnostics(){const e=this.peer,n=this.channel;let i=[];if(e)try{(await e.getStats()).forEach(C=>i.push(C))}catch{i=[]}const r=i.filter(d=>d.type==="local-candidate"||d.type==="remote-candidate").map(d=>ge(d,d.type==="local-candidate"?"local":"remote")),o=fe(i),a=o?r.find(d=>d.id===o.localCandidateId):void 0,g=o?r.find(d=>d.id===o.remoteCandidateId):void 0,k=o?pe(o,a,g):void 0,m=i.find(d=>d.type==="transport"),p=i.find(d=>d.type==="data-channel");return{collectedAt:new Date().toISOString(),carrier:"webrtc",backendId:this.backendId,rendezvousUrl:this.rendezvousUrl,signalingUrl:N(this.rendezvousUrl,"browser"),connectionState:e?.connectionState,iceConnectionState:e?.iceConnectionState,iceGatheringState:e?.iceGatheringState,signalingState:e?.signalingState,dtlsState:e?.sctp?.transport.state??f(m?.dtlsState),icePath:we(a,g),iceServerUrls:[...this.iceServerUrls],...k?{selectedPair:k}:{},candidates:r,dataChannel:{state:n?.readyState,label:n?.label,protocol:n?.protocol,ordered:n?.ordered,bufferedAmount:n?.bufferedAmount,maxMessageSize:e?.sctp?.maxMessageSize,messagesSent:h(p?.messagesSent),messagesReceived:h(p?.messagesReceived),bytesSent:h(p?.bytesSent),bytesReceived:h(p?.bytesReceived)}}}close(e=1e3,n="Client closed"){this.manuallyClosed=!0,this.reconnectTimer&&clearTimeout(this.reconnectTimer),this.reconnectTimer=void 0,this.rendezvous?.close(n),this.closeInternal()}closeInternal(){const e=this.connected;this.connected=!1,this.channel?.close(),this.peer?.close(),this.outgoing.length=0,this.queuedBytes=0,this.decoder.reset(),this.rendezvous?.close(),this.channel=void 0,this.peer=void 0,this.rendezvous=void 0,e&&this.emitConnectionChange({connected:!1,reconnected:!1})}flushOutgoing(){const e=this.channel;if(!(this.flushing||!e||!this.isConnected)){this.flushing=!0;try{for(;this.outgoing.length>0&&e.bufferedAmount<te;){const n=this.outgoing[0];e.send(n.message),this.outgoing.shift(),this.queuedBytes-=n.byteLength}}catch{this.handleDisconnected()}finally{this.flushing=!1}}}handleDisconnected(){this.manuallyClosed||!this.connected||(this.closeInternal(),this.scheduleReconnect())}scheduleReconnect(){if(this.manuallyClosed||this.reconnectTimer)return;const e=this.reconnectAttempt++;this.reconnectTimer=this.schedule(()=>{this.reconnectTimer=void 0,this.connect("webrtc").catch(()=>this.scheduleReconnect())},this.reconnectDelayMs(e))}emitConnectionChange(e){for(const n of this.connectionListeners)n(e)}}const le=new TextEncoder;function ue(t){return[...new Set(t.flatMap(e=>typeof e.urls=="string"?[e.urls]:[...e.urls]))]}function fe(t){const e=t.find(n=>n.type==="transport")?.selectedCandidatePairId;if(typeof e=="string"){const n=t.find(i=>i.id===e&&i.type==="candidate-pair");if(n)return n}return t.find(n=>n.type==="candidate-pair"&&n.selected===!0)??t.find(n=>n.type==="candidate-pair"&&n.nominated===!0&&n.state==="succeeded")}function ge(t,e){return{id:String(t.id),scope:e,address:f(t.address)??f(t.ip),port:h(t.port),protocol:f(t.protocol),candidateType:f(t.candidateType),networkType:f(t.networkType),tcpType:f(t.tcpType),relayProtocol:f(t.relayProtocol),relatedAddress:f(t.relatedAddress),relatedPort:h(t.relatedPort),url:f(t.url),priority:h(t.priority)}}function pe(t,e,n){const i=h(t.currentRoundTripTime);return{state:f(t.state),nominated:typeof t.nominated=="boolean"?t.nominated:void 0,currentRoundTripTimeMs:i===void 0?void 0:i*1e3,availableOutgoingBitrate:h(t.availableOutgoingBitrate),bytesSent:h(t.bytesSent),bytesReceived:h(t.bytesReceived),packetsSent:h(t.packetsSent),packetsReceived:h(t.packetsReceived),...e?{local:e}:{},...n?{remote:n}:{}}}function we(t,e){const n=[t?.candidateType,e?.candidateType];return n.includes("relay")?"turn-relay":n.includes("srflx")||n.includes("prflx")?"direct-stun":n.includes("host")?"direct-host":"unknown"}function f(t){return typeof t=="string"&&t.length>0?t:void 0}function h(t){return typeof t=="number"&&Number.isFinite(t)?t:void 0}function be(t){const[e]=t.split(".");if(!e)throw new Error("Connection ticket is malformed");try{const n=e.replace(/-/g,"+").replace(/_/g,"/").padEnd(Math.ceil(e.length/4)*4,"="),i=W(JSON.parse(atob(n)));if(!i)throw new Error("Connection ticket claims are invalid");return i}catch{throw new Error("Connection ticket claims are malformed")}}export{me as W};
@@ -1,5 +1,5 @@
1
- import{_ as ie}from"./index-DwbBcYUf.js";import{i as oe}from"./__vite-browser-external-DtLbhLGx.js";function ae(i){const e=[1116352408,1899447441,3049323471,3921009573,961987163,1508970993,2453635748,2870763221,3624381080,310598401,607225278,1426881987,1925078388,2162078206,2614888103,3248222580,3835390401,4022224774,264347078,604807628,770255983,1249150122,1555081692,1996064986,2554220882,2821834349,2952996808,3210313671,3336571891,3584528711,113926993,338241895,666307205,773529912,1294757372,1396182291,1695183700,1986661051,2177026350,2456956037,2730485921,2820302411,3259730800,3345764771,3516065817,3600352804,4094571909,275423344,430227734,506948616,659060556,883997877,958139571,1322822218,1537002063,1747873779,1955562222,2024104815,2227730452,2361852424,2428436474,2756734187,3204031479,3329325298],s=(S,f)=>S>>>f|S<<32-f,t=new TextEncoder().encode(i),n=t.length*8,o=new Uint8Array(Math.ceil((t.length+9)/64)*64);o.set(t),o[t.length]=128;const a=new DataView(o.buffer);a.setUint32(o.length-4,n,!1),n>4294967295&&a.setUint32(o.length-8,Math.floor(n/4294967296),!1);let[r,h,u,_,y,v,w,c]=[1779033703,3144134277,1013904242,2773480762,1359893119,2600822924,528734635,1541459225];const g=new Int32Array(64);for(let S=0;S<o.length;S+=64){for(let d=0;d<16;d++)g[d]=a.getInt32(S+d*4,!1);for(let d=16;d<64;d++){const U=s(g[d-15],7)^s(g[d-15],18)^g[d-15]>>>3,q=s(g[d-2],17)^s(g[d-2],19)^g[d-2]>>>10;g[d]=g[d-16]+U+g[d-7]+q|0}let[f,$,L,N,P,A,F,R]=[r,h,u,_,y,v,w,c];for(let d=0;d<64;d++){const U=s(P,6)^s(P,11)^s(P,25),q=P&A^~P&F,V=R+U+q+e[d]+g[d]|0,se=s(f,2)^s(f,13)^s(f,22),te=f&$^f&L^$&L,ne=se+te|0;R=F,F=A,A=P,P=N+V|0,N=L,L=$,$=f,f=V+ne|0}r=r+f|0,h=h+$|0,u=u+L|0,_=_+N|0,y=y+P|0,v=v+A|0,w=w+F|0,c=c+R|0}return[r,h,u,_,y,v,w,c].map(S=>(S>>>0).toString(16).padStart(8,"0")).join("")}async function re(i){if(typeof globalThis.crypto?.subtle?.digest=="function"){const e=new TextEncoder().encode(i),s=await globalThis.crypto.subtle.digest("SHA-256",e),t=new Uint8Array(s);return Array.from(t).map(n=>n.toString(16).padStart(2,"0")).join("")}if(typeof globalThis.process<"u"){const{createHash:e}=await ie(async()=>{const{createHash:s}=await import("./__vite-browser-external-DtLbhLGx.js").then(t=>t._);return{createHash:s}},[]);return e("sha256").update(i,"utf8").digest("hex")}return ae(i)}function ce(i,e){let s=i;for(const t of e)s=s.slice(0,t.offset)+t.insert+s.slice(t.offset+t.deleteCount);return s}async function le(i,e,s){if(s.length===0)return{data:i,hash:e};let t=i,n=e,o;for(const r of s){if(o=r,r.op==="full"){t=r.data,n=r.hash;continue}if(r.baseHash!==n)return console.warn("[jsonl-sync] Base hash mismatch, need full sync",{expected:r.baseHash,actual:n}),null;t=ce(t,r.patches),n=r.hash}const a=await re(t);if(a!==n){const r=o?.op==="full"?"[jsonl-sync] Full sync hash mismatch":"[jsonl-sync] Post-patch hash mismatch";return console.error(r,{expected:n,actual:a}),null}return{data:t,hash:n}}const he=10*6e4,de=he+9e4,Q=1;class D extends Error{constructor(e,s,t){super(`${s}: ${t}`),this.code=e}}function p(i,e,s="invalid_message"){throw new D(s,i,e)}function K(i){return typeof i=="object"&&i!==null&&!Array.isArray(i)}function m(i,e){return K(i)||p(e,"expected an object"),i}function l(i,e,s=!0){return(typeof i!="string"||!s&&i.length===0)&&p(e,s?"expected a string":"expected a non-empty string"),i}function k(i,e){return i===void 0?void 0:l(i,e)}function b(i,e){return typeof i!="boolean"&&p(e,"expected a boolean"),i}function C(i,e){return(!Number.isSafeInteger(i)||i<0)&&p(e,"expected a non-negative safe integer"),i}function M(i,e){return(typeof i!="number"||!Number.isFinite(i))&&p(e,"expected a finite number"),i}function T(i,e){return Array.isArray(i)||p(e,"expected an array"),i}function H(i,e){return T(i,e).map((s,t)=>l(s,`${e}[${t}]`))}function O(i,e){const s=m(i,e);for(const[t,n]of Object.entries(s))l(n,`${e}.${t}`);return s}function Y(i,e){const s=m(i,e);for(const[t,n]of Object.entries(s))n!=="running"&&n!=="done"&&p(`${e}.${t}`,"expected 'running' or 'done'");return s}function ue(i,e){const s=m(i,e);for(const[t,n]of Object.entries(s))H(n,`${e}.${t}`);return s}function B(i,e){return["off","minimal","low","medium","high","xhigh","max"].includes(String(i))||p(e,"expected a supported thinking level"),i}function ge(i,e){const s=m(i,e);return l(s.provider,`${e}.provider`,!1),l(s.modelId,`${e}.modelId`,!1),s}function W(i,e){const s=m(i,e);l(s.provider,`${e}.provider`,!1);const t=k(s.id,`${e}.id`),n=k(s.modelId,`${e}.modelId`);return!t&&!n&&p(e,"expected id or modelId"),s}function pe(i,e){if(i.protocolVersion!==Q){const s=i.protocolVersion===void 0?"missing":JSON.stringify(i.protocolVersion);p(`${e}.protocolVersion`,`unsupported protocol version ${s}; expected ${Q}`,"unsupported_version")}}function X(i){try{return{ok:!0,value:JSON.parse(i)}}catch(e){return{ok:!1,error:{code:"invalid_json",message:`Invalid JSON: ${e instanceof Error?e.message:String(e)}`,requestId:null,command:"parse"}}}}function Z(i,e,s){const t=K(i)?i:void 0,n=typeof t?.id=="string"?t.id:null,o=typeof t?.type=="string"?t.type:s,a=e instanceof D?e:new D("invalid_message","$",e instanceof Error?e.message:String(e));return{ok:!1,error:{code:a.code,message:a.message,requestId:n,command:o}}}function me(i,e){T(i,e).forEach((s,t)=>{const n=m(s,`${e}[${t}]`);l(n.name,`${e}[${t}].name`,!1),k(n.description,`${e}[${t}].description`),["extension","prompt","skill"].includes(String(n.source))||p(`${e}[${t}].source`,"expected extension, prompt, or skill"),n.sourceInfo!==void 0&&m(n.sourceInfo,`${e}[${t}].sourceInfo`),k(n.location,`${e}[${t}].location`),k(n.path,`${e}[${t}].path`)})}function fe(i,e){const s=m(i,e);l(s.sessionFile,`${e}.sessionFile`,!1),l(s.sessionId,`${e}.sessionId`,!1);for(const n of["userMessages","assistantMessages","toolCalls","toolResults","totalMessages"])C(s[n],`${e}.${n}`);const t=m(s.tokens,`${e}.tokens`);for(const n of["input","output","cacheRead","cacheWrite","total"])C(t[n],`${e}.tokens.${n}`);if(M(s.cost,`${e}.cost`)<0&&p(`${e}.cost`,"expected a non-negative number"),s.contextUsage!==void 0){const n=m(s.contextUsage,`${e}.contextUsage`);n.tokens!==null&&C(n.tokens,`${e}.contextUsage.tokens`),C(n.contextWindow,`${e}.contextUsage.contextWindow`),n.percent!==null&&M(n.percent,`${e}.contextUsage.percent`)<0&&p(`${e}.contextUsage.percent`,"expected a non-negative number or null")}}function _e(i,e,s){const t=m(e,s);switch(i){case"install_pi":case"subscribe_session":case"steer":case"remove_steering":case"abort":case"set_session_name":break;case"prompt":case"fork_prompt":l(t.newSessionPath,`${s}.newSessionPath`,!1);break;case"hard_kill":b(t.killed,`${s}.killed`),t.reason!==void 0&&t.reason!=="signal_failed"&&t.reason!=="not_attached"&&p(`${s}.reason`,"expected signal_failed or not_attached");break;case"compact":break;case"get_available_models":T(t.models,`${s}.models`).forEach((n,o)=>W(n,`${s}.models[${o}]`));break;case"get_default_model":t.model!==null&&W(t.model,`${s}.model`),B(t.thinkingLevel,`${s}.thinkingLevel`);break;case"get_session_statuses":Y(t.statuses,`${s}.statuses`);break;case"get_session_stats":fe(t,s);break;case"fork":l(t.text,`${s}.text`),b(t.cancelled,`${s}.cancelled`),t.newSessionPath!==null&&l(t.newSessionPath,`${s}.newSessionPath`,!1);break;case"get_commands":me(t.commands,`${s}.commands`);break;case"reload_processes":C(t.killed,`${s}.killed`),C(t.draining,`${s}.draining`);break}}const Se=new Set(["install_pi","subscribe_session","prompt","steer","remove_steering","abort","hard_kill","compact","get_available_models","get_default_model","get_session_statuses","get_session_stats","fork","fork_prompt","set_session_name","get_commands","reload_processes"]);function ye(i,e){const s=l(i,e,!1);return Se.has(s)||p(e,`unknown response command '${s}'`,"unknown_message"),s}function ve(i,e){T(i,e).forEach((s,t)=>{const n=m(s,`${e}[${t}]`);C(n.offset,`${e}[${t}].offset`),C(n.deleteCount,`${e}[${t}].deleteCount`),l(n.insert,`${e}[${t}].insert`)})}function we(i){const e=X(i);if(!e.ok)return e;const s=e.value;try{const t=m(s,"$message");pe(t,"$message");const n=l(t.type,"$message.type",!1);switch(n){case"response":{if(b(t.success,"$message.success")){l(t.id,"$message.id",!1);const a=ye(t.command,"$message.command");_e(a,t.data,"$message.data")}else t.id!==null&&l(t.id,"$message.id",!1),l(t.command,"$message.command",!1),l(t.error,"$message.error",!1),["invalid_json","invalid_message","unsupported_version","unknown_command","unknown_message","command_failed"].includes(String(t.code))||p("$message.code","expected a protocol error code");break}case"init":Y(t.sessionStatuses,"$message.sessionStatuses"),ue(t.steeringQueues,"$message.steeringQueues"),O(t.providerUsageStatuses,"$message.providerUsageStatuses");break;case"pi_install_required":l(t.command,"$message.command",!1),b(t.installable,"$message.installable"),b(t.installing,"$message.installing"),l(t.message,"$message.message",!1);break;case"session_status_change":l(t.sessionPath,"$message.sessionPath",!1),t.status!=="running"&&t.status!=="done"&&p("$message.status","expected running or done");break;case"provider_usage":O(t.statuses,"$message.statuses");break;case"extension_status":l(t.sessionPath,"$message.sessionPath",!1),O(t.statuses,"$message.statuses");break;case"session_sync":l(t.sessionPath,"$message.sessionPath",!1),C(t.revision,"$message.revision"),l(t.hash,"$message.hash",!1),t.op==="full"?l(t.data,"$message.data"):t.op==="delta"?(l(t.baseHash,"$message.baseHash",!1),ve(t.patches,"$message.patches")):p("$message.op","expected full or delta");break;case"control_state":l(t.sessionPath,"$message.sessionPath",!1),t.controlRevision!==void 0&&C(t.controlRevision,"$message.controlRevision"),W(t.model,"$message.model"),B(t.thinkingLevel,"$message.thinkingLevel");break;case"session_attached":l(t.sessionPath,"$message.sessionPath",!1),k(t.cwd,"$message.cwd"),k(t.firstMessage,"$message.firstMessage");break;case"sessions_changed":l(t.file,"$message.file");break;default:p("$message.type",`unknown server message '${n}'`,"unknown_message")}return{ok:!0,value:t}}catch(t){return Z(s,t,"unknown")}}function Ce(i){const e=X(i);if(!e.ok)return e;const s=e.value;try{const t=m(s,"$session");T(t.messages,"$session.messages").forEach((o,a)=>{const r=m(o,`$session.messages[${a}]`);l(r.role,`$session.messages[${a}].role`,!1)}),b(t.isStreaming,"$session.isStreaming"),H(t.pendingToolCalls,"$session.pendingToolCalls");const n=m(t.toolCallTimings,"$session.toolCallTimings");for(const[o,a]of Object.entries(n)){const r=m(a,`$session.toolCallTimings.${o}`);M(r.startedAt,`$session.toolCallTimings.${o}.startedAt`),r.completedAt!==void 0&&M(r.completedAt,`$session.toolCallTimings.${o}.completedAt`)}return t.model!==null&&ge(t.model,"$session.model"),B(t.thinkingLevel,"$session.thinkingLevel"),H(t.steeringQueue,"$session.steeringQueue"),k(t.error,"$session.error"),{ok:!0,value:t}}catch(t){return Z(s,t,"session_sync")}}function $e(i,e){return JSON.stringify({protocolVersion:Q,id:e,...i})}function Pe(i){throw new Error(`Unhandled protocol variant: ${JSON.stringify(i)}`)}const x=["off","minimal","low","medium","high","xhigh","max"];function E(i){return i?.id??i?.modelId}function j(i,e){const s=E(i),t=E(e);return!!i&&!!e&&!!s&&!!t&&i.provider===e.provider&&s===t}function J(i){const e=E(i);if(!(!i.provider||!e))return{provider:i.provider,modelId:e}}function ke(i){if(!i)return!1;if(typeof i.reasoning=="boolean")return i.reasoning;if(i.thinkingLevelMap)return!0;const e=String(i.provider??"").toLowerCase(),s=String(E(i)??"").toLowerCase();return!!(e==="openai-codex"||e==="openai"&&s.startsWith("gpt-5"))}function be(i){if(!ke(i))return["off"];const e=i?.thinkingLevelMap;return x.filter(s=>{const t=e?.[s];return t===null?!1:s==="xhigh"||s==="max"?t!==void 0:!0})}function G(i,e){const s=be(i);if(s.includes(e))return e;const t=x.indexOf(e);if(t===-1)return s[0]??"off";for(let n=t;n<x.length;n++){const o=x[n];if(s.includes(o))return o}for(let n=t-1;n>=0;n--){const o=x[n];if(s.includes(o))return o}return s[0]??"off"}class Le extends Error{constructor(e,s,t){super(e),this.status=s,this.details=t,this.name="BackendApiError"}}class xe{constructor(e={}){this.fetch=e.fetch??globalThis.fetch.bind(globalThis)}listSessions(){return this.requestJson("/api/sessions")}async deleteSession(e){await this.requestJson("/api/sessions",{method:"DELETE",headers:{"Content-Type":"application/json"},body:JSON.stringify({path:e})})}async listForkMessages(e){return(await this.requestJson(`/api/sessions/fork-messages?path=${encodeURIComponent(e)}`)).messages??[]}browseDirectory(e){return this.requestJson(`/api/browse?path=${encodeURIComponent(e)}`)}createDirectory(e,s){return this.requestJson("/api/directories",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({parentPath:e,name:s})})}async getRawSession(e){const s=await this.fetch(`/api/sessions/raw?path=${encodeURIComponent(e)}`);if(!s.ok)throw await this.toError(s,"Failed to load raw session");return s.text()}getFileContent(e,s){const t=new URLSearchParams({sessionPath:e,path:s});return this.requestJson(`/api/files/content?${t}`,{cache:"no-store"})}createFileUpload(e){return this.requestJson("/api/files/uploads",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(e)})}appendFileUpload(e){return this.requestJson(`/api/files/uploads/${encodeURIComponent(e.uploadId)}/chunks`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({offset:e.offset,data:e.data})})}completeFileUpload(e){return this.requestJson(`/api/files/uploads/${encodeURIComponent(e)}/complete`,{method:"POST"})}getLocalSettings(){return this.requestJson("/api/settings/local")}validateLocalSettings(e){return this.requestValidation("/api/settings/local/validate",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({content:e})})}patchLocalSettings(e){return this.requestValidation("/api/settings/local",{method:"PATCH",headers:{"Content-Type":"application/json"},body:JSON.stringify(e)})}saveLocalSettings(e){return this.requestValidation("/api/settings/local",{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({content:e})})}getUpdates(){return this.requestJson("/api/updates",{cache:"no-store"})}runUpdate(e){return this.requestJson(`/api/updates/${e}`,{method:"POST",headers:{"X-Pipane-Action":"update"}})}async requestValidation(e,s){const t=await this.fetch(e,s),n=await this.readJson(t);if(!t.ok&&typeof n?.valid!="boolean")throw this.errorFromPayload(t,n,"Settings request failed");return n}async requestJson(e,s){const t=s?await this.fetch(e,s):await this.fetch(e),n=await this.readJson(t);if(!t.ok)throw this.errorFromPayload(t,n,`Backend request failed (${t.status})`);return n}async readJson(e){return e.json().catch(()=>({}))}async toError(e,s){const t=await this.readJson(e);return this.errorFromPayload(e,t,s)}errorFromPayload(e,s,t){const n=Array.isArray(s?.errors)?s.errors.filter(a=>typeof a=="string"):[],o=typeof s?.error=="string"?s.error:n.length>0?n.join(`
2
- `):t;return new Le(o,e.status,s)}}const ee=[{name:"help",description:"Show this help",source:"builtin"},{name:"new",description:"Start a new session",source:"builtin"},{name:"session",description:"Show session file, messages, tokens, and cost",source:"builtin"},{name:"fork",description:"Fork session from a previous message",source:"builtin"},{name:"compact",description:"Compact conversation history",source:"builtin",argumentHint:"[instructions]",acceptsArguments:!0},{name:"name",description:"Set session display name",source:"builtin",argumentHint:"<name>",acceptsArguments:!0},{name:"reload",description:"Restart all pooled pi RPC processes",source:"builtin"}];function Re(i){const e=ee.map(t=>({...t})),s=new Set(e.map(t=>t.name.toLocaleLowerCase()));for(const t of i){const n=t.name.toLocaleLowerCase();s.has(n)||(s.add(n),e.push({name:t.name,description:t.description??"",source:t.source,acceptsArguments:!0}))}return e}function Te(i,e){const s=i.toLocaleLowerCase(),t=e.toLocaleLowerCase();if(!t)return 0;if(s===t)return 1e5;let n=s.startsWith(t)?5e4:0,o=0,a=-2,r=-1;for(const h of t){const u=s.indexOf(h,o);if(u===-1)return;r===-1&&(r=u);const _=u>0?s[u-1]:"";u===0||_==="-"||_===":"||_==="_"?n+=18:n+=4,u===a+1&&(n+=12),a=u,o=u+1}return n-=r*3,n-=s.length-t.length,n}function Ue(i,e){return i.map((s,t)=>({command:s,index:t,score:Te(s.name,e)})).filter(s=>s.score!==void 0).sort((s,t)=>t.score-s.score||s.index-t.index).map(s=>s.command)}function qe(i){switch(i){case"builtin":return"Built-in";case"extension":return"Extension";case"prompt":return"Prompt";case"skill":return"Skill"}}const z=1,Ae=1e4;class Fe{constructor(e={}){this.socket=null,this.reconnecting=!1,this.reconnectAttempt=0,this.manuallyClosed=!1,this.frameListeners=new Set,this.connectionListeners=new Set,this.createWebSocket=e.createWebSocket??(s=>new WebSocket(s)),this.schedule=e.schedule??globalThis.setTimeout.bind(globalThis),this.cancelSchedule=e.cancelSchedule??globalThis.clearTimeout.bind(globalThis),e.socket&&(this.socket=e.socket,this.attachFrameHandler(e.socket))}get isConnected(){return this.socket?.readyState===z}get isReconnecting(){return this.reconnecting}onFrame(e){return this.frameListeners.add(e),()=>this.frameListeners.delete(e)}onConnectionChange(e){return this.connectionListeners.add(e),()=>this.connectionListeners.delete(e)}async connect(e){this.isConnected||(this.endpoint=e,this.manuallyClosed=!1,await this.openSocket(!1))}send(e){if(!this.socket||this.socket.readyState!==z)throw new Error("Backend transport is not connected");this.socket.send(e)}close(e=1e3,s="Client closed"){this.manuallyClosed=!0,this.reconnecting=!1,this.reconnectTimer&&(this.cancelSchedule(this.reconnectTimer),this.reconnectTimer=void 0),this.socket?.close(e,s)}attachFrameHandler(e){e.onmessage=s=>{const t=String(s.data);for(const n of this.frameListeners)n(t)}}emitConnectionChange(e){for(const s of this.connectionListeners)s(e)}openSocket(e){const s=this.endpoint;return s?new Promise((t,n)=>{const o=this.createWebSocket(s);let a=!1,r=!1;this.socket=o,this.attachFrameHandler(o),o.onopen=()=>{this.socket===o&&(a=!0,r=!0,this.reconnecting=!1,this.reconnectAttempt=0,this.emitConnectionChange({connected:!0,reconnected:e}),t())},o.onerror=()=>{!e&&!r&&(r=!0,n(new Error("WebSocket connection failed")))},o.onclose=()=>{if(this.socket===o&&(this.socket=null),r||(r=!0,n(new Error("WebSocket closed before connecting"))),this.manuallyClosed){a&&this.emitConnectionChange({connected:!1,reconnected:!1});return}(a||e)&&(this.scheduleReconnect(),a&&this.emitConnectionChange({connected:!1,reconnected:!1}))}}):Promise.reject(new Error("Backend transport endpoint is missing"))}scheduleReconnect(){if(this.reconnectTimer||this.manuallyClosed)return;this.reconnecting=!0,this.reconnectAttempt++;const e=Math.min(500*2**(this.reconnectAttempt-1),Ae);this.reconnectTimer=this.schedule(()=>{this.reconnectTimer=void 0,this.openSocket(!0).catch(()=>{})},e)}}const Ie="provider-usage";function Me(i){const e=typeof i?.provider=="string"?i.provider.toLowerCase():"";if(e.includes("codex"))return"codex";if(e.includes("anthropic"))return"anthropic"}class Oe{constructor(e={}){this.sessionsChangedListeners=new Set,this.piInstallRequiredListeners=new Set,this.pendingRequests=new Map,this.requestId=0,this._connectionListeners=new Set,this._state={model:void 0,thinkingLevel:"off",messages:[],isStreaming:!1,error:void 0},this._pendingToolCallIds=new Set,this._toolCallTimings={},this._steeringQueues=new Map,this._steeringQueueListeners=new Set,this._extensionStatuses=new Map,this._providerUsageStatuses=new Map,this._extensionStatusListeners=new Set,this._controlRevision=0,this._availableModels=null,this._sessionId="",this._sessionStatus="virtual",this._localAssistantMessages=[],this._optimisticSessions=new Map,this._globalSessionStatus=new Map,this._globalStatusListeners=new Set,this._sessionNonce=0,this._pendingNewPrompt=!1,this._startingPrompts=new Map,this._syncJson="",this._syncHash="",this._pendingSessionSyncs=[],this._sessionSyncFlushScheduled=!1,this._sessionSyncFlushInProgress=!1,this._sessionListeners=new Set,this._contentListeners=new Set,this._statusListeners=new Set,this.transport=e.transport??new Fe({socket:e.socket,createWebSocket:e.createWebSocket}),this.api=e.api??new xe({fetch:e.fetch}),this.requestFrame=e.requestFrame??(s=>requestAnimationFrame(s)),this.transport.onFrame(s=>this.handleMessage(s)),this.transport.onConnectionChange(s=>this.handleTransportConnectionChange(s.connected,s.reconnected))}get state(){return this._state}get sessionId(){return this._sessionId}get sessionFile(){return this._sessionPath}get sessionName(){return this._sessionName}get sessionStatus(){return this._sessionStatus}get isConnected(){return this.transport.isConnected}get isReconnecting(){return this.transport.isReconnecting}onConnectionChange(e){return this._connectionListeners.add(e),()=>this._connectionListeners.delete(e)}emitConnectionChange(e){for(const s of this._connectionListeners)s(e)}get pendingToolCallIds(){return this._pendingToolCallIds}get toolCallTimings(){return this._toolCallTimings}get steeringQueue(){return this._sessionPath?this._steeringQueues.get(this._sessionPath)??[]:[]}get extensionStatuses(){const e=new Map(this._extensionStatuses),s=Me(this._state.model),t=s?this._providerUsageStatuses.get(s):this._providerUsageStatuses.size===1?this._providerUsageStatuses.values().next().value:void 0;return t&&e.set(Ie,t),e}onExtensionStatusChange(e){return this._extensionStatusListeners.add(e),()=>this._extensionStatusListeners.delete(e)}replaceExtensionStatuses(e){const s=new Map;if(e&&typeof e=="object"&&!Array.isArray(e))for(const[t,n]of Object.entries(e))typeof n=="string"&&s.set(t,n);if(!(s.size===this._extensionStatuses.size&&[...s].every(([t,n])=>this._extensionStatuses.get(t)===n))){this._extensionStatuses=s;for(const t of this._extensionStatusListeners)t()}}replaceProviderUsageStatuses(e){const s=new Map;if(e&&typeof e=="object"&&!Array.isArray(e))for(const[t,n]of Object.entries(e))typeof n=="string"&&s.set(t,n);if(!(s.size===this._providerUsageStatuses.size&&[...s].every(([t,n])=>this._providerUsageStatuses.get(t)===n))){this._providerUsageStatuses=s;for(const t of this._extensionStatusListeners)t()}}clearExtensionStatuses(){this.replaceExtensionStatuses({})}getSessionStatus(e){return this._globalSessionStatus.get(e)}onGlobalStatusChange(e){return this._globalStatusListeners.add(e),()=>this._globalStatusListeners.delete(e)}emitGlobalStatusChange(){for(const e of this._globalStatusListeners)e()}setGlobalSessionStatus(e,s){this._globalSessionStatus.set(e,s),s==="running"&&this._startingPrompts.get(e)?.resolveReady(e),this.emitGlobalStatusChange()}setAllSessionStatuses(e){this._globalSessionStatus.clear();for(const[s,t]of Object.entries(e))this._globalSessionStatus.set(s,t);this.emitGlobalStatusChange()}onSteeringQueueChange(e){return this._steeringQueueListeners.add(e),()=>this._steeringQueueListeners.delete(e)}emitSteeringQueueChange(){for(const e of this._steeringQueueListeners)e()}enqueueSteering(e,s){const t=this._steeringQueues.get(e)??[];this._steeringQueues.set(e,[...t,s]),this.emitSteeringQueueChange()}onSessionChange(e){return this._sessionListeners.add(e),()=>this._sessionListeners.delete(e)}emitSessionChange(){for(const e of this._sessionListeners)e()}onContentChange(e){return this._contentListeners.add(e),()=>this._contentListeners.delete(e)}emitContentChange(){for(const e of this._contentListeners)e()}onStatusChange(e){return this._statusListeners.add(e),()=>this._statusListeners.delete(e)}emitStatusChange(){for(const e of this._statusListeners)e()}onSessionsChanged(e){return this.sessionsChangedListeners.add(e),()=>this.sessionsChangedListeners.delete(e)}onPiInstallRequired(e){return this.piInstallRequiredListeners.add(e),()=>this.piInstallRequiredListeners.delete(e)}emitPiInstallRequired(e){for(const s of this.piInstallRequiredListeners)s(e)}toErrorMessage(e){if(e instanceof Error)return e.message;if(typeof e=="string")return e;try{return JSON.stringify(e)}catch{return String(e)}}reportError(e,s="Request failed"){const t=this.toErrorMessage(e);this._state.error=t,this._state.messages=[...this._state.messages,{role:"assistant",content:[{type:"text",text:`⚠️ ${s}: ${t}`}],timestamp:Date.now()}],this.emitStatusChange(),this.emitContentChange()}async connect(e){await this.transport.connect(e),document.addEventListener("visibilitychange",()=>{document.visibilityState==="visible"&&this.syncStateOnFocus()})}disconnect(){this.transport.close()}handleTransportConnectionChange(e,s){e?s?this.onReconnected():this._sessionStatus="virtual":this.handleTransportDisconnected(),this.emitConnectionChange(e)}handleTransportDisconnected(){for(const e of this.pendingRequests.values())e.reject(new Error("Backend transport disconnected"));this.pendingRequests.clear(),this._pendingControl?.phase==="sent"?this.rollbackSentControl(this._pendingControl.revision):this._pendingControl?.phase==="acknowledged"&&(this._pendingControl=void 0)}async onReconnected(){this._sessionPath&&this._sessionStatus!=="virtual"&&(this._awaitingFullSync=void 0,this.requestFullSessionSync(this._sessionPath)),this.refreshSessionStatuses()}async syncStateOnFocus(){this.refreshSessionStatuses(),this._sessionPath&&this._sessionStatus!=="virtual"&&this.subscribeToSession(this._sessionPath),this._sessionStatus==="detached"&&this._state.isStreaming&&(console.log("[ws-adapter] Tab regained focus: clearing stale streaming state"),this._state.isStreaming=!1,this._pendingToolCallIds.clear(),this.emitStatusChange())}async refreshSessionStatuses(){try{const e=await this.send({type:"get_session_statuses"});e?.statuses&&this.setAllSessionStatuses(e.statuses)}catch(e){console.error("Failed to refresh session statuses:",e)}}async subscribeToSession(e){try{await this.send({type:"subscribe_session",sessionPath:e??""})}catch(s){console.error("Failed to subscribe to session:",s)}}async requestFullSessionSync(e){const s={sessionPath:e,sessionNonce:this._sessionNonce};this._awaitingFullSync?.sessionPath===s.sessionPath&&this._awaitingFullSync.sessionNonce===s.sessionNonce||(this._awaitingFullSync=s,this._syncJson="",this._syncHash="",this._syncRevision=void 0,this.clearSessionSyncQueue(),await this.subscribeToSession(e))}handleProtocolError(e){const s=`Protocol error: ${e.message}`;console.error(`[ws-adapter] ${s}`),this._state.error=s,this.emitStatusChange(),e.code==="unsupported_version"&&this.transport.close(1002,"Unsupported protocol version")}handleMessage(e){if(oe(e))return;const s=we(e);if(!s.ok){this.handleProtocolError(s.error);return}const t=s.value;switch(t.type){case"response":{if(!t.success&&t.id===null){this._state.error=t.error,this.emitStatusChange();return}if(t.id===null)return;const n=this.pendingRequests.get(t.id);if(!n)return;if(this.pendingRequests.delete(t.id),t.command!==n.command){n.reject(new Error(`Mismatched response for ${n.command}: received ${t.command}`));return}t.success?n.resolve(t.data):(this._state.error=t.error,this.emitStatusChange(),n.reject(new Error(t.error)));return}case"pi_install_required":this.emitPiInstallRequired({command:t.command,installable:t.installable,installing:t.installing,message:t.message});return;case"init":this.setAllSessionStatuses(t.sessionStatuses),this.replaceProviderUsageStatuses(t.providerUsageStatuses),this._steeringQueues.clear();for(const[n,o]of Object.entries(t.steeringQueues))o.length>0&&this._steeringQueues.set(n,[...o]);this.emitSteeringQueueChange();return;case"session_status_change":this.setGlobalSessionStatus(t.sessionPath,t.status);return;case"provider_usage":this.replaceProviderUsageStatuses(t.statuses);return;case"extension_status":t.sessionPath===this._sessionPath&&this.replaceExtensionStatuses(t.statuses);return;case"session_sync":if(t.sessionPath!==this._sessionPath)return;this.enqueueSessionSync({...t,__sessionPath:t.sessionPath,__sessionNonce:this._sessionNonce});return;case"control_state":{if(t.sessionPath!==this._sessionPath)return;this.applyAuthoritativeControlState(t.model,t.thinkingLevel,t.controlRevision)&&this.emitContentChange();return}case"session_attached":{this.setGlobalSessionStatus(t.sessionPath,"running"),t.cwd&&(this._pendingCwd=t.cwd);const n=t.sessionPath===this._sessionPath||this._sessionStatus==="virtual"&&this._pendingNewPrompt;if(n){if(this._sessionStatus==="virtual"){this._sessionPath=t.sessionPath;const a=I.basename(t.sessionPath,".jsonl"),r=a.split("_");this._sessionId=r.length>1?r.slice(1).join("_"):a,this._startingPrompts.get(`virtual:${this._sessionNonce}`)?.resolveReady(t.sessionPath)}this._sessionStatus="attached",this._state.isStreaming=!0,this.requestFullSessionSync(t.sessionPath),this.emitStatusChange()}if(!this._optimisticSessions.has(t.sessionPath)){const o=new Date().toISOString(),a=I.basename(t.sessionPath,".jsonl"),r=a.split("_"),h=r.length>1?r.slice(1).join("_"):a;this._optimisticSessions.set(t.sessionPath,{id:h,path:t.sessionPath,cwd:t.cwd??"",created:o,modified:o,lastUserPromptTime:o,messageCount:1,firstMessage:t.firstMessage??"(new session)"})}n&&this.emitSessionChange();return}case"sessions_changed":for(const n of this.sessionsChangedListeners)n(t.file);return;default:Pe(t)}}enqueueSessionSync(e){const s=this._awaitingFullSync!==void 0&&this._awaitingFullSync.sessionPath===e.__sessionPath&&this._awaitingFullSync.sessionNonce===e.__sessionNonce;if(e.op==="full")s&&(this._awaitingFullSync=void 0),this._pendingSessionSyncs=[e];else{if(s)return;this._pendingSessionSyncs.push(e)}this._sessionSyncFlushScheduled||this._sessionSyncFlushInProgress||(this._sessionSyncFlushScheduled=!0,this.requestFrame(()=>{this._sessionSyncFlushScheduled=!1,this.flushSessionSyncQueue()}))}clearSessionSyncQueue(){this._pendingSessionSyncs=[],this._sessionSyncFlushScheduled=!1}async flushSessionSyncQueue(){if(this._sessionSyncFlushInProgress)return;this._sessionSyncFlushInProgress=!0;const e=this._pendingSessionSyncs.splice(0);try{const s=await this.applySessionSyncBatch(e);s&&this.emitSessionSyncChanges(s)}finally{this._sessionSyncFlushInProgress=!1,this._pendingSessionSyncs.length>0&&!this._sessionSyncFlushScheduled&&(this._sessionSyncFlushScheduled=!0,this.requestFrame(()=>{this._sessionSyncFlushScheduled=!1,this.flushSessionSyncQueue()}))}}emitSessionSyncChanges(e){e.steering&&this.emitSteeringQueueChange(),e.content&&this.emitContentChange(),e.status&&this.emitStatusChange()}async applySessionSyncBatch(e){const s=this._sessionPath,t=this._sessionNonce;if(!s||e.length===0||e.some(c=>c.__sessionPath!==s||c.__sessionNonce!==t))return;let n=this._syncRevision;for(const c of e){if(c.op==="full"){n=c.revision;continue}if(n===void 0||c.revision!==n+1){console.error("[ws-adapter] Session revision gap, re-subscribing",{expected:n===void 0?"full snapshot":n+1,actual:c.revision}),this.requestFullSessionSync(s);return}n=c.revision}const o=e.map(c=>c.op==="full"?{op:"full",data:c.data,hash:c.hash}:{op:"delta",patches:c.patches,hash:c.hash,baseHash:c.baseHash});if(o[0]?.op==="delta"&&!this._syncHash){console.warn("[ws-adapter] Ignoring delta while awaiting full sync");return}const a=await le(this._syncJson,this._syncHash,o);if(s!==this._sessionPath||t!==this._sessionNonce)return;if(!a){console.error("[ws-adapter] Sync verification failed, re-subscribing"),this.requestFullSessionSync(s);return}const r=Ce(a.data);if(!r.ok){this.handleProtocolError(r.error);return}const h=r.value;this._syncJson=a.data,this._syncHash=a.hash,this._syncRevision=n;const u=this._state.isStreaming,_=this._sessionStatus,y=this._state.error,v=[...this._steeringQueues.get(s)??[]];this._state.messages=[...h.messages??[],...this._localAssistantMessages],this._state.isStreaming=h.isStreaming??!1,this._sessionStatus!=="virtual"&&(this._sessionStatus=this._state.isStreaming?"attached":"detached"),this._pendingToolCallIds=new Set(h.pendingToolCalls??[]),this._toolCallTimings=h.toolCallTimings??{},this.applyAuthoritativeControlState(h.model,h.thinkingLevel),Array.isArray(h.steeringQueue)&&(h.steeringQueue.length>0?this._steeringQueues.set(s,[...h.steeringQueue]):this._steeringQueues.delete(s)),this._state.error=h.error||void 0;const w=this._steeringQueues.get(s)??[];return{content:!0,status:u!==this._state.isStreaming||_!==this._sessionStatus||y!==this._state.error,steering:v.length!==w.length||v.some((c,g)=>c!==w[g])}}send(e){if(!this.transport.isConnected)return Promise.reject(new Error("Backend transport is not connected"));const s=`req_${++this.requestId}`;return new Promise((t,n)=>{const o=e.type==="compact"?de:e.type==="prompt"||e.type==="fork_prompt"?9e4:3e4,a=setTimeout(()=>{this.pendingRequests.delete(s),n(new Error(`Timeout waiting for response to ${e.type}`))},o);this.pendingRequests.set(s,{command:e.type,resolve:r=>{clearTimeout(a),t(r)},reject:r=>{clearTimeout(a),n(r)}}),this.transport.send($e(e,s))})}async fetchAvailableModels(){const s=(await this.send({type:"get_available_models"}))?.models??[];this._availableModels=s;const t=this.resolveModel(this._state.model);return t&&(this._state.model=t),s}resolveModel(e){if(!e)return;const s=this._availableModels?.find(n=>j(n,e));if(s)return s;const t=J(e);if(t)return{...e,provider:t.provider,id:t.modelId}}markControlPending(){const e=++this._controlRevision;return this._pendingControl={revision:e,sessionNonce:this._sessionNonce,phase:"local"},e}markControlSent(e){this._pendingControl?.revision===e&&this._pendingControl.sessionNonce===this._sessionNonce&&(this._pendingControl.phase="sent")}rollbackSentControl(e){const s=this._pendingControl;if(!s||s.revision!==e||s.phase!=="sent")return;this._pendingControl=void 0;const t=this._lastAuthoritativeControl;t?.sessionNonce===this._sessionNonce&&(this._state.model=t.model,this._state.thinkingLevel=t.thinkingLevel,this.emitContentChange())}rememberAuthoritativeControl(e,s){this._lastAuthoritativeControl={sessionNonce:this._sessionNonce,model:e,thinkingLevel:s}}applyAuthoritativeControlState(e,s,t){const n=this.resolveModel(e),o=s,a=this._pendingControl;if(t!==void 0){if(a){if(a.sessionNonce!==this._sessionNonce||a.revision!==t)return!1;a.phase="acknowledged",a.model=n,a.thinkingLevel=o}return n&&(this._state.model=n),o&&(this._state.thinkingLevel=o),n&&o&&this.rememberAuthoritativeControl(n,o),!!n||!!o}if(a){if(!(a.phase==="acknowledged"&&!!a.model&&j(e,a.model)&&o===a.thinkingLevel))return!1;this._pendingControl=void 0}return n&&(this._state.model=n),o&&(this._state.thinkingLevel=o),n&&o&&this.rememberAuthoritativeControl(n,o),!!n||!!o}async installPi(){await this.send({type:"install_pi"})}async loadDefaultModel(){if(this._state.model)return;const e=await this.send({type:"get_default_model"});this.applyAuthoritativeControlState(e?.model,e?.thinkingLevel)}setCwd(e){this._pendingCwd=e}get cwd(){return this._pendingCwd}createStartingPrompt(e){let s,t;const n={ready:new Promise(o=>{s=o}),resolveReady:o=>s(o),finished:new Promise(o=>{t=o}),resolveFinished:()=>t()};return this._startingPrompts.set(e,n),n}finishStartingPrompt(e,s,t){s.resolveReady(t),s.resolveFinished(),this._startingPrompts.get(e)===s&&this._startingPrompts.delete(e)}async prompt(e,s){let t;if(typeof e=="string"?t=e:Array.isArray(e)?t=e.map(c=>this.extractText(c)).join(`
1
+ import{_ as ie}from"./index-C7_1ODks.js";import{i as oe}from"./__vite-browser-external-DtLbhLGx.js";function ae(i){const e=[1116352408,1899447441,3049323471,3921009573,961987163,1508970993,2453635748,2870763221,3624381080,310598401,607225278,1426881987,1925078388,2162078206,2614888103,3248222580,3835390401,4022224774,264347078,604807628,770255983,1249150122,1555081692,1996064986,2554220882,2821834349,2952996808,3210313671,3336571891,3584528711,113926993,338241895,666307205,773529912,1294757372,1396182291,1695183700,1986661051,2177026350,2456956037,2730485921,2820302411,3259730800,3345764771,3516065817,3600352804,4094571909,275423344,430227734,506948616,659060556,883997877,958139571,1322822218,1537002063,1747873779,1955562222,2024104815,2227730452,2361852424,2428436474,2756734187,3204031479,3329325298],s=(S,f)=>S>>>f|S<<32-f,t=new TextEncoder().encode(i),n=t.length*8,o=new Uint8Array(Math.ceil((t.length+9)/64)*64);o.set(t),o[t.length]=128;const a=new DataView(o.buffer);a.setUint32(o.length-4,n,!1),n>4294967295&&a.setUint32(o.length-8,Math.floor(n/4294967296),!1);let[r,h,u,_,y,v,w,c]=[1779033703,3144134277,1013904242,2773480762,1359893119,2600822924,528734635,1541459225];const g=new Int32Array(64);for(let S=0;S<o.length;S+=64){for(let d=0;d<16;d++)g[d]=a.getInt32(S+d*4,!1);for(let d=16;d<64;d++){const U=s(g[d-15],7)^s(g[d-15],18)^g[d-15]>>>3,q=s(g[d-2],17)^s(g[d-2],19)^g[d-2]>>>10;g[d]=g[d-16]+U+g[d-7]+q|0}let[f,$,L,N,P,A,F,R]=[r,h,u,_,y,v,w,c];for(let d=0;d<64;d++){const U=s(P,6)^s(P,11)^s(P,25),q=P&A^~P&F,V=R+U+q+e[d]+g[d]|0,se=s(f,2)^s(f,13)^s(f,22),te=f&$^f&L^$&L,ne=se+te|0;R=F,F=A,A=P,P=N+V|0,N=L,L=$,$=f,f=V+ne|0}r=r+f|0,h=h+$|0,u=u+L|0,_=_+N|0,y=y+P|0,v=v+A|0,w=w+F|0,c=c+R|0}return[r,h,u,_,y,v,w,c].map(S=>(S>>>0).toString(16).padStart(8,"0")).join("")}async function re(i){if(typeof globalThis.crypto?.subtle?.digest=="function"){const e=new TextEncoder().encode(i),s=await globalThis.crypto.subtle.digest("SHA-256",e),t=new Uint8Array(s);return Array.from(t).map(n=>n.toString(16).padStart(2,"0")).join("")}if(typeof globalThis.process<"u"){const{createHash:e}=await ie(async()=>{const{createHash:s}=await import("./__vite-browser-external-DtLbhLGx.js").then(t=>t._);return{createHash:s}},[]);return e("sha256").update(i,"utf8").digest("hex")}return ae(i)}function ce(i,e){let s=i;for(const t of e)s=s.slice(0,t.offset)+t.insert+s.slice(t.offset+t.deleteCount);return s}async function le(i,e,s){if(s.length===0)return{data:i,hash:e};let t=i,n=e,o;for(const r of s){if(o=r,r.op==="full"){t=r.data,n=r.hash;continue}if(r.baseHash!==n)return console.warn("[jsonl-sync] Base hash mismatch, need full sync",{expected:r.baseHash,actual:n}),null;t=ce(t,r.patches),n=r.hash}const a=await re(t);if(a!==n){const r=o?.op==="full"?"[jsonl-sync] Full sync hash mismatch":"[jsonl-sync] Post-patch hash mismatch";return console.error(r,{expected:n,actual:a}),null}return{data:t,hash:n}}const he=10*6e4,de=he+9e4,D=1;class Q extends Error{constructor(e,s,t){super(`${s}: ${t}`),this.code=e}}function p(i,e,s="invalid_message"){throw new Q(s,i,e)}function K(i){return typeof i=="object"&&i!==null&&!Array.isArray(i)}function m(i,e){return K(i)||p(e,"expected an object"),i}function l(i,e,s=!0){return(typeof i!="string"||!s&&i.length===0)&&p(e,s?"expected a string":"expected a non-empty string"),i}function k(i,e){return i===void 0?void 0:l(i,e)}function b(i,e){return typeof i!="boolean"&&p(e,"expected a boolean"),i}function C(i,e){return(!Number.isSafeInteger(i)||i<0)&&p(e,"expected a non-negative safe integer"),i}function M(i,e){return(typeof i!="number"||!Number.isFinite(i))&&p(e,"expected a finite number"),i}function T(i,e){return Array.isArray(i)||p(e,"expected an array"),i}function H(i,e){return T(i,e).map((s,t)=>l(s,`${e}[${t}]`))}function O(i,e){const s=m(i,e);for(const[t,n]of Object.entries(s))l(n,`${e}.${t}`);return s}function Y(i,e){const s=m(i,e);for(const[t,n]of Object.entries(s))n!=="running"&&n!=="done"&&p(`${e}.${t}`,"expected 'running' or 'done'");return s}function ue(i,e){const s=m(i,e);for(const[t,n]of Object.entries(s))H(n,`${e}.${t}`);return s}function B(i,e){return["off","minimal","low","medium","high","xhigh","max"].includes(String(i))||p(e,"expected a supported thinking level"),i}function ge(i,e){const s=m(i,e);return l(s.provider,`${e}.provider`,!1),l(s.modelId,`${e}.modelId`,!1),s}function W(i,e){const s=m(i,e);l(s.provider,`${e}.provider`,!1);const t=k(s.id,`${e}.id`),n=k(s.modelId,`${e}.modelId`);return!t&&!n&&p(e,"expected id or modelId"),s}function pe(i,e){if(i.protocolVersion!==D){const s=i.protocolVersion===void 0?"missing":JSON.stringify(i.protocolVersion);p(`${e}.protocolVersion`,`unsupported protocol version ${s}; expected ${D}`,"unsupported_version")}}function X(i){try{return{ok:!0,value:JSON.parse(i)}}catch(e){return{ok:!1,error:{code:"invalid_json",message:`Invalid JSON: ${e instanceof Error?e.message:String(e)}`,requestId:null,command:"parse"}}}}function Z(i,e,s){const t=K(i)?i:void 0,n=typeof t?.id=="string"?t.id:null,o=typeof t?.type=="string"?t.type:s,a=e instanceof Q?e:new Q("invalid_message","$",e instanceof Error?e.message:String(e));return{ok:!1,error:{code:a.code,message:a.message,requestId:n,command:o}}}function me(i,e){T(i,e).forEach((s,t)=>{const n=m(s,`${e}[${t}]`);l(n.name,`${e}[${t}].name`,!1),k(n.description,`${e}[${t}].description`),["extension","prompt","skill"].includes(String(n.source))||p(`${e}[${t}].source`,"expected extension, prompt, or skill"),n.sourceInfo!==void 0&&m(n.sourceInfo,`${e}[${t}].sourceInfo`),k(n.location,`${e}[${t}].location`),k(n.path,`${e}[${t}].path`)})}function fe(i,e){const s=m(i,e);l(s.sessionFile,`${e}.sessionFile`,!1),l(s.sessionId,`${e}.sessionId`,!1);for(const n of["userMessages","assistantMessages","toolCalls","toolResults","totalMessages"])C(s[n],`${e}.${n}`);const t=m(s.tokens,`${e}.tokens`);for(const n of["input","output","cacheRead","cacheWrite","total"])C(t[n],`${e}.tokens.${n}`);if(M(s.cost,`${e}.cost`)<0&&p(`${e}.cost`,"expected a non-negative number"),s.contextUsage!==void 0){const n=m(s.contextUsage,`${e}.contextUsage`);n.tokens!==null&&C(n.tokens,`${e}.contextUsage.tokens`),C(n.contextWindow,`${e}.contextUsage.contextWindow`),n.percent!==null&&M(n.percent,`${e}.contextUsage.percent`)<0&&p(`${e}.contextUsage.percent`,"expected a non-negative number or null")}}function _e(i,e,s){const t=m(e,s);switch(i){case"install_pi":case"subscribe_session":case"steer":case"remove_steering":case"abort":case"set_session_name":break;case"prompt":case"fork_prompt":l(t.newSessionPath,`${s}.newSessionPath`,!1);break;case"hard_kill":b(t.killed,`${s}.killed`),t.reason!==void 0&&t.reason!=="signal_failed"&&t.reason!=="not_attached"&&p(`${s}.reason`,"expected signal_failed or not_attached");break;case"compact":break;case"get_available_models":T(t.models,`${s}.models`).forEach((n,o)=>W(n,`${s}.models[${o}]`));break;case"get_default_model":t.model!==null&&W(t.model,`${s}.model`),B(t.thinkingLevel,`${s}.thinkingLevel`);break;case"get_session_statuses":Y(t.statuses,`${s}.statuses`);break;case"get_session_stats":fe(t,s);break;case"fork":l(t.text,`${s}.text`),b(t.cancelled,`${s}.cancelled`),t.newSessionPath!==null&&l(t.newSessionPath,`${s}.newSessionPath`,!1);break;case"get_commands":me(t.commands,`${s}.commands`);break;case"reload_processes":C(t.killed,`${s}.killed`),C(t.draining,`${s}.draining`);break}}const Se=new Set(["install_pi","subscribe_session","prompt","steer","remove_steering","abort","hard_kill","compact","get_available_models","get_default_model","get_session_statuses","get_session_stats","fork","fork_prompt","set_session_name","get_commands","reload_processes"]);function ye(i,e){const s=l(i,e,!1);return Se.has(s)||p(e,`unknown response command '${s}'`,"unknown_message"),s}function ve(i,e){T(i,e).forEach((s,t)=>{const n=m(s,`${e}[${t}]`);C(n.offset,`${e}[${t}].offset`),C(n.deleteCount,`${e}[${t}].deleteCount`),l(n.insert,`${e}[${t}].insert`)})}function we(i){const e=X(i);if(!e.ok)return e;const s=e.value;try{const t=m(s,"$message");pe(t,"$message");const n=l(t.type,"$message.type",!1);switch(n){case"response":{if(b(t.success,"$message.success")){l(t.id,"$message.id",!1);const a=ye(t.command,"$message.command");_e(a,t.data,"$message.data")}else t.id!==null&&l(t.id,"$message.id",!1),l(t.command,"$message.command",!1),l(t.error,"$message.error",!1),["invalid_json","invalid_message","unsupported_version","unknown_command","unknown_message","command_failed"].includes(String(t.code))||p("$message.code","expected a protocol error code");break}case"init":Y(t.sessionStatuses,"$message.sessionStatuses"),ue(t.steeringQueues,"$message.steeringQueues"),O(t.providerUsageStatuses,"$message.providerUsageStatuses");break;case"pi_install_required":l(t.command,"$message.command",!1),b(t.installable,"$message.installable"),b(t.installing,"$message.installing"),l(t.message,"$message.message",!1);break;case"session_status_change":l(t.sessionPath,"$message.sessionPath",!1),t.status!=="running"&&t.status!=="done"&&p("$message.status","expected running or done");break;case"provider_usage":O(t.statuses,"$message.statuses");break;case"extension_status":l(t.sessionPath,"$message.sessionPath",!1),O(t.statuses,"$message.statuses");break;case"session_sync":l(t.sessionPath,"$message.sessionPath",!1),C(t.revision,"$message.revision"),l(t.hash,"$message.hash",!1),t.op==="full"?l(t.data,"$message.data"):t.op==="delta"?(l(t.baseHash,"$message.baseHash",!1),ve(t.patches,"$message.patches")):p("$message.op","expected full or delta");break;case"control_state":l(t.sessionPath,"$message.sessionPath",!1),t.controlRevision!==void 0&&C(t.controlRevision,"$message.controlRevision"),W(t.model,"$message.model"),B(t.thinkingLevel,"$message.thinkingLevel");break;case"session_attached":l(t.sessionPath,"$message.sessionPath",!1),k(t.cwd,"$message.cwd"),k(t.firstMessage,"$message.firstMessage");break;case"sessions_changed":l(t.file,"$message.file");break;default:p("$message.type",`unknown server message '${n}'`,"unknown_message")}return{ok:!0,value:t}}catch(t){return Z(s,t,"unknown")}}function Ce(i){const e=X(i);if(!e.ok)return e;const s=e.value;try{const t=m(s,"$session");T(t.messages,"$session.messages").forEach((o,a)=>{const r=m(o,`$session.messages[${a}]`);l(r.role,`$session.messages[${a}].role`,!1)}),b(t.isStreaming,"$session.isStreaming"),H(t.pendingToolCalls,"$session.pendingToolCalls");const n=m(t.toolCallTimings,"$session.toolCallTimings");for(const[o,a]of Object.entries(n)){const r=m(a,`$session.toolCallTimings.${o}`);M(r.startedAt,`$session.toolCallTimings.${o}.startedAt`),r.completedAt!==void 0&&M(r.completedAt,`$session.toolCallTimings.${o}.completedAt`)}return t.model!==null&&ge(t.model,"$session.model"),B(t.thinkingLevel,"$session.thinkingLevel"),H(t.steeringQueue,"$session.steeringQueue"),k(t.error,"$session.error"),{ok:!0,value:t}}catch(t){return Z(s,t,"session_sync")}}function $e(i,e){return JSON.stringify({protocolVersion:D,id:e,...i})}function Pe(i){throw new Error(`Unhandled protocol variant: ${JSON.stringify(i)}`)}const x=["off","minimal","low","medium","high","xhigh","max"];function E(i){return i?.id??i?.modelId}function j(i,e){const s=E(i),t=E(e);return!!i&&!!e&&!!s&&!!t&&i.provider===e.provider&&s===t}function J(i){const e=E(i);if(!(!i.provider||!e))return{provider:i.provider,modelId:e}}function ke(i){if(!i)return!1;if(typeof i.reasoning=="boolean")return i.reasoning;if(i.thinkingLevelMap)return!0;const e=String(i.provider??"").toLowerCase(),s=String(E(i)??"").toLowerCase();return!!(e==="openai-codex"||e==="openai"&&s.startsWith("gpt-5"))}function be(i){if(!ke(i))return["off"];const e=i?.thinkingLevelMap;return x.filter(s=>{const t=e?.[s];return t===null?!1:s==="xhigh"||s==="max"?t!==void 0:!0})}function G(i,e){const s=be(i);if(s.includes(e))return e;const t=x.indexOf(e);if(t===-1)return s[0]??"off";for(let n=t;n<x.length;n++){const o=x[n];if(s.includes(o))return o}for(let n=t-1;n>=0;n--){const o=x[n];if(s.includes(o))return o}return s[0]??"off"}class Le extends Error{constructor(e,s,t){super(e),this.status=s,this.details=t,this.name="BackendApiError"}}class xe{constructor(e={}){this.fetch=e.fetch??globalThis.fetch.bind(globalThis)}listSessions(){return this.requestJson("/api/sessions")}async deleteSession(e){await this.requestJson("/api/sessions",{method:"DELETE",headers:{"Content-Type":"application/json"},body:JSON.stringify({path:e})})}async listForkMessages(e){return(await this.requestJson(`/api/sessions/fork-messages?path=${encodeURIComponent(e)}`)).messages??[]}browseDirectory(e){return this.requestJson(`/api/browse?path=${encodeURIComponent(e)}`)}createDirectory(e,s){return this.requestJson("/api/directories",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({parentPath:e,name:s})})}async getRawSession(e){const s=await this.fetch(`/api/sessions/raw?path=${encodeURIComponent(e)}`);if(!s.ok)throw await this.toError(s,"Failed to load raw session");return s.text()}getFileContent(e,s){const t=new URLSearchParams({sessionPath:e,path:s});return this.requestJson(`/api/files/content?${t}`,{cache:"no-store"})}createFileUpload(e){return this.requestJson("/api/files/uploads",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(e)})}appendFileUpload(e){return this.requestJson(`/api/files/uploads/${encodeURIComponent(e.uploadId)}/chunks`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({offset:e.offset,data:e.data})})}completeFileUpload(e){return this.requestJson(`/api/files/uploads/${encodeURIComponent(e)}/complete`,{method:"POST"})}getLocalSettings(){return this.requestJson("/api/settings/local")}validateLocalSettings(e){return this.requestValidation("/api/settings/local/validate",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({content:e})})}patchLocalSettings(e){return this.requestValidation("/api/settings/local",{method:"PATCH",headers:{"Content-Type":"application/json"},body:JSON.stringify(e)})}saveLocalSettings(e){return this.requestValidation("/api/settings/local",{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify({content:e})})}getUpdates(){return this.requestJson("/api/updates",{cache:"no-store"})}runUpdate(e){return this.requestJson(`/api/updates/${e}`,{method:"POST",headers:{"X-Pipane-Action":"update"}})}async requestValidation(e,s){const t=await this.fetch(e,s),n=await this.readJson(t);if(!t.ok&&typeof n?.valid!="boolean")throw this.errorFromPayload(t,n,"Settings request failed");return n}async requestJson(e,s){const t=s?await this.fetch(e,s):await this.fetch(e),n=await this.readJson(t);if(!t.ok)throw this.errorFromPayload(t,n,`Backend request failed (${t.status})`);return n}async readJson(e){return e.json().catch(()=>({}))}async toError(e,s){const t=await this.readJson(e);return this.errorFromPayload(e,t,s)}errorFromPayload(e,s,t){const n=Array.isArray(s?.errors)?s.errors.filter(a=>typeof a=="string"):[],o=typeof s?.error=="string"?s.error:n.length>0?n.join(`
2
+ `):t;return new Le(o,e.status,s)}}const ee=[{name:"help",description:"Show this help",source:"builtin"},{name:"new",description:"Start a new session",source:"builtin"},{name:"session",description:"Show session file, messages, tokens, and cost",source:"builtin"},{name:"fork",description:"Fork session from a previous message",source:"builtin"},{name:"compact",description:"Compact conversation history",source:"builtin",argumentHint:"[instructions]",acceptsArguments:!0},{name:"name",description:"Set session display name",source:"builtin",argumentHint:"<name>",acceptsArguments:!0},{name:"reload",description:"Restart all pooled pi RPC processes",source:"builtin"}];function Re(i){const e=ee.map(t=>({...t})),s=new Set(e.map(t=>t.name.toLocaleLowerCase()));for(const t of i){const n=t.name.toLocaleLowerCase();s.has(n)||(s.add(n),e.push({name:t.name,description:t.description??"",source:t.source,acceptsArguments:!0}))}return e}function Te(i,e){const s=i.toLocaleLowerCase(),t=e.toLocaleLowerCase();if(!t)return 0;if(s===t)return 1e5;let n=s.startsWith(t)?5e4:0,o=0,a=-2,r=-1;for(const h of t){const u=s.indexOf(h,o);if(u===-1)return;r===-1&&(r=u);const _=u>0?s[u-1]:"";u===0||_==="-"||_===":"||_==="_"?n+=18:n+=4,u===a+1&&(n+=12),a=u,o=u+1}return n-=r*3,n-=s.length-t.length,n}function Ue(i,e){return i.map((s,t)=>({command:s,index:t,score:Te(s.name,e)})).filter(s=>s.score!==void 0).sort((s,t)=>t.score-s.score||s.index-t.index).map(s=>s.command)}function qe(i){switch(i){case"builtin":return"Built-in";case"extension":return"Extension";case"prompt":return"Prompt";case"skill":return"Skill"}}const z=1,Ae=1e4;class Fe{constructor(e={}){this.socket=null,this.reconnecting=!1,this.reconnectAttempt=0,this.manuallyClosed=!1,this.frameListeners=new Set,this.connectionListeners=new Set,this.createWebSocket=e.createWebSocket??(s=>new WebSocket(s)),this.schedule=e.schedule??globalThis.setTimeout.bind(globalThis),this.cancelSchedule=e.cancelSchedule??globalThis.clearTimeout.bind(globalThis),e.socket&&(this.socket=e.socket,this.attachFrameHandler(e.socket))}get isConnected(){return this.socket?.readyState===z}get isReconnecting(){return this.reconnecting}onFrame(e){return this.frameListeners.add(e),()=>this.frameListeners.delete(e)}onConnectionChange(e){return this.connectionListeners.add(e),()=>this.connectionListeners.delete(e)}async connect(e){this.isConnected||(this.endpoint=e,this.manuallyClosed=!1,await this.openSocket(!1))}send(e){if(!this.socket||this.socket.readyState!==z)throw new Error("Backend transport is not connected");this.socket.send(e)}close(e=1e3,s="Client closed"){this.manuallyClosed=!0,this.reconnecting=!1,this.reconnectTimer&&(this.cancelSchedule(this.reconnectTimer),this.reconnectTimer=void 0),this.socket?.close(e,s)}attachFrameHandler(e){e.onmessage=s=>{const t=String(s.data);for(const n of this.frameListeners)n(t)}}emitConnectionChange(e){for(const s of this.connectionListeners)s(e)}openSocket(e){const s=this.endpoint;return s?new Promise((t,n)=>{const o=this.createWebSocket(s);let a=!1,r=!1;this.socket=o,this.attachFrameHandler(o),o.onopen=()=>{this.socket===o&&(a=!0,r=!0,this.reconnecting=!1,this.reconnectAttempt=0,this.emitConnectionChange({connected:!0,reconnected:e}),t())},o.onerror=()=>{!e&&!r&&(r=!0,n(new Error("WebSocket connection failed")))},o.onclose=()=>{if(this.socket===o&&(this.socket=null),r||(r=!0,n(new Error("WebSocket closed before connecting"))),this.manuallyClosed){a&&this.emitConnectionChange({connected:!1,reconnected:!1});return}(a||e)&&(this.scheduleReconnect(),a&&this.emitConnectionChange({connected:!1,reconnected:!1}))}}):Promise.reject(new Error("Backend transport endpoint is missing"))}scheduleReconnect(){if(this.reconnectTimer||this.manuallyClosed)return;this.reconnecting=!0,this.reconnectAttempt++;const e=Math.min(500*2**(this.reconnectAttempt-1),Ae);this.reconnectTimer=this.schedule(()=>{this.reconnectTimer=void 0,this.openSocket(!0).catch(()=>{})},e)}}const Ie="provider-usage";function Me(i){const e=typeof i?.provider=="string"?i.provider.toLowerCase():"";if(e.includes("codex"))return"codex";if(e.includes("anthropic"))return"anthropic"}class Oe{constructor(e={}){this.sessionsChangedListeners=new Set,this.piInstallRequiredListeners=new Set,this.pendingRequests=new Map,this.requestId=0,this._connectionListeners=new Set,this._state={model:void 0,thinkingLevel:"off",messages:[],isStreaming:!1,error:void 0},this._pendingToolCallIds=new Set,this._toolCallTimings={},this._steeringQueues=new Map,this._steeringQueueListeners=new Set,this._extensionStatuses=new Map,this._providerUsageStatuses=new Map,this._extensionStatusListeners=new Set,this._controlRevision=0,this._availableModels=null,this._sessionId="",this._sessionStatus="virtual",this._localAssistantMessages=[],this._optimisticSessions=new Map,this._globalSessionStatus=new Map,this._globalStatusListeners=new Set,this._sessionNonce=0,this._pendingNewPrompt=!1,this._startingPrompts=new Map,this._syncJson="",this._syncHash="",this._pendingSessionSyncs=[],this._sessionSyncFlushScheduled=!1,this._sessionSyncFlushInProgress=!1,this._sessionListeners=new Set,this._contentListeners=new Set,this._statusListeners=new Set,this.transport=e.transport??new Fe({socket:e.socket,createWebSocket:e.createWebSocket}),this.api=e.api??new xe({fetch:e.fetch}),this.requestFrame=e.requestFrame??(s=>requestAnimationFrame(s)),this.transport.onFrame(s=>this.handleMessage(s)),this.transport.onConnectionChange(s=>this.handleTransportConnectionChange(s.connected,s.reconnected))}get state(){return this._state}get sessionId(){return this._sessionId}get sessionFile(){return this._sessionPath}get sessionName(){return this._sessionName}get sessionStatus(){return this._sessionStatus}get isConnected(){return this.transport.isConnected}get isReconnecting(){return this.transport.isReconnecting}onConnectionChange(e){return this._connectionListeners.add(e),()=>this._connectionListeners.delete(e)}emitConnectionChange(e){for(const s of this._connectionListeners)s(e)}get pendingToolCallIds(){return this._pendingToolCallIds}get toolCallTimings(){return this._toolCallTimings}get steeringQueue(){return this._sessionPath?this._steeringQueues.get(this._sessionPath)??[]:[]}get extensionStatuses(){const e=new Map(this._extensionStatuses),s=Me(this._state.model),t=s?this._providerUsageStatuses.get(s):this._providerUsageStatuses.size===1?this._providerUsageStatuses.values().next().value:void 0;return t&&e.set(Ie,t),e}onExtensionStatusChange(e){return this._extensionStatusListeners.add(e),()=>this._extensionStatusListeners.delete(e)}replaceExtensionStatuses(e){const s=new Map;if(e&&typeof e=="object"&&!Array.isArray(e))for(const[t,n]of Object.entries(e))typeof n=="string"&&s.set(t,n);if(!(s.size===this._extensionStatuses.size&&[...s].every(([t,n])=>this._extensionStatuses.get(t)===n))){this._extensionStatuses=s;for(const t of this._extensionStatusListeners)t()}}replaceProviderUsageStatuses(e){const s=new Map;if(e&&typeof e=="object"&&!Array.isArray(e))for(const[t,n]of Object.entries(e))typeof n=="string"&&s.set(t,n);if(!(s.size===this._providerUsageStatuses.size&&[...s].every(([t,n])=>this._providerUsageStatuses.get(t)===n))){this._providerUsageStatuses=s;for(const t of this._extensionStatusListeners)t()}}clearExtensionStatuses(){this.replaceExtensionStatuses({})}getSessionStatus(e){return this._globalSessionStatus.get(e)}onGlobalStatusChange(e){return this._globalStatusListeners.add(e),()=>this._globalStatusListeners.delete(e)}emitGlobalStatusChange(){for(const e of this._globalStatusListeners)e()}setGlobalSessionStatus(e,s){this._globalSessionStatus.set(e,s),s==="running"&&this._startingPrompts.get(e)?.resolveReady(e),this.emitGlobalStatusChange()}setAllSessionStatuses(e){this._globalSessionStatus.clear();for(const[s,t]of Object.entries(e))this._globalSessionStatus.set(s,t);this.emitGlobalStatusChange()}onSteeringQueueChange(e){return this._steeringQueueListeners.add(e),()=>this._steeringQueueListeners.delete(e)}emitSteeringQueueChange(){for(const e of this._steeringQueueListeners)e()}enqueueSteering(e,s){const t=this._steeringQueues.get(e)??[];this._steeringQueues.set(e,[...t,s]),this.emitSteeringQueueChange()}onSessionChange(e){return this._sessionListeners.add(e),()=>this._sessionListeners.delete(e)}emitSessionChange(){for(const e of this._sessionListeners)e()}onContentChange(e){return this._contentListeners.add(e),()=>this._contentListeners.delete(e)}emitContentChange(){for(const e of this._contentListeners)e()}onStatusChange(e){return this._statusListeners.add(e),()=>this._statusListeners.delete(e)}emitStatusChange(){for(const e of this._statusListeners)e()}onSessionsChanged(e){return this.sessionsChangedListeners.add(e),()=>this.sessionsChangedListeners.delete(e)}onPiInstallRequired(e){return this.piInstallRequiredListeners.add(e),()=>this.piInstallRequiredListeners.delete(e)}emitPiInstallRequired(e){for(const s of this.piInstallRequiredListeners)s(e)}toErrorMessage(e){if(e instanceof Error)return e.message;if(typeof e=="string")return e;try{return JSON.stringify(e)}catch{return String(e)}}reportError(e,s="Request failed"){const t=this.toErrorMessage(e);this._state.error=t,this._state.messages=[...this._state.messages,{role:"assistant",content:[{type:"text",text:`⚠️ ${s}: ${t}`}],timestamp:Date.now()}],this.emitStatusChange(),this.emitContentChange()}async connect(e){await this.transport.connect(e),document.addEventListener("visibilitychange",()=>{document.visibilityState==="visible"&&this.syncStateOnFocus()})}disconnect(){this.transport.close()}getConnectionDiagnostics(){return this.transport.getConnectionDiagnostics?.()??Promise.resolve(void 0)}handleTransportConnectionChange(e,s){e?s?this.onReconnected():this._sessionStatus="virtual":this.handleTransportDisconnected(),this.emitConnectionChange(e)}handleTransportDisconnected(){for(const e of this.pendingRequests.values())e.reject(new Error("Backend transport disconnected"));this.pendingRequests.clear(),this._pendingControl?.phase==="sent"?this.rollbackSentControl(this._pendingControl.revision):this._pendingControl?.phase==="acknowledged"&&(this._pendingControl=void 0)}async onReconnected(){this._sessionPath&&this._sessionStatus!=="virtual"&&(this._awaitingFullSync=void 0,this.requestFullSessionSync(this._sessionPath)),this.refreshSessionStatuses()}async syncStateOnFocus(){this.refreshSessionStatuses(),this._sessionPath&&this._sessionStatus!=="virtual"&&this.subscribeToSession(this._sessionPath),this._sessionStatus==="detached"&&this._state.isStreaming&&(console.log("[ws-adapter] Tab regained focus: clearing stale streaming state"),this._state.isStreaming=!1,this._pendingToolCallIds.clear(),this.emitStatusChange())}async refreshSessionStatuses(){try{const e=await this.send({type:"get_session_statuses"});e?.statuses&&this.setAllSessionStatuses(e.statuses)}catch(e){console.error("Failed to refresh session statuses:",e)}}async subscribeToSession(e){try{await this.send({type:"subscribe_session",sessionPath:e??""})}catch(s){console.error("Failed to subscribe to session:",s)}}async requestFullSessionSync(e){const s={sessionPath:e,sessionNonce:this._sessionNonce};this._awaitingFullSync?.sessionPath===s.sessionPath&&this._awaitingFullSync.sessionNonce===s.sessionNonce||(this._awaitingFullSync=s,this._syncJson="",this._syncHash="",this._syncRevision=void 0,this.clearSessionSyncQueue(),await this.subscribeToSession(e))}handleProtocolError(e){const s=`Protocol error: ${e.message}`;console.error(`[ws-adapter] ${s}`),this._state.error=s,this.emitStatusChange(),e.code==="unsupported_version"&&this.transport.close(1002,"Unsupported protocol version")}handleMessage(e){if(oe(e))return;const s=we(e);if(!s.ok){this.handleProtocolError(s.error);return}const t=s.value;switch(t.type){case"response":{if(!t.success&&t.id===null){this._state.error=t.error,this.emitStatusChange();return}if(t.id===null)return;const n=this.pendingRequests.get(t.id);if(!n)return;if(this.pendingRequests.delete(t.id),t.command!==n.command){n.reject(new Error(`Mismatched response for ${n.command}: received ${t.command}`));return}t.success?n.resolve(t.data):(this._state.error=t.error,this.emitStatusChange(),n.reject(new Error(t.error)));return}case"pi_install_required":this.emitPiInstallRequired({command:t.command,installable:t.installable,installing:t.installing,message:t.message});return;case"init":this.setAllSessionStatuses(t.sessionStatuses),this.replaceProviderUsageStatuses(t.providerUsageStatuses),this._steeringQueues.clear();for(const[n,o]of Object.entries(t.steeringQueues))o.length>0&&this._steeringQueues.set(n,[...o]);this.emitSteeringQueueChange();return;case"session_status_change":this.setGlobalSessionStatus(t.sessionPath,t.status);return;case"provider_usage":this.replaceProviderUsageStatuses(t.statuses);return;case"extension_status":t.sessionPath===this._sessionPath&&this.replaceExtensionStatuses(t.statuses);return;case"session_sync":if(t.sessionPath!==this._sessionPath)return;this.enqueueSessionSync({...t,__sessionPath:t.sessionPath,__sessionNonce:this._sessionNonce});return;case"control_state":{if(t.sessionPath!==this._sessionPath)return;this.applyAuthoritativeControlState(t.model,t.thinkingLevel,t.controlRevision)&&this.emitContentChange();return}case"session_attached":{this.setGlobalSessionStatus(t.sessionPath,"running"),t.cwd&&(this._pendingCwd=t.cwd);const n=t.sessionPath===this._sessionPath||this._sessionStatus==="virtual"&&this._pendingNewPrompt;if(n){if(this._sessionStatus==="virtual"){this._sessionPath=t.sessionPath;const a=I.basename(t.sessionPath,".jsonl"),r=a.split("_");this._sessionId=r.length>1?r.slice(1).join("_"):a,this._startingPrompts.get(`virtual:${this._sessionNonce}`)?.resolveReady(t.sessionPath)}this._sessionStatus="attached",this._state.isStreaming=!0,this.requestFullSessionSync(t.sessionPath),this.emitStatusChange()}if(!this._optimisticSessions.has(t.sessionPath)){const o=new Date().toISOString(),a=I.basename(t.sessionPath,".jsonl"),r=a.split("_"),h=r.length>1?r.slice(1).join("_"):a;this._optimisticSessions.set(t.sessionPath,{id:h,path:t.sessionPath,cwd:t.cwd??"",created:o,modified:o,lastUserPromptTime:o,messageCount:1,firstMessage:t.firstMessage??"(new session)"})}n&&this.emitSessionChange();return}case"sessions_changed":for(const n of this.sessionsChangedListeners)n(t.file);return;default:Pe(t)}}enqueueSessionSync(e){const s=this._awaitingFullSync!==void 0&&this._awaitingFullSync.sessionPath===e.__sessionPath&&this._awaitingFullSync.sessionNonce===e.__sessionNonce;if(e.op==="full")s&&(this._awaitingFullSync=void 0),this._pendingSessionSyncs=[e];else{if(s)return;this._pendingSessionSyncs.push(e)}this._sessionSyncFlushScheduled||this._sessionSyncFlushInProgress||(this._sessionSyncFlushScheduled=!0,this.requestFrame(()=>{this._sessionSyncFlushScheduled=!1,this.flushSessionSyncQueue()}))}clearSessionSyncQueue(){this._pendingSessionSyncs=[],this._sessionSyncFlushScheduled=!1}async flushSessionSyncQueue(){if(this._sessionSyncFlushInProgress)return;this._sessionSyncFlushInProgress=!0;const e=this._pendingSessionSyncs.splice(0);try{const s=await this.applySessionSyncBatch(e);s&&this.emitSessionSyncChanges(s)}finally{this._sessionSyncFlushInProgress=!1,this._pendingSessionSyncs.length>0&&!this._sessionSyncFlushScheduled&&(this._sessionSyncFlushScheduled=!0,this.requestFrame(()=>{this._sessionSyncFlushScheduled=!1,this.flushSessionSyncQueue()}))}}emitSessionSyncChanges(e){e.steering&&this.emitSteeringQueueChange(),e.content&&this.emitContentChange(),e.status&&this.emitStatusChange()}async applySessionSyncBatch(e){const s=this._sessionPath,t=this._sessionNonce;if(!s||e.length===0||e.some(c=>c.__sessionPath!==s||c.__sessionNonce!==t))return;let n=this._syncRevision;for(const c of e){if(c.op==="full"){n=c.revision;continue}if(n===void 0||c.revision!==n+1){console.error("[ws-adapter] Session revision gap, re-subscribing",{expected:n===void 0?"full snapshot":n+1,actual:c.revision}),this.requestFullSessionSync(s);return}n=c.revision}const o=e.map(c=>c.op==="full"?{op:"full",data:c.data,hash:c.hash}:{op:"delta",patches:c.patches,hash:c.hash,baseHash:c.baseHash});if(o[0]?.op==="delta"&&!this._syncHash){console.warn("[ws-adapter] Ignoring delta while awaiting full sync");return}const a=await le(this._syncJson,this._syncHash,o);if(s!==this._sessionPath||t!==this._sessionNonce)return;if(!a){console.error("[ws-adapter] Sync verification failed, re-subscribing"),this.requestFullSessionSync(s);return}const r=Ce(a.data);if(!r.ok){this.handleProtocolError(r.error);return}const h=r.value;this._syncJson=a.data,this._syncHash=a.hash,this._syncRevision=n;const u=this._state.isStreaming,_=this._sessionStatus,y=this._state.error,v=[...this._steeringQueues.get(s)??[]];this._state.messages=[...h.messages??[],...this._localAssistantMessages],this._state.isStreaming=h.isStreaming??!1,this._sessionStatus!=="virtual"&&(this._sessionStatus=this._state.isStreaming?"attached":"detached"),this._pendingToolCallIds=new Set(h.pendingToolCalls??[]),this._toolCallTimings=h.toolCallTimings??{},this.applyAuthoritativeControlState(h.model,h.thinkingLevel),Array.isArray(h.steeringQueue)&&(h.steeringQueue.length>0?this._steeringQueues.set(s,[...h.steeringQueue]):this._steeringQueues.delete(s)),this._state.error=h.error||void 0;const w=this._steeringQueues.get(s)??[];return{content:!0,status:u!==this._state.isStreaming||_!==this._sessionStatus||y!==this._state.error,steering:v.length!==w.length||v.some((c,g)=>c!==w[g])}}send(e){if(!this.transport.isConnected)return Promise.reject(new Error("Backend transport is not connected"));const s=`req_${++this.requestId}`;return new Promise((t,n)=>{const o=e.type==="compact"?de:e.type==="prompt"||e.type==="fork_prompt"?9e4:3e4,a=setTimeout(()=>{this.pendingRequests.delete(s),n(new Error(`Timeout waiting for response to ${e.type}`))},o);this.pendingRequests.set(s,{command:e.type,resolve:r=>{clearTimeout(a),t(r)},reject:r=>{clearTimeout(a),n(r)}}),this.transport.send($e(e,s))})}async fetchAvailableModels(){const s=(await this.send({type:"get_available_models"}))?.models??[];this._availableModels=s;const t=this.resolveModel(this._state.model);return t&&(this._state.model=t),s}resolveModel(e){if(!e)return;const s=this._availableModels?.find(n=>j(n,e));if(s)return s;const t=J(e);if(t)return{...e,provider:t.provider,id:t.modelId}}markControlPending(){const e=++this._controlRevision;return this._pendingControl={revision:e,sessionNonce:this._sessionNonce,phase:"local"},e}markControlSent(e){this._pendingControl?.revision===e&&this._pendingControl.sessionNonce===this._sessionNonce&&(this._pendingControl.phase="sent")}rollbackSentControl(e){const s=this._pendingControl;if(!s||s.revision!==e||s.phase!=="sent")return;this._pendingControl=void 0;const t=this._lastAuthoritativeControl;t?.sessionNonce===this._sessionNonce&&(this._state.model=t.model,this._state.thinkingLevel=t.thinkingLevel,this.emitContentChange())}rememberAuthoritativeControl(e,s){this._lastAuthoritativeControl={sessionNonce:this._sessionNonce,model:e,thinkingLevel:s}}applyAuthoritativeControlState(e,s,t){const n=this.resolveModel(e),o=s,a=this._pendingControl;if(t!==void 0){if(a){if(a.sessionNonce!==this._sessionNonce||a.revision!==t)return!1;a.phase="acknowledged",a.model=n,a.thinkingLevel=o}return n&&(this._state.model=n),o&&(this._state.thinkingLevel=o),n&&o&&this.rememberAuthoritativeControl(n,o),!!n||!!o}if(a){if(!(a.phase==="acknowledged"&&!!a.model&&j(e,a.model)&&o===a.thinkingLevel))return!1;this._pendingControl=void 0}return n&&(this._state.model=n),o&&(this._state.thinkingLevel=o),n&&o&&this.rememberAuthoritativeControl(n,o),!!n||!!o}async installPi(){await this.send({type:"install_pi"})}async loadDefaultModel(){if(this._state.model)return;const e=await this.send({type:"get_default_model"});this.applyAuthoritativeControlState(e?.model,e?.thinkingLevel)}setCwd(e){this._pendingCwd=e}get cwd(){return this._pendingCwd}createStartingPrompt(e){let s,t;const n={ready:new Promise(o=>{s=o}),resolveReady:o=>s(o),finished:new Promise(o=>{t=o}),resolveFinished:()=>t()};return this._startingPrompts.set(e,n),n}finishStartingPrompt(e,s,t){s.resolveReady(t),s.resolveFinished(),this._startingPrompts.get(e)===s&&this._startingPrompts.delete(e)}async prompt(e,s){let t;if(typeof e=="string"?t=e:Array.isArray(e)?t=e.map(c=>this.extractText(c)).join(`
3
3
  `):t=this.extractText(e),this.clearLocalAssistantMessages(),await this.handleSlashCommand(t))return;const o=this._sessionStatus==="virtual"?`virtual:${this._sessionNonce}`:this._sessionPath,a=o?this._startingPrompts.get(o):void 0;if(a){const c=await a.ready;if(c&&this._globalSessionStatus.get(c)==="running"){this.enqueueSteering(c,t),await this.send({type:"steer",sessionPath:c,message:t});return}if(await a.finished,!c)throw new Error("The session failed to start before the queued prompt could be sent");return this.prompt(e,s)}if((this._sessionPath?this._globalSessionStatus.get(this._sessionPath)==="running":!1)&&this._sessionPath){this.enqueueSteering(this._sessionPath,t),await this.send({type:"steer",sessionPath:this._sessionPath,message:t});return}const h=this._state.model;if(!h)throw new Error(`BUG: effective model is undefined when sending prompt. sessionStatus=${this._sessionStatus}, sessionPath=${this._sessionPath}`);const u=J(h);if(!u)throw new Error("BUG: active model has no provider/model ID");const _=this._state.thinkingLevel,y=this._pendingControl?.revision??this._controlRevision;this.markControlSent(y);const v=o??`virtual:${this._sessionNonce}`,w=this.createStartingPrompt(v);if(this._sessionStatus==="virtual"){const c=this._sessionNonce;this._pendingNewPrompt=!0;try{const g=await this.send({type:"prompt",sessionPath:"__new__",cwd:this._pendingCwd,message:t,model:u,thinkingLevel:_,controlRevision:y,images:s});if(this._sessionNonce!==c){console.log("[ws-adapter] Discarding stale prompt response (session changed during await)");return}const S=g?.newSessionPath;if(S&&!this._sessionPath){this._sessionPath=S;const f=I.basename(S,".jsonl"),$=f.split("_");this._sessionId=$.length>1?$.slice(1).join("_"):f,this._sessionStatus="attached",this.requestFullSessionSync(S),this.emitSessionChange(),this.emitStatusChange()}}catch(g){throw this.rollbackSentControl(y),g}finally{this._pendingNewPrompt=!1;const g=this._sessionNonce===c?this._sessionPath:void 0;this.finishStartingPrompt(v,w,g)}return}if(!this._sessionPath)throw new Error("No session loaded");try{await this.send({type:"prompt",sessionPath:this._sessionPath,message:t,model:u,thinkingLevel:_,controlRevision:y,images:s})}catch(c){throw this.rollbackSentControl(y),c}finally{this.finishStartingPrompt(v,w,this._sessionPath)}}extractText(e){if("content"in e){if(typeof e.content=="string")return e.content;if(Array.isArray(e.content))return e.content.filter(s=>s.type==="text").map(s=>s.text).join(`
4
4
  `)}return""}async fetchCommands(){try{const e=this._sessionPath?{sessionPath:this._sessionPath}:{cwd:this._pendingCwd};return(await this.send({type:"get_commands",...e}))?.commands??[]}catch{return[]}}clearLocalAssistantMessages(){if(this._localAssistantMessages.length===0)return;const e=new Set(this._localAssistantMessages);this._localAssistantMessages=[],this._state.messages=this._state.messages.filter(s=>!e.has(s)),this.emitContentChange()}appendLocalAssistantMessage(e){const s={role:"assistant",content:[{type:"text",text:e}]};this._localAssistantMessages.push(s),this._state.messages=[...this._state.messages,s],this.emitContentChange()}formatSessionStats(e){const s=o=>o.toLocaleString("en-US"),t=`$${e.cost.toFixed(e.cost<.01?4:e.cost<1?3:2)}`,n=["**Session information**","",`- **File:** \`${e.sessionFile}\``,`- **ID:** \`${e.sessionId}\``,`- **Messages:** ${s(e.totalMessages)} total (${s(e.userMessages)} user, ${s(e.assistantMessages)} assistant)`,`- **Tools:** ${s(e.toolCalls)} calls, ${s(e.toolResults)} results`,`- **Tokens:** ${s(e.tokens.total)} total (${s(e.tokens.input)} input, ${s(e.tokens.output)} output, ${s(e.tokens.cacheRead)} cache read, ${s(e.tokens.cacheWrite)} cache write)`,`- **Cost:** ${t}`];if(e.contextUsage){const o=e.contextUsage.tokens===null?`unknown / ${s(e.contextUsage.contextWindow)}`:`${s(e.contextUsage.tokens)} / ${s(e.contextUsage.contextWindow)}${e.contextUsage.percent===null?"":` (${e.contextUsage.percent}%)`}`;n.push(`- **Context:** ${o}`)}return n.join(`
5
5
  `)}async showSessionMessage(){if(!this._sessionPath){this.appendLocalAssistantMessage("No active session yet. Send a message first to create one.");return}const e=await this.send({type:"get_session_stats",sessionPath:this._sessionPath});this.appendLocalAssistantMessage(this.formatSessionStats(e))}async showHelpMessage(){const e=["**Built-in commands:**","","| Command | Description |","|---------|-------------|",...ee.map(a=>{const r=a.argumentHint?` ${a.argumentHint}`:"";return`| \`/${a.name}${r}\` | ${a.description} |`})],s=await this.fetchCommands(),t=s.filter(a=>a.source==="extension"),n=s.filter(a=>a.source==="prompt"),o=s.filter(a=>a.source==="skill");if(t.length>0){e.push("","**Extension commands:**","","| Command | Description |","|---------|-------------|");for(const a of t)e.push(`| \`/${a.name}\` | ${a.description||""} |`)}if(n.length>0){e.push("","**Prompt templates:**","","| Command | Description |","|---------|-------------|");for(const a of n){const r=a.location?` *(${a.location})*`:"";e.push(`| \`/${a.name}\` | ${a.description||""}${r} |`)}}if(o.length>0){e.push("","**Skills:**","","| Command | Description |","|---------|-------------|");for(const a of o){const r=a.location?` *(${a.location})*`:"";e.push(`| \`/${a.name}\` | ${a.description||""}${r} |`)}}e.push("","**Keyboard shortcuts:**","","| Shortcut | Action |","|----------|--------|","| `Enter` | Send message (also works during streaming to steer) |","| `Cmd+Enter` | Fork session and send message in the fork |","| `Shift+Enter` | New line |","| `Escape` | Abort current turn |"),this.appendLocalAssistantMessage(e.join(`
@@ -17,8 +17,8 @@
17
17
  if (colorTheme && colorTheme !== "default") root.setAttribute("data-color-theme", colorTheme);
18
18
  })();
19
19
  </script>
20
- <script type="module" crossorigin src="/assets/index-DwbBcYUf.js"></script>
21
- <link rel="stylesheet" crossorigin href="/assets/index-iblQYBAl.css">
20
+ <script type="module" crossorigin src="/assets/index-C7_1ODks.js"></script>
21
+ <link rel="stylesheet" crossorigin href="/assets/index-DgI_gkjg.css">
22
22
  </head>
23
23
  <body>
24
24
  <div id="app">
@@ -0,0 +1,7 @@
1
+ export const DEFAULT_RENDEZVOUS_URL = "https://pipane.dev";
2
+ export function resolveRendezvousUrl(configuredUrl) {
3
+ return configuredUrl ?? DEFAULT_RENDEZVOUS_URL;
4
+ }
5
+ export function resolveBackendName(configuredName, systemHostname) {
6
+ return configuredName || systemHostname.split(".", 1)[0] || systemHostname;
7
+ }
@@ -179,7 +179,13 @@ export class BackendWebRtcManager {
179
179
  type: "authentication_error",
180
180
  message,
181
181
  };
182
- channel.sendMessage(JSON.stringify(response));
182
+ try {
183
+ if (channel.isOpen())
184
+ channel.sendMessage(JSON.stringify(response));
185
+ }
186
+ catch {
187
+ // The peer may close while asynchronous authorization is settling.
188
+ }
183
189
  this.failPeer(state.request.connectionId, new Error(message));
184
190
  }
185
191
  applySignal(connectionId, signal) {
@@ -0,0 +1,13 @@
1
+ import { readFileSync } from "node:fs";
2
+ export function isDevelopmentCommit(buildInfoPath, packageVersion) {
3
+ try {
4
+ const value = JSON.parse(readFileSync(buildInfoPath, "utf8"));
5
+ if (!value || typeof value !== "object")
6
+ return false;
7
+ const info = value;
8
+ return info.packageVersion === packageVersion && info.developmentCommit === true;
9
+ }
10
+ catch {
11
+ return false;
12
+ }
13
+ }
@@ -1,24 +1,36 @@
1
+ import { DATA_CHANNEL_BUFFER_HIGH_WATER_BYTES, DATA_CHANNEL_BUFFER_LOW_WATER_BYTES, DataChannelFrameDecoder, MAX_DATA_CHANNEL_QUEUED_BYTES, encodeDataChannelFrame, } from "../shared/data-channel-framing.js";
1
2
  export const FRAME_CONNECTION_OPEN = 1;
2
3
  export const FRAME_CONNECTION_CLOSED = 3;
3
- /** Adapts an authenticated reliable DataChannel to the server's frame connection contract. */
4
+ /** Adapts an authenticated reliable DataChannel to the server's logical frame connection contract. */
4
5
  export class DataChannelFrameConnection {
5
6
  channel;
6
7
  messageListeners = new Set();
7
8
  closeListeners = new Set();
9
+ decoder = new DataChannelFrameDecoder();
10
+ outgoing = [];
8
11
  state = FRAME_CONNECTION_OPEN;
12
+ nextFrameId = 0;
13
+ queuedBytes = 0;
14
+ flushing = false;
15
+ closeEmitted = false;
9
16
  constructor(channel) {
10
17
  this.channel = channel;
18
+ channel.setBufferedAmountLowThreshold(DATA_CHANNEL_BUFFER_LOW_WATER_BYTES);
19
+ channel.onBufferedAmountLow(() => this.flushOutgoing());
11
20
  channel.onMessage((message) => {
12
- for (const listener of this.messageListeners)
13
- listener(message);
14
- });
15
- channel.onClosed(() => {
16
- if (this.state === FRAME_CONNECTION_CLOSED)
17
- return;
18
- this.state = FRAME_CONNECTION_CLOSED;
19
- for (const listener of this.closeListeners)
20
- listener();
21
+ try {
22
+ const decoded = this.decoder.accept(message.toString());
23
+ if (decoded === undefined)
24
+ return;
25
+ const frame = { toString: () => decoded };
26
+ for (const listener of this.messageListeners)
27
+ listener(frame);
28
+ }
29
+ catch {
30
+ this.close();
31
+ }
21
32
  });
33
+ channel.onClosed(() => this.finishClose());
22
34
  }
23
35
  get readyState() {
24
36
  return this.state === FRAME_CONNECTION_OPEN && this.channel.isOpen()
@@ -26,17 +38,29 @@ export class DataChannelFrameConnection {
26
38
  : FRAME_CONNECTION_CLOSED;
27
39
  }
28
40
  send(frame) {
29
- if (this.readyState !== FRAME_CONNECTION_OPEN || !this.channel.sendMessage(frame)) {
41
+ if (this.readyState !== FRAME_CONNECTION_OPEN)
30
42
  throw new Error("DataChannel frame connection is closed");
43
+ const messages = encodeDataChannelFrame(frame, `s${(++this.nextFrameId).toString(36)}`);
44
+ const additions = messages.map((message) => ({ message, byteLength: Buffer.byteLength(message) }));
45
+ const addedBytes = additions.reduce((total, item) => total + item.byteLength, 0);
46
+ if (this.queuedBytes + addedBytes > MAX_DATA_CHANNEL_QUEUED_BYTES) {
47
+ this.close();
48
+ throw new Error("DataChannel outgoing frame queue exceeds its limit");
31
49
  }
50
+ this.outgoing.push(...additions);
51
+ this.queuedBytes += addedBytes;
52
+ this.flushOutgoing();
32
53
  }
33
54
  close() {
34
55
  if (this.state === FRAME_CONNECTION_CLOSED)
35
56
  return;
36
57
  this.state = FRAME_CONNECTION_CLOSED;
37
- this.channel.close();
38
- for (const listener of this.closeListeners)
39
- listener();
58
+ try {
59
+ this.channel.close();
60
+ }
61
+ finally {
62
+ this.finishClose();
63
+ }
40
64
  }
41
65
  on(event, listener) {
42
66
  if (event === "message")
@@ -45,4 +69,38 @@ export class DataChannelFrameConnection {
45
69
  this.closeListeners.add(listener);
46
70
  return this;
47
71
  }
72
+ flushOutgoing() {
73
+ if (this.flushing || this.readyState !== FRAME_CONNECTION_OPEN)
74
+ return;
75
+ this.flushing = true;
76
+ try {
77
+ while (this.outgoing.length > 0 && this.channel.bufferedAmount() < DATA_CHANNEL_BUFFER_HIGH_WATER_BYTES) {
78
+ const next = this.outgoing[0];
79
+ // A false result means libdatachannel accepted the message into
80
+ // native buffering. bufferedAmount, not the result, controls flow.
81
+ this.channel.sendMessage(next.message);
82
+ this.outgoing.shift();
83
+ this.queuedBytes -= next.byteLength;
84
+ }
85
+ }
86
+ catch {
87
+ this.close();
88
+ }
89
+ finally {
90
+ this.flushing = false;
91
+ }
92
+ }
93
+ finishClose() {
94
+ if (this.closeEmitted)
95
+ return;
96
+ this.closeEmitted = true;
97
+ this.state = FRAME_CONNECTION_CLOSED;
98
+ this.outgoing.length = 0;
99
+ this.queuedBytes = 0;
100
+ this.decoder.reset();
101
+ const listeners = [...this.closeListeners];
102
+ this.closeListeners.clear();
103
+ for (const listener of listeners)
104
+ listener();
105
+ }
48
106
  }
@@ -33,6 +33,7 @@ import { WsHandler } from "./ws-handler.js";
33
33
  import { LocalSettingsStore } from "./local-settings.js";
34
34
  import { resolveUsageExtensionPath } from "./bundled-extensions.js";
35
35
  import { UpdateManager } from "./update-manager.js";
36
+ import { isDevelopmentCommit } from "./build-info.js";
36
37
  import { registerUpdateApi } from "./update-api.js";
37
38
  import { SessionPathGuard } from "./session-path.js";
38
39
  import { AuthGuard } from "./auth-guard.js";
@@ -45,6 +46,7 @@ import { DataChannelFrameConnection } from "./frame-connection.js";
45
46
  import { routeFrameConnection } from "./frame-router.js";
46
47
  import { BackendProtocolHandler } from "./backend-protocol-handler.js";
47
48
  import { LocalBackendApi } from "./local-backend-api.js";
49
+ import { resolveBackendName, resolveRendezvousUrl } from "./backend-registration-config.js";
48
50
  import qrcode from "qrcode-terminal";
49
51
  const DEFAULT_PORT = process.env.NODE_ENV === "production" ? "8222" : "18111";
50
52
  const REQUESTED_PORT = parseInt(process.env.PORT || DEFAULT_PORT, 10);
@@ -73,7 +75,7 @@ const PI_AVAILABLE = checkCommandAvailable(PI_LAUNCH.command);
73
75
  const PI_MAX_PROCESSES = parseInt(process.env.PI_MAX_PROCESSES || "24", 10);
74
76
  const PI_PREWARM_COUNT = parseInt(process.env.PI_PREWARM_COUNT || "2", 10);
75
77
  const USAGE_EXTENSION_ENABLED = process.env.PIPANE_USAGE_EXTENSION !== "0";
76
- const RENDEZVOUS_URL = process.env.PIPANE_RENDEZVOUS_URL;
78
+ const RENDEZVOUS_URL = resolveRendezvousUrl(process.env.PIPANE_RENDEZVOUS_URL);
77
79
  let rendezvousPairingRuntime;
78
80
  let registeredBackendId;
79
81
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
@@ -88,6 +90,8 @@ const PKG_VERSION = (() => {
88
90
  }
89
91
  })();
90
92
  const PKG_NAME = "pipane";
93
+ const IS_DEVELOPMENT_COMMIT = process.env.NODE_ENV !== "production"
94
+ || isDevelopmentCommit(path.resolve(path.dirname(PKG_JSON_PATH), "dist/build-info.json"), PKG_VERSION);
91
95
  async function startRendezvousRegistration() {
92
96
  if (!RENDEZVOUS_URL)
93
97
  return;
@@ -98,7 +102,7 @@ async function startRendezvousRegistration() {
98
102
  url: RENDEZVOUS_URL,
99
103
  identity,
100
104
  metadata: {
101
- name: process.env.PIPANE_BACKEND_NAME || hostname(),
105
+ name: resolveBackendName(process.env.PIPANE_BACKEND_NAME, hostname()),
102
106
  softwareVersion: PKG_VERSION,
103
107
  protocolVersions: [WS_PROTOCOL_VERSION, BACKEND_PROTOCOL_VERSION],
104
108
  },
@@ -266,6 +270,7 @@ const updateManager = new UpdateManager({
266
270
  pipaneVersion: PKG_VERSION,
267
271
  pipanePackageName: PKG_NAME,
268
272
  piLaunch: PI_LAUNCH,
273
+ skipPipaneCheck: IS_DEVELOPMENT_COMMIT,
269
274
  cwd: PI_CWD,
270
275
  onPiRuntimeChanged: async () => {
271
276
  pool.decommissionAll();
@@ -116,7 +116,9 @@ export class UpdateManager {
116
116
  this.snapshot = { checkedAt: new Date().toISOString(), notices: [] };
117
117
  return this.snapshot;
118
118
  }
119
- const pipaneCheck = this.checkPipaneUpdate();
119
+ const pipaneCheck = this.options.skipPipaneCheck
120
+ ? Promise.resolve(null)
121
+ : this.checkPipaneUpdate();
120
122
  const piCheck = process.env.PI_SKIP_VERSION_CHECK
121
123
  ? Promise.resolve(null)
122
124
  : this.checkPiUpdate();