grix-connector 3.18.0 → 3.19.0
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/acp/acp-adapter.js +12 -11
- package/dist/adapter/claude/claude-bridge-server.js +1 -1
- package/dist/adapter/claude/claude-tools.js +1 -1
- package/dist/adapter/claude/claude-worker-client.js +1 -1
- package/dist/adapter/claude/mcp-http-launcher.js +2 -2
- package/dist/adapter/claude/result-timeout.js +1 -1
- package/dist/adapter/opencode/opencode-adapter.js +1 -1
- package/dist/bridge/bridge.js +9 -9
- package/dist/core/access/allowlist-store.js +1 -1
- package/dist/core/aibot/client.js +2 -2
- package/dist/core/aibot/connection-handle.js +1 -1
- package/dist/core/file-ops/list-files.js +1 -1
- package/dist/core/proxy/relay-state-sync.js +1 -0
- package/dist/grix.js +7 -7
- package/dist/log.js +2 -2
- package/dist/manager.js +2 -2
- package/dist/mcp/stream-http/config.js +1 -1
- package/dist/mcp/stream-http/connection-binding.js +1 -1
- package/dist/mcp/stream-http/security.js +1 -1
- package/dist/mcp/stream-http/tool-executor.js +1 -1
- package/dist/mcp/stream-http/tool-registry.js +1 -1
- package/dist/mcp/stream-http/tool-schemas.js +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
import{readJSONFile as o,writeJSONFileAtomic as
|
|
1
|
+
import{readJSONFile as o,writeJSONFileAtomic as i}from"../util/json-file.js";function n(t){const r=o(t);return r&&typeof r=="object"&&"owners"in r&&Array.isArray(r.owners)?r.owners.filter(e=>typeof e=="string"&&e.trim().length>0):[]}async function s(t,r){await i(t,{owners:r})}export{n as readAllowlist,s as writeAllowlist};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{EventEmitter as A}from"node:events";import{randomUUID as R}from"node:crypto";import $ from"node:os";import O from"ws";import{log as m}from"../log/index.js";import{getMachineName as S}from"../util/index.js";import{detectTailnetIPv4 as I,ensureServerAndGetPort as P,getFileServerHttpsPort as F}from"../files/file-serve.js";import{TerminalOutbox as M}from"../persistence/terminal-outbox.js";import{TerminalCommitTokenStore as q}from"../persistence/terminal-commit-token-store.js";import{StopResultOutbox as B}from"../persistence/stop-result-outbox.js";import{AUTH_CODE_AGENT_DELETED as w,KICKED_REASON_AGENT_DELETED as k,AgentDeletedError as C,RequestTimeoutError as N}from"./errors.js";function D(T){return T.replace(/(?<=[\[:,\[]\s*)(\d{16,})(?=\s*[,}\]\n])/g,'"$1"')}function x(T){const e=[...T??["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}const W="aibot-agent-api-v1",U=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:e.localActions??["exec_approve","exec_reject"],skills:e.skills,librarySkills:e.librarySkills,sharedOwnerId:e.sharedOwnerId,concurrency:e.concurrency,terminalOutboxPath:e.terminalOutboxPath,terminalCommitTokenStorePath:e.terminalCommitTokenStorePath,stopResultOutboxPath:e.stopResultOutboxPath},this.terminalOutbox=new M(this.config.terminalOutboxPath),this.terminalCommitTokens=new q(this.config.terminalCommitTokenStorePath??(this.config.terminalOutboxPath?`${this.config.terminalOutboxPath}.tokens`:void 0)),this.stopResultOutbox=new B(this.config.stopResultOutboxPath??(this.config.terminalOutboxPath?`${this.config.terminalOutboxPath}.stops`:void 0));for(const n of this.terminalOutbox.listPending())n.payload.code==="agent_output_integrity_failed"&&this.blockEventOutput(n.payload.event_id)}get isConnected(){return this.connected}async connect(){this.negotiatedCapabilities.clear();const e=this.ws,t=this.connectionGeneration;if(this.pendingRequests.size>0&&this.rejectAllPendingRequests("connection generation replaced"),e){this.connected=!1,this.stopHeartbeat(),this.clearTerminalRetryTimers(),this.clearStopResultRetryTimers();const o=this.outboundFlushInFlight;o&&o.socket===e&&o.connectionGeneration===t&&this.cancelOutboundFlush(o,"connection_lost","connection generation replaced"),this.failNonReplayableOutputsForSocket(e,"connection generation replaced"),this.abortPendingOutputWrites(!0,e,t,"connection generation replaced"),this.abortPendingAcceptedOutputs(!0,e,t),this.clearAllEventCorrelations(),this.connectionGeneration++;try{e.close(1012,"connection generation replaced")}catch{}this.ws===e&&(this.ws=null)}else this.outboundFlushInFlight&&this.cancelOutboundFlush(this.outboundFlushInFlight,"discard","detached connection generation replaced");const n=++this.connectionGeneration;let s,i,r;const u=(async()=>{try{if(s=await I(),s!==void 0)try{i=await P(s);const o=F();o>0&&(r=o)}catch(o){m.warn("aibot",`file server pre-start failed: ${o}`)}}catch(o){m.warn("aibot",`tailnet detect failed: ${o}`)}})();return new Promise((o,f)=>{const l=new O(this.config.url);this.ws=l;const d=setTimeout(()=>{if(!this.isConnectionCurrent(l,n))return;const c=this.pendingRequests.get(_);c&&(this.pendingRequests.delete(_),clearTimeout(c.timer),c.reject(new Error("Auth timeout: no auth_ack received within 15s"))),this.cleanupSocket(l,n)},15e3),_=++this.seq,E=setTimeout(()=>{if(!this.isConnectionCurrent(l,n))return;const c=this.pendingRequests.get(_);c&&(this.pendingRequests.delete(_),c.reject(new Error("Auth request timeout"))),this.cleanupSocket(l,n)},15e3);this.pendingRequests.set(_,{expected:["auth_ack"],resolve:c=>{clearTimeout(d);const a=c.payload;if(a.code===0){this.negotiatedCapabilities=new Set(Array.isArray(a.supported_capabilities)?a.supported_capabilities:[]),this.connected=!0,this.everConnected=!0,this.reconnectAttempts=0,a.heartbeat_sec&&(this.heartbeatSec=a.heartbeat_sec),a.ack_policy&&(this.ackPolicy=a.ack_policy,m.info("aibot",`ack_policy received: push_ack_timeout_ms=${a.ack_policy.push_ack_timeout_ms??"default"} max_retries=${a.ack_policy.max_retries??"default"} timeout_action=${a.ack_policy.timeout_action??"default"}`)),this.startHeartbeat();const h=this.flushOutboundBuffer(l,n);this.emit("auth",a),o(a),h.then(g=>{this.isConnectionCurrent(l,n)&&(g?(this.replayTerminalOutbox(),this.replayStopResultOutbox()):this.reconnectAfterOutboundFlushFailure(l,n))})}else a.code===w?(this.agentDeleted=!0,f(new C(`Agent deleted: code=${a.code} msg=${a.msg}`))):f(new Error(`Auth failed: code=${a.code} msg=${a.msg}`))},reject:c=>{clearTimeout(d),f(c)},timer:E}),l.on("open",async()=>{if(await u,!this.isConnectionCurrent(l,n))return;const c={agent_id:this.config.agentId,api_key:this.config.apiKey,client_type:this.config.clientType,protocol_version:W,contract_version:U,capabilities:this.config.capabilities??[],local_actions:this.config.localActions,skills:this.config.skills,library_skills:this.config.librarySkills};this.config.sharedOwnerId&&(c.shared_owner_id=this.config.sharedOwnerId),this.config.clientVersion&&(c.client="grix-connector",c.client_version=this.config.clientVersion,c.host_type=this.config.clientType,c.host_version=this.config.clientVersion),this.config.adapterHint&&(c.adapter_hint=this.config.adapterHint),c.host_meta={hostname:S(),platform:$.platform(),arch:$.arch(),os_release:$.release(),...s!==void 0&&{tailnet_ip:s},...i!==void 0&&i>0&&{file_server_port:i},...r!==void 0&&r>0&&{file_server_https_port:r}},this.config.concurrency&&(c.concurrency=this.config.concurrency),this.sendPacket("auth",c,_)}),l.on("message",c=>{if(!this.isConnectionCurrent(l,n))return;let a;try{a=JSON.parse(D(c.toString()))}catch{return}try{this.handlePacket(a)}catch(h){this.emitClientError(new Error(`handlePacket error: ${h}`))}}),l.on("close",(c,a)=>{if(!this.isConnectionCurrent(l,n))return;const h=this.everConnected&&!this.agentDeleted;this.connected=!1,this.negotiatedCapabilities.clear(),this.stopHeartbeat(),this.clearTerminalRetryTimers(),this.clearStopResultRetryTimers(),this.rejectAllPendingRequests("websocket closed");const g=`websocket closed code=${c} reason=${a.toString()||"<none>"}`,b=this.outboundFlushInFlight;b&&b.socket===l&&b.connectionGeneration===n&&this.cancelOutboundFlush(b,h?"connection_lost":"discard",g),h?this.failNonReplayableOutputsForSocket(l,g):this.nonReplayableOutputEventsBySocket.delete(l),this.abortPendingOutputWrites(h,l,n,h?g:void 0),this.abortPendingAcceptedOutputs(h,l,n),this.clearAllEventCorrelations(),this.ws===l&&(this.ws=null),this.connectionGeneration++,this.emit("close",c,a.toString());const y=h;m.info("aibot",`ws closed agent=${this.config.clientType}:${this.config.agentId} code=${c} reason=${a.toString()||"<none>"} everConnected=${this.everConnected} reconnecting=${this.reconnecting} agentDeleted=${this.agentDeleted} willReconnect=${y}`),y&&this.attemptReconnect()}),l.on("error",c=>{if(this.isConnectionCurrent(l,n)&&(this.emitClientError(c instanceof Error?c:new Error(String(c))),!this.connected)){const a=this.pendingRequests.get(_);a&&(this.pendingRequests.delete(_),clearTimeout(a.timer),a.reject(c instanceof Error?c:new Error(String(c))))}})})}handlePacket(e){if(this.packetLog?.logInboundPacket(e.cmd,e.seq,e.payload),this.handleCorrelatedSendResponse(e),e.seq>0&&this.pendingRequests.has(e.seq)){const t=this.pendingRequests.get(e.seq);this.pendingRequests.delete(e.seq),clearTimeout(t.timer),t.expected.includes(e.cmd)?t.resolve(e):t.reject(new Error(`unexpected response: got ${e.cmd}, expected ${t.expected.join("/")}`));return}switch(e.cmd){case"auth_ack":break;case"ping":{this.sendPacket("pong",e.payload??{});break}case"event_msg":{const t=e.payload;if(!this.captureInboundTerminalCommitToken(t.event_id,t.terminal_commit_token))break;this.emit("event",t);break}case"local_action":{this.emit("localAction",e.payload);break}case"event_stop":{const t=e.payload;if(!this.captureInboundTerminalCommitToken(t.event_id,t.terminal_commit_token))break;this.emit("stop",t);break}case"event_revoke":{this.emit("revoke",e.payload);break}case"event_edit":{this.emit("edit",e.payload);break}case"event_cancel":{this.emit("eventCancel",e.payload);break}case"queue_clear":{this.emit("queueClear",e.payload);break}case"queue_reorder":{this.emit("queueReorder",e.payload);break}case"queue_snapshot_query":{this.emit("queueSnapshotQuery",e.payload);break}case"event_hold":{this.emit("eventHold",e.payload);break}case"queue_edit":{this.emit("queueEdit",e.payload);break}case"control_share_set":{this.emit("shareSet",e.payload);break}case"agent_profile_push":{this.emit("profilePush",e.payload);break}case"skill_sync":{this.emit("skillSync",e.payload);break}case"kicked":{const t=e.payload;this.emit("kicked",t),this.connected=!1,this.negotiatedCapabilities.clear(),this.stopHeartbeat(),this.clearTerminalRetryTimers(),this.clearStopResultRetryTimers(),this.rejectAllPendingRequests("kicked");const n=this.ws,s=this.connectionGeneration,i=this.outboundFlushInFlight;if(i&&n&&i.socket===n&&i.connectionGeneration===this.connectionGeneration&&this.cancelOutboundFlush(i,t?.reason===k?"discard":"connection_lost",`kicked reason=${t?.reason??"<none>"}`),t?.reason!==k&&n&&this.failNonReplayableOutputsForSocket(n,`kicked reason=${t?.reason??"<none>"}`),this.abortPendingOutputWrites(t?.reason!==k,n??void 0,s,t?.reason!==k?`kicked reason=${t?.reason??"<none>"}`:void 0),this.abortPendingAcceptedOutputs(t?.reason!==k,n??void 0,s),this.clearAllEventCorrelations(),this.connectionGeneration++,t?.reason===k){if(this.agentDeleted=!0,this.reconnecting=!1,this.outboundBuffer.length=0,this.outputIntegrityFailed.clear(),this.nonReplayableOutputEventsBySocket.clear(),this.ws){try{this.ws.close(4001,"kicked")}catch{}this.ws=null}m.error("aibot",`kicked: agent deleted on platform agent=${this.config.clientType}:${this.config.agentId}, reconnect disabled`),this.emit("agentDeleted",{source:"kicked",reason:t.reason});break}if(this.reconnectAttempts=Math.max(this.reconnectAttempts,3),this.ws){try{this.ws.close(4001,"kicked")}catch{}this.ws===n&&(this.ws=null)}this.attemptReconnect();break}case"error":{const t=e.payload,n=[t.ref_cmd?`ref_cmd=${t.ref_cmd}`:"",t.ref_id?`ref_id=${t.ref_id}`:""].filter(Boolean).join(" ");this.emitClientError(new Error(`Server error: code=${t.code} msg=${t.msg}${n?` ${n}`:""}`));break}case"agent_invoke_result":{this.handleInvokeResult(e.payload);break}case"mcp_frame":{const t=e.payload;this.emit("mcpFrame",t.session_id??"",t.frame??null);break}case"send_ack":break;case"send_nack":break;case"local_action_ack":break;default:break}}captureInboundTerminalCommitToken(e,t){const n=t?.trim();if(!n)return!0;if(!e?.trim())return this.rejectInboundTerminalCommitToken("tokenized event is missing event_id"),!1;if(!this.negotiatedCapabilities.has("terminal_commit_v1"))return this.rejectInboundTerminalCommitToken(`tokenized event received without terminal_commit_v1 negotiation event=${e}`),!1;try{return this.terminalCommitTokens.register(e,n),!0}catch(s){return this.rejectInboundTerminalCommitToken(`terminal commit token persist failed event=${e}: ${s instanceof Error?s.message:s}`),!1}}rejectInboundTerminalCommitToken(e){this.emitClientError(new Error(e));const t=this.ws;if(t)try{t.close(1011,"terminal commit token persistence failed")}catch{}}withTerminalCommitToken(e){const t=e.event_id.trim();if(!t)throw new Error("terminal event_result is missing event_id");const n=e.terminal_commit_token?.trim(),s=this.terminalCommitTokens.get(t);if(n&&s&&n!==s)throw new Error(`terminal commit token mismatch for event=${t}`);n&&!s&&this.terminalCommitTokens.register(t,n);const i=s??n;return i?{...e,event_id:t,terminal_commit_token:i}:{...e,event_id:t}}sendEventAck(e){this.sendPacket("event_ack",e)||m.warn("aibot",`event_ack NOT sent (ws not open) event=${e.event_id} ws=${this.ws?`state=${this.ws.readyState}`:"null"}`)}sendStreamChunk(e){!e.delta_content&&!e.is_finish&&(m.warn("aibot",`stream_chunk delta_content empty, patched to newline event=${e.event_id??""} session=${e.session_id} chunk_seq=${e.chunk_seq} is_finish=${e.is_finish}`),e={...e,delta_content:`
|
|
2
|
-
`}),this.sendPacket("client_stream_chunk",e)}sendMsg(e){const t={...e,client_msg_id:e.client_msg_id?.trim()||R()};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=R(),u=Math.max(1e3,Math.min(n,6e4)),o=setTimeout(()=>{this.pendingInvokes.delete(r),i(new Error(`agent_invoke timeout: ${e}`))},u);this.pendingInvokes.set(r,{resolve:s,reject:i,timer:o}),this.sendPacket("agent_invoke",{invoke_id:r,action:e,params:t,timeout_ms:u})})}sendMcpFrame(e,t){this.sendPacket("mcp_frame",{session_id:e,frame:t})}async request(e,t,n){const s=this.outboundFlushInFlight;if(s){if(!await s.promise)throw new Error(`outbound flush failed before request: ${e}`);if(!this.isConnectionCurrent(s.socket,s.connectionGeneration))throw new Error(`connection changed while waiting for outbound flush: ${e}`)}if(!this.connected||!this.ws||this.ws.readyState!==O.OPEN)throw new Error(`send failed: ${e} (websocket not connected)`);return new Promise((i,r)=>{const u=++this.seq,o=setTimeout(()=>{this.pendingRequests.delete(u),r(new N(`request timeout: ${e} (expected ${n.expected.join("/")})`))},n.timeoutMs);this.pendingRequests.set(u,{expected:n.expected,resolve:i,reject:r,timer:o}),this.sendPacket(e,t,u)||(this.pendingRequests.delete(u),clearTimeout(o),r(new Error(`send failed: ${e}`)))})}async sendStreamChunkRequest(e,t=2e4){return this.request("client_stream_chunk",e,{expected:["send_ack","send_nack","error"],timeoutMs:t})}async relayCredentialRequest(e,t=15e3){return this.request("relay_credential_request",e,{expected:["relay_credential_result","error"],timeoutMs:t})}async sendText(e,t=2e4){return this.request("send_msg",{msg_type:1,...e,client_msg_id:e.client_msg_id?.trim()||R()},{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()||R()},{expected:["send_ack","send_nack","error"],timeoutMs:t})}async editMessage(e,t=2e4){return this.request("edit_msg",e,{expected:["send_ack","send_nack","error"],timeoutMs:t})}async deleteMessage(e,t,n=2e4){return this.request("delete_msg",{session_id:e,msg_id:t},{expected:["send_ack","send_nack","error"],timeoutMs:n})}async sendEventResultRequest(e,t=5e3){return this.request("event_result",e,{expected:["send_ack","send_nack","error"],timeoutMs:t})}disconnect(){m.info("aibot",`disconnect() agent=${this.config.clientType}:${this.config.agentId} wasConnected=${this.connected} reconnecting=${this.reconnecting} reconnectAttempts=${this.reconnectAttempts}`),this.connected=!1,this.negotiatedCapabilities.clear(),this.everConnected=!1,this.reconnecting=!1,this.reconnectRunGeneration++,this.reconnectAttempts=0,this.stopHeartbeat(),this.clearTerminalRetryTimers(),this.clearStopResultRetryTimers();const e=this.ws,t=this.connectionGeneration,n=this.outboundFlushInFlight;n&&this.cancelOutboundFlush(n,"discard","explicit disconnect"),this.connectionGeneration++,this.rejectAllPendingInvokes("disconnect"),this.rejectAllPendingRequests("disconnect"),this.abortPendingOutputWrites(!1,e??void 0,t),this.abortPendingAcceptedOutputs(!1,e??void 0,t),this.clearAllEventCorrelations(),this.outboundBuffer.length=0,this.provisionalRespondedTerminals.clear(),this.outputIntegrityFailed.clear(),this.nonReplayableOutputEventsBySocket.clear(),this.ws&&(this.ws.close(1e3,"client disconnect"),this.ws=null)}static MAX_CONSECUTIVE_AUTH_FAILURES=5;async attemptReconnect(){if(this.reconnecting||this.agentDeleted)return;this.reconnecting=!0;const e=++this.reconnectRunGeneration;m.info("aibot",`attemptReconnect start agent=${this.config.clientType}:${this.config.agentId} fromAttempts=${this.reconnectAttempts}`),this.emit("disconnected");let t=0;for(;this.reconnecting&&this.reconnectRunGeneration===e;){const n=Math.min(1e3*2**this.reconnectAttempts,3e4),s=Math.floor(n*.2*Math.random());if(this.reconnectAttempts++,await new Promise(u=>setTimeout(u,n+s)),!this.reconnecting||this.reconnectRunGeneration!==e)return;let i=null,r=this.connectionGeneration;try{const u=this.connect();if(i=this.ws,r=this.connectionGeneration,await u,this.reconnectRunGeneration!==e)return;if(!i||!this.isConnectionCurrent(i,r)){if(this.connected&&this.ws){this.reconnecting=!1;return}continue}const o=this.reconnectAttempts;this.reconnectAttempts=0,this.reconnecting=!1,m.info("aibot",`reconnect succeeded agent=${this.config.clientType}:${this.config.agentId} attempt=${o}`);return}catch(u){if(this.reconnectRunGeneration!==e)return;if(i&&!this.isConnectionCurrent(i,r)&&this.connected&&this.ws){this.reconnecting=!1;return}if(i&&this.isConnectionCurrent(i,r)){this.connectionGeneration++;try{i.close()}catch{}this.ws===i&&(this.ws=null)}const o=u instanceof Error?u.message:String(u);if(m.warn("aibot",`reconnect failed agent=${this.config.clientType}:${this.config.agentId} attempt=${this.reconnectAttempts} err=${o}`),u instanceof C){this.agentDeleted=!0,this.reconnecting=!1,m.error("aibot",`reconnect aborted: agent deleted on platform agent=${this.config.clientType}:${this.config.agentId}`),this.emit("agentDeleted",{source:"auth_ack",code:w});return}if(/Auth failed/i.test(o)){if(t++,t>=p.MAX_CONSECUTIVE_AUTH_FAILURES){this.reconnecting=!1,m.error("aibot",`reconnect giving up after ${t} consecutive auth failures agent=${this.config.clientType}:${this.config.agentId}`);return}}else t=0}}}sendPacket(e,t,n){const s=this.outboundEventId(e,t);if(s&&this.blockedOutputEvents.has(s))return this.packetLog?.logOutboundPacket(e,n??0,t,"dropped"),m.warn("aibot",`blocked late output after irreversible event failure event=${s} cmd=${e}`),!1;const i=n===void 0?this.createOutboundEntry(e,t):void 0;if(i&&this.outboundFlushInFlight&&!this.outboundFlushInFlight.finalized&&this.isConnectionCurrent(this.outboundFlushInFlight.socket,this.outboundFlushInFlight.connectionGeneration)&&!p.DROPPABLE_COMMANDS.has(e))return this.bufferOutboundEntry(i),!1;if(this.ws&&this.ws.readyState===O.OPEN&&(e==="auth"||this.connected)){const u=this.ws.bufferedAmount>p.BACKPRESSURE_THRESHOLD;if(!u||!p.DROPPABLE_COMMANDS.has(e)){if(u&&p.DROPPABLE_COMMANDS.has(e))return!1;const o=n??++this.seq,f={cmd:e,seq:o,payload:t};this.packetLog?.logOutboundPacket(e,o,t,"sent");const l=this.ws,d=this.connectionGeneration,_=s;_&&this.trackOutboundEventCorrelation(e,o,t),i&&_&&(this.registerPendingOutputWrite(o,_,i,l,d),this.isAckRequiredOutput(e,t)&&this.registerPendingAcceptedOutput(o,_,i,l,d));let E=!1;try{const c=l.readyState,a=l.bufferedAmount;let h=!1;return l.send(JSON.stringify(f),g=>{if(g&&!h&&(E=!0),e==="event_result"){const v=t;g?m.warn("aibot",`event_result ws send callback failed event=${v.event_id??""} status=${v.status??""} seq=${o} readyState=${c} bufferedAmount=${a} err=${g.message}`):m.info("aibot",`event_result ws send callback ok event=${v.event_id??""} status=${v.status??""} seq=${o} readyState=${c} bufferedAmount=${a}`)}else if(e==="client_stream_chunk"){const v=t;g?m.warn("aibot",`stream_chunk ws send failed event=${v.event_id??""} session=${v.session_id??""} seq=${o} chunk_seq=${v.chunk_seq??""} is_finish=${v.is_finish??""} readyState=${c} bufferedAmount=${a} err=${g.message}`):m.info("aibot",`stream_chunk ws send ok event=${v.event_id??""} session=${v.session_id??""} seq=${o} chunk_seq=${v.chunk_seq??""} is_finish=${v.is_finish??""} client_msg_id=${v.client_msg_id??""} quoted_message_id=${v.quoted_message_id??""} readyState=${c} bufferedAmount=${a}`)}else if(e==="event_ack"){const v=t;g?m.warn("aibot",`event_ack ws send failed event=${v.event_id??""} seq=${o} readyState=${c} bufferedAmount=${a} err=${g.message}`):m.info("aibot",`event_ack ws send ok event=${v.event_id??""} seq=${o} readyState=${c} bufferedAmount=${a}`)}else if(e==="send_msg"){const v=t;g?m.warn("aibot",`send_msg ws send failed event=${v.event_id??""} session=${v.session_id??""} seq=${o} readyState=${c} bufferedAmount=${a} err=${g.message}`):m.info("aibot",`send_msg ws send ok event=${v.event_id??""} session=${v.session_id??""} seq=${o} readyState=${c} bufferedAmount=${a}`)}else if(g){const v=t;m.warn("aibot",`${e} ws send failed seq=${o} session=${v.session_id??""} event=${v.event_id??""} client_msg_id=${v.client_msg_id??""} readyState=${c} bufferedAmount=${a} err=${g.message}`)}const b=this.pendingOutputWrites.get(o),y=!!(b&&b.socket===l&&b.connectionGeneration===d);if(!this.isConnectionCurrent(l,d)){y&&(this.settlePendingOutputWrite(o,!1,!1,l,d),this.settlePendingAcceptedOutput(o,"retry",!1,l,d));return}if(!(i&&_&&!y)){if(g)this.removeSeqFromEventCorrelation(o),_?(this.settlePendingOutputWrite(o,!1,!0,l,d),this.settlePendingAcceptedOutput(o,"retry",!1,l,d)):i&&this.bufferOutboundEntry(i),n!==void 0?this.rejectPendingRequestAfterWriteFailure(o,e,g):i&&this.reconnectAfterOutboundWriteFailure(l,`${e} callback failed`);else if(_){const v=y;this.settlePendingOutputWrite(o,!0,!1,l,d),v&&this.trackNonReplayableOutput(l,e,t,o)}}}),h=!0,!E}catch(c){return this.isConnectionCurrent(l,d)?(this.removeSeqFromEventCorrelation(o),_?(this.settlePendingOutputWrite(o,!1,!0,l,d),this.settlePendingAcceptedOutput(o,"retry",!1,l,d)):i&&this.bufferOutboundEntry(i),this.emitClientError(new Error(`sendPacket failed: ${c}`)),i&&this.reconnectAfterOutboundWriteFailure(l,`${e} send threw`),!1):(this.settlePendingOutputWrite(o,!1,!1,l,d),this.settlePendingAcceptedOutput(o,"retry",!1,l,d),!1)}}}if(p.DROPPABLE_COMMANDS.has(e))return this.packetLog?.logOutboundPacket(e,n??0,t,"dropped"),!1;if(n!==void 0)return this.packetLog?.logOutboundPacket(e,n,t,"dropped"),!1;const r=this.bufferOutboundEntry(i??this.createOutboundEntry(e,t));if(this.packetLog?.logOutboundPacket(e,n??0,t,r?"buffered":"dropped"),r&&e==="client_stream_chunk"){const u=t;m.info("aibot",`stream_chunk buffered (ws not open) event=${u.event_id??""} session=${u.session_id??""} chunk_seq=${u.chunk_seq??""} is_finish=${u.is_finish??""} ws=${this.ws?`state=${this.ws.readyState}`:"null"}`)}return!1}replayTerminalOutbox(){for(const e of this.terminalOutbox.listPending())this.scheduleTerminalDelivery(e.payload.event_id,{ignoreNotBefore:!0})}outboundEventId(e,t){if(!p.ORDERED_OUTPUT_COMMANDS.has(e)||!t||typeof t!="object")return"";const n=t.event_id;return typeof n=="string"?n.trim():""}blockEventOutput(e){const t=e.trim();if(t)for(this.blockedOutputEvents.delete(t),this.blockedOutputEvents.set(t,Date.now()),this.outboundBuffer=this.outboundBuffer.filter(n=>this.outboundEventId(n.cmd,n.payload)!==t);this.blockedOutputEvents.size>p.MAX_BLOCKED_OUTPUT_EVENTS;){const n=this.blockedOutputEvents.keys().next().value;if(!n)break;this.blockedOutputEvents.delete(n)}}isAckRequiredOutput(e,t){return e==="send_msg"?!0:e==="client_stream_chunk"&&!!t&&typeof t=="object"&&t.is_finish===!0}isNonReplayableOutput(e,t){return e==="codex_event"?!0:e==="client_stream_chunk"&&!!t&&typeof t=="object"&&t.is_finish!==!0}trackNonReplayableOutput(e,t,n,s){if(!this.isNonReplayableOutput(t,n))return;const i=this.outboundEventId(t,n);if(!i||this.outputIntegrityFailed.has(i)||this.blockedOutputEvents.has(i))return;let r=this.nonReplayableOutputEventsBySocket.get(e);r||(r=new Map,this.nonReplayableOutputEventsBySocket.set(e,r)),r.set(i,Math.max(r.get(i)??0,s))}clearNonReplayableOutputThrough(e,t,n){if(!e||n<=0)return;const s=this.nonReplayableOutputEventsBySocket.get(e);if(!s)return;const i=s.get(t);i===void 0||i>n||(s.delete(t),s.size===0&&this.nonReplayableOutputEventsBySocket.delete(e))}clearNonReplayableOutputEvent(e){for(const[t,n]of this.nonReplayableOutputEventsBySocket)n.delete(e),n.size===0&&this.nonReplayableOutputEventsBySocket.delete(t)}failNonReplayableOutputsForSocket(e,t){const n=this.nonReplayableOutputEventsBySocket.get(e);if(n){this.nonReplayableOutputEventsBySocket.delete(e);for(const s of n.keys())this.markOutputIntegrityFailed(s,`connection lost before a complete output acceptance fence: ${t}`)}}hasNonReplayableOutput(e){for(const t of this.nonReplayableOutputEventsBySocket.values())if(t.has(e))return!0;return!1}hasPendingOutputAcceptanceFence(e){return!!(this.pendingAcceptedOutputSeqsByEvent.get(e)?.size||this.outboundBuffer.some(t=>this.outboundEventId(t.cmd,t.payload)===e&&this.isAckRequiredOutput(t.cmd,t.payload)))}hasUnconfirmedEventOutput(e){return!!(this.pendingOutputSeqsByEvent.get(e)?.size||this.pendingAcceptedOutputSeqsByEvent.get(e)?.size||this.outboundBuffer.some(t=>this.outboundEventId(t.cmd,t.payload)===e))}trackOutboundEventCorrelation(e,t,n){const s=this.outboundEventId(e,n);if(!s)return;const i=n,r=typeof i.client_msg_id=="string"?i.client_msg_id.trim():"";this.pruneExpiredEventCorrelations();const u=Date.now();let o=this.eventCorrelations.get(s);if(!o){if(this.eventCorrelations.size>=p.MAX_EVENT_CORRELATIONS){let l;for(const d of this.eventCorrelations.values())this.eventHasCriticalDeliveryState(d.eventId)||(!l||d.touchedAt<l.touchedAt)&&(l=d);l&&this.releaseEventCorrelations(l.eventId)}o={eventId:s,clientMsgIds:new Set,seqRanges:[],touchedAt:u,expiresAt:u+p.EVENT_CORRELATION_TTL_MS},this.eventCorrelations.set(s,o)}o.touchedAt=u,o.expiresAt=u+p.EVENT_CORRELATION_TTL_MS;const f=o.seqRanges[o.seqRanges.length-1];if(f&&t===f.end+1?f.end=t:(!f||t<f.start||t>f.end)&&o.seqRanges.push({start:t,end:t}),r){o.clientMsgIds.add(r);let l=this.clientMsgEventMap.get(r);l||(l=new Set,this.clientMsgEventMap.set(r,l)),l.add(s)}this.scheduleEventCorrelationCleanup()}uniqueEventIdForClientMsgId(e){const t=this.clientMsgEventMap.get(e);if(t?.size===1)return t.values().next().value}eventHasCriticalDeliveryState(e){return!!(this.terminalOutbox.get(e)||this.pendingAcceptedOutputSeqsByEvent.get(e)?.size)}eventIdForSeq(e){if(!(e<=0)){for(const t of this.eventCorrelations.values())if(t.seqRanges.some(n=>e>=n.start&&e<=n.end))return t.eventId}}removeSeqFromEventCorrelation(e){if(!(e<=0))for(const t of this.eventCorrelations.values()){const n=t.seqRanges.findIndex(i=>e>=i.start&&e<=i.end);if(n<0)continue;const s=t.seqRanges[n];s.start===s.end?t.seqRanges.splice(n,1):e===s.start?s.start++:e===s.end?s.end--:t.seqRanges.splice(n,1,{start:s.start,end:e-1},{start:e+1,end:s.end});return}}releaseEventCorrelations(e){const t=e.trim();if(!t)return;const n=this.eventCorrelations.get(t);if(n){for(const s of n.clientMsgIds){const i=this.clientMsgEventMap.get(s);i?.delete(t),i?.size===0&&this.clientMsgEventMap.delete(s)}this.eventCorrelations.delete(t)}this.cancelEventCorrelationCleanupIfEmpty()}clearAllEventCorrelations(){this.eventCorrelationCleanupTimer&&(clearTimeout(this.eventCorrelationCleanupTimer),this.eventCorrelationCleanupTimer=null),this.eventCorrelations.clear(),this.clientMsgEventMap.clear(),this.lastCorrelationPruneAt=0}pruneExpiredEventCorrelations(e=Date.now(),t=!1){if(!(!t&&e-this.lastCorrelationPruneAt<1e3)){this.lastCorrelationPruneAt=e;for(const n of[...this.eventCorrelations.values()])n.expiresAt<=e&&!this.eventHasCriticalDeliveryState(n.eventId)&&this.releaseEventCorrelations(n.eventId);this.cancelEventCorrelationCleanupIfEmpty()}}scheduleEventCorrelationCleanup(){if(this.eventCorrelationCleanupTimer||this.eventCorrelations.size===0)return;let e=Number.POSITIVE_INFINITY;for(const t of this.eventCorrelations.values())this.eventHasCriticalDeliveryState(t.eventId)||(e=Math.min(e,t.expiresAt));Number.isFinite(e)&&(this.eventCorrelationCleanupTimer=setTimeout(()=>{this.eventCorrelationCleanupTimer=null,this.pruneExpiredEventCorrelations(Date.now(),!0),this.scheduleEventCorrelationCleanup()},Math.max(1,e-Date.now())),this.eventCorrelationCleanupTimer.unref?.())}cancelEventCorrelationCleanupIfEmpty(){!this.eventCorrelationCleanupTimer||this.eventCorrelations.size>0||(clearTimeout(this.eventCorrelationCleanupTimer),this.eventCorrelationCleanupTimer=null)}handleCorrelatedSendResponse(e){if(e.cmd==="send_ack"){const f=e.payload,l=typeof f.client_msg_id=="string"?f.client_msg_id.trim():"";let d=e.seq>0&&this.pendingAcceptedOutputs.has(e.seq)?e.seq:void 0,_;if(d===void 0&&l){const a=this.pendingAcceptedSeqsByClientMsgId.get(l);if(a?.size===1){const h=a.values().next().value,g=this.pendingAcceptedOutputs.get(h),b=this.uniqueEventIdForClientMsgId(l);g&&b&&g.eventId===b&&(d=h,_=b)}}const E=(d!==void 0?this.pendingAcceptedOutputs.get(d)?.eventId:void 0)??_??this.eventIdForSeq(e.seq),c=d??e.seq;if(d!==void 0){const a=this.pendingOutputWrites.get(d);a&&this.settlePendingOutputWrite(d,!0,!1,a.socket,a.connectionGeneration),this.settlePendingAcceptedOutput(d,"accepted",!1)}else if(e.seq>0){const a=this.pendingOutputWrites.get(e.seq);a&&this.settlePendingOutputWrite(e.seq,!0,!1,a.socket,a.connectionGeneration)}E&&this.clearNonReplayableOutputThrough(this.ws,E,c);return}if(e.cmd!=="send_nack"&&e.cmd!=="error")return;this.pruneExpiredEventCorrelations();const t=e.payload,n=typeof t.client_msg_id=="string"?t.client_msg_id.trim():"",s=e.cmd==="error"&&typeof t.ref_id=="string"?t.ref_id.trim():"",i=e.cmd==="error"&&typeof t.ref_cmd=="string"?t.ref_cmd.trim():"",r=this.eventIdForSeq(e.seq),u=s&&(!i||p.ORDERED_OUTPUT_COMMANDS.has(i))?this.uniqueEventIdForClientMsgId(s)??(this.eventCorrelations.has(s)?s:void 0):void 0,o=r??(n?this.uniqueEventIdForClientMsgId(n):void 0)??u;o&&(this.blockEventOutput(o),this.settlePendingOutputWritesForEvent(o,!0),this.clearNonReplayableOutputEvent(o),e.seq>0&&this.pendingAcceptedOutputs.get(e.seq)?.eventId===o?this.settlePendingAcceptedOutput(e.seq,"rejected",!1):this.settlePendingAcceptedOutputsForEvent(o,"rejected",!1),t.code===4003&&(this.purgeBufferedStreamChunks(o),m.warn("aibot",`event payload rejected (4003), purging buffered chunks for event=${o}`)),this.promotePendingTerminalAfterOutputRejection(o,Number(t.code??0),t.msg),this.emit("streamRejected",o,Number(t.code??0)))}promotePendingTerminalAfterOutputRejection(e,t,n){this.provisionalRespondedTerminals.delete(e);const s=this.terminalOutbox.get(e);if(!(!s||s.payload.status!=="responded"&&s.payload.code!=="agent_output_unconfirmed"||s.deliveryStartedAt))try{const i=this.terminalOutbox.enqueue({...s.payload,status:"failed",code:"agent_output_rejected",msg:n?`required agent output was rejected: ${n}`:`required agent output was rejected by the server (code=${t})`,updated_at:Date.now()});this.scheduleTerminalDelivery(i.payload.event_id,{ignoreNotBefore:!0})}catch(i){this.emitClientError(new Error(`failed to persist output-rejection terminal promotion: event=${e} code=${t}: ${i instanceof Error?i.message:i}`))}}scheduleTerminalDelivery(e,t){if(!this.connected||!this.ws||this.ws.readyState!==O.OPEN)return;if(this.terminalInFlight.has(e)){t?.ignoreNotBefore&&this.terminalReplayAfterFlight.add(e);return}const n=this.terminalOutbox.get(e);if(!n)return;const s=this.terminalRetryTimers.get(e);s&&(clearTimeout(s),this.terminalRetryTimers.delete(e));const i=t?.ignoreNotBefore?0:Math.max(0,n.nextAttemptAt-Date.now()),r=Math.max(i,t?.minimumDelayMs??0);if(r>0){const u=setTimeout(()=>{this.terminalRetryTimers.delete(e),this.scheduleTerminalDelivery(e,{ignoreNotBefore:!0})},r);this.terminalRetryTimers.set(e,u);return}this.deliverTerminalEntry(n)}async deliverTerminalEntry(e){const t=e.payload.event_id;if(this.terminalInFlight.has(t)||!this.terminalOutbox.isCurrent(e))return;this.terminalInFlight.add(t);let n=0;try{n=await this.sendEventResultReliable(e)}finally{this.terminalInFlight.delete(t);const s=this.terminalReplayAfterFlight.delete(t);this.terminalOutbox.get(t)&&this.scheduleTerminalDelivery(t,s?{ignoreNotBefore:!0}:{minimumDelayMs:n})}}clearTerminalRetryTimers(){for(const e of this.terminalRetryTimers.values())clearTimeout(e);this.terminalRetryTimers.clear()}async sendEventResultReliable(e){const t=e.payload.event_id,n=e.payload.terminal_commit_token?.trim();if(n&&!this.negotiatedCapabilities.has("terminal_commit_v1")){const d=this.ws;return this.emitClientError(new Error(`refusing tokenized event_result on an unnegotiated connection: event=${t}`)),d&&this.reconnectAfterOutboundWriteFailure(d,"terminal_commit_v1 not negotiated"),p.TERMINAL_RETRY_DELAY_MS}if(!await this.awaitOutputDeliveryBarrier(t))return p.TERMINAL_RETRY_DELAY_MS;const s=this.provisionalRespondedTerminals.get(t);if(s&&this.terminalOutbox.isCurrent(e))if(!this.canReplaceUnsentOutputGuard(e))this.provisionalRespondedTerminals.delete(t);else try{return this.terminalOutbox.enqueue(s),this.provisionalRespondedTerminals.delete(t),0}catch(d){return this.emitClientError(new Error(`failed to promote confirmed output terminal to responded: event=${t}: ${d instanceof Error?d.message:d}`)),p.TERMINAL_RETRY_DELAY_MS}const i=e.payload;try{if(!this.terminalOutbox.markDeliveryStarted(e))return 0}catch(d){return this.emitClientError(new Error(`event_result delivery-start persist failed: event=${t}: ${d instanceof Error?d.message:d}`)),p.TERMINAL_RETRY_DELAY_MS}const r=Math.max(1,this.ackPolicy?.max_retries??3),u=this.ackPolicy?.push_ack_timeout_ms??5e3,o=750;let f="unknown delivery failure";for(let d=1;d<=r;d++){if(!this.terminalOutbox.isCurrent(e))return 0;const _=this.ws?.readyState??-1,E=this.ws?.bufferedAmount??0;m.info("aibot",`event_result send attempt event=${i.event_id} status=${i.status} attempt=${d}/${r} readyState=${_} bufferedAmount=${E}`);try{const c=await this.sendEventResultRequest(i,u);if(!this.terminalOutbox.isCurrent(e))return 0;if(c.cmd==="send_ack"){const h=c.payload;if(m.info("aibot",`event_result ack event=${i.event_id} status=${i.status} attempt=${d}/${r} ack_event=${h.event_id??""} ack_status=${h.status??""}`),h.event_id!==i.event_id||h.status!==i.status)throw new Error(`event_result ACK mismatch: expected event=${i.event_id} status=${i.status}, got event=${h.event_id??""} status=${h.status??""}`);if(n&&(h.terminal_commit_token?.trim()!==n||h.terminal_committed!==!0))throw new Error(`event_result terminal commit ACK mismatch: event=${i.event_id}`);if(n)try{this.terminalCommitTokens.remove(i.event_id,n)}catch(g){throw new Error(`terminal commit token cleanup failed: event=${i.event_id}: ${g instanceof Error?g.message:g}`)}return this.terminalOutbox.acknowledge(e,h.event_id,h.status,h.terminal_commit_token,h.terminal_committed)&&(this.rememberCommittedTerminalEvent(i.event_id),this.tokenizedTerminalRejections.delete(i.event_id),this.blockEventOutput(i.event_id),this.releaseEventCorrelations(i.event_id),this.clearNonReplayableOutputEvent(i.event_id),this.settleOutputIntegrityTerminal(i.event_id),this.scheduleStopResultsForEvent(i.event_id)),0}const a=c.payload;if(m.warn("aibot",`event_result rejected event=${i.event_id} status=${i.status} attempt=${d}/${r} cmd=${c.cmd} code=${a.code??""} msg=${a.msg??""}${a.ref_cmd?` ref_cmd=${a.ref_cmd}`:""}${a.ref_id?` ref_id=${a.ref_id}`:""}`),n){f=`tokenized terminal rejected: cmd=${c.cmd} code=${a.code??""} msg=${a.msg??""}`;const h=(this.tokenizedTerminalRejections.get(i.event_id)??0)+1;if(this.tokenizedTerminalRejections.set(i.event_id,h),h<p.MAX_TOKENIZED_TERMINAL_REJECTIONS)break;this.tokenizedTerminalRejections.delete(i.event_id);try{this.terminalCommitTokens.remove(i.event_id,n),this.terminalOutbox.moveToDeadLetter(e,{responseCmd:c.cmd,code:a.code,message:a.msg})&&(this.blockEventOutput(i.event_id),this.releaseEventCorrelations(i.event_id),this.clearNonReplayableOutputEvent(i.event_id),this.settleOutputIntegrityTerminal(i.event_id),this.emitClientError(new Error(`tokenized event_result permanently rejected after ${h} rejections, moved to dead-letter: event=${i.event_id} status=${i.status} cmd=${c.cmd} code=${a.code??""} msg=${a.msg??""}`)))}catch(g){f=`dead-letter persist failed: ${g instanceof Error?g.message:g}`,this.emitClientError(new Error(`tokenized event_result dead-letter persist failed: event=${i.event_id} status=${i.status}: ${f}`));break}return 0}try{return this.terminalOutbox.moveToDeadLetter(e,{responseCmd:c.cmd,code:a.code,message:a.msg})&&(this.tokenizedTerminalRejections.delete(i.event_id),this.blockEventOutput(i.event_id),this.releaseEventCorrelations(i.event_id),this.clearNonReplayableOutputEvent(i.event_id),this.settleOutputIntegrityTerminal(i.event_id),this.emitClientError(new Error(`event_result rejected and moved to dead-letter: event=${i.event_id} status=${i.status} cmd=${c.cmd} code=${a.code??""} msg=${a.msg??""}`))),0}catch(h){f=`dead-letter persist failed: ${h instanceof Error?h.message:h}`,this.emitClientError(new Error(`event_result dead-letter persist failed: event=${i.event_id} status=${i.status}: ${f}`));break}}catch(c){const a=c instanceof Error?c.message:String(c);if(f=a,m.warn("aibot",`event_result attempt failed event=${i.event_id} status=${i.status} attempt=${d}/${r} err=${a}`),d===r)break;await new Promise(h=>setTimeout(h,o*d))}}const l=Date.now()+p.TERMINAL_RETRY_DELAY_MS;try{this.terminalOutbox.recordRetry(e,l,f)}catch(d){this.emitClientError(new Error(`event_result retry state persist failed: event=${i.event_id} status=${i.status}: ${d instanceof Error?d.message:d}`))}return this.emitClientError(new Error(`event_result ack failed after ${r} attempts; retained for retry: event=${i.event_id} status=${i.status} err=${f}`)),p.TERMINAL_RETRY_DELAY_MS}purgeBufferedStreamChunks(e){const t=this.outboundBuffer.length;this.outboundBuffer=this.outboundBuffer.filter(n=>n.cmd!=="client_stream_chunk"?!0:n.payload?.event_id!==e),this.outboundBuffer.length<t&&m.info("aibot",`purged ${t-this.outboundBuffer.length} buffered stream chunks for event=${e}`)}emitClientError(e){if(this.listenerCount("error")===0){m.warn("aibot",`Client error (no listeners): ${e.message}`);return}this.emit("error",e)}createOutboundEntry(e,t){const n={cmd:e,payload:t};return this.outboundEntryOrders.set(n,++this.nextOutboundEntryOrder),n}makeOutputIntegrityFailurePayload(e,t){return this.withTerminalCommitToken({event_id:e,status:"failed",code:"agent_output_integrity_failed",msg:`required agent output was discarded before delivery: ${t}`,updated_at:Date.now()})}canReplaceUnsentOutputGuard(e){return e.payload.code==="agent_output_unconfirmed"&&!e.deliveryStartedAt}rememberCommittedTerminalEvent(e){for(this.committedTerminalEvents.delete(e),this.committedTerminalEvents.add(e);this.committedTerminalEvents.size>p.MAX_COMMITTED_TERMINAL_EVENTS;){const t=this.committedTerminalEvents.values().next().value;if(!t)break;this.committedTerminalEvents.delete(t)}}replayStopResultOutbox(){for(const e of this.stopResultOutbox.listPending())this.scheduleStopResultDelivery(e,{ignoreNotBefore:!0})}scheduleStopResultsForEvent(e){for(const t of this.stopResultOutbox.listPending())t.payload.event_id===e&&this.scheduleStopResultDelivery(t,{ignoreNotBefore:!0})}scheduleStopResultDelivery(e,t){if(!this.connected||!this.ws||this.ws.readyState!==O.OPEN||!this.stopResultOutbox.isCurrent(e))return;const n=e.payload.event_id;if(this.terminalOutbox.get(n)||this.terminalCommitTokens.get(n))return;const s=this.stopResultRetryTimers.get(e.key);s&&(clearTimeout(s),this.stopResultRetryTimers.delete(e.key));const i=t?.ignoreNotBefore?0:Math.max(0,e.nextAttemptAt-Date.now());if(i>0){const r=setTimeout(()=>{this.stopResultRetryTimers.delete(e.key),this.scheduleStopResultDelivery(e,{ignoreNotBefore:!0})},i);this.stopResultRetryTimers.set(e.key,r);return}this.deliverStopResultEntry(e)}async deliverStopResultEntry(e){if(this.stopResultInFlight.has(e.key)||!this.stopResultOutbox.isCurrent(e))return;this.stopResultInFlight.add(e.key);let t=p.TERMINAL_RETRY_DELAY_MS;try{t=await this.sendStopResultReliable(e)}finally{if(this.stopResultInFlight.delete(e.key),this.stopResultOutbox.isCurrent(e)){const n=setTimeout(()=>{this.stopResultRetryTimers.delete(e.key),this.scheduleStopResultDelivery(e,{ignoreNotBefore:!0})},t);this.stopResultRetryTimers.set(e.key,n)}}}async sendStopResultReliable(e){const t=this.ackPolicy?.push_ack_timeout_ms??5e3;let n="unknown delivery failure";try{const i=await this.request("event_stop_result",e.payload,{expected:["send_ack","send_nack","error"],timeoutMs:t});if(!this.stopResultOutbox.isCurrent(e))return 0;if(i.cmd==="send_ack"){const r=i.payload;if(this.stopResultOutbox.acknowledge(e,r.event_id,r.terminal_commit_token,r.terminal_committed))return 0;n=`event_stop_result ACK mismatch event=${e.payload.event_id}`}else{const r=i.payload;if(n=`event_stop_result rejected: cmd=${i.cmd} code=${r.code??""} msg=${r.msg??""}`,r.code===4001||r.code===4003){try{this.stopResultOutbox.discard(e)&&this.emitClientError(new Error(`event_stop_result permanently rejected and discarded: event=${e.payload.event_id} stop=${e.payload.stop_id} code=${r.code} msg=${r.msg??""}`))}catch(u){this.emitClientError(new Error(`event_stop_result discard persist failed: event=${e.payload.event_id} stop=${e.payload.stop_id}: ${u instanceof Error?u.message:u}`))}return 0}}}catch(i){n=i instanceof Error?i.message:String(i)}const s=Date.now()+p.TERMINAL_RETRY_DELAY_MS;try{this.stopResultOutbox.recordRetry(e,s,n)}catch(i){this.emitClientError(new Error(`event_stop_result retry state persist failed: event=${e.payload.event_id} stop=${e.payload.stop_id}: ${i instanceof Error?i.message:i}`))}return this.emitClientError(new Error(`event_stop_result retained for retry: event=${e.payload.event_id} stop=${e.payload.stop_id} err=${n}`)),p.TERMINAL_RETRY_DELAY_MS}clearStopResultRetryTimers(){for(const e of this.stopResultRetryTimers.values())clearTimeout(e);this.stopResultRetryTimers.clear()}markOutputIntegrityFailed(e,t){if(this.blockEventOutput(e),this.outputIntegrityFailed.has(e))return;const n={reason:t,terminalObserved:!!(this.provisionalRespondedTerminals.has(e)||this.terminalOutbox.get(e)),terminalSettled:!1};this.outputIntegrityFailed.set(e,n),this.provisionalRespondedTerminals.delete(e),this.clearNonReplayableOutputEvent(e);const s=this.outboundBuffer.length;this.outboundBuffer=this.outboundBuffer.filter(u=>this.outboundEventId(u.cmd,u.payload)!==e);const i=this.terminalOutbox.get(e),r=i?this.canReplaceUnsentOutputGuard(i):!1;if(i&&!r)n.terminalObserved=!0,this.scheduleTerminalDelivery(e,{ignoreNotBefore:!0});else if(!this.committedTerminalEvents.has(e))try{const u=this.terminalOutbox.enqueue(this.makeOutputIntegrityFailurePayload(e,t));this.scheduleTerminalDelivery(u.payload.event_id,{ignoreNotBefore:!0})}catch(u){this.emitClientError(new Error(`output-integrity terminal persist failed: event=${e}: ${u instanceof Error?u.message:u}`))}for(const u of[...this.pendingOutputSeqsByEvent.get(e)??[]])this.settlePendingOutputWrite(u,!1,!1);this.settlePendingAcceptedOutputsForEvent(e,"rejected",!1),this.emit("streamRejected",e,0),this.emitClientError(new Error(`required output integrity failed: event=${e} removed=${s-this.outboundBuffer.length} reason=${t}`))}settleOutputIntegrityTerminal(e){const t=this.outputIntegrityFailed.get(e);t&&(t.terminalSettled=!0,t.terminalObserved&&this.outputIntegrityFailed.delete(e))}bufferOutboundEntry(e){if(this.outboundEntryOrders.has(e)||this.outboundEntryOrders.set(e,++this.nextOutboundEntryOrder),this.outboundBuffer.includes(e))return!0;const t=this.outboundEventId(e.cmd,e.payload);return t&&(this.outputIntegrityFailed.has(t)||this.blockedOutputEvents.has(t))?!1:(this.outboundBuffer.length>=p.MAX_OUTBOUND_BUFFER_SIZE&&(this.outboundBuffer=this.outboundBuffer.filter(n=>p.BUFFER_OVERFLOW_RETAIN_COMMANDS.has(n.cmd)),this.outboundBuffer.length>=p.MAX_OUTBOUND_BUFFER_SIZE&&m.warn("aibot",`outbound buffer soft limit exceeded by ${this.outboundBuffer.length} retained packet(s); preserving required output`)),this.outboundBuffer.length>=p.MAX_OUTBOUND_BUFFER_HARD_SIZE?(t?this.markOutputIntegrityFailed(t,`outbound buffer hard limit ${p.MAX_OUTBOUND_BUFFER_HARD_SIZE} reached`):this.emitClientError(new Error(`outbound buffer hard limit ${p.MAX_OUTBOUND_BUFFER_HARD_SIZE} reached; rejected non-event packet cmd=${e.cmd}`)),!1):(this.outboundBuffer.push(e),this.outboundBuffer.sort((n,s)=>(this.outboundEntryOrders.get(n)??Number.MAX_SAFE_INTEGER)-(this.outboundEntryOrders.get(s)??Number.MAX_SAFE_INTEGER)),!0))}registerPendingOutputWrite(e,t,n,s,i){let r;const u=new Promise(l=>{r=l}),o=setTimeout(()=>{const l=this.pendingOutputWrites.get(e);if(!l||l.socket!==s||l.connectionGeneration!==i)return;if(!this.isConnectionCurrent(s,i)){this.settlePendingOutputWrite(e,!1,!1,s,i);return}this.removeSeqFromEventCorrelation(e);const d=this.isNonReplayableOutput(n.cmd,n.payload);this.settlePendingOutputWrite(e,!1,!d,s,i),d&&this.markOutputIntegrityFailed(t,`websocket write callback timed out for non-replayable ${n.cmd} seq=${e}`),this.emitClientError(new Error(`outbound websocket write callback timeout: cmd=${n.cmd} event=${t} seq=${e} delivery_unknown=${d}`)),this.reconnectAfterOutboundWriteFailure(s,`${n.cmd} callback timeout`)},p.OUTBOUND_WRITE_CALLBACK_TIMEOUT_MS);o.unref?.(),this.pendingOutputWrites.set(e,{eventId:t,entry:n,socket:s,connectionGeneration:i,promise:u,resolve:r,timer:o});let f=this.pendingOutputSeqsByEvent.get(t);f||(f=new Set,this.pendingOutputSeqsByEvent.set(t,f)),f.add(e)}settlePendingOutputWrite(e,t,n,s,i){const r=this.pendingOutputWrites.get(e);if(!r||s&&(r.socket!==s||r.connectionGeneration!==i))return;this.pendingOutputWrites.delete(e),clearTimeout(r.timer);const u=this.pendingOutputSeqsByEvent.get(r.eventId);u?.delete(e),u?.size===0&&this.pendingOutputSeqsByEvent.delete(r.eventId),n&&this.bufferOutboundEntry(r.entry),r.resolve(t)}settlePendingOutputWritesForEvent(e,t){const n=this.pendingOutputSeqsByEvent.get(e);if(n)for(const s of[...n]){const i=this.pendingOutputWrites.get(s);i&&this.settlePendingOutputWrite(s,t,!1,i.socket,i.connectionGeneration)}}abortPendingOutputWrites(e,t,n,s){for(const[i,r]of[...this.pendingOutputWrites.entries()])if(!(t&&(r.socket!==t||r.connectionGeneration!==n))){if(this.removeSeqFromEventCorrelation(i),s&&this.isNonReplayableOutput(r.entry.cmd,r.entry.payload)){this.settlePendingOutputWrite(i,!1,!1,t,n),this.markOutputIntegrityFailed(r.eventId,`connection lost while websocket write completion was unknown: ${s}`);continue}this.settlePendingOutputWrite(i,!1,e,t,n)}}registerPendingAcceptedOutput(e,t,n,s,i){let r;const u=new Promise(h=>{r=h}),o=Math.max(1,this.ackPolicy?.push_ack_timeout_ms??5e3),f=Math.max(1,this.ackPolicy?.max_retries??3),l=Math.max(1e3,Math.ceil(o*.25)),d=Math.max(p.MIN_REQUIRED_OUTPUT_ACK_TIMEOUT_MS,o*f+l),_=setTimeout(()=>{const h=this.pendingAcceptedOutputs.get(e);if(!(!h||h.socket!==s||h.connectionGeneration!==i)){if(!this.isConnectionCurrent(s,i)){this.settlePendingAcceptedOutput(e,"retry",!1,s,i);return}this.settlePendingAcceptedOutput(e,"retry",!0,s,i),this.emitClientError(new Error(`required output ACK timeout; retained for reconnect retry: event=${t} seq=${e}`)),this.reconnectAfterOutboundWriteFailure(s,"required output ACK timeout")}},d);_.unref?.(),this.pendingAcceptedOutputs.set(e,{eventId:t,entry:n,socket:s,connectionGeneration:i,promise:u,resolve:r,timer:_});const E=n.payload,c=typeof E.client_msg_id=="string"?E.client_msg_id.trim():"";if(c){let h=this.pendingAcceptedSeqsByClientMsgId.get(c);h||(h=new Set,this.pendingAcceptedSeqsByClientMsgId.set(c,h)),h.add(e)}let a=this.pendingAcceptedOutputSeqsByEvent.get(t);a||(a=new Set,this.pendingAcceptedOutputSeqsByEvent.set(t,a)),a.add(e)}settlePendingAcceptedOutput(e,t,n,s,i){const r=this.pendingAcceptedOutputs.get(e);if(!r||s&&(r.socket!==s||r.connectionGeneration!==i))return;this.pendingAcceptedOutputs.delete(e),clearTimeout(r.timer);const u=this.pendingAcceptedOutputSeqsByEvent.get(r.eventId);u?.delete(e),u?.size===0&&this.pendingAcceptedOutputSeqsByEvent.delete(r.eventId);const o=r.entry.payload,f=typeof o.client_msg_id=="string"?o.client_msg_id.trim():"";if(f){const l=this.pendingAcceptedSeqsByClientMsgId.get(f);l?.delete(e),l?.size===0&&this.pendingAcceptedSeqsByClientMsgId.delete(f)}n&&this.bufferOutboundEntry(r.entry),r.resolve(t)}settlePendingAcceptedOutputsForEvent(e,t,n){const s=this.pendingAcceptedOutputSeqsByEvent.get(e);if(s)for(const i of[...s])this.settlePendingAcceptedOutput(i,t,n)}abortPendingAcceptedOutputs(e,t,n){for(const[s,i]of[...this.pendingAcceptedOutputs.entries()])t&&(i.socket!==t||i.connectionGeneration!==n)||this.settlePendingAcceptedOutput(s,"retry",e,t,n)}rejectPendingRequestAfterWriteFailure(e,t,n){const s=this.pendingRequests.get(e);s&&(this.pendingRequests.delete(e),clearTimeout(s.timer),s.reject(new Error(`websocket write failed: ${t}: ${n.message}`)))}async awaitOutputDeliveryBarrier(e){const t=this.outboundFlushInFlight;if(t&&!await t.promise)return!1;for(;;){const n=this.pendingOutputSeqsByEvent.get(e);if(!n||n.size===0)break;const s=[...n].map(r=>this.pendingOutputWrites.get(r)?.promise).filter(r=>!!r);if(s.length===0)break;if((await Promise.all(s)).some(r=>!r))return!1}for(;;){const n=this.pendingAcceptedOutputSeqsByEvent.get(e);if(!n||n.size===0)break;const s=[...n].map(r=>this.pendingAcceptedOutputs.get(r)?.promise).filter(r=>!!r);if(s.length===0)break;const i=await Promise.all(s);if(i.includes("retry"))return!1;if(i.includes("rejected")){const r=this.terminalOutbox.get(e);if(!r||r.payload.status==="responded")return!1}}return this.outboundBuffer.some(n=>this.outboundEventId(n.cmd,n.payload)===e)?!1:this.hasNonReplayableOutput(e)?(this.markOutputIntegrityFailed(e,"output flush completed without a complete acceptance fence"),!1):!!(this.connected&&this.ws&&this.ws.readyState===O.OPEN)}isConnectionCurrent(e,t){return this.ws===e&&this.connectionGeneration===t}isOutboundFlushCurrent(e){return!e.finalized&&this.outboundFlushInFlight===e&&this.isConnectionCurrent(e.socket,e.connectionGeneration)}flushOutboundBuffer(e=this.ws,t=this.connectionGeneration){if(!e||!this.isConnectionCurrent(e,t))return Promise.resolve(!1);const n=this.outboundFlushInFlight;if(n){if(!n.finalized&&n.socket===e&&n.connectionGeneration===t)return n.promise;this.cancelOutboundFlush(n,"discard","superseded stale flush")}if(this.outboundBuffer.length===0)return Promise.resolve(!0);const s={connectionGeneration:t,flushGeneration:++this.nextOutboundFlushGeneration,socket:e,promise:Promise.resolve(!1),finalized:!1,activeBatch:null,nextIndex:0,currentWritePending:!1,currentWriteOutcome:null,cancelCurrentWrite:null,remainderHandled:!1};return this.outboundFlushInFlight=s,s.promise=this.performOutboundBufferFlush(s),s.promise.finally(()=>{s.finalized=!0,s.cancelCurrentWrite?.(),s.cancelCurrentWrite=null,this.outboundFlushInFlight===s&&(this.outboundFlushInFlight=null)}),s.promise}async performOutboundBufferFlush(e){for(;this.isOutboundFlushCurrent(e)&&this.outboundBuffer.length>0;){const{socket:t}=e;if(t.readyState!==O.OPEN)return!1;const n=this.outboundBuffer;this.outboundBuffer=[],e.activeBatch=n,e.nextIndex=0,e.remainderHandled=!1;for(let s=0;s<n.length;s++){if(!this.isOutboundFlushCurrent(e))return!1;const i=n[s],{cmd:r,payload:u}=i,o=this.outboundEventId(r,u);if(o&&this.blockedOutputEvents.has(o)){this.packetLog?.logOutboundPacket(r,0,u,"dropped"),e.nextIndex=s+1,e.currentWritePending=!1,e.currentWriteOutcome=null;continue}const f=++this.seq,l={cmd:r,seq:f,payload:u};e.nextIndex=s,e.currentWritePending=!0,e.currentWriteOutcome=null,o&&this.isAckRequiredOutput(r,u)&&this.registerPendingAcceptedOutput(f,o,i,t,e.connectionGeneration);const d=await new Promise(_=>{let E=!1;const c=h=>{E||(E=!0,clearTimeout(a),e.currentWriteOutcome=h,_(h))},a=setTimeout(()=>c("uncertain"),p.OUTBOUND_WRITE_CALLBACK_TIMEOUT_MS);a.unref?.(),e.cancelCurrentWrite=()=>c("canceled");try{this.trackOutboundEventCorrelation(r,f,u),t.send(JSON.stringify(l),h=>{c(h?"retry":"written")})}catch{c("retry")}});if(e.cancelCurrentWrite=null,!this.isOutboundFlushCurrent(e))return!1;if(d!=="written"||t.readyState!==O.OPEN){d==="retry"&&this.removeSeqFromEventCorrelation(f),this.settlePendingAcceptedOutput(f,"retry",!1);const _=this.retainOutboundFlushRemainder(e,d,`auth flush write failed for ${r} seq=${f} outcome=${d}`);return this.emitClientError(new Error(`outbound buffer flush failed at cmd=${r} seq=${f} outcome=${d}; ${_} packet(s) retained`)),!1}this.trackNonReplayableOutput(t,r,u,f),e.currentWritePending=!1,e.currentWriteOutcome="written",e.nextIndex=s+1}e.activeBatch=null,e.currentWritePending=!1,e.currentWriteOutcome=null,e.remainderHandled=!0}return this.isOutboundFlushCurrent(e)}retainOutboundFlushRemainder(e,t,n){if(e.remainderHandled||!e.activeBatch)return 0;e.remainderHandled=!0;const s=e.activeBatch,i=Math.min(e.nextIndex,s.length);let r=i;if(e.currentWritePending&&i<s.length){const o=s[i],f=this.outboundEventId(o.cmd,o.payload);t!=="retry"&&this.isNonReplayableOutput(o.cmd,o.payload)&&f&&(this.markOutputIntegrityFailed(f,n),r=i+1)}let u=0;for(const o of s.slice(r))this.bufferOutboundEntry(o)&&u++;return e.activeBatch=null,e.nextIndex=s.length,e.currentWritePending=!1,u}cancelOutboundFlush(e,t,n){if(e.finalized){this.outboundFlushInFlight===e&&(this.outboundFlushInFlight=null);return}e.finalized=!0,this.outboundFlushInFlight===e&&(this.outboundFlushInFlight=null),t==="connection_lost"?this.retainOutboundFlushRemainder(e,e.currentWriteOutcome,`connection lost during auth flush: ${n}`):(e.remainderHandled=!0,e.activeBatch=null,e.currentWritePending=!1);const s=e.cancelCurrentWrite;e.cancelCurrentWrite=null,s?.()}reconnectAfterOutboundFlushFailure(e,t){this.agentDeleted||!this.isConnectionCurrent(e,t)||this.reconnectAfterOutboundWriteFailure(e,"outbound flush failed")}reconnectAfterOutboundWriteFailure(e,t){if(this.agentDeleted||this.ws!==e)return;const n=this.connectionGeneration;this.connected=!1,this.negotiatedCapabilities.clear(),this.stopHeartbeat(),this.clearTerminalRetryTimers(),this.clearStopResultRetryTimers(),this.rejectAllPendingRequests(t);const s=this.outboundFlushInFlight;s&&s.socket===e&&s.connectionGeneration===n&&this.cancelOutboundFlush(s,"connection_lost",t),this.failNonReplayableOutputsForSocket(e,t),this.abortPendingOutputWrites(!0,e,n,t),this.abortPendingAcceptedOutputs(!0,e,n),this.clearAllEventCorrelations(),this.connectionGeneration++;try{e.close(1011,t)}catch{}this.ws===e&&(this.ws=null),this.attemptReconnect()}handleInvokeResult(e){const t=this.pendingInvokes.get(e.invoke_id);t&&(this.pendingInvokes.delete(e.invoke_id),clearTimeout(t.timer),e.code===0?t.resolve(e.data??null):t.reject(new Error(`agent_invoke error code=${e.code}: ${e.msg??""}`)))}rejectAllPendingInvokes(e){for(const[,t]of this.pendingInvokes)clearTimeout(t.timer),t.reject(new Error(`agent_invoke canceled: ${e}`));this.pendingInvokes.clear()}rejectAllPendingRequests(e){for(const[,t]of this.pendingRequests)clearTimeout(t.timer),t.reject(new Error(`request canceled: ${e}`));this.pendingRequests.clear()}cleanupSocket(e=this.ws,t=this.connectionGeneration){if(!(!e||!this.isConnectionCurrent(e,t)))try{e.close()}catch{}}startHeartbeat(){this.stopHeartbeat(),this.heartbeatFailures=0,this.heartbeatTimer=setInterval(()=>{const e=this.ws,t=this.connectionGeneration;!this.connected||!e||this.request("ping",{ts:Date.now()},{expected:["pong"],timeoutMs:5e3}).then(()=>{this.isConnectionCurrent(e,t)&&(this.heartbeatFailures=0)}).catch(()=>{!this.connected||!this.isConnectionCurrent(e,t)||(this.heartbeatFailures++,!(this.heartbeatFailures<p.HEARTBEAT_MAX_FAILURES)&&(this.cleanupSocket(e,t),this.attemptReconnect()))})},this.heartbeatSec*1e3)}stopHeartbeat(){this.heartbeatTimer&&(clearInterval(this.heartbeatTimer),this.heartbeatTimer=null)}}export{p as AibotClient};
|
|
1
|
+
import{EventEmitter as A}from"node:events";import{randomUUID as T}from"node:crypto";import $ from"node:os";import O from"ws";import{log as m}from"../log/index.js";import{getMachineName as S}from"../util/index.js";import{detectTailnetIPv4 as I,ensureServerAndGetPort as P,getFileServerHttpsPort as F}from"../files/file-serve.js";import{TerminalOutbox as M}from"../persistence/terminal-outbox.js";import{TerminalCommitTokenStore as q}from"../persistence/terminal-commit-token-store.js";import{StopResultOutbox as B}from"../persistence/stop-result-outbox.js";import{AUTH_CODE_AGENT_DELETED as w,KICKED_REASON_AGENT_DELETED as k,AgentDeletedError as C,RequestTimeoutError as N}from"./errors.js";function D(R){return R.replace(/(?<=[\[:,\[]\s*)(\d{16,})(?=\s*[,}\]\n])/g,'"$1"')}function x(R){const e=[...R??["stream_chunk","local_action_v1","agent_invoke"]];return e.includes("agent_invoke")||e.push("agent_invoke"),e.includes("event_result_ack")||e.push("event_result_ack"),e.includes("terminal_commit_v1")||e.push("terminal_commit_v1"),e.includes("audit_replay_v2")||e.push("audit_replay_v2"),e}function W(R){const e=[...R??["exec_approve","exec_reject"]];return e.includes("apply_relay_state")||e.push("apply_relay_state"),e}const U="aibot-agent-api-v1",L=1;class p extends A{static DROPPABLE_COMMANDS=new Set(["update_binding_card"]);static BUFFER_OVERFLOW_RETAIN_COMMANDS=new Set(["event_result","codex_event","client_stream_chunk","send_msg","audit_state"]);static MAX_OUTBOUND_BUFFER_SIZE=1e3;static MAX_OUTBOUND_BUFFER_HARD_SIZE=1200;static BACKPRESSURE_THRESHOLD=64*1024;static TERMINAL_RETRY_DELAY_MS=15e3;static MAX_TOKENIZED_TERMINAL_REJECTIONS=5;static EVENT_CORRELATION_TTL_MS=5*6e4;static MAX_EVENT_CORRELATIONS=1024;static MAX_BLOCKED_OUTPUT_EVENTS=4096;static MAX_COMMITTED_TERMINAL_EVENTS=4096;static OUTBOUND_WRITE_CALLBACK_TIMEOUT_MS=1e4;static MIN_REQUIRED_OUTPUT_ACK_TIMEOUT_MS=2e4;static ORDERED_OUTPUT_COMMANDS=new Set(["client_stream_chunk","send_msg","codex_event"]);ws=null;seq=0;heartbeatTimer=null;heartbeatSec=30;heartbeatFailures=0;static HEARTBEAT_MAX_FAILURES=2;connected=!1;reconnecting=!1;reconnectAttempts=0;reconnectRunGeneration=0;everConnected=!1;agentDeleted=!1;config;packetLog;pendingInvokes=new Map;eventCorrelations=new Map;clientMsgEventMap=new Map;eventCorrelationCleanupTimer=null;lastCorrelationPruneAt=0;pendingRequests=new Map;outboundBuffer=[];outboundEntryOrders=new WeakMap;nextOutboundEntryOrder=0;connectionGeneration=0;nextOutboundFlushGeneration=0;outboundFlushInFlight=null;pendingOutputWrites=new Map;pendingOutputSeqsByEvent=new Map;pendingAcceptedOutputs=new Map;pendingAcceptedOutputSeqsByEvent=new Map;pendingAcceptedSeqsByClientMsgId=new Map;nonReplayableOutputEventsBySocket=new Map;provisionalRespondedTerminals=new Map;outputIntegrityFailed=new Map;blockedOutputEvents=new Map;committedTerminalEvents=new Set;ackPolicy=null;negotiatedCapabilities=new Set;terminalOutbox;terminalCommitTokens;stopResultOutbox;terminalInFlight=new Set;terminalRetryTimers=new Map;terminalReplayAfterFlight=new Set;tokenizedTerminalRejections=new Map;stopResultInFlight=new Set;stopResultRetryTimers=new Map;constructor(e,t){super(),this.packetLog=t?.packetLog??null,this.config={url:e.url,agentId:e.agentId,apiKey:e.apiKey,clientType:e.clientType,clientVersion:e.clientVersion??"",adapterHint:e.adapterHint??"",capabilities:x(e.capabilities),localActions:W(e.localActions),skills:e.skills,librarySkills:e.librarySkills,sharedOwnerId:e.sharedOwnerId,concurrency:e.concurrency,terminalOutboxPath:e.terminalOutboxPath,terminalCommitTokenStorePath:e.terminalCommitTokenStorePath,stopResultOutboxPath:e.stopResultOutboxPath},this.terminalOutbox=new M(this.config.terminalOutboxPath),this.terminalCommitTokens=new q(this.config.terminalCommitTokenStorePath??(this.config.terminalOutboxPath?`${this.config.terminalOutboxPath}.tokens`:void 0)),this.stopResultOutbox=new B(this.config.stopResultOutboxPath??(this.config.terminalOutboxPath?`${this.config.terminalOutboxPath}.stops`:void 0));for(const n of this.terminalOutbox.listPending())n.payload.code==="agent_output_integrity_failed"&&this.blockEventOutput(n.payload.event_id)}get isConnected(){return this.connected}async connect(){this.negotiatedCapabilities.clear();const e=this.ws,t=this.connectionGeneration;if(this.pendingRequests.size>0&&this.rejectAllPendingRequests("connection generation replaced"),e){this.connected=!1,this.stopHeartbeat(),this.clearTerminalRetryTimers(),this.clearStopResultRetryTimers();const o=this.outboundFlushInFlight;o&&o.socket===e&&o.connectionGeneration===t&&this.cancelOutboundFlush(o,"connection_lost","connection generation replaced"),this.failNonReplayableOutputsForSocket(e,"connection generation replaced"),this.abortPendingOutputWrites(!0,e,t,"connection generation replaced"),this.abortPendingAcceptedOutputs(!0,e,t),this.clearAllEventCorrelations(),this.connectionGeneration++;try{e.close(1012,"connection generation replaced")}catch{}this.ws===e&&(this.ws=null)}else this.outboundFlushInFlight&&this.cancelOutboundFlush(this.outboundFlushInFlight,"discard","detached connection generation replaced");const n=++this.connectionGeneration;let s,i,r;const u=(async()=>{try{if(s=await I(),s!==void 0)try{i=await P(s);const o=F();o>0&&(r=o)}catch(o){m.warn("aibot",`file server pre-start failed: ${o}`)}}catch(o){m.warn("aibot",`tailnet detect failed: ${o}`)}})();return new Promise((o,f)=>{const l=new O(this.config.url);this.ws=l;const d=setTimeout(()=>{if(!this.isConnectionCurrent(l,n))return;const c=this.pendingRequests.get(_);c&&(this.pendingRequests.delete(_),clearTimeout(c.timer),c.reject(new Error("Auth timeout: no auth_ack received within 15s"))),this.cleanupSocket(l,n)},15e3),_=++this.seq,E=setTimeout(()=>{if(!this.isConnectionCurrent(l,n))return;const c=this.pendingRequests.get(_);c&&(this.pendingRequests.delete(_),c.reject(new Error("Auth request timeout"))),this.cleanupSocket(l,n)},15e3);this.pendingRequests.set(_,{expected:["auth_ack"],resolve:c=>{clearTimeout(d);const a=c.payload;if(a.code===0){this.negotiatedCapabilities=new Set(Array.isArray(a.supported_capabilities)?a.supported_capabilities:[]),this.connected=!0,this.everConnected=!0,this.reconnectAttempts=0,a.heartbeat_sec&&(this.heartbeatSec=a.heartbeat_sec),a.ack_policy&&(this.ackPolicy=a.ack_policy,m.info("aibot",`ack_policy received: push_ack_timeout_ms=${a.ack_policy.push_ack_timeout_ms??"default"} max_retries=${a.ack_policy.max_retries??"default"} timeout_action=${a.ack_policy.timeout_action??"default"}`)),this.startHeartbeat();const h=this.flushOutboundBuffer(l,n);this.emit("auth",a),o(a),h.then(g=>{this.isConnectionCurrent(l,n)&&(g?(this.replayTerminalOutbox(),this.replayStopResultOutbox()):this.reconnectAfterOutboundFlushFailure(l,n))})}else a.code===w?(this.agentDeleted=!0,f(new C(`Agent deleted: code=${a.code} msg=${a.msg}`))):f(new Error(`Auth failed: code=${a.code} msg=${a.msg}`))},reject:c=>{clearTimeout(d),f(c)},timer:E}),l.on("open",async()=>{if(await u,!this.isConnectionCurrent(l,n))return;const c={agent_id:this.config.agentId,api_key:this.config.apiKey,client_type:this.config.clientType,protocol_version:U,contract_version:L,capabilities:this.config.capabilities??[],local_actions:this.config.localActions,skills:this.config.skills,library_skills:this.config.librarySkills};this.config.sharedOwnerId&&(c.shared_owner_id=this.config.sharedOwnerId),this.config.clientVersion&&(c.client="grix-connector",c.client_version=this.config.clientVersion,c.host_type=this.config.clientType,c.host_version=this.config.clientVersion),this.config.adapterHint&&(c.adapter_hint=this.config.adapterHint),c.host_meta={hostname:S(),platform:$.platform(),arch:$.arch(),os_release:$.release(),...s!==void 0&&{tailnet_ip:s},...i!==void 0&&i>0&&{file_server_port:i},...r!==void 0&&r>0&&{file_server_https_port:r}},this.config.concurrency&&(c.concurrency=this.config.concurrency),this.sendPacket("auth",c,_)}),l.on("message",c=>{if(!this.isConnectionCurrent(l,n))return;let a;try{a=JSON.parse(D(c.toString()))}catch{return}try{this.handlePacket(a)}catch(h){this.emitClientError(new Error(`handlePacket error: ${h}`))}}),l.on("close",(c,a)=>{if(!this.isConnectionCurrent(l,n))return;const h=this.everConnected&&!this.agentDeleted;this.connected=!1,this.negotiatedCapabilities.clear(),this.stopHeartbeat(),this.clearTerminalRetryTimers(),this.clearStopResultRetryTimers(),this.rejectAllPendingRequests("websocket closed");const g=`websocket closed code=${c} reason=${a.toString()||"<none>"}`,b=this.outboundFlushInFlight;b&&b.socket===l&&b.connectionGeneration===n&&this.cancelOutboundFlush(b,h?"connection_lost":"discard",g),h?this.failNonReplayableOutputsForSocket(l,g):this.nonReplayableOutputEventsBySocket.delete(l),this.abortPendingOutputWrites(h,l,n,h?g:void 0),this.abortPendingAcceptedOutputs(h,l,n),this.clearAllEventCorrelations(),this.ws===l&&(this.ws=null),this.connectionGeneration++,this.emit("close",c,a.toString());const y=h;m.info("aibot",`ws closed agent=${this.config.clientType}:${this.config.agentId} code=${c} reason=${a.toString()||"<none>"} everConnected=${this.everConnected} reconnecting=${this.reconnecting} agentDeleted=${this.agentDeleted} willReconnect=${y}`),y&&this.attemptReconnect()}),l.on("error",c=>{if(this.isConnectionCurrent(l,n)&&(this.emitClientError(c instanceof Error?c:new Error(String(c))),!this.connected)){const a=this.pendingRequests.get(_);a&&(this.pendingRequests.delete(_),clearTimeout(a.timer),a.reject(c instanceof Error?c:new Error(String(c))))}})})}handlePacket(e){if(this.packetLog?.logInboundPacket(e.cmd,e.seq,e.payload),this.handleCorrelatedSendResponse(e),e.seq>0&&this.pendingRequests.has(e.seq)){const t=this.pendingRequests.get(e.seq);this.pendingRequests.delete(e.seq),clearTimeout(t.timer),t.expected.includes(e.cmd)?t.resolve(e):t.reject(new Error(`unexpected response: got ${e.cmd}, expected ${t.expected.join("/")}`));return}switch(e.cmd){case"auth_ack":break;case"ping":{this.sendPacket("pong",e.payload??{});break}case"event_msg":{const t=e.payload;if(!this.captureInboundTerminalCommitToken(t.event_id,t.terminal_commit_token))break;this.emit("event",t);break}case"local_action":{this.emit("localAction",e.payload);break}case"event_stop":{const t=e.payload;if(!this.captureInboundTerminalCommitToken(t.event_id,t.terminal_commit_token))break;this.emit("stop",t);break}case"event_revoke":{this.emit("revoke",e.payload);break}case"event_edit":{this.emit("edit",e.payload);break}case"event_cancel":{this.emit("eventCancel",e.payload);break}case"queue_clear":{this.emit("queueClear",e.payload);break}case"queue_reorder":{this.emit("queueReorder",e.payload);break}case"queue_snapshot_query":{this.emit("queueSnapshotQuery",e.payload);break}case"event_hold":{this.emit("eventHold",e.payload);break}case"queue_edit":{this.emit("queueEdit",e.payload);break}case"control_share_set":{this.emit("shareSet",e.payload);break}case"agent_profile_push":{this.emit("profilePush",e.payload);break}case"skill_sync":{this.emit("skillSync",e.payload);break}case"kicked":{const t=e.payload;this.emit("kicked",t),this.connected=!1,this.negotiatedCapabilities.clear(),this.stopHeartbeat(),this.clearTerminalRetryTimers(),this.clearStopResultRetryTimers(),this.rejectAllPendingRequests("kicked");const n=this.ws,s=this.connectionGeneration,i=this.outboundFlushInFlight;if(i&&n&&i.socket===n&&i.connectionGeneration===this.connectionGeneration&&this.cancelOutboundFlush(i,t?.reason===k?"discard":"connection_lost",`kicked reason=${t?.reason??"<none>"}`),t?.reason!==k&&n&&this.failNonReplayableOutputsForSocket(n,`kicked reason=${t?.reason??"<none>"}`),this.abortPendingOutputWrites(t?.reason!==k,n??void 0,s,t?.reason!==k?`kicked reason=${t?.reason??"<none>"}`:void 0),this.abortPendingAcceptedOutputs(t?.reason!==k,n??void 0,s),this.clearAllEventCorrelations(),this.connectionGeneration++,t?.reason===k){if(this.agentDeleted=!0,this.reconnecting=!1,this.outboundBuffer.length=0,this.outputIntegrityFailed.clear(),this.nonReplayableOutputEventsBySocket.clear(),this.ws){try{this.ws.close(4001,"kicked")}catch{}this.ws=null}m.error("aibot",`kicked: agent deleted on platform agent=${this.config.clientType}:${this.config.agentId}, reconnect disabled`),this.emit("agentDeleted",{source:"kicked",reason:t.reason});break}if(this.reconnectAttempts=Math.max(this.reconnectAttempts,3),this.ws){try{this.ws.close(4001,"kicked")}catch{}this.ws===n&&(this.ws=null)}this.attemptReconnect();break}case"error":{const t=e.payload,n=[t.ref_cmd?`ref_cmd=${t.ref_cmd}`:"",t.ref_id?`ref_id=${t.ref_id}`:""].filter(Boolean).join(" ");this.emitClientError(new Error(`Server error: code=${t.code} msg=${t.msg}${n?` ${n}`:""}`));break}case"agent_invoke_result":{this.handleInvokeResult(e.payload);break}case"mcp_frame":{const t=e.payload;this.emit("mcpFrame",t.session_id??"",t.frame??null);break}case"send_ack":break;case"send_nack":break;case"local_action_ack":break;default:break}}captureInboundTerminalCommitToken(e,t){const n=t?.trim();if(!n)return!0;if(!e?.trim())return this.rejectInboundTerminalCommitToken("tokenized event is missing event_id"),!1;if(!this.negotiatedCapabilities.has("terminal_commit_v1"))return this.rejectInboundTerminalCommitToken(`tokenized event received without terminal_commit_v1 negotiation event=${e}`),!1;try{return this.terminalCommitTokens.register(e,n),!0}catch(s){return this.rejectInboundTerminalCommitToken(`terminal commit token persist failed event=${e}: ${s instanceof Error?s.message:s}`),!1}}rejectInboundTerminalCommitToken(e){this.emitClientError(new Error(e));const t=this.ws;if(t)try{t.close(1011,"terminal commit token persistence failed")}catch{}}withTerminalCommitToken(e){const t=e.event_id.trim();if(!t)throw new Error("terminal event_result is missing event_id");const n=e.terminal_commit_token?.trim(),s=this.terminalCommitTokens.get(t);if(n&&s&&n!==s)throw new Error(`terminal commit token mismatch for event=${t}`);n&&!s&&this.terminalCommitTokens.register(t,n);const i=s??n;return i?{...e,event_id:t,terminal_commit_token:i}:{...e,event_id:t}}sendEventAck(e){this.sendPacket("event_ack",e)||m.warn("aibot",`event_ack NOT sent (ws not open) event=${e.event_id} ws=${this.ws?`state=${this.ws.readyState}`:"null"}`)}sendStreamChunk(e){!e.delta_content&&!e.is_finish&&(m.warn("aibot",`stream_chunk delta_content empty, patched to newline event=${e.event_id??""} session=${e.session_id} chunk_seq=${e.chunk_seq} is_finish=${e.is_finish}`),e={...e,delta_content:`
|
|
2
|
+
`}),this.sendPacket("client_stream_chunk",e)}sendMsg(e){const t={...e,client_msg_id:e.client_msg_id?.trim()||T()};this.sendPacket("send_msg",t)||m.warn("aibot",`send_msg NOT sent (ws not open) event=${t.event_id??""} session=${t.session_id??""} ws=${this.ws?`state=${this.ws.readyState}`:"null"}`)}editMsg(e){this.sendPacket("edit_msg",e)}sendEventResult(e){try{e=this.withTerminalCommitToken(e)}catch(o){this.emitClientError(new Error(`event_result token validation failed: event=${e.event_id}: ${o instanceof Error?o.message:o}`));return}const t=this.terminalOutbox.get(e.event_id);let n,s=this.outputIntegrityFailed.get(e.event_id);const i=t;if(!s&&i?.payload.code==="agent_output_integrity_failed"&&(s={reason:i.payload.msg??"required agent output was discarded before delivery",terminalObserved:!1,terminalSettled:!1},this.outputIntegrityFailed.set(e.event_id,s)),s){if(s.terminalObserved=!0,this.provisionalRespondedTerminals.delete(e.event_id),i&&i.payload.code!=="agent_output_integrity_failed"&&!this.canReplaceUnsentOutputGuard(i)){this.scheduleTerminalDelivery(e.event_id,{ignoreNotBefore:!0});return}if(i?.payload.code==="agent_output_integrity_failed"){this.scheduleTerminalDelivery(e.event_id,{ignoreNotBefore:!0});return}if(s.terminalSettled||this.committedTerminalEvents.has(e.event_id)&&!i){this.outputIntegrityFailed.delete(e.event_id);return}try{n=this.terminalOutbox.enqueue(this.makeOutputIntegrityFailurePayload(e.event_id,s.reason))}catch(o){this.emitClientError(new Error(`output-integrity terminal persist failed: event=${e.event_id}: ${o instanceof Error?o.message:o}`));return}this.scheduleTerminalDelivery(n.payload.event_id,{ignoreNotBefore:!0});return}if(this.committedTerminalEvents.has(e.event_id)&&!t){m.info("aibot",`ignored duplicate terminal after committed ACK event=${e.event_id} status=${e.status}`);return}if(t){if(!(this.canReplaceUnsentOutputGuard(t)&&(e.status==="canceled"||e.status==="failed"))){m.info("aibot",`preserving first durable terminal event=${e.event_id} existing_status=${t.payload.status} ignored_status=${e.status}`),this.scheduleTerminalDelivery(e.event_id,{ignoreNotBefore:!0});return}this.provisionalRespondedTerminals.delete(e.event_id)}if(e.status==="responded"&&this.hasNonReplayableOutput(e.event_id)&&!this.hasPendingOutputAcceptanceFence(e.event_id)){this.markOutputIntegrityFailed(e.event_id,"terminal requested before a complete output acceptance fence");const o=this.outputIntegrityFailed.get(e.event_id);o&&(o.terminalObserved=!0);return}const r=e.status==="responded"&&this.hasUnconfirmedEventOutput(e.event_id),u=r?{...e,status:"failed",code:"agent_output_unconfirmed",msg:"required agent output was not durably confirmed before connector restart",updated_at:Date.now()}:e;r?this.provisionalRespondedTerminals.set(e.event_id,{...e}):this.provisionalRespondedTerminals.delete(e.event_id);try{n=this.terminalOutbox.enqueue(u)}catch(o){this.provisionalRespondedTerminals.delete(e.event_id),this.emitClientError(new Error(`event_result outbox persist failed: event=${e.event_id} status=${e.status}: ${o instanceof Error?o.message:o}`));return}this.scheduleTerminalDelivery(n.payload.event_id,{ignoreNotBefore:!0})}sendLocalActionResult(e){this.sendPacket("local_action_result",e)}sendEventStopAck(e){this.sendPacket("event_stop_ack",e)}sendEventStopResult(e){const t=e.event_id?.trim(),n=e.terminal_commit_token?.trim(),s=t?this.terminalCommitTokens.get(t):void 0;if(n&&s&&n!==s){this.emitClientError(new Error(`event_stop_result token mismatch event=${t??""}`));return}const i=s??n;if(t&&i&&(e.status==="stopped"||e.status==="already_finished")){try{const r=this.stopResultOutbox.enqueue({...e,event_id:t,terminal_commit_token:i});this.scheduleStopResultDelivery(r,{ignoreNotBefore:!0})}catch(r){this.emitClientError(new Error(`event_stop_result outbox persist failed: event=${t} stop=${e.stop_id}: ${r instanceof Error?r.message:r}`))}return}this.sendPacket("event_stop_result",e)}sendSessionActivitySet(e){this.sendPacket("session_activity_set",e)}sendCodexEvent(e){this.sendPacket("codex_event",e)}sendUpdateBindingCard(e){this.sendPacket("update_binding_card",e)}sendAuditState(e){this.sendPacket("audit_state",e)}sendSkillsUpdate(e){this.sendPacket("agent_skills_update",e)}sendPing(){this.sendPacket("ping",{})}sendEventState(e){this.sendPacket("event_state",e)}sendEventCancelResult(e){this.sendPacket("event_cancel_result",e)}sendQueueClearResult(e){this.sendPacket("queue_clear_result",e)}sendQueueReorderResult(e){this.sendPacket("queue_reorder_result",e)}sendEventHoldResult(e){this.sendPacket("event_hold_result",e)}sendQueueEditResult(e){this.sendPacket("queue_edit_result",e)}sendQueueSnapshot(e){this.sendPacket("queue_snapshot",e)}agentInvoke(e,t,n=15e3){return new Promise((s,i)=>{const r=T(),u=Math.max(1e3,Math.min(n,6e4)),o=setTimeout(()=>{this.pendingInvokes.delete(r),i(new Error(`agent_invoke timeout: ${e}`))},u);this.pendingInvokes.set(r,{resolve:s,reject:i,timer:o}),this.sendPacket("agent_invoke",{invoke_id:r,action:e,params:t,timeout_ms:u})})}sendMcpFrame(e,t){this.sendPacket("mcp_frame",{session_id:e,frame:t})}async request(e,t,n){const s=this.outboundFlushInFlight;if(s){if(!await s.promise)throw new Error(`outbound flush failed before request: ${e}`);if(!this.isConnectionCurrent(s.socket,s.connectionGeneration))throw new Error(`connection changed while waiting for outbound flush: ${e}`)}if(!this.connected||!this.ws||this.ws.readyState!==O.OPEN)throw new Error(`send failed: ${e} (websocket not connected)`);return new Promise((i,r)=>{const u=++this.seq,o=setTimeout(()=>{this.pendingRequests.delete(u),r(new N(`request timeout: ${e} (expected ${n.expected.join("/")})`))},n.timeoutMs);this.pendingRequests.set(u,{expected:n.expected,resolve:i,reject:r,timer:o}),this.sendPacket(e,t,u)||(this.pendingRequests.delete(u),clearTimeout(o),r(new Error(`send failed: ${e}`)))})}async sendStreamChunkRequest(e,t=2e4){return this.request("client_stream_chunk",e,{expected:["send_ack","send_nack","error"],timeoutMs:t})}async relayCredentialRequest(e,t=15e3){return this.request("relay_credential_request",e,{expected:["relay_credential_result","error"],timeoutMs:t})}async relayStateSyncRequest(e,t=15e3){return this.request("relay_state_sync_request",e,{expected:["relay_state_sync_result","error"],timeoutMs:t})}sendRelayStateReport(e){this.sendPacket("relay_state_report",e)}async sendText(e,t=2e4){return this.request("send_msg",{msg_type:1,...e,client_msg_id:e.client_msg_id?.trim()||T()},{expected:["send_ack","send_nack","error"],timeoutMs:t})}async sendMedia(e,t=2e4){return this.request("send_msg",{...e,msg_type:2,client_msg_id:e.client_msg_id?.trim()||T()},{expected:["send_ack","send_nack","error"],timeoutMs:t})}async editMessage(e,t=2e4){return this.request("edit_msg",e,{expected:["send_ack","send_nack","error"],timeoutMs:t})}async deleteMessage(e,t,n=2e4){return this.request("delete_msg",{session_id:e,msg_id:t},{expected:["send_ack","send_nack","error"],timeoutMs:n})}async sendEventResultRequest(e,t=5e3){return this.request("event_result",e,{expected:["send_ack","send_nack","error"],timeoutMs:t})}disconnect(){m.info("aibot",`disconnect() agent=${this.config.clientType}:${this.config.agentId} wasConnected=${this.connected} reconnecting=${this.reconnecting} reconnectAttempts=${this.reconnectAttempts}`),this.connected=!1,this.negotiatedCapabilities.clear(),this.everConnected=!1,this.reconnecting=!1,this.reconnectRunGeneration++,this.reconnectAttempts=0,this.stopHeartbeat(),this.clearTerminalRetryTimers(),this.clearStopResultRetryTimers();const e=this.ws,t=this.connectionGeneration,n=this.outboundFlushInFlight;n&&this.cancelOutboundFlush(n,"discard","explicit disconnect"),this.connectionGeneration++,this.rejectAllPendingInvokes("disconnect"),this.rejectAllPendingRequests("disconnect"),this.abortPendingOutputWrites(!1,e??void 0,t),this.abortPendingAcceptedOutputs(!1,e??void 0,t),this.clearAllEventCorrelations(),this.outboundBuffer.length=0,this.provisionalRespondedTerminals.clear(),this.outputIntegrityFailed.clear(),this.nonReplayableOutputEventsBySocket.clear(),this.ws&&(this.ws.close(1e3,"client disconnect"),this.ws=null)}static MAX_CONSECUTIVE_AUTH_FAILURES=5;async attemptReconnect(){if(this.reconnecting||this.agentDeleted)return;this.reconnecting=!0;const e=++this.reconnectRunGeneration;m.info("aibot",`attemptReconnect start agent=${this.config.clientType}:${this.config.agentId} fromAttempts=${this.reconnectAttempts}`),this.emit("disconnected");let t=0;for(;this.reconnecting&&this.reconnectRunGeneration===e;){const n=Math.min(1e3*2**this.reconnectAttempts,3e4),s=Math.floor(n*.2*Math.random());if(this.reconnectAttempts++,await new Promise(u=>setTimeout(u,n+s)),!this.reconnecting||this.reconnectRunGeneration!==e)return;let i=null,r=this.connectionGeneration;try{const u=this.connect();if(i=this.ws,r=this.connectionGeneration,await u,this.reconnectRunGeneration!==e)return;if(!i||!this.isConnectionCurrent(i,r)){if(this.connected&&this.ws){this.reconnecting=!1;return}continue}const o=this.reconnectAttempts;this.reconnectAttempts=0,this.reconnecting=!1,m.info("aibot",`reconnect succeeded agent=${this.config.clientType}:${this.config.agentId} attempt=${o}`);return}catch(u){if(this.reconnectRunGeneration!==e)return;if(i&&!this.isConnectionCurrent(i,r)&&this.connected&&this.ws){this.reconnecting=!1;return}if(i&&this.isConnectionCurrent(i,r)){this.connectionGeneration++;try{i.close()}catch{}this.ws===i&&(this.ws=null)}const o=u instanceof Error?u.message:String(u);if(m.warn("aibot",`reconnect failed agent=${this.config.clientType}:${this.config.agentId} attempt=${this.reconnectAttempts} err=${o}`),u instanceof C){this.agentDeleted=!0,this.reconnecting=!1,m.error("aibot",`reconnect aborted: agent deleted on platform agent=${this.config.clientType}:${this.config.agentId}`),this.emit("agentDeleted",{source:"auth_ack",code:w});return}if(/Auth failed/i.test(o)){if(t++,t>=p.MAX_CONSECUTIVE_AUTH_FAILURES){this.reconnecting=!1,m.error("aibot",`reconnect giving up after ${t} consecutive auth failures agent=${this.config.clientType}:${this.config.agentId}`);return}}else t=0}}}sendPacket(e,t,n){const s=this.outboundEventId(e,t);if(s&&this.blockedOutputEvents.has(s))return this.packetLog?.logOutboundPacket(e,n??0,t,"dropped"),m.warn("aibot",`blocked late output after irreversible event failure event=${s} cmd=${e}`),!1;const i=n===void 0?this.createOutboundEntry(e,t):void 0;if(i&&this.outboundFlushInFlight&&!this.outboundFlushInFlight.finalized&&this.isConnectionCurrent(this.outboundFlushInFlight.socket,this.outboundFlushInFlight.connectionGeneration)&&!p.DROPPABLE_COMMANDS.has(e))return this.bufferOutboundEntry(i),!1;if(this.ws&&this.ws.readyState===O.OPEN&&(e==="auth"||this.connected)){const u=this.ws.bufferedAmount>p.BACKPRESSURE_THRESHOLD;if(!u||!p.DROPPABLE_COMMANDS.has(e)){if(u&&p.DROPPABLE_COMMANDS.has(e))return!1;const o=n??++this.seq,f={cmd:e,seq:o,payload:t};this.packetLog?.logOutboundPacket(e,o,t,"sent");const l=this.ws,d=this.connectionGeneration,_=s;_&&this.trackOutboundEventCorrelation(e,o,t),i&&_&&(this.registerPendingOutputWrite(o,_,i,l,d),this.isAckRequiredOutput(e,t)&&this.registerPendingAcceptedOutput(o,_,i,l,d));let E=!1;try{const c=l.readyState,a=l.bufferedAmount;let h=!1;return l.send(JSON.stringify(f),g=>{if(g&&!h&&(E=!0),e==="event_result"){const v=t;g?m.warn("aibot",`event_result ws send callback failed event=${v.event_id??""} status=${v.status??""} seq=${o} readyState=${c} bufferedAmount=${a} err=${g.message}`):m.info("aibot",`event_result ws send callback ok event=${v.event_id??""} status=${v.status??""} seq=${o} readyState=${c} bufferedAmount=${a}`)}else if(e==="client_stream_chunk"){const v=t;g?m.warn("aibot",`stream_chunk ws send failed event=${v.event_id??""} session=${v.session_id??""} seq=${o} chunk_seq=${v.chunk_seq??""} is_finish=${v.is_finish??""} readyState=${c} bufferedAmount=${a} err=${g.message}`):m.info("aibot",`stream_chunk ws send ok event=${v.event_id??""} session=${v.session_id??""} seq=${o} chunk_seq=${v.chunk_seq??""} is_finish=${v.is_finish??""} client_msg_id=${v.client_msg_id??""} quoted_message_id=${v.quoted_message_id??""} readyState=${c} bufferedAmount=${a}`)}else if(e==="event_ack"){const v=t;g?m.warn("aibot",`event_ack ws send failed event=${v.event_id??""} seq=${o} readyState=${c} bufferedAmount=${a} err=${g.message}`):m.info("aibot",`event_ack ws send ok event=${v.event_id??""} seq=${o} readyState=${c} bufferedAmount=${a}`)}else if(e==="send_msg"){const v=t;g?m.warn("aibot",`send_msg ws send failed event=${v.event_id??""} session=${v.session_id??""} seq=${o} readyState=${c} bufferedAmount=${a} err=${g.message}`):m.info("aibot",`send_msg ws send ok event=${v.event_id??""} session=${v.session_id??""} seq=${o} readyState=${c} bufferedAmount=${a}`)}else if(g){const v=t;m.warn("aibot",`${e} ws send failed seq=${o} session=${v.session_id??""} event=${v.event_id??""} client_msg_id=${v.client_msg_id??""} readyState=${c} bufferedAmount=${a} err=${g.message}`)}const b=this.pendingOutputWrites.get(o),y=!!(b&&b.socket===l&&b.connectionGeneration===d);if(!this.isConnectionCurrent(l,d)){y&&(this.settlePendingOutputWrite(o,!1,!1,l,d),this.settlePendingAcceptedOutput(o,"retry",!1,l,d));return}if(!(i&&_&&!y)){if(g)this.removeSeqFromEventCorrelation(o),_?(this.settlePendingOutputWrite(o,!1,!0,l,d),this.settlePendingAcceptedOutput(o,"retry",!1,l,d)):i&&this.bufferOutboundEntry(i),n!==void 0?this.rejectPendingRequestAfterWriteFailure(o,e,g):i&&this.reconnectAfterOutboundWriteFailure(l,`${e} callback failed`);else if(_){const v=y;this.settlePendingOutputWrite(o,!0,!1,l,d),v&&this.trackNonReplayableOutput(l,e,t,o)}}}),h=!0,!E}catch(c){return this.isConnectionCurrent(l,d)?(this.removeSeqFromEventCorrelation(o),_?(this.settlePendingOutputWrite(o,!1,!0,l,d),this.settlePendingAcceptedOutput(o,"retry",!1,l,d)):i&&this.bufferOutboundEntry(i),this.emitClientError(new Error(`sendPacket failed: ${c}`)),i&&this.reconnectAfterOutboundWriteFailure(l,`${e} send threw`),!1):(this.settlePendingOutputWrite(o,!1,!1,l,d),this.settlePendingAcceptedOutput(o,"retry",!1,l,d),!1)}}}if(p.DROPPABLE_COMMANDS.has(e))return this.packetLog?.logOutboundPacket(e,n??0,t,"dropped"),!1;if(n!==void 0)return this.packetLog?.logOutboundPacket(e,n,t,"dropped"),!1;const r=this.bufferOutboundEntry(i??this.createOutboundEntry(e,t));if(this.packetLog?.logOutboundPacket(e,n??0,t,r?"buffered":"dropped"),r&&e==="client_stream_chunk"){const u=t;m.info("aibot",`stream_chunk buffered (ws not open) event=${u.event_id??""} session=${u.session_id??""} chunk_seq=${u.chunk_seq??""} is_finish=${u.is_finish??""} ws=${this.ws?`state=${this.ws.readyState}`:"null"}`)}return!1}replayTerminalOutbox(){for(const e of this.terminalOutbox.listPending())this.scheduleTerminalDelivery(e.payload.event_id,{ignoreNotBefore:!0})}outboundEventId(e,t){if(!p.ORDERED_OUTPUT_COMMANDS.has(e)||!t||typeof t!="object")return"";const n=t.event_id;return typeof n=="string"?n.trim():""}blockEventOutput(e){const t=e.trim();if(t)for(this.blockedOutputEvents.delete(t),this.blockedOutputEvents.set(t,Date.now()),this.outboundBuffer=this.outboundBuffer.filter(n=>this.outboundEventId(n.cmd,n.payload)!==t);this.blockedOutputEvents.size>p.MAX_BLOCKED_OUTPUT_EVENTS;){const n=this.blockedOutputEvents.keys().next().value;if(!n)break;this.blockedOutputEvents.delete(n)}}isAckRequiredOutput(e,t){return e==="send_msg"?!0:e==="client_stream_chunk"&&!!t&&typeof t=="object"&&t.is_finish===!0}isNonReplayableOutput(e,t){return e==="codex_event"?!0:e==="client_stream_chunk"&&!!t&&typeof t=="object"&&t.is_finish!==!0}trackNonReplayableOutput(e,t,n,s){if(!this.isNonReplayableOutput(t,n))return;const i=this.outboundEventId(t,n);if(!i||this.outputIntegrityFailed.has(i)||this.blockedOutputEvents.has(i))return;let r=this.nonReplayableOutputEventsBySocket.get(e);r||(r=new Map,this.nonReplayableOutputEventsBySocket.set(e,r)),r.set(i,Math.max(r.get(i)??0,s))}clearNonReplayableOutputThrough(e,t,n){if(!e||n<=0)return;const s=this.nonReplayableOutputEventsBySocket.get(e);if(!s)return;const i=s.get(t);i===void 0||i>n||(s.delete(t),s.size===0&&this.nonReplayableOutputEventsBySocket.delete(e))}clearNonReplayableOutputEvent(e){for(const[t,n]of this.nonReplayableOutputEventsBySocket)n.delete(e),n.size===0&&this.nonReplayableOutputEventsBySocket.delete(t)}failNonReplayableOutputsForSocket(e,t){const n=this.nonReplayableOutputEventsBySocket.get(e);if(n){this.nonReplayableOutputEventsBySocket.delete(e);for(const s of n.keys())this.markOutputIntegrityFailed(s,`connection lost before a complete output acceptance fence: ${t}`)}}hasNonReplayableOutput(e){for(const t of this.nonReplayableOutputEventsBySocket.values())if(t.has(e))return!0;return!1}hasPendingOutputAcceptanceFence(e){return!!(this.pendingAcceptedOutputSeqsByEvent.get(e)?.size||this.outboundBuffer.some(t=>this.outboundEventId(t.cmd,t.payload)===e&&this.isAckRequiredOutput(t.cmd,t.payload)))}hasUnconfirmedEventOutput(e){return!!(this.pendingOutputSeqsByEvent.get(e)?.size||this.pendingAcceptedOutputSeqsByEvent.get(e)?.size||this.outboundBuffer.some(t=>this.outboundEventId(t.cmd,t.payload)===e))}trackOutboundEventCorrelation(e,t,n){const s=this.outboundEventId(e,n);if(!s)return;const i=n,r=typeof i.client_msg_id=="string"?i.client_msg_id.trim():"";this.pruneExpiredEventCorrelations();const u=Date.now();let o=this.eventCorrelations.get(s);if(!o){if(this.eventCorrelations.size>=p.MAX_EVENT_CORRELATIONS){let l;for(const d of this.eventCorrelations.values())this.eventHasCriticalDeliveryState(d.eventId)||(!l||d.touchedAt<l.touchedAt)&&(l=d);l&&this.releaseEventCorrelations(l.eventId)}o={eventId:s,clientMsgIds:new Set,seqRanges:[],touchedAt:u,expiresAt:u+p.EVENT_CORRELATION_TTL_MS},this.eventCorrelations.set(s,o)}o.touchedAt=u,o.expiresAt=u+p.EVENT_CORRELATION_TTL_MS;const f=o.seqRanges[o.seqRanges.length-1];if(f&&t===f.end+1?f.end=t:(!f||t<f.start||t>f.end)&&o.seqRanges.push({start:t,end:t}),r){o.clientMsgIds.add(r);let l=this.clientMsgEventMap.get(r);l||(l=new Set,this.clientMsgEventMap.set(r,l)),l.add(s)}this.scheduleEventCorrelationCleanup()}uniqueEventIdForClientMsgId(e){const t=this.clientMsgEventMap.get(e);if(t?.size===1)return t.values().next().value}eventHasCriticalDeliveryState(e){return!!(this.terminalOutbox.get(e)||this.pendingAcceptedOutputSeqsByEvent.get(e)?.size)}eventIdForSeq(e){if(!(e<=0)){for(const t of this.eventCorrelations.values())if(t.seqRanges.some(n=>e>=n.start&&e<=n.end))return t.eventId}}removeSeqFromEventCorrelation(e){if(!(e<=0))for(const t of this.eventCorrelations.values()){const n=t.seqRanges.findIndex(i=>e>=i.start&&e<=i.end);if(n<0)continue;const s=t.seqRanges[n];s.start===s.end?t.seqRanges.splice(n,1):e===s.start?s.start++:e===s.end?s.end--:t.seqRanges.splice(n,1,{start:s.start,end:e-1},{start:e+1,end:s.end});return}}releaseEventCorrelations(e){const t=e.trim();if(!t)return;const n=this.eventCorrelations.get(t);if(n){for(const s of n.clientMsgIds){const i=this.clientMsgEventMap.get(s);i?.delete(t),i?.size===0&&this.clientMsgEventMap.delete(s)}this.eventCorrelations.delete(t)}this.cancelEventCorrelationCleanupIfEmpty()}clearAllEventCorrelations(){this.eventCorrelationCleanupTimer&&(clearTimeout(this.eventCorrelationCleanupTimer),this.eventCorrelationCleanupTimer=null),this.eventCorrelations.clear(),this.clientMsgEventMap.clear(),this.lastCorrelationPruneAt=0}pruneExpiredEventCorrelations(e=Date.now(),t=!1){if(!(!t&&e-this.lastCorrelationPruneAt<1e3)){this.lastCorrelationPruneAt=e;for(const n of[...this.eventCorrelations.values()])n.expiresAt<=e&&!this.eventHasCriticalDeliveryState(n.eventId)&&this.releaseEventCorrelations(n.eventId);this.cancelEventCorrelationCleanupIfEmpty()}}scheduleEventCorrelationCleanup(){if(this.eventCorrelationCleanupTimer||this.eventCorrelations.size===0)return;let e=Number.POSITIVE_INFINITY;for(const t of this.eventCorrelations.values())this.eventHasCriticalDeliveryState(t.eventId)||(e=Math.min(e,t.expiresAt));Number.isFinite(e)&&(this.eventCorrelationCleanupTimer=setTimeout(()=>{this.eventCorrelationCleanupTimer=null,this.pruneExpiredEventCorrelations(Date.now(),!0),this.scheduleEventCorrelationCleanup()},Math.max(1,e-Date.now())),this.eventCorrelationCleanupTimer.unref?.())}cancelEventCorrelationCleanupIfEmpty(){!this.eventCorrelationCleanupTimer||this.eventCorrelations.size>0||(clearTimeout(this.eventCorrelationCleanupTimer),this.eventCorrelationCleanupTimer=null)}handleCorrelatedSendResponse(e){if(e.cmd==="send_ack"){const f=e.payload,l=typeof f.client_msg_id=="string"?f.client_msg_id.trim():"";let d=e.seq>0&&this.pendingAcceptedOutputs.has(e.seq)?e.seq:void 0,_;if(d===void 0&&l){const a=this.pendingAcceptedSeqsByClientMsgId.get(l);if(a?.size===1){const h=a.values().next().value,g=this.pendingAcceptedOutputs.get(h),b=this.uniqueEventIdForClientMsgId(l);g&&b&&g.eventId===b&&(d=h,_=b)}}const E=(d!==void 0?this.pendingAcceptedOutputs.get(d)?.eventId:void 0)??_??this.eventIdForSeq(e.seq),c=d??e.seq;if(d!==void 0){const a=this.pendingOutputWrites.get(d);a&&this.settlePendingOutputWrite(d,!0,!1,a.socket,a.connectionGeneration),this.settlePendingAcceptedOutput(d,"accepted",!1)}else if(e.seq>0){const a=this.pendingOutputWrites.get(e.seq);a&&this.settlePendingOutputWrite(e.seq,!0,!1,a.socket,a.connectionGeneration)}E&&this.clearNonReplayableOutputThrough(this.ws,E,c);return}if(e.cmd!=="send_nack"&&e.cmd!=="error")return;this.pruneExpiredEventCorrelations();const t=e.payload,n=typeof t.client_msg_id=="string"?t.client_msg_id.trim():"",s=e.cmd==="error"&&typeof t.ref_id=="string"?t.ref_id.trim():"",i=e.cmd==="error"&&typeof t.ref_cmd=="string"?t.ref_cmd.trim():"",r=this.eventIdForSeq(e.seq),u=s&&(!i||p.ORDERED_OUTPUT_COMMANDS.has(i))?this.uniqueEventIdForClientMsgId(s)??(this.eventCorrelations.has(s)?s:void 0):void 0,o=r??(n?this.uniqueEventIdForClientMsgId(n):void 0)??u;o&&(this.blockEventOutput(o),this.settlePendingOutputWritesForEvent(o,!0),this.clearNonReplayableOutputEvent(o),e.seq>0&&this.pendingAcceptedOutputs.get(e.seq)?.eventId===o?this.settlePendingAcceptedOutput(e.seq,"rejected",!1):this.settlePendingAcceptedOutputsForEvent(o,"rejected",!1),t.code===4003&&(this.purgeBufferedStreamChunks(o),m.warn("aibot",`event payload rejected (4003), purging buffered chunks for event=${o}`)),this.promotePendingTerminalAfterOutputRejection(o,Number(t.code??0),t.msg),this.emit("streamRejected",o,Number(t.code??0)))}promotePendingTerminalAfterOutputRejection(e,t,n){this.provisionalRespondedTerminals.delete(e);const s=this.terminalOutbox.get(e);if(!(!s||s.payload.status!=="responded"&&s.payload.code!=="agent_output_unconfirmed"||s.deliveryStartedAt))try{const i=this.terminalOutbox.enqueue({...s.payload,status:"failed",code:"agent_output_rejected",msg:n?`required agent output was rejected: ${n}`:`required agent output was rejected by the server (code=${t})`,updated_at:Date.now()});this.scheduleTerminalDelivery(i.payload.event_id,{ignoreNotBefore:!0})}catch(i){this.emitClientError(new Error(`failed to persist output-rejection terminal promotion: event=${e} code=${t}: ${i instanceof Error?i.message:i}`))}}scheduleTerminalDelivery(e,t){if(!this.connected||!this.ws||this.ws.readyState!==O.OPEN)return;if(this.terminalInFlight.has(e)){t?.ignoreNotBefore&&this.terminalReplayAfterFlight.add(e);return}const n=this.terminalOutbox.get(e);if(!n)return;const s=this.terminalRetryTimers.get(e);s&&(clearTimeout(s),this.terminalRetryTimers.delete(e));const i=t?.ignoreNotBefore?0:Math.max(0,n.nextAttemptAt-Date.now()),r=Math.max(i,t?.minimumDelayMs??0);if(r>0){const u=setTimeout(()=>{this.terminalRetryTimers.delete(e),this.scheduleTerminalDelivery(e,{ignoreNotBefore:!0})},r);this.terminalRetryTimers.set(e,u);return}this.deliverTerminalEntry(n)}async deliverTerminalEntry(e){const t=e.payload.event_id;if(this.terminalInFlight.has(t)||!this.terminalOutbox.isCurrent(e))return;this.terminalInFlight.add(t);let n=0;try{n=await this.sendEventResultReliable(e)}finally{this.terminalInFlight.delete(t);const s=this.terminalReplayAfterFlight.delete(t);this.terminalOutbox.get(t)&&this.scheduleTerminalDelivery(t,s?{ignoreNotBefore:!0}:{minimumDelayMs:n})}}clearTerminalRetryTimers(){for(const e of this.terminalRetryTimers.values())clearTimeout(e);this.terminalRetryTimers.clear()}async sendEventResultReliable(e){const t=e.payload.event_id,n=e.payload.terminal_commit_token?.trim();if(n&&!this.negotiatedCapabilities.has("terminal_commit_v1")){const d=this.ws;return this.emitClientError(new Error(`refusing tokenized event_result on an unnegotiated connection: event=${t}`)),d&&this.reconnectAfterOutboundWriteFailure(d,"terminal_commit_v1 not negotiated"),p.TERMINAL_RETRY_DELAY_MS}if(!await this.awaitOutputDeliveryBarrier(t))return p.TERMINAL_RETRY_DELAY_MS;const s=this.provisionalRespondedTerminals.get(t);if(s&&this.terminalOutbox.isCurrent(e))if(!this.canReplaceUnsentOutputGuard(e))this.provisionalRespondedTerminals.delete(t);else try{return this.terminalOutbox.enqueue(s),this.provisionalRespondedTerminals.delete(t),0}catch(d){return this.emitClientError(new Error(`failed to promote confirmed output terminal to responded: event=${t}: ${d instanceof Error?d.message:d}`)),p.TERMINAL_RETRY_DELAY_MS}const i=e.payload;try{if(!this.terminalOutbox.markDeliveryStarted(e))return 0}catch(d){return this.emitClientError(new Error(`event_result delivery-start persist failed: event=${t}: ${d instanceof Error?d.message:d}`)),p.TERMINAL_RETRY_DELAY_MS}const r=Math.max(1,this.ackPolicy?.max_retries??3),u=this.ackPolicy?.push_ack_timeout_ms??5e3,o=750;let f="unknown delivery failure";for(let d=1;d<=r;d++){if(!this.terminalOutbox.isCurrent(e))return 0;const _=this.ws?.readyState??-1,E=this.ws?.bufferedAmount??0;m.info("aibot",`event_result send attempt event=${i.event_id} status=${i.status} attempt=${d}/${r} readyState=${_} bufferedAmount=${E}`);try{const c=await this.sendEventResultRequest(i,u);if(!this.terminalOutbox.isCurrent(e))return 0;if(c.cmd==="send_ack"){const h=c.payload;if(m.info("aibot",`event_result ack event=${i.event_id} status=${i.status} attempt=${d}/${r} ack_event=${h.event_id??""} ack_status=${h.status??""}`),h.event_id!==i.event_id||h.status!==i.status)throw new Error(`event_result ACK mismatch: expected event=${i.event_id} status=${i.status}, got event=${h.event_id??""} status=${h.status??""}`);if(n&&(h.terminal_commit_token?.trim()!==n||h.terminal_committed!==!0))throw new Error(`event_result terminal commit ACK mismatch: event=${i.event_id}`);if(n)try{this.terminalCommitTokens.remove(i.event_id,n)}catch(g){throw new Error(`terminal commit token cleanup failed: event=${i.event_id}: ${g instanceof Error?g.message:g}`)}return this.terminalOutbox.acknowledge(e,h.event_id,h.status,h.terminal_commit_token,h.terminal_committed)&&(this.rememberCommittedTerminalEvent(i.event_id),this.tokenizedTerminalRejections.delete(i.event_id),this.blockEventOutput(i.event_id),this.releaseEventCorrelations(i.event_id),this.clearNonReplayableOutputEvent(i.event_id),this.settleOutputIntegrityTerminal(i.event_id),this.scheduleStopResultsForEvent(i.event_id)),0}const a=c.payload;if(m.warn("aibot",`event_result rejected event=${i.event_id} status=${i.status} attempt=${d}/${r} cmd=${c.cmd} code=${a.code??""} msg=${a.msg??""}${a.ref_cmd?` ref_cmd=${a.ref_cmd}`:""}${a.ref_id?` ref_id=${a.ref_id}`:""}`),n){f=`tokenized terminal rejected: cmd=${c.cmd} code=${a.code??""} msg=${a.msg??""}`;const h=(this.tokenizedTerminalRejections.get(i.event_id)??0)+1;if(this.tokenizedTerminalRejections.set(i.event_id,h),h<p.MAX_TOKENIZED_TERMINAL_REJECTIONS)break;this.tokenizedTerminalRejections.delete(i.event_id);try{this.terminalCommitTokens.remove(i.event_id,n),this.terminalOutbox.moveToDeadLetter(e,{responseCmd:c.cmd,code:a.code,message:a.msg})&&(this.blockEventOutput(i.event_id),this.releaseEventCorrelations(i.event_id),this.clearNonReplayableOutputEvent(i.event_id),this.settleOutputIntegrityTerminal(i.event_id),this.emitClientError(new Error(`tokenized event_result permanently rejected after ${h} rejections, moved to dead-letter: event=${i.event_id} status=${i.status} cmd=${c.cmd} code=${a.code??""} msg=${a.msg??""}`)))}catch(g){f=`dead-letter persist failed: ${g instanceof Error?g.message:g}`,this.emitClientError(new Error(`tokenized event_result dead-letter persist failed: event=${i.event_id} status=${i.status}: ${f}`));break}return 0}try{return this.terminalOutbox.moveToDeadLetter(e,{responseCmd:c.cmd,code:a.code,message:a.msg})&&(this.tokenizedTerminalRejections.delete(i.event_id),this.blockEventOutput(i.event_id),this.releaseEventCorrelations(i.event_id),this.clearNonReplayableOutputEvent(i.event_id),this.settleOutputIntegrityTerminal(i.event_id),this.emitClientError(new Error(`event_result rejected and moved to dead-letter: event=${i.event_id} status=${i.status} cmd=${c.cmd} code=${a.code??""} msg=${a.msg??""}`))),0}catch(h){f=`dead-letter persist failed: ${h instanceof Error?h.message:h}`,this.emitClientError(new Error(`event_result dead-letter persist failed: event=${i.event_id} status=${i.status}: ${f}`));break}}catch(c){const a=c instanceof Error?c.message:String(c);if(f=a,m.warn("aibot",`event_result attempt failed event=${i.event_id} status=${i.status} attempt=${d}/${r} err=${a}`),d===r)break;await new Promise(h=>setTimeout(h,o*d))}}const l=Date.now()+p.TERMINAL_RETRY_DELAY_MS;try{this.terminalOutbox.recordRetry(e,l,f)}catch(d){this.emitClientError(new Error(`event_result retry state persist failed: event=${i.event_id} status=${i.status}: ${d instanceof Error?d.message:d}`))}return this.emitClientError(new Error(`event_result ack failed after ${r} attempts; retained for retry: event=${i.event_id} status=${i.status} err=${f}`)),p.TERMINAL_RETRY_DELAY_MS}purgeBufferedStreamChunks(e){const t=this.outboundBuffer.length;this.outboundBuffer=this.outboundBuffer.filter(n=>n.cmd!=="client_stream_chunk"?!0:n.payload?.event_id!==e),this.outboundBuffer.length<t&&m.info("aibot",`purged ${t-this.outboundBuffer.length} buffered stream chunks for event=${e}`)}emitClientError(e){if(this.listenerCount("error")===0){m.warn("aibot",`Client error (no listeners): ${e.message}`);return}this.emit("error",e)}createOutboundEntry(e,t){const n={cmd:e,payload:t};return this.outboundEntryOrders.set(n,++this.nextOutboundEntryOrder),n}makeOutputIntegrityFailurePayload(e,t){return this.withTerminalCommitToken({event_id:e,status:"failed",code:"agent_output_integrity_failed",msg:`required agent output was discarded before delivery: ${t}`,updated_at:Date.now()})}canReplaceUnsentOutputGuard(e){return e.payload.code==="agent_output_unconfirmed"&&!e.deliveryStartedAt}rememberCommittedTerminalEvent(e){for(this.committedTerminalEvents.delete(e),this.committedTerminalEvents.add(e);this.committedTerminalEvents.size>p.MAX_COMMITTED_TERMINAL_EVENTS;){const t=this.committedTerminalEvents.values().next().value;if(!t)break;this.committedTerminalEvents.delete(t)}}replayStopResultOutbox(){for(const e of this.stopResultOutbox.listPending())this.scheduleStopResultDelivery(e,{ignoreNotBefore:!0})}scheduleStopResultsForEvent(e){for(const t of this.stopResultOutbox.listPending())t.payload.event_id===e&&this.scheduleStopResultDelivery(t,{ignoreNotBefore:!0})}scheduleStopResultDelivery(e,t){if(!this.connected||!this.ws||this.ws.readyState!==O.OPEN||!this.stopResultOutbox.isCurrent(e))return;const n=e.payload.event_id;if(this.terminalOutbox.get(n)||this.terminalCommitTokens.get(n))return;const s=this.stopResultRetryTimers.get(e.key);s&&(clearTimeout(s),this.stopResultRetryTimers.delete(e.key));const i=t?.ignoreNotBefore?0:Math.max(0,e.nextAttemptAt-Date.now());if(i>0){const r=setTimeout(()=>{this.stopResultRetryTimers.delete(e.key),this.scheduleStopResultDelivery(e,{ignoreNotBefore:!0})},i);this.stopResultRetryTimers.set(e.key,r);return}this.deliverStopResultEntry(e)}async deliverStopResultEntry(e){if(this.stopResultInFlight.has(e.key)||!this.stopResultOutbox.isCurrent(e))return;this.stopResultInFlight.add(e.key);let t=p.TERMINAL_RETRY_DELAY_MS;try{t=await this.sendStopResultReliable(e)}finally{if(this.stopResultInFlight.delete(e.key),this.stopResultOutbox.isCurrent(e)){const n=setTimeout(()=>{this.stopResultRetryTimers.delete(e.key),this.scheduleStopResultDelivery(e,{ignoreNotBefore:!0})},t);this.stopResultRetryTimers.set(e.key,n)}}}async sendStopResultReliable(e){const t=this.ackPolicy?.push_ack_timeout_ms??5e3;let n="unknown delivery failure";try{const i=await this.request("event_stop_result",e.payload,{expected:["send_ack","send_nack","error"],timeoutMs:t});if(!this.stopResultOutbox.isCurrent(e))return 0;if(i.cmd==="send_ack"){const r=i.payload;if(this.stopResultOutbox.acknowledge(e,r.event_id,r.terminal_commit_token,r.terminal_committed))return 0;n=`event_stop_result ACK mismatch event=${e.payload.event_id}`}else{const r=i.payload;if(n=`event_stop_result rejected: cmd=${i.cmd} code=${r.code??""} msg=${r.msg??""}`,r.code===4001||r.code===4003){try{this.stopResultOutbox.discard(e)&&this.emitClientError(new Error(`event_stop_result permanently rejected and discarded: event=${e.payload.event_id} stop=${e.payload.stop_id} code=${r.code} msg=${r.msg??""}`))}catch(u){this.emitClientError(new Error(`event_stop_result discard persist failed: event=${e.payload.event_id} stop=${e.payload.stop_id}: ${u instanceof Error?u.message:u}`))}return 0}}}catch(i){n=i instanceof Error?i.message:String(i)}const s=Date.now()+p.TERMINAL_RETRY_DELAY_MS;try{this.stopResultOutbox.recordRetry(e,s,n)}catch(i){this.emitClientError(new Error(`event_stop_result retry state persist failed: event=${e.payload.event_id} stop=${e.payload.stop_id}: ${i instanceof Error?i.message:i}`))}return this.emitClientError(new Error(`event_stop_result retained for retry: event=${e.payload.event_id} stop=${e.payload.stop_id} err=${n}`)),p.TERMINAL_RETRY_DELAY_MS}clearStopResultRetryTimers(){for(const e of this.stopResultRetryTimers.values())clearTimeout(e);this.stopResultRetryTimers.clear()}markOutputIntegrityFailed(e,t){if(this.blockEventOutput(e),this.outputIntegrityFailed.has(e))return;const n={reason:t,terminalObserved:!!(this.provisionalRespondedTerminals.has(e)||this.terminalOutbox.get(e)),terminalSettled:!1};this.outputIntegrityFailed.set(e,n),this.provisionalRespondedTerminals.delete(e),this.clearNonReplayableOutputEvent(e);const s=this.outboundBuffer.length;this.outboundBuffer=this.outboundBuffer.filter(u=>this.outboundEventId(u.cmd,u.payload)!==e);const i=this.terminalOutbox.get(e),r=i?this.canReplaceUnsentOutputGuard(i):!1;if(i&&!r)n.terminalObserved=!0,this.scheduleTerminalDelivery(e,{ignoreNotBefore:!0});else if(!this.committedTerminalEvents.has(e))try{const u=this.terminalOutbox.enqueue(this.makeOutputIntegrityFailurePayload(e,t));this.scheduleTerminalDelivery(u.payload.event_id,{ignoreNotBefore:!0})}catch(u){this.emitClientError(new Error(`output-integrity terminal persist failed: event=${e}: ${u instanceof Error?u.message:u}`))}for(const u of[...this.pendingOutputSeqsByEvent.get(e)??[]])this.settlePendingOutputWrite(u,!1,!1);this.settlePendingAcceptedOutputsForEvent(e,"rejected",!1),this.emit("streamRejected",e,0),this.emitClientError(new Error(`required output integrity failed: event=${e} removed=${s-this.outboundBuffer.length} reason=${t}`))}settleOutputIntegrityTerminal(e){const t=this.outputIntegrityFailed.get(e);t&&(t.terminalSettled=!0,t.terminalObserved&&this.outputIntegrityFailed.delete(e))}bufferOutboundEntry(e){if(this.outboundEntryOrders.has(e)||this.outboundEntryOrders.set(e,++this.nextOutboundEntryOrder),this.outboundBuffer.includes(e))return!0;const t=this.outboundEventId(e.cmd,e.payload);return t&&(this.outputIntegrityFailed.has(t)||this.blockedOutputEvents.has(t))?!1:(this.outboundBuffer.length>=p.MAX_OUTBOUND_BUFFER_SIZE&&(this.outboundBuffer=this.outboundBuffer.filter(n=>p.BUFFER_OVERFLOW_RETAIN_COMMANDS.has(n.cmd)),this.outboundBuffer.length>=p.MAX_OUTBOUND_BUFFER_SIZE&&m.warn("aibot",`outbound buffer soft limit exceeded by ${this.outboundBuffer.length} retained packet(s); preserving required output`)),this.outboundBuffer.length>=p.MAX_OUTBOUND_BUFFER_HARD_SIZE?(t?this.markOutputIntegrityFailed(t,`outbound buffer hard limit ${p.MAX_OUTBOUND_BUFFER_HARD_SIZE} reached`):this.emitClientError(new Error(`outbound buffer hard limit ${p.MAX_OUTBOUND_BUFFER_HARD_SIZE} reached; rejected non-event packet cmd=${e.cmd}`)),!1):(this.outboundBuffer.push(e),this.outboundBuffer.sort((n,s)=>(this.outboundEntryOrders.get(n)??Number.MAX_SAFE_INTEGER)-(this.outboundEntryOrders.get(s)??Number.MAX_SAFE_INTEGER)),!0))}registerPendingOutputWrite(e,t,n,s,i){let r;const u=new Promise(l=>{r=l}),o=setTimeout(()=>{const l=this.pendingOutputWrites.get(e);if(!l||l.socket!==s||l.connectionGeneration!==i)return;if(!this.isConnectionCurrent(s,i)){this.settlePendingOutputWrite(e,!1,!1,s,i);return}this.removeSeqFromEventCorrelation(e);const d=this.isNonReplayableOutput(n.cmd,n.payload);this.settlePendingOutputWrite(e,!1,!d,s,i),d&&this.markOutputIntegrityFailed(t,`websocket write callback timed out for non-replayable ${n.cmd} seq=${e}`),this.emitClientError(new Error(`outbound websocket write callback timeout: cmd=${n.cmd} event=${t} seq=${e} delivery_unknown=${d}`)),this.reconnectAfterOutboundWriteFailure(s,`${n.cmd} callback timeout`)},p.OUTBOUND_WRITE_CALLBACK_TIMEOUT_MS);o.unref?.(),this.pendingOutputWrites.set(e,{eventId:t,entry:n,socket:s,connectionGeneration:i,promise:u,resolve:r,timer:o});let f=this.pendingOutputSeqsByEvent.get(t);f||(f=new Set,this.pendingOutputSeqsByEvent.set(t,f)),f.add(e)}settlePendingOutputWrite(e,t,n,s,i){const r=this.pendingOutputWrites.get(e);if(!r||s&&(r.socket!==s||r.connectionGeneration!==i))return;this.pendingOutputWrites.delete(e),clearTimeout(r.timer);const u=this.pendingOutputSeqsByEvent.get(r.eventId);u?.delete(e),u?.size===0&&this.pendingOutputSeqsByEvent.delete(r.eventId),n&&this.bufferOutboundEntry(r.entry),r.resolve(t)}settlePendingOutputWritesForEvent(e,t){const n=this.pendingOutputSeqsByEvent.get(e);if(n)for(const s of[...n]){const i=this.pendingOutputWrites.get(s);i&&this.settlePendingOutputWrite(s,t,!1,i.socket,i.connectionGeneration)}}abortPendingOutputWrites(e,t,n,s){for(const[i,r]of[...this.pendingOutputWrites.entries()])if(!(t&&(r.socket!==t||r.connectionGeneration!==n))){if(this.removeSeqFromEventCorrelation(i),s&&this.isNonReplayableOutput(r.entry.cmd,r.entry.payload)){this.settlePendingOutputWrite(i,!1,!1,t,n),this.markOutputIntegrityFailed(r.eventId,`connection lost while websocket write completion was unknown: ${s}`);continue}this.settlePendingOutputWrite(i,!1,e,t,n)}}registerPendingAcceptedOutput(e,t,n,s,i){let r;const u=new Promise(h=>{r=h}),o=Math.max(1,this.ackPolicy?.push_ack_timeout_ms??5e3),f=Math.max(1,this.ackPolicy?.max_retries??3),l=Math.max(1e3,Math.ceil(o*.25)),d=Math.max(p.MIN_REQUIRED_OUTPUT_ACK_TIMEOUT_MS,o*f+l),_=setTimeout(()=>{const h=this.pendingAcceptedOutputs.get(e);if(!(!h||h.socket!==s||h.connectionGeneration!==i)){if(!this.isConnectionCurrent(s,i)){this.settlePendingAcceptedOutput(e,"retry",!1,s,i);return}this.settlePendingAcceptedOutput(e,"retry",!0,s,i),this.emitClientError(new Error(`required output ACK timeout; retained for reconnect retry: event=${t} seq=${e}`)),this.reconnectAfterOutboundWriteFailure(s,"required output ACK timeout")}},d);_.unref?.(),this.pendingAcceptedOutputs.set(e,{eventId:t,entry:n,socket:s,connectionGeneration:i,promise:u,resolve:r,timer:_});const E=n.payload,c=typeof E.client_msg_id=="string"?E.client_msg_id.trim():"";if(c){let h=this.pendingAcceptedSeqsByClientMsgId.get(c);h||(h=new Set,this.pendingAcceptedSeqsByClientMsgId.set(c,h)),h.add(e)}let a=this.pendingAcceptedOutputSeqsByEvent.get(t);a||(a=new Set,this.pendingAcceptedOutputSeqsByEvent.set(t,a)),a.add(e)}settlePendingAcceptedOutput(e,t,n,s,i){const r=this.pendingAcceptedOutputs.get(e);if(!r||s&&(r.socket!==s||r.connectionGeneration!==i))return;this.pendingAcceptedOutputs.delete(e),clearTimeout(r.timer);const u=this.pendingAcceptedOutputSeqsByEvent.get(r.eventId);u?.delete(e),u?.size===0&&this.pendingAcceptedOutputSeqsByEvent.delete(r.eventId);const o=r.entry.payload,f=typeof o.client_msg_id=="string"?o.client_msg_id.trim():"";if(f){const l=this.pendingAcceptedSeqsByClientMsgId.get(f);l?.delete(e),l?.size===0&&this.pendingAcceptedSeqsByClientMsgId.delete(f)}n&&this.bufferOutboundEntry(r.entry),r.resolve(t)}settlePendingAcceptedOutputsForEvent(e,t,n){const s=this.pendingAcceptedOutputSeqsByEvent.get(e);if(s)for(const i of[...s])this.settlePendingAcceptedOutput(i,t,n)}abortPendingAcceptedOutputs(e,t,n){for(const[s,i]of[...this.pendingAcceptedOutputs.entries()])t&&(i.socket!==t||i.connectionGeneration!==n)||this.settlePendingAcceptedOutput(s,"retry",e,t,n)}rejectPendingRequestAfterWriteFailure(e,t,n){const s=this.pendingRequests.get(e);s&&(this.pendingRequests.delete(e),clearTimeout(s.timer),s.reject(new Error(`websocket write failed: ${t}: ${n.message}`)))}async awaitOutputDeliveryBarrier(e){const t=this.outboundFlushInFlight;if(t&&!await t.promise)return!1;for(;;){const n=this.pendingOutputSeqsByEvent.get(e);if(!n||n.size===0)break;const s=[...n].map(r=>this.pendingOutputWrites.get(r)?.promise).filter(r=>!!r);if(s.length===0)break;if((await Promise.all(s)).some(r=>!r))return!1}for(;;){const n=this.pendingAcceptedOutputSeqsByEvent.get(e);if(!n||n.size===0)break;const s=[...n].map(r=>this.pendingAcceptedOutputs.get(r)?.promise).filter(r=>!!r);if(s.length===0)break;const i=await Promise.all(s);if(i.includes("retry"))return!1;if(i.includes("rejected")){const r=this.terminalOutbox.get(e);if(!r||r.payload.status==="responded")return!1}}return this.outboundBuffer.some(n=>this.outboundEventId(n.cmd,n.payload)===e)?!1:this.hasNonReplayableOutput(e)?(this.markOutputIntegrityFailed(e,"output flush completed without a complete acceptance fence"),!1):!!(this.connected&&this.ws&&this.ws.readyState===O.OPEN)}isConnectionCurrent(e,t){return this.ws===e&&this.connectionGeneration===t}isOutboundFlushCurrent(e){return!e.finalized&&this.outboundFlushInFlight===e&&this.isConnectionCurrent(e.socket,e.connectionGeneration)}flushOutboundBuffer(e=this.ws,t=this.connectionGeneration){if(!e||!this.isConnectionCurrent(e,t))return Promise.resolve(!1);const n=this.outboundFlushInFlight;if(n){if(!n.finalized&&n.socket===e&&n.connectionGeneration===t)return n.promise;this.cancelOutboundFlush(n,"discard","superseded stale flush")}if(this.outboundBuffer.length===0)return Promise.resolve(!0);const s={connectionGeneration:t,flushGeneration:++this.nextOutboundFlushGeneration,socket:e,promise:Promise.resolve(!1),finalized:!1,activeBatch:null,nextIndex:0,currentWritePending:!1,currentWriteOutcome:null,cancelCurrentWrite:null,remainderHandled:!1};return this.outboundFlushInFlight=s,s.promise=this.performOutboundBufferFlush(s),s.promise.finally(()=>{s.finalized=!0,s.cancelCurrentWrite?.(),s.cancelCurrentWrite=null,this.outboundFlushInFlight===s&&(this.outboundFlushInFlight=null)}),s.promise}async performOutboundBufferFlush(e){for(;this.isOutboundFlushCurrent(e)&&this.outboundBuffer.length>0;){const{socket:t}=e;if(t.readyState!==O.OPEN)return!1;const n=this.outboundBuffer;this.outboundBuffer=[],e.activeBatch=n,e.nextIndex=0,e.remainderHandled=!1;for(let s=0;s<n.length;s++){if(!this.isOutboundFlushCurrent(e))return!1;const i=n[s],{cmd:r,payload:u}=i,o=this.outboundEventId(r,u);if(o&&this.blockedOutputEvents.has(o)){this.packetLog?.logOutboundPacket(r,0,u,"dropped"),e.nextIndex=s+1,e.currentWritePending=!1,e.currentWriteOutcome=null;continue}const f=++this.seq,l={cmd:r,seq:f,payload:u};e.nextIndex=s,e.currentWritePending=!0,e.currentWriteOutcome=null,o&&this.isAckRequiredOutput(r,u)&&this.registerPendingAcceptedOutput(f,o,i,t,e.connectionGeneration);const d=await new Promise(_=>{let E=!1;const c=h=>{E||(E=!0,clearTimeout(a),e.currentWriteOutcome=h,_(h))},a=setTimeout(()=>c("uncertain"),p.OUTBOUND_WRITE_CALLBACK_TIMEOUT_MS);a.unref?.(),e.cancelCurrentWrite=()=>c("canceled");try{this.trackOutboundEventCorrelation(r,f,u),t.send(JSON.stringify(l),h=>{c(h?"retry":"written")})}catch{c("retry")}});if(e.cancelCurrentWrite=null,!this.isOutboundFlushCurrent(e))return!1;if(d!=="written"||t.readyState!==O.OPEN){d==="retry"&&this.removeSeqFromEventCorrelation(f),this.settlePendingAcceptedOutput(f,"retry",!1);const _=this.retainOutboundFlushRemainder(e,d,`auth flush write failed for ${r} seq=${f} outcome=${d}`);return this.emitClientError(new Error(`outbound buffer flush failed at cmd=${r} seq=${f} outcome=${d}; ${_} packet(s) retained`)),!1}this.trackNonReplayableOutput(t,r,u,f),e.currentWritePending=!1,e.currentWriteOutcome="written",e.nextIndex=s+1}e.activeBatch=null,e.currentWritePending=!1,e.currentWriteOutcome=null,e.remainderHandled=!0}return this.isOutboundFlushCurrent(e)}retainOutboundFlushRemainder(e,t,n){if(e.remainderHandled||!e.activeBatch)return 0;e.remainderHandled=!0;const s=e.activeBatch,i=Math.min(e.nextIndex,s.length);let r=i;if(e.currentWritePending&&i<s.length){const o=s[i],f=this.outboundEventId(o.cmd,o.payload);t!=="retry"&&this.isNonReplayableOutput(o.cmd,o.payload)&&f&&(this.markOutputIntegrityFailed(f,n),r=i+1)}let u=0;for(const o of s.slice(r))this.bufferOutboundEntry(o)&&u++;return e.activeBatch=null,e.nextIndex=s.length,e.currentWritePending=!1,u}cancelOutboundFlush(e,t,n){if(e.finalized){this.outboundFlushInFlight===e&&(this.outboundFlushInFlight=null);return}e.finalized=!0,this.outboundFlushInFlight===e&&(this.outboundFlushInFlight=null),t==="connection_lost"?this.retainOutboundFlushRemainder(e,e.currentWriteOutcome,`connection lost during auth flush: ${n}`):(e.remainderHandled=!0,e.activeBatch=null,e.currentWritePending=!1);const s=e.cancelCurrentWrite;e.cancelCurrentWrite=null,s?.()}reconnectAfterOutboundFlushFailure(e,t){this.agentDeleted||!this.isConnectionCurrent(e,t)||this.reconnectAfterOutboundWriteFailure(e,"outbound flush failed")}reconnectAfterOutboundWriteFailure(e,t){if(this.agentDeleted||this.ws!==e)return;const n=this.connectionGeneration;this.connected=!1,this.negotiatedCapabilities.clear(),this.stopHeartbeat(),this.clearTerminalRetryTimers(),this.clearStopResultRetryTimers(),this.rejectAllPendingRequests(t);const s=this.outboundFlushInFlight;s&&s.socket===e&&s.connectionGeneration===n&&this.cancelOutboundFlush(s,"connection_lost",t),this.failNonReplayableOutputsForSocket(e,t),this.abortPendingOutputWrites(!0,e,n,t),this.abortPendingAcceptedOutputs(!0,e,n),this.clearAllEventCorrelations(),this.connectionGeneration++;try{e.close(1011,t)}catch{}this.ws===e&&(this.ws=null),this.attemptReconnect()}handleInvokeResult(e){const t=this.pendingInvokes.get(e.invoke_id);t&&(this.pendingInvokes.delete(e.invoke_id),clearTimeout(t.timer),e.code===0?t.resolve(e.data??null):t.reject(new Error(`agent_invoke error code=${e.code}: ${e.msg??""}`)))}rejectAllPendingInvokes(e){for(const[,t]of this.pendingInvokes)clearTimeout(t.timer),t.reject(new Error(`agent_invoke canceled: ${e}`));this.pendingInvokes.clear()}rejectAllPendingRequests(e){for(const[,t]of this.pendingRequests)clearTimeout(t.timer),t.reject(new Error(`request canceled: ${e}`));this.pendingRequests.clear()}cleanupSocket(e=this.ws,t=this.connectionGeneration){if(!(!e||!this.isConnectionCurrent(e,t)))try{e.close()}catch{}}startHeartbeat(){this.stopHeartbeat(),this.heartbeatFailures=0,this.heartbeatTimer=setInterval(()=>{const e=this.ws,t=this.connectionGeneration;!this.connected||!e||this.request("ping",{ts:Date.now()},{expected:["pong"],timeoutMs:5e3}).then(()=>{this.isConnectionCurrent(e,t)&&(this.heartbeatFailures=0)}).catch(()=>{!this.connected||!this.isConnectionCurrent(e,t)||(this.heartbeatFailures++,!(this.heartbeatFailures<p.HEARTBEAT_MAX_FAILURES)&&(this.cleanupSocket(e,t),this.attemptReconnect()))})},this.heartbeatSec*1e3)}stopHeartbeat(){this.heartbeatTimer&&(clearInterval(this.heartbeatTimer),this.heartbeatTimer=null)}}export{p as AibotClient};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{BindingCardMetaCache as i}from"./binding-card-meta-cache.js";class c{client;_status="ready";connectedAt;listeners=[];bindingCardMetaCache=new i;authAck=null;constructor(e){this.client=e,this.connectedAt=Date.now(),this.addInternalListener("close",()=>{this._status!=="closing"&&(this._status="reconnecting")}),this.addInternalListener("auth",t=>{this._status="ready",t&&(this.authAck=t)}),this.addInternalListener("disconnected",()=>{this._status!=="closing"&&this._status!=="closed"&&(this._status="reconnecting")})}get status(){return this._status}getStatusSnapshot(){return{status:this._status,connectedAt:this.connectedAt,reconnectAttempts:0}}sendEventAck(e){this.client.sendEventAck(e)}sendStreamChunk(e){this.client.sendStreamChunk(e)}sendMsg(e){this.client.sendMsg(e)}editMsg(e){this.client.editMsg(e)}sendEventResult(e){this.client.sendEventResult(e)}sendLocalActionResult(e){this.client.sendLocalActionResult(e)}sendEventStopAck(e){this.client.sendEventStopAck(e)}sendEventStopResult(e){this.client.sendEventStopResult(e)}sendSessionActivitySet(e){this.client.sendSessionActivitySet(e)}sendCodexEvent(e){this.client.sendCodexEvent(e)}sendUpdateBindingCard(e){const t=this.bindingCardMetaCache.merge(e.session_id,e.meta);this.client.sendUpdateBindingCard(t===e.meta?e:{...e,meta:t})}clearBindingCardMetaCache(e){this.bindingCardMetaCache.delete(e)}sendAuditState(e){this.client.sendAuditState(e)}sendSkillsUpdate(e){this.client.sendSkillsUpdate(e)}sendPing(){this.client.sendPing()}sendEventState(e){this.client.sendEventState(e)}sendEventCancelResult(e){this.client.sendEventCancelResult(e)}sendQueueClearResult(e){this.client.sendQueueClearResult(e)}sendQueueReorderResult(e){this.client.sendQueueReorderResult(e)}sendEventHoldResult(e){this.client.sendEventHoldResult(e)}sendQueueEditResult(e){this.client.sendQueueEditResult(e)}sendQueueSnapshot(e){this.client.sendQueueSnapshot(e)}agentInvoke(e,t,s){return this.client.agentInvoke(e,t,s)}sendStreamChunkRequest(e,t){return this.client.sendStreamChunkRequest(e,t)}sendText(e,t){return this.client.sendText(e,t)}sendMedia(e,t){return this.client.sendMedia(e,t)}editMessage(e,t){return this.client.editMessage(e,t)}deleteMessage(e,t,s){return this.client.deleteMessage(e,t,s)}sendEventResultRequest(e,t){return this.client.sendEventResultRequest(e,t)}relayCredentialRequest(e,t){return this.client.relayCredentialRequest(e,t)}onEvent(e){return this.subscribe("event",e)}onLocalAction(e){return this.subscribe("localAction",e)}onStop(e){return this.subscribe("stop",e)}onRevoke(e){return this.subscribe("revoke",e)}onEdit(e){return this.subscribe("edit",e)}onEventCancel(e){return this.subscribe("eventCancel",e)}onQueueClear(e){return this.subscribe("queueClear",e)}onQueueReorder(e){return this.subscribe("queueReorder",e)}onQueueSnapshotQuery(e){return this.subscribe("queueSnapshotQuery",e)}onEventHold(e){return this.subscribe("eventHold",e)}onQueueEdit(e){return this.subscribe("queueEdit",e)}onKicked(e){return this.subscribe("kicked",e)}onAgentDeleted(e){return this.subscribe("agentDeleted",e)}onShareSet(e){return this.subscribe("shareSet",e)}onProfilePush(e){return this.subscribe("profilePush",e)}onSkillSync(e){return this.subscribe("skillSync",e)}onError(e){return this.subscribe("error",e)}onClose(e){return this.subscribe("close",e)}onDisconnected(e){return this.subscribe("disconnected",e)}onStreamRejected(e){return this.subscribe("streamRejected",e)}onReconnected(e){return this.subscribe("auth",e)}disconnect(){this._status="closing",this.removeAllListeners(),this.client.disconnect(),this._status="closed"}sendMcpFrame(e,t){this.client.sendMcpFrame(e,t)}onMcpFrame(e){return this.subscribe("mcpFrame",e)}subscribe(e,t){return this.client.on(e,t),this.listeners.push({event:e,handler:t}),()=>{this.client.removeListener(e,t);const s=this.listeners.findIndex(n=>n.event===e&&n.handler===t);s>=0&&this.listeners.splice(s,1)}}addInternalListener(e,t){this.client.on(e,t),this.listeners.push({event:e,handler:t})}removeAllListeners(){for(const{event:e,handler:t}of this.listeners)this.client.removeListener(e,t);this.listeners.length=0}}export{c as AibotConnectionHandleImpl};
|
|
1
|
+
import{BindingCardMetaCache as i}from"./binding-card-meta-cache.js";class c{client;_status="ready";connectedAt;listeners=[];bindingCardMetaCache=new i;authAck=null;constructor(e){this.client=e,this.connectedAt=Date.now(),this.addInternalListener("close",()=>{this._status!=="closing"&&(this._status="reconnecting")}),this.addInternalListener("auth",t=>{this._status="ready",t&&(this.authAck=t)}),this.addInternalListener("disconnected",()=>{this._status!=="closing"&&this._status!=="closed"&&(this._status="reconnecting")})}get status(){return this._status}getStatusSnapshot(){return{status:this._status,connectedAt:this.connectedAt,reconnectAttempts:0}}sendEventAck(e){this.client.sendEventAck(e)}sendStreamChunk(e){this.client.sendStreamChunk(e)}sendMsg(e){this.client.sendMsg(e)}editMsg(e){this.client.editMsg(e)}sendEventResult(e){this.client.sendEventResult(e)}sendLocalActionResult(e){this.client.sendLocalActionResult(e)}sendEventStopAck(e){this.client.sendEventStopAck(e)}sendEventStopResult(e){this.client.sendEventStopResult(e)}sendSessionActivitySet(e){this.client.sendSessionActivitySet(e)}sendCodexEvent(e){this.client.sendCodexEvent(e)}sendUpdateBindingCard(e){const t=this.bindingCardMetaCache.merge(e.session_id,e.meta);this.client.sendUpdateBindingCard(t===e.meta?e:{...e,meta:t})}clearBindingCardMetaCache(e){this.bindingCardMetaCache.delete(e)}sendAuditState(e){this.client.sendAuditState(e)}sendSkillsUpdate(e){this.client.sendSkillsUpdate(e)}sendPing(){this.client.sendPing()}sendEventState(e){this.client.sendEventState(e)}sendEventCancelResult(e){this.client.sendEventCancelResult(e)}sendQueueClearResult(e){this.client.sendQueueClearResult(e)}sendQueueReorderResult(e){this.client.sendQueueReorderResult(e)}sendEventHoldResult(e){this.client.sendEventHoldResult(e)}sendQueueEditResult(e){this.client.sendQueueEditResult(e)}sendQueueSnapshot(e){this.client.sendQueueSnapshot(e)}agentInvoke(e,t,s){return this.client.agentInvoke(e,t,s)}sendStreamChunkRequest(e,t){return this.client.sendStreamChunkRequest(e,t)}sendText(e,t){return this.client.sendText(e,t)}sendMedia(e,t){return this.client.sendMedia(e,t)}editMessage(e,t){return this.client.editMessage(e,t)}deleteMessage(e,t,s){return this.client.deleteMessage(e,t,s)}sendEventResultRequest(e,t){return this.client.sendEventResultRequest(e,t)}relayCredentialRequest(e,t){return this.client.relayCredentialRequest(e,t)}relayStateSyncRequest(e,t){return this.client.relayStateSyncRequest(e,t)}sendRelayStateReport(e){this.client.sendRelayStateReport(e)}onEvent(e){return this.subscribe("event",e)}onLocalAction(e){return this.subscribe("localAction",e)}onStop(e){return this.subscribe("stop",e)}onRevoke(e){return this.subscribe("revoke",e)}onEdit(e){return this.subscribe("edit",e)}onEventCancel(e){return this.subscribe("eventCancel",e)}onQueueClear(e){return this.subscribe("queueClear",e)}onQueueReorder(e){return this.subscribe("queueReorder",e)}onQueueSnapshotQuery(e){return this.subscribe("queueSnapshotQuery",e)}onEventHold(e){return this.subscribe("eventHold",e)}onQueueEdit(e){return this.subscribe("queueEdit",e)}onKicked(e){return this.subscribe("kicked",e)}onAgentDeleted(e){return this.subscribe("agentDeleted",e)}onShareSet(e){return this.subscribe("shareSet",e)}onProfilePush(e){return this.subscribe("profilePush",e)}onSkillSync(e){return this.subscribe("skillSync",e)}onError(e){return this.subscribe("error",e)}onClose(e){return this.subscribe("close",e)}onDisconnected(e){return this.subscribe("disconnected",e)}onStreamRejected(e){return this.subscribe("streamRejected",e)}onReconnected(e){return this.subscribe("auth",e)}disconnect(){this._status="closing",this.removeAllListeners(),this.client.disconnect(),this._status="closed"}sendMcpFrame(e,t){this.client.sendMcpFrame(e,t)}onMcpFrame(e){return this.subscribe("mcpFrame",e)}subscribe(e,t){return this.client.on(e,t),this.listeners.push({event:e,handler:t}),()=>{this.client.removeListener(e,t);const s=this.listeners.findIndex(n=>n.event===e&&n.handler===t);s>=0&&this.listeners.splice(s,1)}}addInternalListener(e,t){this.client.on(e,t),this.listeners.push({event:e,handler:t})}removeAllListeners(){for(const{event:e,handler:t}of this.listeners)this.client.removeListener(e,t);this.listeners.length=0}}export{c as AibotConnectionHandleImpl};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{readdir as r,stat as m}from"node:fs/promises";import{join as l,extname as d}from"node:path";const x={pdf:"application/pdf",doc:"application/msword",docx:"application/vnd.openxmlformats-officedocument.wordprocessingml.document",xls:"application/vnd.ms-excel",xlsx:"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",ppt:"application/vnd.ms-powerpoint",pptx:"application/vnd.openxmlformats-officedocument.presentationml.presentation",txt:"text/plain",md:"text/markdown",csv:"text/csv",json:"application/json",xml:"application/xml",yaml:"text/yaml",yml:"text/yaml",html:"text/html",css:"text/css",js:"text/javascript",ts:"text/typescript",zip:"application/zip",rar:"application/x-rar-compressed","7z":"application/x-7z-compressed",tar:"application/x-tar",gz:"application/gzip",jpg:"image/jpeg",jpeg:"image/jpeg",png:"image/png",gif:"image/gif",webp:"image/webp",svg:"image/svg+xml",mp4:"video/mp4",mov:"video/quicktime",avi:"video/x-msvideo",mkv:"video/x-matroska",webm:"video/webm",mp3:"audio/mpeg",wav:"audio/wav",flac:"audio/flac",aac:"audio/aac"};function n(a){const p=d(a).slice(1).toLowerCase();return x[p]}async function f(a,p=!1){const c=await r(a,{withFileTypes:!0}),s=[];for(const
|
|
1
|
+
import{readdir as r,stat as m}from"node:fs/promises";import{join as l,extname as d}from"node:path";const x={pdf:"application/pdf",doc:"application/msword",docx:"application/vnd.openxmlformats-officedocument.wordprocessingml.document",xls:"application/vnd.ms-excel",xlsx:"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",ppt:"application/vnd.ms-powerpoint",pptx:"application/vnd.openxmlformats-officedocument.presentationml.presentation",txt:"text/plain",md:"text/markdown",csv:"text/csv",json:"application/json",xml:"application/xml",yaml:"text/yaml",yml:"text/yaml",html:"text/html",css:"text/css",js:"text/javascript",ts:"text/typescript",zip:"application/zip",rar:"application/x-rar-compressed","7z":"application/x-7z-compressed",tar:"application/x-tar",gz:"application/gzip",jpg:"image/jpeg",jpeg:"image/jpeg",png:"image/png",gif:"image/gif",webp:"image/webp",svg:"image/svg+xml",mp4:"video/mp4",mov:"video/quicktime",avi:"video/x-msvideo",mkv:"video/x-matroska",webm:"video/webm",mp3:"audio/mpeg",wav:"audio/wav",flac:"audio/flac",aac:"audio/aac"};function n(a){const p=d(a).slice(1).toLowerCase();return x[p]}async function f(a,p=!1){const c=await r(a,{withFileTypes:!0}),s=[];for(const i of c){if(!p&&i.name.startsWith("."))continue;const t=l(a,i.name),e={id:t,name:i.name,is_directory:i.isDirectory()};try{if(i.isDirectory()){const o=await m(t);e.modified_at=o.mtime.toISOString()}else{const o=await m(t);e.size=o.size,e.modified_at=o.mtime.toISOString(),e.mime_type=n(i.name)}}catch{}s.push(e)}return s.sort((i,t)=>i.is_directory!==t.is_directory?i.is_directory?-1:1:i.name.localeCompare(t.name)),s}export{f as listFiles,n as resolveMimeType};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{fetchRelayCredentialWithRetry as l,RelayFetchError as c}from"./relay-credential-fetch.js";const p=15e3;function a(n){return n instanceof Error?n.message:String(n)}function o(n){if(n instanceof c)return n.bizCode?.trim()||n.code;const e=n?.code;return typeof e=="string"&&e.trim()?e.trim():"INTERNAL"}class u{ports;lastRevision=null;applyingFromServer=0;chain=Promise.resolve();constructor(e){this.ports=e}get knownRevision(){return this.lastRevision}syncOnConnect(){return this.enqueue(()=>this.doSync())}applyFromLocalAction(e){return this.enqueue(()=>this.doApply(e))}reportLocalChange(){if(this.applyingFromServer>0)return;const e=this.lastRevision;e!=null&&this.safeReport({applied:this.currentApplied(),revision:e})}enqueue(e){const t=this.chain.catch(()=>{}).then(e);return this.chain=t.then(()=>{},()=>{}),t}async doSync(){const e=this.ports.getLocalState();let t;try{t=await this.ports.sendSyncRequest({local_enabled:e.enabled,...e.model?{local_model:e.model}:{}},p)}catch(i){this.ports.log.warn(`relay_state_sync_request not answered: ${a(i)}`);return}if(t.cmd==="error"){const i=t.payload??{};this.ports.log.warn(`relay_state_sync_request rejected by server: code=${i.code??"?"} msg=${i.msg??""}`);return}const r=t.payload??{};if(r.status!=="ok"){this.ports.log.warn(`relay_state_sync_result failed: ${r.error_code??""} ${r.error_msg??""}`.trim());return}const s=Number(r.revision);if(!Number.isFinite(s)){this.ports.log.warn("relay_state_sync_result missing/invalid revision; ignored");return}this.lastRevision=s;try{r.enabled?await this.ensureEnabled(r.model?.trim()||void 0,r.credential):await this.guarded(()=>this.ports.applyDisable())}catch(i){this.ports.log.warn(`relay state apply after sync failed: ${a(i)}`),this.safeReport({applied:this.currentApplied(),revision:s,error_code:o(i)});return}this.safeReport({applied:this.currentApplied(),revision:s})}async doApply(e){const t=e.revision;this.lastRevision=t;const r=e.model?.trim()||void 0;try{return e.enabled?await this.ensureEnabled(r,void 0):await this.guarded(()=>this.ports.applyDisable()),{ok:!0,revision:t}}catch(s){return this.ports.log.warn(`apply_relay_state failed: ${a(s)}`),{ok:!1,error_code:o(s),error_msg:a(s),revision:t}}}async ensureEnabled(e,t){const r=t?.api_key?.trim();if(r){await this.guarded(()=>this.ports.applyEnable({apiKey:r,anthropicBaseUrl:t?.anthropic_base_url?.trim()||void 0,openaiBaseUrl:t?.openai_base_url?.trim()||void 0,model:e},e));return}const s=this.ports.getLocalState();if(s.enabled&&(!e||!s.model||s.model===e))return;const i=await l(this.ports.sendCredentialRequest,e?{model:e}:{});await this.guarded(()=>this.ports.applyEnable(i,e??i.model))}async guarded(e){this.applyingFromServer++;try{await e()}finally{this.applyingFromServer--}}currentApplied(){try{return this.ports.getLocalState().enabled}catch{return!1}}safeReport(e){try{this.ports.sendReport(e)}catch(t){this.ports.log.warn(`relay_state_report not sent: ${a(t)}`)}}}export{u as RelayStateSyncer};
|
package/dist/grix.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import k from"node:path";import{writeFileSync as
|
|
3
|
-
Run \`grix-connector --help\` to see the supported commands and options.`),process.exit(1)),
|
|
2
|
+
import k from"node:path";import{writeFileSync as Y}from"node:fs";import{Manager as me}from"./manager.js";import{ensureGrixDirs as he,initLogger as ye,log as s,installProcessLogRotation as we,setConsoleOutput as q}from"./core/log/index.js";import{HealthServer as Se,bindPortOrFail as z}from"./core/runtime/index.js";import{writePidFile as Re,removePidFile as C,readDaemonPid as Ae}from"./core/runtime/index.js";import{resolveRuntimePaths as H}from"./core/config/index.js";import{supportsNonMitmRelayConfig as B}from"./core/config/provider-env.js";import{ServiceManager as xe}from"./service/service-manager.js";import{killProcessesByCommandLine as Ee,isWindowsElevated as ve}from"./service/process-control.js";import{buildServiceID as be}from"./service/service-paths.js";import{acquireDaemonLock as De,isLockHolderSameProcess as Pe,readDaemonLock as M,releaseDaemonLock as T}from"./runtime/daemon-lock.js";import{writeDaemonStatus as _,removeDaemonStatus as Ie}from"./runtime/service-state.js";import{AdminServer as ke,generateToken as Ce,writeTokenFile as Te}from"./core/admin/index.js";import{initSentry as Ue,closeSentry as G,reportFatal as N}from"./core/observability/sentry.js";import{initProxyManager as $e,getProxyManager as m,relayHostsForClientType as Q}from"./core/proxy/index.js";import{stopProxyIfNobodyNeedsIt as _e,disableAllRelayAndRestart as Fe,setOneAgentRelayAndRestart as Z}from"./core/proxy/relay-orchestration.js";import{setAgentRelayCredential as ee,RelayCredentialError as te}from"./core/proxy/relay-credential.js";import{RelayFetchError as re,RelayToggleCoordinator as Le}from"./core/proxy/relay-credential-fetch.js";import{applyHermesProfileRelay as Oe,clearHermesProfileRelay as He,discoverHermesProfiles as Be}from"./core/proxy/hermes-profile-relay.js";import{disableRelayCredential as Me,enableRelayCredential as Ge,RelayRouteError as oe}from"./core/proxy/relay-credential-router.js";import{resolveClientVersion as Ne}from"./core/util/client-version.js";import{parseCliArgs as je}from"./core/util/cli-args.js";const{command:x,flags:S,unknownFlags:j}=je(process.argv.slice(2));if(j.length>0&&(console.error(`Unknown option${j.length>1?"s":""}: ${j.map(o=>`--${o}`).join(", ")}
|
|
3
|
+
Run \`grix-connector --help\` to see the supported commands and options.`),process.exit(1)),S.version&&(console.log(Ne()),process.exit(0)),S.help&&(console.log(`grix-connector \u2014 Unified AI Agent Bridge
|
|
4
4
|
|
|
5
5
|
Usage: grix-connector <command> [options]
|
|
6
6
|
|
|
@@ -34,9 +34,9 @@ Examples:
|
|
|
34
34
|
grix-connector start # Start as system service
|
|
35
35
|
grix-connector status # Check service status
|
|
36
36
|
grix-connector restart # Restart the service
|
|
37
|
-
`),process.exit(0)),
|
|
37
|
+
`),process.exit(0)),x==="reload"){const o=Ae();o||(console.error("reload failed: daemon is not running (no pid file)"),process.exit(1));try{process.kill(o,0)}catch{console.error(`reload failed: daemon process ${o} is not running (stale pid file)`),process.exit(1)}{const c=M(H().daemonLockFile);c&&c.pid===o&&!Pe(c)&&(console.error(`reload failed: pid ${o} has been reused by another process (stale pid file)`),process.exit(1))}try{process.kill(o,"SIGHUP"),console.log(JSON.stringify({ok:!0,signaled:o},null,2)),process.exit(0)}catch(c){console.error(`reload failed: ${c instanceof Error?c.message:c}`),process.exit(1)}}const ne=["start","stop","restart","status"];if(x&&ne.includes(x)){process.platform==="win32"&&["start","stop","restart"].includes(x)&&!ve()&&console.warn(`Warning: Not running as administrator. Task Scheduler registration is skipped;
|
|
38
38
|
using Startup folder auto-start instead. For full Task Scheduler integration,
|
|
39
|
-
right-click the terminal and select "Run as administrator".`);const
|
|
40
|
-
Valid commands: ${
|
|
41
|
-
`),
|
|
42
|
-
`)}catch{}
|
|
39
|
+
right-click the terminal and select "Run as administrator".`);const o=H(),c=S["config-dir"]??(S.profile?k.join(o.configDir,S.profile):void 0),h=k.resolve(process.argv[1]||`${o.rootDir}/dist/grix.js`),p=new xe({cliPath:h,nodePath:process.execPath});try{let u;switch(x){case"start":(await p.status({rootDir:o.rootDir})).installed?u=await p.start({rootDir:o.rootDir}):u=await p.install({rootDir:o.rootDir,configDir:c});break;case"stop":u=await p.stop({rootDir:o.rootDir});break;case"restart":(await p.status({rootDir:o.rootDir})).installed?u=await p.restart({rootDir:o.rootDir}):u=await p.install({rootDir:o.rootDir,configDir:c});break;case"status":u=await p.status({rootDir:o.rootDir});break}console.log(JSON.stringify(u,null,2)),process.exit(0)}catch(u){console.error(`${x} failed: ${u instanceof Error?u.message:u}`),process.exit(1)}}else x&&(console.error(`Unknown command: ${x}
|
|
40
|
+
Valid commands: ${ne.join(", ")}`),process.exit(1));const l=H(),Ke=S["config-dir"]??(S.profile?`${l.configDir}/${S.profile}`:void 0),r=new me,F=new Se,ae=Ce(),E=new ke(ae);let K=!1,D;async function se(o){process.stderr.write(o.message+`
|
|
41
|
+
`),s.error("main",o.message.replace(/\n/g," \u2014 "));const c=M(l.daemonLockFile);c?.pid===process.pid&&c.instance_id===D&&await _(l.daemonStatusFile,{state:"failed",pid:process.pid,instance_id:D,updated_at:Date.now(),reason:`port_bind_${o.kind}:${o.label}:${o.port}`}).catch(()=>{}),await G(),T(l.daemonLockFile).catch(()=>{}),C(),process.exit(1)}async function L(o){if(K)return;K=!0,s.info("main",`Received ${o}, shutting down...`),F.markShuttingDown();const c=setTimeout(()=>{s.error("main","Shutdown timed out, forcing exit"),T(l.daemonLockFile).catch(()=>{}),C(),process.exit(2)},1e4);try{await r.stop(),await m()?.stop().catch(()=>{}),await E.stop(),await F.stop(),await G(),await T(l.daemonLockFile),await Ie(l.daemonStatusFile).catch(()=>{}),clearTimeout(c),C(),s.info("main","Shutdown complete"),process.exit(0)}catch(h){s.error("main",`Shutdown error: ${h}`),T(l.daemonLockFile).catch(()=>{}),C(),process.exit(2)}}async function Ve(){if(he(),ye(),we(l.stdoutLogFile,l.stderrLogFile),q(!1),await Ue(),process.platform==="win32"){const e=be(l.rootDir,"win32");await Ee(e,{platform:"win32"})}try{D=(await De(l.daemonLockFile,l.rootDir)).instance_id}catch(e){console.error(e instanceof Error?e.message:e),process.exit(1)}Re(),s.info("main",`grix-connector starting (PID ${process.pid})`),await _(l.daemonStatusFile,{state:"starting",pid:process.pid,instance_id:D,updated_at:Date.now()});const o=parseInt(S["health-port"]??process.env.GRIX_HEALTH_PORT??"19579",10);{const e=await z({label:"health",port:o,envVar:"GRIX_HEALTH_PORT",cliFlag:"health-port",start:n=>F.start(n)});e&&await se(e)}const c=k.join(l.dataDir,"health-port");Y(c,String(o),"utf-8"),process.on("SIGINT",()=>L("SIGINT")),process.on("SIGTERM",()=>L("SIGTERM")),process.on("SIGHUP",()=>{K||(s.info("main","Received SIGHUP, reloading config..."),r.reload().then(e=>s.info("main",`reload done: ${JSON.stringify(e)}`)).catch(e=>s.error("main",`reload failed: ${e instanceof Error?e.message:e}`)))});let h="",p=0,u;process.on("uncaughtException",e=>{const n=e instanceof Error?e.stack??e.message:String(e);n===h?(p++,(p<=3||p%100===0)&&s.error("main",`Uncaught exception (x${p}): ${n}`)):(p>3&&s.error("main",`Previous exception repeated ${p} times total`),h=n,p=1,s.error("main",`Uncaught exception: ${e instanceof Error?e.stack:e}`),u||(u=setTimeout(()=>{p>3&&s.error("main",`Previous exception repeated ${p} times total`),h="",p=0,u=void 0},1e4).unref())),!ie(e)&&(N(e,"uncaughtException"),L("uncaughtException"))}),process.on("unhandledRejection",e=>{s.error("main",`Unhandled rejection: ${e}`),!ie(e)&&(N(e,"unhandledRejection"),L("unhandledRejection"))});const b=$e(l.dataDir);await b.clearRuntimeState();const P=b.getRelayAgents();if(P.length>0)try{await b.startIfAnyRelayAgent(),s.info("main",`mitm proxy started for relay agents: ${P.join(", ")} (per-agent; others stay direct)`)}catch(e){s.error("main",`mitm proxy FAILED to start (relay agents: ${P.join(", ")}): ${e}. These agents will REFUSE to start (they must not silently fall back to your own account). Fix the proxy or turn relay off for them. Other agents are unaffected.`)}else s.info("main",'mitm proxy idle (no agent has Grix relay enabled; enable: PUT /api/proxy/agents/<name>/enabled {"enabled":true})');if(F.setStatusProvider(()=>r.getAgentsStatus()),await r.start(Ke),P.length>0){const e=r.getAgentsStatus();for(const n of P){const d=e.find(t=>t.name===n);if(!d)continue;const R=Q(d.clientType);R.length!==0&&await b.setAgentRelayEnabled(n,!0,{relayHosts:R}).catch(t=>{s.warn("main",`failed to reconcile relay hosts for "${n}": ${t}`)})}}const V=parseInt(S["admin-port"]??process.env.GRIX_ADMIN_PORT??"19580",10);E.setAgentHandler({list:()=>r.getAgentsStatus(),add:e=>r.addAgent(e),remove:e=>r.removeAgent(e),restart:e=>r.restartAgent(e),reload:()=>r.reload()}),E.setUpgradeHandler({check:()=>r.checkUpgrade(),trigger:()=>r.triggerUpgrade()}),E.setProbeHandler({probeAll:e=>r.probeAll(e),probeOne:(e,n)=>r.probeOne(e,n)}),E.setInstallHandler({listInstallable:()=>r.listInstallable(),installAgent:e=>r.installAgent(e),getInstallProgress:e=>r.getInstallProgress(e)});let I=null;if(m()){const e=async()=>{const t=m();t&&await _e(t,r)&&s.info("main","no agent uses Grix relay anymore; mitm proxy stopped")};r.setRelayEnvSettledHandler(()=>{e().catch(t=>{s.warn("main",`failed to stop idle mitm proxy: ${t}`)})});const n=()=>{const t=m(),a=t.getRelayAgents(),i=t.getDegradedRelayAgents();return{enabled:a.length>0,relayAgents:a,staleRelayAgents:r.getAgentsWithStaleRelayEnv(),degradedRelayAgents:i,runtime:t.getRuntimeInfo(),config:t.getConfigSnapshot()}},d=new Le,R={status:()=>n(),setAgentRelay:async(t,a,i)=>{const f=m(),w={info:y=>s.info("main",y),warn:y=>s.warn("main",y)},A=y=>r.getAgentsStatus().find(v=>v.name===y),g=A(t);if(!a){d.cancel(t);const y=await d.runExclusive(t,async()=>{if(g&&B(g.clientType))return await r.setAgentProvider(t,void 0),{...n(),restarted:!0,busy:!1};const{restarted:v,busy:O}=await Z(f,r,w,{agentName:t,enabled:!1});return{...n(),restarted:v,busy:O}});return r.reportRelayStateLocalChange(t),y}if(!g)throw Object.assign(new Error(`Agent "${t}" not found`),{code:"NOT_FOUND"});if(!B(g.clientType)&&Q(g.clientType).length===0)throw Object.assign(new Error(`client type "${g.clientType??""}" does not support Grix relay`),{code:"UNSUPPORTED_CLIENT_TYPE"});const de=await d.runExclusive(t,async()=>{const y=d.epoch(t),v=A(t);if(!v)throw Object.assign(new Error(`Agent "${t}" not found`),{code:"NOT_FOUND"});const O=B(v.clientType),X=String(v.agentId??"").trim();if(!X)throw new te(`local agent "${t}" has no agent_id registered`,"MISSING_AGENT_ID");const J=d.waitCancelled(t,y);let $;try{$=await Promise.race([r.fetchRelayCredential(t,{model:i}),J.promise.then(()=>{throw new re("relay enable cancelled by a later disable","CANCELLED")})])}finally{J.dispose()}d.assertCurrent(t,y);const{restarted:pe,busy:ge}=await ee(f,r,A,w,{localName:t,agentId:X,virtualKey:$.apiKey,anthropicBaseUrl:$.anthropicBaseUrl,openaiBaseUrl:$.openaiBaseUrl,model:i},(ue,fe)=>r.setAgentProvider(ue,fe));if(d.epoch(t)!==y)throw O?await r.setAgentProvider(t,void 0):await Z(f,r,w,{agentName:t,enabled:!1}),new re("relay enable cancelled by a later disable","CANCELLED");return{...n(),restarted:pe,busy:ge}});return r.reportRelayStateLocalChange(t),de},setAgentRelayCredential:async(t,a)=>{const i=m(),f=r.getAgentsStatus(),{restarted:w,busy:A}=await ee(i,r,g=>f.find(U=>U.name===g),{info:g=>s.info("main",g),warn:g=>s.warn("main",g)},{localName:t,agentId:a.agentId,virtualKey:a.virtualKey,anthropicBaseUrl:a.anthropicBaseUrl,openaiBaseUrl:a.openaiBaseUrl,model:a.model},(g,U)=>r.setAgentProvider(g,U));return r.reportRelayStateLocalChange(t),{...n(),restarted:w,busy:A}},disableAll:async()=>{const t=m();for(const i of r.getAgentsStatus())d.cancel(i.name);await Fe(t,r,{info:i=>s.info("main",i),warn:i=>s.warn("main",i)});const a=await r.clearDirectProviderConfigs();return a.length>0&&s.info("main",`relay disable-all cleared native provider config for: ${a.join(", ")}`),n()},setRoute:async(t,a)=>{const i=a,f=m();return f.setRoute({routeKey:t,targetBaseUrl:i.targetBaseUrl,...i.headers?{headers:i.headers}:{},...i.model?{model:i.model}:{},...i.modelMap?{modelMap:i.modelMap}:{},...i.passthrough?{passthrough:!0}:{},...i.relayOnly?{relayOnly:!0}:{},...i.codexResponsesToChat?{codexResponsesToChat:!0}:{}}),await f.persistConfig(),n()},deleteRoute:async t=>{const a=m();a.deleteRoute(t),await a.persistConfig()},setDefaultRoute:async t=>{const a=m();return a.setDefaultRouteKey(t),await a.persistConfig(),n()},setInterceptHosts:async t=>{const a=m();return a.setInterceptHosts(t),await a.persistConfig(),n()}};I=R,E.setProxyHandler(R),r.setRelayStateApplier({getLocalState:t=>{const a=m();if(a.getRelayAgents().includes(t)){const f=r.getAgentsStatus().find(g=>g.name===t),w=`grix-gateway-${String(f?.clientType??"").trim().toLowerCase()}`;return{enabled:!0,model:a.getConfigSnapshot().routes?.find(g=>g.routeKey===w)?.model?.trim()||void 0}}const i=r.getAgentProvider(t);return i?.api_key?.trim()?{enabled:!0,model:i.model?.trim()||void 0}:{enabled:!1}},applyEnable:async(t,a,i)=>{const f=r.getAgentsStatus().find(A=>A.name===t),w=String(f?.agentId??"").trim();if(!w)throw new te(`local agent "${t}" has no agent_id registered`,"MISSING_AGENT_ID");await R.setAgentRelayCredential(t,{agentId:w,virtualKey:a.apiKey,anthropicBaseUrl:a.anthropicBaseUrl,openaiBaseUrl:a.openaiBaseUrl,model:i??a.model})},applyDisable:async t=>{await R.setAgentRelay(t,!1)}})}const W={listAgents:()=>r.getAgentsStatus(),enableManaged:(e,n,d)=>{if(!I)throw new oe(`agent "${e}" needs the MITM proxy for relay, but it is not running`,"PROXY_UNAVAILABLE");return I.setAgentRelayCredential(e,{agentId:n,virtualKey:d.virtualKey,anthropicBaseUrl:d.anthropicBaseUrl,openaiBaseUrl:d.openaiBaseUrl,model:d.model})},disableManaged:e=>{if(!I)throw new oe(`agent "${e}" needs the MITM proxy for relay, but it is not running`,"PROXY_UNAVAILABLE");return I.setAgentRelay(e,!1)},enableHermesProfile:(e,n)=>Oe({agentId:e,baseUrl:n.openaiBaseUrl??"",virtualKey:n.virtualKey,model:n.model??""}),disableHermesProfile:e=>He(e)};E.setRelayCredentialHandler({listHermesProfiles:()=>Be(),enable:async(e,n)=>{const{target:d,result:R}=await Ge(W,e,n);return{target:d,...R}},disable:async e=>{const{target:n,result:d}=await Me(W,e);return{target:n,...d}}});{const e=await z({label:"admin",port:V,envVar:"GRIX_ADMIN_PORT",cliFlag:"admin-port",start:n=>E.start(n)});e&&await se(e)}const le=k.join(l.dataDir,"admin-token"),ce=k.join(l.dataDir,"admin-port");Te(le,ae),Y(ce,String(V),"utf-8"),await _(l.daemonStatusFile,{state:"running",pid:process.pid,instance_id:D,updated_at:Date.now()}),process.send&&process.send("ready"),s.info("main","grix-connector ready")}Ve().catch(async o=>{const c=`Fatal: ${o instanceof Error?o.stack??o.message:String(o)}`;try{process.stderr.write(`${c}
|
|
42
|
+
`)}catch{}q(!1),s.error("main",c),N(o,"startup");const h=M(l.daemonLockFile);h?.pid===process.pid&&h.instance_id===D&&await _(l.daemonStatusFile,{state:"failed",pid:process.pid,instance_id:h.instance_id,updated_at:Date.now(),reason:"startup_error"}).catch(()=>{}),await G(),T(l.daemonLockFile).catch(()=>{}),C(),process.exit(1)});const We=new Set(["ECONNRESET","ECONNREFUSED","ETIMEDOUT","EPIPE","EAI_AGAIN","ENOTFOUND","EHOSTUNREACH","ENETUNREACH","EIO"]);function ie(o){return o instanceof Error&&"code"in o?We.has(o.code):!1}
|
package/dist/log.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import{createWriteStream as g,mkdirSync as l,existsSync as f}from"node:fs";import{join as
|
|
2
|
-
`)},error(o,r,...
|
|
1
|
+
import{createWriteStream as g,mkdirSync as l,existsSync as f}from"node:fs";import{join as i}from"node:path";import{homedir as m}from"node:os";const n=i(m(),".grix"),s={base:n,config:i(n,"config"),log:i(n,"log"),data:i(n,"data")};function S(){for(const o of Object.values(s))f(o)||l(o,{recursive:!0})}let a=null;function $(){const o=new Date().toISOString().slice(0,10),r=i(s.log,`grix-acp-${o}.log`);a=g(r,{flags:"a"})}function c(){return new Date().toISOString().slice(11,19)}const u={info(o,r,...t){const e=`${c()} [${o}] ${r}${t.length?" "+t.map(String).join(" "):""}`;console.log(e),a?.write(e+`
|
|
2
|
+
`)},error(o,r,...t){const e=`${c()} [${o}] ERROR ${r}${t.length?" "+t.map(String).join(" "):""}`;console.error(e),a?.write(e+`
|
|
3
3
|
`)}};export{s as GRIX_PATHS,S as ensureGrixDirs,$ as initLogger,u as log};
|