grix-connector 4.2.5 → 4.2.7
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/dist/adapter/claude/skill-scanner.js +2 -2
- package/dist/adapter/codewhale/codewhale-adapter.js +2 -2
- package/dist/adapter/deepseek-harness/dsh-provider-config.js +6 -6
- package/dist/adapter/deepseek-harness/index.js +1 -1
- package/dist/adapter/deepseek-harness/jsonrpc-adapter.js +2 -2
- package/dist/adapter/deepseek-harness/profile-runtime.js +2 -2
- package/dist/assets/dsh-bridge/{grix-dsh-bridge-4.2.5.tgz → grix-dsh-bridge-4.2.7.tgz} +0 -0
- package/dist/assets/dsh-bridge/manifest.json +4 -4
- package/dist/bridge/acp-toolbar-persist.js +1 -1
- package/dist/bridge/bridge.js +7 -7
- package/dist/bridge/event-queue.js +1 -1
- package/dist/core/aibot/client.js +2 -2
- package/dist/core/persistence/session-binding-store.js +2 -2
- package/dist/core/upgrade/upgrade-checker.js +1 -1
- package/dist/mcp/stream-http/security.js +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
const c=25e3,d=
|
|
1
|
+
const c=25e3,d=45e3,l=6e4,m=18e5;class f{config;callbacks;running=new Map;queued=[];timers=new Map;composingTimers=new Map;adapterDoneEventIds=new Set;pauseReasons=new Set;heldEventIds=new Map;get ready(){return this.pauseReasons.size===0}constructor(e,t){this.config=e,this.callbacks=t}pause(e){this.pauseReasons.add(e)}resume(e){this.pauseReasons.delete(e)&&this.ready&&this.drainNext()}submit(e){return this.running.has(e.event_id)||this.queued.some(t=>t.event_id===e.event_id)?"accepted":this.ready&&this.running.size<this.config.maxConcurrent?(this.startRunning(e),"accepted"):this.config.maxQueued<=0||this.queued.length>=this.config.maxQueued?(this.callbacks.onRejected(e,"queue full"),"rejected"):(this.enqueue(e),"accepted")}cancel(e){const t=this.queued.findIndex(i=>i.event_id===e);if(t>=0){if(!this.config.cancelableQueued)return!1;const[i]=this.queued.splice(t,1);return this.clearTimer(e),this.clearHold(e),this.callbacks.onStateChange(e,i.session_id,"canceled",{reason:"canceled by user"}),this.broadcastQueuePositions(),this.drainNext(),this.checkStopComposing(i.session_id),!0}return this.running.has(e)&&this.config.cancelableRunning?(this.callbacks.onCancelRunning(e),!0):!1}removeQueued(e){const t=this.queued.findIndex(u=>u.event_id===e);if(t<0)return!1;const[i]=this.queued.splice(t,1);this.clearTimer(e);const n=this.clearHold(e);return this.broadcastQueuePositions(),n&&this.drainNext(),this.checkStopComposing(i.session_id),!0}complete(e){const t=this.running.get(e);if(!t)return!1;const i=t?.session_id;return this.running.delete(e),this.adapterDoneEventIds.delete(e),this.clearTimer(e),queueMicrotask(()=>this.drainNext()),i&&this.checkStopComposing(i),!0}clear(e,t="queue cleared"){const i=[],n=[];let u=!1;for(const s of this.queued)s.session_id===e?(this.clearTimer(s.event_id),this.clearHold(s.event_id)&&(u=!0),this.callbacks.onStateChange(s.event_id,e,"canceled",{reason:t}),i.push(s.event_id)):n.push(s);return this.queued=n,i.length>0&&this.broadcastQueuePositions(),u&&this.drainNext(),this.checkStopComposing(e),i}reorder(e,t){const i=[],n=[];if(this.queued.forEach((o,r)=>{o.session_id===e&&(i.push(r),n.push(o))}),n.length===0)return[];const u=new Map(n.map(o=>[o.event_id,o])),s=[];for(const o of t){const r=u.get(o);r&&(u.delete(o),s.push(r))}for(const o of n)u.has(o.event_id)&&s.push(o);return s.some((o,r)=>o!==n[r])&&(i.forEach((o,r)=>{this.queued[o]=s[r]}),this.drainNext()),s.map(o=>o.event_id)}hold(e,t,i){if(!this.queued.find(r=>r.event_id===e))return"not_found";const s=Number.isFinite(i)&&i>0?Math.min(18e5,Math.max(6e4,Math.floor(i))):0,a=this.heldEventIds.get(e);a?a.timer&&clearTimeout(a.timer):this.clearTimer(e);const o=s>0?setTimeout(()=>{this.expireHold(e)},s):null;return o?.unref(),this.heldEventIds.set(e,{reason:t||"manual",expireAt:s>0?Date.now()+s:0,timer:o}),this.broadcastQueuePositions(),"ok"}release(e){return this.queued.findIndex(i=>i.event_id===e)<0?"not_found":(this.clearHold(e)&&(this.armQueueTimeout(e),this.broadcastQueuePositions(),queueMicrotask(()=>this.drainNext())),"ok")}editQueued(e,t){if(typeof t!="string"||t.trim().length===0)return"empty_content";const i=this.queued.find(n=>n.event_id===e);return i?(i.content=t,this.heldEventIds.has(e)?this.release(e):this.broadcastQueuePositions(),"ok"):"not_found"}expireHold(e){this.heldEventIds.has(e)&&this.release(e)==="not_found"&&this.clearHold(e)}clearHold(e){const t=this.heldEventIds.get(e);return t?(t.timer&&clearTimeout(t.timer),this.heldEventIds.delete(e),!0):!1}armQueueTimeout(e){if(this.config.queueTimeoutMs<=0)return;this.clearTimer(e);const t=setTimeout(()=>{this.timeoutEvent(e)},this.config.queueTimeoutMs);t.unref(),this.timers.set(e,t)}drainQueuedForSession(e){const t=[],i=[];for(const n of this.queued)n.session_id===e?(this.clearTimer(n.event_id),this.clearHold(n.event_id),t.push(n)):i.push(n);return this.queued=i,t}snapshot(e){const t=[...this.running.values()].filter(s=>s.session_id===e),i=t.map(s=>s.event_id),n=t.map(s=>({event_id:s.event_id,content_preview:this.buildQueueItemTitle(s.content),title:this.buildQueueItemTitle(s.content),summary:this.buildQueueItemTitle(s.content)})),u=this.queued.flatMap((s,a)=>{if(s.session_id!==e)return[];const o=this.heldEventIds.get(s.event_id);return[{event_id:s.event_id,position:a+1,content_preview:this.buildQueueItemTitle(s.content),content:s.content,title:this.buildQueueItemTitle(s.content),summary:this.buildQueueItemTitle(s.content),held:!!o,held_reason:o?.reason??""}]});return{running:i,running_items:n,queued:u}}hasCapacity(){return this.running.size<this.config.maxConcurrent}get runningCount(){return this.running.size}get queuedCount(){return this.queued.length}destroy(){for(const e of this.timers.values())clearTimeout(e);this.timers.clear();for(const e of this.heldEventIds.values())e.timer&&clearTimeout(e.timer);this.heldEventIds.clear();for(const e of this.composingTimers.values())clearInterval(e);this.composingTimers.clear(),this.queued=[],this.running.clear(),this.pauseReasons.clear()}enqueue(e){this.queued.push(e);const t=this.queued.length;this.callbacks.onStateChange(e.event_id,e.session_id,"queued",{queue_position:t,queue_total:t,actions:this.config.cancelableQueued?[{type:"cancel"}]:[],content_preview:this.buildQueueItemTitle(e.content),content:e.content,held:!1,held_reason:""}),this.armQueueTimeout(e.event_id),this.ensureComposing(e.session_id)}timeoutEvent(e){const t=this.queued.findIndex(n=>n.event_id===e);if(t<0||this.heldEventIds.has(e))return;const[i]=this.queued.splice(t,1);this.timers.delete(e),this.callbacks.onStateChange(e,i.session_id,"failed",{reason:"queue timeout"}),this.broadcastQueuePositions(),this.drainNext(),this.checkStopComposing(i.session_id)}startRunning(e){this.running.set(e.event_id,e),this.callbacks.onStateChange(e.event_id,e.session_id,"running",{actions:this.config.cancelableRunning?[{type:"stop"}]:[],content_preview:this.buildQueueItemTitle(e.content),content:e.content}),this.ensureComposing(e.session_id),this.callbacks.onDeliver(e)}drainNext(){for(;this.ready&&this.running.size<this.config.maxConcurrent&&this.queued.length>0&&!this.heldEventIds.has(this.queued[0].event_id);){const e=this.queued.shift();this.clearTimer(e.event_id),this.startRunning(e)}this.queued.length>0&&this.broadcastQueuePositions()}broadcastQueuePositions(){const e=this.queued.length;for(let t=0;t<e;t++){const i=this.queued[t],n=this.heldEventIds.get(i.event_id);this.callbacks.onStateChange(i.event_id,i.session_id,"queued",{queue_position:t+1,queue_total:e,actions:this.config.cancelableQueued?[{type:"cancel"}]:[],content_preview:this.buildQueueItemTitle(i.content),content:i.content,held:!!n,held_reason:n?.reason??""})}}clearTimer(e){const t=this.timers.get(e);t&&(clearTimeout(t),this.timers.delete(e))}buildQueueItemTitle(e){const t=String(e??"").replace(/\s+/g," ").trim();return t?t.length>64?`${t.slice(0,64)}...`:t:"Message"}ensureComposing(e){if(!this.callbacks.onComposing||this.composingTimers.has(e))return;this.callbacks.onComposing(e,!0,this.getFirstRunningEventId(e));const t=setInterval(()=>{this.sessionHasEvents(e)?this.callbacks.onComposing(e,!0,this.getFirstRunningEventId(e)):this.stopComposing(e)},25e3);t.unref(),this.composingTimers.set(e,t)}checkStopComposing(e){this.sessionHasEvents(e)||this.stopComposing(e)}markAdapterDone(e){const t=this.running.get(e);t&&(this.adapterDoneEventIds.add(e),this.sessionHasEvents(t.session_id)||this.stopComposing(t.session_id))}stopComposing(e){const t=this.composingTimers.get(e);t&&(clearInterval(t),this.composingTimers.delete(e)),this.callbacks.onComposing?.(e,!1)}sessionHasEvents(e){for(const t of this.running.values())if(t.session_id===e&&!this.adapterDoneEventIds.has(t.event_id))return!0;return this.queued.some(t=>t.session_id===e)}getFirstRunningEventId(e){for(const t of this.running.values())if(t.session_id===e&&!this.adapterDoneEventIds.has(t.event_id))return t.event_id}}export{f as EventQueue,c as QUEUE_COMPOSING_INTERVAL_MS,d as QUEUE_COMPOSING_TTL_MS};
|
|
@@ -1,2 +1,2 @@
|
|
|
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 h=E.slice(r,t),a=t-u;e+=!o&&a>=16?`"${h}"`:h;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,h)=>{const a=new y(this.config.url);this.ws=a;const d=setTimeout(()=>{if(!this.isConnectionCurrent(a,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(a,n)},15e3),_=++this.seq,b=setTimeout(()=>{if(!this.isConnectionCurrent(a,n))return;const c=this.pendingRequests.get(_);c&&(this.pendingRequests.delete(_),c.reject(new Error("Auth request timeout"))),this.cleanupSocket(a,n)},15e3);this.pendingRequests.set(_,{expected:["auth_ack"],resolve:c=>{clearTimeout(d);const l=c.payload;if(l.code===0){this.negotiatedCapabilities=new Set(Array.isArray(l.supported_capabilities)?l.supported_capabilities:[]),this.connected=!0,this.everConnected=!0,this.reconnectAttempts=0,l.heartbeat_sec&&(this.heartbeatSec=l.heartbeat_sec),l.ack_policy&&(this.ackPolicy=l.ack_policy,m.info("aibot",`ack_policy received: push_ack_timeout_ms=${l.ack_policy.push_ack_timeout_ms??"default"} max_retries=${l.ack_policy.max_retries??"default"} timeout_action=${l.ack_policy.timeout_action??"default"}`)),this.startHeartbeat();const f=this.flushOutboundBuffer(a,n);this.emit("auth",l),o(l),f.then(g=>{this.isConnectionCurrent(a,n)&&(g?(this.replayTerminalOutbox(),this.replayStopResultOutbox()):this.reconnectAfterOutboundFlushFailure(a,n))})}else l.code===w?(this.agentDeleted=!0,h(new C(`Agent deleted: code=${l.code} msg=${l.msg}`))):h(new Error(`Auth failed: code=${l.code} msg=${l.msg}`))},reject:c=>{clearTimeout(d),h(c)},timer:b}),a.on("open",async()=>{if(await u,!this.isConnectionCurrent(a,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,_)}),a.on("message",c=>{if(!this.isConnectionCurrent(a,n))return;let l;try{l=JSON.parse(D(c.toString()))}catch{return}try{this.handlePacket(l)}catch(f){this.emitClientError(new Error(`handlePacket error: ${f}`))}}),a.on("close",(c,l)=>{if(!this.isConnectionCurrent(a,n))return;const f=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=${l.toString()||"<none>"}`,O=this.outboundFlushInFlight;O&&O.socket===a&&O.connectionGeneration===n&&this.cancelOutboundFlush(O,f?"connection_lost":"discard",g),f?this.failNonReplayableOutputsForSocket(a,g):this.nonReplayableOutputEventsBySocket.delete(a),this.abortPendingOutputWrites(f,a,n,f?g:void 0),this.abortPendingAcceptedOutputs(f,a,n),this.clearAllEventCorrelations(),this.ws===a&&(this.ws=null),this.connectionGeneration++,this.emit("close",c,l.toString());const k=f;m.info("aibot",`ws closed agent=${this.config.clientType}:${this.config.agentId} code=${c} reason=${l.toString()||"<none>"} everConnected=${this.everConnected} reconnecting=${this.reconnecting} agentDeleted=${this.agentDeleted} willReconnect=${k}`),k&&this.attemptReconnect(c===1e3||c===1001)}),a.on("error",c=>{if(this.isConnectionCurrent(a,n)&&(this.emitClientError(c instanceof Error?c:new Error(String(c))),!this.connected)){const l=this.pendingRequests.get(_);l&&(this.pendingRequests.delete(_),clearTimeout(l.timer),l.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,75e3)),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(e=!1){if(this.reconnecting||this.agentDeleted)return;this.reconnecting=!0;const t=++this.reconnectRunGeneration;m.info("aibot",`attemptReconnect start agent=${this.config.clientType}:${this.config.agentId} fromAttempts=${this.reconnectAttempts} fastFirstAttempt=${e}`),this.emit("disconnected");let n=0;for(;this.reconnecting&&this.reconnectRunGeneration===t;){const s=e&&this.reconnectAttempts===0,i=s?Math.floor(Math.random()*500):Math.min(1e3*2**this.reconnectAttempts,3e4),r=s?0:Math.floor(i*.2*Math.random());if(this.reconnectAttempts++,await new Promise(h=>setTimeout(h,i+r)),!this.reconnecting||this.reconnectRunGeneration!==t)return;let u=null,o=this.connectionGeneration;try{const h=this.connect();if(u=this.ws,o=this.connectionGeneration,await h,this.reconnectRunGeneration!==t)return;if(!u||!this.isConnectionCurrent(u,o)){if(this.connected&&this.ws){this.reconnecting=!1;return}continue}const a=this.reconnectAttempts;this.reconnectAttempts=0,this.reconnecting=!1,m.info("aibot",`reconnect succeeded agent=${this.config.clientType}:${this.config.agentId} attempt=${a}`);return}catch(h){if(this.reconnectRunGeneration!==t)return;if(u&&!this.isConnectionCurrent(u,o)&&this.connected&&this.ws){this.reconnecting=!1;return}if(u&&this.isConnectionCurrent(u,o)){this.connectionGeneration++;try{u.close()}catch{}this.ws===u&&(this.ws=null)}const a=h instanceof Error?h.message:String(h);if(m.warn("aibot",`reconnect failed agent=${this.config.clientType}:${this.config.agentId} attempt=${this.reconnectAttempts} err=${a}`),h 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(a)){if(n++,n>=p.MAX_CONSECUTIVE_AUTH_FAILURES){this.reconnecting=!1,m.error("aibot",`reconnect giving up after ${n} consecutive auth failures agent=${this.config.clientType}:${this.config.agentId}`);return}}else n=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,h={cmd:e,seq:o,payload:t};this.packetLog?.logOutboundPacket(e,o,t,"sent");const a=this.ws,d=this.connectionGeneration,_=s;_&&this.trackOutboundEventCorrelation(e,o,t),i&&_&&(this.registerPendingOutputWrite(o,_,i,a,d),this.isAckRequiredOutput(e,t)&&this.registerPendingAcceptedOutput(o,_,i,a,d));let b=!1;try{const c=a.readyState,l=a.bufferedAmount;let f=!1;return a.send(JSON.stringify(h),g=>{if(g&&!f&&(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=${l} err=${g.message}`):m.info("aibot",`event_result ws send callback ok event=${v.event_id??""} status=${v.status??""} seq=${o} readyState=${c} bufferedAmount=${l}`)}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=${l} 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=${l}`)}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=${l} err=${g.message}`):m.info("aibot",`event_ack ws send ok event=${v.event_id??""} seq=${o} readyState=${c} bufferedAmount=${l}`)}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=${l} err=${g.message}`):m.info("aibot",`send_msg ws send ok event=${v.event_id??""} session=${v.session_id??""} seq=${o} readyState=${c} bufferedAmount=${l}`)}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=${l} err=${g.message}`)}const O=this.pendingOutputWrites.get(o),k=!!(O&&O.socket===a&&O.connectionGeneration===d);if(!this.isConnectionCurrent(a,d)){k&&(this.settlePendingOutputWrite(o,!1,!1,a,d),this.settlePendingAcceptedOutput(o,"retry",!1,a,d));return}if(!(i&&_&&!k)){if(g)this.removeSeqFromEventCorrelation(o),_?(this.settlePendingOutputWrite(o,!1,!0,a,d),this.settlePendingAcceptedOutput(o,"retry",!1,a,d)):i&&this.bufferOutboundEntry(i),n!==void 0?this.rejectPendingRequestAfterWriteFailure(o,e,g):i&&this.reconnectAfterOutboundWriteFailure(a,`${e} callback failed`);else if(_){const v=k;this.settlePendingOutputWrite(o,!0,!1,a,d),v&&this.trackNonReplayableOutput(a,e,t,o)}}}),f=!0,!b}catch(c){return this.isConnectionCurrent(a,d)?(this.removeSeqFromEventCorrelation(o),_?(this.settlePendingOutputWrite(o,!1,!0,a,d),this.settlePendingAcceptedOutput(o,"retry",!1,a,d)):i&&this.bufferOutboundEntry(i),this.emitClientError(new Error(`sendPacket failed: ${c}`)),i&&this.reconnectAfterOutboundWriteFailure(a,`${e} send threw`),!1):(this.settlePendingOutputWrite(o,!1,!1,a,d),this.settlePendingAcceptedOutput(o,"retry",!1,a,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 a;for(const d of this.eventCorrelations.values())this.eventHasCriticalDeliveryState(d.eventId)||(!a||d.touchedAt<a.touchedAt)&&(a=d);a&&this.releaseEventCorrelations(a.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 h=o.seqRanges[o.seqRanges.length-1];if(h&&t===h.end+1?h.end=t:(!h||t<h.start||t>h.end)&&o.seqRanges.push({start:t,end:t}),r){o.clientMsgIds.add(r);let a=this.clientMsgEventMap.get(r);a||(a=new Set,this.clientMsgEventMap.set(r,a)),a.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 h=e.payload,a=typeof h.client_msg_id=="string"?h.client_msg_id.trim():"";let d=e.seq>0&&this.pendingAcceptedOutputs.has(e.seq)?e.seq:void 0,_;if(d===void 0&&a){const l=this.pendingAcceptedSeqsByClientMsgId.get(a);if(l?.size===1){const f=l.values().next().value,g=this.pendingAcceptedOutputs.get(f),O=this.uniqueEventIdForClientMsgId(a);g&&O&&g.eventId===O&&(d=f,_=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 l=this.pendingOutputWrites.get(d);l&&this.settlePendingOutputWrite(d,!0,!1,l.socket,l.connectionGeneration),this.settlePendingAcceptedOutput(d,"accepted",!1)}else if(e.seq>0){const l=this.pendingOutputWrites.get(e.seq);l&&this.settlePendingOutputWrite(e.seq,!0,!1,l.socket,l.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 h="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 f=c.payload;if(m.info("aibot",`event_result ack event=${i.event_id} status=${i.status} attempt=${d}/${r} ack_event=${f.event_id??""} ack_status=${f.status??""}`),f.event_id!==i.event_id||f.status!==i.status)throw new Error(`event_result ACK mismatch: expected event=${i.event_id} status=${i.status}, got event=${f.event_id??""} status=${f.status??""}`);if(n&&(f.terminal_commit_token?.trim()!==n||f.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,f.event_id,f.status,f.terminal_commit_token,f.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 l=c.payload;if(m.warn("aibot",`event_result rejected event=${i.event_id} status=${i.status} attempt=${d}/${r} cmd=${c.cmd} code=${l.code??""} msg=${l.msg??""}${l.ref_cmd?` ref_cmd=${l.ref_cmd}`:""}${l.ref_id?` ref_id=${l.ref_id}`:""}`),n){h=`tokenized terminal rejected: cmd=${c.cmd} code=${l.code??""} msg=${l.msg??""}`;const f=(this.tokenizedTerminalRejections.get(i.event_id)??0)+1;if(this.tokenizedTerminalRejections.set(i.event_id,f),f<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:l.code,message:l.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 ${f} rejections, moved to dead-letter: event=${i.event_id} status=${i.status} cmd=${c.cmd} code=${l.code??""} msg=${l.msg??""}`)))}catch(g){h=`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}: ${h}`));break}return 0}try{return this.terminalOutbox.moveToDeadLetter(e,{responseCmd:c.cmd,code:l.code,message:l.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=${l.code??""} msg=${l.msg??""}`))),0}catch(f){h=`dead-letter persist failed: ${f instanceof Error?f.message:f}`,this.emitClientError(new Error(`event_result dead-letter persist failed: event=${i.event_id} status=${i.status}: ${h}`));break}}catch(c){const l=c instanceof Error?c.message:String(c);if(h=l,m.warn("aibot",`event_result attempt failed event=${i.event_id} status=${i.status} attempt=${d}/${r} err=${l}`),d===r)break;await new Promise(f=>setTimeout(f,o*d))}}const a=Date.now()+p.TERMINAL_RETRY_DELAY_MS;try{this.terminalOutbox.recordRetry(e,a,h)}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=${h}`)),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(a=>{r=a}),o=setTimeout(()=>{const a=this.pendingOutputWrites.get(e);if(!a||a.socket!==s||a.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 h=this.pendingOutputSeqsByEvent.get(t);h||(h=new Set,this.pendingOutputSeqsByEvent.set(t,h)),h.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(f=>{r=f}),o=Math.max(1,this.ackPolicy?.push_ack_timeout_ms??5e3),h=Math.max(1,this.ackPolicy?.max_retries??3),a=Math.max(1e3,Math.ceil(o*.25)),d=Math.max(p.MIN_REQUIRED_OUTPUT_ACK_TIMEOUT_MS,o*h+a),_=setTimeout(()=>{const f=this.pendingAcceptedOutputs.get(e);if(!(!f||f.socket!==s||f.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 f=this.pendingAcceptedSeqsByClientMsgId.get(c);f||(f=new Set,this.pendingAcceptedSeqsByClientMsgId.set(c,f)),f.add(e)}let l=this.pendingAcceptedOutputSeqsByEvent.get(t);l||(l=new Set,this.pendingAcceptedOutputSeqsByEvent.set(t,l)),l.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,h=typeof o.client_msg_id=="string"?o.client_msg_id.trim():"";if(h){const a=this.pendingAcceptedSeqsByClientMsgId.get(h);a?.delete(e),a?.size===0&&this.pendingAcceptedSeqsByClientMsgId.delete(h)}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 h=++this.seq,a={cmd:r,seq:h,payload:u};e.nextIndex=s,e.currentWritePending=!0,e.currentWriteOutcome=null,o&&this.isAckRequiredOutput(r,u)&&this.registerPendingAcceptedOutput(h,o,i,t,e.connectionGeneration);const d=await new Promise(_=>{let b=!1;const c=f=>{b||(b=!0,clearTimeout(l),e.currentWriteOutcome=f,_(f))},l=setTimeout(()=>c("uncertain"),p.OUTBOUND_WRITE_CALLBACK_TIMEOUT_MS);l.unref?.(),e.cancelCurrentWrite=()=>c("canceled");try{this.trackOutboundEventCorrelation(r,h,u),t.send(JSON.stringify(a),f=>{c(f?"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(h),this.settlePendingAcceptedOutput(h,"retry",!1);const _=this.retainOutboundFlushRemainder(e,d,`auth flush write failed for ${r} seq=${h} outcome=${d}`);return this.emitClientError(new Error(`outbound buffer flush failed at cmd=${r} seq=${h} outcome=${d}; ${_} packet(s) retained`)),!1}this.trackNonReplayableOutput(t,r,u,h),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],h=this.outboundEventId(o.cmd,o.payload);t!=="retry"&&this.isNonReplayableOutput(o.cmd,o.payload)&&h&&(this.markOutputIntegrityFailed(h,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
|
+
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 p}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 N}from"../persistence/stop-result-outbox.js";import{AUTH_CODE_AGENT_DELETED as w,KICKED_REASON_AGENT_DELETED as R,AgentDeletedError as C,RequestTimeoutError as B}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 h=E.slice(r,t),a=t-u;e+=!o&&a>=16?`"${h}"`:h;continue}e+=i,t++}return e}function U(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 x(E){const e=[...E??["exec_approve","exec_reject"]];return e.includes("apply_relay_state")||e.push("apply_relay_state"),e}const W="aibot-agent-api-v1",L=1;class m extends A{static DROPPABLE_COMMANDS=new Set(["update_binding_card"]);static OUTPUT_NACK_CMDS=new Set(["send_msg","client_stream_chunk"]);static UNMATCHED_NACK_LOG_WINDOW_MS=5*6e4;unmatchedNackLoggedAt=new Map;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:U(e.capabilities),localActions:x(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 N(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){p.warn("aibot",`file server pre-start failed: ${o}`)}}catch(o){p.warn("aibot",`tailnet detect failed: ${o}`)}})();return new Promise((o,h)=>{const a=new y(this.config.url);this.ws=a;const d=setTimeout(()=>{if(!this.isConnectionCurrent(a,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(a,n)},15e3),_=++this.seq,b=setTimeout(()=>{if(!this.isConnectionCurrent(a,n))return;const c=this.pendingRequests.get(_);c&&(this.pendingRequests.delete(_),c.reject(new Error("Auth request timeout"))),this.cleanupSocket(a,n)},15e3);this.pendingRequests.set(_,{expected:["auth_ack"],resolve:c=>{clearTimeout(d);const l=c.payload;if(l.code===0){this.negotiatedCapabilities=new Set(Array.isArray(l.supported_capabilities)?l.supported_capabilities:[]),this.connected=!0,this.everConnected=!0,this.reconnectAttempts=0,l.heartbeat_sec&&(this.heartbeatSec=l.heartbeat_sec),l.ack_policy&&(this.ackPolicy=l.ack_policy,p.info("aibot",`ack_policy received: push_ack_timeout_ms=${l.ack_policy.push_ack_timeout_ms??"default"} max_retries=${l.ack_policy.max_retries??"default"} timeout_action=${l.ack_policy.timeout_action??"default"}`)),this.startHeartbeat();const f=this.flushOutboundBuffer(a,n);this.emit("auth",l),o(l),f.then(g=>{this.isConnectionCurrent(a,n)&&(g?(this.replayTerminalOutbox(),this.replayStopResultOutbox()):this.reconnectAfterOutboundFlushFailure(a,n))})}else l.code===w?(this.agentDeleted=!0,h(new C(`Agent deleted: code=${l.code} msg=${l.msg}`))):h(new Error(`Auth failed: code=${l.code} msg=${l.msg}`))},reject:c=>{clearTimeout(d),h(c)},timer:b}),a.on("open",async()=>{if(await u,!this.isConnectionCurrent(a,n))return;const c={agent_id:this.config.agentId,api_key:this.config.apiKey,client_type:this.config.clientType,protocol_version:W,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,_)}),a.on("message",c=>{if(!this.isConnectionCurrent(a,n))return;let l;try{l=JSON.parse(D(c.toString()))}catch{return}try{this.handlePacket(l)}catch(f){this.emitClientError(new Error(`handlePacket error: ${f}`))}}),a.on("close",(c,l)=>{if(!this.isConnectionCurrent(a,n))return;const f=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=${l.toString()||"<none>"}`,O=this.outboundFlushInFlight;O&&O.socket===a&&O.connectionGeneration===n&&this.cancelOutboundFlush(O,f?"connection_lost":"discard",g),f?this.failNonReplayableOutputsForSocket(a,g):this.nonReplayableOutputEventsBySocket.delete(a),this.abortPendingOutputWrites(f,a,n,f?g:void 0),this.abortPendingAcceptedOutputs(f,a,n),this.clearAllEventCorrelations(),this.ws===a&&(this.ws=null),this.connectionGeneration++,this.emit("close",c,l.toString());const k=f;p.info("aibot",`ws closed agent=${this.config.clientType}:${this.config.agentId} code=${c} reason=${l.toString()||"<none>"} everConnected=${this.everConnected} reconnecting=${this.reconnecting} agentDeleted=${this.agentDeleted} willReconnect=${k}`),k&&this.attemptReconnect(c===1e3||c===1001)}),a.on("error",c=>{if(this.isConnectionCurrent(a,n)&&(this.emitClientError(c instanceof Error?c:new Error(String(c))),!this.connected)){const l=this.pendingRequests.get(_);l&&(this.pendingRequests.delete(_),clearTimeout(l.timer),l.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}p.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":this.logUnmatchedNack(e);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)||p.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&&(p.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)||p.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){p.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"))){p.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,75e3)),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 B(`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(){p.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(e=!1){if(this.reconnecting||this.agentDeleted)return;this.reconnecting=!0;const t=++this.reconnectRunGeneration;p.info("aibot",`attemptReconnect start agent=${this.config.clientType}:${this.config.agentId} fromAttempts=${this.reconnectAttempts} fastFirstAttempt=${e}`),this.emit("disconnected");let n=0;for(;this.reconnecting&&this.reconnectRunGeneration===t;){const s=e&&this.reconnectAttempts===0,i=s?Math.floor(Math.random()*500):Math.min(1e3*2**this.reconnectAttempts,3e4),r=s?0:Math.floor(i*.2*Math.random());if(this.reconnectAttempts++,await new Promise(h=>setTimeout(h,i+r)),!this.reconnecting||this.reconnectRunGeneration!==t)return;let u=null,o=this.connectionGeneration;try{const h=this.connect();if(u=this.ws,o=this.connectionGeneration,await h,this.reconnectRunGeneration!==t)return;if(!u||!this.isConnectionCurrent(u,o)){if(this.connected&&this.ws){this.reconnecting=!1;return}continue}const a=this.reconnectAttempts;this.reconnectAttempts=0,this.reconnecting=!1,p.info("aibot",`reconnect succeeded agent=${this.config.clientType}:${this.config.agentId} attempt=${a}`);return}catch(h){if(this.reconnectRunGeneration!==t)return;if(u&&!this.isConnectionCurrent(u,o)&&this.connected&&this.ws){this.reconnecting=!1;return}if(u&&this.isConnectionCurrent(u,o)){this.connectionGeneration++;try{u.close()}catch{}this.ws===u&&(this.ws=null)}const a=h instanceof Error?h.message:String(h);if(p.warn("aibot",`reconnect failed agent=${this.config.clientType}:${this.config.agentId} attempt=${this.reconnectAttempts} err=${a}`),h instanceof C){this.agentDeleted=!0,this.reconnecting=!1,p.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(a)){if(n++,n>=m.MAX_CONSECUTIVE_AUTH_FAILURES){this.reconnecting=!1,p.error("aibot",`reconnect giving up after ${n} consecutive auth failures agent=${this.config.clientType}:${this.config.agentId}`);return}}else n=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"),p.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)&&!m.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>m.BACKPRESSURE_THRESHOLD;if(!u||!m.DROPPABLE_COMMANDS.has(e)){if(u&&m.DROPPABLE_COMMANDS.has(e))return!1;const o=n??++this.seq,h={cmd:e,seq:o,payload:t};this.packetLog?.logOutboundPacket(e,o,t,"sent");const a=this.ws,d=this.connectionGeneration,_=s;_&&this.trackOutboundEventCorrelation(e,o,t),i&&_&&(this.registerPendingOutputWrite(o,_,i,a,d),this.isAckRequiredOutput(e,t)&&this.registerPendingAcceptedOutput(o,_,i,a,d));let b=!1;try{const c=a.readyState,l=a.bufferedAmount;let f=!1;return a.send(JSON.stringify(h),g=>{if(g&&!f&&(b=!0),e==="event_result"){const v=t;g?p.warn("aibot",`event_result ws send callback failed event=${v.event_id??""} status=${v.status??""} seq=${o} readyState=${c} bufferedAmount=${l} err=${g.message}`):p.info("aibot",`event_result ws send callback ok event=${v.event_id??""} status=${v.status??""} seq=${o} readyState=${c} bufferedAmount=${l}`)}else if(e==="client_stream_chunk"){const v=t;g?p.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=${l} err=${g.message}`):p.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=${l}`)}else if(e==="event_ack"){const v=t;g?p.warn("aibot",`event_ack ws send failed event=${v.event_id??""} seq=${o} readyState=${c} bufferedAmount=${l} err=${g.message}`):p.info("aibot",`event_ack ws send ok event=${v.event_id??""} seq=${o} readyState=${c} bufferedAmount=${l}`)}else if(e==="send_msg"){const v=t;g?p.warn("aibot",`send_msg ws send failed event=${v.event_id??""} session=${v.session_id??""} seq=${o} readyState=${c} bufferedAmount=${l} err=${g.message}`):p.info("aibot",`send_msg ws send ok event=${v.event_id??""} session=${v.session_id??""} seq=${o} readyState=${c} bufferedAmount=${l}`)}else if(g){const v=t;p.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=${l} err=${g.message}`)}const O=this.pendingOutputWrites.get(o),k=!!(O&&O.socket===a&&O.connectionGeneration===d);if(!this.isConnectionCurrent(a,d)){k&&(this.settlePendingOutputWrite(o,!1,!1,a,d),this.settlePendingAcceptedOutput(o,"retry",!1,a,d));return}if(!(i&&_&&!k)){if(g)this.removeSeqFromEventCorrelation(o),_?(this.settlePendingOutputWrite(o,!1,!0,a,d),this.settlePendingAcceptedOutput(o,"retry",!1,a,d)):i&&this.bufferOutboundEntry(i),n!==void 0?this.rejectPendingRequestAfterWriteFailure(o,e,g):i&&this.reconnectAfterOutboundWriteFailure(a,`${e} callback failed`);else if(_){const v=k;this.settlePendingOutputWrite(o,!0,!1,a,d),v&&this.trackNonReplayableOutput(a,e,t,o)}}}),f=!0,!b}catch(c){return this.isConnectionCurrent(a,d)?(this.removeSeqFromEventCorrelation(o),_?(this.settlePendingOutputWrite(o,!1,!0,a,d),this.settlePendingAcceptedOutput(o,"retry",!1,a,d)):i&&this.bufferOutboundEntry(i),this.emitClientError(new Error(`sendPacket failed: ${c}`)),i&&this.reconnectAfterOutboundWriteFailure(a,`${e} send threw`),!1):(this.settlePendingOutputWrite(o,!1,!1,a,d),this.settlePendingAcceptedOutput(o,"retry",!1,a,d),!1)}}}if(m.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;p.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(!m.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>m.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>=m.MAX_EVENT_CORRELATIONS){let a;for(const d of this.eventCorrelations.values())this.eventHasCriticalDeliveryState(d.eventId)||(!a||d.touchedAt<a.touchedAt)&&(a=d);a&&this.releaseEventCorrelations(a.eventId)}o={eventId:s,clientMsgIds:new Set,seqRanges:[],touchedAt:u,expiresAt:u+m.EVENT_CORRELATION_TTL_MS},this.eventCorrelations.set(s,o)}o.touchedAt=u,o.expiresAt=u+m.EVENT_CORRELATION_TTL_MS;const h=o.seqRanges[o.seqRanges.length-1];if(h&&t===h.end+1?h.end=t:(!h||t<h.start||t>h.end)&&o.seqRanges.push({start:t,end:t}),r){o.clientMsgIds.add(r);let a=this.clientMsgEventMap.get(r);a||(a=new Set,this.clientMsgEventMap.set(r,a)),a.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)}logUnmatchedNack(e){const t=e.payload??{},n=typeof t.cmd=="string"?t.cmd.trim():"";if(!n||m.OUTPUT_NACK_CMDS.has(n))return;const s=typeof t.session_id=="string"?t.session_id.trim():"",i=typeof t.code=="number"?t.code:Number(t.code??0)||0,r=`${n}|${s}|${i}`,u=Date.now(),o=this.unmatchedNackLoggedAt.get(r)??0;if(o>0&&u-o<m.UNMATCHED_NACK_LOG_WINDOW_MS)return;if(this.unmatchedNackLoggedAt.size>=256){for(const[a,d]of this.unmatchedNackLoggedAt)u-d>=m.UNMATCHED_NACK_LOG_WINDOW_MS&&this.unmatchedNackLoggedAt.delete(a);this.unmatchedNackLoggedAt.size>=256&&this.unmatchedNackLoggedAt.clear()}this.unmatchedNackLoggedAt.set(r,u);const h=typeof t.msg=="string"?t.msg:"";p.warn("aibot",`backend rejected ${n} session=${s||"<none>"} code=${i} msg=${h||"<none>"} seq=${e.seq} (repeats suppressed for ${m.UNMATCHED_NACK_LOG_WINDOW_MS/1e3}s)`)}handleCorrelatedSendResponse(e){if(e.cmd==="send_ack"){const h=e.payload,a=typeof h.client_msg_id=="string"?h.client_msg_id.trim():"";let d=e.seq>0&&this.pendingAcceptedOutputs.has(e.seq)?e.seq:void 0,_;if(d===void 0&&a){const l=this.pendingAcceptedSeqsByClientMsgId.get(a);if(l?.size===1){const f=l.values().next().value,g=this.pendingAcceptedOutputs.get(f),O=this.uniqueEventIdForClientMsgId(a);g&&O&&g.eventId===O&&(d=f,_=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 l=this.pendingOutputWrites.get(d);l&&this.settlePendingOutputWrite(d,!0,!1,l.socket,l.connectionGeneration),this.settlePendingAcceptedOutput(d,"accepted",!1)}else if(e.seq>0){const l=this.pendingOutputWrites.get(e.seq);l&&this.settlePendingOutputWrite(e.seq,!0,!1,l.socket,l.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||m.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),p.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"),m.TERMINAL_RETRY_DELAY_MS}if(!await this.awaitOutputDeliveryBarrier(t))return m.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}`)),m.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}`)),m.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 h="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;p.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 f=c.payload;if(p.info("aibot",`event_result ack event=${i.event_id} status=${i.status} attempt=${d}/${r} ack_event=${f.event_id??""} ack_status=${f.status??""}`),f.event_id!==i.event_id||f.status!==i.status)throw new Error(`event_result ACK mismatch: expected event=${i.event_id} status=${i.status}, got event=${f.event_id??""} status=${f.status??""}`);if(n&&(f.terminal_commit_token?.trim()!==n||f.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,f.event_id,f.status,f.terminal_commit_token,f.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 l=c.payload;if(p.warn("aibot",`event_result rejected event=${i.event_id} status=${i.status} attempt=${d}/${r} cmd=${c.cmd} code=${l.code??""} msg=${l.msg??""}${l.ref_cmd?` ref_cmd=${l.ref_cmd}`:""}${l.ref_id?` ref_id=${l.ref_id}`:""}`),n){h=`tokenized terminal rejected: cmd=${c.cmd} code=${l.code??""} msg=${l.msg??""}`;const f=(this.tokenizedTerminalRejections.get(i.event_id)??0)+1;if(this.tokenizedTerminalRejections.set(i.event_id,f),f<m.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:l.code,message:l.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 ${f} rejections, moved to dead-letter: event=${i.event_id} status=${i.status} cmd=${c.cmd} code=${l.code??""} msg=${l.msg??""}`)))}catch(g){h=`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}: ${h}`));break}return 0}try{return this.terminalOutbox.moveToDeadLetter(e,{responseCmd:c.cmd,code:l.code,message:l.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=${l.code??""} msg=${l.msg??""}`))),0}catch(f){h=`dead-letter persist failed: ${f instanceof Error?f.message:f}`,this.emitClientError(new Error(`event_result dead-letter persist failed: event=${i.event_id} status=${i.status}: ${h}`));break}}catch(c){const l=c instanceof Error?c.message:String(c);if(h=l,p.warn("aibot",`event_result attempt failed event=${i.event_id} status=${i.status} attempt=${d}/${r} err=${l}`),d===r)break;await new Promise(f=>setTimeout(f,o*d))}}const a=Date.now()+m.TERMINAL_RETRY_DELAY_MS;try{this.terminalOutbox.recordRetry(e,a,h)}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=${h}`)),m.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&&p.info("aibot",`purged ${t-this.outboundBuffer.length} buffered stream chunks for event=${e}`)}emitClientError(e){if(this.listenerCount("error")===0){p.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>m.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=m.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()+m.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}`)),m.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>=m.MAX_OUTBOUND_BUFFER_SIZE&&(this.outboundBuffer=this.outboundBuffer.filter(n=>m.BUFFER_OVERFLOW_RETAIN_COMMANDS.has(n.cmd)),this.outboundBuffer.length>=m.MAX_OUTBOUND_BUFFER_SIZE&&p.warn("aibot",`outbound buffer soft limit exceeded by ${this.outboundBuffer.length} retained packet(s); preserving required output`)),this.outboundBuffer.length>=m.MAX_OUTBOUND_BUFFER_HARD_SIZE?(t?this.markOutputIntegrityFailed(t,`outbound buffer hard limit ${m.MAX_OUTBOUND_BUFFER_HARD_SIZE} reached`):this.emitClientError(new Error(`outbound buffer hard limit ${m.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(a=>{r=a}),o=setTimeout(()=>{const a=this.pendingOutputWrites.get(e);if(!a||a.socket!==s||a.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`)},m.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 h=this.pendingOutputSeqsByEvent.get(t);h||(h=new Set,this.pendingOutputSeqsByEvent.set(t,h)),h.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(f=>{r=f}),o=Math.max(1,this.ackPolicy?.push_ack_timeout_ms??5e3),h=Math.max(1,this.ackPolicy?.max_retries??3),a=Math.max(1e3,Math.ceil(o*.25)),d=Math.max(m.MIN_REQUIRED_OUTPUT_ACK_TIMEOUT_MS,o*h+a),_=setTimeout(()=>{const f=this.pendingAcceptedOutputs.get(e);if(!(!f||f.socket!==s||f.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 f=this.pendingAcceptedSeqsByClientMsgId.get(c);f||(f=new Set,this.pendingAcceptedSeqsByClientMsgId.set(c,f)),f.add(e)}let l=this.pendingAcceptedOutputSeqsByEvent.get(t);l||(l=new Set,this.pendingAcceptedOutputSeqsByEvent.set(t,l)),l.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,h=typeof o.client_msg_id=="string"?o.client_msg_id.trim():"";if(h){const a=this.pendingAcceptedSeqsByClientMsgId.get(h);a?.delete(e),a?.size===0&&this.pendingAcceptedSeqsByClientMsgId.delete(h)}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 h=++this.seq,a={cmd:r,seq:h,payload:u};e.nextIndex=s,e.currentWritePending=!0,e.currentWriteOutcome=null,o&&this.isAckRequiredOutput(r,u)&&this.registerPendingAcceptedOutput(h,o,i,t,e.connectionGeneration);const d=await new Promise(_=>{let b=!1;const c=f=>{b||(b=!0,clearTimeout(l),e.currentWriteOutcome=f,_(f))},l=setTimeout(()=>c("uncertain"),m.OUTBOUND_WRITE_CALLBACK_TIMEOUT_MS);l.unref?.(),e.cancelCurrentWrite=()=>c("canceled");try{this.trackOutboundEventCorrelation(r,h,u),t.send(JSON.stringify(a),f=>{c(f?"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(h),this.settlePendingAcceptedOutput(h,"retry",!1);const _=this.retainOutboundFlushRemainder(e,d,`auth flush write failed for ${r} seq=${h} outcome=${d}`);return this.emitClientError(new Error(`outbound buffer flush failed at cmd=${r} seq=${h} outcome=${d}; ${_} packet(s) retained`)),!1}this.trackNonReplayableOutput(t,r,u,h),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],h=this.outboundEventId(o.cmd,o.payload);t!=="retry"&&this.isNonReplayableOutput(o.cmd,o.payload)&&h&&(this.markOutputIntegrityFailed(h,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<m.HEARTBEAT_MAX_FAILURES)&&(this.cleanupSocket(e,t),this.attemptReconnect()))})},this.heartbeatSec*1e3)}stopHeartbeat(){this.heartbeatTimer&&(clearInterval(this.heartbeatTimer),this.heartbeatTimer=null)}}export{m as AibotClient,D as preprocessLargeIntegers,U as withRequiredCapabilities};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{readJSONFile as S,writeJSONFileAtomic as b}from"../util/json-file.js";import{log as l}from"../log/index.js";import{SESSION_MODE_IDS as u}from"../../adapter/claude/protocol-contract.js";import{normalizeClaudeEffort as g}from"../config/claude-direct-env.js";import{chmodSync as M,copyFileSync as y,existsSync as f,renameSync as v,rmSync as w,writeFileSync as C}from"node:fs";import{randomUUID as A}from"node:crypto";const x=u.fullAuto;function a(h){const t=String(h??"").trim().toLowerCase();return t===u.approval||t===u.fullAuto?t:x}class N{bindings=new Map;filePath;legacyFilePath;writePromise=Promise.resolve();constructor(t,e){this.filePath=t??null,this.legacyFilePath=e&&e!==t?e:null}load(){if(!this.filePath)return;const t=f(this.filePath);let e=S(this.filePath),d=!1;if(t&&!Array.isArray(e)){const s=this.quarantineUnreadablePrimary();throw new Error(`Refusing to start with unreadable session bindings: primary=${this.filePath}`+(s?` quarantined=${s}`:"")+(this.legacyFilePath?` legacy=${this.legacyFilePath}`:"")+" (legacy fallback disabled to avoid clobbering newer bindings)")}if(!t&&this.legacyFilePath){d=f(this.legacyFilePath);const s=S(this.legacyFilePath);Array.isArray(s)&&(this.writeMigratedSnapshot(s),e=s,l.info("session-binding-store",`Migrated legacy bindings from ${this.legacyFilePath} to ${this.filePath}; legacy file retained`))}if(!Array.isArray(e)){if(t||d)throw new Error(`Refusing to start with unreadable session bindings: primary=${this.filePath}${this.legacyFilePath?` legacy=${this.legacyFilePath}`:""}`);return}this.bindings.clear();for(const s of e)s.aibotSessionId&&this.bindings.set(s.aibotSessionId,{...s,modeId:a(s.modeId),effort:g(s.effort),dshProviderId:typeof s.dshProviderId=="string"&&s.dshProviderId.trim()?s.dshProviderId.trim():void 0,dshModelId:typeof s.dshModelId=="string"&&s.dshModelId.trim()?s.dshModelId.trim():void 0,dshModeId:s.dshModeId===void 0?void 0:s.dshModeId==="full_auto"?"full_auto":"approval",dshThinking:s.dshThinking==="disabled"?"disabled":s.dshThinking==="enabled"?"enabled":void 0,dshReasoningEffort:s.dshReasoningEffort==="max"?"max":s.dshReasoningEffort==="high"?"high":void 0,dshMaxTokens:Number.isSafeInteger(s.dshMaxTokens)&&Number(s.dshMaxTokens)>0?Number(s.dshMaxTokens):void 0,dshAgentPreset:typeof s.dshAgentPreset=="string"&&s.dshAgentPreset.trim()?s.dshAgentPreset.trim():void 0,dshPluginIds:r(s.dshPluginIds),dshSkillIds:r(s.dshSkillIds),dshSettingsRevision:s.dshSettingsRevision===void 0?void 0:Number.isSafeInteger(s.dshSettingsRevision)&&Number(s.dshSettingsRevision)>=0?Number(s.dshSettingsRevision):0,dshProfileName:typeof s.dshProfileName=="string"&&s.dshProfileName.trim()?s.dshProfileName.trim():void 0,dshProfileSessionId:typeof s.dshProfileSessionId=="string"&&s.dshProfileSessionId.trim()?s.dshProfileSessionId.trim():void 0,dshProfileSessionCreated:s.dshProfileSessionCreated===!0,dshProfileEventCursor:Number.isSafeInteger(s.dshProfileEventCursor)&&Number(s.dshProfileEventCursor)>=0?Number(s.dshProfileEventCursor):0})}set(t,e,d){const s=this.bindings.get(t),i=a(d?.modeId??s?.modeId),n=e!==void 0?e:s?.cwd;this.bindings.set(t,{aibotSessionId:t,cwd:n,acpSessionId:s?.acpSessionId,claudeSessionId:s?.claudeSessionId,codexThreadId:s?.codexThreadId,codexModelId:s?.codexModelId,codexModeId:s?.codexModeId,codewhaleThreadId:s?.codewhaleThreadId,agyConversationId:s?.agyConversationId,codexReasoningEffort:s?.codexReasoningEffort,codexServiceTier:s?.codexServiceTier,codexSandboxMode:s?.codexSandboxMode,piSessionPath:s?.piSessionPath,modeId:i,modelId:s?.modelId,effort:s?.effort,cursorModeId:s?.cursorModeId,acpModelId:s?.acpModelId,acpModeId:s?.acpModeId,dshProviderId:s?.dshProviderId,dshModelId:s?.dshModelId,dshModeId:s?.dshModeId,dshThinking:s?.dshThinking,dshReasoningEffort:s?.dshReasoningEffort,dshMaxTokens:s?.dshMaxTokens,dshAgentPreset:s?.dshAgentPreset,dshPluginIds:s?.dshPluginIds,dshSkillIds:s?.dshSkillIds,dshSettingsRevision:s?.dshSettingsRevision,dshSettingsUpdatedAt:s?.dshSettingsUpdatedAt,dshProfileName:s?.dshProfileName,dshProfileSessionId:s?.dshProfileSessionId,dshProfileSessionCreated:s?.dshProfileSessionCreated,dshProfileEventCursor:s?.dshProfileEventCursor,updatedAt:Date.now()}),this.scheduleWrite()}setAcpSessionId(t,e){this.updateBinding(t,{acpSessionId:e})}setClaudeSessionId(t,e){this.updateBinding(t,{claudeSessionId:e})}getClaudeSessionId(t){return this.bindings.get(t)?.claudeSessionId}setCodexThreadId(t,e){this.updateBinding(t,{codexThreadId:e})}getCodexThreadId(t){return this.bindings.get(t)?.codexThreadId}setAgyConversationId(t,e){this.updateBinding(t,{agyConversationId:e})}getAgyConversationId(t){return this.bindings.get(t)?.agyConversationId}setCodexContext(t,e){this.updateBinding(t,{codexModelId:e.modelId,codexModeId:e.modeId,codexReasoningEffort:e.reasoningEffort,codexServiceTier:e.serviceTier,codexSandboxMode:e.sandboxMode})}getCodexModelId(t){return this.bindings.get(t)?.codexModelId}setCodexModelId(t,e){const d=this.bindings.get(t);if(!d)return;const s=String(e??"").trim();!s||d.codexModelId===s||(this.bindings.set(t,{...d,codexModelId:s,updatedAt:Date.now()}),this.scheduleWrite())}setCodexModeId(t,e){const d=this.bindings.get(t);if(!d)return;const s=String(e??"").trim();!s||d.codexModeId===s||(this.bindings.set(t,{...d,codexModeId:s,updatedAt:Date.now()}),this.scheduleWrite())}setCodexServiceTier(t,e){const d=this.bindings.get(t);if(!d)return;const s=String(e??"").trim();!s||d.codexServiceTier===s||(this.bindings.set(t,{...d,codexServiceTier:s,updatedAt:Date.now()}),this.scheduleWrite())}getCodexModeId(t){return this.bindings.get(t)?.codexModeId}getCodexReasoningEffort(t){return this.bindings.get(t)?.codexReasoningEffort}getCodexServiceTier(t){return this.bindings.get(t)?.codexServiceTier}getCodexSandboxMode(t){return this.bindings.get(t)?.codexSandboxMode}setPiSessionPath(t,e){this.updateBinding(t,{piSessionPath:e})}getPiSessionPath(t){return this.bindings.get(t)?.piSessionPath}setCodeWhaleThreadId(t,e){this.updateBinding(t,{codewhaleThreadId:e})}getCodeWhaleThreadId(t){return this.bindings.get(t)?.codewhaleThreadId}setModeId(t,e){const d=this.bindings.get(t);if(!d)return;const s=a(e);d.modeId!==s&&(this.bindings.set(t,{...d,modeId:s,updatedAt:Date.now()}),this.scheduleWrite())}ensureModeId(t,e=x){const d=this.bindings.get(t);d&&(d.modeId||(this.bindings.set(t,{...d,modeId:a(e),updatedAt:Date.now()}),this.scheduleWrite()))}getAcpSessionId(t){return this.bindings.get(t)?.acpSessionId}getModeId(t){const e=this.bindings.get(t)?.modeId;return e?a(e):void 0}setModelId(t,e){const d=this.bindings.get(t);if(!d)return;const s=e.trim();d.modelId!==s&&(this.bindings.set(t,{...d,modelId:s,updatedAt:Date.now()}),this.scheduleWrite())}getModelId(t){return this.bindings.get(t)?.modelId}setEffort(t,e){const d=this.bindings.get(t);if(!d)return;const s=g(e);s&&d.effort!==s&&(this.bindings.set(t,{...d,effort:s,updatedAt:Date.now()}),this.scheduleWrite())}getEffort(t){return g(this.bindings.get(t)?.effort)}setCursorModeId(t,e){const d=this.bindings.get(t);if(!d)return;const s=String(e??"").trim().toLowerCase(),i=s===""?void 0:s;d.cursorModeId!==i&&(this.bindings.set(t,{...d,cursorModeId:i,updatedAt:Date.now()}),this.scheduleWrite())}getCursorModeId(t){return this.bindings.get(t)?.cursorModeId}setAcpModelId(t,e){const d=this.bindings.get(t);if(!d)return;const s=String(e??"").trim(),i=s===""?void 0:s;d.acpModelId!==i&&(this.bindings.set(t,{...d,acpModelId:i,updatedAt:Date.now()}),this.scheduleWrite())}getAcpModelId(t){return this.bindings.get(t)?.acpModelId}setAcpModeId(t,e){const d=this.bindings.get(t);if(!d)return;const s=String(e??"").trim(),i=s===""?void 0:s;d.acpModeId!==i&&(this.bindings.set(t,{...d,acpModeId:i,updatedAt:Date.now()}),this.scheduleWrite())}getAcpModeId(t){return this.bindings.get(t)?.acpModeId}getDshSettings(t){const e=this.bindings.get(t);if(e)return{...e.dshProviderId?{providerId:e.dshProviderId}:{},...e.dshModelId?{modelId:e.dshModelId}:{},modeId:e.dshModeId==="full_auto"?"full_auto":"approval",...e.dshThinking?{thinking:e.dshThinking}:{},...e.dshReasoningEffort?{reasoningEffort:e.dshReasoningEffort}:{},...e.dshMaxTokens?{maxTokens:e.dshMaxTokens}:{},...e.dshAgentPreset?{agentPreset:e.dshAgentPreset}:{},pluginIds:r(e.dshPluginIds),skillIds:r(e.dshSkillIds),revision:e.dshSettingsRevision??0,...e.dshSettingsUpdatedAt?{updatedAt:e.dshSettingsUpdatedAt}:{}}}updateDshSettings(t,e){const d=this.bindings.get(t);if(!d)return;const s=Object.prototype.hasOwnProperty.call(e,"providerId")?String(e.providerId??"").trim()||void 0:d.dshProviderId,i=Object.prototype.hasOwnProperty.call(e,"modelId")?String(e.modelId??"").trim()||void 0:d.dshModelId,n=Object.prototype.hasOwnProperty.call(e,"modeId")?e.modeId==="full_auto"?"full_auto":"approval":d.dshModeId,o=Object.prototype.hasOwnProperty.call(e,"thinking")?e.thinking==="disabled"?"disabled":"enabled":d.dshThinking,c=Object.prototype.hasOwnProperty.call(e,"reasoningEffort")?e.reasoningEffort==="max"?"max":"high":d.dshReasoningEffort,I=Object.prototype.hasOwnProperty.call(e,"maxTokens")?Number.isSafeInteger(e.maxTokens)&&Number(e.maxTokens)>0?Number(e.maxTokens):void 0:d.dshMaxTokens;if(s===d.dshProviderId&&i===d.dshModelId&&n===d.dshModeId&&o===d.dshThinking&&c===d.dshReasoningEffort&&I===d.dshMaxTokens)return d.dshSettingsRevision??0;const P=Date.now(),m=(d.dshSettingsRevision??0)+1;return this.bindings.set(t,{...d,dshProviderId:s,dshModelId:i,dshModeId:n,dshThinking:o,dshReasoningEffort:c,dshMaxTokens:I,dshSettingsRevision:m,dshSettingsUpdatedAt:P,updatedAt:P}),this.scheduleWrite(),m}getDshSelectedProfile(t){const e=this.bindings.get(t)?.dshProfileName;return typeof e=="string"&&e.trim()?e.trim():void 0}isDshProfileLocked(t){return this.bindings.get(t)?.dshProfileSessionCreated===!0}setDshSelectedProfile(t,e){const d=this.bindings.get(t);if(!d)return!1;const s=String(e??"").trim();return s?d.dshProfileName===s?!0:d.dshProfileSessionCreated===!0?!1:(this.bindings.set(t,{...d,dshProfileName:s,dshProfileSessionId:void 0,dshProfileSessionCreated:!1,dshProfileEventCursor:0,updatedAt:Date.now()}),this.scheduleWrite(),!0):!1}getDshAgentPreset(t){const e=this.bindings.get(t)?.dshAgentPreset;return typeof e=="string"&&e.trim()?e.trim():void 0}isDshAgentPresetLocked(t){return this.bindings.get(t)?.dshProfileSessionCreated===!0}setDshAgentPreset(t,e){const d=this.bindings.get(t);if(!d||d.dshProfileSessionCreated===!0)return!1;const s=String(e??"").trim();return!s||d.dshAgentPreset===s?d.dshAgentPreset===s:(this.bindings.set(t,{...d,dshAgentPreset:s,updatedAt:Date.now()}),this.scheduleWrite(),!0)}getDshEnabledPlugins(t){return r(this.bindings.get(t)?.dshPluginIds)}setDshEnabledPlugins(t,e){const d=this.bindings.get(t);if(!d)return;const s=r(e),i=r(d.dshPluginIds);if(p(i,s))return d.dshSettingsRevision??0;const n=Date.now(),o=(d.dshSettingsRevision??0)+1;return this.bindings.set(t,{...d,dshPluginIds:s,dshSettingsRevision:o,dshSettingsUpdatedAt:n,updatedAt:n}),this.scheduleWrite(),o}getDshEnabledSkills(t){return r(this.bindings.get(t)?.dshSkillIds)}setDshEnabledSkills(t,e){const d=this.bindings.get(t);if(!d)return;const s=r(e),i=r(d.dshSkillIds);if(p(i,s))return d.dshSettingsRevision??0;const n=Date.now(),o=(d.dshSettingsRevision??0)+1;return this.bindings.set(t,{...d,dshSkillIds:s,dshSettingsRevision:o,dshSettingsUpdatedAt:n,updatedAt:n}),this.scheduleWrite(),o}getDshProfileBinding(t,e){const d=this.bindings.get(t);if(!(!d?.dshProfileSessionId||d.dshProfileName!==e))return{profileName:e,sessionId:d.dshProfileSessionId,sessionCreated:d.dshProfileSessionCreated===!0,eventCursor:d.dshProfileEventCursor??0}}setDshProfileBinding(t,e){const d=this.bindings.get(t);if(!d)return;const s=String(e.profileName).trim(),i=String(e.sessionId).trim();if(!s||!i)return;const n=Number.isSafeInteger(e.eventCursor)&&e.eventCursor>=0?e.eventCursor:0;d.dshProfileName===s&&d.dshProfileSessionId===i&&d.dshProfileSessionCreated===e.sessionCreated&&(d.dshProfileEventCursor??0)===n||(this.bindings.set(t,{...d,dshProfileName:s,dshProfileSessionId:i,dshProfileSessionCreated:e.sessionCreated,dshProfileEventCursor:n,updatedAt:Date.now()}),this.scheduleWrite())}get(t){return this.bindings.get(t)}getMostRecentlyUpdatedSessionId(t){const e=t?.requireCwd!==!1;let d,s=-1;for(const i of this.bindings.values())e&&!String(i.cwd??"").trim()||i.updatedAt>s&&(s=i.updatedAt,d=i.aibotSessionId);return d}delete(t){const e=this.bindings.get(t);e&&(l.info("session-binding-store",`delete binding session_id=${t} cwd=${e.cwd??""} codexThreadId=${e.codexThreadId??""}`),this.bindings.delete(t),this.scheduleWrite())}quarantineUnreadablePrimary(){if(!this.filePath||!f(this.filePath))return;const t=`${this.filePath}.corrupt.${Date.now()}`;try{return y(this.filePath,t),l.error("session-binding-store",`Copied unreadable primary bindings to ${t} (original retained to block legacy clobber)`),t}catch(e){l.error("session-binding-store",`Failed to copy unreadable primary ${this.filePath}: ${e instanceof Error?e.message:String(e)}`);return}}entries(){return this.bindings.entries()}async flush(){await this.writePromise}scheduleWrite(){this.filePath&&(this.writePromise=this.writePromise.then(()=>this.write()).catch(t=>{l.error("session-binding-store",`Persist failed: ${t instanceof Error?t.message:t}`)}))}async write(){if(!this.filePath)return;const t=Array.from(this.bindings.values());await b(this.filePath,t)}writeMigratedSnapshot(t){if(!this.filePath)return;const e=`${this.filePath}.${A()}.migration.tmp`;try{C(e,`${JSON.stringify(t,null,2)}
|
|
2
|
-
`,{encoding:"utf8",mode:384,flag:"wx"}),v(e,this.filePath);try{M(this.filePath,384)}catch{}}catch(
|
|
1
|
+
import{readJSONFile as x,writeJSONFileAtomic as b}from"../util/json-file.js";import{log as l}from"../log/index.js";import{SESSION_MODE_IDS as u}from"../../adapter/claude/protocol-contract.js";import{normalizeClaudeEffort as g}from"../config/claude-direct-env.js";import{chmodSync as M,copyFileSync as y,existsSync as f,renameSync as v,rmSync as w,writeFileSync as C}from"node:fs";import{randomUUID as A}from"node:crypto";const S=u.fullAuto;function a(h){const t=String(h??"").trim().toLowerCase();return t===u.approval||t===u.fullAuto?t:S}class N{bindings=new Map;filePath;legacyFilePath;writePromise=Promise.resolve();constructor(t,e){this.filePath=t??null,this.legacyFilePath=e&&e!==t?e:null}load(){if(!this.filePath)return;const t=f(this.filePath);let e=x(this.filePath),s=!1;if(t&&!Array.isArray(e)){const i=this.quarantineUnreadablePrimary();throw new Error(`Refusing to start with unreadable session bindings: primary=${this.filePath}`+(i?` quarantined=${i}`:"")+(this.legacyFilePath?` legacy=${this.legacyFilePath}`:"")+" (legacy fallback disabled to avoid clobbering newer bindings)")}if(!t&&this.legacyFilePath){s=f(this.legacyFilePath);const i=x(this.legacyFilePath);Array.isArray(i)&&(this.writeMigratedSnapshot(i),e=i,l.info("session-binding-store",`Migrated legacy bindings from ${this.legacyFilePath} to ${this.filePath}; legacy file retained`))}if(!Array.isArray(e)){if(t||s)throw new Error(`Refusing to start with unreadable session bindings: primary=${this.filePath}${this.legacyFilePath?` legacy=${this.legacyFilePath}`:""}`);return}this.bindings.clear();for(const i of e)i.aibotSessionId&&this.bindings.set(i.aibotSessionId,{...i,modeId:a(i.modeId),effort:g(i.effort),dshProviderId:typeof i.dshProviderId=="string"&&i.dshProviderId.trim()?i.dshProviderId.trim():void 0,dshModelId:typeof i.dshModelId=="string"&&i.dshModelId.trim()?i.dshModelId.trim():void 0,dshModeId:i.dshModeId===void 0?void 0:i.dshModeId==="full_auto"?"full_auto":"approval",dshThinking:i.dshThinking==="disabled"?"disabled":i.dshThinking==="enabled"?"enabled":void 0,dshReasoningEffort:i.dshReasoningEffort==="max"?"max":i.dshReasoningEffort==="high"?"high":void 0,dshMaxTokens:Number.isSafeInteger(i.dshMaxTokens)&&Number(i.dshMaxTokens)>0?Number(i.dshMaxTokens):void 0,dshAgentPreset:typeof i.dshAgentPreset=="string"&&i.dshAgentPreset.trim()?i.dshAgentPreset.trim():void 0,dshPluginIds:r(i.dshPluginIds),dshSkillIds:r(i.dshSkillIds),dshSettingsRevision:i.dshSettingsRevision===void 0?void 0:Number.isSafeInteger(i.dshSettingsRevision)&&Number(i.dshSettingsRevision)>=0?Number(i.dshSettingsRevision):0,dshProfileName:typeof i.dshProfileName=="string"&&i.dshProfileName.trim()?i.dshProfileName.trim():void 0,dshProfileSessionId:typeof i.dshProfileSessionId=="string"&&i.dshProfileSessionId.trim()?i.dshProfileSessionId.trim():void 0,dshProfileSessionCreated:i.dshProfileSessionCreated===!0,dshProfileEventCursor:Number.isSafeInteger(i.dshProfileEventCursor)&&Number(i.dshProfileEventCursor)>=0?Number(i.dshProfileEventCursor):0})}set(t,e,s){const i=this.bindings.get(t),d=a(s?.modeId??i?.modeId),n=e!==void 0?e:i?.cwd;this.bindings.set(t,{aibotSessionId:t,cwd:n,acpSessionId:i?.acpSessionId,claudeSessionId:i?.claudeSessionId,codexThreadId:i?.codexThreadId,codexModelId:i?.codexModelId,codexModeId:i?.codexModeId,codewhaleThreadId:i?.codewhaleThreadId,agyConversationId:i?.agyConversationId,codexReasoningEffort:i?.codexReasoningEffort,codexServiceTier:i?.codexServiceTier,codexSandboxMode:i?.codexSandboxMode,piSessionPath:i?.piSessionPath,modeId:d,modelId:i?.modelId,effort:i?.effort,cursorModeId:i?.cursorModeId,acpModelId:i?.acpModelId,acpModeId:i?.acpModeId,dshProviderId:i?.dshProviderId,dshModelId:i?.dshModelId,dshModeId:i?.dshModeId,dshThinking:i?.dshThinking,dshReasoningEffort:i?.dshReasoningEffort,dshMaxTokens:i?.dshMaxTokens,dshAgentPreset:i?.dshAgentPreset,dshPluginIds:i?.dshPluginIds,dshSkillIds:i?.dshSkillIds,dshSettingsRevision:i?.dshSettingsRevision,dshSettingsUpdatedAt:i?.dshSettingsUpdatedAt,dshProfileName:i?.dshProfileName,dshProfileSessionId:i?.dshProfileSessionId,dshProfileSessionCreated:i?.dshProfileSessionCreated,dshProfileEventCursor:i?.dshProfileEventCursor,updatedAt:Date.now()}),this.scheduleWrite()}setAcpSessionId(t,e){this.updateBinding(t,{acpSessionId:e})}setClaudeSessionId(t,e){this.updateBinding(t,{claudeSessionId:e})}getClaudeSessionId(t){return this.bindings.get(t)?.claudeSessionId}setCodexThreadId(t,e){this.updateBinding(t,{codexThreadId:e})}getCodexThreadId(t){return this.bindings.get(t)?.codexThreadId}setAgyConversationId(t,e){this.updateBinding(t,{agyConversationId:e})}getAgyConversationId(t){return this.bindings.get(t)?.agyConversationId}setCodexContext(t,e){this.updateBinding(t,{codexModelId:e.modelId,codexModeId:e.modeId,codexReasoningEffort:e.reasoningEffort,codexServiceTier:e.serviceTier,codexSandboxMode:e.sandboxMode})}getCodexModelId(t){return this.bindings.get(t)?.codexModelId}setCodexModelId(t,e){const s=this.bindings.get(t);if(!s)return;const i=String(e??"").trim();!i||s.codexModelId===i||(this.bindings.set(t,{...s,codexModelId:i,updatedAt:Date.now()}),this.scheduleWrite())}setCodexModeId(t,e){const s=this.bindings.get(t);if(!s)return;const i=String(e??"").trim();!i||s.codexModeId===i||(this.bindings.set(t,{...s,codexModeId:i,updatedAt:Date.now()}),this.scheduleWrite())}setCodexServiceTier(t,e){const s=this.bindings.get(t);if(!s)return;const i=String(e??"").trim();!i||s.codexServiceTier===i||(this.bindings.set(t,{...s,codexServiceTier:i,updatedAt:Date.now()}),this.scheduleWrite())}setCodexReasoningEffort(t,e){const s=this.bindings.get(t);if(!s)return;const i=String(e??"").trim();!i||s.codexReasoningEffort===i||(this.bindings.set(t,{...s,codexReasoningEffort:i,updatedAt:Date.now()}),this.scheduleWrite())}setCodexSandboxMode(t,e){const s=this.bindings.get(t);if(!s)return;const i=String(e??"").trim();!i||s.codexSandboxMode===i||(this.bindings.set(t,{...s,codexSandboxMode:i,updatedAt:Date.now()}),this.scheduleWrite())}getCodexModeId(t){return this.bindings.get(t)?.codexModeId}getCodexReasoningEffort(t){return this.bindings.get(t)?.codexReasoningEffort}getCodexServiceTier(t){return this.bindings.get(t)?.codexServiceTier}getCodexSandboxMode(t){return this.bindings.get(t)?.codexSandboxMode}setPiSessionPath(t,e){this.updateBinding(t,{piSessionPath:e})}getPiSessionPath(t){return this.bindings.get(t)?.piSessionPath}setCodeWhaleThreadId(t,e){this.updateBinding(t,{codewhaleThreadId:e})}getCodeWhaleThreadId(t){return this.bindings.get(t)?.codewhaleThreadId}setModeId(t,e){const s=this.bindings.get(t);if(!s)return;const i=a(e);s.modeId!==i&&(this.bindings.set(t,{...s,modeId:i,updatedAt:Date.now()}),this.scheduleWrite())}ensureModeId(t,e=S){const s=this.bindings.get(t);s&&(s.modeId||(this.bindings.set(t,{...s,modeId:a(e),updatedAt:Date.now()}),this.scheduleWrite()))}getAcpSessionId(t){return this.bindings.get(t)?.acpSessionId}getModeId(t){const e=this.bindings.get(t)?.modeId;return e?a(e):void 0}setModelId(t,e){const s=this.bindings.get(t);if(!s)return;const i=e.trim();s.modelId!==i&&(this.bindings.set(t,{...s,modelId:i,updatedAt:Date.now()}),this.scheduleWrite())}getModelId(t){return this.bindings.get(t)?.modelId}setEffort(t,e){const s=this.bindings.get(t);if(!s)return;const i=g(e);i&&s.effort!==i&&(this.bindings.set(t,{...s,effort:i,updatedAt:Date.now()}),this.scheduleWrite())}getEffort(t){return g(this.bindings.get(t)?.effort)}setCursorModeId(t,e){const s=this.bindings.get(t);if(!s)return;const i=String(e??"").trim().toLowerCase(),d=i===""?void 0:i;s.cursorModeId!==d&&(this.bindings.set(t,{...s,cursorModeId:d,updatedAt:Date.now()}),this.scheduleWrite())}getCursorModeId(t){return this.bindings.get(t)?.cursorModeId}setAcpModelId(t,e){const s=this.bindings.get(t);if(!s)return;const i=String(e??"").trim(),d=i===""?void 0:i;s.acpModelId!==d&&(this.bindings.set(t,{...s,acpModelId:d,updatedAt:Date.now()}),this.scheduleWrite())}getAcpModelId(t){return this.bindings.get(t)?.acpModelId}setAcpModeId(t,e){const s=this.bindings.get(t);if(!s)return;const i=String(e??"").trim(),d=i===""?void 0:i;s.acpModeId!==d&&(this.bindings.set(t,{...s,acpModeId:d,updatedAt:Date.now()}),this.scheduleWrite())}getAcpModeId(t){return this.bindings.get(t)?.acpModeId}getDshSettings(t){const e=this.bindings.get(t);if(e)return{...e.dshProviderId?{providerId:e.dshProviderId}:{},...e.dshModelId?{modelId:e.dshModelId}:{},modeId:e.dshModeId==="full_auto"?"full_auto":"approval",...e.dshThinking?{thinking:e.dshThinking}:{},...e.dshReasoningEffort?{reasoningEffort:e.dshReasoningEffort}:{},...e.dshMaxTokens?{maxTokens:e.dshMaxTokens}:{},...e.dshAgentPreset?{agentPreset:e.dshAgentPreset}:{},pluginIds:r(e.dshPluginIds),skillIds:r(e.dshSkillIds),revision:e.dshSettingsRevision??0,...e.dshSettingsUpdatedAt?{updatedAt:e.dshSettingsUpdatedAt}:{}}}updateDshSettings(t,e){const s=this.bindings.get(t);if(!s)return;const i=Object.prototype.hasOwnProperty.call(e,"providerId")?String(e.providerId??"").trim()||void 0:s.dshProviderId,d=Object.prototype.hasOwnProperty.call(e,"modelId")?String(e.modelId??"").trim()||void 0:s.dshModelId,n=Object.prototype.hasOwnProperty.call(e,"modeId")?e.modeId==="full_auto"?"full_auto":"approval":s.dshModeId,o=Object.prototype.hasOwnProperty.call(e,"thinking")?e.thinking==="disabled"?"disabled":"enabled":s.dshThinking,c=Object.prototype.hasOwnProperty.call(e,"reasoningEffort")?e.reasoningEffort==="max"?"max":"high":s.dshReasoningEffort,I=Object.prototype.hasOwnProperty.call(e,"maxTokens")?Number.isSafeInteger(e.maxTokens)&&Number(e.maxTokens)>0?Number(e.maxTokens):void 0:s.dshMaxTokens;if(i===s.dshProviderId&&d===s.dshModelId&&n===s.dshModeId&&o===s.dshThinking&&c===s.dshReasoningEffort&&I===s.dshMaxTokens)return s.dshSettingsRevision??0;const P=Date.now(),m=(s.dshSettingsRevision??0)+1;return this.bindings.set(t,{...s,dshProviderId:i,dshModelId:d,dshModeId:n,dshThinking:o,dshReasoningEffort:c,dshMaxTokens:I,dshSettingsRevision:m,dshSettingsUpdatedAt:P,updatedAt:P}),this.scheduleWrite(),m}getDshSelectedProfile(t){const e=this.bindings.get(t)?.dshProfileName;return typeof e=="string"&&e.trim()?e.trim():void 0}isDshProfileLocked(t){return this.bindings.get(t)?.dshProfileSessionCreated===!0}setDshSelectedProfile(t,e){const s=this.bindings.get(t);if(!s)return!1;const i=String(e??"").trim();return i?s.dshProfileName===i?!0:s.dshProfileSessionCreated===!0?!1:(this.bindings.set(t,{...s,dshProfileName:i,dshProfileSessionId:void 0,dshProfileSessionCreated:!1,dshProfileEventCursor:0,updatedAt:Date.now()}),this.scheduleWrite(),!0):!1}getDshAgentPreset(t){const e=this.bindings.get(t)?.dshAgentPreset;return typeof e=="string"&&e.trim()?e.trim():void 0}isDshAgentPresetLocked(t){return this.bindings.get(t)?.dshProfileSessionCreated===!0}setDshAgentPreset(t,e){const s=this.bindings.get(t);if(!s||s.dshProfileSessionCreated===!0)return!1;const i=String(e??"").trim();return!i||s.dshAgentPreset===i?s.dshAgentPreset===i:(this.bindings.set(t,{...s,dshAgentPreset:i,updatedAt:Date.now()}),this.scheduleWrite(),!0)}getDshEnabledPlugins(t){return r(this.bindings.get(t)?.dshPluginIds)}setDshEnabledPlugins(t,e){const s=this.bindings.get(t);if(!s)return;const i=r(e),d=r(s.dshPluginIds);if(p(d,i))return s.dshSettingsRevision??0;const n=Date.now(),o=(s.dshSettingsRevision??0)+1;return this.bindings.set(t,{...s,dshPluginIds:i,dshSettingsRevision:o,dshSettingsUpdatedAt:n,updatedAt:n}),this.scheduleWrite(),o}getDshEnabledSkills(t){return r(this.bindings.get(t)?.dshSkillIds)}setDshEnabledSkills(t,e){const s=this.bindings.get(t);if(!s)return;const i=r(e),d=r(s.dshSkillIds);if(p(d,i))return s.dshSettingsRevision??0;const n=Date.now(),o=(s.dshSettingsRevision??0)+1;return this.bindings.set(t,{...s,dshSkillIds:i,dshSettingsRevision:o,dshSettingsUpdatedAt:n,updatedAt:n}),this.scheduleWrite(),o}getDshProfileBinding(t,e){const s=this.bindings.get(t);if(!(!s?.dshProfileSessionId||s.dshProfileName!==e))return{profileName:e,sessionId:s.dshProfileSessionId,sessionCreated:s.dshProfileSessionCreated===!0,eventCursor:s.dshProfileEventCursor??0}}setDshProfileBinding(t,e){const s=this.bindings.get(t);if(!s)return;const i=String(e.profileName).trim(),d=String(e.sessionId).trim();if(!i||!d)return;const n=Number.isSafeInteger(e.eventCursor)&&e.eventCursor>=0?e.eventCursor:0;s.dshProfileName===i&&s.dshProfileSessionId===d&&s.dshProfileSessionCreated===e.sessionCreated&&(s.dshProfileEventCursor??0)===n||(this.bindings.set(t,{...s,dshProfileName:i,dshProfileSessionId:d,dshProfileSessionCreated:e.sessionCreated,dshProfileEventCursor:n,updatedAt:Date.now()}),this.scheduleWrite())}get(t){return this.bindings.get(t)}getMostRecentlyUpdatedSessionId(t){const e=t?.requireCwd!==!1;let s,i=-1;for(const d of this.bindings.values())e&&!String(d.cwd??"").trim()||d.updatedAt>i&&(i=d.updatedAt,s=d.aibotSessionId);return s}delete(t){const e=this.bindings.get(t);e&&(l.info("session-binding-store",`delete binding session_id=${t} cwd=${e.cwd??""} codexThreadId=${e.codexThreadId??""}`),this.bindings.delete(t),this.scheduleWrite())}quarantineUnreadablePrimary(){if(!this.filePath||!f(this.filePath))return;const t=`${this.filePath}.corrupt.${Date.now()}`;try{return y(this.filePath,t),l.error("session-binding-store",`Copied unreadable primary bindings to ${t} (original retained to block legacy clobber)`),t}catch(e){l.error("session-binding-store",`Failed to copy unreadable primary ${this.filePath}: ${e instanceof Error?e.message:String(e)}`);return}}entries(){return this.bindings.entries()}async flush(){await this.writePromise}scheduleWrite(){this.filePath&&(this.writePromise=this.writePromise.then(()=>this.write()).catch(t=>{l.error("session-binding-store",`Persist failed: ${t instanceof Error?t.message:t}`)}))}async write(){if(!this.filePath)return;const t=Array.from(this.bindings.values());await b(this.filePath,t)}writeMigratedSnapshot(t){if(!this.filePath)return;const e=`${this.filePath}.${A()}.migration.tmp`;try{C(e,`${JSON.stringify(t,null,2)}
|
|
2
|
+
`,{encoding:"utf8",mode:384,flag:"wx"}),v(e,this.filePath);try{M(this.filePath,384)}catch{}}catch(s){if(f(e))try{w(e)}catch{}throw new Error(`Failed to migrate legacy session bindings from ${this.legacyFilePath} to ${this.filePath}: ${s instanceof Error?s.message:String(s)}`)}}updateBinding(t,e){const s=this.bindings.get(t),i={aibotSessionId:t,acpSessionId:Object.prototype.hasOwnProperty.call(e,"acpSessionId")?e.acpSessionId:s?.acpSessionId,claudeSessionId:Object.prototype.hasOwnProperty.call(e,"claudeSessionId")?e.claudeSessionId:s?.claudeSessionId,codexThreadId:Object.prototype.hasOwnProperty.call(e,"codexThreadId")?e.codexThreadId:s?.codexThreadId,codexModelId:Object.prototype.hasOwnProperty.call(e,"codexModelId")?e.codexModelId:s?.codexModelId,codexModeId:Object.prototype.hasOwnProperty.call(e,"codexModeId")?e.codexModeId:s?.codexModeId,codewhaleThreadId:Object.prototype.hasOwnProperty.call(e,"codewhaleThreadId")?e.codewhaleThreadId:s?.codewhaleThreadId,agyConversationId:Object.prototype.hasOwnProperty.call(e,"agyConversationId")?e.agyConversationId:s?.agyConversationId,codexReasoningEffort:Object.prototype.hasOwnProperty.call(e,"codexReasoningEffort")?e.codexReasoningEffort:s?.codexReasoningEffort,codexServiceTier:Object.prototype.hasOwnProperty.call(e,"codexServiceTier")?e.codexServiceTier:s?.codexServiceTier,codexSandboxMode:Object.prototype.hasOwnProperty.call(e,"codexSandboxMode")?e.codexSandboxMode:s?.codexSandboxMode,piSessionPath:Object.prototype.hasOwnProperty.call(e,"piSessionPath")?e.piSessionPath:s?.piSessionPath,cwd:s?.cwd,modeId:a(s?.modeId),modelId:s?.modelId,effort:Object.prototype.hasOwnProperty.call(e,"effort")?g(e.effort):g(s?.effort),cursorModeId:s?.cursorModeId,acpModelId:s?.acpModelId,acpModeId:s?.acpModeId,dshProviderId:s?.dshProviderId,dshModelId:s?.dshModelId,dshModeId:s?.dshModeId,dshMaxTokens:s?.dshMaxTokens,dshAgentPreset:s?.dshAgentPreset,dshPluginIds:s?.dshPluginIds,dshSkillIds:s?.dshSkillIds,dshSettingsRevision:s?.dshSettingsRevision,dshSettingsUpdatedAt:s?.dshSettingsUpdatedAt,dshProfileName:s?.dshProfileName,dshProfileSessionId:s?.dshProfileSessionId,dshProfileSessionCreated:s?.dshProfileSessionCreated,dshProfileEventCursor:s?.dshProfileEventCursor,updatedAt:Date.now()};this.bindings.set(t,i),this.scheduleWrite()}}function r(h){const t=new Set,e=[];for(const s of h??[]){const i=String(s??"").trim();!i||t.has(i)||(t.add(i),e.push(i))}return e}function p(h,t){return h.length===t.length&&h.every((e,s)=>e===t[s])}export{N as SessionBindingStore};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{existsSync as v,mkdirSync as K,readFileSync as S,renameSync as X,writeFileSync as J}from"node:fs";import{join as g,dirname as Y}from"node:path";import{spawn as D}from"node:child_process";import{log as f}from"../log/index.js";import{GRIX_PATHS as _}from"../log/index.js";import{resolveClientVersion as R}from"../util/client-version.js";import{UpgradeError as T,armWindowsUpgradeHandoff as U,armWindowsUpgradeRollback as Z,cleanupPendingBackup as $,collectEnvInfo as Q,discardPendingStaging as N,finalizePendingTransaction as P,getUpgradeLogTail as ee,isVersionAvailable as te,npmInstall as ae,normalizeUpgradeVersion as re,pendingExists as O,preflightCheck as ie,readPending as k,readPendingPackageVersion as ne,removeWindowsHandoffFiles as se,rollbackPendingInstall as oe,updatePending as E,upgradeLog as n,verifyInstalledVersion as de,writePending as ce}from"./npm-upgrader.js";import{isRetriableRegistryError as le,isTransientInstallError as ue}from"../installer/npm-registry.js";const pe=360*60*1e3,he=300*1e3,G=1800*1e3,fe=2,ge=3,me=10,M="grix-connector",W=1e4,L=15,_e=2e3,we=8e3,ve=1e3,ye=2e3,be=120*1e3,C=1e4,Ae=6e4,Se=3,Te=600*1e3,Pe=new Set(["preparing","staged","handoff_ready","activating","verifying","healthy","rolling_back","rolled_back","finalizing","failed"]),x=600*1e3,I=1800*1e3;function q(s,e=x){const t=Date.parse(s.activation_started_at??s.upgraded_at??"");return Number.isNaN(t)?!0:Date.now()-t>e}function V(s){const e=Date.parse(s.verification_started_at??s.activation_started_at??"");return Number.isNaN(e)||Date.now()-e>be}function Re(s){if((s.guardian_handback_count??0)>=Se)return!1;const e=Date.parse(s.guardian_handback_at??"");return Number.isNaN(e)?!0:Date.now()-e>=Te}function ke(s){if(!Number.isInteger(s)||(s??0)<=0)return!1;try{return process.kill(s,0),!0}catch{return!1}}function A(){const s=g(_.base,"bin"),e=g(s,"upgrade-guardian.mjs");if(v(e))return e;const t=g(s,"upgrade-guardian.fallback.mjs");return v(t)?t:null}function H(s){if(!ke(s.guardian_pid))return!1;try{const t=JSON.parse(S(g(_.data,"upgrade-monitor-heartbeat.json"),"utf-8"));if(t.transaction_id===s.transaction_id&&t.pid===s.guardian_pid&&typeof t.updated_at=="number"&&Date.now()-t.updated_at<=C)return!0}catch{}const e=Date.parse(s.guardian_started_at??"");return Number.isFinite(e)&&Date.now()-e<=C}function B(s){return new URL(s.replace(/^wss:/,"https:").replace(/^ws:/,"http:")).origin}function F(){return g(_.data,"upgrade-state.json")}function j(){const s=F();if(!v(s))return{daily_attempts:{},version_attempts:{}};try{return JSON.parse(S(s,"utf-8"))}catch{return{daily_attempts:{},version_attempts:{}}}}function Ee(s){const e=F();K(Y(e),{recursive:!0});const t=e+".tmp";J(t,JSON.stringify(s),"utf-8"),X(t,e)}function z(){return new Date().toISOString().slice(0,10)}class Ce{agentConfigs;isBusy;closeAdmission;options;timer=null;initialTimer=null;pendingTimer=null;guardianWaitLoggedAt=0;guardianWaitLoggedPhase="";recordedRollbackFailures=new Set;posixGuardianResumeAttempted=new Set;running=!1;stopped=!1;constructor(e,t,i,a={}){this.agentConfigs=e,this.isBusy=t,this.closeAdmission=i,this.options=a}async start(){await this.handlePendingOnStartup(),this.initialTimer=setTimeout(()=>{this.stopped||(this.runCheck(),!this.stopped&&(this.timer=setInterval(()=>this.runCheck(),pe)))},he)}stop(){this.stopped=!0,this.initialTimer&&(clearTimeout(this.initialTimer),this.initialTimer=null),this.timer&&(clearInterval(this.timer),this.timer=null),this.pendingTimer&&(clearTimeout(this.pendingTimer),this.pendingTimer=null)}triggerCheck(){this.stopped||this.runCheck()}async checkForUpdate(){return this.agentConfigs.length===0?{available:!1}:(await Promise.all(this.agentConfigs.map(i=>this.queryUpgrade(i)))).find(i=>i.available&&i.release)??{available:!1}}async handlePendingOnStartup(){if(!O())return;let e=k();if(!e){n("daemon startup: pending journal is unreadable; preserving it and blocking automatic upgrade for manual recovery");return}if(e.phase&&!Pe.has(e.phase)&&(e=E({phase:"failed",error_code:"INVALID_UPGRADE_STATE",error_msg:`Unknown upgrade transaction phase: ${String(e.phase)}`},e.transaction_id)),e.phase==="finalizing"){P(e)||(n(`terminal transaction ${e.transaction_id??"legacy"} is waiting for its shared lock to be released`),this.schedulePendingReconcile());return}if(e.phase==="preparing"||e.phase==="staged"){const o=!!(e.wrapper_pause_file&&v(e.wrapper_pause_file));if(e.phase==="staged"&&o){this.schedulePendingReconcile();return}N(e),e=E({phase:"rolled_back",error_code:"UPGRADE_INTERRUPTED",error_msg:"Upgrade stopped before Windows activation started"})}if(e.phase==="verifying"){if(H(e)&&!V(e)){this.schedulePendingReconcile();return}if(V(e)&&e.transaction_id&&["win32","darwin"].includes(this.options.platform??process.platform)&&this.options.requestGracefulShutdown){const o=`Target ${e.target_version} did not become healthy before the Windows verification deadline`;Z(e.transaction_id,o),n(`${o}; requesting graceful shutdown for offline rollback`),await this.options.requestGracefulShutdown("upgrade");return}await this.resumeWindowsGuardian(),this.schedulePendingReconcile();return}const t=new Set(["handoff_ready","activating","rolling_back"]);if(e.phase&&t.has(e.phase)){if(H(e)){this.logGuardianWait(e),this.schedulePendingReconcile();return}if(q(e)&&e.transaction_id&&(this.options.platform??process.platform)==="win32"&&this.options.requestGracefulShutdown&&Re(e)){const o=`Guardian did not settle ${e.phase} for ${e.target_version} before the Windows activation deadline`;E({guardian_handback_count:(e.guardian_handback_count??0)+1,guardian_handback_at:new Date().toISOString()},e.transaction_id),n(`${o}; requesting graceful shutdown so the wrapper re-runs the guardian`),await this.options.requestGracefulShutdown("upgrade");return}await this.resumeWindowsGuardian(),this.logGuardianWait(e),this.schedulePendingReconcile();return}if(e.phase==="failed"){n(`daemon startup: upgrade transaction requires manual recovery: ${e.error_msg??e.error_code??"unknown error"}`);return}const i=ne(e),a=R(),r=e.phase==="healthy",d=e.phase==="rolled_back",h=!i||i===e.target_version,c=r?a===e.target_version&&i===e.target_version:!d&&a===e.target_version&&h;if(!c&&h&&!d){if(!q(e)){if((this.options.platform??process.platform)!=="win32"&&i===e.target_version){const o=e.transaction_id??`${e.target_version}@${e.upgraded_at}`;this.posixGuardianResumeAttempted.has(o)||(this.posixGuardianResumeAttempted.add(o),n(`resuming POSIX guardian for stalled activation of ${e.target_version}`),this.startGuardian())}this.logThrottled("posix-activation-wait",`daemon startup: runtime ${a} is waiting for guardian activation of ${e.target_version} (active package=${i??"unknown"})`),this.schedulePendingReconcile();return}n(`daemon startup: activation wait for ${e.target_version} expired (> ${x/6e4}min); no guardian will finish the switch, reporting by version verdict`)}const u=c?{from_version:e.from_version,to_version:e.target_version,status:"success"}:{from_version:e.from_version,to_version:e.target_version,status:"rolled_back",error_code:e.error_code??"STARTUP_CRASH",error_msg:e.error_msg,crash_count:e.crash_count};if(n(c?`daemon startup: version matches target ${e.target_version}, upgrade succeeded`:`daemon startup: runtime=${a}, active package=${i??"unknown"}, target=${e.target_version}; rolled back`),!c){const o=e.transaction_id??`${e.target_version}@${e.upgraded_at}`;this.recordedRollbackFailures.has(o)||(this.recordedRollbackFailures.add(o),this.recordFailure(e.target_version))}if(c){const o=await this.confirmHealthz(we,r?e.target_version:void 0,r?e.old_pid:void 0,r?e.activation_started_at:void 0);if(r&&!o){n("guardian marked healthy but target-version healthz is not yet observable; retrying reconciliation"),this.schedulePendingReconcile();return}n(o?"healthz confirmed healthy, reporting success":"healthz not confirmed within window; reporting success on version-match (guardian still guards crashes)")}if(!$(e)){n(`terminal transaction ${e.transaction_id??"legacy"} is waiting for antivirus-held artifact cleanup`),this.schedulePendingReconcile();return}if(await this.reportUpgrade(u)){P(e)||(n(`terminal transaction ${e.transaction_id??"legacy"} could not release its shared lock; retrying reconciliation`),this.schedulePendingReconcile());return}this.retryStartupReport(u,e,!1)}schedulePendingReconcile(){this.stopped||this.pendingTimer||(this.pendingTimer=setTimeout(()=>{this.pendingTimer=null,!this.stopped&&this.handlePendingOnStartup().catch(e=>{f.warn("upgrade",`Pending reconciliation failed: ${e instanceof Error?e.message:e}`),this.schedulePendingReconcile()})},ye))}logThrottled(e,t){const i=Date.now();e===this.guardianWaitLoggedPhase&&i-this.guardianWaitLoggedAt<Ae||(this.guardianWaitLoggedPhase=e,this.guardianWaitLoggedAt=i,n(t))}logGuardianWait(e){const t=String(e.phase??"");this.logThrottled(`guardian:${t}`,`transaction ${e.transaction_id??"legacy"} is ${t}; waiting for guardian verdict`)}async resumeWindowsGuardian(){if(this.options.resumeWindowsGuardian){await this.options.resumeWindowsGuardian();return}const e=A();if(!e)return;D(process.execPath,[e,"activate","--root",_.base],{detached:!0,stdio:"ignore",windowsHide:!0}).unref()}async retryStartupReport(e,t,i){const a=t?.transaction_id,r=()=>{const d=k();if(!(a&&d?.transaction_id!==a)){if(i&&!$(t)){n(`terminal backup cleanup is still blocked for ${a??"legacy transaction"}`),this.schedulePendingReconcile();return}P(t)||(n(`terminal shared lock release is still blocked for ${a??"legacy transaction"}`),this.schedulePendingReconcile())}};for(let d=2;d<=L;d++){if(this.stopped)return;const h=_e*(d-1)+Math.floor(Math.random()*1e3);if(await new Promise(c=>setTimeout(c,h)),this.stopped)return;if(await this.reportUpgrade(e)){r();return}}n(`startup report (${e.status}) not delivered after ${L} attempts; clearing pending to unblock future checks`),r()}async confirmHealthz(e,t,i,a){let r="19579";try{const c=g(_.data,"health-port");v(c)&&(r=S(c,"utf-8").trim()||r)}catch{}const d=`http://127.0.0.1:${r}/healthz`,h=Date.now()+e;for(;Date.now()<h&&!this.stopped;){try{const c=await fetch(d,{signal:AbortSignal.timeout(2e3)});if(c.ok){if(!t)return!0;const u=await c.json();if(u.status==="ok"&&u.version===t&&typeof u.pid=="number"&&u.pid>0&&u.pid!==i)try{const o=JSON.parse(S(g(_.base,"daemon-status.json"),"utf-8")),y=Date.parse(a??"");return!!(o.state==="running"&&o.pid===u.pid&&typeof o.updated_at=="number"&&Number.isFinite(y)&&o.updated_at>=y)}catch{return!1}}}catch{}await new Promise(c=>setTimeout(c,ve))}return!1}async runCheck(){if(!this.running){this.running=!0;try{await this.check()}catch(e){f.error("upgrade",`Check failed: ${e instanceof Error?e.message:e}`)}finally{this.running=!1}}}async check(){if(O()){await this.handlePendingOnStartup();return}if(this.agentConfigs.length===0)return;const i=(await Promise.all(this.agentConfigs.map(l=>this.queryUpgrade(l)))).find(l=>l.available&&l.release)?.release;if(!i)return;const a=R();let r;try{r=re(i.version)}catch(l){const p=l;await this.reportUpgrade({from_version:a,to_version:String(i.version),status:"failed",error_code:p.code??"INVALID_VERSION",error_msg:p.message});return}const d=i.force,h=this.options.platform??process.platform,c=this.options.env??process.env;if(h==="win32"){const l=A(),p=c.GRIX_WRAPPER_SUPERVISED==="1";if(!p||!l||!this.options.requestGracefulShutdown){const m=`Windows automatic upgrade requires a refreshed service installation (${[...p?[]:["managed wrapper"],...l?[]:["external guardian"],...this.options.requestGracefulShutdown?[]:["graceful shutdown hook"]].join(", ")})`;n(m),await this.reportUpgrade({from_version:a,to_version:r,status:"failed",error_code:"WINDOWS_UPGRADE_UNSUPPORTED",error_msg:m});return}}const u=h==="darwin"&&!!A()&&!!this.options.requestGracefulShutdown;if(h==="darwin"&&!u&&n("macOS transactional upgrade prerequisites missing; falling back to the legacy guardian flow"),!d&&!this.checkRateLimit(r))return;const o=ie();if(!o.ok){await this.reportUpgrade({from_version:a,to_version:r,status:"failed",error_code:o.errorCode,error_msg:o.errorMsg});return}if(!await te(M,r)){n(`target ${r} not resolvable on any registry yet; skipping this cycle (no failure recorded)`);return}n(`upgrade start: ${a} -> ${r}`);const y=Date.now();let w;try{w=ce(a,r,h==="win32"||u?"preparing":void 0).transaction_id,(await ae(M,r,void 0,u?{activationMode:"staged"}:{}))?.activationRequired||de(r),n("npm install verified, reporting installed"),await this.reportUpgrade({from_version:a,to_version:r,status:"installed",duration_ms:Date.now()-y}),n("shutting down for restart");const p=await this.waitUntilIdle();if(p!=="idle")throw n(`upgrade aborted before lifecycle handoff: ${p}`),this.options.reopenLifecycleAdmission?.(),new T("UPGRADE_ABORTED",`Upgrade was aborted before lifecycle handoff (${p})`);if(h==="win32"){if(!A())throw new T("WINDOWS_UPGRADE_UNSUPPORTED","Windows external guardian disappeared before lifecycle handoff");U(process.pid,w),n("Windows handoff armed; requesting graceful daemon shutdown"),await this.options.requestGracefulShutdown("upgrade")}else if(u){if(!A())throw new T("MACOS_UPGRADE_UNSUPPORTED","macOS guardian helper disappeared before lifecycle handoff");U(process.pid,w),n("macOS handoff armed; spawning guardian before graceful daemon shutdown"),await this.resumeWindowsGuardian(),await this.options.requestGracefulShutdown("upgrade")}else this.startGuardian(),process.kill(process.pid,"SIGTERM")}catch(l){const p=l instanceof T?l.code:"NPM_INSTALL_FAILED",b=l instanceof Error?l.message:String(l);n(`upgrade failed: ${p} ${b}`);const m=k();if(w&&m?.transaction_id===w&&(h==="win32"||u?(se(),N(m)):oe(m)&&n("upgrade failed before restart; previous package restored atomically"),P(m)||(n(`failed transaction ${w} is waiting for shared lock release`),this.schedulePendingReconcile()),this.options.reopenLifecycleAdmission?.()),p==="UPGRADE_IN_PROGRESS")return;le(b)||ue(b)||p==="NPM_TIMEOUT"?n(`failure is retriable (${p}); skipping cooldown, will retry next cycle`):this.recordFailure(r),await this.reportUpgrade({from_version:a,to_version:r,status:"failed",error_code:p,error_msg:b,duration_ms:Date.now()-y,upgrade_log:ee()})}}async waitUntilIdle(){if(this.isBusy?.()){n(`active tasks detected, waiting up to ${Math.round(I/6e4)}min until idle before restart (admission stays open while waiting)`);const e=Date.now()+I,t=5e3,i=600*1e3;let a=Date.now();for(;this.isBusy()&&!this.stopped;){if(Date.now()>=e)return f.warn("upgrade",`Idle wait exceeded ${Math.round(I/6e4)}min; abandoning this upgrade attempt. Admission was never closed, so agents kept serving. A busy state that never clears usually means an adapter leaked its busy flag \u2014 check for a turn that never completed.`),n("idle wait timed out; upgrade attempt abandoned without ever closing admission"),"timeout";await new Promise(r=>setTimeout(r,t)),Date.now()-a>=i&&(a=Date.now(),n("still waiting for active tasks to finish before restart"))}}return this.stopped?"stopped":(this.closeAdmission?.(),this.isBusy?.()?(n("work arrived while closing admission; reopening and abandoning this upgrade attempt"),this.options.reopenLifecycleAdmission?.(),"busy-again"):(n("all tasks completed, proceeding with restart"),"idle"))}checkRateLimit(e){const t=j();if(t.last_failure_at){const d=Date.now()-new Date(t.last_failure_at).getTime();if(d<G)return f.info("upgrade",`In cooldown, ${(G-d)/6e4}m remaining`),!1}const i=t.version_attempts[e]??0;if(i>=ge)return f.info("upgrade",`Version ${e} already tried ${i} times, skipping`),!1;const a=z(),r=t.daily_attempts[a]??0;return r>=fe?(f.info("upgrade",`Already ${r} attempts today, skipping`),!1):!0}recordFailure(e){const t=j(),i=z();t.last_failure_at=new Date().toISOString(),t.last_failure_version=e,t.daily_attempts[i]=(t.daily_attempts[i]??0)+1,t.version_attempts[e]=(t.version_attempts[e]??0)+1;for(const r of Object.keys(t.daily_attempts))r<i&&delete t.daily_attempts[r];const a=Object.keys(t.version_attempts);for(const r of a.slice(0,Math.max(0,a.length-me)))r!==e&&delete t.version_attempts[r];Ee(t)}async queryUpgrade(e){const i=`${B(e.wsUrl)}/v1/agent-api/upgrade/check?`+new URLSearchParams({client_type:"grix-connector",client_version:R(),channel:e.channel??"stable",platform:process.platform,arch:process.arch}).toString();try{const a=await fetch(i,{headers:{Authorization:`Bearer ${e.apiKey}`},signal:AbortSignal.timeout(W)});if(!a.ok)return f.warn("upgrade",`Check API returned ${a.status}`),{available:!1};const r=await a.json();return r.code!==0?{available:!1}:r.data}catch(a){return f.warn("upgrade",`Check API error: ${a instanceof Error?a.message:a}`),{available:!1}}}async reportUpgrade(e){if(this.agentConfigs.length===0)return!1;const t={client_type:"grix-connector",...e.npm_version?e:{...e,...Q()}};return(await Promise.all(this.agentConfigs.map(async a=>{const d=`${B(a.wsUrl)}/v1/agent-api/upgrade/report`;try{return(await fetch(d,{method:"POST",headers:{Authorization:`Bearer ${a.apiKey}`,"Content-Type":"application/json"},body:JSON.stringify(t),signal:AbortSignal.timeout(W)})).ok}catch{return!1}}))).some(a=>a)}startGuardian(){const e=g(_.base,"bin","upgrade-guardian.sh");if(process.platform==="win32"||!v(e)){f.info("upgrade","Guardian not available on this platform, skipping");return}try{const t=D(e,[],{detached:!0,stdio:"ignore"});t.unref(),n(`guardian started (pid ${t.pid})`)}catch(t){f.warn("upgrade",`Failed to start guardian: ${t instanceof Error?t.message:t}`)}}}export{Ce as UpgradeChecker};
|
|
1
|
+
import{existsSync as v,mkdirSync as X,readFileSync as S,renameSync as J,writeFileSync as Y}from"node:fs";import{join as g,dirname as Z}from"node:path";import{spawn as U}from"node:child_process";import{log as f}from"../log/index.js";import{GRIX_PATHS as _}from"../log/index.js";import{resolveClientVersion as R}from"../util/client-version.js";import{UpgradeError as T,armWindowsUpgradeHandoff as N,armWindowsUpgradeRollback as Q,cleanupPendingBackup as O,collectEnvInfo as ee,discardPendingStaging as k,finalizePendingTransaction as P,getUpgradeLogTail as te,isVersionAvailable as ae,npmInstall as re,normalizeUpgradeVersion as ie,pendingExists as G,preflightCheck as ne,readPending as E,readPendingPackageVersion as se,removeWindowsHandoffFiles as M,rollbackPendingInstall as oe,updatePending as I,upgradeLog as n,verifyInstalledVersion as de,writePending as ce}from"./npm-upgrader.js";import{isRetriableRegistryError as le,isTransientInstallError as ue}from"../installer/npm-registry.js";const pe=360*60*1e3,he=300*1e3,W=1800*1e3,fe=2,ge=3,me=10,L="grix-connector",C=1e4,x=15,_e=2e3,we=8e3,ve=1e3,ye=2e3,be=120*1e3,q=1e4,Ae=6e4,Se=3,Te=600*1e3,Pe=new Set(["preparing","staged","handoff_ready","activating","verifying","healthy","rolling_back","rolled_back","finalizing","failed"]),V=600*1e3,D=1800*1e3;function $(s,e=V){const t=Date.parse(s.activation_started_at??s.upgraded_at??"");return Number.isNaN(t)?!0:Date.now()-t>e}function H(s){const e=Date.parse(s.verification_started_at??s.activation_started_at??"");return Number.isNaN(e)||Date.now()-e>be}function Re(s){if((s.guardian_handback_count??0)>=Se)return!1;const e=Date.parse(s.guardian_handback_at??"");return Number.isNaN(e)?!0:Date.now()-e>=Te}function ke(s){if(!Number.isInteger(s)||(s??0)<=0)return!1;try{return process.kill(s,0),!0}catch{return!1}}function A(){const s=g(_.base,"bin"),e=g(s,"upgrade-guardian.mjs");if(v(e))return e;const t=g(s,"upgrade-guardian.fallback.mjs");return v(t)?t:null}function B(s){if(!ke(s.guardian_pid))return!1;try{const t=JSON.parse(S(g(_.data,"upgrade-monitor-heartbeat.json"),"utf-8"));if(t.transaction_id===s.transaction_id&&t.pid===s.guardian_pid&&typeof t.updated_at=="number"&&Date.now()-t.updated_at<=q)return!0}catch{}const e=Date.parse(s.guardian_started_at??"");return Number.isFinite(e)&&Date.now()-e<=q}function F(s){return new URL(s.replace(/^wss:/,"https:").replace(/^ws:/,"http:")).origin}function j(){return g(_.data,"upgrade-state.json")}function z(){const s=j();if(!v(s))return{daily_attempts:{},version_attempts:{}};try{return JSON.parse(S(s,"utf-8"))}catch{return{daily_attempts:{},version_attempts:{}}}}function Ee(s){const e=j();X(Z(e),{recursive:!0});const t=e+".tmp";Y(t,JSON.stringify(s),"utf-8"),J(t,e)}function K(){return new Date().toISOString().slice(0,10)}class Ce{agentConfigs;isBusy;closeAdmission;options;timer=null;initialTimer=null;pendingTimer=null;guardianWaitLoggedAt=0;guardianWaitLoggedPhase="";recordedRollbackFailures=new Set;posixGuardianResumeAttempted=new Set;running=!1;stopped=!1;constructor(e,t,i,a={}){this.agentConfigs=e,this.isBusy=t,this.closeAdmission=i,this.options=a}async start(){await this.handlePendingOnStartup(),this.initialTimer=setTimeout(()=>{this.stopped||(this.runCheck(),!this.stopped&&(this.timer=setInterval(()=>this.runCheck(),pe)))},he)}stop(){this.stopped=!0,this.initialTimer&&(clearTimeout(this.initialTimer),this.initialTimer=null),this.timer&&(clearInterval(this.timer),this.timer=null),this.pendingTimer&&(clearTimeout(this.pendingTimer),this.pendingTimer=null)}triggerCheck(){this.stopped||this.runCheck()}async checkForUpdate(){return this.agentConfigs.length===0?{available:!1}:(await Promise.all(this.agentConfigs.map(i=>this.queryUpgrade(i)))).find(i=>i.available&&i.release)??{available:!1}}async handlePendingOnStartup(){if(!G())return;let e=E();if(!e){n("daemon startup: pending journal is unreadable; preserving it and blocking automatic upgrade for manual recovery");return}if(e.phase&&!Pe.has(e.phase)&&(e=I({phase:"failed",error_code:"INVALID_UPGRADE_STATE",error_msg:`Unknown upgrade transaction phase: ${String(e.phase)}`},e.transaction_id)),e.phase==="finalizing"){P(e)||(n(`terminal transaction ${e.transaction_id??"legacy"} is waiting for its shared lock to be released`),this.schedulePendingReconcile());return}if(e.phase==="preparing"||e.phase==="staged"){const o=!!(e.wrapper_pause_file&&v(e.wrapper_pause_file));if(e.phase==="staged"&&o){this.schedulePendingReconcile();return}k(e),e=I({phase:"rolled_back",error_code:"UPGRADE_INTERRUPTED",error_msg:"Upgrade stopped before Windows activation started"})}if(e.phase==="verifying"){if(B(e)&&!H(e)){this.schedulePendingReconcile();return}if(H(e)&&e.transaction_id&&["win32","darwin"].includes(this.options.platform??process.platform)&&this.options.requestGracefulShutdown){const o=`Target ${e.target_version} did not become healthy before the Windows verification deadline`;Q(e.transaction_id,o),n(`${o}; requesting graceful shutdown for offline rollback`),await this.options.requestGracefulShutdown("upgrade");return}await this.resumeWindowsGuardian(),this.schedulePendingReconcile();return}const t=new Set(["handoff_ready","activating","rolling_back"]);if(e.phase&&t.has(e.phase)){if(B(e)){this.logGuardianWait(e),this.schedulePendingReconcile();return}if($(e)&&e.transaction_id&&(this.options.platform??process.platform)==="win32"&&this.options.requestGracefulShutdown&&Re(e)){const o=`Guardian did not settle ${e.phase} for ${e.target_version} before the Windows activation deadline`;I({guardian_handback_count:(e.guardian_handback_count??0)+1,guardian_handback_at:new Date().toISOString()},e.transaction_id),n(`${o}; requesting graceful shutdown so the wrapper re-runs the guardian`),await this.options.requestGracefulShutdown("upgrade");return}if($(e))n(`daemon startup: guardian did not settle ${e.phase} for ${e.target_version} and cannot be resumed; collapsing the transaction by version verdict`),M(),k(e);else{await this.resumeWindowsGuardian(),this.logGuardianWait(e),this.schedulePendingReconcile();return}}if(e.phase==="failed"){n(`daemon startup: upgrade transaction requires manual recovery: ${e.error_msg??e.error_code??"unknown error"}`);return}const i=se(e),a=R(),r=e.phase==="healthy",d=e.phase==="rolled_back",h=!i||i===e.target_version,c=r?a===e.target_version&&i===e.target_version:!d&&a===e.target_version&&h;if(!c&&h&&!d){if(!$(e)){if((this.options.platform??process.platform)!=="win32"&&i===e.target_version){const o=e.transaction_id??`${e.target_version}@${e.upgraded_at}`;this.posixGuardianResumeAttempted.has(o)||(this.posixGuardianResumeAttempted.add(o),n(`resuming POSIX guardian for stalled activation of ${e.target_version}`),this.startGuardian())}this.logThrottled("posix-activation-wait",`daemon startup: runtime ${a} is waiting for guardian activation of ${e.target_version} (active package=${i??"unknown"})`),this.schedulePendingReconcile();return}n(`daemon startup: activation wait for ${e.target_version} expired (> ${V/6e4}min); no guardian will finish the switch, reporting by version verdict`)}const u=c?{from_version:e.from_version,to_version:e.target_version,status:"success"}:{from_version:e.from_version,to_version:e.target_version,status:"rolled_back",error_code:e.error_code??"STARTUP_CRASH",error_msg:e.error_msg,crash_count:e.crash_count};if(n(c?`daemon startup: version matches target ${e.target_version}, upgrade succeeded`:`daemon startup: runtime=${a}, active package=${i??"unknown"}, target=${e.target_version}; rolled back`),!c){const o=e.transaction_id??`${e.target_version}@${e.upgraded_at}`;this.recordedRollbackFailures.has(o)||(this.recordedRollbackFailures.add(o),this.recordFailure(e.target_version))}if(c){const o=await this.confirmHealthz(we,r?e.target_version:void 0,r?e.old_pid:void 0,r?e.activation_started_at:void 0);if(r&&!o){n("guardian marked healthy but target-version healthz is not yet observable; retrying reconciliation"),this.schedulePendingReconcile();return}n(o?"healthz confirmed healthy, reporting success":"healthz not confirmed within window; reporting success on version-match (guardian still guards crashes)")}if(!O(e)){n(`terminal transaction ${e.transaction_id??"legacy"} is waiting for antivirus-held artifact cleanup`),this.schedulePendingReconcile();return}if(await this.reportUpgrade(u)){P(e)||(n(`terminal transaction ${e.transaction_id??"legacy"} could not release its shared lock; retrying reconciliation`),this.schedulePendingReconcile());return}this.retryStartupReport(u,e,!1)}schedulePendingReconcile(){this.stopped||this.pendingTimer||(this.pendingTimer=setTimeout(()=>{this.pendingTimer=null,!this.stopped&&this.handlePendingOnStartup().catch(e=>{f.warn("upgrade",`Pending reconciliation failed: ${e instanceof Error?e.message:e}`),this.schedulePendingReconcile()})},ye))}logThrottled(e,t){const i=Date.now();e===this.guardianWaitLoggedPhase&&i-this.guardianWaitLoggedAt<Ae||(this.guardianWaitLoggedPhase=e,this.guardianWaitLoggedAt=i,n(t))}logGuardianWait(e){const t=String(e.phase??"");this.logThrottled(`guardian:${t}`,`transaction ${e.transaction_id??"legacy"} is ${t}; waiting for guardian verdict`)}async resumeWindowsGuardian(){if(this.options.resumeWindowsGuardian){await this.options.resumeWindowsGuardian();return}const e=A();if(!e)return;U(process.execPath,[e,"activate","--root",_.base],{detached:!0,stdio:"ignore",windowsHide:!0}).unref()}async retryStartupReport(e,t,i){const a=t?.transaction_id,r=()=>{const d=E();if(!(a&&d?.transaction_id!==a)){if(i&&!O(t)){n(`terminal backup cleanup is still blocked for ${a??"legacy transaction"}`),this.schedulePendingReconcile();return}P(t)||(n(`terminal shared lock release is still blocked for ${a??"legacy transaction"}`),this.schedulePendingReconcile())}};for(let d=2;d<=x;d++){if(this.stopped)return;const h=_e*(d-1)+Math.floor(Math.random()*1e3);if(await new Promise(c=>setTimeout(c,h)),this.stopped)return;if(await this.reportUpgrade(e)){r();return}}n(`startup report (${e.status}) not delivered after ${x} attempts; clearing pending to unblock future checks`),r()}async confirmHealthz(e,t,i,a){let r="19579";try{const c=g(_.data,"health-port");v(c)&&(r=S(c,"utf-8").trim()||r)}catch{}const d=`http://127.0.0.1:${r}/healthz`,h=Date.now()+e;for(;Date.now()<h&&!this.stopped;){try{const c=await fetch(d,{signal:AbortSignal.timeout(2e3)});if(c.ok){if(!t)return!0;const u=await c.json();if(u.status==="ok"&&u.version===t&&typeof u.pid=="number"&&u.pid>0&&u.pid!==i)try{const o=JSON.parse(S(g(_.base,"daemon-status.json"),"utf-8")),y=Date.parse(a??"");return!!(o.state==="running"&&o.pid===u.pid&&typeof o.updated_at=="number"&&Number.isFinite(y)&&o.updated_at>=y)}catch{return!1}}}catch{}await new Promise(c=>setTimeout(c,ve))}return!1}async runCheck(){if(!this.running){this.running=!0;try{await this.check()}catch(e){f.error("upgrade",`Check failed: ${e instanceof Error?e.message:e}`)}finally{this.running=!1}}}async check(){if(G()){await this.handlePendingOnStartup();return}if(this.agentConfigs.length===0)return;const i=(await Promise.all(this.agentConfigs.map(l=>this.queryUpgrade(l)))).find(l=>l.available&&l.release)?.release;if(!i)return;const a=R();let r;try{r=ie(i.version)}catch(l){const p=l;await this.reportUpgrade({from_version:a,to_version:String(i.version),status:"failed",error_code:p.code??"INVALID_VERSION",error_msg:p.message});return}const d=i.force,h=this.options.platform??process.platform,c=this.options.env??process.env;if(h==="win32"){const l=A(),p=c.GRIX_WRAPPER_SUPERVISED==="1";if(!p||!l||!this.options.requestGracefulShutdown){const m=`Windows automatic upgrade requires a refreshed service installation (${[...p?[]:["managed wrapper"],...l?[]:["external guardian"],...this.options.requestGracefulShutdown?[]:["graceful shutdown hook"]].join(", ")})`;n(m),await this.reportUpgrade({from_version:a,to_version:r,status:"failed",error_code:"WINDOWS_UPGRADE_UNSUPPORTED",error_msg:m});return}}const u=h==="darwin"&&!!A()&&!!this.options.requestGracefulShutdown;if(h==="darwin"&&!u&&n("macOS transactional upgrade prerequisites missing; falling back to the legacy guardian flow"),!d&&!this.checkRateLimit(r))return;const o=ne();if(!o.ok){await this.reportUpgrade({from_version:a,to_version:r,status:"failed",error_code:o.errorCode,error_msg:o.errorMsg});return}if(!await ae(L,r)){n(`target ${r} not resolvable on any registry yet; skipping this cycle (no failure recorded)`);return}n(`upgrade start: ${a} -> ${r}`);const y=Date.now();let w;try{w=ce(a,r,h==="win32"||u?"preparing":void 0).transaction_id,(await re(L,r,void 0,u?{activationMode:"staged"}:{}))?.activationRequired||de(r),n("npm install verified, reporting installed"),await this.reportUpgrade({from_version:a,to_version:r,status:"installed",duration_ms:Date.now()-y}),n("shutting down for restart");const p=await this.waitUntilIdle();if(p!=="idle")throw n(`upgrade aborted before lifecycle handoff: ${p}`),this.options.reopenLifecycleAdmission?.(),new T("UPGRADE_ABORTED",`Upgrade was aborted before lifecycle handoff (${p})`);if(h==="win32"){if(!A())throw new T("WINDOWS_UPGRADE_UNSUPPORTED","Windows external guardian disappeared before lifecycle handoff");N(process.pid,w),n("Windows handoff armed; requesting graceful daemon shutdown"),await this.options.requestGracefulShutdown("upgrade")}else if(u){if(!A())throw new T("MACOS_UPGRADE_UNSUPPORTED","macOS guardian helper disappeared before lifecycle handoff");N(process.pid,w),n("macOS handoff armed; spawning guardian before graceful daemon shutdown"),await this.resumeWindowsGuardian(),await this.options.requestGracefulShutdown("upgrade")}else this.startGuardian(),process.kill(process.pid,"SIGTERM")}catch(l){const p=l instanceof T?l.code:"NPM_INSTALL_FAILED",b=l instanceof Error?l.message:String(l);n(`upgrade failed: ${p} ${b}`);const m=E();if(w&&m?.transaction_id===w&&(h==="win32"||u?(M(),k(m)):oe(m)&&n("upgrade failed before restart; previous package restored atomically"),P(m)||(n(`failed transaction ${w} is waiting for shared lock release`),this.schedulePendingReconcile()),this.options.reopenLifecycleAdmission?.()),p==="UPGRADE_IN_PROGRESS")return;le(b)||ue(b)||p==="NPM_TIMEOUT"?n(`failure is retriable (${p}); skipping cooldown, will retry next cycle`):this.recordFailure(r),await this.reportUpgrade({from_version:a,to_version:r,status:"failed",error_code:p,error_msg:b,duration_ms:Date.now()-y,upgrade_log:te()})}}async waitUntilIdle(){if(this.isBusy?.()){n(`active tasks detected, waiting up to ${Math.round(D/6e4)}min until idle before restart (admission stays open while waiting)`);const e=Date.now()+D,t=5e3,i=600*1e3;let a=Date.now();for(;this.isBusy()&&!this.stopped;){if(Date.now()>=e)return f.warn("upgrade",`Idle wait exceeded ${Math.round(D/6e4)}min; abandoning this upgrade attempt. Admission was never closed, so agents kept serving. A busy state that never clears usually means an adapter leaked its busy flag \u2014 check for a turn that never completed.`),n("idle wait timed out; upgrade attempt abandoned without ever closing admission"),"timeout";await new Promise(r=>setTimeout(r,t)),Date.now()-a>=i&&(a=Date.now(),n("still waiting for active tasks to finish before restart"))}}return this.stopped?"stopped":(this.closeAdmission?.(),this.isBusy?.()?(n("work arrived while closing admission; reopening and abandoning this upgrade attempt"),this.options.reopenLifecycleAdmission?.(),"busy-again"):(n("all tasks completed, proceeding with restart"),"idle"))}checkRateLimit(e){const t=z();if(t.last_failure_at){const d=Date.now()-new Date(t.last_failure_at).getTime();if(d<W)return f.info("upgrade",`In cooldown, ${(W-d)/6e4}m remaining`),!1}const i=t.version_attempts[e]??0;if(i>=ge)return f.info("upgrade",`Version ${e} already tried ${i} times, skipping`),!1;const a=K(),r=t.daily_attempts[a]??0;return r>=fe?(f.info("upgrade",`Already ${r} attempts today, skipping`),!1):!0}recordFailure(e){const t=z(),i=K();t.last_failure_at=new Date().toISOString(),t.last_failure_version=e,t.daily_attempts[i]=(t.daily_attempts[i]??0)+1,t.version_attempts[e]=(t.version_attempts[e]??0)+1;for(const r of Object.keys(t.daily_attempts))r<i&&delete t.daily_attempts[r];const a=Object.keys(t.version_attempts);for(const r of a.slice(0,Math.max(0,a.length-me)))r!==e&&delete t.version_attempts[r];Ee(t)}async queryUpgrade(e){const i=`${F(e.wsUrl)}/v1/agent-api/upgrade/check?`+new URLSearchParams({client_type:"grix-connector",client_version:R(),channel:e.channel??"stable",platform:process.platform,arch:process.arch}).toString();try{const a=await fetch(i,{headers:{Authorization:`Bearer ${e.apiKey}`},signal:AbortSignal.timeout(C)});if(!a.ok)return f.warn("upgrade",`Check API returned ${a.status}`),{available:!1};const r=await a.json();return r.code!==0?{available:!1}:r.data}catch(a){return f.warn("upgrade",`Check API error: ${a instanceof Error?a.message:a}`),{available:!1}}}async reportUpgrade(e){if(this.agentConfigs.length===0)return!1;const t={client_type:"grix-connector",...e.npm_version?e:{...e,...ee()}};return(await Promise.all(this.agentConfigs.map(async a=>{const d=`${F(a.wsUrl)}/v1/agent-api/upgrade/report`;try{return(await fetch(d,{method:"POST",headers:{Authorization:`Bearer ${a.apiKey}`,"Content-Type":"application/json"},body:JSON.stringify(t),signal:AbortSignal.timeout(C)})).ok}catch{return!1}}))).some(a=>a)}startGuardian(){const e=g(_.base,"bin","upgrade-guardian.sh");if(process.platform==="win32"||!v(e)){f.info("upgrade","Guardian not available on this platform, skipping");return}try{const t=U(e,[],{detached:!0,stdio:"ignore"});t.unref(),n(`guardian started (pid ${t.pid})`)}catch(t){f.warn("upgrade",`Failed to start guardian: ${t instanceof Error?t.message:t}`)}}}export{Ce as UpgradeChecker};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
function a(
|
|
1
|
+
function a(o){const e=new Set([`http://127.0.0.1:${o.serverPort}`,`http://localhost:${o.serverPort}`,...o.allowedOrigins]),t=new Set([`127.0.0.1:${o.serverPort}`,`localhost:${o.serverPort}`,...o.allowedHosts]);return{validateRequest(s){const r=i(s,e);if(!r.ok)return r;const n=l(s,t);return n.ok?{ok:!0}:n}}}function i(o,e){const t=o.headers.origin;return t?e.has(t)?{ok:!0}:{ok:!1,statusCode:403,message:`Origin not allowed: ${t}`}:{ok:!0}}function l(o,e){const t=o.headers.host;return t?e.has(t)?{ok:!0}:{ok:!1,statusCode:403,message:`Host not allowed: ${t}`}:{ok:!1,statusCode:403,message:"Missing Host header"}}export{a as createSecurityPolicy};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "grix-connector",
|
|
3
|
-
"version": "4.2.
|
|
3
|
+
"version": "4.2.7",
|
|
4
4
|
"description": "Connect local AI coding agents (Claude, Codex, Gemini, Qwen, DeepSeek, Cursor, OpenCode, Pi, OpenHuman, Reasonix) to the Grix scheduling platform. Also serves as an OpenClaw plugin for Grix channel transport.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|