sootsim 0.0.1 → 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -0
- package/dist-cli/bin.js +16 -10
- package/dist-cli/chunks/agent-3T4BJEZM.js +61 -0
- package/dist-cli/chunks/agent-wrapper-WCYNLWHZ.js +15 -0
- package/dist-cli/chunks/assert-FPFJEFF3.js +47 -0
- package/dist-cli/chunks/auto-bootstrap-HDW6N77H.js +2 -0
- package/dist-cli/chunks/chunk-3HBBSRLE.js +2 -0
- package/dist-cli/chunks/chunk-4372UQHZ.js +308 -0
- package/dist-cli/chunks/chunk-4GWEO5CL.js +1 -0
- package/dist-cli/chunks/{chunk-G5MR66EB.js → chunk-5C5I5OFM.js} +2 -2
- package/dist-cli/chunks/chunk-6IPY24VM.js +11 -0
- package/dist-cli/chunks/chunk-AS4V7TZU.js +2 -0
- package/dist-cli/chunks/chunk-B5R4K2DG.js +5 -0
- package/dist-cli/chunks/chunk-CXTA5VGA.js +4 -0
- package/dist-cli/chunks/chunk-CZZB4DWG.js +3 -0
- package/dist-cli/chunks/chunk-DW54UPRZ.js +119 -0
- package/dist-cli/chunks/chunk-EIZCWDRE.js +1 -0
- package/dist-cli/chunks/{chunk-KSACMDXK.js → chunk-ET3NNZAR.js} +2 -2
- package/dist-cli/chunks/chunk-EWEKADK4.js +5 -0
- package/dist-cli/chunks/{chunk-JSF5LPNT.js → chunk-EWMYTXM2.js} +5 -5
- package/dist-cli/chunks/chunk-FUQ4XA6I.js +16 -0
- package/dist-cli/chunks/chunk-GQUOQNTP.js +27 -0
- package/dist-cli/chunks/chunk-HBNVKYSC.js +2 -0
- package/dist-cli/chunks/{chunk-64TOMNZX.js → chunk-HORCHQT7.js} +2 -2
- package/dist-cli/chunks/{chunk-YCETS3B3.js → chunk-ISAMAM3I.js} +2 -2
- package/dist-cli/chunks/{chunk-GPVPHE2B.js → chunk-K6YUSCAC.js} +2 -2
- package/dist-cli/chunks/{chunk-E522F5JW.js → chunk-K7LDP7JL.js} +1 -1
- package/dist-cli/chunks/chunk-KZ2LIDW6.js +2 -0
- package/dist-cli/chunks/{chunk-J2S3OCWA.js → chunk-LOV766MI.js} +1 -1
- package/dist-cli/chunks/{chunk-OROM7DZI.js → chunk-LXCFGKL2.js} +1 -1
- package/dist-cli/chunks/{chunk-PWXPA745.js → chunk-NE62JSI6.js} +1 -1
- package/dist-cli/chunks/chunk-NHA3G6A3.js +22 -0
- package/dist-cli/chunks/chunk-NXWCDGWS.js +2 -0
- package/dist-cli/chunks/{chunk-QOBRRY5X.js → chunk-RJUBGX5M.js} +1 -1
- package/dist-cli/chunks/chunk-SLCVEGTW.js +4 -0
- package/dist-cli/chunks/chunk-TGDP3D3V.js +34 -0
- package/dist-cli/chunks/chunk-TSZBQS6W.js +62 -0
- package/dist-cli/chunks/chunk-XKDQEYTE.js +1 -0
- package/dist-cli/chunks/chunk-XXUAOYYT.js +4 -0
- package/dist-cli/chunks/{chunk-7X6OPSRD.js → chunk-YVSZHVLU.js} +2 -2
- package/dist-cli/chunks/{compat-MRN2ORY5.js → compat-3HMKLGXL.js} +4 -4
- package/dist-cli/chunks/{config-CO5IYWUY.js → config-IJQ3KANN.js} +5 -5
- package/dist-cli/chunks/control-3RAFI4AW.js +2 -0
- package/dist-cli/chunks/{daemon-G4XVRFHM.js → daemon-BBEQJLRY.js} +2 -2
- package/dist-cli/chunks/{debug-ZNSZTWT6.js → debug-SGZ5ZFQI.js} +4 -4
- package/dist-cli/chunks/demo-app-registry-NCYP3WA6.js +2 -0
- package/dist-cli/chunks/{detox-JEGYNTYV.js → detox-PK74V2Y7.js} +2 -2
- package/dist-cli/chunks/{device-BS34FAFM.js → device-MWNFX54L.js} +2 -2
- package/dist-cli/chunks/drivers-EXUREU4B.js +2 -0
- package/dist-cli/chunks/electron-3NIHSU2K.js +15 -0
- package/dist-cli/chunks/flow-6Y3E6E5P.js +2 -0
- package/dist-cli/chunks/{hints-7Z656W4H.js → hints-XZJLBIXW.js} +2 -2
- package/dist-cli/chunks/home-paths-BNRMUBJA.js +2 -0
- package/dist-cli/chunks/{inspect-NAHXP2M5.js → inspect-FGTUAK4C.js} +153 -165
- package/dist-cli/chunks/install-LCXALH26.js +65 -0
- package/dist-cli/chunks/{install-desktop-PYIZIH67.js → install-desktop-U3RQ6XUX.js} +8 -4
- package/dist-cli/chunks/install-dev-desktop-BLKRFI42.js +100 -0
- package/dist-cli/chunks/keys-N5LBDSD5.js +19 -0
- package/dist-cli/chunks/launch-NIMSJH5I.js +16 -0
- package/dist-cli/chunks/{login-Z5Z54HUJ.js → login-CQV2XBRM.js} +5 -5
- package/dist-cli/chunks/{logout-T2QDYGCB.js → logout-R56NWAWQ.js} +2 -2
- package/dist-cli/chunks/{maestro-4AXTS7OE.js → maestro-ZYUVTM7H.js} +2 -2
- package/dist-cli/chunks/{preview-NMGWHWMX.js → preview-AOAWAYEQ.js} +2 -2
- package/dist-cli/chunks/{profile-6RGJA4FR.js → profile-DDADDPRW.js} +3 -3
- package/dist-cli/chunks/record-3OIOTHP6.js +37 -0
- package/dist-cli/chunks/runtime-JTLZYEXK.js +25 -0
- package/dist-cli/chunks/{screenshot-R3GCCSCI.js → screenshot-Q6N2V5LL.js} +3 -3
- package/dist-cli/chunks/screenshot-mode-WWLWJWQD.js +17 -0
- package/dist-cli/chunks/{screenshots-4UQJE4NC.js → screenshots-2JEPJGZO.js} +2 -2
- package/dist-cli/chunks/server-VH34RVAX.js +29 -0
- package/dist-cli/chunks/{skills-2PPKPL4B.js → skills-PU4627FY.js} +2 -2
- package/dist-cli/chunks/store-U2VDD2S4.js +2 -0
- package/dist-cli/chunks/{test-5LFKOQ4M.js → test-AECE56E7.js} +3 -3
- package/dist-cli/chunks/upload-KPP7KG6E.js +2 -0
- package/dist-cli/chunks/{whoami-H6FW34JS.js → whoami-NCGRRR7X.js} +2 -2
- package/dist-lib/agent-daemon-client.cjs +414 -0
- package/dist-lib/agent-events.cjs +48 -0
- package/dist-lib/agent-sessions.cjs +692 -0
- package/dist-lib/attached-projects.cjs +448 -0
- package/dist-lib/auth/shared-session.cjs +174 -0
- package/dist-lib/backend-origin.cjs +70 -0
- package/dist-lib/bridge-constants.cjs +32 -0
- package/dist-lib/cli-constants.cjs +32 -0
- package/dist-lib/config.cjs +88 -0
- package/dist-lib/dev-bundle-resolution.cjs +236 -0
- package/dist-lib/home-paths.cjs +234 -0
- package/dist-lib/host/bridge-host.cjs +3458 -0
- package/dist-lib/index.cjs +361 -0
- package/dist-lib/metro.cjs +215 -0
- package/dist-lib/render-mode.cjs +54 -0
- package/dist-lib/vite-base.cjs +4217 -0
- package/dist-lib/vite.cjs +178 -0
- package/package.json +80 -13
- package/scripts/postinstall.cjs +70 -0
- package/dist-cli/chunks/bridge-host-2EY7Z4AO.js +0 -2
- package/dist-cli/chunks/chunk-3C3ZH7PP.js +0 -4
- package/dist-cli/chunks/chunk-3R4ZZESY.js +0 -119
- package/dist-cli/chunks/chunk-74XPLOV4.js +0 -2
- package/dist-cli/chunks/chunk-7LMDCMSI.js +0 -8
- package/dist-cli/chunks/chunk-A2CZQIWO.js +0 -1
- package/dist-cli/chunks/chunk-CKZ376AY.js +0 -322
- package/dist-cli/chunks/chunk-E5UBZEYR.js +0 -2
- package/dist-cli/chunks/chunk-HOIHCO7S.js +0 -3
- package/dist-cli/chunks/chunk-KQWZZ56P.js +0 -2
- package/dist-cli/chunks/chunk-KSB6MSZ4.js +0 -34
- package/dist-cli/chunks/chunk-KXYKAYYB.js +0 -51
- package/dist-cli/chunks/chunk-MBFP2LVH.js +0 -3
- package/dist-cli/chunks/chunk-MPSZ5EWF.js +0 -16
- package/dist-cli/chunks/chunk-X2U72K7X.js +0 -1
- package/dist-cli/chunks/control-Y7TKKB6D.js +0 -2
- package/dist-cli/chunks/dev-ZUKCZQEX.js +0 -25
- package/dist-cli/chunks/dev-checkout-IEZVVTCN.js +0 -2
- package/dist-cli/chunks/drivers-46PFFIDF.js +0 -2
- package/dist-cli/chunks/electron-P2KOPX2S.js +0 -15
- package/dist-cli/chunks/flow-VVOF6UNC.js +0 -2
- package/dist-cli/chunks/install-EPUJX4AT.js +0 -67
- package/dist-cli/chunks/record-IE27Z2GA.js +0 -37
- package/dist-cli/chunks/screenshot-mode-SZQDNGYE.js +0 -17
- package/dist-cli/chunks/server-AN2G5KO4.js +0 -21
- package/dist-cli/chunks/store-PU5ES4YQ.js +0 -2
- package/dist-cli/chunks/upload-BYNPC54C.js +0 -2
- package/dist-cli/chunks/vite-plugin-5AEUUBKP.js +0 -9
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/*! sootsim v0.0.2 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
|
|
2
|
+
import{b as U,c as R,d as K,e as N,f as J,h as q,i as w,j as G,k as V,l as z,p as Y,s as b,t as Z,u as Q,v as X,w as ee}from"./chunk-SLCVEGTW.js";import"./chunk-KZ2LIDW6.js";import{a as $}from"./chunk-EWEKADK4.js";import"./chunk-NHA3G6A3.js";import"./chunk-AS4V7TZU.js";import"./chunk-EIZCWDRE.js";import{o as A,p as D,q as _,r as L,t as M,u as W,v as F,w as j,x as H,y as O}from"./chunk-CXTA5VGA.js";import"./chunk-K7LDP7JL.js";import{spawn as T}from"child_process";import v from"fs";import{createServer as ae}from"http";import y from"path";import{WebSocket as f,WebSocketServer as ce}from"ws";import te from"node:fs";import C from"node:path";var E=1;function ne(){return[Number(process.env.VITE_PORT_WEB||process.env.PORT||3e3),Number(process.env.VITE_PORT_ZERO||7849),Number(process.env.VITE_PORT_POSTGRES||7432),Number(process.env.VITE_PORT_R2||9500)].filter(h=>Number.isFinite(h)&&h>0)}var P=class{subscriptions=new Map;sessionsBySocket=new Map;allSockets=new Set;pendingPromptEchoes=new Map;pendingTurns=new Map;opts;constructor(e={}){this.opts=e}registerSocket(e){this.allSockets.add(e)}unregisterSocket(e){let t=this.sessionsBySocket.get(e);if(t){for(let r of t)this.decrementSubscription(r);this.sessionsBySocket.delete(e)}this.allSockets.delete(e)}async handleMessage(e,t){let r=t?.type;if(typeof r!="string"||!r.startsWith("agent:"))return!1;let o=t.id;try{let s=await this.dispatch(e,r,t);this.respond(e,o,s)}catch(s){s instanceof b?this.respondError(e,o,s.message,s.code):this.respondError(e,o,s instanceof Error?s.message:String(s))}return!0}async seedOnBoot(){try{await z()}catch(e){process.stderr.write(`[sootsim-agent] seedFromDemoAppRegistry failed: ${e instanceof Error?e.message:String(e)}
|
|
3
|
+
`)}}close(){for(let e of this.subscriptions.values())try{e.unsubscribe()}catch{}this.subscriptions.clear(),this.sessionsBySocket.clear(),this.allSockets.clear()}async dispatch(e,t,r){switch(t){case"agent:list-projects":return N();case"agent:upsert-project":return R(r.input??{});case"agent:delete-project":return q(String(r.projectId)),{ok:!0};case"agent:auto-attach-for-url":return this.autoAttachForUrl(r.input??{});case"agent:list-sessions":return G(r.projectId?String(r.projectId):void 0);case"agent:start-session":return this.doStartSession(r.input??{});case"agent:send-prompt":{let s=String(r.sessionId),n=w(s);if(!n)throw new b("NO_SESSION",`no session: ${s}`);let i=this.normalizePromptEnvelope(r);return await Q(s,i),this.notePromptAccepted(s,i,n.status==="working")}case"agent:end-session":this.dropSessionFanout(String(r.sessionId)),await X(String(r.sessionId));let o=w(String(r.sessionId));return o&&this.broadcastSessionStatus(o),{ok:!0};case"agent:get-transcript":return this.getTranscript(String(r.sessionId));case"agent:get-paths":return this.getPaths();case"agent:subscribe-events":return this.subscribeSocket(e,String(r.sessionId));case"agent:unsubscribe-events":return this.unsubscribeSocket(e,String(r.sessionId));default:throw new b("UNKNOWN_AGENT_MSG",`unknown agent message: ${t}`)}}async doStartSession(e){if(!K(e.projectId))throw new b("NO_PROJECT",`no project: ${e.projectId}`);let r=await Z(e);return this.broadcastSessionStatus(r.session),r}async autoAttachForUrl(e){let t=e.bundleUrl??"",r=(()=>{try{return new URL(t).port||null}catch{return null}})();if(!r)return{project:null};let o=this.opts.getExcludePorts?.()??ne(),n=(await $({excludePorts:o})).find(d=>String(d.port)===r);if(!n||!n.cwd)return{project:null};let i=N().find(d=>d.cwd===n.cwd)??null,a=Array.from(new Set([...i?.knownBundleUrls??[],n.bundleUrl,t]));return{project:R({cwd:n.cwd,name:n.projectName??C.basename(n.cwd),preferredProvider:e.provider??i?.preferredProvider,sourceRoots:i?.sourceRoots??[n.cwd],knownBundleUrls:a,framework:i?.framework??oe(n.framework),bundleId:n.bundleId??i?.bundleId})}}getTranscript(e){let t=Y(e);return te.existsSync(t)?te.readFileSync(t,"utf8"):{error:"transcript not found",code:"NO_TRANSCRIPT"}}getPaths(){let e=U();return{userDataDir:e,storeFile:C.join(e,"attached-projects.json"),sessionsDir:C.join(e,"sessions"),transcriptsDir:C.join(e,"transcripts")}}subscribeSocket(e,t){let r=this.sessionsBySocket.get(e);if(r||(r=new Set,this.sessionsBySocket.set(e,r)),r.has(t))return{ok:!0,refCount:this.subscriptions.get(t)?.refCount??1};r.add(t);let o=this.subscriptions.get(t);if(o)return o.refCount++,{ok:!0,refCount:o.refCount};let s=ee(t,n=>{let i=this.coalescePromptEcho(t,n);if(i&&(this.applySessionEvent(t,i),this.fanOutEvent(t,i)),n.type==="turn-completed"){let a=w(t);if(a)try{J(a.projectId,{usd:n.costUsd,ts:n.ts})}catch(c){process.stderr.write(`[sootsim-agent] recordTurnTelemetry failed: ${c instanceof Error?c.message:String(c)}
|
|
4
|
+
`)}}});return this.subscriptions.set(t,{unsubscribe:s,refCount:1}),{ok:!0,refCount:1}}unsubscribeSocket(e,t){let r=this.sessionsBySocket.get(e);return!r||!r.has(t)?{ok:!0,refCount:0}:(r.delete(t),this.decrementSubscription(t))}decrementSubscription(e){let t=this.subscriptions.get(e);if(!t)return{ok:!0,refCount:0};if(t.refCount--,t.refCount<=0){try{t.unsubscribe()}catch{}return this.subscriptions.delete(e),{ok:!0,refCount:0}}return{ok:!0,refCount:t.refCount}}dropSessionFanout(e){let t=this.subscriptions.get(e);if(t){try{t.unsubscribe()}catch{}this.subscriptions.delete(e)}for(let r of this.sessionsBySocket.values())r.delete(e);this.clearPromptTracking(e)}normalizePromptEnvelope(e){if(e?.prompt&&typeof e.prompt=="object"){let t=e.prompt;return{text:String(t.text??""),...typeof t.displayText=="string"?{displayText:t.displayText}:{},...typeof t.inspectSummary=="string"?{inspectSummary:t.inspectSummary}:{},...typeof t.inspectTrace=="string"?{inspectTrace:t.inspectTrace}:{}}}return{text:String(e?.text??""),...typeof e?.displayText=="string"?{displayText:e.displayText}:{},...typeof e?.inspectSummary=="string"?{inspectSummary:e.inspectSummary}:{},...typeof e?.inspectTrace=="string"?{inspectTrace:e.inspectTrace}:{}}}notePromptAccepted(e,t,r){let o=Date.now(),s=this.pendingPromptEchoes.get(e)??[];s.push({sentAt:o}),this.pendingPromptEchoes.set(e,s);let n=Math.max(this.pendingTurns.get(e)??0,r?1:0)+1;this.pendingTurns.set(e,n);let i=t.displayText??t.text;return this.patchSession(e,{lastPrompt:i,status:"working",needsAttention:!1}),this.fanOutEvent(e,{type:"prompt-received",text:i,...t.inspectSummary?{inspectSummary:t.inspectSummary}:{},...t.inspectTrace?{inspectTrace:t.inspectTrace}:{},ts:o}),{ok:!0,queued:n>1,pendingTurns:n,queueDepth:Math.max(0,n-1)}}applySessionEvent(e,t){switch(t.type){case"prompt-received":case"turn-started":this.patchSession(e,{status:"working",needsAttention:!1});return;case"turn-completed":{let r=this.consumeSettledTurn(e);this.patchSession(e,{status:r>0?"working":"idle",needsAttention:!1,lastTurnFiles:t.filesTouched,currentlyEditing:void 0});return}case"approval-needed":this.patchSession(e,{status:"needs-attention",needsAttention:!0});return;case"error":{let r=this.consumeSettledTurn(e);this.patchSession(e,{status:r>0?"working":"needs-attention",needsAttention:r<=0,currentlyEditing:void 0});return}case"exited":this.clearPromptTracking(e),this.patchSession(e,{status:"ended",needsAttention:!1,wrapperPid:void 0,currentlyEditing:void 0});return;case"ready":case"turn-reasoning":case"turn-message":case"turn-plan":case"tool-call":case"file-edited":case"file-diff-delta":return}}patchSession(e,t){V(e,t);let r=w(e);r&&this.broadcastSessionStatus(r)}coalescePromptEcho(e,t){if(t.type!=="prompt-received")return t;let r=this.pendingPromptEchoes.get(e);if(!r||r.length===0)return t;for(;r.length>0&&Date.now()-r[0].sentAt>15e3;)r.shift();return r.length===0?(this.pendingPromptEchoes.delete(e),t):(r.shift(),r.length===0?this.pendingPromptEchoes.delete(e):this.pendingPromptEchoes.set(e,r),null)}consumeSettledTurn(e){let t=Math.max(0,(this.pendingTurns.get(e)??1)-1);return t>0?this.pendingTurns.set(e,t):this.pendingTurns.delete(e),t}clearPromptTracking(e){this.pendingPromptEchoes.delete(e),this.pendingTurns.delete(e)}fanOutEvent(e,t){let r=JSON.stringify({type:"agent:event",sessionId:e,event:t});for(let[o,s]of this.sessionsBySocket)if(s.has(e)&&o.readyState===E)try{o.send(r)}catch{}}broadcastSessionStatus(e){let t=JSON.stringify({type:"agent:session-status",session:e});for(let r of this.allSockets)if(r.readyState===E)try{r.send(t)}catch{}}respond(e,t,r){if(e.readyState===E)try{e.send(JSON.stringify({id:t,result:r}))}catch{}}respondError(e,t,r,o){if(e.readyState===E)try{e.send(JSON.stringify({id:t,error:r,...o?{code:o}:{}}))}catch{}}};function oe(h){return h==="expo"?"expo":h==="one"||h==="vxrn"?"one":"unknown"}var de=new Set(["tap","keyboard","close"]);function le(h){return!h||typeof h.type!="string"?!1:h.acquireLock===!0?!0:h.readOnly===!0?!1:de.has(h.type)}var ue=5e3,pe={".html":"text/html; charset=utf-8",".js":"application/javascript",".cjs":"application/javascript",".mjs":"application/javascript",".css":"text/css; charset=utf-8",".json":"application/json; charset=utf-8",".png":"image/png",".jpg":"image/jpeg",".jpeg":"image/jpeg",".gif":"image/gif",".svg":"image/svg+xml",".webp":"image/webp",".avif":"image/avif",".ico":"image/x-icon",".wasm":"application/wasm",".ttf":"font/ttf",".otf":"font/otf",".woff":"font/woff",".woff2":"font/woff2",".map":"application/json",".txt":"text/plain; charset=utf-8"},I=class h{port;openUrlHandler;httpServer=null;wss=null;nextCommandId=1;nextBrowserId=1;browsers=new Map;primaryBrowserId=null;pendingCommands=new Map;cliBySentId=new Map;cliBrowserBySocket=new Map;cliLastCommandAt=new Map;cliSessionKeyBySocket=new Map;cliLabelBySocket=new Map;restorableBrowsers=new Map;nextCliFallbackId=1;cliIdleTimer=null;agentHost;static CLI_IDLE_TIMEOUT_MS=6e4;static CLI_LEASE_TTL_MS=6e5;static USER_ACTIVE_LEASE_TTL_MS=8e3;static BROWSER_RECONNECT_TTL_MS=3e4;preferredPort;portFallbackCount;shouldWriteLockfile;effectivePort=0;startedAt=0;heartbeatTimer=null;activeRuntimeVersion=null;activeRuntimeDirPath=null;constructor(e={}){this.preferredPort=e.port||7668,this.port=this.preferredPort,this.shouldWriteLockfile=e.writeLockfile===!0;let t=this.shouldWriteLockfile?1:10;this.portFallbackCount=Math.max(1,e.portFallbackCount??t),this.openUrlHandler=e.openUrl,this.agentHost=new P({getExcludePorts:e.agentScanExcludes})}getAgentHost(){return this.agentHost}start(e){this.startAsync(e)}async startAsync(e){if(this.httpServer||this.wss)return this.effectivePort;this.refreshActiveRuntime();for(let t=0;t<this.portFallbackCount;t++){let r=this.preferredPort+t;try{return await this.bindOnce(r,e?.silent===!0),this.effectivePort=r,this.port=r,this.startedAt=Date.now(),t>0&&!e?.silent&&process.stderr.write(`ws bridge bound to port ${r} (preferred ${this.preferredPort} was taken)
|
|
5
|
+
`),this.afterBind(),r}catch(o){if(o?.code!=="EADDRINUSE")throw o;e?.silent||process.stderr.write(`ws bridge port ${r} already in use, trying ${r+1}
|
|
6
|
+
`)}}throw new Error(`could not bind ws bridge after ${this.portFallbackCount} attempts starting at ${this.preferredPort}`)}bindOnce(e,t){return new Promise((r,o)=>{let s=ae((a,c)=>this.handleHttpRequest(a,c)),n=!1,i=a=>{if(!n){n=!0;try{s.close()}catch{}this.httpServer=null,this.wss=null,o(a)}};s.once("error",i),s.listen(e,"127.0.0.1",()=>{n||(n=!0,s.removeListener("error",i),s.on("error",a=>{process.stderr.write(`ws bridge http error: ${String(a)}
|
|
7
|
+
`)}),this.httpServer=s,this.wss=new ce({server:s}),this.wireWebSocketServer(),r())})})}wireWebSocketServer(){this.wss&&this.wss.on("connection",(e,t)=>{let r=t.headers.origin,o=r?"browser":"cli",s=null;if(this.agentHost.registerSocket(e),o==="browser")s={id:`tab-${this.nextBrowserId++}`,ws:e,origin:r,connectedAt:Date.now(),lastSeenAt:Date.now(),lastActiveAt:0,recentActions:[]},this.browsers.set(s.id,s),this.shouldPromoteBrowser(s)&&(this.primaryBrowserId=s.id),this.broadcastBrowserAssignments(),this.broadcastBrowserClientStates();else{let n=`ws-${this.nextCliFallbackId++}`;this.cliSessionKeyBySocket.set(e,n)}e.on("message",n=>{let i;try{i=JSON.parse(n.toString())}catch{return}if(!(!i||typeof i!="object")){if(typeof i.type=="string"&&i.type.startsWith("agent:")){this.agentHost.handleMessage(e,i);return}if(i.type==="runtime:list"){let a=L(),c=this.getActiveRuntime(),d={type:"runtime:list:ok",id:i.id,installed:a,active:c.version,activeRuntimeDir:c.runtimeDir};try{e.send(JSON.stringify(d))}catch{}return}if(i.type==="runtime:use"){let a=typeof i.version=="string"?i.version:"";if(!L().includes(a)){try{e.send(JSON.stringify({type:"runtime:use:error",id:i.id,error:`runtime ${a||"(missing)"} is not installed`}))}catch{}return}let d=this.setActiveRuntime(a);try{e.send(JSON.stringify({type:"runtime:use:ok",id:i.id,version:d.version,runtimeDir:d.runtimeDir}))}catch{}return}if(i.type==="runtime:get"){let a=this.getActiveRuntime();try{e.send(JSON.stringify({type:"runtime:get:ok",id:i.id,active:a.version,activeRuntimeDir:a.runtimeDir}))}catch{}return}if(o==="browser"){if(s&&(s.lastSeenAt=Date.now()),i.type==="bridge:register"&&s){let d=i,l=this.tryRestoreBrowserId(s,d.browserId);s.url=d.url,s.title=d.title,s.userAgent=d.userAgent,l&&(this.broadcastBrowserAssignments(),this.broadcastBrowserClientStates());return}if(i.type==="bridge:user-focus-state"&&s){let d=i;this.updateUserFocusLease(s,d.focused===!0);return}if(i.type==="bridge:user-interact"&&s){this.updateUserActivity(s);return}if(i.type==="bridge:open-path"){let d=typeof i.path=="string"?i.path:"",l=typeof i.line=="number"&&Number.isFinite(i.line)?i.line:void 0,p=typeof i.column=="number"&&Number.isFinite(i.column)?i.column:void 0;d&&this.openPathInEditor(d,l,p);return}if(i.type==="bridge:boot-clients"&&s){let d=[];for(let[p,u]of this.cliBrowserBySocket)u===s.id&&d.push(p);for(let p of d){this.cliBrowserBySocket.delete(p);try{p.close(1e3,"booted by browser")}catch{}}let l=!!s.cliLease;s.cliLease=void 0,(d.length>0||l)&&(process.stderr.write(`sootsim booted ${d.length} cli client(s)${l?" + cleared lease":""} from [${s.id}]
|
|
8
|
+
`),this.recordBrowserAction(s.id,"browser booted cli clients"),this.broadcastBrowserClientStates());return}let a=this.pendingCommands.get(i.id);if(a){this.pendingCommands.delete(i.id),i.error?a.reject(new Error(i.error)):a.resolve(i.result);return}let c=this.cliBySentId.get(i.id);if(c&&(this.cliBySentId.delete(i.id),c.ws.readyState===f.OPEN)){let d=this.getOtherCliSessionCount(c.ws,c.browserId),l=d>0?{...i,id:c.originalId,i:d}:{...i,id:c.originalId};c.ws.send(JSON.stringify(l))}return}(async()=>{this.cliLastCommandAt.set(e,Date.now());try{if(i.type==="bridge:bye"){let p=this.cliBrowserBySocket.delete(e);this.cliLastCommandAt.delete(e),this.cliSessionKeyBySocket.delete(e),this.cliLabelBySocket.delete(e);for(let[u,m]of this.cliBySentId)m.ws===e&&this.cliBySentId.delete(u);p&&this.broadcastBrowserClientStates();return}if(i.type==="bridge:hello"){let p=typeof i.cliSessionKey=="string"&&i.cliSessionKey.trim()?i.cliSessionKey.trim():this.cliSessionKeyBySocket.get(e)||`ws-${this.nextCliFallbackId++}`;this.cliSessionKeyBySocket.set(e,p),typeof i.cliLabel=="string"&&i.cliLabel.trim()&&this.cliLabelBySocket.set(e,i.cliLabel.trim()),e.readyState===f.OPEN&&e.send(JSON.stringify({id:i.id,result:{cliSessionKey:p,leaseTtlMs:h.CLI_LEASE_TTL_MS,leasing:!0}}));return}if(i.type==="bridge:list-browsers"){e.readyState===f.OPEN&&e.send(JSON.stringify({id:i.id,result:this.listBrowsers()}));return}if(i.type==="bridge:open"){if(typeof i.url!="string"||!i.url)throw new Error("bridge:open requires a url");await this.openUrl(i.url),e.readyState===f.OPEN&&e.send(JSON.stringify({id:i.id,result:{ok:!0,url:i.url}}));return}if(i.type==="bridge:claim"){let p=await this.waitForBrowser(i.browserId),u=this.tryAcquireLease(e,p,{force:i.force===!0});if(!u.granted){e.readyState===f.OPEN&&e.send(JSON.stringify({id:i.id,error:`tab ${p.id} is locked by another cli`,o:u.lock}));return}this.setCliBrowserTarget(e,p.id),this.recordBrowserAction(p.id,u.bootedCount>0?`cli force-claimed tab (booted ${u.bootedCount})`:"cli claimed tab"),e.readyState===f.OPEN&&e.send(JSON.stringify({id:i.id,result:{browserId:p.id,lockedBy:u.lease.cliSessionKey,lockExpiresAt:u.lease.expiresAt,bootedCount:u.bootedCount}}));return}let a=await this.waitForBrowser(i.browserId);if(le(i)){let p=this.tryAcquireLease(e,a);if(!p.granted){e.readyState===f.OPEN&&e.send(JSON.stringify({id:i.id,error:`tab ${a.id} is locked by another cli \u2014 use \`sootsim claim ${a.id} --force\` or \`sootsim open --new\``,o:p.lock}));return}}else this.ensureCliSessionKey(e);this.setCliBrowserTarget(e,a.id),this.recordBrowserAction(a.id,this.describeForwardedCommand(i));let c=this.nextCommandId++;this.cliBySentId.set(c,{browserId:a.id,ws:e,originalId:i.id});let{browserId:d,...l}=i;a.ws.send(JSON.stringify({...l,id:c}))}catch(a){e.readyState===f.OPEN&&e.send(JSON.stringify({id:i.id,error:a instanceof Error?a.message:String(a)}))}})()}}),e.on("close",()=>{if(this.agentHost.unregisterSocket(e),o==="browser"&&s){this.rememberDisconnectedBrowser(s),this.primaryBrowserId===s.id&&(this.primaryBrowserId=this.getOpenBrowser()?.id??null);for(let[n,i]of this.pendingCommands)i.browserId===s.id&&(i.reject(new Error("browser disconnected")),this.pendingCommands.delete(n));for(let[n,i]of this.cliBySentId)i.browserId===s.id&&(i.ws.readyState===f.OPEN&&i.ws.send(JSON.stringify({id:i.originalId,error:"browser disconnected before responding"})),this.cliBySentId.delete(n));this.broadcastBrowserAssignments(),this.broadcastBrowserClientStates()}else if(o==="cli"){let n=this.cliBrowserBySocket.delete(e);this.cliLastCommandAt.delete(e),this.cliSessionKeyBySocket.delete(e),this.cliLabelBySocket.delete(e);for(let[i,a]of this.cliBySentId)a.ws===e&&this.cliBySentId.delete(i);n&&this.broadcastBrowserClientStates()}})})}afterBind(){if(process.stderr.write(`ws bridge listening on port ${this.port}
|
|
9
|
+
`),this.cliIdleTimer=setInterval(()=>this.sweepIdleCliClients(),3e4),this.cliIdleTimer.unref(),this.shouldWriteLockfile){try{if(A(),!H(this.buildLockfileSnapshot()))throw new Error("another sootsim daemon wrote the lockfile during startup \u2014 aborting")}catch(e){throw process.stderr.write(`ws bridge failed to claim daemon lockfile: ${String(e)}
|
|
10
|
+
`),e}this.heartbeatTimer=setInterval(()=>{try{this.writeLockfileSnapshot()}catch{}},ue),this.heartbeatTimer.unref()}this.agentHost.seedOnBoot()}buildLockfileSnapshot(){return{schema:1,pid:process.pid,platform:process.platform,bridgePort:this.effectivePort,runtimePort:this.effectivePort,activeRuntime:this.activeRuntimeVersion,activeRuntimeDir:this.activeRuntimeDirPath,startedAt:this.startedAt,heartbeatAt:Date.now()}}writeLockfileSnapshot(){j(this.buildLockfileSnapshot())}refreshActiveRuntime(){this.activeRuntimeVersion=D(),this.activeRuntimeDirPath=M()}setActiveRuntime(e){if(_(e),this.refreshActiveRuntime(),this.shouldWriteLockfile&&this.httpServer)try{this.writeLockfileSnapshot()}catch{}let t=JSON.stringify({type:"runtime:changed",version:e,runtimeDir:this.activeRuntimeDirPath});for(let r of this.browsers.values())if(r.ws.readyState===f.OPEN)try{r.ws.send(t)}catch{}return{version:e,runtimeDir:this.activeRuntimeDirPath}}getActiveRuntime(){return{version:this.activeRuntimeVersion,runtimeDir:this.activeRuntimeDirPath}}removeLockfile(){if(this.shouldWriteLockfile)try{O()}catch{}}handleHttpRequest(e,t){let r=(e.method||"GET").toUpperCase();if(r!=="GET"&&r!=="HEAD"){t.writeHead(405,{Allow:"GET, HEAD"}),t.end("method not allowed");return}let o=new URL(e.url||"/","http://localhost");if(o.pathname==="/__bundle-proxy"){let c=o.searchParams.get("url");if(!c){t.writeHead(400,{"Content-Type":"text/plain"}),t.end("bundle-proxy: missing url query param");return}let d;try{d=new URL(c)}catch{t.writeHead(400,{"Content-Type":"text/plain"}),t.end("bundle-proxy: invalid url");return}let l=d.hostname;if(!(l==="localhost"||l==="127.0.0.1"||l==="::1"||l.endsWith(".localhost"))){t.writeHead(403,{"Content-Type":"text/plain"}),t.end("bundle-proxy: only loopback targets allowed");return}(async()=>{try{let u=await fetch(d.toString(),{redirect:"follow"}),m={},g=u.headers.get("content-type");if(g&&(m["Content-Type"]=g),m["Cache-Control"]="no-store",t.writeHead(u.status,m),!u.body){t.end();return}let S=u.body.getReader();for(;;){let{done:B,value:k}=await S.read();if(B)break;t.write(Buffer.from(k))}t.end()}catch(u){t.writeHead(502,{"Content-Type":"text/plain"}),t.end(`bundle-proxy: upstream fetch failed: ${u instanceof Error?u.message:String(u)}`)}})();return}if(o.pathname==="/healthz"){t.writeHead(200,{"Content-Type":"application/json","Cache-Control":"no-store"}),t.end(JSON.stringify({ok:!0,pid:process.pid,platform:process.platform,bridgePort:this.effectivePort,runtimePort:this.effectivePort,activeRuntime:this.activeRuntimeVersion,startedAt:this.startedAt,uptimeMs:this.startedAt>0?Date.now()-this.startedAt:0}));return}this.refreshActiveRuntime();let s=this.activeRuntimeDirPath;if(!s){t.writeHead(503,{"Content-Type":"text/plain; charset=utf-8"}),t.end("sootsim: no active runtime installed. run `sootsim runtime install` to fetch one.");return}let n=o.pathname;if(n==="/runtime"||n==="/runtime/"?n="/":n.startsWith("/runtime/")&&(n=n.slice(8)),(n===""||n==="/")&&(n="/index.html"),n.includes("\0")){t.writeHead(400),t.end("bad request");return}if(process.platform!=="win32"&&n.includes("\\")){t.writeHead(400),t.end("bad request");return}for(let c of n.split("/"))if(c===".."){t.writeHead(403),t.end("forbidden");return}let i=y.resolve(s,"."+n),a=s.endsWith(y.sep)?s:s+y.sep;if(!i.startsWith(a)&&i!==s){t.writeHead(403),t.end("forbidden");return}v.realpath(i,(c,d)=>{let l=c?i:d,p=l.endsWith(y.sep)?l:l+y.sep;if(!c){let u=(()=>{try{let m=v.realpathSync(s);return m.endsWith(y.sep)?m:m+y.sep}catch{return a}})();if(!p.startsWith(u)&&l+y.sep!==u){t.writeHead(403),t.end("forbidden");return}}v.stat(l,(u,m)=>{if(u||!m?.isFile()){let k=y.extname(n).toLowerCase();if(k&&k!==".html"){t.writeHead(404),t.end("not found");return}let re=y.join(s,"index.html");v.readFile(re,(ie,se)=>{if(ie){t.writeHead(404),t.end("not found");return}if(t.writeHead(200,{"Content-Type":"text/html; charset=utf-8"}),r==="HEAD"){t.end();return}t.end(se)});return}let g=y.extname(l).toLowerCase(),S=pe[g]||"application/octet-stream";if(t.writeHead(200,{"Content-Type":S}),r==="HEAD"){t.end();return}let B=v.createReadStream(l);B.pipe(t),B.on("error",()=>{try{t.end()}catch{}})})})}sweepIdleCliClients(){let e=Date.now(),t=!1;for(let[r,o]of this.cliBrowserBySocket){let s=this.cliLastCommandAt.get(r)??0;if(!(e-s<h.CLI_IDLE_TIMEOUT_MS)){this.cliBrowserBySocket.delete(r),this.cliLastCommandAt.delete(r);for(let[n,i]of this.cliBySentId)i.ws===r&&this.cliBySentId.delete(n);try{r.close(1e3,"idle timeout")}catch{}t=!0}}t&&this.broadcastBrowserClientStates(),this.sweepRestorableBrowsers(e)}listBrowsers(){return Array.from(this.browsers.values()).sort((e,t)=>e.id===this.primaryBrowserId?-1:t.id===this.primaryBrowserId?1:e.connectedAt-t.connectedAt).map(e=>this.describeBrowser(e))}async sendCommand(e){let t=await this.waitForBrowser(e.browserId),r=this.nextCommandId++;return new Promise((o,s)=>{let n=setTimeout(()=>{this.pendingCommands.delete(r),this.broadcastBrowserClientStates(),s(new Error("command timed out after 30s"))},3e4);this.pendingCommands.set(r,{browserId:t.id,resolve:c=>{clearTimeout(n),this.pendingCommands.delete(r),this.broadcastBrowserClientStates(),o(c)},reject:c=>{clearTimeout(n),this.pendingCommands.delete(r),this.broadcastBrowserClientStates(),s(c)}}),this.broadcastBrowserClientStates();let{browserId:i,...a}=e;t.ws.send(JSON.stringify({...a,id:r}))})}async evaluate(e,t){return this.sendCommand({type:"evaluate",code:e,browserId:t})}async focusBrowser(e){return this.sendCommand({type:"focus",browserId:e})}async closeBrowser(e){return this.sendCommand({type:"close",browserId:e})}async openPathInEditor(e,t,r){let o=t!=null?`:${t}${r!=null?`:${r}`:""}`:"",s=`${e}${o}`,n=(a,c)=>new Promise(d=>{try{let l=T(a,c,{detached:!0,stdio:"ignore"}),p=!1;l.on("error",()=>{p||(p=!0,d(!1))}),l.on("spawn",()=>{p||(p=!0,l.unref(),d(!0))})}catch{d(!1)}}),i=process.env.REACT_EDITOR||process.env.EDITOR;if(i){let a=i.split(" ").filter(Boolean);if(a.length&&await n(a[0],[...a.slice(1),"-g",s]))return}await n("cursor",["-g",s])||await n("code",["-g",s])||await this.openUrl(e)}async openUrl(e){if(this.openUrlHandler){await this.openUrlHandler(e);return}if(process.platform==="darwin"){T("open",["-g",e],{detached:!0,stdio:"ignore"}).unref();return}if(process.platform==="win32"){T("cmd",["/c","start","",e],{detached:!0,stdio:"ignore"}).unref();return}T("xdg-open",[e],{detached:!0,stdio:"ignore"}).unref()}async close(){if(this.cliIdleTimer&&(clearInterval(this.cliIdleTimer),this.cliIdleTimer=null),this.heartbeatTimer&&(clearInterval(this.heartbeatTimer),this.heartbeatTimer=null),this.shouldWriteLockfile)try{O()}catch{}this.effectivePort=0,this.startedAt=0,this.agentHost.close();for(let[r,o]of this.pendingCommands)o.reject(new Error("server closing")),this.pendingCommands.delete(r);for(let r of this.browsers.values())r.ws.close();this.browsers.clear(),this.primaryBrowserId=null;let e=this.wss,t=this.httpServer;if(this.wss=null,this.httpServer=null,e)try{e.close()}catch{}if(t)try{t.close()}catch{}}describeBrowser(e){let t;try{t=e.ws.readyState}catch{t=f.CLOSED}let r=this.getActiveLease(e);return{id:e.id,origin:e.origin,url:e.url,title:e.title,userAgent:e.userAgent,connectedAt:e.connectedAt,lastSeenAt:e.lastSeenAt,lastActiveAt:e.lastActiveAt||void 0,isPrimary:e.id===this.primaryBrowserId,readyState:t===f.OPEN?"open":t===f.CLOSING?"closing":"closed",attachedCliCount:this.getAttachedCliCount(e.id),lockedBy:r?r.cliLabel||r.cliSessionKey:void 0,lockedByKind:r?r.kind:void 0,lockExpiresAt:r?r.expiresAt:void 0,userFocused:e.userFocused||void 0}}getActiveLease(e){let t=e.cliLease;return t?Date.now()>=t.expiresAt?(e.cliLease=void 0,null):t:null}tryAcquireLease(e,t,r={}){let o=this.cliSessionKeyBySocket.get(e)??(()=>{let l=`ws-${this.nextCliFallbackId++}`;return this.cliSessionKeyBySocket.set(e,l),l})(),s=this.cliLabelBySocket.get(e),n=Date.now(),i=this.getActiveLease(t),a=i&&i.cliSessionKey===o,c=0;if(i&&!a&&!r.force)return{granted:!1,lease:i,lock:{by:i.cliLabel||i.cliSessionKey,expiresInMs:Math.max(0,i.expiresAt-n)},bootedCount:0};if(i&&!a&&r.force)for(let[l,p]of this.cliBrowserBySocket){if(p!==t.id)continue;let u=this.cliSessionKeyBySocket.get(l);if(u&&u!==o){this.cliBrowserBySocket.delete(l);try{l.close(1e3,"lease claimed by another cli")}catch{}c++}}let d={kind:"cli",cliSessionKey:o,cliLabel:s,expiresAt:n+h.CLI_LEASE_TTL_MS};return t.cliLease=d,{granted:!0,lease:d,bootedCount:c}}updateUserFocusLease(e,t){let r=t;e.userFocused!==r&&(e.userFocused=r,this.broadcastBrowserClientStates())}updateUserActivity(e){let t=this.getActiveLease(e);t&&t.kind==="cli"||(e.cliLease={kind:"user-active",cliSessionKey:"__user-active__",cliLabel:"active user",expiresAt:Date.now()+h.USER_ACTIVE_LEASE_TTL_MS},this.broadcastBrowserClientStates())}ensureCliSessionKey(e){let t=this.cliSessionKeyBySocket.get(e);if(t)return t;let r=`ws-${this.nextCliFallbackId++}`;return this.cliSessionKeyBySocket.set(e,r),r}getOpenBrowser(e){if(e){let r=this.browsers.get(e);return r?.ws.readyState===f.OPEN?r:null}let t=this.primaryBrowserId!=null?this.browsers.get(this.primaryBrowserId):null;if(t?.ws.readyState===f.OPEN)return t;for(let r of this.browsers.values())if(r.ws.readyState===f.OPEN)return r;return null}async waitForBrowser(e,t={}){let r=t.attempts??10,o=t.intervalMs??200;for(let s=0;s<r;s++){let n=this.getOpenBrowser(e);if(n)return n;await new Promise(i=>setTimeout(i,o))}throw new Error(e?`no browser connected with id ${e}`:"no browser connected")}shouldPromoteBrowser(e){let t=this.primaryBrowserId?this.browsers.get(this.primaryBrowserId):null,r=e.origin?.includes(":5173"),o=t?.origin?.includes(":5173");return!t||t.ws.readyState!==f.OPEN||!!r||!o}broadcastBrowserAssignments(){for(let e of this.browsers.values())e.ws.readyState===f.OPEN&&e.ws.send(JSON.stringify({type:"bridge:welcome",browserId:e.id,isPrimary:e.id===this.primaryBrowserId}))}broadcastBrowserClientStates(){for(let e of this.browsers.values()){if(e.ws.readyState!==f.OPEN)continue;let t=this.getActiveLease(e),r={type:"bridge:client-state",attachedCliCount:this.getAttachedCliCount(e.id),activeAgentCommandCount:this.getActiveAgentCommandCount(e.id),recentActions:e.recentActions,lockedBy:t?t.cliLabel||t.cliSessionKey:void 0,lockedByKind:t?t.kind:void 0,lockExpiresAt:t?t.expiresAt:void 0,userFocused:e.userFocused||void 0};e.ws.send(JSON.stringify(r))}}setCliBrowserTarget(e,t){let r=this.cliBrowserBySocket.get(e);r!==t&&(this.cliBrowserBySocket.set(e,t),this.recordBrowserAction(t,r?"cli switched tabs":"cli connected",!1),this.broadcastBrowserClientStates())}recordBrowserAction(e,t,r=!0){let o=t?.trim();if(!o)return;let s=this.browsers.get(e);if(!s)return;let n=Date.now();s.lastActiveAt=n,s.recentActions=[{label:o,at:n},...s.recentActions.filter(i=>i.label!==o)].slice(0,4),r&&this.broadcastBrowserClientStates()}describeForwardedCommand(e){switch(e?.type){case"evaluate":return"evaluated page state";case"screenshot":return"captured screenshot";case"tap":return"sent tap event";case"keyboard":return e?.action==="type"?"typed text":"used keyboard";case"tree":return"dumped tree";case"focus":return"focused tab";case"close":return"requested close";default:return typeof e?.type=="string"?e.type:null}}getAttachedCliCount(e){let t=new Set;for(let[r,o]of this.cliBrowserBySocket){if(o!==e||r.readyState!==f.OPEN)continue;let s=this.cliSessionKeyBySocket.get(r);t.add(s??`ws-unknown-${t.size}`)}return t.size}getOtherCliSessionCount(e,t){let r=this.cliSessionKeyBySocket.get(e),o=new Set;for(let[s,n]of this.cliBrowserBySocket){if(n!==t||s.readyState!==f.OPEN)continue;let i=this.cliSessionKeyBySocket.get(s);i&&i===r||o.add(i??`ws-unknown-${o.size}`)}return o.size}getActiveAgentCommandCount(e){let t=0;for(let r of this.pendingCommands.values())r.browserId===e&&t++;return t}tryRestoreBrowserId(e,t){let r=t?.trim();if(!r||r===e.id)return!1;let o=this.browsers.get(r);if(o&&o!==e&&o.ws.readyState===f.OPEN)return!1;let s=this.getRestorableBrowserState(r),n=e.id;this.browsers.delete(n),e.id=r,s&&(e.recentActions=s.recentActions.map(i=>({...i})),e.lastActiveAt=s.lastActiveAt,e.cliLease=s.cliLease?{...s.cliLease}:void 0,this.restorableBrowsers.delete(r)),this.browsers.set(e.id,e),this.primaryBrowserId===n&&(this.primaryBrowserId=e.id);for(let[i,a]of this.cliBrowserBySocket)a===n&&this.cliBrowserBySocket.set(i,e.id);return!0}rememberDisconnectedBrowser(e){let t=this.getActiveLease(e);this.restorableBrowsers.set(e.id,{recentActions:e.recentActions.map(r=>({...r})),lastActiveAt:e.lastActiveAt,cliLease:t&&t.kind==="cli"?{...t}:void 0,expiresAt:Date.now()+h.BROWSER_RECONNECT_TTL_MS}),this.browsers.delete(e.id)}getRestorableBrowserState(e){let t=this.restorableBrowsers.get(e);return t?t.expiresAt<=Date.now()?(this.restorableBrowsers.delete(e),null):(t.cliLease&&t.cliLease.expiresAt<=Date.now()&&(t.cliLease=void 0),t):null}sweepRestorableBrowsers(e=Date.now()){for(let[t,r]of this.restorableBrowsers)if(!(r.expiresAt>e)){this.restorableBrowsers.delete(t);for(let[o,s]of this.cliBrowserBySocket)s===t&&this.cliBrowserBySocket.delete(o)}}resetServerState(){this.cliIdleTimer&&(clearInterval(this.cliIdleTimer),this.cliIdleTimer=null);let e=this.wss,t=this.httpServer;if(this.wss=null,this.httpServer=null,e)try{e.close()}catch{}if(t)try{t.close()}catch{}}};async function Le(h,e={}){(h.includes("--help")||h.includes("-h"))&&(console.log(`
|
|
11
|
+
sootsim server \u2014 run the sootsim bridge daemon in the foreground
|
|
12
|
+
|
|
13
|
+
hosts the WS bridge that CLI commands talk to. once running, any sootsim
|
|
14
|
+
tab (browser, electron, headless playwright) that connects to port 7668
|
|
15
|
+
becomes drivable from 'sootsim describe', 'sootsim tap', etc.
|
|
16
|
+
|
|
17
|
+
usage:
|
|
18
|
+
sootsim server [options]
|
|
19
|
+
|
|
20
|
+
options:
|
|
21
|
+
--port <n> bridge port (defaults to ${7668})
|
|
22
|
+
--quiet suppress per-connection logging
|
|
23
|
+
|
|
24
|
+
examples:
|
|
25
|
+
sootsim server
|
|
26
|
+
sootsim server --port 7668 --quiet
|
|
27
|
+
`),process.exit(0));let t=h.indexOf("--port"),r=t>=0&&h[t+1]?Number(h[t+1]):e.port??7668;Number.isNaN(r)&&(console.error(` invalid --port value: ${h[t+1]}`),process.exit(1));let o=h.includes("--quiet")||h.includes("-q"),s=W();s&&F(s)&&(console.error(` a sootsim daemon is already running (pid ${s.pid}, port ${s.bridgePort})`),console.error(" stop it with 'sootsim daemon stop' first"),process.exit(1)),A();let n=new I({port:r,writeLockfile:!0}),i=await n.startAsync({silent:o}),a=Date.now(),c=u=>{o||process.stdout.write(`${u}
|
|
28
|
+
`)},d=new Set,l=setInterval(()=>{let u=n.listBrowsers(),m=new Set(u.map(g=>g.id));for(let g of u)if(!d.has(g.id)){let S=g.title||g.url||g.origin||"(unknown)";c(` + ${g.id} ${S}`)}for(let g of d)m.has(g)||c(` - ${g}`);d.clear();for(let g of m)d.add(g)},500);c(`sootsim bridge listening on ws://localhost:${i} (runtime http on same port)`),i!==r&&c(` (preferred port ${r} was taken \u2014 fell back to ${i})`),c(" ready for browser tabs, electron, or headless playwright to connect"),c(" (ctrl-c to stop)");let p=async u=>{clearInterval(l),c(`
|
|
29
|
+
${u} received \u2014 shutting down after ${Math.round((Date.now()-a)/1e3)}s`);try{await n.close()}catch{}process.exit(0)};process.on("SIGINT",()=>p("SIGINT")),process.on("SIGTERM",()=>p("SIGTERM")),process.on("SIGHUP",()=>p("SIGHUP")),process.on("exit",()=>{try{n.removeLockfile()}catch{}}),await new Promise(()=>{})}export{Le as runServer};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
/*! sootsim v0.0.
|
|
2
|
-
import"./chunk-
|
|
1
|
+
/*! sootsim v0.0.2 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
|
|
2
|
+
import"./chunk-K7LDP7JL.js";import*as l from"fs";import*as i from"path";import{fileURLToPath as f}from"url";function g(){try{let s=f(import.meta.resolve("sootsim/package.json"));return i.join(i.dirname(s),"skills")}catch{let s=i.dirname(f(import.meta.url));return i.resolve(s,"../../skills")}}var m=g();function d(s){let n=s.match(/^---\n([\s\S]*?)\n---/);if(!n)return null;let e=n[1],o=e.match(/^name:\s*(.+)$/m),r=e.match(/^description:\s*(.+)$/m);return o?{name:o[1].trim(),description:r?.[1]?.trim()||""}:null}function p(){if(!l.existsSync(m))return[];let s=l.readdirSync(m).filter(e=>e.endsWith(".md")),n=[];for(let e of s){let o=l.readFileSync(i.join(m,e),"utf8"),r=d(o);r&&n.push({...r,filename:e})}return n}function u(s,n){let e=i.join(m,s.filename),o=i.join(n,s.filename);l.copyFileSync(e,o),console.log(` copied ${s.name} \u2192 ${o}`)}async function h(s){if(s.includes("--list")||s.includes("-l")||s.length===0){let t=p();if(t.length===0){console.log(" no skills found");return}console.log(`
|
|
3
3
|
available skills:
|
|
4
4
|
`);for(let c of t)console.log(` ${c.name}`),console.log(` ${c.description}
|
|
5
5
|
`);console.log(" usage: sootsim skills <target-dir> [skill-name...]"),console.log(" example: sootsim skills ./my-project"),console.log(` example: sootsim skills ./my-project debug perf
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
/*! sootsim v0.0.
|
|
2
|
-
import"./chunk-
|
|
1
|
+
/*! sootsim v0.0.2 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
|
|
2
|
+
import"./chunk-K7LDP7JL.js";import{spawn as m}from"child_process";import{existsSync as h,readdirSync as w}from"fs";import{resolve as u,dirname as x}from"path";import{fileURLToPath as y}from"url";var d=x(y(import.meta.resolve("sootsim-engine/package.json")));async function v(t,r){let l=t.includes("--flows"),f=t.includes("--detox"),c=t.includes("--parallel"),i=t.includes("--watch"),a=t.find((e,o)=>t[o-1]==="--reporter")||"console";if((t.includes("--help")||t.includes("-h"))&&(console.log(`
|
|
3
3
|
sootsim test \u2014 run tests against sootsim
|
|
4
4
|
|
|
5
5
|
usage:
|
|
@@ -26,6 +26,6 @@ examples:
|
|
|
26
26
|
notes:
|
|
27
27
|
playwright is best for smoke and package-local debugging
|
|
28
28
|
--detox is the preferred parity lane for behavior that should match real RN
|
|
29
|
-
`),process.exit(0)),l)await b(t,r,c,a);else if(f){let{runDetox:e}=await import("./detox-
|
|
29
|
+
`),process.exit(0)),l)await b(t,r,c,a);else if(f){let{runDetox:e}=await import("./detox-PK74V2Y7.js");await e(t.filter(o=>o!=="--detox"),r)}else await g(t,r)}async function g(t,r){let l=["playwright","test"],f=t.find((e,o)=>t[o-1]==="-t")||t.find((e,o)=>t[o-1]==="--testNamePattern")||t.find((e,o)=>t[o-1]==="--grep");f&&l.push("-g",f),t.includes("--watch")&&l.push("--ui");let c=new Set(["--flows","--detox","--parallel","--watch","--reporter","-t","--testNamePattern","--grep"]);for(let e=0;e<t.length;e++){if(c.has(t[e])){(t[e]==="-t"||t[e]==="--testNamePattern"||t[e]==="--grep"||t[e]==="--reporter")&&e++;continue}l.push(t[e])}console.log(` running: npx ${l.join(" ")}`);let i=m("npx",l,{cwd:d,stdio:"inherit",env:{...process.env}}),a=await new Promise(e=>{i.on("exit",o=>e(o||0))});process.exit(a)}async function b(t,r,l,f){let c=["flows","test/flows","e2e/flows",".maestro","maestro"],i=[];for(let o of c){let s=u(d,o);if(h(s)){let p=w(s).filter(n=>n.endsWith(".yaml")||n.endsWith(".yml"));for(let n of p)i.push(u(s,n))}}for(let o of c){let s=u(process.cwd(),o);if(!s.startsWith(d)&&h(s)){let p=w(s).filter(n=>n.endsWith(".yaml")||n.endsWith(".yml"));for(let n of p)i.push(u(s,n))}}i.length===0&&(console.log(" no flow files found in flows/, test/flows/, or e2e/flows/"),process.exit(0)),console.log(` found ${i.length} flow(s)`);let a=0,e=0;for(let o of i)try{let{runFlowPlayback:s}=await import("./flow-6Y3E6E5P.js"),p=r.port?`http://localhost:${r.port}`:"http://localhost:5173";if(await s([o,"--url",p,"--new"])!==0){e++;continue}a++}catch{e++}console.log(`
|
|
30
30
|
results: ${a} passed, ${e} failed (${i.length} total)
|
|
31
31
|
`),process.exit(e>0?1:0)}export{v as runTest};
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
/*! sootsim v0.0.2 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
|
|
2
|
+
import{c as a,d as b}from"./chunk-TSZBQS6W.js";import"./chunk-CZZB4DWG.js";import"./chunk-NE62JSI6.js";import"./chunk-LXCFGKL2.js";import"./chunk-FUQ4XA6I.js";import"./chunk-XXUAOYYT.js";import"./chunk-EIZCWDRE.js";import"./chunk-CXTA5VGA.js";import"./chunk-K7LDP7JL.js";export{a as resolveDefaultUploadOrigin,b as runUpload};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
/*! sootsim v0.0.
|
|
2
|
-
import{d as n}from"./chunk-
|
|
1
|
+
/*! sootsim v0.0.2 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
|
|
2
|
+
import{d as n}from"./chunk-NE62JSI6.js";import"./chunk-K7LDP7JL.js";async function s(){let o=await n();o?.token||(console.log(" not signed in"),process.exit(1));let e=o.user;console.log(` ${e?.email||e?.name||e?.id||"signed in"}`),console.log(` origin: ${o.origin}`),console.log(` updated: ${o.updatedAt}`)}export{s as runWhoami};
|
|
@@ -0,0 +1,414 @@
|
|
|
1
|
+
/*! sootsim v0.0.2 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
|
|
2
|
+
let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
|
|
3
|
+
"use strict";
|
|
4
|
+
var __create = Object.create;
|
|
5
|
+
var __defProp = Object.defineProperty;
|
|
6
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
7
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
8
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
9
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
10
|
+
var __export = (target, all) => {
|
|
11
|
+
for (var name in all)
|
|
12
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
13
|
+
};
|
|
14
|
+
var __copyProps = (to, from, except, desc) => {
|
|
15
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
16
|
+
for (let key of __getOwnPropNames(from))
|
|
17
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
18
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
19
|
+
}
|
|
20
|
+
return to;
|
|
21
|
+
};
|
|
22
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
23
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
24
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
25
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
26
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
27
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
28
|
+
mod
|
|
29
|
+
));
|
|
30
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
31
|
+
|
|
32
|
+
// src/agent-daemon-client.ts
|
|
33
|
+
var agent_daemon_client_exports = {};
|
|
34
|
+
__export(agent_daemon_client_exports, {
|
|
35
|
+
AgentDaemonClient: () => AgentDaemonClient,
|
|
36
|
+
AgentDaemonError: () => AgentDaemonError,
|
|
37
|
+
connectToDaemon: () => connectToDaemon,
|
|
38
|
+
ensureDaemonRunning: () => ensureDaemonRunning,
|
|
39
|
+
isBridgeUp: () => isBridgeUp
|
|
40
|
+
});
|
|
41
|
+
module.exports = __toCommonJS(agent_daemon_client_exports);
|
|
42
|
+
var import_node_child_process = require("node:child_process");
|
|
43
|
+
var import_node_net = __toESM(require("node:net"), 1);
|
|
44
|
+
var import_ws = require("ws");
|
|
45
|
+
|
|
46
|
+
// src/agent-sessions.ts
|
|
47
|
+
var import_node_fs = __toESM(require("node:fs"), 1);
|
|
48
|
+
var import_node_path = __toESM(require("node:path"), 1);
|
|
49
|
+
|
|
50
|
+
// src/attached-projects.ts
|
|
51
|
+
var COST_HISTORY_MAX_AGE_MS = 14 * 24 * 60 * 60 * 1e3;
|
|
52
|
+
|
|
53
|
+
// src/agent-sessions.ts
|
|
54
|
+
function resolveSootsimInvocation() {
|
|
55
|
+
if (process.env.SOOTSIM_BIN) {
|
|
56
|
+
return { cmd: process.env.SOOTSIM_BIN, prefixArgs: [] };
|
|
57
|
+
}
|
|
58
|
+
if (process.versions.electron) {
|
|
59
|
+
const resourcesPath = process.resourcesPath;
|
|
60
|
+
if (resourcesPath) {
|
|
61
|
+
const candidates = [
|
|
62
|
+
import_node_path.default.join(resourcesPath, "bin", "sootsim"),
|
|
63
|
+
import_node_path.default.join(resourcesPath, "bin", `sootsim-${process.platform}-${process.arch}`)
|
|
64
|
+
];
|
|
65
|
+
for (const c of candidates) {
|
|
66
|
+
if (import_node_fs.default.existsSync(c)) return { cmd: c, prefixArgs: [] };
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
const workspace = tryWorkspaceSootsim();
|
|
71
|
+
if (workspace) return workspace;
|
|
72
|
+
const argv0 = process.argv[0];
|
|
73
|
+
const argv1 = process.argv[1];
|
|
74
|
+
if (argv1 && /\.(ts|tsx|mjs|cjs|js)$/.test(argv1)) {
|
|
75
|
+
return { cmd: argv0, prefixArgs: [argv1] };
|
|
76
|
+
}
|
|
77
|
+
if (!argv1 || argv1.includes("/.bin/")) {
|
|
78
|
+
throw new Error(
|
|
79
|
+
"sootsim CLI not found. set SOOTSIM_BIN to the path of the sootsim binary, or build the workspace CLI via `bun run --cwd packages/sootsim build:cli`."
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
return { cmd: argv0, prefixArgs: [] };
|
|
83
|
+
}
|
|
84
|
+
function tryWorkspaceSootsim() {
|
|
85
|
+
try {
|
|
86
|
+
const sootsimDir = resolveSootsimPackageDir();
|
|
87
|
+
if (!sootsimDir) return null;
|
|
88
|
+
const binaryName = `sootsim-${process.platform}-${process.arch}`;
|
|
89
|
+
const distBinary = import_node_path.default.join(sootsimDir, "dist-bin", binaryName);
|
|
90
|
+
if (import_node_fs.default.existsSync(distBinary)) return { cmd: distBinary, prefixArgs: [] };
|
|
91
|
+
const distBin = import_node_path.default.join(sootsimDir, "dist-cli", "bin.js");
|
|
92
|
+
if (import_node_fs.default.existsSync(distBin)) {
|
|
93
|
+
try {
|
|
94
|
+
const src = import_node_path.default.join(sootsimDir, "cli", "commands", "agent-wrapper.ts");
|
|
95
|
+
if (import_node_fs.default.existsSync(src)) {
|
|
96
|
+
const srcMtime = import_node_fs.default.statSync(src).mtimeMs;
|
|
97
|
+
const buildMtime = import_node_fs.default.statSync(distBin).mtimeMs;
|
|
98
|
+
if (buildMtime < srcMtime) {
|
|
99
|
+
console.warn(
|
|
100
|
+
`[sootsim] dist-cli/bin.js is older than agent-wrapper.ts \u2014 rebuild with \`bun run --cwd packages/sootsim build:cli\` (watch:cli:binary builds dist-bin/ instead).`
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
} catch {
|
|
105
|
+
}
|
|
106
|
+
return { cmd: process.execPath, prefixArgs: [distBin] };
|
|
107
|
+
}
|
|
108
|
+
return null;
|
|
109
|
+
} catch {
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
function resolveSootsimPackageDir() {
|
|
114
|
+
try {
|
|
115
|
+
const resolved = require.resolve("sootsim/package.json");
|
|
116
|
+
return import_node_path.default.dirname(resolved);
|
|
117
|
+
} catch {
|
|
118
|
+
}
|
|
119
|
+
const here = fileFromImportMeta();
|
|
120
|
+
if (!here) return null;
|
|
121
|
+
let cur = import_node_path.default.dirname(here);
|
|
122
|
+
for (let i = 0; i < 8; i++) {
|
|
123
|
+
const pkg = import_node_path.default.join(cur, "package.json");
|
|
124
|
+
try {
|
|
125
|
+
if (import_node_fs.default.existsSync(pkg)) {
|
|
126
|
+
const parsed = JSON.parse(import_node_fs.default.readFileSync(pkg, "utf8"));
|
|
127
|
+
if (parsed.name === "sootsim") return cur;
|
|
128
|
+
}
|
|
129
|
+
} catch {
|
|
130
|
+
}
|
|
131
|
+
const parent = import_node_path.default.dirname(cur);
|
|
132
|
+
if (parent === cur) break;
|
|
133
|
+
cur = parent;
|
|
134
|
+
}
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
function fileFromImportMeta() {
|
|
138
|
+
try {
|
|
139
|
+
const url = __sootsim_import_meta_url;
|
|
140
|
+
if (!url || !url.startsWith("file://")) return null;
|
|
141
|
+
return decodeURIComponent(url.slice("file://".length));
|
|
142
|
+
} catch {
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// src/bridge-constants.ts
|
|
148
|
+
var DEFAULT_SOOTSIM_BRIDGE_PORT = 7668;
|
|
149
|
+
|
|
150
|
+
// src/agent-daemon-client.ts
|
|
151
|
+
var AgentDaemonError = class extends Error {
|
|
152
|
+
code;
|
|
153
|
+
constructor(message, code) {
|
|
154
|
+
super(message);
|
|
155
|
+
this.name = "AgentDaemonError";
|
|
156
|
+
this.code = code;
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
var AgentDaemonClient = class {
|
|
160
|
+
ws;
|
|
161
|
+
port;
|
|
162
|
+
commandTimeoutMs;
|
|
163
|
+
ready;
|
|
164
|
+
closed = false;
|
|
165
|
+
nextId = 1;
|
|
166
|
+
pending = /* @__PURE__ */ new Map();
|
|
167
|
+
eventListeners = /* @__PURE__ */ new Set();
|
|
168
|
+
statusListeners = /* @__PURE__ */ new Set();
|
|
169
|
+
disconnectListeners = /* @__PURE__ */ new Set();
|
|
170
|
+
constructor(opts = {}) {
|
|
171
|
+
this.port = opts.port ?? DEFAULT_SOOTSIM_BRIDGE_PORT;
|
|
172
|
+
this.commandTimeoutMs = opts.commandTimeoutMs ?? 15e3;
|
|
173
|
+
this.ws = new import_ws.WebSocket(`ws://localhost:${this.port}`);
|
|
174
|
+
this.ready = new Promise((resolve, reject) => {
|
|
175
|
+
const onOpen = () => {
|
|
176
|
+
this.ws.off("error", onError);
|
|
177
|
+
resolve();
|
|
178
|
+
};
|
|
179
|
+
const onError = (err) => {
|
|
180
|
+
this.ws.off("open", onOpen);
|
|
181
|
+
reject(
|
|
182
|
+
new AgentDaemonError(
|
|
183
|
+
`could not connect to sootsim daemon on port ${this.port}: ${err.message}`,
|
|
184
|
+
"NO_DAEMON"
|
|
185
|
+
)
|
|
186
|
+
);
|
|
187
|
+
};
|
|
188
|
+
this.ws.once("open", onOpen);
|
|
189
|
+
this.ws.once("error", onError);
|
|
190
|
+
});
|
|
191
|
+
this.ws.on("message", (data) => this.handleMessage(data));
|
|
192
|
+
this.ws.on("close", () => this.handleClose());
|
|
193
|
+
}
|
|
194
|
+
async waitReady() {
|
|
195
|
+
return this.ready;
|
|
196
|
+
}
|
|
197
|
+
// --- public api ---
|
|
198
|
+
async listProjects() {
|
|
199
|
+
return this.send("agent:list-projects");
|
|
200
|
+
}
|
|
201
|
+
async upsertProject(input) {
|
|
202
|
+
return this.send("agent:upsert-project", { input });
|
|
203
|
+
}
|
|
204
|
+
async deleteProject(projectId) {
|
|
205
|
+
return this.send("agent:delete-project", { projectId });
|
|
206
|
+
}
|
|
207
|
+
async autoAttachForUrl(input) {
|
|
208
|
+
return this.send("agent:auto-attach-for-url", {
|
|
209
|
+
input
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
async listSessions(projectId) {
|
|
213
|
+
return this.send("agent:list-sessions", { projectId });
|
|
214
|
+
}
|
|
215
|
+
async startSession(input) {
|
|
216
|
+
return this.send("agent:start-session", { input });
|
|
217
|
+
}
|
|
218
|
+
async sendPrompt(sessionId, prompt) {
|
|
219
|
+
return this.send("agent:send-prompt", {
|
|
220
|
+
sessionId,
|
|
221
|
+
prompt
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
async endSession(sessionId) {
|
|
225
|
+
return this.send("agent:end-session", { sessionId });
|
|
226
|
+
}
|
|
227
|
+
async getTranscript(sessionId) {
|
|
228
|
+
return this.send("agent:get-transcript", {
|
|
229
|
+
sessionId
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
async getPaths() {
|
|
233
|
+
return this.send("agent:get-paths");
|
|
234
|
+
}
|
|
235
|
+
async subscribeEvents(sessionId) {
|
|
236
|
+
return this.send("agent:subscribe-events", {
|
|
237
|
+
sessionId
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
async unsubscribeEvents(sessionId) {
|
|
241
|
+
return this.send("agent:unsubscribe-events", {
|
|
242
|
+
sessionId
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
onAgentEvent(cb) {
|
|
246
|
+
this.eventListeners.add(cb);
|
|
247
|
+
return () => this.eventListeners.delete(cb);
|
|
248
|
+
}
|
|
249
|
+
onSessionStatusChange(cb) {
|
|
250
|
+
this.statusListeners.add(cb);
|
|
251
|
+
return () => this.statusListeners.delete(cb);
|
|
252
|
+
}
|
|
253
|
+
onDisconnect(cb) {
|
|
254
|
+
this.disconnectListeners.add(cb);
|
|
255
|
+
return () => this.disconnectListeners.delete(cb);
|
|
256
|
+
}
|
|
257
|
+
close() {
|
|
258
|
+
if (this.closed) return;
|
|
259
|
+
this.closed = true;
|
|
260
|
+
try {
|
|
261
|
+
this.ws.close();
|
|
262
|
+
} catch {
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
// --- internals ---
|
|
266
|
+
async send(type, payload = {}) {
|
|
267
|
+
await this.ready;
|
|
268
|
+
if (this.closed || this.ws.readyState !== import_ws.WebSocket.OPEN) {
|
|
269
|
+
throw new AgentDaemonError("daemon connection is closed", "NO_DAEMON");
|
|
270
|
+
}
|
|
271
|
+
const id = this.nextId++;
|
|
272
|
+
return new Promise((resolve, reject) => {
|
|
273
|
+
const timer = setTimeout(() => {
|
|
274
|
+
this.pending.delete(id);
|
|
275
|
+
reject(
|
|
276
|
+
new AgentDaemonError(
|
|
277
|
+
`${type} timed out after ${Math.round(this.commandTimeoutMs / 1e3)}s`,
|
|
278
|
+
"TIMEOUT"
|
|
279
|
+
)
|
|
280
|
+
);
|
|
281
|
+
}, this.commandTimeoutMs);
|
|
282
|
+
this.pending.set(id, {
|
|
283
|
+
resolve,
|
|
284
|
+
reject,
|
|
285
|
+
timer
|
|
286
|
+
});
|
|
287
|
+
try {
|
|
288
|
+
this.ws.send(JSON.stringify({ id, type, ...payload }));
|
|
289
|
+
} catch (err) {
|
|
290
|
+
clearTimeout(timer);
|
|
291
|
+
this.pending.delete(id);
|
|
292
|
+
reject(err instanceof Error ? err : new Error(String(err)));
|
|
293
|
+
}
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
handleMessage(data) {
|
|
297
|
+
let msg;
|
|
298
|
+
try {
|
|
299
|
+
msg = JSON.parse(String(data));
|
|
300
|
+
} catch {
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
if (!msg || typeof msg !== "object") return;
|
|
304
|
+
if (msg.type === "agent:event") {
|
|
305
|
+
for (const cb of this.eventListeners) {
|
|
306
|
+
try {
|
|
307
|
+
cb({ sessionId: msg.sessionId, event: msg.event });
|
|
308
|
+
} catch {
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
if (msg.type === "agent:session-status") {
|
|
314
|
+
for (const cb of this.statusListeners) {
|
|
315
|
+
try {
|
|
316
|
+
cb(msg.session);
|
|
317
|
+
} catch {
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
if (typeof msg.id !== "number") return;
|
|
323
|
+
const entry = this.pending.get(msg.id);
|
|
324
|
+
if (!entry) return;
|
|
325
|
+
this.pending.delete(msg.id);
|
|
326
|
+
clearTimeout(entry.timer);
|
|
327
|
+
if (msg.error) {
|
|
328
|
+
entry.reject(new AgentDaemonError(msg.error, msg.code));
|
|
329
|
+
} else {
|
|
330
|
+
entry.resolve(msg.result);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
handleClose() {
|
|
334
|
+
if (this.closed) return;
|
|
335
|
+
this.closed = true;
|
|
336
|
+
for (const [, entry] of this.pending) {
|
|
337
|
+
clearTimeout(entry.timer);
|
|
338
|
+
entry.reject(new AgentDaemonError("daemon disconnected", "DISCONNECT"));
|
|
339
|
+
}
|
|
340
|
+
this.pending.clear();
|
|
341
|
+
for (const cb of this.disconnectListeners) {
|
|
342
|
+
try {
|
|
343
|
+
cb();
|
|
344
|
+
} catch {
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
};
|
|
349
|
+
function isBridgeUp(port = DEFAULT_SOOTSIM_BRIDGE_PORT, timeoutMs = 400) {
|
|
350
|
+
return new Promise((resolve) => {
|
|
351
|
+
const socket = new import_node_net.default.Socket();
|
|
352
|
+
let settled = false;
|
|
353
|
+
const done = (ok) => {
|
|
354
|
+
if (settled) return;
|
|
355
|
+
settled = true;
|
|
356
|
+
socket.destroy();
|
|
357
|
+
resolve(ok);
|
|
358
|
+
};
|
|
359
|
+
socket.setTimeout(timeoutMs);
|
|
360
|
+
socket.once("connect", () => done(true));
|
|
361
|
+
socket.once("timeout", () => done(false));
|
|
362
|
+
socket.once("error", () => done(false));
|
|
363
|
+
socket.connect(port, "127.0.0.1");
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
async function ensureDaemonRunning(opts = {}) {
|
|
367
|
+
const port = opts.port ?? DEFAULT_SOOTSIM_BRIDGE_PORT;
|
|
368
|
+
if (await isBridgeUp(port)) {
|
|
369
|
+
return { alreadyRunning: true };
|
|
370
|
+
}
|
|
371
|
+
const { cmd, prefixArgs } = resolveSootsimInvocation();
|
|
372
|
+
const args = [...prefixArgs, "server", "--quiet"];
|
|
373
|
+
if (port !== DEFAULT_SOOTSIM_BRIDGE_PORT) {
|
|
374
|
+
args.push("--port", String(port));
|
|
375
|
+
}
|
|
376
|
+
const child = (0, import_node_child_process.spawn)(cmd, args, {
|
|
377
|
+
detached: true,
|
|
378
|
+
stdio: "ignore",
|
|
379
|
+
env: process.env,
|
|
380
|
+
cwd: process.cwd()
|
|
381
|
+
});
|
|
382
|
+
child.unref();
|
|
383
|
+
const deadline = Date.now() + (opts.startupTimeoutMs ?? 5e3);
|
|
384
|
+
while (Date.now() < deadline) {
|
|
385
|
+
if (await isBridgeUp(port)) return { alreadyRunning: false, pid: child.pid };
|
|
386
|
+
await new Promise((r) => setTimeout(r, 100));
|
|
387
|
+
}
|
|
388
|
+
throw new AgentDaemonError(
|
|
389
|
+
`spawned sootsim daemon on port ${port} but it did not come up in time. run \`sootsim server\` manually to diagnose.`,
|
|
390
|
+
"SPAWN_TIMEOUT"
|
|
391
|
+
);
|
|
392
|
+
}
|
|
393
|
+
async function connectToDaemon(opts = {}) {
|
|
394
|
+
await ensureDaemonRunning({
|
|
395
|
+
port: opts.port,
|
|
396
|
+
startupTimeoutMs: opts.startupTimeoutMs
|
|
397
|
+
});
|
|
398
|
+
const client = new AgentDaemonClient(opts);
|
|
399
|
+
try {
|
|
400
|
+
await client.waitReady();
|
|
401
|
+
} catch (err) {
|
|
402
|
+
client.close();
|
|
403
|
+
throw err;
|
|
404
|
+
}
|
|
405
|
+
return client;
|
|
406
|
+
}
|
|
407
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
408
|
+
0 && (module.exports = {
|
|
409
|
+
AgentDaemonClient,
|
|
410
|
+
AgentDaemonError,
|
|
411
|
+
connectToDaemon,
|
|
412
|
+
ensureDaemonRunning,
|
|
413
|
+
isBridgeUp
|
|
414
|
+
});
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/*! sootsim v0.0.2 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
|
|
2
|
+
let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
|
|
3
|
+
"use strict";
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
21
|
+
|
|
22
|
+
// src/agent-events.ts
|
|
23
|
+
var agent_events_exports = {};
|
|
24
|
+
__export(agent_events_exports, {
|
|
25
|
+
isAgentEvent: () => isAgentEvent,
|
|
26
|
+
parseAgentEventLine: () => parseAgentEventLine
|
|
27
|
+
});
|
|
28
|
+
module.exports = __toCommonJS(agent_events_exports);
|
|
29
|
+
function isAgentEvent(value) {
|
|
30
|
+
if (!value || typeof value !== "object") return false;
|
|
31
|
+
const v = value;
|
|
32
|
+
return typeof v.type === "string" && typeof v.ts === "number";
|
|
33
|
+
}
|
|
34
|
+
function parseAgentEventLine(line) {
|
|
35
|
+
const trimmed = line.trim();
|
|
36
|
+
if (!trimmed) return null;
|
|
37
|
+
try {
|
|
38
|
+
const parsed = JSON.parse(trimmed);
|
|
39
|
+
return isAgentEvent(parsed) ? parsed : null;
|
|
40
|
+
} catch {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
45
|
+
0 && (module.exports = {
|
|
46
|
+
isAgentEvent,
|
|
47
|
+
parseAgentEventLine
|
|
48
|
+
});
|