grix-connector 3.26.2 → 3.26.3
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/LICENSE +176 -0
- package/NOTICE +4 -0
- package/README.md +1 -1
- package/dist/adapter/claude/claude-adapter.js +1 -1
- package/dist/adapter/claude/claude-bridge-server.js +1 -1
- package/dist/adapter/claude/claude-tools.js +1 -1
- package/dist/adapter/claude/claude-worker-client.js +1 -1
- package/dist/adapter/claude/mcp-http-launcher.js +2 -2
- package/dist/adapter/claude/result-timeout.js +1 -1
- package/dist/adapter/claude/skill-scanner.js +2 -2
- package/dist/adapter/codex/codex-bridge.js +8 -8
- package/dist/bridge/bridge.js +9 -11
- package/dist/core/access/allowlist-store.js +1 -1
- package/dist/core/admin/admin-server.js +1 -1
- package/dist/core/aibot/client.js +2 -2
- package/dist/core/file-ops/list-files.js +1 -1
- package/dist/core/installer/installer.js +11 -10
- package/dist/core/installer/npm-registry.js +4 -2
- package/dist/core/proxy/compat/codex-openai/translator.js +1 -1
- package/dist/core/skill-sync/skill-syncer.js +1 -1
- package/dist/core/upgrade/npm-upgrader.js +2 -2
- package/dist/log.js +2 -2
- package/dist/manager.js +2 -2
- package/dist/mcp/stream-http/config.js +1 -1
- package/dist/mcp/stream-http/connection-binding.js +1 -1
- package/dist/mcp/stream-http/security.js +1 -1
- package/dist/mcp/stream-http/tool-executor.js +1 -1
- package/dist/mcp/stream-http/tool-registry.js +1 -1
- package/dist/mcp/stream-http/tool-schemas.js +1 -1
- package/package.json +4 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
import{readJSONFile as o,writeJSONFileAtomic as
|
|
1
|
+
import{readJSONFile as o,writeJSONFileAtomic as i}from"../util/json-file.js";function n(t){const r=o(t);return r&&typeof r=="object"&&"owners"in r&&Array.isArray(r.owners)?r.owners.filter(e=>typeof e=="string"&&e.trim().length>0):[]}async function s(t,r){await i(t,{owners:r})}export{n as readAllowlist,s as writeAllowlist};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{createServer as u}from"node:http";import{log as c}from"../log/logger.js";import{RelayFetchError as p}from"../proxy/relay-credential-fetch.js";class _{server=null;token;handler=null;upgradeHandler=null;probeHandler=null;installHandler=null;proxyHandler=null;relayCredentialHandler=null;constructor(e){this.token=e}setAgentHandler(e){this.handler=e}setUpgradeHandler(e){this.upgradeHandler=e}setProbeHandler(e){this.probeHandler=e}setInstallHandler(e){this.installHandler=e}setProxyHandler(e){this.proxyHandler=e}setRelayCredentialHandler(e){this.relayCredentialHandler=e}async start(e){return new Promise((n,t)=>{this.server=u((r,o)=>this.handleRequest(r,o)),this.server.listen(e,"127.0.0.1",()=>{c.info("admin",`Listening on 127.0.0.1:${e}`),n()}),this.server.on("error",t)})}async stop(){if(this.server)return new Promise(e=>{this.server.close(()=>e())})}handleRequest(e,n){const t=e.method??"",r=e.url??"";if(r==="/api/agents"&&t==="GET")this.handleList(n);else if(r==="/api/agents"&&t==="POST")this.readBody(e).then(o=>this.handleAdd(n,o)).catch(o=>this.error(n,o));else if(t==="DELETE"&&r.startsWith("/api/agents/")){const o=decodeURIComponent(r.slice(12));this.handleRemove(n,o)}else if(t==="POST"&&r.match(/^\/api\/agents\/[^/]+\/restart$/)){const o=decodeURIComponent(r.slice(12,r.lastIndexOf("/restart")));this.handleRestart(n,o)}else if(r==="/api/reload"&&t==="POST")this.handleReload(n);else if(r==="/api/upgrade"&&t==="GET")this.handleCheckUpgrade(n);else if(r==="/api/upgrade"&&t==="POST")this.handleTriggerUpgrade(n);else if(t==="GET"&&r.startsWith("/api/probe"))this.handleProbe(e,n,r);else if(r==="/api/install"&&t==="GET")this.handleInstallList(n);else if(r==="/api/install"&&t==="POST")this.readBody(e).then(o=>this.handleInstall(n,o)).catch(o=>this.error(n,o));else if(t==="GET"&&r.startsWith("/api/install/")){const o=decodeURIComponent(r.slice(13));this.handleInstallProgress(n,o)}else if(r==="/api/proxy"&&t==="GET")this.handleProxyStatus(n);else if(t==="PUT"&&r.match(/^\/api\/proxy\/agents\/[^/]+\/enabled$/)){const o=decodeURIComponent(r.slice(18,-8));this.readBody(e).then(s=>this.handleProxySetAgentRelay(n,o,s)).catch(s=>this.error(n,s))}else if(t==="PUT"&&r.match(/^\/api\/proxy\/agents\/[^/]+\/relay-credential$/)){const o=decodeURIComponent(r.slice(18,-17));this.readBody(e).then(s=>this.handleProxySetAgentRelayCredential(n,o,s)).catch(s=>this.error(n,s))}else if(r==="/api/proxy/hermes-profiles"&&t==="GET")this.handleHermesProfileList(n);else if(t==="PUT"&&r.match(/^\/api\/proxy\/relay-credential\/[^/]+$/)){const o=decodeURIComponent(r.slice(28));this.readBody(e).then(s=>this.handleRelayCredentialEnable(n,o,s)).catch(s=>this.error(n,s))}else if(t==="DELETE"&&r.match(/^\/api\/proxy\/relay-credential\/[^/]+$/)){const o=decodeURIComponent(r.slice(28));this.handleRelayCredentialDisable(n,o)}else if(t==="PUT"&&r==="/api/proxy/enabled")this.readBody(e).then(o=>this.handleProxySetEnabled(n,o)).catch(o=>this.error(n,o));else if(t==="PUT"&&r==="/api/proxy/default-route")this.readBody(e).then(o=>this.handleProxySetDefaultRoute(n,o)).catch(o=>this.error(n,o));else if(t==="PUT"&&r==="/api/proxy/intercept-hosts")this.readBody(e).then(o=>this.handleProxySetInterceptHosts(n,o)).catch(o=>this.error(n,o));else if(t==="PUT"&&r.match(/^\/api\/proxy\/routes\/[^/]+$/)){const o=decodeURIComponent(r.slice(18));this.readBody(e).then(s=>this.handleProxySetRoute(n,o,s)).catch(s=>this.error(n,s))}else if(t==="DELETE"&&r.startsWith("/api/proxy/routes/")){const o=decodeURIComponent(r.slice(18));this.handleProxyDeleteRoute(n,o)}else this.json(n,404,{error:"not_found"})}ensureRelayCredential(e){return this.relayCredentialHandler?!0:(this.json(e,501,{error:"relay credential handler not configured"}),!1)}handleHermesProfileList(e){this.ensureRelayCredential(e)&&this.relayCredentialHandler.listHermesProfiles().then(n=>this.json(e,200,{profiles:n})).catch(n=>this.error(e,n))}async handleRelayCredentialEnable(e,n,t){if(!this.ensureRelayCredential(e))return;const r=t,o=s=>typeof s=="string"&&s.trim()?s.trim():void 0;try{const s=await this.relayCredentialHandler.enable(n.trim(),{virtualKey:o(r?.virtual_key)??"",anthropicBaseUrl:o(r?.anthropic_base_url),openaiBaseUrl:o(r?.openai_base_url),model:o(r?.model),directRelay:r?.direct_relay});this.json(e,200,s??{ok:!0})}catch(s){this.error(e,s)}}handleRelayCredentialDisable(e,n){this.ensureRelayCredential(e)&&this.relayCredentialHandler.disable(n.trim()).then(t=>this.json(e,200,t??{ok:!0})).catch(t=>this.error(e,t))}ensureProxy(e){return this.proxyHandler?!0:(this.json(e,501,{error:"proxy not configured"}),!1)}handleProxyStatus(e){if(this.ensureProxy(e))try{this.json(e,200,this.proxyHandler.status())}catch(n){this.error(e,n)}}async handleProxySetRoute(e,n,t){if(!this.ensureProxy(e))return;const r=t;if(!r||typeof r.targetBaseUrl!="string"||!r.targetBaseUrl.trim()){this.json(e,400,{error:"targetBaseUrl is required"});return}try{new URL(r.targetBaseUrl)}catch{this.json(e,400,{error:"targetBaseUrl must be a valid URL"});return}if(r.headers!==void 0&&!this.isStringRecord(r.headers)){this.json(e,400,{error:"headers must be a string map"});return}if(r.model!==void 0&&typeof r.model!="string"){this.json(e,400,{error:"model must be a string"});return}if(r.modelMap!==void 0&&!this.isStringRecord(r.modelMap)){this.json(e,400,{error:"modelMap must be a string map"});return}if(r.passthrough!==void 0&&typeof r.passthrough!="boolean"){this.json(e,400,{error:"passthrough must be a boolean"});return}if(r.relayOnly!==void 0&&typeof r.relayOnly!="boolean"){this.json(e,400,{error:"relayOnly must be a boolean"});return}if(r.codexResponsesToChat!==void 0&&typeof r.codexResponsesToChat!="boolean"){this.json(e,400,{error:"codexResponsesToChat must be a boolean"});return}if(r.passthrough===!0&&r.codexResponsesToChat===!0){this.json(e,400,{error:"passthrough and codexResponsesToChat are mutually exclusive"});return}try{const o=await this.proxyHandler.setRoute(n,t);this.json(e,200,o??{ok:!0})}catch(o){this.error(e,o)}}handleProxyDeleteRoute(e,n){this.ensureProxy(e)&&this.proxyHandler.deleteRoute(n).then(()=>{e.writeHead(204),e.end()}).catch(t=>this.error(e,t))}async handleProxySetAgentRelay(e,n,t){if(!this.ensureProxy(e))return;const r=n.trim();if(!r){this.json(e,400,{error:"agent name is required"});return}const o=t;if(typeof o?.enabled!="boolean"){this.json(e,400,{error:"enabled must be a boolean"});return}if(o.model!==void 0&&typeof o.model!="string"){this.json(e,400,{error:"model must be a string",code:"INVALID_ARGUMENT"});return}try{const s=await this.proxyHandler.setAgentRelay(r,o.enabled,typeof o.model=="string"&&o.model.trim()?o.model.trim():void 0);this.json(e,200,s??{ok:!0})}catch(s){this.error(e,s)}}async handleProxySetAgentRelayCredential(e,n,t){if(!this.ensureProxy(e))return;const r=n.trim();if(!r){this.json(e,400,{error:"agent name is required",code:"INVALID_ARGUMENT"});return}const o=t;if(typeof o?.agent_id!="string"||!o.agent_id.trim()){this.json(e,400,{error:"agent_id is required",code:"MISSING_AGENT_ID"});return}if(typeof o?.virtual_key!="string"||!o.virtual_key.trim()){this.json(e,400,{error:"virtual_key is required",code:"MISSING_VIRTUAL_KEY"});return}if(o.anthropic_base_url!==void 0&&typeof o.anthropic_base_url!="string"){this.json(e,400,{error:"anthropic_base_url must be a string",code:"INVALID_ARGUMENT"});return}if(o.openai_base_url!==void 0&&typeof o.openai_base_url!="string"){this.json(e,400,{error:"openai_base_url must be a string",code:"INVALID_ARGUMENT"});return}if(o.model!==void 0&&typeof o.model!="string"){this.json(e,400,{error:"model must be a string",code:"INVALID_ARGUMENT"});return}try{const s=await this.proxyHandler.setAgentRelayCredential(r,{agentId:o.agent_id,virtualKey:o.virtual_key,anthropicBaseUrl:typeof o.anthropic_base_url=="string"?o.anthropic_base_url:void 0,openaiBaseUrl:typeof o.openai_base_url=="string"?o.openai_base_url:void 0,model:typeof o.model=="string"?o.model:void 0,directRelay:o.direct_relay});this.json(e,200,s??{ok:!0})}catch(s){this.error(e,s)}}async handleProxySetEnabled(e,n){if(!this.ensureProxy(e))return;const t=n;if(typeof t?.enabled!="boolean"){this.json(e,400,{error:"enabled must be a boolean"});return}if(t.enabled){this.json(e,400,{error:'relay is per-agent; use PUT /api/proxy/agents/<name>/enabled {"enabled":true}'});return}try{const r=await this.proxyHandler.disableAll();this.json(e,200,r??{ok:!0})}catch(r){this.error(e,r)}}async handleProxySetDefaultRoute(e,n){if(!this.ensureProxy(e))return;const r=n?.routeKey??null;if(r!==null&&typeof r!="string"){this.json(e,400,{error:"routeKey must be a string or null"});return}try{const o=await this.proxyHandler.setDefaultRoute(r);this.json(e,200,o??{ok:!0})}catch(o){this.error(e,o)}}async handleProxySetInterceptHosts(e,n){if(!this.ensureProxy(e))return;const t=n;if(!Array.isArray(t?.hosts)||!t.hosts.every(r=>typeof r=="string")){this.json(e,400,{error:"hosts must be a string array"});return}try{const r=await this.proxyHandler.setInterceptHosts(t.hosts);this.json(e,200,r??{ok:!0})}catch(r){this.error(e,r)}}isStringRecord(e){return typeof e!="object"||e===null||Array.isArray(e)?!1:Object.values(e).every(n=>typeof n=="string")}handleList(e){try{const n=this.handler?.list()??[];this.json(e,200,n)}catch(n){this.error(e,n)}}async handleAdd(e,n){try{const t=await this.handler.add(n);this.json(e,201,t??{ok:!0})}catch(t){this.error(e,t)}}handleRemove(e,n){this.handler.remove(n).then(()=>{e.writeHead(204),e.end()}).catch(t=>this.error(e,t))}handleRestart(e,n){this.handler.restart(n).then(()=>{this.json(e,200,{ok:!0})}).catch(t=>this.error(e,t))}handleReload(e){this.handler.reload().then(n=>{this.json(e,200,{ok:!0,result:n})}).catch(n=>this.error(e,n))}handleCheckUpgrade(e){if(!this.upgradeHandler){this.json(e,501,{error:"upgrade not configured"});return}this.upgradeHandler.check().then(n=>{this.json(e,200,n)}).catch(n=>this.error(e,n))}handleTriggerUpgrade(e){if(!this.upgradeHandler){this.json(e,501,{error:"upgrade not configured"});return}this.upgradeHandler.trigger(),this.json(e,200,{ok:!0,message:"upgrade check triggered"})}error(e,n){const t=n;if(t.code==="NOT_FOUND")this.json(e,404,{error:t.message??"not found"});else if(t.code==="PROFILE_NOT_FOUND")this.json(e,404,{error:t.message,code:t.code});else if(t.code==="PROXY_UNAVAILABLE")this.json(e,503,{error:t.message,code:t.code});else if(t.code==="AMBIGUOUS_AGENT_ID")this.json(e,409,{error:t.message,code:t.code});else if(t.code==="HERMES_NOT_FOUND"||t.code==="HERMES_IMPORT_FAILED"||t.code==="SCRIPT_MISSING"||t.code==="SCRIPT_FAILED"||t.code==="CONFIG_WRITE_FAILED"||t.code==="CONFIG_WRITE_VERIFY_FAILED"||t.code==="CONFIG_UNREADABLE")this.json(e,500,{error:t.message,code:t.code});else if(t.code==="RELOAD_UNSAFE"||t.code==="NO_RELAY_ROUTE"||t.code==="AGENT_ID_MISMATCH")this.json(e,409,{error:t.message,code:t.code});else if(t.code==="UNKNOWN_AGENT"||t.code==="UNSUPPORTED_OS"||t.code==="UNSUPPORTED_CLIENT_TYPE"||t.code==="MISSING_AGENT_ID"||t.code==="MISSING_VIRTUAL_KEY"||t.code==="MISSING_TARGET_BASE_URL"||t.code==="MISSING_MODEL"||t.code==="MISSING_BASE_URL"||t.code==="MISSING_API_KEY"||t.code==="INVALID_BASE_URL"||t.code==="BAD_REQUEST"||t.code==="INVALID_TARGET_BASE_URL"||t.code==="INVALID_DIRECT_CAPABILITY"||t.code==="INVALID_ARGUMENT")this.json(e,400,{error:t.message,code:t.code});else if(t.code==="ALREADY_INSTALLED"||t.code==="INSTALL_IN_PROGRESS")this.json(e,409,{error:t.message,code:t.code});else if(n instanceof p){const r=n.bizCode;n.code==="OFFLINE"?this.json(e,503,{error:t.message,code:"RELAY_WS_OFFLINE"}):n.code==="TIMEOUT"?this.json(e,504,{error:t.message,code:"RELAY_CREDENTIAL_TIMEOUT"}):n.code==="CANCELLED"?this.json(e,409,{error:t.message,code:"RELAY_TOGGLE_CANCELLED"}):n.code==="UNSUPPORTED"?this.json(e,400,{error:t.message,code:"RELAY_UNSUPPORTED"}):this.json(e,502,{error:t.message,code:"RELAY_CREDENTIAL_FAILED",...r?{biz_code:r}:{}})}else t.code==="INSTALL_FAILED"||t.code==="INSTALL_TIMEOUT"||t.code==="PREFLIGHT_FAILED"||t.code==="VERIFICATION_FAILED"||t.code==="PREREQ_MISSING"||t.code==="PREREQ_INSTALL_FAILED"||t.code==="FALLBACK_EXHAUSTED"||t.code==="ENVIRONMENT_UNSUPPORTED"?this.json(e,500,{error:t.message,code:t.code}):(c.error("admin",`Handler error: ${t.message??n}`),this.json(e,500,{error:t.message??"internal error"}))}json(e,n,t){const r=JSON.stringify(t);e.writeHead(n,{"Content-Type":"application/json"}),e.end(r)}readBody(e){return new Promise((n,t)=>{let r="";e.setEncoding("utf8"),e.on("data",o=>{r+=o}),e.on("end",()=>{try{n(JSON.parse(r))}catch{t(new Error("invalid JSON body"))}}),e.on("error",t)})}handleProbe(e,n,t){if(!this.probeHandler){this.json(n,501,{error:"probe not configured"});return}const r=t.indexOf("?"),o=r>=0?t.slice(0,r):t,s=r>=0?new URLSearchParams(t.slice(r+1)):new URLSearchParams,i={};s.get("conversation")==="true"&&(i.conversation=!0),s.get("fresh")==="true"&&(i.fresh=!0);const l=Number(s.get("timeoutMs"));Number.isFinite(l)&&l>0&&(i.timeoutMs=l);const h=o.match(/^\/api\/probe\/(.+)$/);if(h){const a=decodeURIComponent(h[1]);this.probeHandler.probeOne(a,i).then(d=>{this.json(n,200,d)}).catch(d=>this.error(n,d));return}this.probeHandler.probeAll(i).then(a=>{this.json(n,200,a)}).catch(a=>this.error(n,a))}handleInstallList(e){if(!this.installHandler){this.json(e,501,{error:"install not configured"});return}try{const n=this.installHandler.listInstallable();this.json(e,200,n)}catch(n){this.error(e,n)}}handleInstallProgress(e,n){if(!this.installHandler){this.json(e,501,{error:"install not configured"});return}const t=this.installHandler.getInstallProgress(n);if(!t){this.json(e,200,{agentType:n,status:"unknown",inProgress:!1,progress:null});return}let r,o,s;switch(t.phase){case"completed":r="done",o="\u5B89\u88C5\u5B8C\u6210";break;case"failed":r="error",s=t.outputTail||"\u5B89\u88C5\u5931\u8D25";break;case"preflight":r="pending",o="\u68C0\u67E5\u524D\u7F6E\u4F9D\u8D56...";break;case"installing_prereq":r="downloading",o=t.currentPrereq?`\u6B63\u5728\u5B89\u88C5\u524D\u7F6E\u4F9D\u8D56: ${t.currentPrereq}`:"\u6B63\u5728\u5B89\u88C5\u524D\u7F6E\u4F9D\u8D56...";break;case"installing":r="installing",o=`\u6B63\u5728\u5B89\u88C5 ${n}...`;break;case"verifying":r="installing",o="\u9A8C\u8BC1\u5B89\u88C5...";break;default:r="unknown"}this.json(e,200,{agentType:n,status:r,inProgress:!0,progress:t.elapsedMs?Math.min(.9,t.elapsedMs/3e4):.1,message:o,error:s})}async handleInstall(e,n){if(!this.installHandler){this.json(e,501,{error:"install not configured"});return}try{const t=n;if(!t||typeof t.agentType!="string"||!t.agentType){this.json(e,400,{error:"agentType is required"});return}const r=await this.installHandler.installAgent(t);if(r.ok)this.json(e,200,r);else{const o=r.error?.code;o==="UNKNOWN_AGENT"||o==="UNSUPPORTED_OS"?this.json(e,400,r):o==="INSTALL_IN_PROGRESS"?this.json(e,409,r):this.json(e,500,r)}}catch(t){this.error(e,t)}}}export{_ as AdminServer};
|
|
1
|
+
import{createServer as u}from"node:http";import{log as c}from"../log/logger.js";import{RelayFetchError as p}from"../proxy/relay-credential-fetch.js";class _{server=null;token;handler=null;upgradeHandler=null;probeHandler=null;installHandler=null;proxyHandler=null;relayCredentialHandler=null;constructor(e){this.token=e}setAgentHandler(e){this.handler=e}setUpgradeHandler(e){this.upgradeHandler=e}setProbeHandler(e){this.probeHandler=e}setInstallHandler(e){this.installHandler=e}setProxyHandler(e){this.proxyHandler=e}setRelayCredentialHandler(e){this.relayCredentialHandler=e}async start(e){return new Promise((n,t)=>{this.server=u((r,o)=>this.handleRequest(r,o)),this.server.listen(e,"127.0.0.1",()=>{c.info("admin",`Listening on 127.0.0.1:${e}`),n()}),this.server.on("error",t)})}async stop(){if(this.server)return new Promise(e=>{this.server.close(()=>e())})}handleRequest(e,n){const t=e.method??"",r=e.url??"";if(r==="/api/agents"&&t==="GET")this.handleList(n);else if(r==="/api/agents"&&t==="POST")this.readBody(e).then(o=>this.handleAdd(n,o)).catch(o=>this.error(n,o));else if(t==="DELETE"&&r.startsWith("/api/agents/")){const o=decodeURIComponent(r.slice(12));this.handleRemove(n,o)}else if(t==="POST"&&r.match(/^\/api\/agents\/[^/]+\/restart$/)){const o=decodeURIComponent(r.slice(12,r.lastIndexOf("/restart")));this.handleRestart(n,o)}else if(r==="/api/reload"&&t==="POST")this.handleReload(n);else if(r==="/api/upgrade"&&t==="GET")this.handleCheckUpgrade(n);else if(r==="/api/upgrade"&&t==="POST")this.handleTriggerUpgrade(n);else if(t==="GET"&&r.startsWith("/api/probe"))this.handleProbe(e,n,r);else if(r==="/api/install"&&t==="GET")this.handleInstallList(n);else if(r==="/api/install"&&t==="POST")this.readBody(e).then(o=>this.handleInstall(n,o)).catch(o=>this.error(n,o));else if(t==="GET"&&r.startsWith("/api/install/")){const o=decodeURIComponent(r.slice(13));this.handleInstallProgress(n,o)}else if(r==="/api/proxy"&&t==="GET")this.handleProxyStatus(n);else if(t==="PUT"&&r.match(/^\/api\/proxy\/agents\/[^/]+\/enabled$/)){const o=decodeURIComponent(r.slice(18,-8));this.readBody(e).then(s=>this.handleProxySetAgentRelay(n,o,s)).catch(s=>this.error(n,s))}else if(t==="PUT"&&r.match(/^\/api\/proxy\/agents\/[^/]+\/relay-credential$/)){const o=decodeURIComponent(r.slice(18,-17));this.readBody(e).then(s=>this.handleProxySetAgentRelayCredential(n,o,s)).catch(s=>this.error(n,s))}else if(r==="/api/proxy/hermes-profiles"&&t==="GET")this.handleHermesProfileList(n);else if(t==="PUT"&&r.match(/^\/api\/proxy\/relay-credential\/[^/]+$/)){const o=decodeURIComponent(r.slice(28));this.readBody(e).then(s=>this.handleRelayCredentialEnable(n,o,s)).catch(s=>this.error(n,s))}else if(t==="DELETE"&&r.match(/^\/api\/proxy\/relay-credential\/[^/]+$/)){const o=decodeURIComponent(r.slice(28));this.handleRelayCredentialDisable(n,o)}else if(t==="PUT"&&r==="/api/proxy/enabled")this.readBody(e).then(o=>this.handleProxySetEnabled(n,o)).catch(o=>this.error(n,o));else if(t==="PUT"&&r==="/api/proxy/default-route")this.readBody(e).then(o=>this.handleProxySetDefaultRoute(n,o)).catch(o=>this.error(n,o));else if(t==="PUT"&&r==="/api/proxy/intercept-hosts")this.readBody(e).then(o=>this.handleProxySetInterceptHosts(n,o)).catch(o=>this.error(n,o));else if(t==="PUT"&&r.match(/^\/api\/proxy\/routes\/[^/]+$/)){const o=decodeURIComponent(r.slice(18));this.readBody(e).then(s=>this.handleProxySetRoute(n,o,s)).catch(s=>this.error(n,s))}else if(t==="DELETE"&&r.startsWith("/api/proxy/routes/")){const o=decodeURIComponent(r.slice(18));this.handleProxyDeleteRoute(n,o)}else this.json(n,404,{error:"not_found"})}ensureRelayCredential(e){return this.relayCredentialHandler?!0:(this.json(e,501,{error:"relay credential handler not configured"}),!1)}handleHermesProfileList(e){this.ensureRelayCredential(e)&&this.relayCredentialHandler.listHermesProfiles().then(n=>this.json(e,200,{profiles:n})).catch(n=>this.error(e,n))}async handleRelayCredentialEnable(e,n,t){if(!this.ensureRelayCredential(e))return;const r=t,o=s=>typeof s=="string"&&s.trim()?s.trim():void 0;try{const s=await this.relayCredentialHandler.enable(n.trim(),{virtualKey:o(r?.virtual_key)??"",anthropicBaseUrl:o(r?.anthropic_base_url),openaiBaseUrl:o(r?.openai_base_url),model:o(r?.model),directRelay:r?.direct_relay});this.json(e,200,s??{ok:!0})}catch(s){this.error(e,s)}}handleRelayCredentialDisable(e,n){this.ensureRelayCredential(e)&&this.relayCredentialHandler.disable(n.trim()).then(t=>this.json(e,200,t??{ok:!0})).catch(t=>this.error(e,t))}ensureProxy(e){return this.proxyHandler?!0:(this.json(e,501,{error:"proxy not configured"}),!1)}handleProxyStatus(e){if(this.ensureProxy(e))try{this.json(e,200,this.proxyHandler.status())}catch(n){this.error(e,n)}}async handleProxySetRoute(e,n,t){if(!this.ensureProxy(e))return;const r=t;if(!r||typeof r.targetBaseUrl!="string"||!r.targetBaseUrl.trim()){this.json(e,400,{error:"targetBaseUrl is required"});return}try{new URL(r.targetBaseUrl)}catch{this.json(e,400,{error:"targetBaseUrl must be a valid URL"});return}if(r.headers!==void 0&&!this.isStringRecord(r.headers)){this.json(e,400,{error:"headers must be a string map"});return}if(r.model!==void 0&&typeof r.model!="string"){this.json(e,400,{error:"model must be a string"});return}if(r.modelMap!==void 0&&!this.isStringRecord(r.modelMap)){this.json(e,400,{error:"modelMap must be a string map"});return}if(r.passthrough!==void 0&&typeof r.passthrough!="boolean"){this.json(e,400,{error:"passthrough must be a boolean"});return}if(r.relayOnly!==void 0&&typeof r.relayOnly!="boolean"){this.json(e,400,{error:"relayOnly must be a boolean"});return}if(r.codexResponsesToChat!==void 0&&typeof r.codexResponsesToChat!="boolean"){this.json(e,400,{error:"codexResponsesToChat must be a boolean"});return}if(r.passthrough===!0&&r.codexResponsesToChat===!0){this.json(e,400,{error:"passthrough and codexResponsesToChat are mutually exclusive"});return}try{const o=await this.proxyHandler.setRoute(n,t);this.json(e,200,o??{ok:!0})}catch(o){this.error(e,o)}}handleProxyDeleteRoute(e,n){this.ensureProxy(e)&&this.proxyHandler.deleteRoute(n).then(()=>{e.writeHead(204),e.end()}).catch(t=>this.error(e,t))}async handleProxySetAgentRelay(e,n,t){if(!this.ensureProxy(e))return;const r=n.trim();if(!r){this.json(e,400,{error:"agent name is required"});return}const o=t;if(typeof o?.enabled!="boolean"){this.json(e,400,{error:"enabled must be a boolean"});return}if(o.model!==void 0&&typeof o.model!="string"){this.json(e,400,{error:"model must be a string",code:"INVALID_ARGUMENT"});return}try{const s=await this.proxyHandler.setAgentRelay(r,o.enabled,typeof o.model=="string"&&o.model.trim()?o.model.trim():void 0);this.json(e,200,s??{ok:!0})}catch(s){this.error(e,s)}}async handleProxySetAgentRelayCredential(e,n,t){if(!this.ensureProxy(e))return;const r=n.trim();if(!r){this.json(e,400,{error:"agent name is required",code:"INVALID_ARGUMENT"});return}const o=t;if(typeof o?.agent_id!="string"||!o.agent_id.trim()){this.json(e,400,{error:"agent_id is required",code:"MISSING_AGENT_ID"});return}if(typeof o?.virtual_key!="string"||!o.virtual_key.trim()){this.json(e,400,{error:"virtual_key is required",code:"MISSING_VIRTUAL_KEY"});return}if(o.anthropic_base_url!==void 0&&typeof o.anthropic_base_url!="string"){this.json(e,400,{error:"anthropic_base_url must be a string",code:"INVALID_ARGUMENT"});return}if(o.openai_base_url!==void 0&&typeof o.openai_base_url!="string"){this.json(e,400,{error:"openai_base_url must be a string",code:"INVALID_ARGUMENT"});return}if(o.model!==void 0&&typeof o.model!="string"){this.json(e,400,{error:"model must be a string",code:"INVALID_ARGUMENT"});return}try{const s=await this.proxyHandler.setAgentRelayCredential(r,{agentId:o.agent_id,virtualKey:o.virtual_key,anthropicBaseUrl:typeof o.anthropic_base_url=="string"?o.anthropic_base_url:void 0,openaiBaseUrl:typeof o.openai_base_url=="string"?o.openai_base_url:void 0,model:typeof o.model=="string"?o.model:void 0,directRelay:o.direct_relay});this.json(e,200,s??{ok:!0})}catch(s){this.error(e,s)}}async handleProxySetEnabled(e,n){if(!this.ensureProxy(e))return;const t=n;if(typeof t?.enabled!="boolean"){this.json(e,400,{error:"enabled must be a boolean"});return}if(t.enabled){this.json(e,400,{error:'relay is per-agent; use PUT /api/proxy/agents/<name>/enabled {"enabled":true}'});return}try{const r=await this.proxyHandler.disableAll();this.json(e,200,r??{ok:!0})}catch(r){this.error(e,r)}}async handleProxySetDefaultRoute(e,n){if(!this.ensureProxy(e))return;const r=n?.routeKey??null;if(r!==null&&typeof r!="string"){this.json(e,400,{error:"routeKey must be a string or null"});return}try{const o=await this.proxyHandler.setDefaultRoute(r);this.json(e,200,o??{ok:!0})}catch(o){this.error(e,o)}}async handleProxySetInterceptHosts(e,n){if(!this.ensureProxy(e))return;const t=n;if(!Array.isArray(t?.hosts)||!t.hosts.every(r=>typeof r=="string")){this.json(e,400,{error:"hosts must be a string array"});return}try{const r=await this.proxyHandler.setInterceptHosts(t.hosts);this.json(e,200,r??{ok:!0})}catch(r){this.error(e,r)}}isStringRecord(e){return typeof e!="object"||e===null||Array.isArray(e)?!1:Object.values(e).every(n=>typeof n=="string")}handleList(e){try{const n=this.handler?.list()??[];this.json(e,200,n)}catch(n){this.error(e,n)}}async handleAdd(e,n){try{const t=await this.handler.add(n);this.json(e,201,t??{ok:!0})}catch(t){this.error(e,t)}}handleRemove(e,n){this.handler.remove(n).then(()=>{e.writeHead(204),e.end()}).catch(t=>this.error(e,t))}handleRestart(e,n){this.handler.restart(n).then(()=>{this.json(e,200,{ok:!0})}).catch(t=>this.error(e,t))}handleReload(e){this.handler.reload().then(n=>{this.json(e,200,{ok:!0,result:n})}).catch(n=>this.error(e,n))}handleCheckUpgrade(e){if(!this.upgradeHandler){this.json(e,501,{error:"upgrade not configured"});return}this.upgradeHandler.check().then(n=>{this.json(e,200,n)}).catch(n=>this.error(e,n))}handleTriggerUpgrade(e){if(!this.upgradeHandler){this.json(e,501,{error:"upgrade not configured"});return}this.upgradeHandler.trigger(),this.json(e,200,{ok:!0,message:"upgrade check triggered"})}error(e,n){const t=n;if(t.code==="NOT_FOUND")this.json(e,404,{error:t.message??"not found"});else if(t.code==="PROFILE_NOT_FOUND")this.json(e,404,{error:t.message,code:t.code});else if(t.code==="PROXY_UNAVAILABLE")this.json(e,503,{error:t.message,code:t.code});else if(t.code==="AMBIGUOUS_AGENT_ID")this.json(e,409,{error:t.message,code:t.code});else if(t.code==="HERMES_NOT_FOUND"||t.code==="HERMES_IMPORT_FAILED"||t.code==="SCRIPT_MISSING"||t.code==="SCRIPT_FAILED"||t.code==="CONFIG_WRITE_FAILED"||t.code==="CONFIG_WRITE_VERIFY_FAILED"||t.code==="CONFIG_UNREADABLE")this.json(e,500,{error:t.message,code:t.code});else if(t.code==="RELOAD_UNSAFE"||t.code==="NO_RELAY_ROUTE"||t.code==="AGENT_ID_MISMATCH")this.json(e,409,{error:t.message,code:t.code});else if(t.code==="UNKNOWN_AGENT"||t.code==="UNSUPPORTED_OS"||t.code==="UNSUPPORTED_CLIENT_TYPE"||t.code==="MISSING_AGENT_ID"||t.code==="MISSING_VIRTUAL_KEY"||t.code==="MISSING_TARGET_BASE_URL"||t.code==="MISSING_MODEL"||t.code==="MISSING_BASE_URL"||t.code==="MISSING_API_KEY"||t.code==="INVALID_BASE_URL"||t.code==="BAD_REQUEST"||t.code==="INVALID_TARGET_BASE_URL"||t.code==="INVALID_DIRECT_CAPABILITY"||t.code==="INVALID_ARGUMENT")this.json(e,400,{error:t.message,code:t.code});else if(t.code==="ALREADY_INSTALLED"||t.code==="INSTALL_IN_PROGRESS")this.json(e,409,{error:t.message,code:t.code});else if(n instanceof p){const r=n.bizCode;n.code==="OFFLINE"?this.json(e,503,{error:t.message,code:"RELAY_WS_OFFLINE"}):n.code==="TIMEOUT"?this.json(e,504,{error:t.message,code:"RELAY_CREDENTIAL_TIMEOUT"}):n.code==="CANCELLED"?this.json(e,409,{error:t.message,code:"RELAY_TOGGLE_CANCELLED"}):n.code==="UNSUPPORTED"?this.json(e,400,{error:t.message,code:"RELAY_UNSUPPORTED"}):this.json(e,502,{error:t.message,code:"RELAY_CREDENTIAL_FAILED",...r?{biz_code:r}:{}})}else t.code==="INSTALL_FAILED"||t.code==="INSTALL_TIMEOUT"||t.code==="PREFLIGHT_FAILED"||t.code==="VERIFICATION_FAILED"||t.code==="PREREQ_MISSING"||t.code==="PREREQ_INSTALL_FAILED"||t.code==="FALLBACK_EXHAUSTED"||t.code==="ENVIRONMENT_UNSUPPORTED"?this.json(e,500,{error:t.message,code:t.code}):(c.error("admin",`Handler error: ${t.message??n}`),this.json(e,500,{error:t.message??"internal error"}))}json(e,n,t){const r=JSON.stringify(t);e.writeHead(n,{"Content-Type":"application/json"}),e.end(r)}readBody(e){return new Promise((n,t)=>{let r="";e.setEncoding("utf8"),e.on("data",o=>{r+=o}),e.on("end",()=>{try{n(JSON.parse(r))}catch{t(new Error("invalid JSON body"))}}),e.on("error",t)})}handleProbe(e,n,t){if(!this.probeHandler){this.json(n,501,{error:"probe not configured"});return}const r=t.indexOf("?"),o=r>=0?t.slice(0,r):t,s=r>=0?new URLSearchParams(t.slice(r+1)):new URLSearchParams,i={};s.get("conversation")==="true"&&(i.conversation=!0),s.get("fresh")==="true"&&(i.fresh=!0);const l=Number(s.get("timeoutMs"));Number.isFinite(l)&&l>0&&(i.timeoutMs=l);const h=o.match(/^\/api\/probe\/(.+)$/);if(h){const a=decodeURIComponent(h[1]);this.probeHandler.probeOne(a,i).then(d=>{this.json(n,200,d)}).catch(d=>this.error(n,d));return}this.probeHandler.probeAll(i).then(a=>{this.json(n,200,a)}).catch(a=>this.error(n,a))}handleInstallList(e){if(!this.installHandler){this.json(e,501,{error:"install not configured"});return}try{const n=this.installHandler.listInstallable();this.json(e,200,n)}catch(n){this.error(e,n)}}handleInstallProgress(e,n){if(!this.installHandler){this.json(e,501,{error:"install not configured"});return}const t=this.installHandler.getInstallProgress(n);if(!t){this.json(e,200,{agentType:n,status:"unknown",inProgress:!1,progress:null});return}let r,o,s;switch(t.phase){case"completed":r="done",o="\u5B89\u88C5\u5B8C\u6210";break;case"failed":r="error",s=t.outputTail||"\u5B89\u88C5\u5931\u8D25";break;case"preflight":r="pending",o="\u68C0\u67E5\u524D\u7F6E\u4F9D\u8D56...";break;case"installing_prereq":r="downloading",o=t.currentPrereq?`\u6B63\u5728\u5B89\u88C5\u524D\u7F6E\u4F9D\u8D56: ${t.currentPrereq}`:"\u6B63\u5728\u5B89\u88C5\u524D\u7F6E\u4F9D\u8D56...";break;case"installing":r="installing",o=`\u6B63\u5728\u5B89\u88C5 ${n}...`;break;case"verifying":r="installing",o="\u9A8C\u8BC1\u5B89\u88C5...";break;default:r="unknown"}this.json(e,200,{agentType:n,status:r,inProgress:!0,progress:t.elapsedMs?Math.min(.9,t.elapsedMs/3e4):.1,message:o,error:s,outputTail:t.outputTail,currentPrereq:t.currentPrereq,pendingPrereqs:t.pendingPrereqs})}async handleInstall(e,n){if(!this.installHandler){this.json(e,501,{error:"install not configured"});return}try{const t=n;if(!t||typeof t.agentType!="string"||!t.agentType){this.json(e,400,{error:"agentType is required"});return}const r=await this.installHandler.installAgent(t);if(r.ok)this.json(e,200,r);else{const o=r.error?.code;o==="UNKNOWN_AGENT"||o==="UNSUPPORTED_OS"?this.json(e,400,r):o==="INSTALL_IN_PROGRESS"?this.json(e,409,r):this.json(e,500,r)}}catch(t){this.error(e,t)}}}export{_ as AdminServer};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{EventEmitter as A}from"node:events";import{randomUUID as T}from"node:crypto";import $ from"node:os";import O from"ws";import{log as m}from"../log/index.js";import{getMachineName as S}from"../util/index.js";import{detectTailnetIPv4 as I,ensureServerAndGetPort as P,getFileServerHttpsPort as F}from"../files/file-serve.js";import{TerminalOutbox as M}from"../persistence/terminal-outbox.js";import{TerminalCommitTokenStore as q}from"../persistence/terminal-commit-token-store.js";import{StopResultOutbox as B}from"../persistence/stop-result-outbox.js";import{AUTH_CODE_AGENT_DELETED as w,KICKED_REASON_AGENT_DELETED as k,AgentDeletedError as C,RequestTimeoutError as N}from"./errors.js";function D(R){return R.replace(/(?<=[\[:,\[]\s*)(\d{16,})(?=\s*[,}\]\n])/g,'"$1"')}function x(R){const e=[...R??["stream_chunk","local_action_v1","agent_invoke"]];return e.includes("agent_invoke")||e.push("agent_invoke"),e.includes("event_result_ack")||e.push("event_result_ack"),e.includes("terminal_commit_v1")||e.push("terminal_commit_v1"),e.includes("audit_replay_v2")||e.push("audit_replay_v2"),e.includes("session_send_quote_v1")||e.push("session_send_quote_v1"),e}function W(R){const e=[...R??["exec_approve","exec_reject"]];return e.includes("apply_relay_state")||e.push("apply_relay_state"),e}const U="aibot-agent-api-v1",L=1;class p extends A{static DROPPABLE_COMMANDS=new Set(["update_binding_card"]);static BUFFER_OVERFLOW_RETAIN_COMMANDS=new Set(["event_result","codex_event","client_stream_chunk","send_msg","audit_state"]);static MAX_OUTBOUND_BUFFER_SIZE=1e3;static MAX_OUTBOUND_BUFFER_HARD_SIZE=1200;static BACKPRESSURE_THRESHOLD=64*1024;static TERMINAL_RETRY_DELAY_MS=15e3;static MAX_TOKENIZED_TERMINAL_REJECTIONS=5;static EVENT_CORRELATION_TTL_MS=5*6e4;static MAX_EVENT_CORRELATIONS=1024;static MAX_BLOCKED_OUTPUT_EVENTS=4096;static MAX_COMMITTED_TERMINAL_EVENTS=4096;static OUTBOUND_WRITE_CALLBACK_TIMEOUT_MS=1e4;static MIN_REQUIRED_OUTPUT_ACK_TIMEOUT_MS=2e4;static ORDERED_OUTPUT_COMMANDS=new Set(["client_stream_chunk","send_msg","codex_event"]);ws=null;seq=0;heartbeatTimer=null;heartbeatSec=30;heartbeatFailures=0;static HEARTBEAT_MAX_FAILURES=2;connected=!1;reconnecting=!1;reconnectAttempts=0;reconnectRunGeneration=0;everConnected=!1;agentDeleted=!1;config;packetLog;pendingInvokes=new Map;eventCorrelations=new Map;clientMsgEventMap=new Map;eventCorrelationCleanupTimer=null;lastCorrelationPruneAt=0;pendingRequests=new Map;outboundBuffer=[];outboundEntryOrders=new WeakMap;nextOutboundEntryOrder=0;connectionGeneration=0;nextOutboundFlushGeneration=0;outboundFlushInFlight=null;pendingOutputWrites=new Map;pendingOutputSeqsByEvent=new Map;pendingAcceptedOutputs=new Map;pendingAcceptedOutputSeqsByEvent=new Map;pendingAcceptedSeqsByClientMsgId=new Map;nonReplayableOutputEventsBySocket=new Map;provisionalRespondedTerminals=new Map;outputIntegrityFailed=new Map;blockedOutputEvents=new Map;committedTerminalEvents=new Set;ackPolicy=null;negotiatedCapabilities=new Set;terminalOutbox;terminalCommitTokens;stopResultOutbox;terminalInFlight=new Set;terminalRetryTimers=new Map;terminalReplayAfterFlight=new Set;tokenizedTerminalRejections=new Map;stopResultInFlight=new Set;stopResultRetryTimers=new Map;constructor(e,t){super(),this.packetLog=t?.packetLog??null,this.config={url:e.url,agentId:e.agentId,apiKey:e.apiKey,clientType:e.clientType,clientVersion:e.clientVersion??"",adapterHint:e.adapterHint??"",capabilities:x(e.capabilities),localActions:W(e.localActions),skills:e.skills,librarySkills:e.librarySkills,sharedOwnerId:e.sharedOwnerId,concurrency:e.concurrency,terminalOutboxPath:e.terminalOutboxPath,terminalCommitTokenStorePath:e.terminalCommitTokenStorePath,stopResultOutboxPath:e.stopResultOutboxPath},this.terminalOutbox=new M(this.config.terminalOutboxPath),this.terminalCommitTokens=new q(this.config.terminalCommitTokenStorePath??(this.config.terminalOutboxPath?`${this.config.terminalOutboxPath}.tokens`:void 0)),this.stopResultOutbox=new B(this.config.stopResultOutboxPath??(this.config.terminalOutboxPath?`${this.config.terminalOutboxPath}.stops`:void 0));for(const n of this.terminalOutbox.listPending())n.payload.code==="agent_output_integrity_failed"&&this.blockEventOutput(n.payload.event_id)}get isConnected(){return this.connected}async connect(){this.negotiatedCapabilities.clear();const e=this.ws,t=this.connectionGeneration;if(this.pendingRequests.size>0&&this.rejectAllPendingRequests("connection generation replaced"),e){this.connected=!1,this.stopHeartbeat(),this.clearTerminalRetryTimers(),this.clearStopResultRetryTimers();const o=this.outboundFlushInFlight;o&&o.socket===e&&o.connectionGeneration===t&&this.cancelOutboundFlush(o,"connection_lost","connection generation replaced"),this.failNonReplayableOutputsForSocket(e,"connection generation replaced"),this.abortPendingOutputWrites(!0,e,t,"connection generation replaced"),this.abortPendingAcceptedOutputs(!0,e,t),this.clearAllEventCorrelations(),this.connectionGeneration++;try{e.close(1012,"connection generation replaced")}catch{}this.ws===e&&(this.ws=null)}else this.outboundFlushInFlight&&this.cancelOutboundFlush(this.outboundFlushInFlight,"discard","detached connection generation replaced");const n=++this.connectionGeneration;let s,i,r;const u=(async()=>{try{if(s=await I(),s!==void 0)try{i=await P(s);const o=F();o>0&&(r=o)}catch(o){m.warn("aibot",`file server pre-start failed: ${o}`)}}catch(o){m.warn("aibot",`tailnet detect failed: ${o}`)}})();return new Promise((o,f)=>{const l=new O(this.config.url);this.ws=l;const d=setTimeout(()=>{if(!this.isConnectionCurrent(l,n))return;const c=this.pendingRequests.get(_);c&&(this.pendingRequests.delete(_),clearTimeout(c.timer),c.reject(new Error("Auth timeout: no auth_ack received within 15s"))),this.cleanupSocket(l,n)},15e3),_=++this.seq,E=setTimeout(()=>{if(!this.isConnectionCurrent(l,n))return;const c=this.pendingRequests.get(_);c&&(this.pendingRequests.delete(_),c.reject(new Error("Auth request timeout"))),this.cleanupSocket(l,n)},15e3);this.pendingRequests.set(_,{expected:["auth_ack"],resolve:c=>{clearTimeout(d);const a=c.payload;if(a.code===0){this.negotiatedCapabilities=new Set(Array.isArray(a.supported_capabilities)?a.supported_capabilities:[]),this.connected=!0,this.everConnected=!0,this.reconnectAttempts=0,a.heartbeat_sec&&(this.heartbeatSec=a.heartbeat_sec),a.ack_policy&&(this.ackPolicy=a.ack_policy,m.info("aibot",`ack_policy received: push_ack_timeout_ms=${a.ack_policy.push_ack_timeout_ms??"default"} max_retries=${a.ack_policy.max_retries??"default"} timeout_action=${a.ack_policy.timeout_action??"default"}`)),this.startHeartbeat();const h=this.flushOutboundBuffer(l,n);this.emit("auth",a),o(a),h.then(g=>{this.isConnectionCurrent(l,n)&&(g?(this.replayTerminalOutbox(),this.replayStopResultOutbox()):this.reconnectAfterOutboundFlushFailure(l,n))})}else a.code===w?(this.agentDeleted=!0,f(new C(`Agent deleted: code=${a.code} msg=${a.msg}`))):f(new Error(`Auth failed: code=${a.code} msg=${a.msg}`))},reject:c=>{clearTimeout(d),f(c)},timer:E}),l.on("open",async()=>{if(await u,!this.isConnectionCurrent(l,n))return;const c={agent_id:this.config.agentId,api_key:this.config.apiKey,client_type:this.config.clientType,protocol_version:U,contract_version:L,capabilities:this.config.capabilities??[],local_actions:this.config.localActions,skills:this.config.skills,library_skills:this.config.librarySkills};this.config.sharedOwnerId&&(c.shared_owner_id=this.config.sharedOwnerId),this.config.clientVersion&&(c.client="grix-connector",c.client_version=this.config.clientVersion,c.host_type=this.config.clientType,c.host_version=this.config.clientVersion),this.config.adapterHint&&(c.adapter_hint=this.config.adapterHint),c.host_meta={hostname:S(),platform:$.platform(),arch:$.arch(),os_release:$.release(),...s!==void 0&&{tailnet_ip:s},...i!==void 0&&i>0&&{file_server_port:i},...r!==void 0&&r>0&&{file_server_https_port:r}},this.config.concurrency&&(c.concurrency=this.config.concurrency),this.sendPacket("auth",c,_)}),l.on("message",c=>{if(!this.isConnectionCurrent(l,n))return;let a;try{a=JSON.parse(D(c.toString()))}catch{return}try{this.handlePacket(a)}catch(h){this.emitClientError(new Error(`handlePacket error: ${h}`))}}),l.on("close",(c,a)=>{if(!this.isConnectionCurrent(l,n))return;const h=this.everConnected&&!this.agentDeleted;this.connected=!1,this.negotiatedCapabilities.clear(),this.stopHeartbeat(),this.clearTerminalRetryTimers(),this.clearStopResultRetryTimers(),this.rejectAllPendingRequests("websocket closed");const g=`websocket closed code=${c} reason=${a.toString()||"<none>"}`,b=this.outboundFlushInFlight;b&&b.socket===l&&b.connectionGeneration===n&&this.cancelOutboundFlush(b,h?"connection_lost":"discard",g),h?this.failNonReplayableOutputsForSocket(l,g):this.nonReplayableOutputEventsBySocket.delete(l),this.abortPendingOutputWrites(h,l,n,h?g:void 0),this.abortPendingAcceptedOutputs(h,l,n),this.clearAllEventCorrelations(),this.ws===l&&(this.ws=null),this.connectionGeneration++,this.emit("close",c,a.toString());const y=h;m.info("aibot",`ws closed agent=${this.config.clientType}:${this.config.agentId} code=${c} reason=${a.toString()||"<none>"} everConnected=${this.everConnected} reconnecting=${this.reconnecting} agentDeleted=${this.agentDeleted} willReconnect=${y}`),y&&this.attemptReconnect()}),l.on("error",c=>{if(this.isConnectionCurrent(l,n)&&(this.emitClientError(c instanceof Error?c:new Error(String(c))),!this.connected)){const a=this.pendingRequests.get(_);a&&(this.pendingRequests.delete(_),clearTimeout(a.timer),a.reject(c instanceof Error?c:new Error(String(c))))}})})}handlePacket(e){if(this.packetLog?.logInboundPacket(e.cmd,e.seq,e.payload),this.handleCorrelatedSendResponse(e),e.seq>0&&this.pendingRequests.has(e.seq)){const t=this.pendingRequests.get(e.seq);this.pendingRequests.delete(e.seq),clearTimeout(t.timer),t.expected.includes(e.cmd)?t.resolve(e):t.reject(new Error(`unexpected response: got ${e.cmd}, expected ${t.expected.join("/")}`));return}switch(e.cmd){case"auth_ack":break;case"ping":{this.sendPacket("pong",e.payload??{});break}case"event_msg":{const t=e.payload;if(!this.captureInboundTerminalCommitToken(t.event_id,t.terminal_commit_token))break;this.emit("event",t);break}case"local_action":{this.emit("localAction",e.payload);break}case"event_stop":{const t=e.payload;if(!this.captureInboundTerminalCommitToken(t.event_id,t.terminal_commit_token))break;this.emit("stop",t);break}case"event_revoke":{this.emit("revoke",e.payload);break}case"event_edit":{this.emit("edit",e.payload);break}case"event_cancel":{this.emit("eventCancel",e.payload);break}case"queue_clear":{this.emit("queueClear",e.payload);break}case"queue_reorder":{this.emit("queueReorder",e.payload);break}case"queue_snapshot_query":{this.emit("queueSnapshotQuery",e.payload);break}case"event_hold":{this.emit("eventHold",e.payload);break}case"queue_edit":{this.emit("queueEdit",e.payload);break}case"control_share_set":{this.emit("shareSet",e.payload);break}case"agent_profile_push":{this.emit("profilePush",e.payload);break}case"skill_sync":{this.emit("skillSync",e.payload);break}case"kicked":{const t=e.payload;this.emit("kicked",t),this.connected=!1,this.negotiatedCapabilities.clear(),this.stopHeartbeat(),this.clearTerminalRetryTimers(),this.clearStopResultRetryTimers(),this.rejectAllPendingRequests("kicked");const n=this.ws,s=this.connectionGeneration,i=this.outboundFlushInFlight;if(i&&n&&i.socket===n&&i.connectionGeneration===this.connectionGeneration&&this.cancelOutboundFlush(i,t?.reason===k?"discard":"connection_lost",`kicked reason=${t?.reason??"<none>"}`),t?.reason!==k&&n&&this.failNonReplayableOutputsForSocket(n,`kicked reason=${t?.reason??"<none>"}`),this.abortPendingOutputWrites(t?.reason!==k,n??void 0,s,t?.reason!==k?`kicked reason=${t?.reason??"<none>"}`:void 0),this.abortPendingAcceptedOutputs(t?.reason!==k,n??void 0,s),this.clearAllEventCorrelations(),this.connectionGeneration++,t?.reason===k){if(this.agentDeleted=!0,this.reconnecting=!1,this.outboundBuffer.length=0,this.outputIntegrityFailed.clear(),this.nonReplayableOutputEventsBySocket.clear(),this.ws){try{this.ws.close(4001,"kicked")}catch{}this.ws=null}m.error("aibot",`kicked: agent deleted on platform agent=${this.config.clientType}:${this.config.agentId}, reconnect disabled`),this.emit("agentDeleted",{source:"kicked",reason:t.reason});break}if(this.reconnectAttempts=Math.max(this.reconnectAttempts,3),this.ws){try{this.ws.close(4001,"kicked")}catch{}this.ws===n&&(this.ws=null)}this.attemptReconnect();break}case"error":{const t=e.payload,n=[t.ref_cmd?`ref_cmd=${t.ref_cmd}`:"",t.ref_id?`ref_id=${t.ref_id}`:""].filter(Boolean).join(" ");this.emitClientError(new Error(`Server error: code=${t.code} msg=${t.msg}${n?` ${n}`:""}`));break}case"agent_invoke_result":{this.handleInvokeResult(e.payload);break}case"mcp_frame":{const t=e.payload;this.emit("mcpFrame",t.session_id??"",t.frame??null);break}case"send_ack":break;case"send_nack":break;case"local_action_ack":break;default:break}}captureInboundTerminalCommitToken(e,t){const n=t?.trim();if(!n)return!0;if(!e?.trim())return this.rejectInboundTerminalCommitToken("tokenized event is missing event_id"),!1;if(!this.negotiatedCapabilities.has("terminal_commit_v1"))return this.rejectInboundTerminalCommitToken(`tokenized event received without terminal_commit_v1 negotiation event=${e}`),!1;try{return this.terminalCommitTokens.register(e,n),!0}catch(s){return this.rejectInboundTerminalCommitToken(`terminal commit token persist failed event=${e}: ${s instanceof Error?s.message:s}`),!1}}rejectInboundTerminalCommitToken(e){this.emitClientError(new Error(e));const t=this.ws;if(t)try{t.close(1011,"terminal commit token persistence failed")}catch{}}withTerminalCommitToken(e){const t=e.event_id.trim();if(!t)throw new Error("terminal event_result is missing event_id");const n=e.terminal_commit_token?.trim(),s=this.terminalCommitTokens.get(t);if(n&&s&&n!==s)throw new Error(`terminal commit token mismatch for event=${t}`);n&&!s&&this.terminalCommitTokens.register(t,n);const i=s??n;return i?{...e,event_id:t,terminal_commit_token:i}:{...e,event_id:t}}sendEventAck(e){this.sendPacket("event_ack",e)||m.warn("aibot",`event_ack NOT sent (ws not open) event=${e.event_id} ws=${this.ws?`state=${this.ws.readyState}`:"null"}`)}sendStreamChunk(e){!e.delta_content&&!e.is_finish&&(m.warn("aibot",`stream_chunk delta_content empty, patched to newline event=${e.event_id??""} session=${e.session_id} chunk_seq=${e.chunk_seq} is_finish=${e.is_finish}`),e={...e,delta_content:`
|
|
2
|
-
`}),this.sendPacket("client_stream_chunk",e)}sendMsg(e){const t={...e,client_msg_id:e.client_msg_id?.trim()||T()};this.sendPacket("send_msg",t)||m.warn("aibot",`send_msg NOT sent (ws not open) event=${t.event_id??""} session=${t.session_id??""} ws=${this.ws?`state=${this.ws.readyState}`:"null"}`)}editMsg(e){this.sendPacket("edit_msg",e)}sendEventResult(e){try{e=this.withTerminalCommitToken(e)}catch(o){this.emitClientError(new Error(`event_result token validation failed: event=${e.event_id}: ${o instanceof Error?o.message:o}`));return}const t=this.terminalOutbox.get(e.event_id);let n,s=this.outputIntegrityFailed.get(e.event_id);const i=t;if(!s&&i?.payload.code==="agent_output_integrity_failed"&&(s={reason:i.payload.msg??"required agent output was discarded before delivery",terminalObserved:!1,terminalSettled:!1},this.outputIntegrityFailed.set(e.event_id,s)),s){if(s.terminalObserved=!0,this.provisionalRespondedTerminals.delete(e.event_id),i&&i.payload.code!=="agent_output_integrity_failed"&&!this.canReplaceUnsentOutputGuard(i)){this.scheduleTerminalDelivery(e.event_id,{ignoreNotBefore:!0});return}if(i?.payload.code==="agent_output_integrity_failed"){this.scheduleTerminalDelivery(e.event_id,{ignoreNotBefore:!0});return}if(s.terminalSettled||this.committedTerminalEvents.has(e.event_id)&&!i){this.outputIntegrityFailed.delete(e.event_id);return}try{n=this.terminalOutbox.enqueue(this.makeOutputIntegrityFailurePayload(e.event_id,s.reason))}catch(o){this.emitClientError(new Error(`output-integrity terminal persist failed: event=${e.event_id}: ${o instanceof Error?o.message:o}`));return}this.scheduleTerminalDelivery(n.payload.event_id,{ignoreNotBefore:!0});return}if(this.committedTerminalEvents.has(e.event_id)&&!t){m.info("aibot",`ignored duplicate terminal after committed ACK event=${e.event_id} status=${e.status}`);return}if(t){if(!(this.canReplaceUnsentOutputGuard(t)&&(e.status==="canceled"||e.status==="failed"))){m.info("aibot",`preserving first durable terminal event=${e.event_id} existing_status=${t.payload.status} ignored_status=${e.status}`),this.scheduleTerminalDelivery(e.event_id,{ignoreNotBefore:!0});return}this.provisionalRespondedTerminals.delete(e.event_id)}if(e.status==="responded"&&this.hasNonReplayableOutput(e.event_id)&&!this.hasPendingOutputAcceptanceFence(e.event_id)){this.markOutputIntegrityFailed(e.event_id,"terminal requested before a complete output acceptance fence");const o=this.outputIntegrityFailed.get(e.event_id);o&&(o.terminalObserved=!0);return}const r=e.status==="responded"&&this.hasUnconfirmedEventOutput(e.event_id),u=r?{...e,status:"failed",code:"agent_output_unconfirmed",msg:"required agent output was not durably confirmed before connector restart",updated_at:Date.now()}:e;r?this.provisionalRespondedTerminals.set(e.event_id,{...e}):this.provisionalRespondedTerminals.delete(e.event_id);try{n=this.terminalOutbox.enqueue(u)}catch(o){this.provisionalRespondedTerminals.delete(e.event_id),this.emitClientError(new Error(`event_result outbox persist failed: event=${e.event_id} status=${e.status}: ${o instanceof Error?o.message:o}`));return}this.scheduleTerminalDelivery(n.payload.event_id,{ignoreNotBefore:!0})}sendLocalActionResult(e){this.sendPacket("local_action_result",e)}sendEventStopAck(e){this.sendPacket("event_stop_ack",e)}sendEventStopResult(e){const t=e.event_id?.trim(),n=e.terminal_commit_token?.trim(),s=t?this.terminalCommitTokens.get(t):void 0;if(n&&s&&n!==s){this.emitClientError(new Error(`event_stop_result token mismatch event=${t??""}`));return}const i=s??n;if(t&&i&&(e.status==="stopped"||e.status==="already_finished")){try{const r=this.stopResultOutbox.enqueue({...e,event_id:t,terminal_commit_token:i});this.scheduleStopResultDelivery(r,{ignoreNotBefore:!0})}catch(r){this.emitClientError(new Error(`event_stop_result outbox persist failed: event=${t} stop=${e.stop_id}: ${r instanceof Error?r.message:r}`))}return}this.sendPacket("event_stop_result",e)}sendSessionActivitySet(e){this.sendPacket("session_activity_set",e)}sendCodexEvent(e){this.sendPacket("codex_event",e)}sendUpdateBindingCard(e){this.sendPacket("update_binding_card",e)}sendAuditState(e){this.sendPacket("audit_state",e)}sendSkillsUpdate(e){this.sendPacket("agent_skills_update",e)}sendPing(){this.sendPacket("ping",{})}sendEventState(e){this.sendPacket("event_state",e)}sendEventCancelResult(e){this.sendPacket("event_cancel_result",e)}sendQueueClearResult(e){this.sendPacket("queue_clear_result",e)}sendQueueReorderResult(e){this.sendPacket("queue_reorder_result",e)}sendEventHoldResult(e){this.sendPacket("event_hold_result",e)}sendQueueEditResult(e){this.sendPacket("queue_edit_result",e)}sendQueueSnapshot(e){this.sendPacket("queue_snapshot",e)}agentInvoke(e,t,n=15e3){return new Promise((s,i)=>{const r=T(),u=Math.max(1e3,Math.min(n,6e4)),o=setTimeout(()=>{this.pendingInvokes.delete(r),i(new Error(`agent_invoke timeout: ${e}`))},u);this.pendingInvokes.set(r,{resolve:s,reject:i,timer:o}),this.sendPacket("agent_invoke",{invoke_id:r,action:e,params:t,timeout_ms:u})})}sendMcpFrame(e,t){this.sendPacket("mcp_frame",{session_id:e,frame:t})}async request(e,t,n){const s=this.outboundFlushInFlight;if(s){if(!await s.promise)throw new Error(`outbound flush failed before request: ${e}`);if(!this.isConnectionCurrent(s.socket,s.connectionGeneration))throw new Error(`connection changed while waiting for outbound flush: ${e}`)}if(!this.connected||!this.ws||this.ws.readyState!==O.OPEN)throw new Error(`send failed: ${e} (websocket not connected)`);return new Promise((i,r)=>{const u=++this.seq,o=setTimeout(()=>{this.pendingRequests.delete(u),r(new N(`request timeout: ${e} (expected ${n.expected.join("/")})`))},n.timeoutMs);this.pendingRequests.set(u,{expected:n.expected,resolve:i,reject:r,timer:o}),this.sendPacket(e,t,u)||(this.pendingRequests.delete(u),clearTimeout(o),r(new Error(`send failed: ${e}`)))})}async sendStreamChunkRequest(e,t=2e4){return this.request("client_stream_chunk",e,{expected:["send_ack","send_nack","error"],timeoutMs:t})}async relayCredentialRequest(e,t=15e3){return this.request("relay_credential_request",e,{expected:["relay_credential_result","error"],timeoutMs:t})}async relayStateSyncRequest(e,t=15e3){return this.request("relay_state_sync_request",e,{expected:["relay_state_sync_result","error"],timeoutMs:t})}sendRelayStateReport(e){this.sendPacket("relay_state_report",e)}async sendText(e,t=2e4){return this.request("send_msg",{msg_type:1,...e,client_msg_id:e.client_msg_id?.trim()||T()},{expected:["send_ack","send_nack","error"],timeoutMs:t})}async sendMedia(e,t=2e4){return this.request("send_msg",{...e,msg_type:2,client_msg_id:e.client_msg_id?.trim()||T()},{expected:["send_ack","send_nack","error"],timeoutMs:t})}async editMessage(e,t=2e4){return this.request("edit_msg",e,{expected:["send_ack","send_nack","error"],timeoutMs:t})}async deleteMessage(e,t,n=2e4){return this.request("delete_msg",{session_id:e,msg_id:t},{expected:["send_ack","send_nack","error"],timeoutMs:n})}async sendEventResultRequest(e,t=5e3){return this.request("event_result",e,{expected:["send_ack","send_nack","error"],timeoutMs:t})}disconnect(){m.info("aibot",`disconnect() agent=${this.config.clientType}:${this.config.agentId} wasConnected=${this.connected} reconnecting=${this.reconnecting} reconnectAttempts=${this.reconnectAttempts}`),this.connected=!1,this.negotiatedCapabilities.clear(),this.everConnected=!1,this.reconnecting=!1,this.reconnectRunGeneration++,this.reconnectAttempts=0,this.stopHeartbeat(),this.clearTerminalRetryTimers(),this.clearStopResultRetryTimers();const e=this.ws,t=this.connectionGeneration,n=this.outboundFlushInFlight;n&&this.cancelOutboundFlush(n,"discard","explicit disconnect"),this.connectionGeneration++,this.rejectAllPendingInvokes("disconnect"),this.rejectAllPendingRequests("disconnect"),this.abortPendingOutputWrites(!1,e??void 0,t),this.abortPendingAcceptedOutputs(!1,e??void 0,t),this.clearAllEventCorrelations(),this.outboundBuffer.length=0,this.provisionalRespondedTerminals.clear(),this.outputIntegrityFailed.clear(),this.nonReplayableOutputEventsBySocket.clear(),this.ws&&(this.ws.close(1e3,"client disconnect"),this.ws=null)}static MAX_CONSECUTIVE_AUTH_FAILURES=5;async attemptReconnect(){if(this.reconnecting||this.agentDeleted)return;this.reconnecting=!0;const e=++this.reconnectRunGeneration;m.info("aibot",`attemptReconnect start agent=${this.config.clientType}:${this.config.agentId} fromAttempts=${this.reconnectAttempts}`),this.emit("disconnected");let t=0;for(;this.reconnecting&&this.reconnectRunGeneration===e;){const n=Math.min(1e3*2**this.reconnectAttempts,3e4),s=Math.floor(n*.2*Math.random());if(this.reconnectAttempts++,await new Promise(u=>setTimeout(u,n+s)),!this.reconnecting||this.reconnectRunGeneration!==e)return;let i=null,r=this.connectionGeneration;try{const u=this.connect();if(i=this.ws,r=this.connectionGeneration,await u,this.reconnectRunGeneration!==e)return;if(!i||!this.isConnectionCurrent(i,r)){if(this.connected&&this.ws){this.reconnecting=!1;return}continue}const o=this.reconnectAttempts;this.reconnectAttempts=0,this.reconnecting=!1,m.info("aibot",`reconnect succeeded agent=${this.config.clientType}:${this.config.agentId} attempt=${o}`);return}catch(u){if(this.reconnectRunGeneration!==e)return;if(i&&!this.isConnectionCurrent(i,r)&&this.connected&&this.ws){this.reconnecting=!1;return}if(i&&this.isConnectionCurrent(i,r)){this.connectionGeneration++;try{i.close()}catch{}this.ws===i&&(this.ws=null)}const o=u instanceof Error?u.message:String(u);if(m.warn("aibot",`reconnect failed agent=${this.config.clientType}:${this.config.agentId} attempt=${this.reconnectAttempts} err=${o}`),u instanceof C){this.agentDeleted=!0,this.reconnecting=!1,m.error("aibot",`reconnect aborted: agent deleted on platform agent=${this.config.clientType}:${this.config.agentId}`),this.emit("agentDeleted",{source:"auth_ack",code:w});return}if(/Auth failed/i.test(o)){if(t++,t>=p.MAX_CONSECUTIVE_AUTH_FAILURES){this.reconnecting=!1,m.error("aibot",`reconnect giving up after ${t} consecutive auth failures agent=${this.config.clientType}:${this.config.agentId}`);return}}else t=0}}}sendPacket(e,t,n){const s=this.outboundEventId(e,t);if(s&&this.blockedOutputEvents.has(s))return this.packetLog?.logOutboundPacket(e,n??0,t,"dropped"),m.warn("aibot",`blocked late output after irreversible event failure event=${s} cmd=${e}`),!1;const i=n===void 0?this.createOutboundEntry(e,t):void 0;if(i&&this.outboundFlushInFlight&&!this.outboundFlushInFlight.finalized&&this.isConnectionCurrent(this.outboundFlushInFlight.socket,this.outboundFlushInFlight.connectionGeneration)&&!p.DROPPABLE_COMMANDS.has(e))return this.bufferOutboundEntry(i),!1;if(this.ws&&this.ws.readyState===O.OPEN&&(e==="auth"||this.connected)){const u=this.ws.bufferedAmount>p.BACKPRESSURE_THRESHOLD;if(!u||!p.DROPPABLE_COMMANDS.has(e)){if(u&&p.DROPPABLE_COMMANDS.has(e))return!1;const o=n??++this.seq,f={cmd:e,seq:o,payload:t};this.packetLog?.logOutboundPacket(e,o,t,"sent");const l=this.ws,d=this.connectionGeneration,_=s;_&&this.trackOutboundEventCorrelation(e,o,t),i&&_&&(this.registerPendingOutputWrite(o,_,i,l,d),this.isAckRequiredOutput(e,t)&&this.registerPendingAcceptedOutput(o,_,i,l,d));let E=!1;try{const c=l.readyState,a=l.bufferedAmount;let h=!1;return l.send(JSON.stringify(f),g=>{if(g&&!h&&(E=!0),e==="event_result"){const v=t;g?m.warn("aibot",`event_result ws send callback failed event=${v.event_id??""} status=${v.status??""} seq=${o} readyState=${c} bufferedAmount=${a} err=${g.message}`):m.info("aibot",`event_result ws send callback ok event=${v.event_id??""} status=${v.status??""} seq=${o} readyState=${c} bufferedAmount=${a}`)}else if(e==="client_stream_chunk"){const v=t;g?m.warn("aibot",`stream_chunk ws send failed event=${v.event_id??""} session=${v.session_id??""} seq=${o} chunk_seq=${v.chunk_seq??""} is_finish=${v.is_finish??""} readyState=${c} bufferedAmount=${a} err=${g.message}`):m.info("aibot",`stream_chunk ws send ok event=${v.event_id??""} session=${v.session_id??""} seq=${o} chunk_seq=${v.chunk_seq??""} is_finish=${v.is_finish??""} client_msg_id=${v.client_msg_id??""} quoted_message_id=${v.quoted_message_id??""} readyState=${c} bufferedAmount=${a}`)}else if(e==="event_ack"){const v=t;g?m.warn("aibot",`event_ack ws send failed event=${v.event_id??""} seq=${o} readyState=${c} bufferedAmount=${a} err=${g.message}`):m.info("aibot",`event_ack ws send ok event=${v.event_id??""} seq=${o} readyState=${c} bufferedAmount=${a}`)}else if(e==="send_msg"){const v=t;g?m.warn("aibot",`send_msg ws send failed event=${v.event_id??""} session=${v.session_id??""} seq=${o} readyState=${c} bufferedAmount=${a} err=${g.message}`):m.info("aibot",`send_msg ws send ok event=${v.event_id??""} session=${v.session_id??""} seq=${o} readyState=${c} bufferedAmount=${a}`)}else if(g){const v=t;m.warn("aibot",`${e} ws send failed seq=${o} session=${v.session_id??""} event=${v.event_id??""} client_msg_id=${v.client_msg_id??""} readyState=${c} bufferedAmount=${a} err=${g.message}`)}const b=this.pendingOutputWrites.get(o),y=!!(b&&b.socket===l&&b.connectionGeneration===d);if(!this.isConnectionCurrent(l,d)){y&&(this.settlePendingOutputWrite(o,!1,!1,l,d),this.settlePendingAcceptedOutput(o,"retry",!1,l,d));return}if(!(i&&_&&!y)){if(g)this.removeSeqFromEventCorrelation(o),_?(this.settlePendingOutputWrite(o,!1,!0,l,d),this.settlePendingAcceptedOutput(o,"retry",!1,l,d)):i&&this.bufferOutboundEntry(i),n!==void 0?this.rejectPendingRequestAfterWriteFailure(o,e,g):i&&this.reconnectAfterOutboundWriteFailure(l,`${e} callback failed`);else if(_){const v=y;this.settlePendingOutputWrite(o,!0,!1,l,d),v&&this.trackNonReplayableOutput(l,e,t,o)}}}),h=!0,!E}catch(c){return this.isConnectionCurrent(l,d)?(this.removeSeqFromEventCorrelation(o),_?(this.settlePendingOutputWrite(o,!1,!0,l,d),this.settlePendingAcceptedOutput(o,"retry",!1,l,d)):i&&this.bufferOutboundEntry(i),this.emitClientError(new Error(`sendPacket failed: ${c}`)),i&&this.reconnectAfterOutboundWriteFailure(l,`${e} send threw`),!1):(this.settlePendingOutputWrite(o,!1,!1,l,d),this.settlePendingAcceptedOutput(o,"retry",!1,l,d),!1)}}}if(p.DROPPABLE_COMMANDS.has(e))return this.packetLog?.logOutboundPacket(e,n??0,t,"dropped"),!1;if(n!==void 0)return this.packetLog?.logOutboundPacket(e,n,t,"dropped"),!1;const r=this.bufferOutboundEntry(i??this.createOutboundEntry(e,t));if(this.packetLog?.logOutboundPacket(e,n??0,t,r?"buffered":"dropped"),r&&e==="client_stream_chunk"){const u=t;m.info("aibot",`stream_chunk buffered (ws not open) event=${u.event_id??""} session=${u.session_id??""} chunk_seq=${u.chunk_seq??""} is_finish=${u.is_finish??""} ws=${this.ws?`state=${this.ws.readyState}`:"null"}`)}return!1}replayTerminalOutbox(){for(const e of this.terminalOutbox.listPending())this.scheduleTerminalDelivery(e.payload.event_id,{ignoreNotBefore:!0})}outboundEventId(e,t){if(!p.ORDERED_OUTPUT_COMMANDS.has(e)||!t||typeof t!="object")return"";const n=t.event_id;return typeof n=="string"?n.trim():""}blockEventOutput(e){const t=e.trim();if(t)for(this.blockedOutputEvents.delete(t),this.blockedOutputEvents.set(t,Date.now()),this.outboundBuffer=this.outboundBuffer.filter(n=>this.outboundEventId(n.cmd,n.payload)!==t);this.blockedOutputEvents.size>p.MAX_BLOCKED_OUTPUT_EVENTS;){const n=this.blockedOutputEvents.keys().next().value;if(!n)break;this.blockedOutputEvents.delete(n)}}isAckRequiredOutput(e,t){return e==="send_msg"?!0:e==="client_stream_chunk"&&!!t&&typeof t=="object"&&t.is_finish===!0}isNonReplayableOutput(e,t){return e==="codex_event"?!0:e==="client_stream_chunk"&&!!t&&typeof t=="object"&&t.is_finish!==!0}trackNonReplayableOutput(e,t,n,s){if(!this.isNonReplayableOutput(t,n))return;const i=this.outboundEventId(t,n);if(!i||this.outputIntegrityFailed.has(i)||this.blockedOutputEvents.has(i))return;let r=this.nonReplayableOutputEventsBySocket.get(e);r||(r=new Map,this.nonReplayableOutputEventsBySocket.set(e,r)),r.set(i,Math.max(r.get(i)??0,s))}clearNonReplayableOutputThrough(e,t,n){if(!e||n<=0)return;const s=this.nonReplayableOutputEventsBySocket.get(e);if(!s)return;const i=s.get(t);i===void 0||i>n||(s.delete(t),s.size===0&&this.nonReplayableOutputEventsBySocket.delete(e))}clearNonReplayableOutputEvent(e){for(const[t,n]of this.nonReplayableOutputEventsBySocket)n.delete(e),n.size===0&&this.nonReplayableOutputEventsBySocket.delete(t)}failNonReplayableOutputsForSocket(e,t){const n=this.nonReplayableOutputEventsBySocket.get(e);if(n){this.nonReplayableOutputEventsBySocket.delete(e);for(const s of n.keys())this.markOutputIntegrityFailed(s,`connection lost before a complete output acceptance fence: ${t}`)}}hasNonReplayableOutput(e){for(const t of this.nonReplayableOutputEventsBySocket.values())if(t.has(e))return!0;return!1}hasPendingOutputAcceptanceFence(e){return!!(this.pendingAcceptedOutputSeqsByEvent.get(e)?.size||this.outboundBuffer.some(t=>this.outboundEventId(t.cmd,t.payload)===e&&this.isAckRequiredOutput(t.cmd,t.payload)))}hasUnconfirmedEventOutput(e){return!!(this.pendingOutputSeqsByEvent.get(e)?.size||this.pendingAcceptedOutputSeqsByEvent.get(e)?.size||this.outboundBuffer.some(t=>this.outboundEventId(t.cmd,t.payload)===e))}trackOutboundEventCorrelation(e,t,n){const s=this.outboundEventId(e,n);if(!s)return;const i=n,r=typeof i.client_msg_id=="string"?i.client_msg_id.trim():"";this.pruneExpiredEventCorrelations();const u=Date.now();let o=this.eventCorrelations.get(s);if(!o){if(this.eventCorrelations.size>=p.MAX_EVENT_CORRELATIONS){let l;for(const d of this.eventCorrelations.values())this.eventHasCriticalDeliveryState(d.eventId)||(!l||d.touchedAt<l.touchedAt)&&(l=d);l&&this.releaseEventCorrelations(l.eventId)}o={eventId:s,clientMsgIds:new Set,seqRanges:[],touchedAt:u,expiresAt:u+p.EVENT_CORRELATION_TTL_MS},this.eventCorrelations.set(s,o)}o.touchedAt=u,o.expiresAt=u+p.EVENT_CORRELATION_TTL_MS;const f=o.seqRanges[o.seqRanges.length-1];if(f&&t===f.end+1?f.end=t:(!f||t<f.start||t>f.end)&&o.seqRanges.push({start:t,end:t}),r){o.clientMsgIds.add(r);let l=this.clientMsgEventMap.get(r);l||(l=new Set,this.clientMsgEventMap.set(r,l)),l.add(s)}this.scheduleEventCorrelationCleanup()}uniqueEventIdForClientMsgId(e){const t=this.clientMsgEventMap.get(e);if(t?.size===1)return t.values().next().value}eventHasCriticalDeliveryState(e){return!!(this.terminalOutbox.get(e)||this.pendingAcceptedOutputSeqsByEvent.get(e)?.size)}eventIdForSeq(e){if(!(e<=0)){for(const t of this.eventCorrelations.values())if(t.seqRanges.some(n=>e>=n.start&&e<=n.end))return t.eventId}}removeSeqFromEventCorrelation(e){if(!(e<=0))for(const t of this.eventCorrelations.values()){const n=t.seqRanges.findIndex(i=>e>=i.start&&e<=i.end);if(n<0)continue;const s=t.seqRanges[n];s.start===s.end?t.seqRanges.splice(n,1):e===s.start?s.start++:e===s.end?s.end--:t.seqRanges.splice(n,1,{start:s.start,end:e-1},{start:e+1,end:s.end});return}}releaseEventCorrelations(e){const t=e.trim();if(!t)return;const n=this.eventCorrelations.get(t);if(n){for(const s of n.clientMsgIds){const i=this.clientMsgEventMap.get(s);i?.delete(t),i?.size===0&&this.clientMsgEventMap.delete(s)}this.eventCorrelations.delete(t)}this.cancelEventCorrelationCleanupIfEmpty()}clearAllEventCorrelations(){this.eventCorrelationCleanupTimer&&(clearTimeout(this.eventCorrelationCleanupTimer),this.eventCorrelationCleanupTimer=null),this.eventCorrelations.clear(),this.clientMsgEventMap.clear(),this.lastCorrelationPruneAt=0}pruneExpiredEventCorrelations(e=Date.now(),t=!1){if(!(!t&&e-this.lastCorrelationPruneAt<1e3)){this.lastCorrelationPruneAt=e;for(const n of[...this.eventCorrelations.values()])n.expiresAt<=e&&!this.eventHasCriticalDeliveryState(n.eventId)&&this.releaseEventCorrelations(n.eventId);this.cancelEventCorrelationCleanupIfEmpty()}}scheduleEventCorrelationCleanup(){if(this.eventCorrelationCleanupTimer||this.eventCorrelations.size===0)return;let e=Number.POSITIVE_INFINITY;for(const t of this.eventCorrelations.values())this.eventHasCriticalDeliveryState(t.eventId)||(e=Math.min(e,t.expiresAt));Number.isFinite(e)&&(this.eventCorrelationCleanupTimer=setTimeout(()=>{this.eventCorrelationCleanupTimer=null,this.pruneExpiredEventCorrelations(Date.now(),!0),this.scheduleEventCorrelationCleanup()},Math.max(1,e-Date.now())),this.eventCorrelationCleanupTimer.unref?.())}cancelEventCorrelationCleanupIfEmpty(){!this.eventCorrelationCleanupTimer||this.eventCorrelations.size>0||(clearTimeout(this.eventCorrelationCleanupTimer),this.eventCorrelationCleanupTimer=null)}handleCorrelatedSendResponse(e){if(e.cmd==="send_ack"){const f=e.payload,l=typeof f.client_msg_id=="string"?f.client_msg_id.trim():"";let d=e.seq>0&&this.pendingAcceptedOutputs.has(e.seq)?e.seq:void 0,_;if(d===void 0&&l){const a=this.pendingAcceptedSeqsByClientMsgId.get(l);if(a?.size===1){const h=a.values().next().value,g=this.pendingAcceptedOutputs.get(h),b=this.uniqueEventIdForClientMsgId(l);g&&b&&g.eventId===b&&(d=h,_=b)}}const E=(d!==void 0?this.pendingAcceptedOutputs.get(d)?.eventId:void 0)??_??this.eventIdForSeq(e.seq),c=d??e.seq;if(d!==void 0){const a=this.pendingOutputWrites.get(d);a&&this.settlePendingOutputWrite(d,!0,!1,a.socket,a.connectionGeneration),this.settlePendingAcceptedOutput(d,"accepted",!1)}else if(e.seq>0){const a=this.pendingOutputWrites.get(e.seq);a&&this.settlePendingOutputWrite(e.seq,!0,!1,a.socket,a.connectionGeneration)}E&&this.clearNonReplayableOutputThrough(this.ws,E,c);return}if(e.cmd!=="send_nack"&&e.cmd!=="error")return;this.pruneExpiredEventCorrelations();const t=e.payload,n=typeof t.client_msg_id=="string"?t.client_msg_id.trim():"",s=e.cmd==="error"&&typeof t.ref_id=="string"?t.ref_id.trim():"",i=e.cmd==="error"&&typeof t.ref_cmd=="string"?t.ref_cmd.trim():"",r=this.eventIdForSeq(e.seq),u=s&&(!i||p.ORDERED_OUTPUT_COMMANDS.has(i))?this.uniqueEventIdForClientMsgId(s)??(this.eventCorrelations.has(s)?s:void 0):void 0,o=r??(n?this.uniqueEventIdForClientMsgId(n):void 0)??u;o&&(this.blockEventOutput(o),this.settlePendingOutputWritesForEvent(o,!0),this.clearNonReplayableOutputEvent(o),e.seq>0&&this.pendingAcceptedOutputs.get(e.seq)?.eventId===o?this.settlePendingAcceptedOutput(e.seq,"rejected",!1):this.settlePendingAcceptedOutputsForEvent(o,"rejected",!1),t.code===4003&&(this.purgeBufferedStreamChunks(o),m.warn("aibot",`event payload rejected (4003), purging buffered chunks for event=${o}`)),this.promotePendingTerminalAfterOutputRejection(o,Number(t.code??0),t.msg),this.emit("streamRejected",o,Number(t.code??0)))}promotePendingTerminalAfterOutputRejection(e,t,n){this.provisionalRespondedTerminals.delete(e);const s=this.terminalOutbox.get(e);if(!(!s||s.payload.status!=="responded"&&s.payload.code!=="agent_output_unconfirmed"||s.deliveryStartedAt))try{const i=this.terminalOutbox.enqueue({...s.payload,status:"failed",code:"agent_output_rejected",msg:n?`required agent output was rejected: ${n}`:`required agent output was rejected by the server (code=${t})`,updated_at:Date.now()});this.scheduleTerminalDelivery(i.payload.event_id,{ignoreNotBefore:!0})}catch(i){this.emitClientError(new Error(`failed to persist output-rejection terminal promotion: event=${e} code=${t}: ${i instanceof Error?i.message:i}`))}}scheduleTerminalDelivery(e,t){if(!this.connected||!this.ws||this.ws.readyState!==O.OPEN)return;if(this.terminalInFlight.has(e)){t?.ignoreNotBefore&&this.terminalReplayAfterFlight.add(e);return}const n=this.terminalOutbox.get(e);if(!n)return;const s=this.terminalRetryTimers.get(e);s&&(clearTimeout(s),this.terminalRetryTimers.delete(e));const i=t?.ignoreNotBefore?0:Math.max(0,n.nextAttemptAt-Date.now()),r=Math.max(i,t?.minimumDelayMs??0);if(r>0){const u=setTimeout(()=>{this.terminalRetryTimers.delete(e),this.scheduleTerminalDelivery(e,{ignoreNotBefore:!0})},r);this.terminalRetryTimers.set(e,u);return}this.deliverTerminalEntry(n)}async deliverTerminalEntry(e){const t=e.payload.event_id;if(this.terminalInFlight.has(t)||!this.terminalOutbox.isCurrent(e))return;this.terminalInFlight.add(t);let n=0;try{n=await this.sendEventResultReliable(e)}finally{this.terminalInFlight.delete(t);const s=this.terminalReplayAfterFlight.delete(t);this.terminalOutbox.get(t)&&this.scheduleTerminalDelivery(t,s?{ignoreNotBefore:!0}:{minimumDelayMs:n})}}clearTerminalRetryTimers(){for(const e of this.terminalRetryTimers.values())clearTimeout(e);this.terminalRetryTimers.clear()}async sendEventResultReliable(e){const t=e.payload.event_id,n=e.payload.terminal_commit_token?.trim();if(n&&!this.negotiatedCapabilities.has("terminal_commit_v1")){const d=this.ws;return this.emitClientError(new Error(`refusing tokenized event_result on an unnegotiated connection: event=${t}`)),d&&this.reconnectAfterOutboundWriteFailure(d,"terminal_commit_v1 not negotiated"),p.TERMINAL_RETRY_DELAY_MS}if(!await this.awaitOutputDeliveryBarrier(t))return p.TERMINAL_RETRY_DELAY_MS;const s=this.provisionalRespondedTerminals.get(t);if(s&&this.terminalOutbox.isCurrent(e))if(!this.canReplaceUnsentOutputGuard(e))this.provisionalRespondedTerminals.delete(t);else try{return this.terminalOutbox.enqueue(s),this.provisionalRespondedTerminals.delete(t),0}catch(d){return this.emitClientError(new Error(`failed to promote confirmed output terminal to responded: event=${t}: ${d instanceof Error?d.message:d}`)),p.TERMINAL_RETRY_DELAY_MS}const i=e.payload;try{if(!this.terminalOutbox.markDeliveryStarted(e))return 0}catch(d){return this.emitClientError(new Error(`event_result delivery-start persist failed: event=${t}: ${d instanceof Error?d.message:d}`)),p.TERMINAL_RETRY_DELAY_MS}const r=Math.max(1,this.ackPolicy?.max_retries??3),u=this.ackPolicy?.push_ack_timeout_ms??5e3,o=750;let f="unknown delivery failure";for(let d=1;d<=r;d++){if(!this.terminalOutbox.isCurrent(e))return 0;const _=this.ws?.readyState??-1,E=this.ws?.bufferedAmount??0;m.info("aibot",`event_result send attempt event=${i.event_id} status=${i.status} attempt=${d}/${r} readyState=${_} bufferedAmount=${E}`);try{const c=await this.sendEventResultRequest(i,u);if(!this.terminalOutbox.isCurrent(e))return 0;if(c.cmd==="send_ack"){const h=c.payload;if(m.info("aibot",`event_result ack event=${i.event_id} status=${i.status} attempt=${d}/${r} ack_event=${h.event_id??""} ack_status=${h.status??""}`),h.event_id!==i.event_id||h.status!==i.status)throw new Error(`event_result ACK mismatch: expected event=${i.event_id} status=${i.status}, got event=${h.event_id??""} status=${h.status??""}`);if(n&&(h.terminal_commit_token?.trim()!==n||h.terminal_committed!==!0))throw new Error(`event_result terminal commit ACK mismatch: event=${i.event_id}`);if(n)try{this.terminalCommitTokens.remove(i.event_id,n)}catch(g){throw new Error(`terminal commit token cleanup failed: event=${i.event_id}: ${g instanceof Error?g.message:g}`)}return this.terminalOutbox.acknowledge(e,h.event_id,h.status,h.terminal_commit_token,h.terminal_committed)&&(this.rememberCommittedTerminalEvent(i.event_id),this.tokenizedTerminalRejections.delete(i.event_id),this.blockEventOutput(i.event_id),this.releaseEventCorrelations(i.event_id),this.clearNonReplayableOutputEvent(i.event_id),this.settleOutputIntegrityTerminal(i.event_id),this.scheduleStopResultsForEvent(i.event_id)),0}const a=c.payload;if(m.warn("aibot",`event_result rejected event=${i.event_id} status=${i.status} attempt=${d}/${r} cmd=${c.cmd} code=${a.code??""} msg=${a.msg??""}${a.ref_cmd?` ref_cmd=${a.ref_cmd}`:""}${a.ref_id?` ref_id=${a.ref_id}`:""}`),n){f=`tokenized terminal rejected: cmd=${c.cmd} code=${a.code??""} msg=${a.msg??""}`;const h=(this.tokenizedTerminalRejections.get(i.event_id)??0)+1;if(this.tokenizedTerminalRejections.set(i.event_id,h),h<p.MAX_TOKENIZED_TERMINAL_REJECTIONS)break;this.tokenizedTerminalRejections.delete(i.event_id);try{this.terminalCommitTokens.remove(i.event_id,n),this.terminalOutbox.moveToDeadLetter(e,{responseCmd:c.cmd,code:a.code,message:a.msg})&&(this.blockEventOutput(i.event_id),this.releaseEventCorrelations(i.event_id),this.clearNonReplayableOutputEvent(i.event_id),this.settleOutputIntegrityTerminal(i.event_id),this.emitClientError(new Error(`tokenized event_result permanently rejected after ${h} rejections, moved to dead-letter: event=${i.event_id} status=${i.status} cmd=${c.cmd} code=${a.code??""} msg=${a.msg??""}`)))}catch(g){f=`dead-letter persist failed: ${g instanceof Error?g.message:g}`,this.emitClientError(new Error(`tokenized event_result dead-letter persist failed: event=${i.event_id} status=${i.status}: ${f}`));break}return 0}try{return this.terminalOutbox.moveToDeadLetter(e,{responseCmd:c.cmd,code:a.code,message:a.msg})&&(this.tokenizedTerminalRejections.delete(i.event_id),this.blockEventOutput(i.event_id),this.releaseEventCorrelations(i.event_id),this.clearNonReplayableOutputEvent(i.event_id),this.settleOutputIntegrityTerminal(i.event_id),this.emitClientError(new Error(`event_result rejected and moved to dead-letter: event=${i.event_id} status=${i.status} cmd=${c.cmd} code=${a.code??""} msg=${a.msg??""}`))),0}catch(h){f=`dead-letter persist failed: ${h instanceof Error?h.message:h}`,this.emitClientError(new Error(`event_result dead-letter persist failed: event=${i.event_id} status=${i.status}: ${f}`));break}}catch(c){const a=c instanceof Error?c.message:String(c);if(f=a,m.warn("aibot",`event_result attempt failed event=${i.event_id} status=${i.status} attempt=${d}/${r} err=${a}`),d===r)break;await new Promise(h=>setTimeout(h,o*d))}}const l=Date.now()+p.TERMINAL_RETRY_DELAY_MS;try{this.terminalOutbox.recordRetry(e,l,f)}catch(d){this.emitClientError(new Error(`event_result retry state persist failed: event=${i.event_id} status=${i.status}: ${d instanceof Error?d.message:d}`))}return this.emitClientError(new Error(`event_result ack failed after ${r} attempts; retained for retry: event=${i.event_id} status=${i.status} err=${f}`)),p.TERMINAL_RETRY_DELAY_MS}purgeBufferedStreamChunks(e){const t=this.outboundBuffer.length;this.outboundBuffer=this.outboundBuffer.filter(n=>n.cmd!=="client_stream_chunk"?!0:n.payload?.event_id!==e),this.outboundBuffer.length<t&&m.info("aibot",`purged ${t-this.outboundBuffer.length} buffered stream chunks for event=${e}`)}emitClientError(e){if(this.listenerCount("error")===0){m.warn("aibot",`Client error (no listeners): ${e.message}`);return}this.emit("error",e)}createOutboundEntry(e,t){const n={cmd:e,payload:t};return this.outboundEntryOrders.set(n,++this.nextOutboundEntryOrder),n}makeOutputIntegrityFailurePayload(e,t){return this.withTerminalCommitToken({event_id:e,status:"failed",code:"agent_output_integrity_failed",msg:`required agent output was discarded before delivery: ${t}`,updated_at:Date.now()})}canReplaceUnsentOutputGuard(e){return e.payload.code==="agent_output_unconfirmed"&&!e.deliveryStartedAt}rememberCommittedTerminalEvent(e){for(this.committedTerminalEvents.delete(e),this.committedTerminalEvents.add(e);this.committedTerminalEvents.size>p.MAX_COMMITTED_TERMINAL_EVENTS;){const t=this.committedTerminalEvents.values().next().value;if(!t)break;this.committedTerminalEvents.delete(t)}}replayStopResultOutbox(){for(const e of this.stopResultOutbox.listPending())this.scheduleStopResultDelivery(e,{ignoreNotBefore:!0})}scheduleStopResultsForEvent(e){for(const t of this.stopResultOutbox.listPending())t.payload.event_id===e&&this.scheduleStopResultDelivery(t,{ignoreNotBefore:!0})}scheduleStopResultDelivery(e,t){if(!this.connected||!this.ws||this.ws.readyState!==O.OPEN||!this.stopResultOutbox.isCurrent(e))return;const n=e.payload.event_id;if(this.terminalOutbox.get(n)||this.terminalCommitTokens.get(n))return;const s=this.stopResultRetryTimers.get(e.key);s&&(clearTimeout(s),this.stopResultRetryTimers.delete(e.key));const i=t?.ignoreNotBefore?0:Math.max(0,e.nextAttemptAt-Date.now());if(i>0){const r=setTimeout(()=>{this.stopResultRetryTimers.delete(e.key),this.scheduleStopResultDelivery(e,{ignoreNotBefore:!0})},i);this.stopResultRetryTimers.set(e.key,r);return}this.deliverStopResultEntry(e)}async deliverStopResultEntry(e){if(this.stopResultInFlight.has(e.key)||!this.stopResultOutbox.isCurrent(e))return;this.stopResultInFlight.add(e.key);let t=p.TERMINAL_RETRY_DELAY_MS;try{t=await this.sendStopResultReliable(e)}finally{if(this.stopResultInFlight.delete(e.key),this.stopResultOutbox.isCurrent(e)){const n=setTimeout(()=>{this.stopResultRetryTimers.delete(e.key),this.scheduleStopResultDelivery(e,{ignoreNotBefore:!0})},t);this.stopResultRetryTimers.set(e.key,n)}}}async sendStopResultReliable(e){const t=this.ackPolicy?.push_ack_timeout_ms??5e3;let n="unknown delivery failure";try{const i=await this.request("event_stop_result",e.payload,{expected:["send_ack","send_nack","error"],timeoutMs:t});if(!this.stopResultOutbox.isCurrent(e))return 0;if(i.cmd==="send_ack"){const r=i.payload;if(this.stopResultOutbox.acknowledge(e,r.event_id,r.terminal_commit_token,r.terminal_committed))return 0;n=`event_stop_result ACK mismatch event=${e.payload.event_id}`}else{const r=i.payload;if(n=`event_stop_result rejected: cmd=${i.cmd} code=${r.code??""} msg=${r.msg??""}`,r.code===4001||r.code===4003){try{this.stopResultOutbox.discard(e)&&this.emitClientError(new Error(`event_stop_result permanently rejected and discarded: event=${e.payload.event_id} stop=${e.payload.stop_id} code=${r.code} msg=${r.msg??""}`))}catch(u){this.emitClientError(new Error(`event_stop_result discard persist failed: event=${e.payload.event_id} stop=${e.payload.stop_id}: ${u instanceof Error?u.message:u}`))}return 0}}}catch(i){n=i instanceof Error?i.message:String(i)}const s=Date.now()+p.TERMINAL_RETRY_DELAY_MS;try{this.stopResultOutbox.recordRetry(e,s,n)}catch(i){this.emitClientError(new Error(`event_stop_result retry state persist failed: event=${e.payload.event_id} stop=${e.payload.stop_id}: ${i instanceof Error?i.message:i}`))}return this.emitClientError(new Error(`event_stop_result retained for retry: event=${e.payload.event_id} stop=${e.payload.stop_id} err=${n}`)),p.TERMINAL_RETRY_DELAY_MS}clearStopResultRetryTimers(){for(const e of this.stopResultRetryTimers.values())clearTimeout(e);this.stopResultRetryTimers.clear()}markOutputIntegrityFailed(e,t){if(this.blockEventOutput(e),this.outputIntegrityFailed.has(e))return;const n={reason:t,terminalObserved:!!(this.provisionalRespondedTerminals.has(e)||this.terminalOutbox.get(e)),terminalSettled:!1};this.outputIntegrityFailed.set(e,n),this.provisionalRespondedTerminals.delete(e),this.clearNonReplayableOutputEvent(e);const s=this.outboundBuffer.length;this.outboundBuffer=this.outboundBuffer.filter(u=>this.outboundEventId(u.cmd,u.payload)!==e);const i=this.terminalOutbox.get(e),r=i?this.canReplaceUnsentOutputGuard(i):!1;if(i&&!r)n.terminalObserved=!0,this.scheduleTerminalDelivery(e,{ignoreNotBefore:!0});else if(!this.committedTerminalEvents.has(e))try{const u=this.terminalOutbox.enqueue(this.makeOutputIntegrityFailurePayload(e,t));this.scheduleTerminalDelivery(u.payload.event_id,{ignoreNotBefore:!0})}catch(u){this.emitClientError(new Error(`output-integrity terminal persist failed: event=${e}: ${u instanceof Error?u.message:u}`))}for(const u of[...this.pendingOutputSeqsByEvent.get(e)??[]])this.settlePendingOutputWrite(u,!1,!1);this.settlePendingAcceptedOutputsForEvent(e,"rejected",!1),this.emit("streamRejected",e,0),this.emitClientError(new Error(`required output integrity failed: event=${e} removed=${s-this.outboundBuffer.length} reason=${t}`))}settleOutputIntegrityTerminal(e){const t=this.outputIntegrityFailed.get(e);t&&(t.terminalSettled=!0,t.terminalObserved&&this.outputIntegrityFailed.delete(e))}bufferOutboundEntry(e){if(this.outboundEntryOrders.has(e)||this.outboundEntryOrders.set(e,++this.nextOutboundEntryOrder),this.outboundBuffer.includes(e))return!0;const t=this.outboundEventId(e.cmd,e.payload);return t&&(this.outputIntegrityFailed.has(t)||this.blockedOutputEvents.has(t))?!1:(this.outboundBuffer.length>=p.MAX_OUTBOUND_BUFFER_SIZE&&(this.outboundBuffer=this.outboundBuffer.filter(n=>p.BUFFER_OVERFLOW_RETAIN_COMMANDS.has(n.cmd)),this.outboundBuffer.length>=p.MAX_OUTBOUND_BUFFER_SIZE&&m.warn("aibot",`outbound buffer soft limit exceeded by ${this.outboundBuffer.length} retained packet(s); preserving required output`)),this.outboundBuffer.length>=p.MAX_OUTBOUND_BUFFER_HARD_SIZE?(t?this.markOutputIntegrityFailed(t,`outbound buffer hard limit ${p.MAX_OUTBOUND_BUFFER_HARD_SIZE} reached`):this.emitClientError(new Error(`outbound buffer hard limit ${p.MAX_OUTBOUND_BUFFER_HARD_SIZE} reached; rejected non-event packet cmd=${e.cmd}`)),!1):(this.outboundBuffer.push(e),this.outboundBuffer.sort((n,s)=>(this.outboundEntryOrders.get(n)??Number.MAX_SAFE_INTEGER)-(this.outboundEntryOrders.get(s)??Number.MAX_SAFE_INTEGER)),!0))}registerPendingOutputWrite(e,t,n,s,i){let r;const u=new Promise(l=>{r=l}),o=setTimeout(()=>{const l=this.pendingOutputWrites.get(e);if(!l||l.socket!==s||l.connectionGeneration!==i)return;if(!this.isConnectionCurrent(s,i)){this.settlePendingOutputWrite(e,!1,!1,s,i);return}this.removeSeqFromEventCorrelation(e);const d=this.isNonReplayableOutput(n.cmd,n.payload);this.settlePendingOutputWrite(e,!1,!d,s,i),d&&this.markOutputIntegrityFailed(t,`websocket write callback timed out for non-replayable ${n.cmd} seq=${e}`),this.emitClientError(new Error(`outbound websocket write callback timeout: cmd=${n.cmd} event=${t} seq=${e} delivery_unknown=${d}`)),this.reconnectAfterOutboundWriteFailure(s,`${n.cmd} callback timeout`)},p.OUTBOUND_WRITE_CALLBACK_TIMEOUT_MS);o.unref?.(),this.pendingOutputWrites.set(e,{eventId:t,entry:n,socket:s,connectionGeneration:i,promise:u,resolve:r,timer:o});let f=this.pendingOutputSeqsByEvent.get(t);f||(f=new Set,this.pendingOutputSeqsByEvent.set(t,f)),f.add(e)}settlePendingOutputWrite(e,t,n,s,i){const r=this.pendingOutputWrites.get(e);if(!r||s&&(r.socket!==s||r.connectionGeneration!==i))return;this.pendingOutputWrites.delete(e),clearTimeout(r.timer);const u=this.pendingOutputSeqsByEvent.get(r.eventId);u?.delete(e),u?.size===0&&this.pendingOutputSeqsByEvent.delete(r.eventId),n&&this.bufferOutboundEntry(r.entry),r.resolve(t)}settlePendingOutputWritesForEvent(e,t){const n=this.pendingOutputSeqsByEvent.get(e);if(n)for(const s of[...n]){const i=this.pendingOutputWrites.get(s);i&&this.settlePendingOutputWrite(s,t,!1,i.socket,i.connectionGeneration)}}abortPendingOutputWrites(e,t,n,s){for(const[i,r]of[...this.pendingOutputWrites.entries()])if(!(t&&(r.socket!==t||r.connectionGeneration!==n))){if(this.removeSeqFromEventCorrelation(i),s&&this.isNonReplayableOutput(r.entry.cmd,r.entry.payload)){this.settlePendingOutputWrite(i,!1,!1,t,n),this.markOutputIntegrityFailed(r.eventId,`connection lost while websocket write completion was unknown: ${s}`);continue}this.settlePendingOutputWrite(i,!1,e,t,n)}}registerPendingAcceptedOutput(e,t,n,s,i){let r;const u=new Promise(h=>{r=h}),o=Math.max(1,this.ackPolicy?.push_ack_timeout_ms??5e3),f=Math.max(1,this.ackPolicy?.max_retries??3),l=Math.max(1e3,Math.ceil(o*.25)),d=Math.max(p.MIN_REQUIRED_OUTPUT_ACK_TIMEOUT_MS,o*f+l),_=setTimeout(()=>{const h=this.pendingAcceptedOutputs.get(e);if(!(!h||h.socket!==s||h.connectionGeneration!==i)){if(!this.isConnectionCurrent(s,i)){this.settlePendingAcceptedOutput(e,"retry",!1,s,i);return}this.settlePendingAcceptedOutput(e,"retry",!0,s,i),this.emitClientError(new Error(`required output ACK timeout; retained for reconnect retry: event=${t} seq=${e}`)),this.reconnectAfterOutboundWriteFailure(s,"required output ACK timeout")}},d);_.unref?.(),this.pendingAcceptedOutputs.set(e,{eventId:t,entry:n,socket:s,connectionGeneration:i,promise:u,resolve:r,timer:_});const E=n.payload,c=typeof E.client_msg_id=="string"?E.client_msg_id.trim():"";if(c){let h=this.pendingAcceptedSeqsByClientMsgId.get(c);h||(h=new Set,this.pendingAcceptedSeqsByClientMsgId.set(c,h)),h.add(e)}let a=this.pendingAcceptedOutputSeqsByEvent.get(t);a||(a=new Set,this.pendingAcceptedOutputSeqsByEvent.set(t,a)),a.add(e)}settlePendingAcceptedOutput(e,t,n,s,i){const r=this.pendingAcceptedOutputs.get(e);if(!r||s&&(r.socket!==s||r.connectionGeneration!==i))return;this.pendingAcceptedOutputs.delete(e),clearTimeout(r.timer);const u=this.pendingAcceptedOutputSeqsByEvent.get(r.eventId);u?.delete(e),u?.size===0&&this.pendingAcceptedOutputSeqsByEvent.delete(r.eventId);const o=r.entry.payload,f=typeof o.client_msg_id=="string"?o.client_msg_id.trim():"";if(f){const l=this.pendingAcceptedSeqsByClientMsgId.get(f);l?.delete(e),l?.size===0&&this.pendingAcceptedSeqsByClientMsgId.delete(f)}n&&this.bufferOutboundEntry(r.entry),r.resolve(t)}settlePendingAcceptedOutputsForEvent(e,t,n){const s=this.pendingAcceptedOutputSeqsByEvent.get(e);if(s)for(const i of[...s])this.settlePendingAcceptedOutput(i,t,n)}abortPendingAcceptedOutputs(e,t,n){for(const[s,i]of[...this.pendingAcceptedOutputs.entries()])t&&(i.socket!==t||i.connectionGeneration!==n)||this.settlePendingAcceptedOutput(s,"retry",e,t,n)}rejectPendingRequestAfterWriteFailure(e,t,n){const s=this.pendingRequests.get(e);s&&(this.pendingRequests.delete(e),clearTimeout(s.timer),s.reject(new Error(`websocket write failed: ${t}: ${n.message}`)))}async awaitOutputDeliveryBarrier(e){const t=this.outboundFlushInFlight;if(t&&!await t.promise)return!1;for(;;){const n=this.pendingOutputSeqsByEvent.get(e);if(!n||n.size===0)break;const s=[...n].map(r=>this.pendingOutputWrites.get(r)?.promise).filter(r=>!!r);if(s.length===0)break;if((await Promise.all(s)).some(r=>!r))return!1}for(;;){const n=this.pendingAcceptedOutputSeqsByEvent.get(e);if(!n||n.size===0)break;const s=[...n].map(r=>this.pendingAcceptedOutputs.get(r)?.promise).filter(r=>!!r);if(s.length===0)break;const i=await Promise.all(s);if(i.includes("retry"))return!1;if(i.includes("rejected")){const r=this.terminalOutbox.get(e);if(!r||r.payload.status==="responded")return!1}}return this.outboundBuffer.some(n=>this.outboundEventId(n.cmd,n.payload)===e)?!1:this.hasNonReplayableOutput(e)?(this.markOutputIntegrityFailed(e,"output flush completed without a complete acceptance fence"),!1):!!(this.connected&&this.ws&&this.ws.readyState===O.OPEN)}isConnectionCurrent(e,t){return this.ws===e&&this.connectionGeneration===t}isOutboundFlushCurrent(e){return!e.finalized&&this.outboundFlushInFlight===e&&this.isConnectionCurrent(e.socket,e.connectionGeneration)}flushOutboundBuffer(e=this.ws,t=this.connectionGeneration){if(!e||!this.isConnectionCurrent(e,t))return Promise.resolve(!1);const n=this.outboundFlushInFlight;if(n){if(!n.finalized&&n.socket===e&&n.connectionGeneration===t)return n.promise;this.cancelOutboundFlush(n,"discard","superseded stale flush")}if(this.outboundBuffer.length===0)return Promise.resolve(!0);const s={connectionGeneration:t,flushGeneration:++this.nextOutboundFlushGeneration,socket:e,promise:Promise.resolve(!1),finalized:!1,activeBatch:null,nextIndex:0,currentWritePending:!1,currentWriteOutcome:null,cancelCurrentWrite:null,remainderHandled:!1};return this.outboundFlushInFlight=s,s.promise=this.performOutboundBufferFlush(s),s.promise.finally(()=>{s.finalized=!0,s.cancelCurrentWrite?.(),s.cancelCurrentWrite=null,this.outboundFlushInFlight===s&&(this.outboundFlushInFlight=null)}),s.promise}async performOutboundBufferFlush(e){for(;this.isOutboundFlushCurrent(e)&&this.outboundBuffer.length>0;){const{socket:t}=e;if(t.readyState!==O.OPEN)return!1;const n=this.outboundBuffer;this.outboundBuffer=[],e.activeBatch=n,e.nextIndex=0,e.remainderHandled=!1;for(let s=0;s<n.length;s++){if(!this.isOutboundFlushCurrent(e))return!1;const i=n[s],{cmd:r,payload:u}=i,o=this.outboundEventId(r,u);if(o&&this.blockedOutputEvents.has(o)){this.packetLog?.logOutboundPacket(r,0,u,"dropped"),e.nextIndex=s+1,e.currentWritePending=!1,e.currentWriteOutcome=null;continue}const f=++this.seq,l={cmd:r,seq:f,payload:u};e.nextIndex=s,e.currentWritePending=!0,e.currentWriteOutcome=null,o&&this.isAckRequiredOutput(r,u)&&this.registerPendingAcceptedOutput(f,o,i,t,e.connectionGeneration);const d=await new Promise(_=>{let E=!1;const c=h=>{E||(E=!0,clearTimeout(a),e.currentWriteOutcome=h,_(h))},a=setTimeout(()=>c("uncertain"),p.OUTBOUND_WRITE_CALLBACK_TIMEOUT_MS);a.unref?.(),e.cancelCurrentWrite=()=>c("canceled");try{this.trackOutboundEventCorrelation(r,f,u),t.send(JSON.stringify(l),h=>{c(h?"retry":"written")})}catch{c("retry")}});if(e.cancelCurrentWrite=null,!this.isOutboundFlushCurrent(e))return!1;if(d!=="written"||t.readyState!==O.OPEN){d==="retry"&&this.removeSeqFromEventCorrelation(f),this.settlePendingAcceptedOutput(f,"retry",!1);const _=this.retainOutboundFlushRemainder(e,d,`auth flush write failed for ${r} seq=${f} outcome=${d}`);return this.emitClientError(new Error(`outbound buffer flush failed at cmd=${r} seq=${f} outcome=${d}; ${_} packet(s) retained`)),!1}this.trackNonReplayableOutput(t,r,u,f),e.currentWritePending=!1,e.currentWriteOutcome="written",e.nextIndex=s+1}e.activeBatch=null,e.currentWritePending=!1,e.currentWriteOutcome=null,e.remainderHandled=!0}return this.isOutboundFlushCurrent(e)}retainOutboundFlushRemainder(e,t,n){if(e.remainderHandled||!e.activeBatch)return 0;e.remainderHandled=!0;const s=e.activeBatch,i=Math.min(e.nextIndex,s.length);let r=i;if(e.currentWritePending&&i<s.length){const o=s[i],f=this.outboundEventId(o.cmd,o.payload);t!=="retry"&&this.isNonReplayableOutput(o.cmd,o.payload)&&f&&(this.markOutputIntegrityFailed(f,n),r=i+1)}let u=0;for(const o of s.slice(r))this.bufferOutboundEntry(o)&&u++;return e.activeBatch=null,e.nextIndex=s.length,e.currentWritePending=!1,u}cancelOutboundFlush(e,t,n){if(e.finalized){this.outboundFlushInFlight===e&&(this.outboundFlushInFlight=null);return}e.finalized=!0,this.outboundFlushInFlight===e&&(this.outboundFlushInFlight=null),t==="connection_lost"?this.retainOutboundFlushRemainder(e,e.currentWriteOutcome,`connection lost during auth flush: ${n}`):(e.remainderHandled=!0,e.activeBatch=null,e.currentWritePending=!1);const s=e.cancelCurrentWrite;e.cancelCurrentWrite=null,s?.()}reconnectAfterOutboundFlushFailure(e,t){this.agentDeleted||!this.isConnectionCurrent(e,t)||this.reconnectAfterOutboundWriteFailure(e,"outbound flush failed")}reconnectAfterOutboundWriteFailure(e,t){if(this.agentDeleted||this.ws!==e)return;const n=this.connectionGeneration;this.connected=!1,this.negotiatedCapabilities.clear(),this.stopHeartbeat(),this.clearTerminalRetryTimers(),this.clearStopResultRetryTimers(),this.rejectAllPendingRequests(t);const s=this.outboundFlushInFlight;s&&s.socket===e&&s.connectionGeneration===n&&this.cancelOutboundFlush(s,"connection_lost",t),this.failNonReplayableOutputsForSocket(e,t),this.abortPendingOutputWrites(!0,e,n,t),this.abortPendingAcceptedOutputs(!0,e,n),this.clearAllEventCorrelations(),this.connectionGeneration++;try{e.close(1011,t)}catch{}this.ws===e&&(this.ws=null),this.attemptReconnect()}handleInvokeResult(e){const t=this.pendingInvokes.get(e.invoke_id);t&&(this.pendingInvokes.delete(e.invoke_id),clearTimeout(t.timer),e.code===0?t.resolve(e.data??null):t.reject(new Error(`agent_invoke error code=${e.code}: ${e.msg??""}`)))}rejectAllPendingInvokes(e){for(const[,t]of this.pendingInvokes)clearTimeout(t.timer),t.reject(new Error(`agent_invoke canceled: ${e}`));this.pendingInvokes.clear()}rejectAllPendingRequests(e){for(const[,t]of this.pendingRequests)clearTimeout(t.timer),t.reject(new Error(`request canceled: ${e}`));this.pendingRequests.clear()}cleanupSocket(e=this.ws,t=this.connectionGeneration){if(!(!e||!this.isConnectionCurrent(e,t)))try{e.close()}catch{}}startHeartbeat(){this.stopHeartbeat(),this.heartbeatFailures=0,this.heartbeatTimer=setInterval(()=>{const e=this.ws,t=this.connectionGeneration;!this.connected||!e||this.request("ping",{ts:Date.now()},{expected:["pong"],timeoutMs:5e3}).then(()=>{this.isConnectionCurrent(e,t)&&(this.heartbeatFailures=0)}).catch(()=>{!this.connected||!this.isConnectionCurrent(e,t)||(this.heartbeatFailures++,!(this.heartbeatFailures<p.HEARTBEAT_MAX_FAILURES)&&(this.cleanupSocket(e,t),this.attemptReconnect()))})},this.heartbeatSec*1e3)}stopHeartbeat(){this.heartbeatTimer&&(clearInterval(this.heartbeatTimer),this.heartbeatTimer=null)}}export{p as AibotClient,x as withRequiredCapabilities};
|
|
1
|
+
import{EventEmitter as A}from"node:events";import{randomUUID as T}from"node:crypto";import $ from"node:os";import y from"ws";import{log as m}from"../log/index.js";import{getMachineName as S}from"../util/index.js";import{detectTailnetIPv4 as I,ensureServerAndGetPort as P,getFileServerHttpsPort as F}from"../files/file-serve.js";import{TerminalOutbox as M}from"../persistence/terminal-outbox.js";import{TerminalCommitTokenStore as q}from"../persistence/terminal-commit-token-store.js";import{StopResultOutbox as B}from"../persistence/stop-result-outbox.js";import{AUTH_CODE_AGENT_DELETED as w,KICKED_REASON_AGENT_DELETED as R,AgentDeletedError as C,RequestTimeoutError as N}from"./errors.js";function D(E){let e="",t=0,n=!1,s=!1;for(;t<E.length;){const i=E[t];if(n){e+=i,s?s=!1:i==="\\"?s=!0:i==='"'&&(n=!1),t++;continue}if(i==='"'){n=!0,e+=i,t++;continue}if(i==="-"||i>="0"&&i<="9"){const r=t;i==="-"&&t++;const u=t;for(;t<E.length&&E[t]>="0"&&E[t]<="9";)t++;let o=!1;if(E[t]===".")for(o=!0,t++;t<E.length&&E[t]>="0"&&E[t]<="9";)t++;if(E[t]==="e"||E[t]==="E")for(o=!0,t++,(E[t]==="+"||E[t]==="-")&&t++;t<E.length&&E[t]>="0"&&E[t]<="9";)t++;const f=E.slice(r,t),l=t-u;e+=!o&&l>=16?`"${f}"`:f;continue}e+=i,t++}return e}function x(E){const e=[...E??["stream_chunk","local_action_v1","agent_invoke"]];return e.includes("agent_invoke")||e.push("agent_invoke"),e.includes("event_result_ack")||e.push("event_result_ack"),e.includes("terminal_commit_v1")||e.push("terminal_commit_v1"),e.includes("audit_replay_v2")||e.push("audit_replay_v2"),e.includes("session_send_quote_v1")||e.push("session_send_quote_v1"),e}function W(E){const e=[...E??["exec_approve","exec_reject"]];return e.includes("apply_relay_state")||e.push("apply_relay_state"),e}const U="aibot-agent-api-v1",L=1;class p extends A{static DROPPABLE_COMMANDS=new Set(["update_binding_card"]);static BUFFER_OVERFLOW_RETAIN_COMMANDS=new Set(["event_result","codex_event","client_stream_chunk","send_msg","audit_state"]);static MAX_OUTBOUND_BUFFER_SIZE=1e3;static MAX_OUTBOUND_BUFFER_HARD_SIZE=1200;static BACKPRESSURE_THRESHOLD=64*1024;static TERMINAL_RETRY_DELAY_MS=15e3;static MAX_TOKENIZED_TERMINAL_REJECTIONS=5;static EVENT_CORRELATION_TTL_MS=5*6e4;static MAX_EVENT_CORRELATIONS=1024;static MAX_BLOCKED_OUTPUT_EVENTS=4096;static MAX_COMMITTED_TERMINAL_EVENTS=4096;static OUTBOUND_WRITE_CALLBACK_TIMEOUT_MS=1e4;static MIN_REQUIRED_OUTPUT_ACK_TIMEOUT_MS=2e4;static ORDERED_OUTPUT_COMMANDS=new Set(["client_stream_chunk","send_msg","codex_event"]);ws=null;seq=0;heartbeatTimer=null;heartbeatSec=30;heartbeatFailures=0;static HEARTBEAT_MAX_FAILURES=2;connected=!1;reconnecting=!1;reconnectAttempts=0;reconnectRunGeneration=0;everConnected=!1;agentDeleted=!1;config;packetLog;pendingInvokes=new Map;eventCorrelations=new Map;clientMsgEventMap=new Map;eventCorrelationCleanupTimer=null;lastCorrelationPruneAt=0;pendingRequests=new Map;outboundBuffer=[];outboundEntryOrders=new WeakMap;nextOutboundEntryOrder=0;connectionGeneration=0;nextOutboundFlushGeneration=0;outboundFlushInFlight=null;pendingOutputWrites=new Map;pendingOutputSeqsByEvent=new Map;pendingAcceptedOutputs=new Map;pendingAcceptedOutputSeqsByEvent=new Map;pendingAcceptedSeqsByClientMsgId=new Map;nonReplayableOutputEventsBySocket=new Map;provisionalRespondedTerminals=new Map;outputIntegrityFailed=new Map;blockedOutputEvents=new Map;committedTerminalEvents=new Set;ackPolicy=null;negotiatedCapabilities=new Set;terminalOutbox;terminalCommitTokens;stopResultOutbox;terminalInFlight=new Set;terminalRetryTimers=new Map;terminalReplayAfterFlight=new Set;tokenizedTerminalRejections=new Map;stopResultInFlight=new Set;stopResultRetryTimers=new Map;constructor(e,t){super(),this.packetLog=t?.packetLog??null,this.config={url:e.url,agentId:e.agentId,apiKey:e.apiKey,clientType:e.clientType,clientVersion:e.clientVersion??"",adapterHint:e.adapterHint??"",capabilities:x(e.capabilities),localActions:W(e.localActions),skills:e.skills,librarySkills:e.librarySkills,sharedOwnerId:e.sharedOwnerId,concurrency:e.concurrency,terminalOutboxPath:e.terminalOutboxPath,terminalCommitTokenStorePath:e.terminalCommitTokenStorePath,stopResultOutboxPath:e.stopResultOutboxPath},this.terminalOutbox=new M(this.config.terminalOutboxPath),this.terminalCommitTokens=new q(this.config.terminalCommitTokenStorePath??(this.config.terminalOutboxPath?`${this.config.terminalOutboxPath}.tokens`:void 0)),this.stopResultOutbox=new B(this.config.stopResultOutboxPath??(this.config.terminalOutboxPath?`${this.config.terminalOutboxPath}.stops`:void 0));for(const n of this.terminalOutbox.listPending())n.payload.code==="agent_output_integrity_failed"&&this.blockEventOutput(n.payload.event_id)}get isConnected(){return this.connected}async connect(){this.negotiatedCapabilities.clear();const e=this.ws,t=this.connectionGeneration;if(this.pendingRequests.size>0&&this.rejectAllPendingRequests("connection generation replaced"),e){this.connected=!1,this.stopHeartbeat(),this.clearTerminalRetryTimers(),this.clearStopResultRetryTimers();const o=this.outboundFlushInFlight;o&&o.socket===e&&o.connectionGeneration===t&&this.cancelOutboundFlush(o,"connection_lost","connection generation replaced"),this.failNonReplayableOutputsForSocket(e,"connection generation replaced"),this.abortPendingOutputWrites(!0,e,t,"connection generation replaced"),this.abortPendingAcceptedOutputs(!0,e,t),this.clearAllEventCorrelations(),this.connectionGeneration++;try{e.close(1012,"connection generation replaced")}catch{}this.ws===e&&(this.ws=null)}else this.outboundFlushInFlight&&this.cancelOutboundFlush(this.outboundFlushInFlight,"discard","detached connection generation replaced");const n=++this.connectionGeneration;let s,i,r;const u=(async()=>{try{if(s=await I(),s!==void 0)try{i=await P(s);const o=F();o>0&&(r=o)}catch(o){m.warn("aibot",`file server pre-start failed: ${o}`)}}catch(o){m.warn("aibot",`tailnet detect failed: ${o}`)}})();return new Promise((o,f)=>{const l=new y(this.config.url);this.ws=l;const d=setTimeout(()=>{if(!this.isConnectionCurrent(l,n))return;const c=this.pendingRequests.get(_);c&&(this.pendingRequests.delete(_),clearTimeout(c.timer),c.reject(new Error("Auth timeout: no auth_ack received within 15s"))),this.cleanupSocket(l,n)},15e3),_=++this.seq,b=setTimeout(()=>{if(!this.isConnectionCurrent(l,n))return;const c=this.pendingRequests.get(_);c&&(this.pendingRequests.delete(_),c.reject(new Error("Auth request timeout"))),this.cleanupSocket(l,n)},15e3);this.pendingRequests.set(_,{expected:["auth_ack"],resolve:c=>{clearTimeout(d);const a=c.payload;if(a.code===0){this.negotiatedCapabilities=new Set(Array.isArray(a.supported_capabilities)?a.supported_capabilities:[]),this.connected=!0,this.everConnected=!0,this.reconnectAttempts=0,a.heartbeat_sec&&(this.heartbeatSec=a.heartbeat_sec),a.ack_policy&&(this.ackPolicy=a.ack_policy,m.info("aibot",`ack_policy received: push_ack_timeout_ms=${a.ack_policy.push_ack_timeout_ms??"default"} max_retries=${a.ack_policy.max_retries??"default"} timeout_action=${a.ack_policy.timeout_action??"default"}`)),this.startHeartbeat();const h=this.flushOutboundBuffer(l,n);this.emit("auth",a),o(a),h.then(g=>{this.isConnectionCurrent(l,n)&&(g?(this.replayTerminalOutbox(),this.replayStopResultOutbox()):this.reconnectAfterOutboundFlushFailure(l,n))})}else a.code===w?(this.agentDeleted=!0,f(new C(`Agent deleted: code=${a.code} msg=${a.msg}`))):f(new Error(`Auth failed: code=${a.code} msg=${a.msg}`))},reject:c=>{clearTimeout(d),f(c)},timer:b}),l.on("open",async()=>{if(await u,!this.isConnectionCurrent(l,n))return;const c={agent_id:this.config.agentId,api_key:this.config.apiKey,client_type:this.config.clientType,protocol_version:U,contract_version:L,capabilities:this.config.capabilities??[],local_actions:this.config.localActions,skills:this.config.skills,library_skills:this.config.librarySkills};this.config.sharedOwnerId&&(c.shared_owner_id=this.config.sharedOwnerId),this.config.clientVersion&&(c.client="grix-connector",c.client_version=this.config.clientVersion,c.host_type=this.config.clientType,c.host_version=this.config.clientVersion),this.config.adapterHint&&(c.adapter_hint=this.config.adapterHint),c.host_meta={hostname:S(),platform:$.platform(),arch:$.arch(),os_release:$.release(),...s!==void 0&&{tailnet_ip:s},...i!==void 0&&i>0&&{file_server_port:i},...r!==void 0&&r>0&&{file_server_https_port:r}},this.config.concurrency&&(c.concurrency=this.config.concurrency),this.sendPacket("auth",c,_)}),l.on("message",c=>{if(!this.isConnectionCurrent(l,n))return;let a;try{a=JSON.parse(D(c.toString()))}catch{return}try{this.handlePacket(a)}catch(h){this.emitClientError(new Error(`handlePacket error: ${h}`))}}),l.on("close",(c,a)=>{if(!this.isConnectionCurrent(l,n))return;const h=this.everConnected&&!this.agentDeleted;this.connected=!1,this.negotiatedCapabilities.clear(),this.stopHeartbeat(),this.clearTerminalRetryTimers(),this.clearStopResultRetryTimers(),this.rejectAllPendingRequests("websocket closed");const g=`websocket closed code=${c} reason=${a.toString()||"<none>"}`,O=this.outboundFlushInFlight;O&&O.socket===l&&O.connectionGeneration===n&&this.cancelOutboundFlush(O,h?"connection_lost":"discard",g),h?this.failNonReplayableOutputsForSocket(l,g):this.nonReplayableOutputEventsBySocket.delete(l),this.abortPendingOutputWrites(h,l,n,h?g:void 0),this.abortPendingAcceptedOutputs(h,l,n),this.clearAllEventCorrelations(),this.ws===l&&(this.ws=null),this.connectionGeneration++,this.emit("close",c,a.toString());const k=h;m.info("aibot",`ws closed agent=${this.config.clientType}:${this.config.agentId} code=${c} reason=${a.toString()||"<none>"} everConnected=${this.everConnected} reconnecting=${this.reconnecting} agentDeleted=${this.agentDeleted} willReconnect=${k}`),k&&this.attemptReconnect()}),l.on("error",c=>{if(this.isConnectionCurrent(l,n)&&(this.emitClientError(c instanceof Error?c:new Error(String(c))),!this.connected)){const a=this.pendingRequests.get(_);a&&(this.pendingRequests.delete(_),clearTimeout(a.timer),a.reject(c instanceof Error?c:new Error(String(c))))}})})}handlePacket(e){if(this.packetLog?.logInboundPacket(e.cmd,e.seq,e.payload),this.handleCorrelatedSendResponse(e),e.seq>0&&this.pendingRequests.has(e.seq)){const t=this.pendingRequests.get(e.seq);this.pendingRequests.delete(e.seq),clearTimeout(t.timer),t.expected.includes(e.cmd)?t.resolve(e):t.reject(new Error(`unexpected response: got ${e.cmd}, expected ${t.expected.join("/")}`));return}switch(e.cmd){case"auth_ack":break;case"ping":{this.sendPacket("pong",e.payload??{});break}case"event_msg":{const t=e.payload;if(!this.captureInboundTerminalCommitToken(t.event_id,t.terminal_commit_token))break;this.emit("event",t);break}case"local_action":{this.emit("localAction",e.payload);break}case"event_stop":{const t=e.payload;if(!this.captureInboundTerminalCommitToken(t.event_id,t.terminal_commit_token))break;this.emit("stop",t);break}case"event_revoke":{this.emit("revoke",e.payload);break}case"event_edit":{this.emit("edit",e.payload);break}case"event_cancel":{this.emit("eventCancel",e.payload);break}case"queue_clear":{this.emit("queueClear",e.payload);break}case"queue_reorder":{this.emit("queueReorder",e.payload);break}case"queue_snapshot_query":{this.emit("queueSnapshotQuery",e.payload);break}case"event_hold":{this.emit("eventHold",e.payload);break}case"queue_edit":{this.emit("queueEdit",e.payload);break}case"control_share_set":{this.emit("shareSet",e.payload);break}case"agent_profile_push":{this.emit("profilePush",e.payload);break}case"skill_sync":{this.emit("skillSync",e.payload);break}case"kicked":{const t=e.payload;this.emit("kicked",t),this.connected=!1,this.negotiatedCapabilities.clear(),this.stopHeartbeat(),this.clearTerminalRetryTimers(),this.clearStopResultRetryTimers(),this.rejectAllPendingRequests("kicked");const n=this.ws,s=this.connectionGeneration,i=this.outboundFlushInFlight;if(i&&n&&i.socket===n&&i.connectionGeneration===this.connectionGeneration&&this.cancelOutboundFlush(i,t?.reason===R?"discard":"connection_lost",`kicked reason=${t?.reason??"<none>"}`),t?.reason!==R&&n&&this.failNonReplayableOutputsForSocket(n,`kicked reason=${t?.reason??"<none>"}`),this.abortPendingOutputWrites(t?.reason!==R,n??void 0,s,t?.reason!==R?`kicked reason=${t?.reason??"<none>"}`:void 0),this.abortPendingAcceptedOutputs(t?.reason!==R,n??void 0,s),this.clearAllEventCorrelations(),this.connectionGeneration++,t?.reason===R){if(this.agentDeleted=!0,this.reconnecting=!1,this.outboundBuffer.length=0,this.outputIntegrityFailed.clear(),this.nonReplayableOutputEventsBySocket.clear(),this.ws){try{this.ws.close(4001,"kicked")}catch{}this.ws=null}m.error("aibot",`kicked: agent deleted on platform agent=${this.config.clientType}:${this.config.agentId}, reconnect disabled`),this.emit("agentDeleted",{source:"kicked",reason:t.reason});break}if(this.reconnectAttempts=Math.max(this.reconnectAttempts,3),this.ws){try{this.ws.close(4001,"kicked")}catch{}this.ws===n&&(this.ws=null)}this.attemptReconnect();break}case"error":{const t=e.payload,n=[t.ref_cmd?`ref_cmd=${t.ref_cmd}`:"",t.ref_id?`ref_id=${t.ref_id}`:""].filter(Boolean).join(" ");this.emitClientError(new Error(`Server error: code=${t.code} msg=${t.msg}${n?` ${n}`:""}`));break}case"agent_invoke_result":{this.handleInvokeResult(e.payload);break}case"mcp_frame":{const t=e.payload;this.emit("mcpFrame",t.session_id??"",t.frame??null);break}case"send_ack":break;case"send_nack":break;case"local_action_ack":break;default:break}}captureInboundTerminalCommitToken(e,t){const n=t?.trim();if(!n)return!0;if(!e?.trim())return this.rejectInboundTerminalCommitToken("tokenized event is missing event_id"),!1;if(!this.negotiatedCapabilities.has("terminal_commit_v1"))return this.rejectInboundTerminalCommitToken(`tokenized event received without terminal_commit_v1 negotiation event=${e}`),!1;try{return this.terminalCommitTokens.register(e,n),!0}catch(s){return this.rejectInboundTerminalCommitToken(`terminal commit token persist failed event=${e}: ${s instanceof Error?s.message:s}`),!1}}rejectInboundTerminalCommitToken(e){this.emitClientError(new Error(e));const t=this.ws;if(t)try{t.close(1011,"terminal commit token persistence failed")}catch{}}withTerminalCommitToken(e){const t=e.event_id.trim();if(!t)throw new Error("terminal event_result is missing event_id");const n=e.terminal_commit_token?.trim(),s=this.terminalCommitTokens.get(t);if(n&&s&&n!==s)throw new Error(`terminal commit token mismatch for event=${t}`);n&&!s&&this.terminalCommitTokens.register(t,n);const i=s??n;return i?{...e,event_id:t,terminal_commit_token:i}:{...e,event_id:t}}sendEventAck(e){this.sendPacket("event_ack",e)||m.warn("aibot",`event_ack NOT sent (ws not open) event=${e.event_id} ws=${this.ws?`state=${this.ws.readyState}`:"null"}`)}sendStreamChunk(e){!e.delta_content&&!e.is_finish&&(m.warn("aibot",`stream_chunk delta_content empty, patched to newline event=${e.event_id??""} session=${e.session_id} chunk_seq=${e.chunk_seq} is_finish=${e.is_finish}`),e={...e,delta_content:`
|
|
2
|
+
`}),this.sendPacket("client_stream_chunk",e)}sendMsg(e){const t={...e,client_msg_id:e.client_msg_id?.trim()||T()};this.sendPacket("send_msg",t)||m.warn("aibot",`send_msg NOT sent (ws not open) event=${t.event_id??""} session=${t.session_id??""} ws=${this.ws?`state=${this.ws.readyState}`:"null"}`)}editMsg(e){this.sendPacket("edit_msg",e)}sendEventResult(e){try{e=this.withTerminalCommitToken(e)}catch(o){this.emitClientError(new Error(`event_result token validation failed: event=${e.event_id}: ${o instanceof Error?o.message:o}`));return}const t=this.terminalOutbox.get(e.event_id);let n,s=this.outputIntegrityFailed.get(e.event_id);const i=t;if(!s&&i?.payload.code==="agent_output_integrity_failed"&&(s={reason:i.payload.msg??"required agent output was discarded before delivery",terminalObserved:!1,terminalSettled:!1},this.outputIntegrityFailed.set(e.event_id,s)),s){if(s.terminalObserved=!0,this.provisionalRespondedTerminals.delete(e.event_id),i&&i.payload.code!=="agent_output_integrity_failed"&&!this.canReplaceUnsentOutputGuard(i)){this.scheduleTerminalDelivery(e.event_id,{ignoreNotBefore:!0});return}if(i?.payload.code==="agent_output_integrity_failed"){this.scheduleTerminalDelivery(e.event_id,{ignoreNotBefore:!0});return}if(s.terminalSettled||this.committedTerminalEvents.has(e.event_id)&&!i){this.outputIntegrityFailed.delete(e.event_id);return}try{n=this.terminalOutbox.enqueue(this.makeOutputIntegrityFailurePayload(e.event_id,s.reason))}catch(o){this.emitClientError(new Error(`output-integrity terminal persist failed: event=${e.event_id}: ${o instanceof Error?o.message:o}`));return}this.scheduleTerminalDelivery(n.payload.event_id,{ignoreNotBefore:!0});return}if(this.committedTerminalEvents.has(e.event_id)&&!t){m.info("aibot",`ignored duplicate terminal after committed ACK event=${e.event_id} status=${e.status}`);return}if(t){if(!(this.canReplaceUnsentOutputGuard(t)&&(e.status==="canceled"||e.status==="failed"))){m.info("aibot",`preserving first durable terminal event=${e.event_id} existing_status=${t.payload.status} ignored_status=${e.status}`),this.scheduleTerminalDelivery(e.event_id,{ignoreNotBefore:!0});return}this.provisionalRespondedTerminals.delete(e.event_id)}if(e.status==="responded"&&this.hasNonReplayableOutput(e.event_id)&&!this.hasPendingOutputAcceptanceFence(e.event_id)){this.markOutputIntegrityFailed(e.event_id,"terminal requested before a complete output acceptance fence");const o=this.outputIntegrityFailed.get(e.event_id);o&&(o.terminalObserved=!0);return}const r=e.status==="responded"&&this.hasUnconfirmedEventOutput(e.event_id),u=r?{...e,status:"failed",code:"agent_output_unconfirmed",msg:"required agent output was not durably confirmed before connector restart",updated_at:Date.now()}:e;r?this.provisionalRespondedTerminals.set(e.event_id,{...e}):this.provisionalRespondedTerminals.delete(e.event_id);try{n=this.terminalOutbox.enqueue(u)}catch(o){this.provisionalRespondedTerminals.delete(e.event_id),this.emitClientError(new Error(`event_result outbox persist failed: event=${e.event_id} status=${e.status}: ${o instanceof Error?o.message:o}`));return}this.scheduleTerminalDelivery(n.payload.event_id,{ignoreNotBefore:!0})}sendLocalActionResult(e){this.sendPacket("local_action_result",e)}sendEventStopAck(e){this.sendPacket("event_stop_ack",e)}sendEventStopResult(e){const t=e.event_id?.trim(),n=e.terminal_commit_token?.trim(),s=t?this.terminalCommitTokens.get(t):void 0;if(n&&s&&n!==s){this.emitClientError(new Error(`event_stop_result token mismatch event=${t??""}`));return}const i=s??n;if(t&&i&&(e.status==="stopped"||e.status==="already_finished")){try{const r=this.stopResultOutbox.enqueue({...e,event_id:t,terminal_commit_token:i});this.scheduleStopResultDelivery(r,{ignoreNotBefore:!0})}catch(r){this.emitClientError(new Error(`event_stop_result outbox persist failed: event=${t} stop=${e.stop_id}: ${r instanceof Error?r.message:r}`))}return}this.sendPacket("event_stop_result",e)}sendSessionActivitySet(e){this.sendPacket("session_activity_set",e)}sendCodexEvent(e){this.sendPacket("codex_event",e)}sendUpdateBindingCard(e){this.sendPacket("update_binding_card",e)}sendAuditState(e){this.sendPacket("audit_state",e)}sendSkillsUpdate(e){this.sendPacket("agent_skills_update",e)}sendPing(){this.sendPacket("ping",{})}sendEventState(e){this.sendPacket("event_state",e)}sendEventCancelResult(e){this.sendPacket("event_cancel_result",e)}sendQueueClearResult(e){this.sendPacket("queue_clear_result",e)}sendQueueReorderResult(e){this.sendPacket("queue_reorder_result",e)}sendEventHoldResult(e){this.sendPacket("event_hold_result",e)}sendQueueEditResult(e){this.sendPacket("queue_edit_result",e)}sendQueueSnapshot(e){this.sendPacket("queue_snapshot",e)}agentInvoke(e,t,n=15e3){return new Promise((s,i)=>{const r=T(),u=Math.max(1e3,Math.min(n,6e4)),o=setTimeout(()=>{this.pendingInvokes.delete(r),i(new Error(`agent_invoke timeout: ${e}`))},u);this.pendingInvokes.set(r,{resolve:s,reject:i,timer:o}),this.sendPacket("agent_invoke",{invoke_id:r,action:e,params:t,timeout_ms:u})})}sendMcpFrame(e,t){this.sendPacket("mcp_frame",{session_id:e,frame:t})}async request(e,t,n){const s=this.outboundFlushInFlight;if(s){if(!await s.promise)throw new Error(`outbound flush failed before request: ${e}`);if(!this.isConnectionCurrent(s.socket,s.connectionGeneration))throw new Error(`connection changed while waiting for outbound flush: ${e}`)}if(!this.connected||!this.ws||this.ws.readyState!==y.OPEN)throw new Error(`send failed: ${e} (websocket not connected)`);return new Promise((i,r)=>{const u=++this.seq,o=setTimeout(()=>{this.pendingRequests.delete(u),r(new N(`request timeout: ${e} (expected ${n.expected.join("/")})`))},n.timeoutMs);this.pendingRequests.set(u,{expected:n.expected,resolve:i,reject:r,timer:o}),this.sendPacket(e,t,u)||(this.pendingRequests.delete(u),clearTimeout(o),r(new Error(`send failed: ${e}`)))})}async sendStreamChunkRequest(e,t=2e4){return this.request("client_stream_chunk",e,{expected:["send_ack","send_nack","error"],timeoutMs:t})}async relayCredentialRequest(e,t=15e3){return this.request("relay_credential_request",e,{expected:["relay_credential_result","error"],timeoutMs:t})}async relayStateSyncRequest(e,t=15e3){return this.request("relay_state_sync_request",e,{expected:["relay_state_sync_result","error"],timeoutMs:t})}sendRelayStateReport(e){this.sendPacket("relay_state_report",e)}async sendText(e,t=2e4){return this.request("send_msg",{msg_type:1,...e,client_msg_id:e.client_msg_id?.trim()||T()},{expected:["send_ack","send_nack","error"],timeoutMs:t})}async sendMedia(e,t=2e4){return this.request("send_msg",{...e,msg_type:2,client_msg_id:e.client_msg_id?.trim()||T()},{expected:["send_ack","send_nack","error"],timeoutMs:t})}async editMessage(e,t=2e4){return this.request("edit_msg",e,{expected:["send_ack","send_nack","error"],timeoutMs:t})}async deleteMessage(e,t,n=2e4){return this.request("delete_msg",{session_id:e,msg_id:t},{expected:["send_ack","send_nack","error"],timeoutMs:n})}async sendEventResultRequest(e,t=5e3){return this.request("event_result",e,{expected:["send_ack","send_nack","error"],timeoutMs:t})}disconnect(){m.info("aibot",`disconnect() agent=${this.config.clientType}:${this.config.agentId} wasConnected=${this.connected} reconnecting=${this.reconnecting} reconnectAttempts=${this.reconnectAttempts}`),this.connected=!1,this.negotiatedCapabilities.clear(),this.everConnected=!1,this.reconnecting=!1,this.reconnectRunGeneration++,this.reconnectAttempts=0,this.stopHeartbeat(),this.clearTerminalRetryTimers(),this.clearStopResultRetryTimers();const e=this.ws,t=this.connectionGeneration,n=this.outboundFlushInFlight;n&&this.cancelOutboundFlush(n,"discard","explicit disconnect"),this.connectionGeneration++,this.rejectAllPendingInvokes("disconnect"),this.rejectAllPendingRequests("disconnect"),this.abortPendingOutputWrites(!1,e??void 0,t),this.abortPendingAcceptedOutputs(!1,e??void 0,t),this.clearAllEventCorrelations(),this.outboundBuffer.length=0,this.provisionalRespondedTerminals.clear(),this.outputIntegrityFailed.clear(),this.nonReplayableOutputEventsBySocket.clear(),this.ws&&(this.ws.close(1e3,"client disconnect"),this.ws=null)}static MAX_CONSECUTIVE_AUTH_FAILURES=5;async attemptReconnect(){if(this.reconnecting||this.agentDeleted)return;this.reconnecting=!0;const e=++this.reconnectRunGeneration;m.info("aibot",`attemptReconnect start agent=${this.config.clientType}:${this.config.agentId} fromAttempts=${this.reconnectAttempts}`),this.emit("disconnected");let t=0;for(;this.reconnecting&&this.reconnectRunGeneration===e;){const n=Math.min(1e3*2**this.reconnectAttempts,3e4),s=Math.floor(n*.2*Math.random());if(this.reconnectAttempts++,await new Promise(u=>setTimeout(u,n+s)),!this.reconnecting||this.reconnectRunGeneration!==e)return;let i=null,r=this.connectionGeneration;try{const u=this.connect();if(i=this.ws,r=this.connectionGeneration,await u,this.reconnectRunGeneration!==e)return;if(!i||!this.isConnectionCurrent(i,r)){if(this.connected&&this.ws){this.reconnecting=!1;return}continue}const o=this.reconnectAttempts;this.reconnectAttempts=0,this.reconnecting=!1,m.info("aibot",`reconnect succeeded agent=${this.config.clientType}:${this.config.agentId} attempt=${o}`);return}catch(u){if(this.reconnectRunGeneration!==e)return;if(i&&!this.isConnectionCurrent(i,r)&&this.connected&&this.ws){this.reconnecting=!1;return}if(i&&this.isConnectionCurrent(i,r)){this.connectionGeneration++;try{i.close()}catch{}this.ws===i&&(this.ws=null)}const o=u instanceof Error?u.message:String(u);if(m.warn("aibot",`reconnect failed agent=${this.config.clientType}:${this.config.agentId} attempt=${this.reconnectAttempts} err=${o}`),u instanceof C){this.agentDeleted=!0,this.reconnecting=!1,m.error("aibot",`reconnect aborted: agent deleted on platform agent=${this.config.clientType}:${this.config.agentId}`),this.emit("agentDeleted",{source:"auth_ack",code:w});return}if(/Auth failed/i.test(o)){if(t++,t>=p.MAX_CONSECUTIVE_AUTH_FAILURES){this.reconnecting=!1,m.error("aibot",`reconnect giving up after ${t} consecutive auth failures agent=${this.config.clientType}:${this.config.agentId}`);return}}else t=0}}}sendPacket(e,t,n){const s=this.outboundEventId(e,t);if(s&&this.blockedOutputEvents.has(s))return this.packetLog?.logOutboundPacket(e,n??0,t,"dropped"),m.warn("aibot",`blocked late output after irreversible event failure event=${s} cmd=${e}`),!1;const i=n===void 0?this.createOutboundEntry(e,t):void 0;if(i&&this.outboundFlushInFlight&&!this.outboundFlushInFlight.finalized&&this.isConnectionCurrent(this.outboundFlushInFlight.socket,this.outboundFlushInFlight.connectionGeneration)&&!p.DROPPABLE_COMMANDS.has(e))return this.bufferOutboundEntry(i),!1;if(this.ws&&this.ws.readyState===y.OPEN&&(e==="auth"||this.connected)){const u=this.ws.bufferedAmount>p.BACKPRESSURE_THRESHOLD;if(!u||!p.DROPPABLE_COMMANDS.has(e)){if(u&&p.DROPPABLE_COMMANDS.has(e))return!1;const o=n??++this.seq,f={cmd:e,seq:o,payload:t};this.packetLog?.logOutboundPacket(e,o,t,"sent");const l=this.ws,d=this.connectionGeneration,_=s;_&&this.trackOutboundEventCorrelation(e,o,t),i&&_&&(this.registerPendingOutputWrite(o,_,i,l,d),this.isAckRequiredOutput(e,t)&&this.registerPendingAcceptedOutput(o,_,i,l,d));let b=!1;try{const c=l.readyState,a=l.bufferedAmount;let h=!1;return l.send(JSON.stringify(f),g=>{if(g&&!h&&(b=!0),e==="event_result"){const v=t;g?m.warn("aibot",`event_result ws send callback failed event=${v.event_id??""} status=${v.status??""} seq=${o} readyState=${c} bufferedAmount=${a} err=${g.message}`):m.info("aibot",`event_result ws send callback ok event=${v.event_id??""} status=${v.status??""} seq=${o} readyState=${c} bufferedAmount=${a}`)}else if(e==="client_stream_chunk"){const v=t;g?m.warn("aibot",`stream_chunk ws send failed event=${v.event_id??""} session=${v.session_id??""} seq=${o} chunk_seq=${v.chunk_seq??""} is_finish=${v.is_finish??""} readyState=${c} bufferedAmount=${a} err=${g.message}`):m.info("aibot",`stream_chunk ws send ok event=${v.event_id??""} session=${v.session_id??""} seq=${o} chunk_seq=${v.chunk_seq??""} is_finish=${v.is_finish??""} client_msg_id=${v.client_msg_id??""} quoted_message_id=${v.quoted_message_id??""} readyState=${c} bufferedAmount=${a}`)}else if(e==="event_ack"){const v=t;g?m.warn("aibot",`event_ack ws send failed event=${v.event_id??""} seq=${o} readyState=${c} bufferedAmount=${a} err=${g.message}`):m.info("aibot",`event_ack ws send ok event=${v.event_id??""} seq=${o} readyState=${c} bufferedAmount=${a}`)}else if(e==="send_msg"){const v=t;g?m.warn("aibot",`send_msg ws send failed event=${v.event_id??""} session=${v.session_id??""} seq=${o} readyState=${c} bufferedAmount=${a} err=${g.message}`):m.info("aibot",`send_msg ws send ok event=${v.event_id??""} session=${v.session_id??""} seq=${o} readyState=${c} bufferedAmount=${a}`)}else if(g){const v=t;m.warn("aibot",`${e} ws send failed seq=${o} session=${v.session_id??""} event=${v.event_id??""} client_msg_id=${v.client_msg_id??""} readyState=${c} bufferedAmount=${a} err=${g.message}`)}const O=this.pendingOutputWrites.get(o),k=!!(O&&O.socket===l&&O.connectionGeneration===d);if(!this.isConnectionCurrent(l,d)){k&&(this.settlePendingOutputWrite(o,!1,!1,l,d),this.settlePendingAcceptedOutput(o,"retry",!1,l,d));return}if(!(i&&_&&!k)){if(g)this.removeSeqFromEventCorrelation(o),_?(this.settlePendingOutputWrite(o,!1,!0,l,d),this.settlePendingAcceptedOutput(o,"retry",!1,l,d)):i&&this.bufferOutboundEntry(i),n!==void 0?this.rejectPendingRequestAfterWriteFailure(o,e,g):i&&this.reconnectAfterOutboundWriteFailure(l,`${e} callback failed`);else if(_){const v=k;this.settlePendingOutputWrite(o,!0,!1,l,d),v&&this.trackNonReplayableOutput(l,e,t,o)}}}),h=!0,!b}catch(c){return this.isConnectionCurrent(l,d)?(this.removeSeqFromEventCorrelation(o),_?(this.settlePendingOutputWrite(o,!1,!0,l,d),this.settlePendingAcceptedOutput(o,"retry",!1,l,d)):i&&this.bufferOutboundEntry(i),this.emitClientError(new Error(`sendPacket failed: ${c}`)),i&&this.reconnectAfterOutboundWriteFailure(l,`${e} send threw`),!1):(this.settlePendingOutputWrite(o,!1,!1,l,d),this.settlePendingAcceptedOutput(o,"retry",!1,l,d),!1)}}}if(p.DROPPABLE_COMMANDS.has(e))return this.packetLog?.logOutboundPacket(e,n??0,t,"dropped"),!1;if(n!==void 0)return this.packetLog?.logOutboundPacket(e,n,t,"dropped"),!1;const r=this.bufferOutboundEntry(i??this.createOutboundEntry(e,t));if(this.packetLog?.logOutboundPacket(e,n??0,t,r?"buffered":"dropped"),r&&e==="client_stream_chunk"){const u=t;m.info("aibot",`stream_chunk buffered (ws not open) event=${u.event_id??""} session=${u.session_id??""} chunk_seq=${u.chunk_seq??""} is_finish=${u.is_finish??""} ws=${this.ws?`state=${this.ws.readyState}`:"null"}`)}return!1}replayTerminalOutbox(){for(const e of this.terminalOutbox.listPending())this.scheduleTerminalDelivery(e.payload.event_id,{ignoreNotBefore:!0})}outboundEventId(e,t){if(!p.ORDERED_OUTPUT_COMMANDS.has(e)||!t||typeof t!="object")return"";const n=t.event_id;return typeof n=="string"?n.trim():""}blockEventOutput(e){const t=e.trim();if(t)for(this.blockedOutputEvents.delete(t),this.blockedOutputEvents.set(t,Date.now()),this.outboundBuffer=this.outboundBuffer.filter(n=>this.outboundEventId(n.cmd,n.payload)!==t);this.blockedOutputEvents.size>p.MAX_BLOCKED_OUTPUT_EVENTS;){const n=this.blockedOutputEvents.keys().next().value;if(!n)break;this.blockedOutputEvents.delete(n)}}isAckRequiredOutput(e,t){return e==="send_msg"?!0:e==="client_stream_chunk"&&!!t&&typeof t=="object"&&t.is_finish===!0}isNonReplayableOutput(e,t){return e==="codex_event"?!0:e==="client_stream_chunk"&&!!t&&typeof t=="object"&&t.is_finish!==!0}trackNonReplayableOutput(e,t,n,s){if(!this.isNonReplayableOutput(t,n))return;const i=this.outboundEventId(t,n);if(!i||this.outputIntegrityFailed.has(i)||this.blockedOutputEvents.has(i))return;let r=this.nonReplayableOutputEventsBySocket.get(e);r||(r=new Map,this.nonReplayableOutputEventsBySocket.set(e,r)),r.set(i,Math.max(r.get(i)??0,s))}clearNonReplayableOutputThrough(e,t,n){if(!e||n<=0)return;const s=this.nonReplayableOutputEventsBySocket.get(e);if(!s)return;const i=s.get(t);i===void 0||i>n||(s.delete(t),s.size===0&&this.nonReplayableOutputEventsBySocket.delete(e))}clearNonReplayableOutputEvent(e){for(const[t,n]of this.nonReplayableOutputEventsBySocket)n.delete(e),n.size===0&&this.nonReplayableOutputEventsBySocket.delete(t)}failNonReplayableOutputsForSocket(e,t){const n=this.nonReplayableOutputEventsBySocket.get(e);if(n){this.nonReplayableOutputEventsBySocket.delete(e);for(const s of n.keys())this.markOutputIntegrityFailed(s,`connection lost before a complete output acceptance fence: ${t}`)}}hasNonReplayableOutput(e){for(const t of this.nonReplayableOutputEventsBySocket.values())if(t.has(e))return!0;return!1}hasPendingOutputAcceptanceFence(e){return!!(this.pendingAcceptedOutputSeqsByEvent.get(e)?.size||this.outboundBuffer.some(t=>this.outboundEventId(t.cmd,t.payload)===e&&this.isAckRequiredOutput(t.cmd,t.payload)))}hasUnconfirmedEventOutput(e){return!!(this.pendingOutputSeqsByEvent.get(e)?.size||this.pendingAcceptedOutputSeqsByEvent.get(e)?.size||this.outboundBuffer.some(t=>this.outboundEventId(t.cmd,t.payload)===e))}trackOutboundEventCorrelation(e,t,n){const s=this.outboundEventId(e,n);if(!s)return;const i=n,r=typeof i.client_msg_id=="string"?i.client_msg_id.trim():"";this.pruneExpiredEventCorrelations();const u=Date.now();let o=this.eventCorrelations.get(s);if(!o){if(this.eventCorrelations.size>=p.MAX_EVENT_CORRELATIONS){let l;for(const d of this.eventCorrelations.values())this.eventHasCriticalDeliveryState(d.eventId)||(!l||d.touchedAt<l.touchedAt)&&(l=d);l&&this.releaseEventCorrelations(l.eventId)}o={eventId:s,clientMsgIds:new Set,seqRanges:[],touchedAt:u,expiresAt:u+p.EVENT_CORRELATION_TTL_MS},this.eventCorrelations.set(s,o)}o.touchedAt=u,o.expiresAt=u+p.EVENT_CORRELATION_TTL_MS;const f=o.seqRanges[o.seqRanges.length-1];if(f&&t===f.end+1?f.end=t:(!f||t<f.start||t>f.end)&&o.seqRanges.push({start:t,end:t}),r){o.clientMsgIds.add(r);let l=this.clientMsgEventMap.get(r);l||(l=new Set,this.clientMsgEventMap.set(r,l)),l.add(s)}this.scheduleEventCorrelationCleanup()}uniqueEventIdForClientMsgId(e){const t=this.clientMsgEventMap.get(e);if(t?.size===1)return t.values().next().value}eventHasCriticalDeliveryState(e){return!!(this.terminalOutbox.get(e)||this.pendingAcceptedOutputSeqsByEvent.get(e)?.size)}eventIdForSeq(e){if(!(e<=0)){for(const t of this.eventCorrelations.values())if(t.seqRanges.some(n=>e>=n.start&&e<=n.end))return t.eventId}}removeSeqFromEventCorrelation(e){if(!(e<=0))for(const t of this.eventCorrelations.values()){const n=t.seqRanges.findIndex(i=>e>=i.start&&e<=i.end);if(n<0)continue;const s=t.seqRanges[n];s.start===s.end?t.seqRanges.splice(n,1):e===s.start?s.start++:e===s.end?s.end--:t.seqRanges.splice(n,1,{start:s.start,end:e-1},{start:e+1,end:s.end});return}}releaseEventCorrelations(e){const t=e.trim();if(!t)return;const n=this.eventCorrelations.get(t);if(n){for(const s of n.clientMsgIds){const i=this.clientMsgEventMap.get(s);i?.delete(t),i?.size===0&&this.clientMsgEventMap.delete(s)}this.eventCorrelations.delete(t)}this.cancelEventCorrelationCleanupIfEmpty()}clearAllEventCorrelations(){this.eventCorrelationCleanupTimer&&(clearTimeout(this.eventCorrelationCleanupTimer),this.eventCorrelationCleanupTimer=null),this.eventCorrelations.clear(),this.clientMsgEventMap.clear(),this.lastCorrelationPruneAt=0}pruneExpiredEventCorrelations(e=Date.now(),t=!1){if(!(!t&&e-this.lastCorrelationPruneAt<1e3)){this.lastCorrelationPruneAt=e;for(const n of[...this.eventCorrelations.values()])n.expiresAt<=e&&!this.eventHasCriticalDeliveryState(n.eventId)&&this.releaseEventCorrelations(n.eventId);this.cancelEventCorrelationCleanupIfEmpty()}}scheduleEventCorrelationCleanup(){if(this.eventCorrelationCleanupTimer||this.eventCorrelations.size===0)return;let e=Number.POSITIVE_INFINITY;for(const t of this.eventCorrelations.values())this.eventHasCriticalDeliveryState(t.eventId)||(e=Math.min(e,t.expiresAt));Number.isFinite(e)&&(this.eventCorrelationCleanupTimer=setTimeout(()=>{this.eventCorrelationCleanupTimer=null,this.pruneExpiredEventCorrelations(Date.now(),!0),this.scheduleEventCorrelationCleanup()},Math.max(1,e-Date.now())),this.eventCorrelationCleanupTimer.unref?.())}cancelEventCorrelationCleanupIfEmpty(){!this.eventCorrelationCleanupTimer||this.eventCorrelations.size>0||(clearTimeout(this.eventCorrelationCleanupTimer),this.eventCorrelationCleanupTimer=null)}handleCorrelatedSendResponse(e){if(e.cmd==="send_ack"){const f=e.payload,l=typeof f.client_msg_id=="string"?f.client_msg_id.trim():"";let d=e.seq>0&&this.pendingAcceptedOutputs.has(e.seq)?e.seq:void 0,_;if(d===void 0&&l){const a=this.pendingAcceptedSeqsByClientMsgId.get(l);if(a?.size===1){const h=a.values().next().value,g=this.pendingAcceptedOutputs.get(h),O=this.uniqueEventIdForClientMsgId(l);g&&O&&g.eventId===O&&(d=h,_=O)}}const b=(d!==void 0?this.pendingAcceptedOutputs.get(d)?.eventId:void 0)??_??this.eventIdForSeq(e.seq),c=d??e.seq;if(d!==void 0){const a=this.pendingOutputWrites.get(d);a&&this.settlePendingOutputWrite(d,!0,!1,a.socket,a.connectionGeneration),this.settlePendingAcceptedOutput(d,"accepted",!1)}else if(e.seq>0){const a=this.pendingOutputWrites.get(e.seq);a&&this.settlePendingOutputWrite(e.seq,!0,!1,a.socket,a.connectionGeneration)}b&&this.clearNonReplayableOutputThrough(this.ws,b,c);return}if(e.cmd!=="send_nack"&&e.cmd!=="error")return;this.pruneExpiredEventCorrelations();const t=e.payload,n=typeof t.client_msg_id=="string"?t.client_msg_id.trim():"",s=e.cmd==="error"&&typeof t.ref_id=="string"?t.ref_id.trim():"",i=e.cmd==="error"&&typeof t.ref_cmd=="string"?t.ref_cmd.trim():"",r=this.eventIdForSeq(e.seq),u=s&&(!i||p.ORDERED_OUTPUT_COMMANDS.has(i))?this.uniqueEventIdForClientMsgId(s)??(this.eventCorrelations.has(s)?s:void 0):void 0,o=r??(n?this.uniqueEventIdForClientMsgId(n):void 0)??u;o&&(this.blockEventOutput(o),this.settlePendingOutputWritesForEvent(o,!0),this.clearNonReplayableOutputEvent(o),e.seq>0&&this.pendingAcceptedOutputs.get(e.seq)?.eventId===o?this.settlePendingAcceptedOutput(e.seq,"rejected",!1):this.settlePendingAcceptedOutputsForEvent(o,"rejected",!1),t.code===4003&&(this.purgeBufferedStreamChunks(o),m.warn("aibot",`event payload rejected (4003), purging buffered chunks for event=${o}`)),this.promotePendingTerminalAfterOutputRejection(o,Number(t.code??0),t.msg),this.emit("streamRejected",o,Number(t.code??0)))}promotePendingTerminalAfterOutputRejection(e,t,n){this.provisionalRespondedTerminals.delete(e);const s=this.terminalOutbox.get(e);if(!(!s||s.payload.status!=="responded"&&s.payload.code!=="agent_output_unconfirmed"||s.deliveryStartedAt))try{const i=this.terminalOutbox.enqueue({...s.payload,status:"failed",code:"agent_output_rejected",msg:n?`required agent output was rejected: ${n}`:`required agent output was rejected by the server (code=${t})`,updated_at:Date.now()});this.scheduleTerminalDelivery(i.payload.event_id,{ignoreNotBefore:!0})}catch(i){this.emitClientError(new Error(`failed to persist output-rejection terminal promotion: event=${e} code=${t}: ${i instanceof Error?i.message:i}`))}}scheduleTerminalDelivery(e,t){if(!this.connected||!this.ws||this.ws.readyState!==y.OPEN)return;if(this.terminalInFlight.has(e)){t?.ignoreNotBefore&&this.terminalReplayAfterFlight.add(e);return}const n=this.terminalOutbox.get(e);if(!n)return;const s=this.terminalRetryTimers.get(e);s&&(clearTimeout(s),this.terminalRetryTimers.delete(e));const i=t?.ignoreNotBefore?0:Math.max(0,n.nextAttemptAt-Date.now()),r=Math.max(i,t?.minimumDelayMs??0);if(r>0){const u=setTimeout(()=>{this.terminalRetryTimers.delete(e),this.scheduleTerminalDelivery(e,{ignoreNotBefore:!0})},r);this.terminalRetryTimers.set(e,u);return}this.deliverTerminalEntry(n)}async deliverTerminalEntry(e){const t=e.payload.event_id;if(this.terminalInFlight.has(t)||!this.terminalOutbox.isCurrent(e))return;this.terminalInFlight.add(t);let n=0;try{n=await this.sendEventResultReliable(e)}finally{this.terminalInFlight.delete(t);const s=this.terminalReplayAfterFlight.delete(t);this.terminalOutbox.get(t)&&this.scheduleTerminalDelivery(t,s?{ignoreNotBefore:!0}:{minimumDelayMs:n})}}clearTerminalRetryTimers(){for(const e of this.terminalRetryTimers.values())clearTimeout(e);this.terminalRetryTimers.clear()}async sendEventResultReliable(e){const t=e.payload.event_id,n=e.payload.terminal_commit_token?.trim();if(n&&!this.negotiatedCapabilities.has("terminal_commit_v1")){const d=this.ws;return this.emitClientError(new Error(`refusing tokenized event_result on an unnegotiated connection: event=${t}`)),d&&this.reconnectAfterOutboundWriteFailure(d,"terminal_commit_v1 not negotiated"),p.TERMINAL_RETRY_DELAY_MS}if(!await this.awaitOutputDeliveryBarrier(t))return p.TERMINAL_RETRY_DELAY_MS;const s=this.provisionalRespondedTerminals.get(t);if(s&&this.terminalOutbox.isCurrent(e))if(!this.canReplaceUnsentOutputGuard(e))this.provisionalRespondedTerminals.delete(t);else try{return this.terminalOutbox.enqueue(s),this.provisionalRespondedTerminals.delete(t),0}catch(d){return this.emitClientError(new Error(`failed to promote confirmed output terminal to responded: event=${t}: ${d instanceof Error?d.message:d}`)),p.TERMINAL_RETRY_DELAY_MS}const i=e.payload;try{if(!this.terminalOutbox.markDeliveryStarted(e))return 0}catch(d){return this.emitClientError(new Error(`event_result delivery-start persist failed: event=${t}: ${d instanceof Error?d.message:d}`)),p.TERMINAL_RETRY_DELAY_MS}const r=Math.max(1,this.ackPolicy?.max_retries??3),u=this.ackPolicy?.push_ack_timeout_ms??5e3,o=750;let f="unknown delivery failure";for(let d=1;d<=r;d++){if(!this.terminalOutbox.isCurrent(e))return 0;const _=this.ws?.readyState??-1,b=this.ws?.bufferedAmount??0;m.info("aibot",`event_result send attempt event=${i.event_id} status=${i.status} attempt=${d}/${r} readyState=${_} bufferedAmount=${b}`);try{const c=await this.sendEventResultRequest(i,u);if(!this.terminalOutbox.isCurrent(e))return 0;if(c.cmd==="send_ack"){const h=c.payload;if(m.info("aibot",`event_result ack event=${i.event_id} status=${i.status} attempt=${d}/${r} ack_event=${h.event_id??""} ack_status=${h.status??""}`),h.event_id!==i.event_id||h.status!==i.status)throw new Error(`event_result ACK mismatch: expected event=${i.event_id} status=${i.status}, got event=${h.event_id??""} status=${h.status??""}`);if(n&&(h.terminal_commit_token?.trim()!==n||h.terminal_committed!==!0))throw new Error(`event_result terminal commit ACK mismatch: event=${i.event_id}`);if(n)try{this.terminalCommitTokens.remove(i.event_id,n)}catch(g){throw new Error(`terminal commit token cleanup failed: event=${i.event_id}: ${g instanceof Error?g.message:g}`)}return this.terminalOutbox.acknowledge(e,h.event_id,h.status,h.terminal_commit_token,h.terminal_committed)&&(this.rememberCommittedTerminalEvent(i.event_id),this.tokenizedTerminalRejections.delete(i.event_id),this.blockEventOutput(i.event_id),this.releaseEventCorrelations(i.event_id),this.clearNonReplayableOutputEvent(i.event_id),this.settleOutputIntegrityTerminal(i.event_id),this.scheduleStopResultsForEvent(i.event_id)),0}const a=c.payload;if(m.warn("aibot",`event_result rejected event=${i.event_id} status=${i.status} attempt=${d}/${r} cmd=${c.cmd} code=${a.code??""} msg=${a.msg??""}${a.ref_cmd?` ref_cmd=${a.ref_cmd}`:""}${a.ref_id?` ref_id=${a.ref_id}`:""}`),n){f=`tokenized terminal rejected: cmd=${c.cmd} code=${a.code??""} msg=${a.msg??""}`;const h=(this.tokenizedTerminalRejections.get(i.event_id)??0)+1;if(this.tokenizedTerminalRejections.set(i.event_id,h),h<p.MAX_TOKENIZED_TERMINAL_REJECTIONS)break;this.tokenizedTerminalRejections.delete(i.event_id);try{this.terminalCommitTokens.remove(i.event_id,n),this.terminalOutbox.moveToDeadLetter(e,{responseCmd:c.cmd,code:a.code,message:a.msg})&&(this.blockEventOutput(i.event_id),this.releaseEventCorrelations(i.event_id),this.clearNonReplayableOutputEvent(i.event_id),this.settleOutputIntegrityTerminal(i.event_id),this.emitClientError(new Error(`tokenized event_result permanently rejected after ${h} rejections, moved to dead-letter: event=${i.event_id} status=${i.status} cmd=${c.cmd} code=${a.code??""} msg=${a.msg??""}`)))}catch(g){f=`dead-letter persist failed: ${g instanceof Error?g.message:g}`,this.emitClientError(new Error(`tokenized event_result dead-letter persist failed: event=${i.event_id} status=${i.status}: ${f}`));break}return 0}try{return this.terminalOutbox.moveToDeadLetter(e,{responseCmd:c.cmd,code:a.code,message:a.msg})&&(this.tokenizedTerminalRejections.delete(i.event_id),this.blockEventOutput(i.event_id),this.releaseEventCorrelations(i.event_id),this.clearNonReplayableOutputEvent(i.event_id),this.settleOutputIntegrityTerminal(i.event_id),this.emitClientError(new Error(`event_result rejected and moved to dead-letter: event=${i.event_id} status=${i.status} cmd=${c.cmd} code=${a.code??""} msg=${a.msg??""}`))),0}catch(h){f=`dead-letter persist failed: ${h instanceof Error?h.message:h}`,this.emitClientError(new Error(`event_result dead-letter persist failed: event=${i.event_id} status=${i.status}: ${f}`));break}}catch(c){const a=c instanceof Error?c.message:String(c);if(f=a,m.warn("aibot",`event_result attempt failed event=${i.event_id} status=${i.status} attempt=${d}/${r} err=${a}`),d===r)break;await new Promise(h=>setTimeout(h,o*d))}}const l=Date.now()+p.TERMINAL_RETRY_DELAY_MS;try{this.terminalOutbox.recordRetry(e,l,f)}catch(d){this.emitClientError(new Error(`event_result retry state persist failed: event=${i.event_id} status=${i.status}: ${d instanceof Error?d.message:d}`))}return this.emitClientError(new Error(`event_result ack failed after ${r} attempts; retained for retry: event=${i.event_id} status=${i.status} err=${f}`)),p.TERMINAL_RETRY_DELAY_MS}purgeBufferedStreamChunks(e){const t=this.outboundBuffer.length;this.outboundBuffer=this.outboundBuffer.filter(n=>n.cmd!=="client_stream_chunk"?!0:n.payload?.event_id!==e),this.outboundBuffer.length<t&&m.info("aibot",`purged ${t-this.outboundBuffer.length} buffered stream chunks for event=${e}`)}emitClientError(e){if(this.listenerCount("error")===0){m.warn("aibot",`Client error (no listeners): ${e.message}`);return}this.emit("error",e)}createOutboundEntry(e,t){const n={cmd:e,payload:t};return this.outboundEntryOrders.set(n,++this.nextOutboundEntryOrder),n}makeOutputIntegrityFailurePayload(e,t){return this.withTerminalCommitToken({event_id:e,status:"failed",code:"agent_output_integrity_failed",msg:`required agent output was discarded before delivery: ${t}`,updated_at:Date.now()})}canReplaceUnsentOutputGuard(e){return e.payload.code==="agent_output_unconfirmed"&&!e.deliveryStartedAt}rememberCommittedTerminalEvent(e){for(this.committedTerminalEvents.delete(e),this.committedTerminalEvents.add(e);this.committedTerminalEvents.size>p.MAX_COMMITTED_TERMINAL_EVENTS;){const t=this.committedTerminalEvents.values().next().value;if(!t)break;this.committedTerminalEvents.delete(t)}}replayStopResultOutbox(){for(const e of this.stopResultOutbox.listPending())this.scheduleStopResultDelivery(e,{ignoreNotBefore:!0})}scheduleStopResultsForEvent(e){for(const t of this.stopResultOutbox.listPending())t.payload.event_id===e&&this.scheduleStopResultDelivery(t,{ignoreNotBefore:!0})}scheduleStopResultDelivery(e,t){if(!this.connected||!this.ws||this.ws.readyState!==y.OPEN||!this.stopResultOutbox.isCurrent(e))return;const n=e.payload.event_id;if(this.terminalOutbox.get(n)||this.terminalCommitTokens.get(n))return;const s=this.stopResultRetryTimers.get(e.key);s&&(clearTimeout(s),this.stopResultRetryTimers.delete(e.key));const i=t?.ignoreNotBefore?0:Math.max(0,e.nextAttemptAt-Date.now());if(i>0){const r=setTimeout(()=>{this.stopResultRetryTimers.delete(e.key),this.scheduleStopResultDelivery(e,{ignoreNotBefore:!0})},i);this.stopResultRetryTimers.set(e.key,r);return}this.deliverStopResultEntry(e)}async deliverStopResultEntry(e){if(this.stopResultInFlight.has(e.key)||!this.stopResultOutbox.isCurrent(e))return;this.stopResultInFlight.add(e.key);let t=p.TERMINAL_RETRY_DELAY_MS;try{t=await this.sendStopResultReliable(e)}finally{if(this.stopResultInFlight.delete(e.key),this.stopResultOutbox.isCurrent(e)){const n=setTimeout(()=>{this.stopResultRetryTimers.delete(e.key),this.scheduleStopResultDelivery(e,{ignoreNotBefore:!0})},t);this.stopResultRetryTimers.set(e.key,n)}}}async sendStopResultReliable(e){const t=this.ackPolicy?.push_ack_timeout_ms??5e3;let n="unknown delivery failure";try{const i=await this.request("event_stop_result",e.payload,{expected:["send_ack","send_nack","error"],timeoutMs:t});if(!this.stopResultOutbox.isCurrent(e))return 0;if(i.cmd==="send_ack"){const r=i.payload;if(this.stopResultOutbox.acknowledge(e,r.event_id,r.terminal_commit_token,r.terminal_committed))return 0;n=`event_stop_result ACK mismatch event=${e.payload.event_id}`}else{const r=i.payload;if(n=`event_stop_result rejected: cmd=${i.cmd} code=${r.code??""} msg=${r.msg??""}`,r.code===4001||r.code===4003){try{this.stopResultOutbox.discard(e)&&this.emitClientError(new Error(`event_stop_result permanently rejected and discarded: event=${e.payload.event_id} stop=${e.payload.stop_id} code=${r.code} msg=${r.msg??""}`))}catch(u){this.emitClientError(new Error(`event_stop_result discard persist failed: event=${e.payload.event_id} stop=${e.payload.stop_id}: ${u instanceof Error?u.message:u}`))}return 0}}}catch(i){n=i instanceof Error?i.message:String(i)}const s=Date.now()+p.TERMINAL_RETRY_DELAY_MS;try{this.stopResultOutbox.recordRetry(e,s,n)}catch(i){this.emitClientError(new Error(`event_stop_result retry state persist failed: event=${e.payload.event_id} stop=${e.payload.stop_id}: ${i instanceof Error?i.message:i}`))}return this.emitClientError(new Error(`event_stop_result retained for retry: event=${e.payload.event_id} stop=${e.payload.stop_id} err=${n}`)),p.TERMINAL_RETRY_DELAY_MS}clearStopResultRetryTimers(){for(const e of this.stopResultRetryTimers.values())clearTimeout(e);this.stopResultRetryTimers.clear()}markOutputIntegrityFailed(e,t){if(this.blockEventOutput(e),this.outputIntegrityFailed.has(e))return;const n={reason:t,terminalObserved:!!(this.provisionalRespondedTerminals.has(e)||this.terminalOutbox.get(e)),terminalSettled:!1};this.outputIntegrityFailed.set(e,n),this.provisionalRespondedTerminals.delete(e),this.clearNonReplayableOutputEvent(e);const s=this.outboundBuffer.length;this.outboundBuffer=this.outboundBuffer.filter(u=>this.outboundEventId(u.cmd,u.payload)!==e);const i=this.terminalOutbox.get(e),r=i?this.canReplaceUnsentOutputGuard(i):!1;if(i&&!r)n.terminalObserved=!0,this.scheduleTerminalDelivery(e,{ignoreNotBefore:!0});else if(!this.committedTerminalEvents.has(e))try{const u=this.terminalOutbox.enqueue(this.makeOutputIntegrityFailurePayload(e,t));this.scheduleTerminalDelivery(u.payload.event_id,{ignoreNotBefore:!0})}catch(u){this.emitClientError(new Error(`output-integrity terminal persist failed: event=${e}: ${u instanceof Error?u.message:u}`))}for(const u of[...this.pendingOutputSeqsByEvent.get(e)??[]])this.settlePendingOutputWrite(u,!1,!1);this.settlePendingAcceptedOutputsForEvent(e,"rejected",!1),this.emit("streamRejected",e,0),this.emitClientError(new Error(`required output integrity failed: event=${e} removed=${s-this.outboundBuffer.length} reason=${t}`))}settleOutputIntegrityTerminal(e){const t=this.outputIntegrityFailed.get(e);t&&(t.terminalSettled=!0,t.terminalObserved&&this.outputIntegrityFailed.delete(e))}bufferOutboundEntry(e){if(this.outboundEntryOrders.has(e)||this.outboundEntryOrders.set(e,++this.nextOutboundEntryOrder),this.outboundBuffer.includes(e))return!0;const t=this.outboundEventId(e.cmd,e.payload);return t&&(this.outputIntegrityFailed.has(t)||this.blockedOutputEvents.has(t))?!1:(this.outboundBuffer.length>=p.MAX_OUTBOUND_BUFFER_SIZE&&(this.outboundBuffer=this.outboundBuffer.filter(n=>p.BUFFER_OVERFLOW_RETAIN_COMMANDS.has(n.cmd)),this.outboundBuffer.length>=p.MAX_OUTBOUND_BUFFER_SIZE&&m.warn("aibot",`outbound buffer soft limit exceeded by ${this.outboundBuffer.length} retained packet(s); preserving required output`)),this.outboundBuffer.length>=p.MAX_OUTBOUND_BUFFER_HARD_SIZE?(t?this.markOutputIntegrityFailed(t,`outbound buffer hard limit ${p.MAX_OUTBOUND_BUFFER_HARD_SIZE} reached`):this.emitClientError(new Error(`outbound buffer hard limit ${p.MAX_OUTBOUND_BUFFER_HARD_SIZE} reached; rejected non-event packet cmd=${e.cmd}`)),!1):(this.outboundBuffer.push(e),this.outboundBuffer.sort((n,s)=>(this.outboundEntryOrders.get(n)??Number.MAX_SAFE_INTEGER)-(this.outboundEntryOrders.get(s)??Number.MAX_SAFE_INTEGER)),!0))}registerPendingOutputWrite(e,t,n,s,i){let r;const u=new Promise(l=>{r=l}),o=setTimeout(()=>{const l=this.pendingOutputWrites.get(e);if(!l||l.socket!==s||l.connectionGeneration!==i)return;if(!this.isConnectionCurrent(s,i)){this.settlePendingOutputWrite(e,!1,!1,s,i);return}this.removeSeqFromEventCorrelation(e);const d=this.isNonReplayableOutput(n.cmd,n.payload);this.settlePendingOutputWrite(e,!1,!d,s,i),d&&this.markOutputIntegrityFailed(t,`websocket write callback timed out for non-replayable ${n.cmd} seq=${e}`),this.emitClientError(new Error(`outbound websocket write callback timeout: cmd=${n.cmd} event=${t} seq=${e} delivery_unknown=${d}`)),this.reconnectAfterOutboundWriteFailure(s,`${n.cmd} callback timeout`)},p.OUTBOUND_WRITE_CALLBACK_TIMEOUT_MS);o.unref?.(),this.pendingOutputWrites.set(e,{eventId:t,entry:n,socket:s,connectionGeneration:i,promise:u,resolve:r,timer:o});let f=this.pendingOutputSeqsByEvent.get(t);f||(f=new Set,this.pendingOutputSeqsByEvent.set(t,f)),f.add(e)}settlePendingOutputWrite(e,t,n,s,i){const r=this.pendingOutputWrites.get(e);if(!r||s&&(r.socket!==s||r.connectionGeneration!==i))return;this.pendingOutputWrites.delete(e),clearTimeout(r.timer);const u=this.pendingOutputSeqsByEvent.get(r.eventId);u?.delete(e),u?.size===0&&this.pendingOutputSeqsByEvent.delete(r.eventId),n&&this.bufferOutboundEntry(r.entry),r.resolve(t)}settlePendingOutputWritesForEvent(e,t){const n=this.pendingOutputSeqsByEvent.get(e);if(n)for(const s of[...n]){const i=this.pendingOutputWrites.get(s);i&&this.settlePendingOutputWrite(s,t,!1,i.socket,i.connectionGeneration)}}abortPendingOutputWrites(e,t,n,s){for(const[i,r]of[...this.pendingOutputWrites.entries()])if(!(t&&(r.socket!==t||r.connectionGeneration!==n))){if(this.removeSeqFromEventCorrelation(i),s&&this.isNonReplayableOutput(r.entry.cmd,r.entry.payload)){this.settlePendingOutputWrite(i,!1,!1,t,n),this.markOutputIntegrityFailed(r.eventId,`connection lost while websocket write completion was unknown: ${s}`);continue}this.settlePendingOutputWrite(i,!1,e,t,n)}}registerPendingAcceptedOutput(e,t,n,s,i){let r;const u=new Promise(h=>{r=h}),o=Math.max(1,this.ackPolicy?.push_ack_timeout_ms??5e3),f=Math.max(1,this.ackPolicy?.max_retries??3),l=Math.max(1e3,Math.ceil(o*.25)),d=Math.max(p.MIN_REQUIRED_OUTPUT_ACK_TIMEOUT_MS,o*f+l),_=setTimeout(()=>{const h=this.pendingAcceptedOutputs.get(e);if(!(!h||h.socket!==s||h.connectionGeneration!==i)){if(!this.isConnectionCurrent(s,i)){this.settlePendingAcceptedOutput(e,"retry",!1,s,i);return}this.settlePendingAcceptedOutput(e,"retry",!0,s,i),this.emitClientError(new Error(`required output ACK timeout; retained for reconnect retry: event=${t} seq=${e}`)),this.reconnectAfterOutboundWriteFailure(s,"required output ACK timeout")}},d);_.unref?.(),this.pendingAcceptedOutputs.set(e,{eventId:t,entry:n,socket:s,connectionGeneration:i,promise:u,resolve:r,timer:_});const b=n.payload,c=typeof b.client_msg_id=="string"?b.client_msg_id.trim():"";if(c){let h=this.pendingAcceptedSeqsByClientMsgId.get(c);h||(h=new Set,this.pendingAcceptedSeqsByClientMsgId.set(c,h)),h.add(e)}let a=this.pendingAcceptedOutputSeqsByEvent.get(t);a||(a=new Set,this.pendingAcceptedOutputSeqsByEvent.set(t,a)),a.add(e)}settlePendingAcceptedOutput(e,t,n,s,i){const r=this.pendingAcceptedOutputs.get(e);if(!r||s&&(r.socket!==s||r.connectionGeneration!==i))return;this.pendingAcceptedOutputs.delete(e),clearTimeout(r.timer);const u=this.pendingAcceptedOutputSeqsByEvent.get(r.eventId);u?.delete(e),u?.size===0&&this.pendingAcceptedOutputSeqsByEvent.delete(r.eventId);const o=r.entry.payload,f=typeof o.client_msg_id=="string"?o.client_msg_id.trim():"";if(f){const l=this.pendingAcceptedSeqsByClientMsgId.get(f);l?.delete(e),l?.size===0&&this.pendingAcceptedSeqsByClientMsgId.delete(f)}n&&this.bufferOutboundEntry(r.entry),r.resolve(t)}settlePendingAcceptedOutputsForEvent(e,t,n){const s=this.pendingAcceptedOutputSeqsByEvent.get(e);if(s)for(const i of[...s])this.settlePendingAcceptedOutput(i,t,n)}abortPendingAcceptedOutputs(e,t,n){for(const[s,i]of[...this.pendingAcceptedOutputs.entries()])t&&(i.socket!==t||i.connectionGeneration!==n)||this.settlePendingAcceptedOutput(s,"retry",e,t,n)}rejectPendingRequestAfterWriteFailure(e,t,n){const s=this.pendingRequests.get(e);s&&(this.pendingRequests.delete(e),clearTimeout(s.timer),s.reject(new Error(`websocket write failed: ${t}: ${n.message}`)))}async awaitOutputDeliveryBarrier(e){const t=this.outboundFlushInFlight;if(t&&!await t.promise)return!1;for(;;){const n=this.pendingOutputSeqsByEvent.get(e);if(!n||n.size===0)break;const s=[...n].map(r=>this.pendingOutputWrites.get(r)?.promise).filter(r=>!!r);if(s.length===0)break;if((await Promise.all(s)).some(r=>!r))return!1}for(;;){const n=this.pendingAcceptedOutputSeqsByEvent.get(e);if(!n||n.size===0)break;const s=[...n].map(r=>this.pendingAcceptedOutputs.get(r)?.promise).filter(r=>!!r);if(s.length===0)break;const i=await Promise.all(s);if(i.includes("retry"))return!1;if(i.includes("rejected")){const r=this.terminalOutbox.get(e);if(!r||r.payload.status==="responded")return!1}}return this.outboundBuffer.some(n=>this.outboundEventId(n.cmd,n.payload)===e)?!1:this.hasNonReplayableOutput(e)?(this.markOutputIntegrityFailed(e,"output flush completed without a complete acceptance fence"),!1):!!(this.connected&&this.ws&&this.ws.readyState===y.OPEN)}isConnectionCurrent(e,t){return this.ws===e&&this.connectionGeneration===t}isOutboundFlushCurrent(e){return!e.finalized&&this.outboundFlushInFlight===e&&this.isConnectionCurrent(e.socket,e.connectionGeneration)}flushOutboundBuffer(e=this.ws,t=this.connectionGeneration){if(!e||!this.isConnectionCurrent(e,t))return Promise.resolve(!1);const n=this.outboundFlushInFlight;if(n){if(!n.finalized&&n.socket===e&&n.connectionGeneration===t)return n.promise;this.cancelOutboundFlush(n,"discard","superseded stale flush")}if(this.outboundBuffer.length===0)return Promise.resolve(!0);const s={connectionGeneration:t,flushGeneration:++this.nextOutboundFlushGeneration,socket:e,promise:Promise.resolve(!1),finalized:!1,activeBatch:null,nextIndex:0,currentWritePending:!1,currentWriteOutcome:null,cancelCurrentWrite:null,remainderHandled:!1};return this.outboundFlushInFlight=s,s.promise=this.performOutboundBufferFlush(s),s.promise.finally(()=>{s.finalized=!0,s.cancelCurrentWrite?.(),s.cancelCurrentWrite=null,this.outboundFlushInFlight===s&&(this.outboundFlushInFlight=null)}),s.promise}async performOutboundBufferFlush(e){for(;this.isOutboundFlushCurrent(e)&&this.outboundBuffer.length>0;){const{socket:t}=e;if(t.readyState!==y.OPEN)return!1;const n=this.outboundBuffer;this.outboundBuffer=[],e.activeBatch=n,e.nextIndex=0,e.remainderHandled=!1;for(let s=0;s<n.length;s++){if(!this.isOutboundFlushCurrent(e))return!1;const i=n[s],{cmd:r,payload:u}=i,o=this.outboundEventId(r,u);if(o&&this.blockedOutputEvents.has(o)){this.packetLog?.logOutboundPacket(r,0,u,"dropped"),e.nextIndex=s+1,e.currentWritePending=!1,e.currentWriteOutcome=null;continue}const f=++this.seq,l={cmd:r,seq:f,payload:u};e.nextIndex=s,e.currentWritePending=!0,e.currentWriteOutcome=null,o&&this.isAckRequiredOutput(r,u)&&this.registerPendingAcceptedOutput(f,o,i,t,e.connectionGeneration);const d=await new Promise(_=>{let b=!1;const c=h=>{b||(b=!0,clearTimeout(a),e.currentWriteOutcome=h,_(h))},a=setTimeout(()=>c("uncertain"),p.OUTBOUND_WRITE_CALLBACK_TIMEOUT_MS);a.unref?.(),e.cancelCurrentWrite=()=>c("canceled");try{this.trackOutboundEventCorrelation(r,f,u),t.send(JSON.stringify(l),h=>{c(h?"retry":"written")})}catch{c("retry")}});if(e.cancelCurrentWrite=null,!this.isOutboundFlushCurrent(e))return!1;if(d!=="written"||t.readyState!==y.OPEN){d==="retry"&&this.removeSeqFromEventCorrelation(f),this.settlePendingAcceptedOutput(f,"retry",!1);const _=this.retainOutboundFlushRemainder(e,d,`auth flush write failed for ${r} seq=${f} outcome=${d}`);return this.emitClientError(new Error(`outbound buffer flush failed at cmd=${r} seq=${f} outcome=${d}; ${_} packet(s) retained`)),!1}this.trackNonReplayableOutput(t,r,u,f),e.currentWritePending=!1,e.currentWriteOutcome="written",e.nextIndex=s+1}e.activeBatch=null,e.currentWritePending=!1,e.currentWriteOutcome=null,e.remainderHandled=!0}return this.isOutboundFlushCurrent(e)}retainOutboundFlushRemainder(e,t,n){if(e.remainderHandled||!e.activeBatch)return 0;e.remainderHandled=!0;const s=e.activeBatch,i=Math.min(e.nextIndex,s.length);let r=i;if(e.currentWritePending&&i<s.length){const o=s[i],f=this.outboundEventId(o.cmd,o.payload);t!=="retry"&&this.isNonReplayableOutput(o.cmd,o.payload)&&f&&(this.markOutputIntegrityFailed(f,n),r=i+1)}let u=0;for(const o of s.slice(r))this.bufferOutboundEntry(o)&&u++;return e.activeBatch=null,e.nextIndex=s.length,e.currentWritePending=!1,u}cancelOutboundFlush(e,t,n){if(e.finalized){this.outboundFlushInFlight===e&&(this.outboundFlushInFlight=null);return}e.finalized=!0,this.outboundFlushInFlight===e&&(this.outboundFlushInFlight=null),t==="connection_lost"?this.retainOutboundFlushRemainder(e,e.currentWriteOutcome,`connection lost during auth flush: ${n}`):(e.remainderHandled=!0,e.activeBatch=null,e.currentWritePending=!1);const s=e.cancelCurrentWrite;e.cancelCurrentWrite=null,s?.()}reconnectAfterOutboundFlushFailure(e,t){this.agentDeleted||!this.isConnectionCurrent(e,t)||this.reconnectAfterOutboundWriteFailure(e,"outbound flush failed")}reconnectAfterOutboundWriteFailure(e,t){if(this.agentDeleted||this.ws!==e)return;const n=this.connectionGeneration;this.connected=!1,this.negotiatedCapabilities.clear(),this.stopHeartbeat(),this.clearTerminalRetryTimers(),this.clearStopResultRetryTimers(),this.rejectAllPendingRequests(t);const s=this.outboundFlushInFlight;s&&s.socket===e&&s.connectionGeneration===n&&this.cancelOutboundFlush(s,"connection_lost",t),this.failNonReplayableOutputsForSocket(e,t),this.abortPendingOutputWrites(!0,e,n,t),this.abortPendingAcceptedOutputs(!0,e,n),this.clearAllEventCorrelations(),this.connectionGeneration++;try{e.close(1011,t)}catch{}this.ws===e&&(this.ws=null),this.attemptReconnect()}handleInvokeResult(e){const t=this.pendingInvokes.get(e.invoke_id);t&&(this.pendingInvokes.delete(e.invoke_id),clearTimeout(t.timer),e.code===0?t.resolve(e.data??null):t.reject(new Error(`agent_invoke error code=${e.code}: ${e.msg??""}`)))}rejectAllPendingInvokes(e){for(const[,t]of this.pendingInvokes)clearTimeout(t.timer),t.reject(new Error(`agent_invoke canceled: ${e}`));this.pendingInvokes.clear()}rejectAllPendingRequests(e){for(const[,t]of this.pendingRequests)clearTimeout(t.timer),t.reject(new Error(`request canceled: ${e}`));this.pendingRequests.clear()}cleanupSocket(e=this.ws,t=this.connectionGeneration){if(!(!e||!this.isConnectionCurrent(e,t)))try{e.close()}catch{}}startHeartbeat(){this.stopHeartbeat(),this.heartbeatFailures=0,this.heartbeatTimer=setInterval(()=>{const e=this.ws,t=this.connectionGeneration;!this.connected||!e||this.request("ping",{ts:Date.now()},{expected:["pong"],timeoutMs:5e3}).then(()=>{this.isConnectionCurrent(e,t)&&(this.heartbeatFailures=0)}).catch(()=>{!this.connected||!this.isConnectionCurrent(e,t)||(this.heartbeatFailures++,!(this.heartbeatFailures<p.HEARTBEAT_MAX_FAILURES)&&(this.cleanupSocket(e,t),this.attemptReconnect()))})},this.heartbeatSec*1e3)}stopHeartbeat(){this.heartbeatTimer&&(clearInterval(this.heartbeatTimer),this.heartbeatTimer=null)}}export{p as AibotClient,D as preprocessLargeIntegers,x as withRequiredCapabilities};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{readdir as r,stat as m}from"node:fs/promises";import{join as l,extname as d}from"node:path";const x={pdf:"application/pdf",doc:"application/msword",docx:"application/vnd.openxmlformats-officedocument.wordprocessingml.document",xls:"application/vnd.ms-excel",xlsx:"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",ppt:"application/vnd.ms-powerpoint",pptx:"application/vnd.openxmlformats-officedocument.presentationml.presentation",txt:"text/plain",md:"text/markdown",csv:"text/csv",json:"application/json",xml:"application/xml",yaml:"text/yaml",yml:"text/yaml",html:"text/html",css:"text/css",js:"text/javascript",ts:"text/typescript",zip:"application/zip",rar:"application/x-rar-compressed","7z":"application/x-7z-compressed",tar:"application/x-tar",gz:"application/gzip",jpg:"image/jpeg",jpeg:"image/jpeg",png:"image/png",gif:"image/gif",webp:"image/webp",svg:"image/svg+xml",mp4:"video/mp4",mov:"video/quicktime",avi:"video/x-msvideo",mkv:"video/x-matroska",webm:"video/webm",mp3:"audio/mpeg",wav:"audio/wav",flac:"audio/flac",aac:"audio/aac"};function n(a){const p=d(a).slice(1).toLowerCase();return x[p]}async function f(a,p=!1){const c=await r(a,{withFileTypes:!0}),s=[];for(const
|
|
1
|
+
import{readdir as r,stat as m}from"node:fs/promises";import{join as l,extname as d}from"node:path";const x={pdf:"application/pdf",doc:"application/msword",docx:"application/vnd.openxmlformats-officedocument.wordprocessingml.document",xls:"application/vnd.ms-excel",xlsx:"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",ppt:"application/vnd.ms-powerpoint",pptx:"application/vnd.openxmlformats-officedocument.presentationml.presentation",txt:"text/plain",md:"text/markdown",csv:"text/csv",json:"application/json",xml:"application/xml",yaml:"text/yaml",yml:"text/yaml",html:"text/html",css:"text/css",js:"text/javascript",ts:"text/typescript",zip:"application/zip",rar:"application/x-rar-compressed","7z":"application/x-7z-compressed",tar:"application/x-tar",gz:"application/gzip",jpg:"image/jpeg",jpeg:"image/jpeg",png:"image/png",gif:"image/gif",webp:"image/webp",svg:"image/svg+xml",mp4:"video/mp4",mov:"video/quicktime",avi:"video/x-msvideo",mkv:"video/x-matroska",webm:"video/webm",mp3:"audio/mpeg",wav:"audio/wav",flac:"audio/flac",aac:"audio/aac"};function n(a){const p=d(a).slice(1).toLowerCase();return x[p]}async function f(a,p=!1){const c=await r(a,{withFileTypes:!0}),s=[];for(const i of c){if(!p&&i.name.startsWith("."))continue;const t=l(a,i.name),e={id:t,name:i.name,is_directory:i.isDirectory()};try{if(i.isDirectory()){const o=await m(t);e.modified_at=o.mtime.toISOString()}else{const o=await m(t);e.size=o.size,e.modified_at=o.mtime.toISOString(),e.mime_type=n(i.name)}}catch{}s.push(e)}return s.sort((i,t)=>i.is_directory!==t.is_directory?i.is_directory?-1:1:i.name.localeCompare(t.name)),s}export{f as listFiles,n as resolveMimeType};
|
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
import{execFile as
|
|
2
|
-
${F(i)}`);const
|
|
3
|
-
${
|
|
4
|
-
Primary: ${
|
|
5
|
-
Fallback: ${I}
|
|
1
|
+
import{execFile as R,spawn as _}from"node:child_process";import{log as m}from"../log/logger.js";import{resolveCliPath as P,getCliVersion as v,invalidateCliPathCache as x}from"../util/cli-probe.js";import{getInstallCommand as S,getCliBinary as O,isKnownAgent as D,detectPlatformOS as A,formatInstallCommand as U}from"./registry.js";import{checkPrerequisites as y,getMissingPrerequisites as k}from"./preflight.js";import{installMissingPrerequisites as C}from"./prereq-installer.js";import{detectEnvironment as V,formatEnvironmentInfo as F,isEnvironmentSupported as q}from"./env-detect.js";import{generateManualGuide as b}from"./manual-guide.js";import{npmInstallWithMirror as j,isTransientInstallError as G}from"./npm-registry.js";import{getAllAgentInstallInfo as B}from"./registry.js";class u extends Error{code;output;constructor(n,e,t){super(e),this.name="InstallerError",this.code=n,this.output=t}}const T=64*1024,H=20,N=2,W=3e3;class lt{os;activeInstalls=new Map;constructor(){this.os=A()}listInstallable(){return{platform:this.os,agents:B(this.os)}}getProgress(n){return this.activeInstalls.get(n)}isInProgress(n){return this.activeInstalls.has(n)}async install(n){const{agentType:e}=n,t=Date.now();if(this.activeInstalls.has(e))return this.fail(e,"preflight",t,new u("INSTALL_IN_PROGRESS",`${e} is already being installed`));try{return await this._doInstall(n,t)}catch(i){this.activeInstalls.delete(e);const o=i instanceof Error?i.message:String(i);return m.error("installer",`${e} install unexpected error: ${o}`),{agentType:e,ok:!1,phase:"failed",error:{code:"INTERNAL",message:`Unexpected error: ${o}`},durationMs:Date.now()-t,output:""}}}async _doInstall(n,e){const{agentType:t}=n;let i;try{i=await V()}catch(s){const a=s instanceof Error?s.message:String(s);return this.fail(t,"preflight",e,new u("INTERNAL",`Environment detection failed: ${a}`))}m.info("installer",`Install request: ${t}
|
|
2
|
+
${F(i)}`);const o=q(i);if(!o.supported)return this.fail(t,"preflight",e,new u("ENVIRONMENT_UNSUPPORTED",`Current environment is not supported for automatic installation: ${o.reason}. Please install ${t} manually.`),i);if(this.setProgress(t,"preflight",e),!D(t))return this.fail(t,"preflight",e,new u("UNKNOWN_AGENT",`Unknown agent type: ${t}`),i);const l=S(t,this.os);if(!l){const s=this.getManualHint(t,this.os),a=s?`Installation of ${t} is not supported on ${this.os}. ${s}`:`Installation of ${t} is not supported on ${this.os}`;return this.fail(t,"preflight",e,new u("UNSUPPORTED_OS",a),i)}const c=O(t),p=await P(c),d=p?(await v(p)).version:null;if(p&&!n.force&&!n.dryRun){this.activeInstalls.delete(t);const s=Date.now()-e;return m.info("installer",`${t} already installed at ${p}${d?` (v${d})`:""}`),{agentType:t,ok:!0,phase:"completed",installedPath:p,installedVersion:d,durationMs:s,output:"",environment:i}}const h=l.prerequisites??[];let r=[];if(h.length>0){m.info("installer",`Checking prerequisites for ${t}: ${h.join(", ")}`),r=await y(h,this.os),m.info("installer",`Prerequisites: ${r.map(a=>`${a.label}=${a.met?a.version:"missing"}`).join(", ")}`);const s=k(r);if(s.length>0){const a=s.map(g=>`${g.label}${g.minVersion?` >= ${g.minVersion}`:""}`).join(", ");if(!n.dryRun){if(n.skipPrereqInstall)return this.fail(t,"preflight",e,new u("PREREQ_MISSING",`Missing prerequisites: ${a}. Install them first or retry without skipPrereqInstall.`),i,r);const g=s.map($=>`${$.label}${$.minVersion?` >= ${$.minVersion}`:""}`);m.info("installer",`Will auto-install prerequisites: ${g.join(", ")}`),this.setProgress(t,"installing_prereq",e,s[0].label,g);const I=await C(s,this.os);if(!I.allOk){const $=I.results.find(M=>!M.ok),L=$?`Failed to install prerequisite ${$.prereq.label}: ${$.output}`:"Prerequisite installation failed";return this.fail(t,"installing_prereq",e,new u("PREREQ_INSTALL_FAILED",L),i,r)}m.info("installer",`All prerequisites installed for ${t}`),r=await y(h,this.os)}}}if(n.dryRun){this.activeInstalls.delete(t);const s=k(r),a=this.getManualHint(t,this.os),g={agentType:t,environment:i,canInstall:!0,alreadyInstalled:!!p,installedPath:p,installedVersion:d,installCommand:U(l),installMode:l.mode,prerequisites:r,missingPrerequisites:s,fallbackCommand:l.fallback?.command??null,manualHint:a},I=b({agentType:t,os:this.os,env:i,missingPrereqs:s});return{agentType:t,ok:!0,phase:"completed",durationMs:Date.now()-e,output:"dry-run: no commands executed",environment:i,dryRun:g,manualGuide:I}}this.setProgress(t,"installing",e),m.info("installer",`Installing ${t}: ${l.command}`);let f;try{f=await this.executeWithRetry(l,t,e,n.timeoutMs)}catch(s){if(l.fallback&&s instanceof u&&(s.code==="INSTALL_FAILED"||s.code==="INSTALL_TIMEOUT")){m.info("installer",`Primary install (${l.command}) failed: ${s.message}`),m.info("installer",`Trying fallback: ${l.fallback.command}`),this.setProgress(t,"installing",e);try{f=await this.executeWithRetry(l.fallback,t,e,n.timeoutMs),f=`[primary failed, fallback succeeded]
|
|
3
|
+
${f}`}catch(a){const g=s.message,I=a instanceof u?a.message:String(a),$=a instanceof u?a.output:void 0,L=X(s.output,$);return this.fail(t,"installing",e,new u("FALLBACK_EXHAUSTED",`Both primary and fallback install methods failed.
|
|
4
|
+
Primary: ${g}
|
|
5
|
+
Fallback: ${I}`,L),i,r)}}else return s instanceof u?this.fail(t,"installing",e,new u(s.code,s.message,s.output),i,r):this.fail(t,"installing",e,new u("INTERNAL",s instanceof Error?s.message:String(s)),i,r)}{const s=(f??"").trim().split(`
|
|
6
6
|
`).slice(-12).join(`
|
|
7
|
-
`);m.info("installer",`${
|
|
8
|
-
${s||"<\u7A7A>"}`)}if(x(),!n.skipVerify&&!l.skipVerification){this.setProgress(
|
|
9
|
-
${
|
|
10
|
-
`).slice(-
|
|
11
|
-
`),l=this.activeInstalls.get(n);l&&(l.outputTail=
|
|
7
|
+
`);m.info("installer",`${t} \u5B89\u88C5\u547D\u4EE4\u8F93\u51FA(\u5C3E\u90E8):
|
|
8
|
+
${s||"<\u7A7A>"}`)}if(x(),!n.skipVerify&&!l.skipVerification){this.setProgress(t,"verifying",e),m.info("installer",`Verifying ${t} installation...`);const s=await P(c);if(!s)return this.fail(t,"verifying",e,new u("VERIFICATION_FAILED",`${c} not found on PATH after installation. You may need to open a new terminal or run: source ~/.zshrc (or ~/.bashrc)`),i,r);let{version:a,error:g}=await v(s);a===null&&g&&(await this.sleep(1e3),{version:a,error:g}=await v(s));const I=Date.now()-e;return this.activeInstalls.delete(t),m.info("installer",`${t} installed successfully at ${s} (v${a??"unknown"}, ${I}ms)`),{agentType:t,ok:!0,phase:"completed",installedPath:s,installedVersion:a,durationMs:I,output:f,prerequisites:r.length>0?r:void 0,environment:i}}const E=Date.now()-e;return this.activeInstalls.delete(t),m.info("installer",`${t} install command completed (${E}ms, verification skipped)`),{agentType:t,ok:!0,phase:"completed",installedPath:null,installedVersion:null,durationMs:E,output:f,prerequisites:r.length>0?r:void 0,environment:i}}getManualHint(n,e){const i=S(n,e)?.fallback,l={claude:"https://docs.anthropic.com/en/docs/claude-code/overview",codex:"https://github.com/openai/codex",qwen:"https://github.com/QwenLM/qwen-code",cursor:"https://cursor.com/docs/cli/installation",copilot:"https://github.com/github/copilot-cli",kiro:"https://kiro.dev/docs/cli/",openclaw:"https://github.com/openclaw/openclaw",reasonix:"https://github.com/esengine/DeepSeek-Reasonix"}[n],c=[];return l&&c.push(`Docs: ${l}`),i&&c.push(`Alternative: ${i.command}`),c.length>0?c.join(" | "):null}setProgress(n,e,t,i,o){this.activeInstalls.set(n,{agentType:n,phase:e,startedAt:t,elapsedMs:Date.now()-t,...i?{currentPrereq:i}:{},...o?{pendingPrereqs:o}:{}})}fail(n,e,t,i,o,l,c){const p=c??i.output??this.activeInstalls.get(n)?.outputTail??"";this.activeInstalls.delete(n),m.error("installer",`${n} install failed at ${e}: ${i.message}`);const d=l?k(l):[],h=b({agentType:n,os:this.os,env:o??{platform:this.os,osVersion:"unknown",arch:process.arch,shell:process.env.SHELL??null,nodeVersion:null,npmVersion:null,isDocker:!1,isCI:!1},missingPrereqs:d,primaryFailed:e==="installing",fallbackFailed:i.code==="FALLBACK_EXHAUSTED",error:i.message});return{agentType:n,ok:!1,phase:"failed",error:{code:i.code,message:i.message},durationMs:Date.now()-t,output:p,environment:o,prerequisites:l,manualGuide:h}}async executeWithRetry(n,e,t,i){let o=null;for(let l=0;l<=N;l++)try{return l>0&&(m.info("installer",`Retry ${l}/${N} for ${e}...`),await this.sleep(W)),await this.executeCommand(n,e,t,i)}catch(c){if(o=c instanceof u?c:new u("INTERNAL",String(c)),!(o.code==="INSTALL_TIMEOUT"||o.code==="INSTALL_FAILED"&&G(o.message))||l>=N)throw o;m.info("installer",`Attempt ${l+1} failed (retryable): ${o.message}`)}throw o??new u("INTERNAL","Unexpected retry loop exit")}sleep(n){return new Promise(e=>setTimeout(e,n))}executeCommand(n,e,t,i){const o=i??n.timeoutMs;switch(n.mode){case"npm":return this.executeNpm(n.npmPackage,o,e,t);case"shell":return this.executeShell(n.command,o,e,t);case"exec":return this.executeExec(n.command,n.execArgs??[],o,e,t);default:return Promise.reject(new u("INTERNAL",`Unknown install mode: ${n.mode}`))}}async executeNpm(n,e,t,i){try{const{output:o,registry:l}=await j(n,e,T);return m.info("installer",`npm install ${n} succeeded via ${l}`),o}catch(o){const l=o instanceof Error?o.message:String(o),c=K(o);throw c&&this.updateOutputTail(t,i,c),l.includes("timed out")||l.includes("ETIMEDOUT")?new u("INSTALL_TIMEOUT",`npm install timed out (tried all mirrors): ${l}`,c):new u("INSTALL_FAILED",`npm install failed (tried all mirrors): ${l}`,c)}}executeShell(n,e,t,i){return new Promise((o,l)=>{const c=process.platform==="win32",p=c?"cmd.exe":"bash",d=c?["/d","/c",n]:["-c",`set -o pipefail; ${n}`];m.info("installer",`exec: ${p} ${d.join(" ")}`);const h=_(p,d,{timeout:e,stdio:["ignore","pipe","pipe"],...c?{windowsVerbatimArguments:!0}:{},env:{...process.env,NONINTERACTIVE:"1",DEBIAN_FRONTEND:"noninteractive"}});let r="",f=!1;const E=setTimeout(()=>{f=!0;try{h.kill("SIGTERM")}catch{}setTimeout(()=>{try{h.kill("SIGKILL")}catch{}},5e3).unref()},e);h.stdout?.on("data",s=>{const a=s.toString("utf-8");r+=a,r.length>T&&(r=r.slice(-T)),this.updateOutputTail(t,i,r)}),h.stderr?.on("data",s=>{const a=s.toString("utf-8");r+=a,r.length>T&&(r=r.slice(-T)),this.updateOutputTail(t,i,r)}),h.on("error",s=>{clearTimeout(E),l(new u("INSTALL_FAILED",`Spawn error: ${s.message}`))}),h.on("close",s=>{if(clearTimeout(E),f){l(new u("INSTALL_TIMEOUT",`Install timed out after ${e/1e3}s`,r.trim()));return}if(s!==0){const a=r.slice(-1024);l(new u("INSTALL_FAILED",`Process exited with code ${s}: ${a}`,r.trim()));return}o(r.trim())})})}executeExec(n,e,t,i,o){return new Promise((l,c)=>{m.info("installer",`exec: ${n} ${e.join(" ")}`);const p=R(n,e,{timeout:t,maxBuffer:T},(d,h,r)=>{const f=`${h??""}
|
|
9
|
+
${r??""}`.trim();if(f&&this.updateOutputTail(i,o,f),d){if(d.killed)c(new u("INSTALL_TIMEOUT",`${n} timed out after ${t/1e3}s`,f));else{const E=f||d.message;c(new u("INSTALL_FAILED",`${n} failed: ${E}`,f))}return}l(f)});this.trackOutput(p,i,o)})}trackOutput(n,e,t){n.on("close",()=>{const i=this.activeInstalls.get(e);i&&(i.elapsedMs=Date.now()-t)})}updateOutputTail(n,e,t){const o=t.split(`
|
|
10
|
+
`).slice(-H).join(`
|
|
11
|
+
`),l=this.activeInstalls.get(n);l&&(l.outputTail=o,l.elapsedMs=Date.now()-e)}}function K(w){if(w&&typeof w=="object"&&"output"in w){const n=w.output;return typeof n=="string"?n:""}return""}function X(...w){return w.map(e=>e?.trim()).filter(e=>!!e).join(`
|
|
12
|
+
`)||void 0}export{lt as AgentInstaller,u as InstallerError};
|
|
@@ -1,2 +1,4 @@
|
|
|
1
|
-
import{execFile as
|
|
2
|
-
${
|
|
1
|
+
import{execFile as d}from"node:child_process";import{promisify as x}from"node:util";import{log as b}from"../log/logger.js";import{probeUrls as $}from"./speed-test.js";const T=x(d),I=1e4;class E extends Error{output;constructor(r,e){super(r),this.name="NpmInstallError",this.output=e}}const u=[{id:"official",label:"npm \u5B98\u65B9",url:"https://registry.npmjs.org"},{id:"npmmirror",label:"npmmirror (\u6DD8\u5B9D\u955C\u50CF)",url:"https://registry.npmmirror.com"}];async function O(){const t=await $(u.map(e=>({url:e.url,label:e.label})),5e3),r=[];for(const e of t)if(e.reachable){const n=u.find(i=>i.url===e.url);n&&r.push(n)}for(const e of t)if(!e.reachable){const n=u.find(i=>i.url===e.url);n&&!r.includes(n)&&r.push(n)}for(const e of u)r.includes(e)||r.push(e);return r}async function D(){try{const t=process.platform==="win32",r=t?"cmd.exe":"npm",e=t?["/c","npm","config","get","registry"]:["config","get","registry"],{stdout:n}=await T(r,e,{timeout:I,encoding:"utf-8"});return n.trim()}catch{return"https://registry.npmjs.org"}}async function F(t,r,e,n){const i=await O();if(i.length===0)throw new Error("All npm registries are unreachable. Check your network connection.");let a="",c="";for(const o of i)try{return b.info("installer",`npm install ${t} using ${o.label} (${o.url})`),{output:await R(t,o.url,r,e,n),registry:o.label}}catch(s){const l=s instanceof Error?s.message:String(s),p=k(s);if(a=l,c=C(`${c}
|
|
2
|
+
[${o.label}] ${l}
|
|
3
|
+
${p}`.trim(),e),b.info("installer",`${o.label} failed: ${l.slice(0,200)}`),!N(l))throw new E(l,c)}throw new E(`npm install failed on all registries. Last error: ${a}`,c)}function N(t){return/ECONNRESET|ETIMEDOUT|ECONNREFUSED|ENOTFOUND|EAI_AGAIN|ERR_SOCKET_TIMEOUT|getaddrinfo|socket hang up|network|fetch failed|timed out|tunneling socket|self.signed|404|ETARGET|No matching version|notarget/i.test(t)}function j(t){return/\bEBUSY\b|resource busy or locked|EPERM[^\n]*\brename\b|\bELOCKED\b/i.test(t)}function R(t,r,e,n,i){return new Promise((a,c)=>{const o=["install","-g",t,"--registry",r,"--prefer-online","--no-audit","--no-fund"];i?.prefix&&o.push("--prefix",i.prefix);const s=process.platform==="win32",l=s?"cmd.exe":"npm",p=s?["/c","npm",...o]:o;d(l,p,{timeout:e,maxBuffer:n},(m,h,y)=>{const f=`${h??""}
|
|
4
|
+
${y??""}`.trim();if(m){const g=f||m.message,w=m.killed===!0?`ETIMEDOUT: npm install exceeded ${e}ms (registry likely unreachable): ${g}`:g;c(new E(w,f||g));return}a(f)})})}function k(t){if(t&&typeof t=="object"&&"output"in t){const r=t.output;return typeof r=="string"?r:""}return""}function C(t,r){return t.length<=r?t:t.slice(-r)}function G(t){return u.map(r=>({label:r.label,command:`npm install -g ${t} --registry ${r.url}`}))}export{u as NPM_REGISTRIES,E as NpmInstallError,D as getCurrentRegistry,G as getMirrorInstallCommands,N as isRetriableRegistryError,j as isTransientInstallError,F as npmInstallWithMirror,O as probeRegistries};
|