pipane 0.1.10 → 0.1.12

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{l as y}from"./device-identity-C_w2NbSk.js";import{R as f,p as h}from"./rendezvous-trust-api-xLnrhe-v.js";import{W as x}from"./webrtc-frame-transport-Drpqhhdy.js";import"./__vite-browser-external-DGfF_P2w.js";const w={loadIdentity:()=>y(),createTrustApi:()=>new f(window.location.origin),createTransport:i=>new x(i)};async function E(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{E as initializePairingPage};
1
+ import{l as y}from"./device-identity-C_w2NbSk.js";import{R as f,a as h}from"./rendezvous-trust-api-Bv6fZVJA.js";import{W as x}from"./webrtc-frame-transport-Drpqhhdy.js";import"./__vite-browser-external-DGfF_P2w.js";const w={loadIdentity:()=>y(),createTrustApi:()=>new f(window.location.origin),createTransport:i=>new x(i)};async function E(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{E as initializePairingPage};
@@ -1 +1 @@
1
- import{B as c,e as f,i as w,d as m}from"./backend-protocol-BvNAsFvb.js";import{l as y}from"./device-identity-C_w2NbSk.js";import{R as g}from"./rendezvous-trust-api-xLnrhe-v.js";import{W as v}from"./webrtc-frame-transport-Drpqhhdy.js";import{W as k}from"./ws-agent-adapter-Cauprivt.js";import"./__vite-browser-external-DGfF_P2w.js";import"./index-bOr1Y6MY.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=m(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 R{constructor(e,t={loadIdentity:()=>y(),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{R as RemoteBackendManager};
1
+ import{B as c,e as f,i as w,d as m}from"./backend-protocol-BvNAsFvb.js";import{l as y}from"./device-identity-C_w2NbSk.js";import{R as g}from"./rendezvous-trust-api-Bv6fZVJA.js";import{W as v}from"./webrtc-frame-transport-Drpqhhdy.js";import{W as k}from"./ws-agent-adapter-BUykBiJL.js";import"./__vite-browser-external-DGfF_P2w.js";import"./index-CG96lus1.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=m(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 R{constructor(e,t={loadIdentity:()=>y(),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{R as RemoteBackendManager};
@@ -0,0 +1 @@
1
+ import{s}from"./device-identity-C_w2NbSk.js";class h{constructor(e,n=globalThis.fetch.bind(globalThis)){const t=new URL(e);t.pathname="/",t.search="",t.hash="",this.baseUrl=t.toString(),this.fetch=n}async createPairingTicket(e,n,t=`c_${crypto.randomUUID()}`){const r=await this.createChallenge({purpose:"pair",deviceId:e.deviceId,devicePublicKey:e.publicKey,backendId:n.backendId,connectionId:t,pairId:n.pairId});return this.completeTicket(`v1/pairings/${encodeURIComponent(n.pairId)}/tickets`,e,r)}async createConnectionTicket(e,n,t=`c_${crypto.randomUUID()}`){const r=await this.createChallenge({purpose:"connect",deviceId:e.deviceId,backendId:n,connectionId:t});return this.completeTicket("v1/connections/tickets",e,r)}async listAuthorizedBackends(e){const n=await this.createChallenge({purpose:"discover",deviceId:e.deviceId}),t=await this.completeSignedRequest("v1/accounts/backends",e,n);if(!Array.isArray(t.backends)||!t.backends.every(d))throw new Error("Rendezvous returned an invalid backend list");return t.backends}async createDeviceInvite(e){const n=await this.createChallenge({purpose:"create_device_invite",deviceId:e.deviceId}),t=await this.completeSignedRequest("v1/device-invites",e,n);if(!a(t))throw new Error("Rendezvous returned an invalid device invite");const r=new URL(`invite/${encodeURIComponent(t.inviteId)}`,this.baseUrl);return r.hash=new URLSearchParams({secret:t.secret}).toString(),{url:r.toString(),expiresAt:t.expiresAt}}async acceptDeviceInvite(e,n){const t=await this.createChallenge({purpose:"accept_device_invite",deviceId:e.deviceId,devicePublicKey:e.publicKey,pairId:n.inviteId}),r=await this.completeSignedRequest(`v1/device-invites/${encodeURIComponent(n.inviteId)}/accept`,e,t,{secret:n.secret});if(!o(r,e.deviceId))throw new Error("Rendezvous returned an invalid device invite acceptance");return r}async revokeDevice(e,n,t){const r=await this.createChallenge({purpose:"revoke_device",deviceId:e.deviceId,backendId:n,targetDeviceId:t});await this.completeMutation("v1/revocations/devices",e,r)}async revokeBackend(e,n){const t=await this.createChallenge({purpose:"revoke_backend",deviceId:e.deviceId,backendId:n});await this.completeMutation("v1/revocations/backends",e,t)}async createChallenge(e){return this.requestJson("v1/auth/challenges",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(e)})}async completeTicket(e,n,t){const r=await s(n,t),i=await this.requestJson(e,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({challengeId:t.challengeId,signature:r})});if(!i||typeof i.ticket!="string"||!Array.isArray(i.iceServers))throw new Error("Rendezvous returned an invalid connection ticket");return i}async completeMutation(e,n,t){await this.completeSignedRequest(e,n,t)}async completeSignedRequest(e,n,t,r={}){const i=await s(n,t);return this.requestJson(e,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({challengeId:t.challengeId,signature:i,...r})})}async requestJson(e,n){const t=await this.fetch(new URL(e,this.baseUrl),n),r=await t.json().catch(()=>{});if(!t.ok)throw new Error(typeof r?.error=="string"?r.error:`Rendezvous request failed (${t.status})`);return r}}function a(c){if(!c||typeof c!="object"||Array.isArray(c))return!1;const e=c;return typeof e.inviteId=="string"&&e.inviteId.length>0&&typeof e.secret=="string"&&e.secret.length>0&&Number.isSafeInteger(e.expiresAt)&&e.expiresAt>0}function o(c,e){if(!c||typeof c!="object"||Array.isArray(c))return!1;const n=c;return typeof n.accountId=="string"&&n.accountId.length>0&&n.deviceId===e}function d(c){if(!c||typeof c!="object"||Array.isArray(c))return!1;const e=c;return typeof e.backendId=="string"&&e.backendId.length>0&&(e.name===void 0||typeof e.name=="string")&&(e.softwareVersion===void 0||typeof e.softwareVersion=="string")&&Array.isArray(e.protocolVersions)&&e.protocolVersions.every(n=>Number.isSafeInteger(n)&&n>0)&&typeof e.online=="boolean"}function l(c){const e=new URL(c),n=/^\/pair\/([^/]+)$/u.exec(e.pathname),t=new URLSearchParams(e.hash.replace(/^#/u,"")),r=t.get("backend"),i=t.get("secret");if(!n?.[1]||!r||!i)throw new Error("Pairing URL is malformed");return{pairId:decodeURIComponent(n[1]),backendId:r,secret:i}}function v(c){const e=new URL(c),n=/^\/invite\/([^/]+)$/u.exec(e.pathname),t=new URLSearchParams(e.hash.replace(/^#/u,"")).get("secret");if(!n?.[1]||!t)throw new Error("Device invite URL is malformed");return{inviteId:decodeURIComponent(n[1]),secret:t}}export{h as R,l as a,v as p};
@@ -0,0 +1 @@
1
+ import{B as a}from"./backend-protocol-BvNAsFvb.js";const u={model:void 0,thinkingLevel:"off",messages:[],isStreaming:!1};class C{constructor(e,t,i){if(this.manager=t,this.contexts=new Map,this.endpoint="webrtc",this.connectedOnce=!1,this.connectionListeners=new Set,this.extensionStatusListeners=new Set,this.globalStatusListeners=new Set,this.steeringQueueListeners=new Set,this.sessionListeners=new Set,this.contentListeners=new Set,this.statusListeners=new Set,this.sessionsChangedListeners=new Set,this.piInstallRequiredListeners=new Set,this.workspaceListeners=new Set,e.length===0)throw new Error("No authorized backends are available");for(const s of e)this.contexts.set(s.backendId,{descriptor:{...s,protocolVersions:[...s.protocolVersions]},sessions:[],...this.isCompatible(s)?{}:{error:`Update required for semantic protocol v${a}`},initialized:!1,unsubscribers:[]});this.activeId=this.chooseInitialBackend(i),this.ensureClient(this.requireContext(this.activeId))}get state(){return this.activeClient?.state??u}get sessionId(){return this.activeClient?.sessionId??""}get sessionFile(){return this.activeClient?.sessionFile}get sessionName(){return this.activeClient?.sessionName}get sessionStatus(){return this.activeClient?.sessionStatus??"virtual"}get isConnected(){return this.activeClient?.isConnected??!1}get isReconnecting(){return this.activeClient?.isReconnecting??!1}get pendingToolCallIds(){return this.activeClient?.pendingToolCallIds??new Set}get toolCallTimings(){return this.activeClient?.toolCallTimings??{}}get steeringQueue(){return this.activeClient?.steeringQueue??[]}get extensionStatuses(){return this.activeClient?.extensionStatuses??new Map}get cwd(){return this.activeClient?.cwd}get activeBackendId(){return this.activeId}get workspaceBackends(){return[...this.contexts.values()].map(e=>({...e.descriptor,protocolVersions:[...e.descriptor.protocolVersions],connected:e.client?.isConnected??!1,reconnecting:e.client?.isReconnecting??!1,...e.error?{error:e.error}:{}}))}get optimisticSessions(){return[...this.contexts.entries()].flatMap(([e,t])=>(t.client?.optimisticSessions??[]).map(i=>({...i,backendId:e})))}get virtualSessionInfo(){const e=this.activeClient?.virtualSessionInfo;return e?{...e,backendId:this.activeId}:void 0}async connect(e){this.endpoint=e;const t=[...this.contexts.values()].filter(n=>n.descriptor.online&&this.isCompatible(n.descriptor));if(t.length===0)throw this.emitWorkspaceChange(),new Error("All authorized backends are offline");const i=t.map(async n=>{const l=this.ensureClient(n);try{return await l.connect(e),n.error=void 0,await this.refreshContextSessions(n),n}catch(h){throw n.error=c(h),this.emitWorkspaceChange(),h}});let s;try{s=await Promise.any(i)}catch{throw this.emitWorkspaceChange(),new Error("Could not connect to any authorized backend")}this.connectedOnce=!0,this.activeClient?.isConnected||this.setActiveBackend(s.descriptor.backendId),this.emitWorkspaceChange(),Promise.allSettled(i)}disconnect(){for(const e of this.contexts.values())e.client?.disconnect()}onConnectionChange(e){return r(this.connectionListeners,e)}onExtensionStatusChange(e){return r(this.extensionStatusListeners,e)}onGlobalStatusChange(e){return r(this.globalStatusListeners,e)}onSteeringQueueChange(e){return r(this.steeringQueueListeners,e)}onSessionChange(e){return r(this.sessionListeners,e)}onContentChange(e){return r(this.contentListeners,e)}onStatusChange(e){return r(this.statusListeners,e)}onSessionsChanged(e){return r(this.sessionsChangedListeners,e)}onPiInstallRequired(e){return r(this.piInstallRequiredListeners,e)}onWorkspaceChange(e){return r(this.workspaceListeners,e)}getSessionStatus(e,t=this.activeId){return this.contexts.get(t)?.client?.getSessionStatus(e)}reportError(e,t){this.requireActiveClient().reportError(e,t)}fetchAvailableModels(){return this.requireActiveClient().fetchAvailableModels()}installPi(){return this.requireActiveClient().installPi()}async loadDefaultModel(){await this.requireActiveClient().loadDefaultModel(),this.requireContext(this.activeId).initialized=!0}setCwd(e){this.requireActiveClient().setCwd(e)}prompt(e,t){return this.requireActiveClient().prompt(e,t)}fetchCommands(){return this.requireActiveClient().fetchCommands()}abort(){this.requireActiveClient().abort()}hardKill(){this.requireActiveClient().hardKill()}steer(e){this.requireActiveClient().steer(e)}removeSteering(e){this.requireActiveClient().removeSteering(e)}setModel(e){this.requireActiveClient().setModel(e)}setThinkingLevel(e){this.requireActiveClient().setThinkingLevel(e)}getForkMessages(){return this.requireActiveClient().getForkMessages()}fork(e){return this.requireActiveClient().fork(e)}forkAndPrompt(e,t){return this.requireActiveClient().forkAndPrompt(e,t)}async switchSession(e,t,i=this.activeId){const s=await this.activateContext(i,!1);await this.ensureConversationInitialized(s),await this.requireContextClient(s).switchSession(e,t)}async newSession(e,t=this.activeId){const i=await this.activateContext(t,!1);await this.ensureConversationInitialized(i),await this.requireContextClient(i).newSession(e)}async activateBackend(e){const t=await this.activateContext(e);await this.ensureConversationInitialized(t)}async removeBackend(e){const t=this.requireContext(e);await this.manager.revokeBackend(e);for(const i of t.unsubscribers)i();if(t.client?.disconnect(),this.contexts.delete(e),this.contexts.size===0){this.emitWorkspaceChange();return}if(this.activeId===e){const i=[...this.contexts.values()].find(s=>s.client?.isConnected)??[...this.contexts.values()].find(s=>s.descriptor.online)??[...this.contexts.values()][0];this.setActiveBackend(i.descriptor.backendId)}this.emitWorkspaceChange()}getConnectionDiagnostics(){return this.requireActiveClient().getConnectionDiagnostics?.()??Promise.resolve(void 0)}getBackendConnectionDiagnostics(e){const t=this.requireContext(e);return this.ensureClient(t).getConnectionDiagnostics?.()??Promise.resolve(void 0)}async listSessions(){return[...this.contexts.entries()].flatMap(([e,t])=>t.sessions.map(i=>({...i,backendId:e})))}async deleteSession(e,t=this.activeId){const i=this.requireContext(t),s=this.ensureClient(i);s.isConnected||await this.connectContext(i),await s.deleteSession(e),i.sessions=i.sessions.filter(n=>n.path!==e),this.emitSessionsChanged(t,"")}async browseDirectory(e,t=this.activeId){return this.connectedClient(t).then(i=>i.browseDirectory(e))}async createDirectory(e,t,i=this.activeId){return this.connectedClient(i).then(s=>s.createDirectory(e,t))}listForkMessages(e){return this.requireActiveClient().listForkMessages(e)}getRawSession(e){return this.requireActiveClient().getRawSession(e)}getFileContent(e,t){return this.requireActiveClient().getFileContent(e,t)}createFileUpload(e){return this.requireActiveClient().createFileUpload(e)}appendFileUpload(e){return this.requireActiveClient().appendFileUpload(e)}completeFileUpload(e){return this.requireActiveClient().completeFileUpload(e)}getLocalSettings(){return this.requireActiveClient().getLocalSettings()}validateLocalSettings(e){return this.requireActiveClient().validateLocalSettings(e)}patchLocalSettings(e){return this.requireActiveClient().patchLocalSettings(e)}saveLocalSettings(e){return this.requireActiveClient().saveLocalSettings(e)}getUpdates(){return this.requireActiveClient().getUpdates()}runUpdate(e){return this.requireActiveClient().runUpdate(e)}getCapabilities(){return this.requireActiveClient().getCapabilities?.()??Promise.reject(new Error("Backend capabilities are unavailable"))}get activeClient(){return this.contexts.get(this.activeId)?.client}chooseInitialBackend(e){const t=[...this.contexts.values()].filter(i=>this.isCompatible(i.descriptor));if(t.length===0)throw new Error(`No authorized backend supports semantic protocol v${a}`);return e&&t.some(i=>i.descriptor.backendId===e)?e:t.find(i=>i.descriptor.online)?.descriptor.backendId??t[0].descriptor.backendId}isCompatible(e){return e.protocolVersions.includes(a)}requireContext(e){const t=this.contexts.get(e);if(!t)throw new Error("This browser is not authorized for the requested backend");return t}requireActiveClient(){const e=this.activeClient;if(!e)throw new Error("No active backend is available");return e}requireContextClient(e){return e.client??this.ensureClient(e)}ensureClient(e){if(!this.isCompatible(e.descriptor))throw new Error(e.error||"Backend update required");if(e.client)return e.client;const t=e.descriptor.backendId,i=this.manager.getClient(t);return e.client=i,e.unsubscribers.push(i.onConnectionChange(s=>{e.error=s?void 0:e.error,s&&this.connectedOnce&&this.refreshContextSessions(e),this.emitWorkspaceChange(),this.activeId===t&&this.emit(this.connectionListeners,s)}),i.onExtensionStatusChange(()=>{this.activeId===t&&this.emit(this.extensionStatusListeners)}),i.onGlobalStatusChange(()=>{this.emit(this.globalStatusListeners),this.emitWorkspaceChange()}),i.onSteeringQueueChange(()=>{this.activeId===t&&this.emit(this.steeringQueueListeners)}),i.onSessionChange(()=>{this.refreshContextSessions(e),this.activeId===t&&this.emit(this.sessionListeners)}),i.onContentChange(()=>{this.activeId===t&&this.emit(this.contentListeners)}),i.onStatusChange(()=>{this.activeId===t&&this.emit(this.statusListeners)}),i.onSessionsChanged(s=>{this.refreshContextSessions(e).finally(()=>this.emitSessionsChanged(t,s))}),i.onPiInstallRequired(s=>{this.activeId===t&&this.emit(this.piInstallRequiredListeners,s)})),i}async activateContext(e,t=!0){const i=this.requireContext(e),s=this.ensureClient(i);return s.isConnected||await this.connectContext(i),this.activeId!==e&&(await this.activeClient?.setSessionSubscriptionActive?.(!1),this.setActiveBackend(e),t&&await s.setSessionSubscriptionActive?.(!0)),i}async connectContext(e){const t=this.ensureClient(e);try{await t.connect(this.endpoint),e.error=void 0,await this.refreshContextSessions(e)}catch(i){throw e.error=c(i),this.emitWorkspaceChange(),i}}async connectedClient(e){const t=this.requireContext(e),i=this.ensureClient(t);return i.isConnected||await this.connectContext(t),i}async ensureConversationInitialized(e){if(e.initialized)return;if(e.initializing)return e.initializing;const t=this.requireContextClient(e);return e.initializing=(async()=>{try{await t.fetchAvailableModels()}catch{}await t.loadDefaultModel(),e.initialized=!0})().finally(()=>{e.initializing=void 0}),e.initializing}async refreshContextSessions(e){const t=e.client;if(t?.isConnected)try{e.sessions=await t.listSessions(),e.error=void 0,this.emitWorkspaceChange()}catch(i){e.error=c(i),this.emitWorkspaceChange()}}setActiveBackend(e){this.activeId=e,this.connectedOnce&&window.location.pathname.startsWith("/backend/")&&window.history.replaceState(null,"","/"),this.emitWorkspaceChange(),this.emit(this.connectionListeners,this.isConnected),this.emit(this.sessionListeners),this.emit(this.contentListeners),this.emit(this.statusListeners),this.emit(this.extensionStatusListeners),this.emit(this.steeringQueueListeners),this.emit(this.sessionsChangedListeners,"__local_settings__")}emitSessionsChanged(e,t){this.emit(this.sessionsChangedListeners,t),this.emitWorkspaceChange()}emitWorkspaceChange(){this.emit(this.workspaceListeners)}emit(e,...t){for(const i of e)i(...t)}}function r(o,e){return o.add(e),()=>o.delete(e)}function c(o){return o instanceof Error?o.message:String(o)}export{C as WorkspaceBackendClient};
@@ -1,4 +1,4 @@
1
- import{_ as ie}from"./index-bOr1Y6MY.js";import{i as oe}from"./backend-protocol-BvNAsFvb.js";import{D as ae}from"./__vite-browser-external-DGfF_P2w.js";function re(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,l,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,T,F,R]=[r,l,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&T^~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=T,T=P,P=N+V|0,N=L,L=$,$=f,f=V+ne|0}r=r+f|0,l=l+$|0,u=u+L|0,_=_+N|0,y=y+P|0,v=v+T|0,w=w+F|0,c=c+R|0}return[r,l,u,_,y,v,w,c].map(S=>(S>>>0).toString(16).padStart(8,"0")).join("")}async function ce(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-DGfF_P2w.js").then(t=>t._);return{createHash:s}},[]);return e("sha256").update(i,"utf8").digest("hex")}return re(i)}function he(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=he(t,r.patches),n=r.hash}const a=await ce(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 de=10*6e4,ue=de+9e4,J=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 h(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:h(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 A(i,e){return Array.isArray(i)||p(e,"expected an array"),i}function H(i,e){return A(i,e).map((s,t)=>h(s,`${e}[${t}]`))}function O(i,e){const s=m(i,e);for(const[t,n]of Object.entries(s))h(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 ge(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 pe(i,e){const s=m(i,e);return h(s.provider,`${e}.provider`,!1),h(s.modelId,`${e}.modelId`,!1),s}function W(i,e){const s=m(i,e);h(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 me(i,e){if(i.protocolVersion!==J){const s=i.protocolVersion===void 0?"missing":JSON.stringify(i.protocolVersion);p(`${e}.protocolVersion`,`unsupported protocol version ${s}; expected ${J}`,"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 fe(i,e){A(i,e).forEach((s,t)=>{const n=m(s,`${e}[${t}]`);h(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 _e(i,e){const s=m(i,e);h(s.sessionFile,`${e}.sessionFile`,!1),h(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 Se(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":h(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":A(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":_e(t,s);break;case"fork":h(t.text,`${s}.text`),b(t.cancelled,`${s}.cancelled`),t.newSessionPath!==null&&h(t.newSessionPath,`${s}.newSessionPath`,!1);break;case"get_commands":fe(t.commands,`${s}.commands`);break;case"reload_processes":C(t.killed,`${s}.killed`),C(t.draining,`${s}.draining`);break}}const ye=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 ve(i,e){const s=h(i,e,!1);return ye.has(s)||p(e,`unknown response command '${s}'`,"unknown_message"),s}function we(i,e){A(i,e).forEach((s,t)=>{const n=m(s,`${e}[${t}]`);C(n.offset,`${e}[${t}].offset`),C(n.deleteCount,`${e}[${t}].deleteCount`),h(n.insert,`${e}[${t}].insert`)})}function Ce(i){const e=X(i);if(!e.ok)return e;const s=e.value;try{const t=m(s,"$message");me(t,"$message");const n=h(t.type,"$message.type",!1);switch(n){case"response":{if(b(t.success,"$message.success")){h(t.id,"$message.id",!1);const a=ve(t.command,"$message.command");Se(a,t.data,"$message.data")}else t.id!==null&&h(t.id,"$message.id",!1),h(t.command,"$message.command",!1),h(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"),ge(t.steeringQueues,"$message.steeringQueues"),O(t.providerUsageStatuses,"$message.providerUsageStatuses");break;case"pi_install_required":h(t.command,"$message.command",!1),b(t.installable,"$message.installable"),b(t.installing,"$message.installing"),h(t.message,"$message.message",!1);break;case"session_status_change":h(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":h(t.sessionPath,"$message.sessionPath",!1),O(t.statuses,"$message.statuses");break;case"session_sync":h(t.sessionPath,"$message.sessionPath",!1),C(t.revision,"$message.revision"),h(t.hash,"$message.hash",!1),t.op==="full"?h(t.data,"$message.data"):t.op==="delta"?(h(t.baseHash,"$message.baseHash",!1),we(t.patches,"$message.patches")):p("$message.op","expected full or delta");break;case"control_state":h(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":h(t.sessionPath,"$message.sessionPath",!1),k(t.cwd,"$message.cwd"),k(t.firstMessage,"$message.firstMessage");break;case"sessions_changed":h(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 $e(i){const e=X(i);if(!e.ok)return e;const s=e.value;try{const t=m(s,"$session");A(t.messages,"$session.messages").forEach((o,a)=>{const r=m(o,`$session.messages[${a}]`);h(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&&pe(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 Pe(i,e){return JSON.stringify({protocolVersion:J,id:e,...i})}function ke(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 D(i){const e=E(i);if(!(!i.provider||!e))return{provider:i.provider,modelId:e}}function be(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 Le(i){if(!be(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=Le(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 xe extends Error{constructor(e,s,t){super(e),this.status=s,this.details=t,this.name="BackendApiError"}}class Ae{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(`
1
+ import{_ as ie}from"./index-CG96lus1.js";import{i as oe}from"./backend-protocol-BvNAsFvb.js";import{D as ae}from"./__vite-browser-external-DGfF_P2w.js";function re(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,l,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,T,F,R]=[r,l,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&T^~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=T,T=P,P=N+V|0,N=L,L=$,$=f,f=V+ne|0}r=r+f|0,l=l+$|0,u=u+L|0,_=_+N|0,y=y+P|0,v=v+T|0,w=w+F|0,c=c+R|0}return[r,l,u,_,y,v,w,c].map(S=>(S>>>0).toString(16).padStart(8,"0")).join("")}async function ce(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-DGfF_P2w.js").then(t=>t._);return{createHash:s}},[]);return e("sha256").update(i,"utf8").digest("hex")}return re(i)}function he(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=he(t,r.patches),n=r.hash}const a=await ce(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 de=10*6e4,ue=de+9e4,J=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 h(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:h(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 A(i,e){return Array.isArray(i)||p(e,"expected an array"),i}function H(i,e){return A(i,e).map((s,t)=>h(s,`${e}[${t}]`))}function O(i,e){const s=m(i,e);for(const[t,n]of Object.entries(s))h(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 ge(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 pe(i,e){const s=m(i,e);return h(s.provider,`${e}.provider`,!1),h(s.modelId,`${e}.modelId`,!1),s}function W(i,e){const s=m(i,e);h(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 me(i,e){if(i.protocolVersion!==J){const s=i.protocolVersion===void 0?"missing":JSON.stringify(i.protocolVersion);p(`${e}.protocolVersion`,`unsupported protocol version ${s}; expected ${J}`,"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 fe(i,e){A(i,e).forEach((s,t)=>{const n=m(s,`${e}[${t}]`);h(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 _e(i,e){const s=m(i,e);h(s.sessionFile,`${e}.sessionFile`,!1),h(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 Se(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":h(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":A(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":_e(t,s);break;case"fork":h(t.text,`${s}.text`),b(t.cancelled,`${s}.cancelled`),t.newSessionPath!==null&&h(t.newSessionPath,`${s}.newSessionPath`,!1);break;case"get_commands":fe(t.commands,`${s}.commands`);break;case"reload_processes":C(t.killed,`${s}.killed`),C(t.draining,`${s}.draining`);break}}const ye=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 ve(i,e){const s=h(i,e,!1);return ye.has(s)||p(e,`unknown response command '${s}'`,"unknown_message"),s}function we(i,e){A(i,e).forEach((s,t)=>{const n=m(s,`${e}[${t}]`);C(n.offset,`${e}[${t}].offset`),C(n.deleteCount,`${e}[${t}].deleteCount`),h(n.insert,`${e}[${t}].insert`)})}function Ce(i){const e=X(i);if(!e.ok)return e;const s=e.value;try{const t=m(s,"$message");me(t,"$message");const n=h(t.type,"$message.type",!1);switch(n){case"response":{if(b(t.success,"$message.success")){h(t.id,"$message.id",!1);const a=ve(t.command,"$message.command");Se(a,t.data,"$message.data")}else t.id!==null&&h(t.id,"$message.id",!1),h(t.command,"$message.command",!1),h(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"),ge(t.steeringQueues,"$message.steeringQueues"),O(t.providerUsageStatuses,"$message.providerUsageStatuses");break;case"pi_install_required":h(t.command,"$message.command",!1),b(t.installable,"$message.installable"),b(t.installing,"$message.installing"),h(t.message,"$message.message",!1);break;case"session_status_change":h(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":h(t.sessionPath,"$message.sessionPath",!1),O(t.statuses,"$message.statuses");break;case"session_sync":h(t.sessionPath,"$message.sessionPath",!1),C(t.revision,"$message.revision"),h(t.hash,"$message.hash",!1),t.op==="full"?h(t.data,"$message.data"):t.op==="delta"?(h(t.baseHash,"$message.baseHash",!1),we(t.patches,"$message.patches")):p("$message.op","expected full or delta");break;case"control_state":h(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":h(t.sessionPath,"$message.sessionPath",!1),k(t.cwd,"$message.cwd"),k(t.firstMessage,"$message.firstMessage");break;case"sessions_changed":h(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 $e(i){const e=X(i);if(!e.ok)return e;const s=e.value;try{const t=m(s,"$session");A(t.messages,"$session.messages").forEach((o,a)=>{const r=m(o,`$session.messages[${a}]`);h(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&&pe(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 Pe(i,e){return JSON.stringify({protocolVersion:J,id:e,...i})}function ke(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 D(i){const e=E(i);if(!(!i.provider||!e))return{provider:i.provider,modelId:e}}function be(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 Le(i){if(!be(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=Le(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 xe extends Error{constructor(e,s,t){super(e),this.status=s,this.details=t,this.name="BackendApiError"}}class Ae{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
2
  `):t;return new xe(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 qe(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 l of t){const u=s.indexOf(l,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 Oe(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 je(i){switch(i){case"builtin":return"Built-in";case"extension":return"Extension";case"prompt":return"Prompt";case"skill":return"Skill"}}const z=1,Fe=1e4;class Ie{constructor(e={}){this.socket=null,this.reconnecting=!1,this.reconnectAttempt=0,this.manuallyClosed=!1,this.decoder=new ae,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.decoder.reset(),this.socket?.close(e,s)}attachFrameHandler(e){e.onmessage=s=>{try{const t=this.decoder.accept(String(s.data));if(t===void 0)return;for(const n of this.frameListeners)n(t)}catch{e.close(1002,"Invalid carrier frame")}}}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),this.decoder.reset(),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),Fe);this.reconnectTimer=this.schedule(()=>{this.reconnectTimer=void 0,this.openSocket(!0).catch(()=>{})},e)}}const Me="provider-usage";function Ee(i){const e=typeof i?.provider=="string"?i.provider.toLowerCase():"";if(e.includes("codex"))return"codex";if(e.includes("anthropic"))return"anthropic"}class De{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._sessionSubscriptionActive=!0,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 Ie({socket:e.socket,createWebSocket:e.createWebSocket}),this.api=e.api??new Ae({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=Ee(this._state.model),t=s?this._providerUsageStatuses.get(s):this._providerUsageStatuses.size===1?this._providerUsageStatuses.values().next().value:void 0;return t&&e.set(Me,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()}async setSessionSubscriptionActive(e){if(e!==this._sessionSubscriptionActive){if(this._sessionSubscriptionActive=e,this._sessionNonce++,this._awaitingFullSync=void 0,this.clearSessionSyncQueue(),!e){await this.subscribeToSession(void 0);return}this._sessionPath&&this._sessionStatus!=="virtual"&&await this.requestFullSessionSync(this._sessionPath)}}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._sessionSubscriptionActive&&this._sessionPath&&this._sessionStatus!=="virtual"&&(this._awaitingFullSync=void 0,this.requestFullSessionSync(this._sessionPath)),this.refreshSessionStatuses()}async syncStateOnFocus(){this.refreshSessionStatuses(),this._sessionSubscriptionActive&&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=Ce(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("_"),l=r.length>1?r.slice(1).join("_"):a;this._optimisticSessions.set(t.sessionPath,{id:l,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:ke(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=$e(a.data);if(!r.ok){this.handleProtocolError(r.error);return}const l=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=[...l.messages??[],...this._localAssistantMessages],this._state.isStreaming=l.isStreaming??!1,this._sessionStatus!=="virtual"&&(this._sessionStatus=this._state.isStreaming?"attached":"detached"),this._pendingToolCallIds=new Set(l.pendingToolCalls??[]),this._toolCallTimings=l.toolCallTimings??{},this.applyAuthoritativeControlState(l.model,l.thinkingLevel),Array.isArray(l.steeringQueue)&&(l.steeringQueue.length>0?this._steeringQueues.set(s,[...l.steeringQueue]):this._steeringQueues.delete(s)),this._state.error=l.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"?ue: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(Pe(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=D(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 l=this._state.model;if(!l)throw new Error(`BUG: effective model is undefined when sending prompt. sessionStatus=${this._sessionStatus}, sessionPath=${this._sessionPath}`);const u=D(l);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(`
@@ -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-bOr1Y6MY.js"></script>
21
- <link rel="stylesheet" crossorigin href="/assets/index-DtrSEX3O.css">
20
+ <script type="module" crossorigin src="/assets/index-CG96lus1.js"></script>
21
+ <link rel="stylesheet" crossorigin href="/assets/index-VW8gJNOU.css">
22
22
  </head>
23
23
  <body>
24
24
  <div id="app">
@@ -125,6 +125,15 @@ export function createRendezvousServer(options = {}) {
125
125
  });
126
126
  response.json({ backends });
127
127
  }));
128
+ app.post("/v1/device-invites", route((request, response) => {
129
+ const { challengeId, signature } = requireSignatureBody(request.body);
130
+ response.json(trustStore.createDeviceInvite(challengeId, signature));
131
+ }));
132
+ app.post("/v1/device-invites/:inviteId/accept", route((request, response) => {
133
+ const { challengeId, signature } = requireSignatureBody(request.body);
134
+ const secret = requireBodyString(request.body, "secret");
135
+ response.json(trustStore.acceptDeviceInvite(challengeId, signature, param(request, "inviteId"), secret));
136
+ }));
128
137
  app.post("/v1/revocations/devices", route((request, response) => {
129
138
  const { challengeId, signature } = requireSignatureBody(request.body);
130
139
  const result = trustStore.revokeDevice(challengeId, signature);
@@ -144,7 +153,7 @@ export function createRendezvousServer(options = {}) {
144
153
  if (clientDist && existsSync(path.join(clientDist, "index.html"))) {
145
154
  app.use(express.static(clientDist));
146
155
  app.use((request, response, next) => {
147
- if (request.method === "GET" && (/^\/pair\/[^/]+$/u.test(request.path) || /^\/backend\/[^/]+$/u.test(request.path))) {
156
+ if (request.method === "GET" && (/^\/(?:pair|invite)\/[^/]+$/u.test(request.path) || /^\/backend\/[^/]+$/u.test(request.path))) {
148
157
  response.sendFile(path.join(clientDist, "index.html"));
149
158
  return;
150
159
  }
@@ -223,6 +232,14 @@ function requireSignatureBody(value) {
223
232
  }
224
233
  return { challengeId: body.challengeId, signature: body.signature };
225
234
  }
235
+ function requireBodyString(value, name) {
236
+ if (!value || typeof value !== "object" || Array.isArray(value))
237
+ throw new Error("Request body must be an object");
238
+ const result = value[name];
239
+ if (typeof result !== "string" || !result)
240
+ throw new Error(`${name} is required`);
241
+ return result;
242
+ }
226
243
  function envUrls(name) {
227
244
  return (process.env[name] ?? "").split(",").map((value) => value.trim()).filter(Boolean);
228
245
  }
@@ -1,4 +1,4 @@
1
- import { createHmac, createPrivateKey, createPublicKey, generateKeyPairSync, randomBytes, randomUUID, sign, } from "node:crypto";
1
+ import { createHash, createHmac, createPrivateKey, createPublicKey, generateKeyPairSync, randomBytes, randomUUID, sign, timingSafeEqual, } from "node:crypto";
2
2
  import { chmodSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
3
3
  import { homedir } from "node:os";
4
4
  import path from "node:path";
@@ -10,8 +10,10 @@ const SIGNING_IDENTITY_VERSION = 1;
10
10
  const ACCOUNT_ID_PREFIX = "a_";
11
11
  const TICKET_ID_PREFIX = "t_";
12
12
  const CHALLENGE_ID_PREFIX = "ch_";
13
+ const DEVICE_INVITE_ID_PREFIX = "invite_";
13
14
  const DEFAULT_CHALLENGE_TTL_MS = 2 * 60_000;
14
15
  const DEFAULT_TICKET_TTL_MS = 60_000;
16
+ const DEVICE_INVITE_TTL_MS = 10 * 60_000;
15
17
  export class IceServerProvider {
16
18
  staticServers;
17
19
  turn;
@@ -186,6 +188,51 @@ export class RendezvousTrustStore {
186
188
  const device = this.requireActiveDevice(challenge.deviceId);
187
189
  return [...this.state.accounts[device.accountId].backendIds];
188
190
  }
191
+ createDeviceInvite(challengeId, signature) {
192
+ const challenge = this.consumeChallenge(challengeId, signature, "create_device_invite");
193
+ const actor = this.requireActiveDevice(challenge.deviceId);
194
+ const secret = randomBytes(32).toString("base64url");
195
+ const invite = {
196
+ inviteId: `${DEVICE_INVITE_ID_PREFIX}${randomUUID()}`,
197
+ secretHash: hashDeviceInviteSecret(secret),
198
+ accountId: actor.accountId,
199
+ createdByDeviceId: actor.deviceId,
200
+ createdAt: this.now(),
201
+ expiresAt: this.now() + DEVICE_INVITE_TTL_MS,
202
+ };
203
+ this.state.deviceInvites[invite.inviteId] = invite;
204
+ this.persist();
205
+ return { inviteId: invite.inviteId, secret, expiresAt: invite.expiresAt };
206
+ }
207
+ acceptDeviceInvite(challengeId, signature, inviteId, secret) {
208
+ const challenge = this.consumeChallenge(challengeId, signature, "accept_device_invite");
209
+ if (challenge.pairId !== inviteId)
210
+ throw new Error("Device challenge does not match this invite");
211
+ const invite = this.state.deviceInvites[inviteId];
212
+ if (!invite || invite.expiresAt <= this.now())
213
+ throw new Error("Device invite is missing or expired");
214
+ if (!secretsEqual(hashDeviceInviteSecret(secret), invite.secretHash))
215
+ throw new Error("Invalid device invite secret");
216
+ const existingDevice = this.state.devices[challenge.deviceId];
217
+ if (existingDevice) {
218
+ if (existingDevice.accountId === invite.accountId)
219
+ throw new Error("This browser already has access to this account");
220
+ throw new Error("This browser already belongs to another account");
221
+ }
222
+ const account = this.state.accounts[invite.accountId];
223
+ if (!account)
224
+ throw new Error("Device invite account is missing");
225
+ account.deviceIds.push(challenge.deviceId);
226
+ this.state.devices[challenge.deviceId] = {
227
+ deviceId: challenge.deviceId,
228
+ publicKey: challenge.devicePublicKey,
229
+ accountId: account.accountId,
230
+ createdAt: this.now(),
231
+ };
232
+ delete this.state.deviceInvites[inviteId];
233
+ this.persist();
234
+ return { accountId: account.accountId, deviceId: challenge.deviceId };
235
+ }
189
236
  revokeDevice(challengeId, signature) {
190
237
  const challenge = this.consumeChallenge(challengeId, signature, "revoke_device");
191
238
  const actor = this.requireActiveDevice(challenge.deviceId);
@@ -242,12 +289,14 @@ export class RendezvousTrustStore {
242
289
  throw new Error("Device public key mismatch");
243
290
  return { deviceId: device.deviceId, publicKey: device.publicKey };
244
291
  }
245
- if (request.purpose === "pair" && request.devicePublicKey && deriveDeviceId(request.devicePublicKey) === request.deviceId) {
292
+ if ((request.purpose === "pair" || request.purpose === "accept_device_invite")
293
+ && request.devicePublicKey
294
+ && deriveDeviceId(request.devicePublicKey) === request.deviceId) {
246
295
  return { deviceId: request.deviceId, publicKey: request.devicePublicKey };
247
296
  }
248
297
  throw new Error("Device is unknown or revoked");
249
298
  }
250
- if (request.purpose !== "pair" || !request.devicePublicKey) {
299
+ if ((request.purpose !== "pair" && request.purpose !== "accept_device_invite") || !request.devicePublicKey) {
251
300
  throw new Error("An existing device is required for this challenge");
252
301
  }
253
302
  return { deviceId: deriveDeviceId(request.devicePublicKey), publicKey: request.devicePublicKey };
@@ -256,18 +305,28 @@ export class RendezvousTrustStore {
256
305
  if (request.purpose !== "pair"
257
306
  && request.purpose !== "connect"
258
307
  && request.purpose !== "discover"
308
+ && request.purpose !== "create_device_invite"
309
+ && request.purpose !== "accept_device_invite"
259
310
  && request.purpose !== "revoke_device"
260
311
  && request.purpose !== "revoke_backend") {
261
312
  throw new Error("Unsupported device challenge purpose");
262
313
  }
263
- if (request.purpose !== "discover" && !request.backendId)
314
+ if (request.purpose !== "discover"
315
+ && request.purpose !== "create_device_invite"
316
+ && request.purpose !== "accept_device_invite"
317
+ && !request.backendId) {
264
318
  throw new Error("backendId is required");
319
+ }
265
320
  if (request.purpose === "pair" || request.purpose === "connect") {
266
321
  if (!request.connectionId)
267
322
  throw new Error("connectionId is required");
268
323
  }
269
324
  if (request.purpose === "pair" && !request.pairId)
270
325
  throw new Error("pairId is required");
326
+ if (request.purpose === "accept_device_invite") {
327
+ if (!request.pairId || !this.state.deviceInvites[request.pairId])
328
+ throw new Error("Device invite is missing or expired");
329
+ }
271
330
  if (request.purpose === "revoke_device" && !request.targetDeviceId)
272
331
  throw new Error("targetDeviceId is required");
273
332
  }
@@ -292,6 +351,12 @@ export class RendezvousTrustStore {
292
351
  this.challenges.delete(challengeId);
293
352
  }
294
353
  let changed = false;
354
+ for (const [inviteId, invite] of Object.entries(this.state.deviceInvites)) {
355
+ if (invite.expiresAt > now)
356
+ continue;
357
+ delete this.state.deviceInvites[inviteId];
358
+ changed = true;
359
+ }
295
360
  for (const [ticketId, expiresAt] of Object.entries(this.state.usedTickets)) {
296
361
  if (expiresAt > now)
297
362
  continue;
@@ -345,6 +410,7 @@ function loadTrustState(filePath) {
345
410
  if (!isTrustState(value))
346
411
  throw new Error("unsupported or malformed state");
347
412
  value.backendRevocations ??= {};
413
+ value.deviceInvites ??= {};
348
414
  if (!hasConsistentReferences(value))
349
415
  throw new Error("unsupported or malformed state");
350
416
  return value;
@@ -359,6 +425,7 @@ function loadTrustState(filePath) {
359
425
  backendOwners: {},
360
426
  backendRevocations: {},
361
427
  pairingCompletions: {},
428
+ deviceInvites: {},
362
429
  usedTickets: {},
363
430
  };
364
431
  }
@@ -388,6 +455,14 @@ function isTrustState(value) {
388
455
  && isNonEmptyString(completion.deviceId)
389
456
  && isNonEmptyString(completion.backendId)
390
457
  && isNonNegativeInteger(completion.completedAt))
458
+ && (value.deviceInvites === undefined || isRecordOf(value.deviceInvites, (invite, key) => isRecord(invite)
459
+ && invite.inviteId === key
460
+ && isNonEmptyString(invite.secretHash)
461
+ && isNonEmptyString(invite.accountId)
462
+ && isNonEmptyString(invite.createdByDeviceId)
463
+ && isNonNegativeInteger(invite.createdAt)
464
+ && isNonNegativeInteger(invite.expiresAt)
465
+ && invite.expiresAt > invite.createdAt))
391
466
  && isRecordOf(value.usedTickets, (expiresAt) => isNonNegativeInteger(expiresAt));
392
467
  }
393
468
  function hasConsistentReferences(state) {
@@ -409,12 +484,26 @@ function hasConsistentReferences(state) {
409
484
  if (!state.accounts[accountId]?.backendIds.includes(backendId))
410
485
  return false;
411
486
  }
487
+ for (const invite of Object.values(state.deviceInvites)) {
488
+ if (!state.accounts[invite.accountId])
489
+ return false;
490
+ if (state.devices[invite.createdByDeviceId]?.accountId !== invite.accountId)
491
+ return false;
492
+ }
412
493
  return true;
413
494
  }
414
495
  catch {
415
496
  return false;
416
497
  }
417
498
  }
499
+ function hashDeviceInviteSecret(secret) {
500
+ return createHash("sha256").update(`pipane-device-invite-v1\n${secret}`).digest("base64url");
501
+ }
502
+ function secretsEqual(actual, expected) {
503
+ const actualBytes = Buffer.from(actual);
504
+ const expectedBytes = Buffer.from(expected);
505
+ return actualBytes.length === expectedBytes.length && timingSafeEqual(actualBytes, expectedBytes);
506
+ }
418
507
  function isRecord(value) {
419
508
  return !!value && typeof value === "object" && !Array.isArray(value);
420
509
  }
@@ -23,6 +23,7 @@ import { SessionPathError, SessionPathGuard } from "./session-path.js";
23
23
  import { extensionStatusSnapshot, isValidExtensionStatusKey, normalizeExtensionStatusText, providerForUsageStatus, PROVIDER_USAGE_STATUS_KEY, } from "./extension-status.js";
24
24
  const DETACHED_SYNC_COALESCE_MS = 75;
25
25
  const SESSION_SYNC_TRANSFER_KEY = "active-session-sync";
26
+ const MODEL_CATALOG_RETRY_DELAYS_MS = [100, 250, 500, 1_000, 2_000];
26
27
  let nextTurnId = 0;
27
28
  function makeTurnId() {
28
29
  return `turn_${Date.now()}_${++nextTurnId}`;
@@ -897,11 +898,7 @@ export class WsHandler {
897
898
  }
898
899
  let stateResponse = await this.pool.sendRpcChecked(proc, { type: "get_state" });
899
900
  if (!modelsMatch(stateResponse.data?.model, command.model)) {
900
- await this.pool.sendRpcChecked(proc, {
901
- type: "set_model",
902
- provider: command.model.provider,
903
- modelId: command.model.modelId,
904
- });
901
+ await this.setRequestedModel(proc, command.model);
905
902
  }
906
903
  if (typeof command.thinkingLevel === "string") {
907
904
  await this.pool.sendRpcChecked(proc, {
@@ -927,6 +924,34 @@ export class WsHandler {
927
924
  });
928
925
  }
929
926
  }
927
+ /**
928
+ * Pi refreshes remote model catalogs in the background after RPC startup.
929
+ * A freshly spawned worker can therefore reject a model that becomes visible
930
+ * moments later. Retry only Pi's exact missing-model response; auth and other
931
+ * control failures must still surface immediately.
932
+ */
933
+ async setRequestedModel(proc, model) {
934
+ let lastError;
935
+ for (const delayMs of [0, ...MODEL_CATALOG_RETRY_DELAYS_MS]) {
936
+ if (delayMs > 0)
937
+ await this.sleep(delayMs);
938
+ try {
939
+ await this.pool.sendRpcChecked(proc, {
940
+ type: "set_model",
941
+ provider: model.provider,
942
+ modelId: model.modelId,
943
+ });
944
+ return;
945
+ }
946
+ catch (error) {
947
+ const message = error instanceof Error ? error.message : String(error);
948
+ if (message !== `Model not found: ${model.provider}/${model.modelId}`)
949
+ throw error;
950
+ lastError = error;
951
+ }
952
+ }
953
+ throw lastError;
954
+ }
930
955
  async waitForPromptSettlement(proc, observer) {
931
956
  const state = await this.pool.sendRpcChecked(proc, { type: "get_state" });
932
957
  // Pi >= 0.80 acknowledges a prompt only after preflight. Before that
package/docs/protocol.md CHANGED
@@ -62,11 +62,15 @@ The browser creates a non-exportable P-256 private key and stores the `CryptoKey
62
62
  - `POST /v1/pairings/:pairId/tickets`
63
63
  - `POST /v1/connections/tickets`
64
64
  - `POST /v1/accounts/backends`
65
+ - `POST /v1/device-invites`
66
+ - `POST /v1/device-invites/:inviteId/accept`
65
67
  - `POST /v1/revocations/devices`
66
68
  - `POST /v1/revocations/backends`
67
69
 
68
70
  The first backend-confirmed pairing creates an anonymous account. Later terminal pairings can add another browser device to the backend's owner account, or an authenticated device can add an unowned backend to its account. A signed `discover` challenge returns only that account's backend grants, with registration metadata and current reachability. A backend has one owning account in this protocol version.
69
71
 
72
+ An authenticated browser can also sign a `create_device_invite` challenge to create a ten-minute, one-use invitation for its anonymous account. The returned secret is placed only in the fragment of `/invite/:inviteId#secret=...`; rendezvous persists only its hash. A new browser creates its own non-exportable key, signs an `accept_device_invite` challenge bound to that invite id, and submits the secret. Successful redemption adds the device to the same account, so signed discovery returns the account's complete backend grant set without copying any backend credentials or browser private keys. Existing devices, devices from another account, expired capabilities, incorrect secrets, and replay are rejected.
73
+
70
74
  ### Pairing capabilities
71
75
 
72
76
  The backend creates a 256-bit secret, stores only its hash, and publishes the opaque pair id and expiry with `open_pairing`. The QR URL has this shape:
@@ -114,7 +118,7 @@ The currently implemented semantic methods are:
114
118
 
115
119
  Every method has runtime-validated parameters, correlated responses, stable error codes, bounded concurrency, and a bounded device-scoped completed-request cache. File uploads use bounded, offset-addressed base64 chunks so arbitrary non-image attachments can cross either local HTTP or the authenticated DataChannel, land in a private temporary backend path, and be referenced in the agent prompt. Pending browser requests retain their id across a carrier reconnect, so an in-flight mutation is resumed or answered from the cache instead of being executed twice. The backend uses one `LocalBackendApi` implementation for both the legacy local HTTP facade and semantic DataChannel requests. Remote session results are scoped to a structured `{ backendId, path }` identity; paths remain backend-local identifiers rather than authorization.
116
120
 
117
- The browser's `RemoteBackendManager` maintains one client/store per authorized backend id, requests a fresh ticket whenever a WebRTC carrier reconnects, and never treats an arbitrary URL backend id as authorized until signed account discovery includes it. The account workspace opens a direct connection to each reachable authorized host so it can merge backend-scoped session metadata and live status in one sidebar; only the selected host/session drives the conversation renderer and receives a session subscription. Session metadata remains end-to-end between browser and backend and is not indexed by rendezvous. Terminal `pipane pair` remains the no-email recovery path when browser storage is lost.
121
+ The browser's `RemoteBackendManager` maintains one client/store per authorized backend id, requests a fresh ticket whenever a WebRTC carrier reconnects, and never treats an arbitrary URL backend id as authorized until signed account discovery includes it. The account workspace opens a direct connection to each reachable authorized host so it can merge backend-scoped session metadata and live status in one sidebar; only the selected host/session drives the conversation renderer and receives a session subscription. Session metadata remains end-to-end between browser and backend and is not indexed by rendezvous. An authorized browser can invite another device from the host menu; terminal `pipane pair` remains the no-email fallback when every browser key is unavailable.
118
122
 
119
123
  Application streaming, turn control, and session snapshots remain on validated v1 frames during parity migration. Semantic v2 is deployed beside v1 rather than changing v1's renderer-state contract in place.
120
124
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pipane",
3
- "version": "0.1.10",
3
+ "version": "0.1.12",
4
4
  "description": "A clean web interface for the pi coding agent — chat UI with real-time tool calls, streaming output, session management, and model picker",
5
5
  "private": false,
6
6
  "type": "module",
@@ -43,6 +43,7 @@
43
43
  "scripts": {
44
44
  "dev": "./dev.sh",
45
45
  "deploy:dev": "./deploy-dev.sh",
46
+ "deploy:preview": "./deploy-preview.sh",
46
47
  "deploy:prod": "./deploy-prod.sh",
47
48
  "dev:server": "tsx watch src/server/server.ts",
48
49
  "dev:client": "vite",
@@ -85,6 +86,7 @@
85
86
  "marked-katex-extension": "^5.1.10",
86
87
  "node-datachannel": "^0.32.3",
87
88
  "pdfjs-dist": "5.4.394",
89
+ "qrcode": "^1.5.4",
88
90
  "qrcode-terminal": "^0.12.0",
89
91
  "ws": "^8.18.0",
90
92
  "xlsx": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz"
@@ -92,6 +94,7 @@
92
94
  "devDependencies": {
93
95
  "@playwright/test": "^1.58.2",
94
96
  "@types/express": "^5.0.0",
97
+ "@types/qrcode": "^1.5.5",
95
98
  "@types/qrcode-terminal": "^0.12.2",
96
99
  "@types/ws": "^8.5.10",
97
100
  "@vitest/coverage-v8": "3.2.7",
@@ -1,2 +0,0 @@
1
- const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/pairing-page-CiEb8c3R.js","assets/device-identity-C_w2NbSk.js","assets/rendezvous-trust-api-xLnrhe-v.js","assets/webrtc-frame-transport-Drpqhhdy.js","assets/__vite-browser-external-DGfF_P2w.js","assets/remote-backend-manager-Bbt7CXRP.js","assets/backend-protocol-BvNAsFvb.js","assets/ws-agent-adapter-Cauprivt.js","assets/workspace-backend-client-kUrfC0HS.js","assets/main-CQj3ADar.js","assets/app-runtime-Rw-O-AwW.js","assets/backend-landing-page-CF2aHs0U.js"])))=>i.map(i=>d[i]);
2
- (function(){const n=document.createElement("link").relList;if(n&&n.supports&&n.supports("modulepreload"))return;for(const e of document.querySelectorAll('link[rel="modulepreload"]'))c(e);new MutationObserver(e=>{for(const r of e)if(r.type==="childList")for(const o of r.addedNodes)o.tagName==="LINK"&&o.rel==="modulepreload"&&c(o)}).observe(document,{childList:!0,subtree:!0});function i(e){const r={};return e.integrity&&(r.integrity=e.integrity),e.referrerPolicy&&(r.referrerPolicy=e.referrerPolicy),e.crossOrigin==="use-credentials"?r.credentials="include":e.crossOrigin==="anonymous"?r.credentials="omit":r.credentials="same-origin",r}function c(e){if(e.ep)return;e.ep=!0;const r=i(e);fetch(e.href,r)}})();const w="modulepreload",_=function(t){return"/"+t},m={},u=function(n,i,c){let e=Promise.resolve();if(i&&i.length>0){let f=function(s){return Promise.all(s.map(l=>Promise.resolve(l).then(p=>({status:"fulfilled",value:p}),p=>({status:"rejected",reason:p}))))};document.getElementsByTagName("link");const o=document.querySelector("meta[property=csp-nonce]"),a=o?.nonce||o?.getAttribute("nonce");e=f(i.map(s=>{if(s=_(s),s in m)return;m[s]=!0;const l=s.endsWith(".css"),p=l?'[rel="stylesheet"]':"";if(document.querySelector(`link[href="${s}"]${p}`))return;const d=document.createElement("link");if(d.rel=l?"stylesheet":w,l||(d.as="script"),d.crossOrigin="",d.href=s,a&&d.setAttribute("nonce",a),document.head.appendChild(d),l)return new Promise((g,v)=>{d.addEventListener("load",g),d.addEventListener("error",()=>v(new Error(`Unable to preload CSS for ${s}`)))})}))}function r(o){const a=new Event("vite:preloadError",{cancelable:!0});if(a.payload=o,window.dispatchEvent(a),!a.defaultPrevented)throw o}return e.then(o=>{for(const a of o||[])a.status==="rejected"&&r(a.reason);return n().catch(r)})};"serviceWorker"in navigator&&navigator.serviceWorker.getRegistrations().then(t=>{for(const n of t)n.unregister()});y();async function y(){if(b(window.location.pathname)){const{initializePairingPage:i}=await u(async()=>{const{initializePairingPage:c}=await import("./pairing-page-CiEb8c3R.js");return{initializePairingPage:c}},__vite__mapDeps([0,1,2,3,4]));await i();return}const t=k(window.location.pathname);if(t!==void 0||window.location.pathname==="/"&&await E())try{const[{configureAppRuntime:i},{RemoteBackendManager:c},{WorkspaceBackendClient:e},{loadBrowserDeviceIdentity:r}]=await Promise.all([u(()=>import("./app-runtime-Rw-O-AwW.js"),[]),u(()=>import("./remote-backend-manager-Bbt7CXRP.js"),__vite__mapDeps([5,6,1,2,3,4,7])),u(()=>import("./workspace-backend-client-kUrfC0HS.js"),__vite__mapDeps([8,6])),u(()=>import("./device-identity-C_w2NbSk.js").then(f=>f.d),[])]);if(!await r()){await h();return}const o=new c(window.location.origin),a=await o.initialize();if(a.length===0){await h();return}i({client:new e(a,o,t)})}catch(i){P(i);return}await u(()=>import("./main-CQj3ADar.js"),__vite__mapDeps([9,7,6,4,10]))}async function h(){const{initializeBackendLandingPage:t}=await u(async()=>{const{initializeBackendLandingPage:n}=await import("./backend-landing-page-CF2aHs0U.js");return{initializeBackendLandingPage:n}},__vite__mapDeps([11,6,1,2]));await t()}async function E(){try{const t=await fetch("/health",{cache:"no-store"});if(!t.ok||!t.headers.get("content-type")?.includes("application/json"))return!1;const n=await t.json();return!!n&&typeof n=="object"&&n.ok===!0}catch{return!1}}function P(t){const n=document.getElementById("app")??document.body;n.replaceChildren();const i=document.createElement("main");i.style.cssText="min-height:100vh;display:grid;place-items:center;padding:24px;font-family:ui-sans-serif,system-ui;color:var(--foreground,#111827);background:var(--background,#fff)";const c=document.createElement("section");c.style.cssText="max-width:520px;border:1px solid var(--border,#d1d5db);border-radius:12px;padding:28px";const e=document.createElement("h1");e.textContent="Cannot open backend";const r=document.createElement("p");r.textContent=t instanceof Error?t.message:"This browser is not authorized for the backend.";const o=document.createElement("p");o.textContent="To restore access, run `pipane pair` in the backend terminal and scan the new QR code with this browser.",c.append(e,r,o),i.append(c),n.append(i)}function b(t){return/^\/pair\/[^/]+$/u.test(t)}function k(t){const n=/^\/backend\/([^/]+)$/u.exec(t);return n?.[1]?decodeURIComponent(n[1]):void 0}export{u as _};