grix-connector 3.33.1 → 3.33.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapter/acp/acp-adapter.js +2 -2
- package/dist/adapter/agy/agy-adapter.js +5 -5
- package/dist/adapter/agy/model-list.js +2 -2
- package/dist/adapter/claude/claude-adapter.js +5 -5
- package/dist/adapter/claude/model-list.js +1 -1
- package/dist/adapter/codewhale/codewhale-adapter.js +2 -2
- package/dist/adapter/codewhale/model-list.js +2 -2
- package/dist/adapter/codex/codex-bridge.js +1 -1
- package/dist/adapter/cursor/cursor-adapter.js +7 -7
- package/dist/adapter/cursor/fetch-models.js +2 -2
- package/dist/adapter/cursor/resolve-command.js +1 -1
- package/dist/adapter/openhuman/openhuman-adapter.js +3 -3
- package/dist/assets/dsh-bridge/{grix-dsh-bridge-3.33.1.tgz → grix-dsh-bridge-3.33.3.tgz} +0 -0
- package/dist/assets/dsh-bridge/manifest.json +5 -5
- package/dist/audit/adapters/openclaw/openclaw-audit-adapter.js +1 -1
- package/dist/audit/adapters/pi/pi-transcript-source.js +1 -1
- package/dist/audit/core/transcript-fingerprint.js +1 -0
- package/dist/audit/producer/ipc-audit-producer.js +1 -1
- package/dist/bridge/adapter-pool.js +1 -1
- package/dist/bridge/bridge.js +9 -9
- package/dist/bridge/cli-install-offer.js +1 -0
- package/dist/bridge/respawn-manager.js +1 -1
- package/dist/core/files/file-serve.js +1 -1
- package/dist/core/installer/installer.js +6 -6
- package/dist/core/installer/npm-registry.js +3 -3
- package/dist/core/installer/prereq-installer.js +1 -1
- package/dist/core/proxy/hermes-profile-relay.js +1 -1
- package/dist/core/runtime/spawn.js +3 -3
- package/dist/core/upgrade/npm-upgrader.js +2 -2
- package/dist/core/util/cli-probe.js +2 -2
- package/dist/core/util/git-project-root.js +1 -1
- package/dist/default-skills/grix-egg/SKILL.md +2 -2
- package/dist/default-skills/message-send/SKILL.md +1 -1
- package/dist/default-skills/message-unsend/SKILL.md +1 -1
- package/dist/manager.js +1 -1
- package/dist/service/process-control.js +1 -1
- package/openclaw-plugin/audit-recorder-worker.js +170 -44
- package/openclaw-plugin/index.js +88 -14
- package/package.json +1 -1
|
@@ -17,10 +17,10 @@ The agent's own background turn kept the session busy past the wait limit, so th
|
|
|
17
17
|
`)}catch(s){r.warn("acp-adapter",`Failed to build kiro recovery context: ${I(s)}`);return}}async generateSummaryByKiroCli(e,t){const s=["chat","--no-interactive","--trust-tools=read,grep",["\u8BF7\u57FA\u4E8E\u4E0B\u9762\u5F15\u7528\u7684 Kiro \u539F\u59CB\u4F1A\u8BDD\u6587\u4EF6\u751F\u6210\u6458\u8981\uFF0C\u8F93\u51FA\u5FC5\u987B\u4E3A Markdown\u3002","\u8981\u6C42\uFF1A1) \u7B80\u660E\u51C6\u786E\uFF1B2) \u5305\u542B goals/constraints/decisions/open_items \u56DB\u90E8\u5206\uFF1B3) \u4E0D\u8981\u8F93\u51FA\u4EE3\u7801\u5757\u56F4\u680F\u3002",`\u4F1A\u8BDD\u6587\u4EF6\uFF1A@"${e}"`].join(`
|
|
18
18
|
`)],n=await this.runCommandCapture("kiro-cli",s,t,45e3);if(!n)return;const o=we(n).trim(),a=o.indexOf("> "),l=a>=0?o.slice(a+2).trim():o;if(l)return r.info("acp-adapter",`Kiro summary generated from ${e} (raw_chars=${o.length}, body_chars=${l.length})`),l.split(`
|
|
19
19
|
`).filter(d=>!d.includes("Credits:")&&!d.includes("Time:")).join(`
|
|
20
|
-
`).trim()}runCommandCapture(e,t,i,s){return new Promise((n,o)=>{const a=$(e,t,{cwd:i,stdio:["ignore","pipe","pipe"]});let l="",d="";const p=setTimeout(()=>{a.kill("SIGTERM"),o(new Error(`${e} timed out after ${s}ms`))},s);a.stdout.on("data",u=>{l+=String(u)}),a.stderr.on("data",u=>{d+=String(u)}),a.on("error",u=>{clearTimeout(p),o(u)}),a.on("close",u=>{if(clearTimeout(p),u===0){n(l||d);return}o(new Error(`${e} exited with code ${u}: ${d||l}`))})})}handleConnectFailure(e){r.error("acp-adapter",`ACP session creation failed: ${I(e)}`),this.acpClient?.removeAllListeners(),this.acpClient=null,this.emit("exit",null)}async startInternalApiAndMcp(){try{this.internalApi||(this.internalApi=new se,this.internalApi.setInvokeHandler(async(o,a,l,d)=>this.callbacks.agentInvoke(o,a,d)),await this.internalApi.start(0),r.info("acp-adapter",`Internal API started at ${this.internalApi.url}`)),this.internalApi.setMcpBridgeUpHandler(o=>this.onMcpBridgeUp(o));const e=this.config.command??"",t=e.includes("qwen")?h.join(v(),".qwen","skills"):e.includes("gemini")?h.join(v(),".gemini","skills"):e.includes("kiro")?h.join(v(),".kiro","skills"):this.isReasonix?h.join(v(),".reasonix","skills"):this.isKimi?h.join(X({...process.env,...this.config.env}),"skills"):this.auditAgentType==="copilot"||/^copilot(?:\.cmd)?$/i.test(h.basename(e).toLowerCase())||/^gh(?:\.exe)?$/i.test(h.basename(e).toLowerCase())&&(this.config.args??[]).includes("copilot")?h.join(v(),".copilot","skills"):this.auditAgentType==="hermes"?h.join(this.config.env?.HERMES_HOME?.trim()||J({...process.env,...this.config.env},this.nativeProviderScope),"skills"):void 0;if(t){const o=V(t);o.length>0&&r.info("acp-adapter",`Synced connector skills to ${t}: [${o.join(", ")}]`)}const i=h.resolve(_,"../../mcp/mcp-bridge-server.js"),s=this.internalApi.mcpBridgeWsUrl,n=h.resolve(_,"../../mcp/acp-mcp-server.js");return[{name:"grix-app-bridge",command:process.execPath,args:[i,"--ws-url",s],...!this.isReasonix&&{env:{GRIX_CONNECTOR_MCP_BRIDGE_WS:s}}},{name:"grix-connector-tools",command:process.execPath,args:[n,"--api-url",this.internalApi.url],...!this.isReasonix&&{env:{}}}]}catch(e){r.error("acp-adapter",`Failed to start MCP tools: ${e}`);return}}onMcpBridgeUp(e){this.callbacks.sendMcpFrame?.(e)}deliverMcpFrameToAgent(e){this.internalApi?.sendMcpFrameToBridge(e)}cleanup(){this.flushOrphanBuffer(),this.activeRun,this.pendingApprovals.clear(),this.pendingQuestions.clear(),this.pendingAutoCompact=!1,this.rejectDeferredEvents("adapter cleaned up"),this.cancelAllDeferredTimers(),this.sessionConnected=!1,this.acpClient&&(this.acpClient.clearSettleTimer(),this.acpClient.removeAllListeners(),this.acpClient=null),this.agentProcess&&(this.agentProcess.removeAllListeners(),this.agentProcess=null),this.baselineGroupPids=[],this.terminalAuthChild&&(this.terminalAuthChild.kill("SIGKILL"),this.terminalAuthChild=null)}async handleAuthRequired(e){r.info("acp-adapter",`Auth required, methods: ${e.authMethods.map(s=>s.id).join(", ")}`);const t=e.authMethods.find(s=>/oauth|browser/i.test(s.id))??e.authMethods[0];if(!t)throw e;const i=this.currentAibotSessionId??"";this.callbacks.sendAuthNotification(i,`Authentication required (${t.id}). Initiating auth flow...`);for(const[s,n]of this.sessionBindings)this.callbacks.sendUpdateBindingCard(s,"failed",n);if(t.type==="terminal")await this.runTerminalAuth(t,i);else{const s=await this.captureAuthUrl();s&&this.callbacks.sendAuthNotification(i,`Please open this URL to authenticate:
|
|
20
|
+
`).trim()}runCommandCapture(e,t,i,s){return new Promise((n,o)=>{const a=$(e,t,{cwd:i,stdio:["ignore","pipe","pipe"],windowsHide:!0});let l="",d="";const p=setTimeout(()=>{a.kill("SIGTERM"),o(new Error(`${e} timed out after ${s}ms`))},s);a.stdout.on("data",u=>{l+=String(u)}),a.stderr.on("data",u=>{d+=String(u)}),a.on("error",u=>{clearTimeout(p),o(u)}),a.on("close",u=>{if(clearTimeout(p),u===0){n(l||d);return}o(new Error(`${e} exited with code ${u}: ${d||l}`))})})}handleConnectFailure(e){r.error("acp-adapter",`ACP session creation failed: ${I(e)}`),this.acpClient?.removeAllListeners(),this.acpClient=null,this.emit("exit",null)}async startInternalApiAndMcp(){try{this.internalApi||(this.internalApi=new se,this.internalApi.setInvokeHandler(async(o,a,l,d)=>this.callbacks.agentInvoke(o,a,d)),await this.internalApi.start(0),r.info("acp-adapter",`Internal API started at ${this.internalApi.url}`)),this.internalApi.setMcpBridgeUpHandler(o=>this.onMcpBridgeUp(o));const e=this.config.command??"",t=e.includes("qwen")?h.join(v(),".qwen","skills"):e.includes("gemini")?h.join(v(),".gemini","skills"):e.includes("kiro")?h.join(v(),".kiro","skills"):this.isReasonix?h.join(v(),".reasonix","skills"):this.isKimi?h.join(X({...process.env,...this.config.env}),"skills"):this.auditAgentType==="copilot"||/^copilot(?:\.cmd)?$/i.test(h.basename(e).toLowerCase())||/^gh(?:\.exe)?$/i.test(h.basename(e).toLowerCase())&&(this.config.args??[]).includes("copilot")?h.join(v(),".copilot","skills"):this.auditAgentType==="hermes"?h.join(this.config.env?.HERMES_HOME?.trim()||J({...process.env,...this.config.env},this.nativeProviderScope),"skills"):void 0;if(t){const o=V(t);o.length>0&&r.info("acp-adapter",`Synced connector skills to ${t}: [${o.join(", ")}]`)}const i=h.resolve(_,"../../mcp/mcp-bridge-server.js"),s=this.internalApi.mcpBridgeWsUrl,n=h.resolve(_,"../../mcp/acp-mcp-server.js");return[{name:"grix-app-bridge",command:process.execPath,args:[i,"--ws-url",s],...!this.isReasonix&&{env:{GRIX_CONNECTOR_MCP_BRIDGE_WS:s}}},{name:"grix-connector-tools",command:process.execPath,args:[n,"--api-url",this.internalApi.url],...!this.isReasonix&&{env:{}}}]}catch(e){r.error("acp-adapter",`Failed to start MCP tools: ${e}`);return}}onMcpBridgeUp(e){this.callbacks.sendMcpFrame?.(e)}deliverMcpFrameToAgent(e){this.internalApi?.sendMcpFrameToBridge(e)}cleanup(){this.flushOrphanBuffer(),this.activeRun,this.pendingApprovals.clear(),this.pendingQuestions.clear(),this.pendingAutoCompact=!1,this.rejectDeferredEvents("adapter cleaned up"),this.cancelAllDeferredTimers(),this.sessionConnected=!1,this.acpClient&&(this.acpClient.clearSettleTimer(),this.acpClient.removeAllListeners(),this.acpClient=null),this.agentProcess&&(this.agentProcess.removeAllListeners(),this.agentProcess=null),this.baselineGroupPids=[],this.terminalAuthChild&&(this.terminalAuthChild.kill("SIGKILL"),this.terminalAuthChild=null)}async handleAuthRequired(e){r.info("acp-adapter",`Auth required, methods: ${e.authMethods.map(s=>s.id).join(", ")}`);const t=e.authMethods.find(s=>/oauth|browser/i.test(s.id))??e.authMethods[0];if(!t)throw e;const i=this.currentAibotSessionId??"";this.callbacks.sendAuthNotification(i,`Authentication required (${t.id}). Initiating auth flow...`);for(const[s,n]of this.sessionBindings)this.callbacks.sendUpdateBindingCard(s,"failed",n);if(t.type==="terminal")await this.runTerminalAuth(t,i);else{const s=await this.captureAuthUrl();s&&this.callbacks.sendAuthNotification(i,`Please open this URL to authenticate:
|
|
21
21
|
${s}
|
|
22
22
|
|
|
23
|
-
Waiting for authentication to complete...`)}try{await this.acpClient.authenticate(t.id),r.info("acp-adapter","Authentication successful"),this.callbacks.sendAuthNotification(i,"Authentication successful. Resuming...");const s=this.acpMcpTools?await this.startInternalApiAndMcp():void 0;await this.acpClient.connect({transport:this.agentProcess.transport,initialMode:this.acpInitialMode,initialModel:this.acpInitialModel,cwd:this.resolveCwd(),mcpServers:s}),r.info("acp-adapter",`ACP session ready after auth: ${this.acpClient.sessionId}`),this.captureBaselineGroupPids(),this.emit("acpSessionReady",this.acpClient.sessionId);for(const[n,o]of this.sessionBindings)this.callbacks.sendUpdateBindingCard(n,"ready",o,this.buildToolbarContext("binding_ready",o))}catch(s){throw r.error("acp-adapter",`Auth retry failed: ${s}`),s}}runTerminalAuth(e,t){return new Promise((i,s)=>{const n=$(this.config.command,e.args??[],{env:{...process.env,...this.config.env??{}},stdio:["ignore","pipe","pipe"]});this.terminalAuthChild=n;const o=/https?:\/\/[^\s"')\]]+/;let a=!1,l=!1;const d=u=>{if(a)return;const y=u.toString().replace(/\x1b\[[0-9;]*m/g,"").match(o);y&&(a=!0,r.info("acp-adapter",`Terminal auth URL captured: ${y[0]}`),this.callbacks.sendAuthNotification(t,`Please open this URL on any device to authenticate:
|
|
23
|
+
Waiting for authentication to complete...`)}try{await this.acpClient.authenticate(t.id),r.info("acp-adapter","Authentication successful"),this.callbacks.sendAuthNotification(i,"Authentication successful. Resuming...");const s=this.acpMcpTools?await this.startInternalApiAndMcp():void 0;await this.acpClient.connect({transport:this.agentProcess.transport,initialMode:this.acpInitialMode,initialModel:this.acpInitialModel,cwd:this.resolveCwd(),mcpServers:s}),r.info("acp-adapter",`ACP session ready after auth: ${this.acpClient.sessionId}`),this.captureBaselineGroupPids(),this.emit("acpSessionReady",this.acpClient.sessionId);for(const[n,o]of this.sessionBindings)this.callbacks.sendUpdateBindingCard(n,"ready",o,this.buildToolbarContext("binding_ready",o))}catch(s){throw r.error("acp-adapter",`Auth retry failed: ${s}`),s}}runTerminalAuth(e,t){return new Promise((i,s)=>{const n=$(this.config.command,e.args??[],{env:{...process.env,...this.config.env??{}},stdio:["ignore","pipe","pipe"],windowsHide:!0});this.terminalAuthChild=n;const o=/https?:\/\/[^\s"')\]]+/;let a=!1,l=!1;const d=u=>{if(a)return;const y=u.toString().replace(/\x1b\[[0-9;]*m/g,"").match(o);y&&(a=!0,r.info("acp-adapter",`Terminal auth URL captured: ${y[0]}`),this.callbacks.sendAuthNotification(t,`Please open this URL on any device to authenticate:
|
|
24
24
|
${y[0]}
|
|
25
25
|
|
|
26
26
|
Waiting for authentication to complete...`))};n.stdout?.on("data",d),n.stderr?.on("data",d);const p=setTimeout(()=>{l||(l=!0,n.kill("SIGKILL"),this.terminalAuthChild=null,s(new Error(`terminal auth timed out after ${j}ms`)))},j);n.on("error",u=>{l||(l=!0,clearTimeout(p),this.terminalAuthChild=null,s(u))}),n.on("exit",u=>{l||(l=!0,clearTimeout(p),this.terminalAuthChild=null,u===0?i():s(new Error(`terminal auth process exited with code ${u}`)))})})}captureAuthUrl(){return new Promise(e=>{if(!this.agentProcess){e(null);return}const t=/https?:\/\/[^\s"')\]]+/;let i=!1;const s=n=>{if(i)return;const a=n.toString().replace(/\x1b\[[0-9;]*m/g,"").match(t);a&&(i=!0,this.agentProcess.removeListener("stderr",s),e(a[0]))};this.agentProcess.on("stderr",s),setTimeout(()=>{i||(i=!0,this.agentProcess.removeListener("stderr",s),e(null))},3e4)})}handleAcpEvent(e){try{r.info("acp-adapter","incoming acp event",{eventType:e.type,sessionId:this.activeRun?.sessionId??null,eventKeys:Object.keys(e),rawEvent:M(e,2e3)})}catch{}if(e.type===f.PermissionRequest){this.activeRun&&this.resetIdleTimer(this.activeRun),this.handlePermissionRequest(e);return}if(e.type===f.ContextWindowUpdate){e.contextWindow&&this.callbacks.onContextWindowUpdated?.(e.contextWindow),this.compacting?this.finishCompaction("context-window-update"):e.contextWindow&&this.maybeScheduleAutoCompact(e.contextWindow);return}const t=this.activeRun;if(!t){this.handleOrphanAcpEvent(e);return}if(t.busyWaiting){this.handleOrphanAcpEvent(e);return}this.captureAuditEvent(e),t.responded||(t.responded=!0);let i=!1;switch(e.type){case f.Text:{if(t.lastToolUse=null,t.lastToolResult=null,e.content){i=!0;const s=t.quotedStream.consume(e.content);s.quotedMessageId&&(t.quotedMessageId=s.quotedMessageId),s.deltaContent&&this.appendToStream(t,s.deltaContent)}break}case f.ToolUse:{if(i=!0,t.awaitingToolResult=!0,t.lastToolResult=null,e.toolName){if(e.toolCallId&&(t.toolNamesById.set(e.toolCallId,e.toolName),t.sentToolUseIds.has(e.toolCallId)))break;const s=e.toolInput??"",n=A(s);if(t.lastToolUse?.toolName===e.toolName&&A(t.lastToolUse.toolInput)===n)break;t.lastToolUse={toolName:e.toolName,toolInput:s},e.toolCallId&&t.sentToolUseIds.add(e.toolCallId);const o=this.trimToolWirePayload?E(s):s;this.emitRawEventEnvelope(t,{type:"tool_use",payload:{tool_name:e.toolName,tool_input:o}})||this.callbacks.sendToolUse(t.eventId,t.sessionId,e.toolName,o)}break}case f.ToolResult:{t.awaitingToolResult=!1;{const s=e.toolCallId||e.toolName||"";t.lastToolProgress.delete(s),t.lastToolProgressSentAt.delete(s)}if(t.lastToolUse=null,e.content){i=!0;const s=x(t,e),n=e.content,o=A(n);if(t.lastToolResult?.toolName===s&&A(t.lastToolResult.content)===o)break;if(t.lastToolResult={toolName:s,content:n},!this.trimToolWirePayload||e.toolStatus==="failed"){const a=this.trimToolWirePayload?"(failed)":n;this.emitRawEventEnvelope(t,{type:"tool_result",payload:{tool_name:s,content:a}})||this.callbacks.sendToolResult(t.eventId,t.sessionId,s,a)}}break}case f.ToolProgress:{if(i=!0,e.content){const s=e.toolCallId||e.toolName||"";if(t.lastToolProgress.get(s)===e.content||(t.lastToolProgress.set(s,e.content),this.trimToolWirePayload))break;const n=Date.now(),o=t.lastToolProgressSentAt.get(s);if(o!==void 0&&n-o<le)break;t.lastToolProgressSentAt.set(s,n);const a=x(t,e);this.emitRawEventEnvelope(t,{type:"tool_progress",payload:{tool_name:a,content:e.content}})||this.callbacks.sendToolResult(t.eventId,t.sessionId,a,e.content)}break}case f.Thinking:{t.lastToolUse=null,t.lastToolResult=null,e.content&&(i=!0,this.emitRawEventEnvelope(t,{type:"thinking",payload:{content:e.content}})||this.callbacks.sendThinking(t.eventId,t.sessionId,e.content));break}case f.Error:{t.lastToolUse=null,t.lastToolResult=null;const s=String(e.error??"unknown error");this.emitRawEventEnvelope(t,{type:"error",payload:{message:s}}),r.error("acp-adapter",`ACP error: ${s}`),this.callbacks.sendRunError(t.eventId,t.sessionId,s);break}case f.Result:{if(e.synthetic&&this.shouldDeferSyntheticResultForDetachedKimiTask(t,e)){r.info("acp-adapter",`Synthetic ACP Result deferred: kimi detached task still running event=${t.eventId} session=${t.sessionId}`),this.acpClient?.deferSettledResult();return}this.emitRawEventEnvelope(t,{type:"result",payload:{done:e.done??!0}});const s=t.quotedStream.flush();s.deltaContent&&this.appendToStream(t,s.deltaContent),s.quotedMessageId&&(t.quotedMessageId=s.quotedMessageId),this.flushStream(),this.finishRun("responded");break}}i&&(t.lastProgressAt=Date.now(),t.idleNoProgressCount=0),this.resetIdleTimer(t)}handlePermissionRequest(e){const t=e.permissionRequest;if(r.info("acp-adapter","permission_request received",{sessionId:this.activeRun?.sessionId,eventId:this.activeRun?.eventId,requestId:e.requestId??null,toolCallId:t?.toolCallId??null,toolName:t?.toolName??null,toolTitle:t?.toolTitle??null,optionsCount:Array.isArray(t?.options)?t.options.length:0,hasActiveRun:this.activeRun!==null,approvalMode:this.approvalMode,rawTransport:this.rawTransport,hasRawEventEnvelopeCb:typeof this.callbacks.sendRawEventEnvelope=="function",hasSendPermissionCardCb:typeof this.callbacks.sendPermissionCard=="function",rawPr:M(t,3e3)}),!t||!e.requestId||!this.acpClient){r.warn("acp-adapter","permission_request dropped: missing prerequisite",{hasPr:t!=null,hasRequestId:!!e.requestId,hasAcpClient:!!this.acpClient});return}const i=this.activeRun,s=de(t);if(s&&i&&this.callbacks.sendAgentQuestionCard){i.idleTimer&&(clearTimeout(i.idleTimer),i.idleTimer=null),this.pendingQuestions.set(e.requestId,{options:[...t.options]}),this.callbacks.sendAgentQuestionCard(i.eventId,i.sessionId,{request_id:e.requestId,mode:"form",questions:[{header:s.prompt,prompt:s.prompt,options:s.answerOptions.map(a=>a.name)}]}),r.info("acp-adapter","AskUserQuestion forwarded via agent_question card",{sessionId:i.sessionId,eventId:i.eventId,requestId:e.requestId,optionsCount:s.answerOptions.length});return}if(this.approvalMode==="yolo"||this.approvalMode==="autoEdit"){r.info("acp-adapter",`Auto-approving (${this.approvalMode}): ${t.toolName}`),this.acpClient.respondPermission(t.requestId,{behavior:"allow"}).catch(()=>{});return}const n=t.toolCallId,o=ce(t.rawParams,e.toolInput,t.toolTitle);this.pendingApprovals.set(n,e.requestId),i?(i.idleTimer&&(clearTimeout(i.idleTimer),i.idleTimer=null),this.emitRawEventEnvelope(i,{type:"permission_request",payload:{request_id:t.requestId,tool_call_id:n,tool_name:t.toolName,tool_title:t.toolTitle,...o?{tool_input:o}:{},options:t.options}})?r.info("acp-adapter","permission_request forwarded via rawEventEnvelope",{sessionId:i.sessionId,eventId:i.eventId,toolCallId:n,toolName:t.toolName}):(r.info("acp-adapter","permission_request falling back to sendPermissionCard",{sessionId:i.sessionId,eventId:i.eventId,toolCallId:n,toolName:t.toolName}),this.callbacks.sendPermissionCard({eventId:i.eventId,sessionId:i.sessionId,toolCallId:n,toolName:t.toolName,toolTitle:t.toolTitle,toolInput:o,options:t.options}))):(r.info("acp-adapter",`Permission request without active run, auto-approving: ${t.toolName}`),this.acpClient.respondPermission(e.requestId,{behavior:"allow"}),this.pendingApprovals.delete(n))}emitRawEventEnvelope(e,t){return!e||!this.rawTransport||!this.callbacks.sendRawEventEnvelope?!1:(this.callbacks.sendRawEventEnvelope(e.eventId,e.sessionId,{type:t.type,payload:t.payload,seq:++this.rawEventSeq,at:new Date().toISOString()}),!0)}resetIdleTimer(e,t){e.idleTimer&&clearTimeout(e.idleTimer);const i=t??(e.awaitingToolResult?Ie:ve),s=e.lastProgressAt+S-Date.now(),n=Math.max(0,Math.min(i,s));e.idleTimer=setTimeout(()=>this.onIdleTimeout(e),n)}onIdleTimeout(e){if(this.activeRun?.eventId!==e.eventId)return;const t=Date.now()-e.lastProgressAt;if(t>=S){r.error("acp-adapter",`Turn inactive ${Math.round(t/1e3)}s \u2265 cap ${Math.round(S/1e3)}s, forcing timeout: ${e.eventId}`),this.finishRun("failed",`turn inactivity timeout after ${Math.round(t/1e3)}s with no ACP activity`),this.declareStuck();return}const i=this.acpClient;i?.isAlive?i.ping(5e3).then(async s=>{if(this.activeRun?.eventId!==e.eventId)return;if(!s){await this.handlePingFailure(e,i);return}if(e.consecutivePingFailures=0,e.awaitingToolResult){const d=e.lastIdleCheckAt,p=await this.checkSessionFileActivity(i.sessionId,d);if(this.activeRun?.eventId!==e.eventId)return;e.lastIdleCheckAt=Date.now(),p?(e.lastProgressAt=Date.now(),r.info("acp-adapter",`Idle timer: tool running, ping ok, session file active, refreshing watchdog: ${e.eventId}`)):r.info("acp-adapter",`Idle timer: tool running, ping ok, continuing until inactivity cap: ${e.eventId}`),e.idleNoProgressCount=0,this.resetIdleTimer(e);return}const n=e.lastIdleCheckAt,o=e.lastProgressAt>n,a=o?!1:await this.checkSessionFileActivity(i.sessionId,n);if(this.activeRun?.eventId!==e.eventId)return;const l=o||a;e.lastIdleCheckAt=Date.now(),l?(a&&(r.info("acp-adapter",`Idle timer: no stream progress but session file active, continuing: ${e.eventId}`),e.lastProgressAt=Date.now()),e.idleNoProgressCount=0,this.resetIdleTimer(e)):(e.idleNoProgressCount++,r.info("acp-adapter",`Idle timer: ping ok, no stream progress (cycle=${e.idleNoProgressCount}), extending until inactivity cap (${Math.round(S/6e4)}min): ${e.eventId}`),this.resetIdleTimer(e))}).catch(()=>{this.activeRun?.eventId===e.eventId&&this.handlePingFailure(e,i)}):(r.error("acp-adapter",`Agent idle (no client), declaring stuck: ${e.eventId}`),this.finishRun("failed","agent unreachable"),this.declareStuck())}async handlePingFailure(e,t){const i=e.lastIdleCheckAt,s=e.lastProgressAt>i,n=s?!1:await this.checkSessionFileActivity(t.sessionId,i);if(this.activeRun?.eventId===e.eventId){if(s||n||e.awaitingToolResult){const o=s?"stream progress":n?"session file activity":"tool in flight";r.info("acp-adapter",`Idle timer: ping failed but ${o}, continuing: ${e.eventId}`),e.consecutivePingFailures=0,e.lastIdleCheckAt=Date.now(),n&&(e.lastProgressAt=Date.now()),this.resetIdleTimer(e);return}if(e.consecutivePingFailures++,e.lastIdleCheckAt=Date.now(),e.consecutivePingFailures<D){r.warn("acp-adapter",`Idle timer: ping failed (${e.consecutivePingFailures}/${D}), rechecking in ${B/1e3}s: ${e.eventId}`),this.resetIdleTimer(e,B);return}r.error("acp-adapter",`Ping failed ${e.consecutivePingFailures}x with no activity evidence, declaring stuck: ${e.eventId}`),this.finishRun("failed","agent unreachable"),this.declareStuck()}}async checkSessionFileActivity(e,t){if(!e)return!1;try{return this.isKimi?await he(e,t):this.config.command==="kiro-cli"?await ue(e,t):!1}catch{return!1}}shouldDeferSyntheticResultForDetachedKimiTask(e,t){if(!this.isKimi)return!1;try{return pe(t.sessionId||this.acpClient?.sessionId||e.sessionId)}catch{return!1}}declareStuck(){this.acpClient&&(this.acpClient.clearSettleTimer(),this.acpClient.removeAllListeners(),this.acpClient=null),this.agentProcess&&(this.agentProcess.close().catch(()=>{}),this.agentProcess=null),this.emit("stuck")}appendToStream(e,t){e.buffer+=t,e.flushTimer||(e.flushTimer=setTimeout(()=>this.flushStream(),ge))}emitSegmentedStream(e,t){if(!t)return;const i=e.markdownSegmenter.push(t);for(const s of i)s.text&&(this.callbacks.sendStreamChunk(e.eventId,e.sessionId,s.text,++e.chunkSeq,s.closeAfter===!0,e.currentClientMsgId,e.quotedMessageId),s.closeAfter&&(e.currentSegmentIndex+=1,e.currentClientMsgId=`${e.clientMsgIdBase}_seg_${e.currentSegmentIndex}`,e.chunkSeq=0,r.info("acp-adapter",`stream segment rollover event=${e.eventId} segment=${e.currentSegmentIndex} reason=${s.reason??"threshold"}`)))}handleOrphanAcpEvent(e){if(!(!this.sessionConnected||this.compacting||Date.now()<this.orphanSuppressedUntil)){if(e.type===f.Text&&e.content){this.orphanSessionId===null&&(this.orphanSessionId=this.currentAibotSessionId??"");const t=this.orphanQuotedStream.consume(e.content);t.deltaContent&&(this.orphanBuffer+=t.deltaContent),this.orphanBuffer.length>=Se?this.flushOrphanBuffer():this.scheduleOrphanFlush();return}e.type===f.Result&&this.flushOrphanBuffer()}}scheduleOrphanFlush(){this.orphanFlushTimer&&clearTimeout(this.orphanFlushTimer),this.orphanFlushTimer=setTimeout(()=>{this.orphanFlushTimer=null,this.flushOrphanBuffer()},Ae),this.orphanFlushTimer.unref?.()}flushOrphanBuffer(){if(this.orphanFlushTimer&&(clearTimeout(this.orphanFlushTimer),this.orphanFlushTimer=null),this.orphanSessionId===null&&!this.orphanBuffer)return;const e=this.orphanQuotedStream.flush();e.deltaContent&&(this.orphanBuffer+=e.deltaContent),this.orphanQuotedStream=new C;const t=this.orphanSessionId||this.currentAibotSessionId;this.orphanSessionId=null;const i=this.orphanBuffer.trim();if(this.orphanBuffer="",!!i){if(!t||!this.callbacks.sendAgentMessage){r.warn("acp-adapter",`orphan agent output dropped (${t?"no sendAgentMessage callback":"no session id"}) chars=${i.length}`);return}r.info("acp-adapter",`delivering orphan agent output session=${t} chars=${i.length}`);for(let s=0;s<i.length;s+=L)this.callbacks.sendAgentMessage(t,i.slice(s,s+L))}}suppressOrphanOutput(e){this.orphanSuppressedUntil=Date.now()+e,this.orphanFlushTimer&&(clearTimeout(this.orphanFlushTimer),this.orphanFlushTimer=null),this.orphanBuffer="",this.orphanQuotedStream=new C,this.orphanSessionId=null}flushStream(){const e=this.activeRun;if(!e||!e.buffer)return;e.flushTimer&&(clearTimeout(e.flushTimer),e.flushTimer=null);const t=e.buffer;e.buffer="",this.emitSegmentedStream(e,t)}finishRun(e,t){const i=this.activeRun;if(!i)return;this.snapshotAuditBoundary(i.eventId),this.activeRun=null,this.acpClient?.clearSettleTimer(),this.emit("eventDone",i.eventId),this.clearRunTimers(i);const s=i.quotedStream.flush();s.deltaContent&&(i.buffer+=s.deltaContent),s.quotedMessageId&&(i.quotedMessageId=s.quotedMessageId),t&&(e==="failed"?(r.error("acp-adapter",`finishRun failed: ${t} event=${i.eventId}`),i.buffer+=`
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import{spawn as $}from"node:child_process";import{randomUUID as k}from"node:crypto";import{EventEmitter as I}from"node:events";import{existsSync as
|
|
2
|
-
`,"utf8"),o.info(a,`MCP config injected into ${e}`);const n=p(
|
|
1
|
+
import{spawn as $}from"node:child_process";import{randomUUID as k}from"node:crypto";import{EventEmitter as I}from"node:events";import{existsSync as S,readFileSync as _,readdirSync as b,statSync as w,mkdirSync as x,writeFileSync as C,realpathSync as L}from"node:fs";import{homedir as f}from"node:os";import{join as p,dirname as R,resolve as T}from"node:path";import{fileURLToPath as M}from"node:url";import{log as o}from"../../core/log/index.js";import{killProcessGroup as m}from"../../core/runtime/spawn.js";import{InternalApiServer as O}from"../../core/mcp/internal-api-server.js";import{resolveMcpServerName as j,pruneLegacyMcpServerEntries as G}from"../../core/mcp/server-name.js";import{syncDefaultSkillsToDir as D}from"../../default-skills/index.js";import{buildSimpleProbeReport as N}from"../shared/probe-util.js";import{getAgyModelCatalog as F,resolveAgyModelFlag as B}from"./model-list.js";const a="agy-adapter";function Q(v){try{return process.kill(v,0),!0}catch(t){return t.code==="EPERM"}}function V(v){try{return L(v)}catch{return v}}const y=p(f(),".gemini","antigravity-cli","log"),A=p(f(),".gemini","antigravity-cli","cache","last_conversations.json"),q=["\u4F60\u63A5\u5165\u7684\u662F Grix \u804A\u5929\u3002\u7528\u6237\u6D88\u606F\u88AB\u5305\u5728 <channel ...> \u6807\u7B7E\u91CC\uFF0C\u6807\u7B7E\u5C5E\u6027\u643A\u5E26\u672C\u8F6E\u4E0A\u4E0B\u6587\uFF1A","chat_id\uFF08\u5F53\u524D\u4F1A\u8BDD\uFF09\u3001agent_id\uFF08\u5F53\u524D Agent\uFF09\u3001event_id\uFF08\u672C\u8F6E\u4E8B\u4EF6\uFF09\u3001message_id\uFF08\u7528\u6237\u8FD9\u6761\u6D88\u606F\u7684 ID\uFF09\u3001user_id\uFF08\u53D1\u9001\u8005\uFF09\u3002","\u91CD\u8981\uFF1A\u4F60\u7684\u56DE\u590D\u5FC5\u987B\u8C03\u7528 grix \u7684 `grix_message_send` \u5DE5\u5177\u53D1\u9001\uFF08sessionId \u53D6\u81EA channel \u7684 chat_id\uFF0Ccontent \u4E3A\u4F60\u7684\u56DE\u590D\u5185\u5BB9\uFF09\uFF0C","\u4E0D\u8981\u53EA\u628A\u7B54\u6848\u6253\u5370\u5230\u6807\u51C6\u8F93\u51FA\u2014\u2014\u53EA\u6709\u8C03\u7528\u5DE5\u5177\u624D\u4F1A\u628A\u6D88\u606F\u771F\u6B63\u53D1\u7ED9\u7528\u6237\u3002","grix_message_send \u4F1A\u8FD4\u56DE\u4F60\u521A\u53D1\u51FA\u6D88\u606F\u7684 msg_id\u3002\u9700\u8981\u65F6\u4F60\u53EF\u4EE5\u7528\u8FD9\u4E2A ID \u505A\u540E\u7EED\u64CD\u4F5C\uFF0C\u4F8B\u5982\u8C03\u7528 grix_message_unsend\uFF08\u4F20 msgId\uFF09\u64A4\u56DE\u67D0\u6761\u6D88\u606F\u3002"].join(""),Y=new Set(["send_msg","grix_reply"]),U=new Set(["send_msg","grix_reply","delete_msg","session_send","grix_complete","grix_composing","grix_event_ack"]);class at extends I{type="agy";config;callbacks;runtimeResolver;alive=!1;stopped=!1;internalApi=null;activeEventId=null;activeEventSessionId=null;activeProcess=null;completedEventIds=new Set;activeEventSentViaTool=!1;sessionConversationMap=new Map;conversationOutputCache=new Map;eventQueue=[];constructor(t,e,s){super(),this.config=t,this.callbacks=e,this.runtimeResolver=s}async start(){this.alive||(this.alive=!0,this.stopped=!1,await this.startInternalApiAndInjectMcp(),o.info(a,"AgyAdapter started"))}async startInternalApiAndInjectMcp(){try{this.internalApi=new O,this.internalApi.setInvokeHandler(async(r,l,h,u)=>this.handleToolInvoke(r,l,u)),await this.internalApi.start(0),o.info(a,`Internal API started at ${this.internalApi.url}`);const t=this.getMcpConfig(),e=p(f(),".gemini","config","mcp_config.json");x(R(e),{recursive:!0});let s={};try{S(e)&&(s=JSON.parse(_(e,"utf8")))}catch{}const i=s.mcpServers&&typeof s.mcpServers=="object"?s.mcpServers:{};G(i,this.internalApi.url),i[t.name]={command:t.command,args:t.args},s.mcpServers=i,C(e,`${JSON.stringify(s,null,2)}
|
|
2
|
+
`,"utf8"),o.info(a,`MCP config injected into ${e}`);const n=p(f(),".gemini","skills"),c=D(n);c.length>0&&o.info(a,`Synced connector skills to ${n}: [${c.join(", ")}]`)}catch(t){o.warn(a,`Failed to start MCP tools (non-fatal): ${t instanceof Error?t.message:String(t)}`)}}async handleToolInvoke(t,e,s){let i=e;U.has(t)&&this.activeEventSessionId&&(i.session_id==null||i.session_id==="")&&(i={...i,session_id:this.activeEventSessionId});const n=await this.callbacks.agentInvoke(t,i,s);return Y.has(t)&&this.activeEventId&&(this.activeEventSentViaTool=!0),n}async stop(){if(!this.stopped){if(this.stopped=!0,this.alive=!1,this.activeProcess){try{m(this.activeProcess,"SIGKILL")}catch{}this.activeProcess=null}if(this.activeEventId&&this.activeEventSessionId)try{this.callbacks.sendEventResult(this.activeEventId,"canceled","adapter stopped")}catch{}if(this.activeEventId=null,this.activeEventSessionId=null,this.internalApi){try{await this.internalApi.stop()}catch{}this.internalApi=null}this.emit("exit",0),o.info(a,"AgyAdapter stopped")}}isAlive(){return this.alive}async createSession(t){return t.cwd??k()}async resumeSession(t,e){}async destroySession(t){}sendPrompt(t){const e=new I;return e.adapterSessionId=t.adapterSessionId,e.cancel=async()=>{e.emit("done",{status:"canceled"})},e}async cancel(t){}cancelCurrentRun(){if(!this.activeEventId||!this.activeProcess)return;const t=this.activeEventId;o.info(a,`cancelCurrentRun: killing process group for event ${t}`);try{m(this.activeProcess,"SIGKILL")}catch{}this.activeProcess=null,this.callbacks.sendEventResult(t,"canceled","restarted by user"),this.clearActiveEvent(t),this.drainQueue()}deliverInboundEvent(t){if(!this.stopped){if(this.completedEventIds.has(t.event_id)){o.debug(a,`Skipping duplicate event: ${t.event_id}`);return}if(this.callbacks.sendEventAck(t.event_id,t.session_id),this.activeEventId){o.info(a,`Queuing event ${t.event_id} (active: ${this.activeEventId})`),this.eventQueue.push(t);return}this.processEvent(t)}}deliverStopEvent(t,e){if(this.activeEventId===t&&this.activeProcess){o.info(a,`Stopping event ${t}`);try{m(this.activeProcess,"SIGKILL")}catch{}this.activeProcess=null,this.callbacks.sendEventResult(t,"canceled","stopped by user"),this.clearActiveEvent(t),this.drainQueue()}}setPermissionHandler(){}async ping(t){if(this.stopped||!this.alive||!this.internalApi)return!1;const e=this.activeProcess;if(!e)return!0;if(!e.pid||!Q(e.pid))return!1;const s=!!(e.stdin&&!e.stdin.destroyed),i=!!(e.stdout&&!e.stdout.destroyed),n=!!(e.stderr&&!e.stderr.destroyed);return s&&i&&n}getStatus(){return{alive:this.alive,busy:this.activeEventId!==null,sessions:this.sessionConversationMap.size}}async probe(t){const e=this.getStatus();return N(this.config.command||"agy",{alive:e.alive,busy:e.busy,started:e.alive},t)}getActiveEventIds(){return this.activeEventId?[this.activeEventId]:[]}clearActiveEventForShutdown(){if(this.activeProcess)try{m(this.activeProcess,"SIGKILL")}catch{}this.activeEventId=null,this.activeEventSessionId=null,this.activeProcess=null,this.activeEventSentViaTool=!1}getMcpConfig(){if(!this.internalApi)return null;const t=T(M(import.meta.url),"../../../mcp/acp-mcp-server.js");return{name:j(this.callbacks.getAgentId?.()),command:process.execPath,args:[t,"--api-url",this.internalApi.url]}}processEvent(t){const{event_id:e,session_id:s}=t,i=this.runtimeResolver(s);if(!i.cwd){o.warn(a,`No working directory for session ${s}, event should have been intercepted by bridge`),this.callbacks.forceCompleteInternalEvent(e,s);return}this.activeEventId=e,this.activeEventSessionId=s,this.activeEventSentViaTool=!1,this.emit("eventStarted",e,s),o.info(a,`Processing event ${e} for session ${s}`);const n=this.buildAgyPrompt(t);this.spawnAgyPrint(e,s,n,i)}buildAgyPrompt(t){const s=[["chat_id",t.session_id],["agent_id",this.callbacks.getAgentId?.()],["event_id",t.event_id],["message_id",t.msg_id],["user_id",t.sender_id],["quoted_message_id",t.quoted_message_id]].filter(([,l])=>l!=null&&l!=="").map(([l,h])=>`${l}="${h}"`).join(" "),i=this.renderContextBlock(t),n=i?`${i}
|
|
3
3
|
|
|
4
4
|
${t.content}`:t.content,c=`<channel source="grix-agy" ${s}>
|
|
5
5
|
${n}
|
|
6
|
-
</channel>`;return`${this.buildIdentityBlock()}${
|
|
6
|
+
</channel>`;return`${this.buildIdentityBlock()}${q}
|
|
7
7
|
|
|
8
8
|
${c}`}buildIdentityBlock(){const t=this.callbacks.getAgentProfile?.(),e=t?.agentName?.trim()??"",s=t?.introduction?.trim()??"";if(!e&&!s)return"";const i=["[system-identity]"];return e&&i.push(`\u4F60\u7684\u540D\u5B57\u662F"${e}"\u3002`),s&&i.push(`\u4F60\u7684\u81EA\u6211\u4ECB\u7ECD\uFF1A${s}`),i.push("\u8FD9\u662F\u5E26\u5916\u6307\u4EE4\uFF1B\u4E0D\u8981\u5728\u56DE\u590D\u4E2D\u590D\u8FF0\u6216\u5F15\u7528\u672C\u6BB5\u3002"),i.push("[/system-identity]"),`${i.join(`
|
|
9
9
|
`)}
|
|
10
10
|
|
|
11
11
|
`}renderContextBlock(t){const e=t.context_messages_json;if(!e)return"";let s;try{const r=JSON.parse(e);if(!Array.isArray(r))return"";s=r}catch{return""}const i=String(t.session_type??"")==="2",n=String(t.msg_id??""),c=[];for(const r of s){const l=String(r?.msg_id??"");if(l&&l===n)continue;const h=String(r?.content??"").trim();if(!h)continue;const u=String(r?.sender_id??"");if(h.startsWith("[\u5F15\u7528\u6D88\u606F]")){const d=h.slice(6).replace(/^\s*\n?/,"").trim();c.push(i&&u?`[\u5F15\u7528\u6D88\u606F] (\u6765\u81EA ${u})\uFF1A${d}`:`[\u5F15\u7528\u6D88\u606F]\uFF1A${d}`)}else c.push(i&&u?`[${u}]\uFF1A${h}`:h)}return c.join(`
|
|
12
|
-
`)}spawnAgyPrint(t,e,s,i){const n=this.buildPrintArgs(s,i),c={...process.env,...this.config.env??{}};o.info(a,`Spawning: agy ${n.map(d=>d.includes(" ")?`"${d}"`:d).join(" ")}`);const r=this.getLatestLogFilePath(),l=$(this.config.command,n,{cwd:i.cwd,env:c,stdio:["pipe","pipe","pipe"],detached
|
|
13
|
-
`).filter(n=>n.startsWith("E")).map(n=>{const c=n.indexOf("] ");return c>=0?n.slice(c+2):n}).filter(n=>n.length>0);if(i.length>0){const n=i[i.length-1];return o.info(a,`Extracted error from agy log: ${n.slice(0,120)}`),n.length>500?n.slice(0,500)+"...":n}return"agy completed without output (possible auth or quota issue)"}catch(e){return o.info(a,`extractLogError: ${e}`),"agy completed without output"}}handleProcessResult(t,e,s,i,n){if(this.activeEventId===t){if(i&&!s.trim()){o.error(a,`Event ${t} failed: ${i}`);const r=i.includes("RESOURCE_EXHAUSTED")||i.includes("quota")?"The agy API quota has been exhausted. Please try again later.":`agy execution failed: ${i}`;this.callbacks.sendStreamChunk(t,e,r,1,!0,`agy-${t}`),this.callbacks.sendEventResult(t,"failed",i)}else{const c=s.trim();if(c){const r=n.cwd,l=r?this.extractDelta(c,r):c;r&&this.conversationOutputCache.set(r,c),l&&!this.activeEventSentViaTool&&this.callbacks.sendStreamChunk(t,e,l,1,!0,`agy-${t}`)}if(n.cwd){const r=this.readAgyConversationId(n.cwd);r&&r!==n.conversationId&&(this.sessionConversationMap.set(e,r),this.callbacks.persistConversationId(e,r))}this.callbacks.sendEventResult(t,"responded")}this.clearActiveEvent(t),this.drainQueue()}}extractDelta(t,e){const s=this.conversationOutputCache.get(e);return s&&t.startsWith(s)?t.slice(s.length).trim():t}clearActiveEvent(t){const e=t??this.activeEventId;if(e&&(this.completedEventIds.add(e),this.completedEventIds.size>1e3)){const s=Array.from(this.completedEventIds);this.completedEventIds.clear();for(let i=500;i<s.length;i++)this.completedEventIds.add(s[i])}this.activeEventId=null,this.activeEventSessionId=null,this.activeProcess=null,this.activeEventSentViaTool=!1,e&&this.emit("eventDone",e)}drainQueue(){if(this.stopped||this.activeEventId)return;const t=this.eventQueue.shift();t&&(o.info(a,`Draining queued event ${t.event_id}`),this.processEvent(t))}}export{
|
|
12
|
+
`)}spawnAgyPrint(t,e,s,i){const n=this.buildPrintArgs(s,i),c={...process.env,...this.config.env??{}};o.info(a,`Spawning: agy ${n.map(d=>d.includes(" ")?`"${d}"`:d).join(" ")}`);const r=this.getLatestLogFilePath(),l=$(this.config.command,n,{cwd:i.cwd,env:c,stdio:["pipe","pipe","pipe"],detached:process.platform!=="win32"});this.activeProcess=l;const h=[],u=[];l.stdout?.on("data",d=>{h.push(d)}),l.stderr?.on("data",d=>{u.push(d)}),l.on("error",d=>{o.error(a,`Process spawn error for event ${t}: ${d.message}`),this.handleProcessResult(t,e,"",`spawn error: ${d.message}`,i)}),l.on("close",d=>{if(o.info(a,`Process exited for event ${t} with code ${d}`),this.activeEventId!==t)return;const g=Buffer.concat(h).toString("utf-8"),P=Buffer.concat(u).toString("utf-8");if(d!==0){const E=P.trim()||`process exited with code ${d}`;this.handleProcessResult(t,e,g,E,i)}else g.trim()?this.handleProcessResult(t,e,g,"",i):this.activeEventSentViaTool?this.handleProcessResult(t,e,g,"",i):setTimeout(()=>{const E=this.extractLogError(r);this.handleProcessResult(t,e,g,E,i)},300)}),l.stdin?.end()}buildPrintArgs(t,e){const s=[],i=e.modelId?B(e.modelId,F()):"";return i&&s.push("--model",i),e.cwd&&s.push("--add-dir",e.cwd),s.push("--dangerously-skip-permissions"),e.conversationId&&s.push("--conversation",e.conversationId),s.push("-p",t),s}readAgyConversationId(t){try{if(S(A)){const e=JSON.parse(_(A,"utf-8"));return e[V(t)]||e[t]||void 0}}catch(e){o.debug(a,`readAgyConversationId: ${e}`)}}getLatestLogFilePath(){try{if(!S(y))return null;const t=b(y).filter(e=>e.startsWith("cli-")&&e.endsWith(".log")).map(e=>({name:e,path:p(y,e),mtime:w(p(y,e)).mtimeMs})).sort((e,s)=>s.mtime-e.mtime);return t.length>0?t[0].path:null}catch{return null}}extractLogError(t){try{const e=this.getLatestLogFilePath();if(!e)return o.info(a,"extractLogError: no log files found"),"agy completed without output";const s=_(e,"utf-8");if(!s)return o.info(a,`extractLogError: log file is empty: ${e}`),"agy completed without output";const i=s.split(`
|
|
13
|
+
`).filter(n=>n.startsWith("E")).map(n=>{const c=n.indexOf("] ");return c>=0?n.slice(c+2):n}).filter(n=>n.length>0);if(i.length>0){const n=i[i.length-1];return o.info(a,`Extracted error from agy log: ${n.slice(0,120)}`),n.length>500?n.slice(0,500)+"...":n}return"agy completed without output (possible auth or quota issue)"}catch(e){return o.info(a,`extractLogError: ${e}`),"agy completed without output"}}handleProcessResult(t,e,s,i,n){if(this.activeEventId===t){if(i&&!s.trim()){o.error(a,`Event ${t} failed: ${i}`);const r=i.includes("RESOURCE_EXHAUSTED")||i.includes("quota")?"The agy API quota has been exhausted. Please try again later.":`agy execution failed: ${i}`;this.callbacks.sendStreamChunk(t,e,r,1,!0,`agy-${t}`),this.callbacks.sendEventResult(t,"failed",i)}else{const c=s.trim();if(c){const r=n.cwd,l=r?this.extractDelta(c,r):c;r&&this.conversationOutputCache.set(r,c),l&&!this.activeEventSentViaTool&&this.callbacks.sendStreamChunk(t,e,l,1,!0,`agy-${t}`)}if(n.cwd){const r=this.readAgyConversationId(n.cwd);r&&r!==n.conversationId&&(this.sessionConversationMap.set(e,r),this.callbacks.persistConversationId(e,r))}this.callbacks.sendEventResult(t,"responded")}this.clearActiveEvent(t),this.drainQueue()}}extractDelta(t,e){const s=this.conversationOutputCache.get(e);return s&&t.startsWith(s)?t.slice(s.length).trim():t}clearActiveEvent(t){const e=t??this.activeEventId;if(e&&(this.completedEventIds.add(e),this.completedEventIds.size>1e3)){const s=Array.from(this.completedEventIds);this.completedEventIds.clear();for(let i=500;i<s.length;i++)this.completedEventIds.add(s[i])}this.activeEventId=null,this.activeEventSessionId=null,this.activeProcess=null,this.activeEventSentViaTool=!1,e&&this.emit("eventDone",e)}drainQueue(){if(this.stopped||this.activeEventId)return;const t=this.eventQueue.shift();t&&(o.info(a,`Draining queued event ${t.event_id}`),this.processEvent(t))}}export{at as AgyAdapter};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{spawn as
|
|
2
|
-
`).
|
|
1
|
+
import{spawn as p}from"node:child_process";import{log as m}from"../../core/log/index.js";const u="agy-model-list",y=3600*1e3,w=1e4,M=[["gemini-3.7-flash-high","Gemini 3.7 Flash (High)"],["gemini-3.7-flash-medium","Gemini 3.7 Flash (Medium)"],["gemini-3.7-flash-low","Gemini 3.7 Flash (Low)"],["gemini-3.6-flash-high","Gemini 3.6 Flash (High)"],["gemini-3.6-flash-medium","Gemini 3.6 Flash (Medium)"],["gemini-3.6-flash-low","Gemini 3.6 Flash (Low)"],["gemini-3.5-flash-high","Gemini 3.5 Flash (High)"],["gemini-3.5-flash-medium","Gemini 3.5 Flash (Medium)"],["gemini-3.5-flash-low","Gemini 3.5 Flash (Low)"],["gemini-3.1-pro-high","Gemini 3.1 Pro (High)"],["gemini-3.1-pro-low","Gemini 3.1 Pro (Low)"],["claude-sonnet-4-6","Claude Sonnet 4.6 (Thinking)"],["claude-opus-4-6-thinking","Claude Opus 4.6 (Thinking)"],["gpt-oss-120b-medium","GPT-OSS 120B (Medium)"]].map(([n,e])=>({id:n,displayName:e}));let g=null,c=0,r=null;function A(n){return new Promise((e,i)=>{const t=p(n,["models"],{stdio:["ignore","pipe","pipe"],windowsHide:!0});let o="",a="";const l=setTimeout(()=>{t.kill("SIGKILL"),i(new Error("agy models timed out"))},w);t.stdout?.on("data",s=>{o+=s.toString("utf-8")}),t.stderr?.on("data",s=>{a+=s.toString("utf-8")}),t.on("error",s=>{clearTimeout(l),i(s)}),t.on("close",s=>{clearTimeout(l),s===0?e(o):i(new Error(a.trim()||`agy models exited with code ${s}`))})})}function d(n){const e=n.trim(),i=e.indexOf(" ");if(i<0)return{slug:e,displayName:e};const t=e.slice(0,i).trim(),o=e.slice(i+1).trim();return{slug:t||o,displayName:o||t}}function f(n,e){const{slug:i,displayName:t}=d(n);if(!(!i&&!t))return e.find(o=>o.id===i||o.displayName===i||o.id===t||o.displayName===t)}function S(n,e=[]){const i=f(n,e);return i?i.displayName:d(n).displayName}function x(n,e){const i=f(n,e);return i?i.id:d(n).slug}function G(n){const e=new Set,i=[];for(const t of n.split(`
|
|
2
|
+
`)){const o=t.trim();if(!o||/^(Usage|Error|Flags|Available|Fetching)/i.test(o))continue;const{slug:a,displayName:l}=d(o);if(!l)continue;const s=a||l;e.has(s)||(e.add(s),i.push({id:s,displayName:l}))}return i}function h(n){return r||(r=(async()=>{try{const e=await A(n),i=G(e);i.length>0?(g=i,c=Date.now(),m.info(u,`refreshed agy models: ${i.length} entries`)):m.info(u,"agy models returned no parsable entries, keeping fallback")}catch(e){m.info(u,`agy models failed, using fallback: ${e instanceof Error?e.message:e}`)}finally{r=null}})(),r)}function N(n){return(!g||Date.now()-c>=y)&&n&&h(n),F()}function F(){return g??M}async function b(n){n&&await h(n)}export{F as getAgyModelCatalog,N as getCachedAgyModels,f as matchAgyModel,G as parseAgyModels,b as primeAgyModels,S as resolveAgyModelFlag,x as resolveAgyToolbarModelId};
|
|
@@ -3,23 +3,23 @@ import{spawn as Z,spawnSync as ee,execSync as te,execFile as We}from"node:child_
|
|
|
3
3
|
`)}finally{it(r)}}else this.claudeProcess?.stdin?.write(`${s}
|
|
4
4
|
`);if(e==="compact"){const n=new Promise(a=>{this.compactionDoneResolver=a});this.beginCompaction();const r=await n;return r==="process-exit"?{status:"failed",message:"Claude exited during compaction"}:r==="stopped"?{status:"failed",message:"Adapter stopped during compaction"}:{status:"ok",message:"Compacted"}}return{status:"ok",message:`Sent: ${s}`}}catch(n){return{status:"failed",message:`Failed to send: ${n instanceof Error?n.message:n}`}}}handleStatusLineUpdate(e){try{const t=e.context_window,i=e.cost,s=e.model,n=e.rate_limits??e.rateLimits,r=t?.current_usage;if(this.sessionState={contextWindow:{usedPercentage:t?.used_percentage!=null?S(t.used_percentage):null,remainingPercentage:t?.remaining_percentage!=null?S(t.remaining_percentage):null,totalInputTokens:S(t?.total_input_tokens),totalOutputTokens:S(t?.total_output_tokens),contextWindowSize:S(t?.context_window_size)||2e5,currentUsage:r?{inputTokens:S(r.input_tokens),outputTokens:S(r.output_tokens),cacheCreationInputTokens:S(r.cache_creation_input_tokens),cacheReadInputTokens:S(r.cache_read_input_tokens)}:null},cost:{totalCostUsd:S(i?.total_cost_usd),totalDurationMs:S(i?.total_duration_ms),totalApiDurationMs:S(i?.total_api_duration_ms),totalLinesAdded:S(i?.total_lines_added),totalLinesRemoved:S(i?.total_lines_removed)},rateLimits:this.parseStatusRateLimits(n),model:{id:String(s?.id??""),displayName:String(s?.display_name??"")},fastMode:e.fast_mode===!0,effort:e.effort!=null?String(e.effort):void 0,thinkingEnabled:e.thinking_enabled===!0,exceeds200kTokens:e.exceeds_200k_tokens===!0,version:String(e.version??""),sampledAt:Date.now()},n){const a=this.sessionState.rateLimits?.fiveHour,c=this.sessionState.rateLimits?.sevenDay;o.info("claude-adapter",`[rate-limits] statusLine parsed: fiveHour=${a?`${a.usedPercentage}% resetsAt=${a.resetsAt}`:"n/a"} sevenDay=${c?`${c.usedPercentage}% resetsAt=${c.resetsAt}`:"n/a"}`)}else o.debug("claude-adapter","[rate-limits] statusLine: no rate_limits in payload");try{this.bridgeCallbacks.onStatusLineUpdated?.(this.sessionState)}catch(a){o.warn("claude-adapter",`onStatusLineUpdated callback error: ${a instanceof Error?a.message:a}`)}}catch(t){o.warn("claude-adapter",`Failed to parse statusLine payload: ${t instanceof Error?t.message:t}`)}}parseStatusRateLimits(e){if(!e)return;const t=n=>{if(!n||typeof n!="object")return;const r=n,a=S(r.used_percentage??r.usedPercent),c=S(r.resets_at??r.resetsAt);if(!(a<=0&&c<=0))return{usedPercentage:a,resetsAt:c}},i=t(e.five_hour??e.fiveHour),s=t(e.seven_day??e.sevenDay);if(!(!i&&!s))return{...i?{fiveHour:i}:{},...s?{sevenDay:s}:{}}}deliverInboundEvent(e){if(this.sessionId||(this.sessionId=e.session_id),this.pruneCompletedEvents(),this.completedEventIds.has(e.event_id)){o.info("claude-adapter",`Event ${e.event_id} rejected: duplicate`),this.bridgeCallbacks.sendEventResult(e.event_id,"responded","duplicate event");return}if(Date.now()<this.authFailureUntil){o.info("claude-adapter",`Event ${e.event_id} rejected: auth cooldown`),this.bridgeCallbacks.sendEventResult(e.event_id,"failed","Claude authentication failed \u2014 please re-login");return}if(Date.now()<this.usageLimitUntil){o.info("claude-adapter",`Event ${e.event_id} rejected: usage limit cooldown`),this.bridgeCallbacks.sendEventResult(e.event_id,"failed","Claude usage limit reached \u2014 waiting for reset");return}if(this.activeEvent?.eventId===e.event_id){o.info("claude-adapter",`Event ${e.event_id} ignored: same as active event`);return}if(this.activeEvent||this.compacting||this.stopHookBarrierSessionId===e.session_id){o.error("claude-adapter",`Scheduler invariant violated: event ${e.event_id} delivered while busy (active=${this.activeEvent?.eventId??"none"}, compacting=${this.compacting}, barrier=${this.stopHookBarrierSessionId===e.session_id}) \u2014 failing event to release slot`),this.bridgeCallbacks.sendEventResult(e.event_id,"failed","agent busy (scheduler invariant violated)");return}this.activeEvent={eventId:e.event_id,sessionId:e.session_id,rawEvent:e},this.stopSelfDriven(),this.lastClearedEvent=null,this.markActiveEventActivity(e.event_id,e.session_id),this.resetActiveEventHardTimer(e.event_id),this.emit("eventStarted",e.event_id,e.session_id),this.ensureClaudeAndPushEvent(e)}deliverStopEvent(e,t){if(this.activeEvent?.eventId!==e){o.info("claude-adapter",`Stop for non-active event=${e} (active=${this.activeEvent?.eventId??"none"}, compacting=${this.compacting}) \u2014 emitting eventDone to unblock platform`),this.emit("eventDone",e);return}const i=this.activeEvent.sessionId;o.info("claude-adapter",`Stop requested for active event=${e} \u2014 killing Claude process`),this.mcpServerReady&&this.pushNotification("notifications/event_stop",{event_id:e,session_id:i,stop_id:J()}),(this.claudeProcess||this.claudePty)&&this.emit("pauseIntake","restart"),this.bridgeCallbacks.sendEventResult(e,"canceled","stopped by user"),this.clearActiveEvent(),this.killClaudeProcess("stop")}async handleLocalAction(e){if(!this.mcpServerReady)return{handled:!1,kind:""};const t=String(e.action_type??"");if(t==="claude_interaction_reply"){const i=e.params??{};if(String(i.kind??"")==="permission"){const n=i.resolution??{},r=String(n.value??""),a={...e,action_type:r==="allow"?"exec_approve":"exec_reject",params:{...i,approval_command_id:i.request_id}};return this.handlePermissionApproval(a)}return this.handleQuestionReply(e)}return t==="exec_approve"||t==="exec_reject"?this.handlePermissionApproval(e):(this.pushNotification("notifications/local_action",e),{handled:!0,kind:""})}async handleQuestionReply(e){const t=e.params??{},i=String(t.request_id??"");if(!i)return o.warn("claude-adapter","Question reply missing request_id"),this.bridgeCallbacks.sendLocalActionResult(e.action_id,"failed",void 0,"request_id_required","request_id is required"),{handled:!0,kind:"question_reply_no_id"};const s=w(),n=new _e(s.questionRequestsDir),r=await n.getRequest(i),a=Number(r?.created_at??0),c=a>0&&Date.now()-a>xt;if(!r||r.resolved||c){o.warn("claude-adapter",`Question reply has no pending waiter: request_id=${i} exists=${!!r} resolved=${r?.resolved===!0} expired=${c}`);const v=this.pendingQuestion.get(i);return v&&this.activeEvent?.eventId===v.eventId&&(this.activeEvent.awaitingUserQuestion=!1),this.pendingQuestion.delete(i),this.bridgeCallbacks.sendLocalActionResult(e.action_id,"failed",void 0,"interaction_request_not_pending","The question is no longer pending; the reply was not delivered."),{handled:!0,kind:"question_reply_not_pending"}}const l=t.resolution??{},u=String(l.type??""),h=l;if(u==="action"&&String(l.value??"")==="cancel"){o.info("claude-adapter",`Question cancelled by user: request_id=${i}`),await n.resolveRequest(i,"cancel",void 0,h);const y=this.pendingQuestion.get(i);return y&&this.activeEvent?.eventId===y.eventId&&(this.activeEvent.awaitingUserQuestion=!1),this.pendingQuestion.delete(i),this.bridgeCallbacks.sendLocalActionResult(e.action_id,"ok",{request_id:i,resolution:"cancel"}),{handled:!0,kind:"question_reply_cancel"}}let f="";if(u==="text"?f=String(l.value??""):u==="map"&&(f=(Array.isArray(l.entries)?l.entries:[]).map(y=>y.value).join(", ")),!f){o.warn("claude-adapter",`Empty answer for question reply request_id=${i}`),await n.resolveRequest(i,"cancel",void 0,h);const v=this.pendingQuestion.get(i);return v&&this.activeEvent?.eventId===v.eventId&&(this.activeEvent.awaitingUserQuestion=!1),this.pendingQuestion.delete(i),this.bridgeCallbacks.sendLocalActionResult(e.action_id,"ok",{request_id:i,resolution:"cancel"}),{handled:!0,kind:"question_reply_empty"}}o.info("claude-adapter",`Resolving question in store: request_id=${i} answer=${f.slice(0,80)}`),await n.resolveRequest(i,"answer",f,h);const m=this.pendingQuestion.get(i);return m&&this.activeEvent?.eventId===m.eventId&&(this.activeEvent.awaitingUserQuestion=!1),this.pendingQuestion.delete(i),this.bridgeCallbacks.sendLocalActionResult(e.action_id,"ok",{request_id:i,resolution:"answer"}),{handled:!0,kind:"question_reply"}}async handlePermissionApproval(e){const t=e.params??{},i=String(t.approval_id??t.approval_command_id??t.tool_call_id??""),s=String(e.action_type??"")==="exec_approve";if(!i)return this.bridgeCallbacks.sendLocalActionResult(e.action_id,"failed",void 0,"approval_id_required","approval_id is required"),{handled:!0,kind:"permission_approval"};if(!this.pendingPermissions.get(i))return this.bridgeCallbacks.sendLocalActionResult(e.action_id,"failed",void 0,"approval_not_found",`no pending permission for approval_id: ${i}`),{handled:!0,kind:"permission_approval"};const r=w(),a=new W(r.permissionRequestsDir),c=s?"allow":"deny";return await a.resolveRequest(i,c),this.pendingPermissions.delete(i),this.bridgeCallbacks.sendLocalActionResult(e.action_id,"ok"),o.info("claude-adapter",`Permission ${c}: approvalId=${i}`),{handled:!0,kind:"permission_approval"}}async ensureClaudeAndPushEvent(e,t=0){t===0&&!this.claudeProcess&&this.startComposing(e.session_id,"preparing");try{if(this.mcpChannelBroken){await this.runSingleTurnFallback(e,"mcp channel unavailable");return}if(await this.isChannelGateClosed()){await this.runSingleTurnFallback(e,"channel gate closed: tengu_harbor not enabled in cachedGrowthBookFeatures");return}if(!this.getClaudeCliCapabilities(this.config.command||"claude").developmentChannels){await this.runSingleTurnFallback(e,"claude CLI does not support development channels");return}if(await this.ensureClaudeProcessReady(),!this.mcpServerReady)throw new Error("MCP stdio server not available");if(await this.waitForNotifyPort(3e4),process.platform==="win32"?await this.waitForWindowsChannelReady(je):await this.waitForChannelListening(je),this.activeEvent?.eventId!==e.event_id||this.mcpChannelBroken)return;const s={},n=[["chat_id",e.session_id],["event_id",e.event_id],["event_type","user_chat"],["message_id",e.msg_id],["sender_id",e.sender_id],["user",e.sender_id],["user_id",e.sender_id],["msg_id",e.msg_id],["session_type",e.session_type!=null?String(e.session_type):void 0],["msg_type",e.msg_type!=null?String(e.msg_type):void 0],["quoted_message_id",e.quoted_message_id],["ts",e.created_at!=null?new Date(Number(e.created_at)).toISOString():void 0],["owner_id",e.owner_id],["agent_id",this.bridgeCallbacks.getAgentId?.()],["attachments_json",e.attachments_json],["context_messages_json",Ue(e.context_messages_json)??e.context_messages_json]];for(const[a,c]of n)c!=null&&c!==""&&(s[a]=c);this.captureEventJsonlBaseOffset(e.event_id);const r=e.content??"";this.pushNotification("notifications/claude/channel",{content:r,meta:s}),o.info("claude-adapter",`Event ${e.event_id} pushed to MCP stdio server`)}catch(s){if(this.stopped){this.activeEvent?.eventId===e.event_id&&(this.bridgeCallbacks.sendEventResult(e.event_id,"canceled","adapter stopped"),this.clearActiveEvent());return}if(s instanceof Error&&(s.message.includes("exited before")||s.message.includes("timeout")||s.message.includes("spawn failed"))&&t<3)return o.warn("claude-adapter",`Spawn failed (attempt ${t+1}/4), retrying: ${s}`),this.claudeProcess=null,this.claudeChildPid=0,this.alive=!1,this.releaseNotifyPortReservation(),this.stopMcpServer(),ce(this.claudeCliSessionId,[]),await new Promise(a=>setTimeout(a,(t+1)*2e3)),this.ensureClaudeAndPushEvent(e,t+1);o.error("claude-adapter",`Failed to deliver event ${e.event_id}: ${s}`);let r=s;if(this.shouldFallbackToSingleTurn(s)){o.warn("claude-adapter",`Channel path failed, switching to single-turn fallback: ${s instanceof Error?s.message:String(s)}`);try{await this.runSingleTurnFallback(e,s instanceof Error?s.message:String(s));return}catch(a){o.error("claude-adapter",`Single-turn fallback failed for ${e.event_id}: ${a instanceof Error?a.message:String(a)}`),r=a}}this.activeEvent?.eventId===e.event_id&&(this.bridgeCallbacks.sendEventResult(e.event_id,"failed",r instanceof Error?r.message:String(r)),this.clearActiveEvent())}}shouldFallbackToSingleTurn(e){const t=(e instanceof Error?e.message:String(e)).toLowerCase();return t?t.includes("mcp server startup failed")||t.includes("claude channel listener not ready")||t.includes("channel ready check timed out")||t.includes("channels are not currently available")||t.includes("does not support development channels")||t.includes("unknown option")||t.includes("unknown or unexpected option")||t.includes("mcp stdio server not available")||t.includes("notify port not ready")||t.includes("file not found:"):!1}getClaudeCliCapabilities(e){return this.claudeCliCapabilities?this.claudeCliCapabilities:(this.claudeCliCapabilities=oi(e),this.claudeCliCapabilities)}buildSingleTurnPrompt(e,t=!0){const i=t&&e.context_messages_json?Ue(e.context_messages_json)??String(e.context_messages_json):"",s=["Reply to the latest user message directly and concisely."];return i&&(s.push("Conversation context (JSON):"),s.push(i)),s.push("Latest user message:"),s.push(e.content??""),s.join(`
|
|
5
5
|
`)}extractSingleTurnDelta(e){const t=e.trim();if(!t)return null;let i;try{i=JSON.parse(t)}catch{return o.warn("claude-adapter",`Single-turn fallback: dropping non-JSON stdout noise: ${t.slice(0,200)}`),null}const s=i.type==="stream_event"&&i.event&&typeof i.event=="object"?i.event:i,n=String(s.type??"");if(n==="content_block_delta"){const r=s.delta;if(r&&typeof r=="object"){const a=String(r.text??"");if(a)return{text:a,final:!1}}return null}if(n==="result"){const r=String(s.subtype??""),a=String(s.terminal_reason??""),c=s.is_error===!0||a==="api_error";if(r&&r!=="success"||c){const u=Array.isArray(s.errors)?s.errors.join("; "):"",h=String(s.result??"").trim(),f=u||h||a;throw new Error(`claude single-turn result ${r||"error"}${f?`: ${f}`:""}`)}const l=String(s.result??"");return l?{text:l,final:!0}:null}return null}resolveEventAuditConfig(e,t){if(e.audit?.enabled===!0)return e.audit;const i=t?.audit;if(i?.enabled!==!0)return;const s=i.profile??"full";return{enabled:!0,profile:s,capture:i.capture??(s==="usage"?{inputOutput:!1,tools:!1,subagents:!0,thinking:!1,rawProviderBody:i.rawProviderBody===!0}:{inputOutput:!0,tools:!0,subagents:!0,thinking:!1,rawProviderBody:i.rawProviderBody===!0})}}buildClaudeRuntimeEnv(e=this.resolveSessionRuntime()){const t={...process.env,...this.config.env};if(delete t.CLAUDECODE,t.CLAUDE_PLUGIN_DATA=w().dataDir,t.GRIX_HOOK_SIGNALS_PATH=this.resolveHookSignalsPath(),this.internalApi&&(t.GRIX_CLAUDE_INTERNAL_API_URL=this.internalApi.baseUrl,t.GRIX_CLAUDE_INTERNAL_API_TOKEN=this.internalApi.token),e.audit?.enabled===!0&&!this.internalApi&&o.error("claude-adapter","Audit enabled but internal API not started; OTEL capture env will not be injected"),e.audit?.enabled===!0&&this.internalApi){const s=this.ensureClaudeAuditEvidence(e.audit);t.CLAUDE_CODE_ENABLE_TELEMETRY="1",t.CLAUDE_CODE_ENHANCED_TELEMETRY_BETA="1",t.OTEL_LOGS_EXPORTER="otlp",t.OTEL_TRACES_EXPORTER="otlp",t.OTEL_METRICS_EXPORTER="none",t.OTEL_EXPORTER_OTLP_PROTOCOL="http/json",t.OTEL_EXPORTER_OTLP_ENDPOINT=this.internalApi.baseUrl,t.OTEL_EXPORTER_OTLP_HEADERS=`Authorization=Bearer ${this.internalApi.token}`,t.OTEL_LOGS_EXPORT_INTERVAL="1000",t.OTEL_TRACES_EXPORT_INTERVAL="1000",t.OTEL_LOG_USER_PROMPTS=e.audit.capture.inputOutput?"1":"0",t.OTEL_LOG_ASSISTANT_RESPONSES=e.audit.capture.inputOutput?"1":"0",t.OTEL_LOG_TOOL_DETAILS=e.audit.capture.tools?"1":"0",t.OTEL_LOG_TOOL_CONTENT=e.audit.capture.tools?"1":"0",e.audit.capture.rawProviderBody&&(t.OTEL_LOG_RAW_API_BODIES=`file:${s.rawBodyDir}`)}const i=dt(e.effort);if(i==="auto")delete t.CLAUDE_CODE_EFFORT_LEVEL;else{const s=ut(i);s&&(t.CLAUDE_CODE_EFFORT_LEVEL=s)}return t}ensureClaudeAuditEvidence(e){if(this.auditEvidencePaths)return this.auditEvidencePaths;const t=this.config.options??{},s=String(t.auditEvidenceRoot??"").trim()||p(w().dataDir,"audit-evidence"),n=$e(this.sessionId),r=$e(this.claudeCliSessionId||"pending"),a=p(s,"claude",n,r),c=p(a,"raw-api-bodies");ve(c,{recursive:!0,mode:448}),ie(a,448),ie(c,448);const l={rootDir:a,logsPath:p(a,"otel-logs.jsonl"),tracesPath:p(a,"otel-traces.jsonl"),rawBodyDir:c,rawProviderBody:e.capture.rawProviderBody};return this.internalApi?.setOtelHandler(async(u,h,f)=>{const m=u==="logs"?l.logsPath:l.tracesPath;await nt(m,`${JSON.stringify({observedAt:new Date().toISOString(),signal:u,payload:h,rawBody:f})}
|
|
6
|
-
`,{encoding:"utf8",mode:384}),ie(m,384)}),this.auditEvidencePaths=l,l}async runSingleTurnFallback(e,t){const i=this.resolveSessionRuntime(),s=this.resolveEventAuditConfig(i,e);if(s&&!this.internalApi){o.error("claude-adapter",`Audit capture unavailable for event ${e.event_id}: internal API not started`),this.bridgeCallbacks.sendEventResult(e.event_id,"failed","audit capture unavailable: internal API not started","audit_capture_unavailable");return}if(this.activeEvent?.eventId===e.event_id&&(this.activeEvent.singleTurnFallback=!0,s&&(this.activeEvent.auditSingleTurnFallback=!0)),this.claudeProcess||this.claudePty){const L=this.mcpChannelBroken,g=this.channelGateClosed;this.killClaudeProcess("single-turn fallback isolation"),this.mcpChannelBroken=L,this.channelGateClosed=g}const n=this.config.options??{},r=this.config.command||"claude",a=(this.config.args??[]).filter(L=>{const g=String(L).trim().toLowerCase();return g!=="--dangerously-load-development-channels"&&g!=="--session-id"&&g!=="--resume"}),c=String(i.cwd??"").trim(),l=String(i.modelId??"").trim(),u=Be(i.modeId);await this.ensureWorkspaceTrust(c),await this.ensureClaudeOnboardingFlags(c),await this.ensureSkipDangerousPermissionPrompt(),await this.injectStatusLineSettings(c),await this.ensureStdioMcpServer();const h=String(i.claudeSessionId??"").trim()||this.claudeCliSessionId||J();this.claudeCliSessionId=h,this.claudeSessionCwd=c,!i.claudeSessionId&&i.onSessionIdAssigned&&i.onSessionIdAssigned(h);const f=this.buildClaudeRuntimeEnv(s?{...i,audit:s}:i),m=i.pluginDir??n.pluginDir??await this.ensureClaudePluginDir();let v=ze(h,c||void 0);v&&Je(h,c||void 0)&&(v=!1);const y=String(i.claudeSessionId??"").trim()!==""&&!v;v&&Ge(h,c||void 0),this.captureSingleTurnFallbackJsonlBaseOffset(e.event_id),y&&o.warn("claude-adapter",`Claude local transcript missing for session ${h}; continuing silently with platform context`);const F=this.buildSingleTurnPrompt(e,!v),_=[...a,"-p","--output-format","stream-json","--verbose","--include-partial-messages",...v?["--resume",h]:["--session-id",h]];_.push("--plugin-dir",m,"--strict-mcp-config","--mcp-config",this.claudeMcpConfigPath,"--permission-mode",u===H.fullAuto?"bypassPermissions":"default"),l&&_.push("--model",l),_.push(F),o.warn("claude-adapter",`Running single-turn fallback for event ${e.event_id}: ${t}`),this.bridgeCallbacks.sendEventAck(e.event_id,e.session_id),this.startComposing(e.session_id,"fallback_single_turn"),await new Promise((L,g)=>{const D=Z(r,_,{cwd:c||process.cwd(),env:f,stdio:["ignore","pipe","pipe"],detached
|
|
6
|
+
`,{encoding:"utf8",mode:384}),ie(m,384)}),this.auditEvidencePaths=l,l}async runSingleTurnFallback(e,t){const i=this.resolveSessionRuntime(),s=this.resolveEventAuditConfig(i,e);if(s&&!this.internalApi){o.error("claude-adapter",`Audit capture unavailable for event ${e.event_id}: internal API not started`),this.bridgeCallbacks.sendEventResult(e.event_id,"failed","audit capture unavailable: internal API not started","audit_capture_unavailable");return}if(this.activeEvent?.eventId===e.event_id&&(this.activeEvent.singleTurnFallback=!0,s&&(this.activeEvent.auditSingleTurnFallback=!0)),this.claudeProcess||this.claudePty){const L=this.mcpChannelBroken,g=this.channelGateClosed;this.killClaudeProcess("single-turn fallback isolation"),this.mcpChannelBroken=L,this.channelGateClosed=g}const n=this.config.options??{},r=this.config.command||"claude",a=(this.config.args??[]).filter(L=>{const g=String(L).trim().toLowerCase();return g!=="--dangerously-load-development-channels"&&g!=="--session-id"&&g!=="--resume"}),c=String(i.cwd??"").trim(),l=String(i.modelId??"").trim(),u=Be(i.modeId);await this.ensureWorkspaceTrust(c),await this.ensureClaudeOnboardingFlags(c),await this.ensureSkipDangerousPermissionPrompt(),await this.injectStatusLineSettings(c),await this.ensureStdioMcpServer();const h=String(i.claudeSessionId??"").trim()||this.claudeCliSessionId||J();this.claudeCliSessionId=h,this.claudeSessionCwd=c,!i.claudeSessionId&&i.onSessionIdAssigned&&i.onSessionIdAssigned(h);const f=this.buildClaudeRuntimeEnv(s?{...i,audit:s}:i),m=i.pluginDir??n.pluginDir??await this.ensureClaudePluginDir();let v=ze(h,c||void 0);v&&Je(h,c||void 0)&&(v=!1);const y=String(i.claudeSessionId??"").trim()!==""&&!v;v&&Ge(h,c||void 0),this.captureSingleTurnFallbackJsonlBaseOffset(e.event_id),y&&o.warn("claude-adapter",`Claude local transcript missing for session ${h}; continuing silently with platform context`);const F=this.buildSingleTurnPrompt(e,!v),_=[...a,"-p","--output-format","stream-json","--verbose","--include-partial-messages",...v?["--resume",h]:["--session-id",h]];_.push("--plugin-dir",m,"--strict-mcp-config","--mcp-config",this.claudeMcpConfigPath,"--permission-mode",u===H.fullAuto?"bypassPermissions":"default"),l&&_.push("--model",l),_.push(F),o.warn("claude-adapter",`Running single-turn fallback for event ${e.event_id}: ${t}`),this.bridgeCallbacks.sendEventAck(e.event_id,e.session_id),this.startComposing(e.session_id,"fallback_single_turn"),await new Promise((L,g)=>{const D=Z(r,_,{cwd:c||process.cwd(),env:f,stdio:["ignore","pipe","pipe"],detached:process.platform!=="win32",windowsHide:!0});let E="",C=0,k=!1,N=null;const he=`fallback_${e.event_id}_${Date.now()}`,fe=String(e.msg_id??"").trim()||void 0;let x="";const pe=$=>{let A;try{A=this.extractSingleTurnDelta($)}catch(B){N=B instanceof Error?B:new Error(String(B));return}A&&(A.final&&k||this.activeEvent?.eventId===e.event_id&&this.isEventAnswered()||(C+=1,k=!0,this.markActiveEventActivity(e.event_id,e.session_id),this.bridgeCallbacks.sendStreamChunk(e.event_id,e.session_id,A.text,C,!1,he,fe)))};D.stdout?.setEncoding("utf8"),D.stdout?.on("data",$=>{x+=$;let A=x.indexOf(`
|
|
7
7
|
`);for(;A>=0;){const B=x.slice(0,A);x=x.slice(A+1),pe(B),A=x.indexOf(`
|
|
8
|
-
`)}}),D.stderr?.setEncoding("utf8"),D.stderr?.on("data",$=>{E.length>=oe||(E+=$,E.length>oe&&(E=E.slice(0,oe)))}),D.on("error",$=>g($)),D.on("close",$=>{if(x.trim()&&pe(x),N){g(N);return}if($!==0&&!k){const A=E.trim();g(new Error(A||`claude single-turn exited with code ${$}`));return}if(!k&&this.activeEvent?.eventId===e.event_id&&!this.isEventAnswered()){const A=E.trim();g(new Error(A||"claude single-turn produced no output"));return}k&&(C+=1,this.bridgeCallbacks.sendStreamChunk(e.event_id,e.session_id,"",C,!0,he,fe)),L()})}),this.activeEvent?.eventId===e.event_id&&(this.bridgeCallbacks.sendEventResult(e.event_id,"responded"),this.clearActiveEvent())}async ensureClaudeProcessReady(){this.claudeProcess||this.claudePty||(this.spawnPromise||(this.spawnPromise=this.spawnClaude().finally(()=>{this.spawnPromise=null})),await this.spawnPromise)}async spawnClaude(){if(this.claudeProcess)return;const e=this.lifecycleVersion,t=()=>{if(this.stopped||e!==this.lifecycleVersion)throw new Error("adapter stopped")},i=this.config.options??{};this.sessionIdConflictDetected=!1;const s=this.resolveSessionRuntime(),n=this.config.command||"claude",r=this.config.args??[],a=Be(s.modeId),c=String(s.modelId??"").trim(),l=String(s.cwd??"").trim();this.claudeSessionCwd=l;const u=String(s.claudeSessionId??"").trim()||J();this.claudeCliSessionId=u,!s.claudeSessionId&&s.onSessionIdAssigned&&s.onSessionIdAssigned(u);const h=this.buildClaudeRuntimeEnv((()=>{const _=this.resolveEventAuditConfig(s,this.activeEvent?.rawEvent);return _?{...s,audit:_}:s})());let f=ze(u,l||void 0);if(f&&Je(u,l||void 0)&&(f=!1),String(s.claudeSessionId??"").trim()!==""&&!f&&o.warn("claude-adapter",`Claude local transcript missing for session ${u}; continuing silently with platform context`),f?Ge(u,l||void 0):(ce(u,[]),de(u)),this.runtimeResolver&&!l)throw new Error("Claude session binding missing cwd \u2014 run /grix open <working-directory> first");if(!l)throw new Error("Claude runtime cwd is required");await this.ensureWorkspaceTrust(l),await this.ensureClaudeOnboardingFlags(l),await this.ensureSkipDangerousPermissionPrompt(),await this.injectStatusLineSettings(l);let m=!1,v="",y=null;try{t(),this.notifyPort=await Xt([this.internalApiPort]),m=!0,t(),o.info("claude-adapter",`Allocated MCP notify port ${this.notifyPort} (internal API ${this.internalApiPort})`),await this.ensureStdioMcpServer(),t();const _=i.pluginDir||await this.ensureClaudePluginDir(),L=f?["--resume",u]:["--session-id",u],g=[...r,"--name",`grix-${this.sessionId}`,...L,"--plugin-dir",_,"--strict-mcp-config","--mcp-config",this.claudeMcpConfigPath];c&&(/^claude/i.test(c)?g.push("--model",c):(o.warn("claude-adapter",`Skipping --model ${c}: non-Anthropic models are incompatible with development channels. Using default model so channels can load. Switch models after startup via /model if needed.`),this.deferredModelId=c)),a===H.fullAuto&&g.push("--dangerously-skip-permissions"),g.push("--dangerously-load-development-channels",`server:${M}`);const D=ei(this.bridgeCallbacks.getAgentProfile?.(),f?void 0:this.sessionId,this.bridgeCallbacks.getAgentId?.());if(process.platform==="win32"){const E=D.replace(/</g,"[").replace(/>/g,"]").replace(/"/g,"'"),C=ai(this.claudeCliSessionId);await di(C,E),g.push("--append-system-prompt-file",C)}else g.push("--append-system-prompt",D);if(process.platform==="win32")if(G){const E=ui(),C=["/c",n,...g];o.info("claude-adapter",`Spawning Claude via PTY on win32: cwd=${l} mode=${a} ${f?"resume":"new"} ${n} ${g.join(" ")}`);const k=G.spawn(E,C,{name:"xterm-256color",cols:120,rows:30,cwd:l,env:h,useConpty:!0,conptyInheritCursor:!1});this.claudePty=k,this.claudeChildPid=k.pid,this.startPtyAutoConfirm(k),y=null}else o.info("claude-adapter",`Spawning Claude via shell on win32 (node-pty unavailable): cwd=${l} mode=${a} ${f?"resume":"new"} ${n} ${g.join(" ")}`),y=Z(n,g,{cwd:l,env:h,stdio:["pipe","pipe","pipe"],detached:!0,shell:!0,windowsHide:!0});else{o.info("claude-adapter",`Spawning via expect PTY: cwd=${l} mode=${a} ${f?"resume":"new"} ${n} ${g.join(" ")}`);const E=Mt(typeof h.PATH=="string"?h.PATH:void 0);if(!E)throw new Error("expect not found in PATH. Install it with: apt install expect / dnf install expect / brew install expect");const C=p(lt(),`grix-claude-${J()}`);await O(C,{recursive:!0}),this.expectRunDir=C;const{expectPath:k,pidPath:N}=await ii(C,n,g);v=N,t(),y=Z(E,[k],{cwd:l,env:h,stdio:["pipe","pipe","pipe"],detached:!0})}if(this.claudeProcess=y,this.clearPendingMcpFailureTimer(),this.mcpStartupFailureHandled=!1,this.mcpChannelBroken=!1,this.channelGateClosed=!1,this.startupChannelListening=!1,this.startupChannelListeningAt=0,y&&y.on("error",E=>{o.error("claude-adapter","Claude process spawn error: "+E),this.claudeProcess=null,this.claudeChildPid=0,this.alive=!1,this.releaseNotifyPortReservation(),this.stopMcpServer(),this.clearPendingMcpFailureTimer(),this.activeEvent&&(this.bridgeCallbacks.sendEventResult(this.activeEvent.eventId,"failed","Claude process spawn failed"),this.clearActiveEvent())}),t(),!this.claudePty)if(process.platform==="win32")this.claudeChildPid=y?.pid??0;else{const E=await ni(y,v);if(t(),!Number.isFinite(E)||E<=0)throw new Error("failed to determine spawned Claude pid");this.claudeChildPid=E}if(!this.claudeChildPid||this.claudeChildPid<=0)throw new Error("failed to determine spawned Claude pid")}catch(_){if(y?.pid&&U(y,"SIGTERM"),this.claudePty){try{this.claudePty.kill()}catch{}this.claudePty=null}throw this.claudeProcess===y&&(this.claudeProcess=null),this.claudeChildPid=0,m&&this.releaseNotifyPortReservation(),this.stopMcpServer(),_}const F=this.claudeChildPid;o.info("claude-adapter","Claude child PID: "+F+(this.claudePty?" (via PTY)":"")),this.setupProcessOrPtyHandlers(),this.alive=!0}setupProcessOrPtyHandlers(){if(this.claudeProcess){const e=this.claudeProcess;e.on("exit",(t,i)=>{if(o.info("claude-adapter",`Claude process exited (code=${t}, signal=${i})`),this.singleTurnFallbackProcesses.delete(e)){o.info("claude-adapter","Ignoring stale Claude process exit after lifecycle ownership transfer");return}if(this.claudeProcess=null,this.claudeChildPid=0,this.alive=!1,this.releaseNotifyPortReservation(),this.stopMcpServer(),this.clearPendingMcpFailureTimer(),!this.tryRecoverSessionIdConflict()){if(this.activeEvent&&!this.stopped){const s=this.isEventAnswered(),n=s?"responded":"failed",r=s?void 0:"Claude process exited";o.error("claude-adapter",`Claude process exited with active event ${this.activeEvent.eventId}, answered=${s}, sending ${n}`),this.bridgeCallbacks.sendEventResult(this.activeEvent.eventId,n,r),this.clearActiveEvent()}this.stopped||this.emit("exit",t),this.compacting&&this.finishCompaction("process-exit")}}),e.stdout?.on("data",t=>{this.claudeProcess===e&&this.handleClaudeOutput(t.toString())}),e.stderr?.on("data",t=>{if(this.claudeProcess!==e)return;const i=t.toString().trim();i&&o.info("claude-adapter",`[claude stderr] ${i}`),this.checkFailurePatterns(i)})}if(this.claudePty){const e=this.claudePty;e.onExit(({exitCode:t,signal:i})=>{if(o.info("claude-adapter",`Claude PTY process exited (code=${t}, signal=${i})`),this.singleTurnFallbackPtys.delete(e)){o.info("claude-adapter","Ignoring stale Claude PTY exit after lifecycle ownership transfer");return}if(this.claudePty=null,this.claudeChildPid=0,this.alive=!1,this.releaseNotifyPortReservation(),this.stopMcpServer(),this.clearPendingMcpFailureTimer(),!this.tryRecoverSessionIdConflict()){if(this.activeEvent&&!this.stopped){const s=this.isEventAnswered(),n=s?"responded":"failed",r=s?void 0:"Claude process exited";o.error("claude-adapter",`Claude PTY exited with active event ${this.activeEvent.eventId}, answered=${s}, sending ${n}`),this.bridgeCallbacks.sendEventResult(this.activeEvent.eventId,n,r),this.clearActiveEvent()}this.stopped||this.emit("exit",t),this.compacting&&this.finishCompaction("process-exit")}}),e.onData(t=>{this.claudePty===e&&(this.handleClaudeOutput(t),this.checkFailurePatterns(t))})}}lastPtyOutputAt=0;ptyProbe={title:0,spinner:0,meaningful:0,lastLogAt:0,lastMeaningfulAt:0};recordPtyProbe(e){const t=Date.now();this.lastPtyOutputAt=t;const i=e.includes("]0;"),s=e.replace(/\x1b\[[0-9;?]*[ -/]*[@-~]/g,"").replace(/[\x00-\x1f\x7f-\x9f]/g,"").trim(),n=s.length<=3||/^[✳✶✻✽✵❋✺✴❈❖✦✧✢◉◎⬥⬦◇◆▸▹►▻→←↑↓⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏·…\d]*still\s+thinking/i.test(s);if(i?this.ptyProbe.title++:!s||n?this.ptyProbe.spinner++:(this.ptyProbe.meaningful++,this.ptyProbe.lastMeaningfulAt=t),t-this.ptyProbe.lastLogAt>=2e3){const r=this.activeEvent?this.isEventAnswered()?"replied":"running":"none",a=this.ptyProbe.lastMeaningfulAt?t-this.ptyProbe.lastMeaningfulAt:-1;o.debug("claude-adapter",`[pty-probe] session=${this.sessionId??"?"} event=${r} win2s title=${this.ptyProbe.title} spinner=${this.ptyProbe.spinner} meaningful=${this.ptyProbe.meaningful} meaningfulGapMs=${a}`),this.ptyProbe.title=0,this.ptyProbe.spinner=0,this.ptyProbe.meaningful=0,this.ptyProbe.lastLogAt=t}}handleClaudeOutput(e){this.recordPtyProbe(e),this.compacting&&this.resetCompactingActivityTimer();const t=e.trim();if(t){const a=t.replace(/\x1b\[[0-9;?]*[ -/]*[@-~]/g,"").replace(/[\x00-\x1f\x7f-\x9f\u200b-\u200f\u2028-\u202f\ufeff]/g,"").trim();a?a.length<=3||/^[✳✶✻✽✵❋✺✴❈❖✦✧✢◉◎⬥⬦◇◆▸▹►▻→←↑↓⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏·…\d]*still\s+thinking/i.test(a)?o.debug("claude-adapter",`[claude output] ${a}`):o.info("claude-adapter",`[claude output] ${a.slice(0,500)}`):o.debug("claude-adapter",`[claude output] ${t.slice(0,200).replace(/[^\x20-\x7e]/g,c=>`\\x${c.charCodeAt(0).toString(16).padStart(2,"0")}`)}`)}this.activeEvent&&this.markActiveEventActivity(this.activeEvent.eventId,this.activeEvent.sessionId);const i=t.match(/Session ID (\S+) is already in use/i);if(i){const a=i[1];this.sessionIdConflictDetected=!0;const c=[this.claudeProcess?.pid,this.claudeChildPid,this.claudePty?.pid].filter(l=>!!l&&l>0);ce(a,c)}const s=t.replace(/\[[0-9;?]*[ -/]*[@-~]/g," ").replace(/[^a-zA-Z]+/g," ").toLowerCase(),n=s.replace(/ /g,"");if(this.claudePty&&!this.startupChannelListening&&/trust.*folder|quick.*safety.*check/.test(n)&&(o.info("claude-adapter","Auto-accepting workspace trust dialog"),this.claudePty.write("1\r")),(/listeningforch\w*nelmessages/.test(n)||/nnelmessagesfrom/.test(n)||/inboundmessageswillbepushedintothissession/.test(n))&&(this.startupChannelListening||(this.startupChannelListeningAt=Date.now(),this.startupChannelListening=!0,this.sendDeferredModelSwitch()),this.clearPendingMcpFailureTimer()),!this.startupChannelListening&&/channels?arenotcurrentlyavailable/.test(n)&&(this.channelGateClosed=!0,o.warn("claude-adapter",'Claude reports "Channels are not currently available" \u2014 channel gate (tengu_harbor) is closed for this account/model; switching to single-turn fallback')),!this.mcpStartupFailureHandled&&s.includes("mcp server failed")){if(this.startupChannelListening){o.warn("claude-adapter","Claude reported MCP server failed for one channel, but channel listening is active; ignoring non-blocking failure");return}this.pendingMcpFailureTimer||(this.pendingMcpFailureTimer=setTimeout(()=>{this.pendingMcpFailureTimer=null,!(this.startupChannelListening||this.mcpStartupFailureHandled)&&this.markMcpStartupFailure()},Yt))}}checkFailurePatterns(e){/Session ID (\S+) is already in use/i.test(e)&&(this.sessionIdConflictDetected=!0),$t.some(t=>t.test(e))&&(o.error("claude-adapter",`Auth failure: ${e}`),this.authFailureUntil=Date.now()+Ae,this.activeEvent&&(this.bridgeCallbacks.sendStreamChunk(this.activeEvent.eventId,this.activeEvent.sessionId,`
|
|
8
|
+
`)}}),D.stderr?.setEncoding("utf8"),D.stderr?.on("data",$=>{E.length>=oe||(E+=$,E.length>oe&&(E=E.slice(0,oe)))}),D.on("error",$=>g($)),D.on("close",$=>{if(x.trim()&&pe(x),N){g(N);return}if($!==0&&!k){const A=E.trim();g(new Error(A||`claude single-turn exited with code ${$}`));return}if(!k&&this.activeEvent?.eventId===e.event_id&&!this.isEventAnswered()){const A=E.trim();g(new Error(A||"claude single-turn produced no output"));return}k&&(C+=1,this.bridgeCallbacks.sendStreamChunk(e.event_id,e.session_id,"",C,!0,he,fe)),L()})}),this.activeEvent?.eventId===e.event_id&&(this.bridgeCallbacks.sendEventResult(e.event_id,"responded"),this.clearActiveEvent())}async ensureClaudeProcessReady(){this.claudeProcess||this.claudePty||(this.spawnPromise||(this.spawnPromise=this.spawnClaude().finally(()=>{this.spawnPromise=null})),await this.spawnPromise)}async spawnClaude(){if(this.claudeProcess)return;const e=this.lifecycleVersion,t=()=>{if(this.stopped||e!==this.lifecycleVersion)throw new Error("adapter stopped")},i=this.config.options??{};this.sessionIdConflictDetected=!1;const s=this.resolveSessionRuntime(),n=this.config.command||"claude",r=this.config.args??[],a=Be(s.modeId),c=String(s.modelId??"").trim(),l=String(s.cwd??"").trim();this.claudeSessionCwd=l;const u=String(s.claudeSessionId??"").trim()||J();this.claudeCliSessionId=u,!s.claudeSessionId&&s.onSessionIdAssigned&&s.onSessionIdAssigned(u);const h=this.buildClaudeRuntimeEnv((()=>{const _=this.resolveEventAuditConfig(s,this.activeEvent?.rawEvent);return _?{...s,audit:_}:s})());let f=ze(u,l||void 0);if(f&&Je(u,l||void 0)&&(f=!1),String(s.claudeSessionId??"").trim()!==""&&!f&&o.warn("claude-adapter",`Claude local transcript missing for session ${u}; continuing silently with platform context`),f?Ge(u,l||void 0):(ce(u,[]),de(u)),this.runtimeResolver&&!l)throw new Error("Claude session binding missing cwd \u2014 run /grix open <working-directory> first");if(!l)throw new Error("Claude runtime cwd is required");await this.ensureWorkspaceTrust(l),await this.ensureClaudeOnboardingFlags(l),await this.ensureSkipDangerousPermissionPrompt(),await this.injectStatusLineSettings(l);let m=!1,v="",y=null;try{t(),this.notifyPort=await Xt([this.internalApiPort]),m=!0,t(),o.info("claude-adapter",`Allocated MCP notify port ${this.notifyPort} (internal API ${this.internalApiPort})`),await this.ensureStdioMcpServer(),t();const _=i.pluginDir||await this.ensureClaudePluginDir(),L=f?["--resume",u]:["--session-id",u],g=[...r,"--name",`grix-${this.sessionId}`,...L,"--plugin-dir",_,"--strict-mcp-config","--mcp-config",this.claudeMcpConfigPath];c&&(/^claude/i.test(c)?g.push("--model",c):(o.warn("claude-adapter",`Skipping --model ${c}: non-Anthropic models are incompatible with development channels. Using default model so channels can load. Switch models after startup via /model if needed.`),this.deferredModelId=c)),a===H.fullAuto&&g.push("--dangerously-skip-permissions"),g.push("--dangerously-load-development-channels",`server:${M}`);const D=ei(this.bridgeCallbacks.getAgentProfile?.(),f?void 0:this.sessionId,this.bridgeCallbacks.getAgentId?.());if(process.platform==="win32"){const E=D.replace(/</g,"[").replace(/>/g,"]").replace(/"/g,"'"),C=ai(this.claudeCliSessionId);await di(C,E),g.push("--append-system-prompt-file",C)}else g.push("--append-system-prompt",D);if(process.platform==="win32")if(G){const E=ui(),C=["/c",n,...g];o.info("claude-adapter",`Spawning Claude via PTY on win32: cwd=${l} mode=${a} ${f?"resume":"new"} ${n} ${g.join(" ")}`);const k=G.spawn(E,C,{name:"xterm-256color",cols:120,rows:30,cwd:l,env:h,useConpty:!0,conptyInheritCursor:!1});this.claudePty=k,this.claudeChildPid=k.pid,this.startPtyAutoConfirm(k),y=null}else o.info("claude-adapter",`Spawning Claude via shell on win32 (node-pty unavailable): cwd=${l} mode=${a} ${f?"resume":"new"} ${n} ${g.join(" ")}`),y=Z(n,g,{cwd:l,env:h,stdio:["pipe","pipe","pipe"],detached:process.platform!=="win32",shell:!0,windowsHide:!0});else{o.info("claude-adapter",`Spawning via expect PTY: cwd=${l} mode=${a} ${f?"resume":"new"} ${n} ${g.join(" ")}`);const E=Mt(typeof h.PATH=="string"?h.PATH:void 0);if(!E)throw new Error("expect not found in PATH. Install it with: apt install expect / dnf install expect / brew install expect");const C=p(lt(),`grix-claude-${J()}`);await O(C,{recursive:!0}),this.expectRunDir=C;const{expectPath:k,pidPath:N}=await ii(C,n,g);v=N,t(),y=Z(E,[k],{cwd:l,env:h,stdio:["pipe","pipe","pipe"],detached:!0})}if(this.claudeProcess=y,this.clearPendingMcpFailureTimer(),this.mcpStartupFailureHandled=!1,this.mcpChannelBroken=!1,this.channelGateClosed=!1,this.startupChannelListening=!1,this.startupChannelListeningAt=0,y&&y.on("error",E=>{o.error("claude-adapter","Claude process spawn error: "+E),this.claudeProcess=null,this.claudeChildPid=0,this.alive=!1,this.releaseNotifyPortReservation(),this.stopMcpServer(),this.clearPendingMcpFailureTimer(),this.activeEvent&&(this.bridgeCallbacks.sendEventResult(this.activeEvent.eventId,"failed","Claude process spawn failed"),this.clearActiveEvent())}),t(),!this.claudePty)if(process.platform==="win32")this.claudeChildPid=y?.pid??0;else{const E=await ni(y,v);if(t(),!Number.isFinite(E)||E<=0)throw new Error("failed to determine spawned Claude pid");this.claudeChildPid=E}if(!this.claudeChildPid||this.claudeChildPid<=0)throw new Error("failed to determine spawned Claude pid")}catch(_){if(y?.pid&&U(y,"SIGTERM"),this.claudePty){try{this.claudePty.kill()}catch{}this.claudePty=null}throw this.claudeProcess===y&&(this.claudeProcess=null),this.claudeChildPid=0,m&&this.releaseNotifyPortReservation(),this.stopMcpServer(),_}const F=this.claudeChildPid;o.info("claude-adapter","Claude child PID: "+F+(this.claudePty?" (via PTY)":"")),this.setupProcessOrPtyHandlers(),this.alive=!0}setupProcessOrPtyHandlers(){if(this.claudeProcess){const e=this.claudeProcess;e.on("exit",(t,i)=>{if(o.info("claude-adapter",`Claude process exited (code=${t}, signal=${i})`),this.singleTurnFallbackProcesses.delete(e)){o.info("claude-adapter","Ignoring stale Claude process exit after lifecycle ownership transfer");return}if(this.claudeProcess=null,this.claudeChildPid=0,this.alive=!1,this.releaseNotifyPortReservation(),this.stopMcpServer(),this.clearPendingMcpFailureTimer(),!this.tryRecoverSessionIdConflict()){if(this.activeEvent&&!this.stopped){const s=this.isEventAnswered(),n=s?"responded":"failed",r=s?void 0:"Claude process exited";o.error("claude-adapter",`Claude process exited with active event ${this.activeEvent.eventId}, answered=${s}, sending ${n}`),this.bridgeCallbacks.sendEventResult(this.activeEvent.eventId,n,r),this.clearActiveEvent()}this.stopped||this.emit("exit",t),this.compacting&&this.finishCompaction("process-exit")}}),e.stdout?.on("data",t=>{this.claudeProcess===e&&this.handleClaudeOutput(t.toString())}),e.stderr?.on("data",t=>{if(this.claudeProcess!==e)return;const i=t.toString().trim();i&&o.info("claude-adapter",`[claude stderr] ${i}`),this.checkFailurePatterns(i)})}if(this.claudePty){const e=this.claudePty;e.onExit(({exitCode:t,signal:i})=>{if(o.info("claude-adapter",`Claude PTY process exited (code=${t}, signal=${i})`),this.singleTurnFallbackPtys.delete(e)){o.info("claude-adapter","Ignoring stale Claude PTY exit after lifecycle ownership transfer");return}if(this.claudePty=null,this.claudeChildPid=0,this.alive=!1,this.releaseNotifyPortReservation(),this.stopMcpServer(),this.clearPendingMcpFailureTimer(),!this.tryRecoverSessionIdConflict()){if(this.activeEvent&&!this.stopped){const s=this.isEventAnswered(),n=s?"responded":"failed",r=s?void 0:"Claude process exited";o.error("claude-adapter",`Claude PTY exited with active event ${this.activeEvent.eventId}, answered=${s}, sending ${n}`),this.bridgeCallbacks.sendEventResult(this.activeEvent.eventId,n,r),this.clearActiveEvent()}this.stopped||this.emit("exit",t),this.compacting&&this.finishCompaction("process-exit")}}),e.onData(t=>{this.claudePty===e&&(this.handleClaudeOutput(t),this.checkFailurePatterns(t))})}}lastPtyOutputAt=0;ptyProbe={title:0,spinner:0,meaningful:0,lastLogAt:0,lastMeaningfulAt:0};recordPtyProbe(e){const t=Date.now();this.lastPtyOutputAt=t;const i=e.includes("]0;"),s=e.replace(/\x1b\[[0-9;?]*[ -/]*[@-~]/g,"").replace(/[\x00-\x1f\x7f-\x9f]/g,"").trim(),n=s.length<=3||/^[✳✶✻✽✵❋✺✴❈❖✦✧✢◉◎⬥⬦◇◆▸▹►▻→←↑↓⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏·…\d]*still\s+thinking/i.test(s);if(i?this.ptyProbe.title++:!s||n?this.ptyProbe.spinner++:(this.ptyProbe.meaningful++,this.ptyProbe.lastMeaningfulAt=t),t-this.ptyProbe.lastLogAt>=2e3){const r=this.activeEvent?this.isEventAnswered()?"replied":"running":"none",a=this.ptyProbe.lastMeaningfulAt?t-this.ptyProbe.lastMeaningfulAt:-1;o.debug("claude-adapter",`[pty-probe] session=${this.sessionId??"?"} event=${r} win2s title=${this.ptyProbe.title} spinner=${this.ptyProbe.spinner} meaningful=${this.ptyProbe.meaningful} meaningfulGapMs=${a}`),this.ptyProbe.title=0,this.ptyProbe.spinner=0,this.ptyProbe.meaningful=0,this.ptyProbe.lastLogAt=t}}handleClaudeOutput(e){this.recordPtyProbe(e),this.compacting&&this.resetCompactingActivityTimer();const t=e.trim();if(t){const a=t.replace(/\x1b\[[0-9;?]*[ -/]*[@-~]/g,"").replace(/[\x00-\x1f\x7f-\x9f\u200b-\u200f\u2028-\u202f\ufeff]/g,"").trim();a?a.length<=3||/^[✳✶✻✽✵❋✺✴❈❖✦✧✢◉◎⬥⬦◇◆▸▹►▻→←↑↓⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏·…\d]*still\s+thinking/i.test(a)?o.debug("claude-adapter",`[claude output] ${a}`):o.info("claude-adapter",`[claude output] ${a.slice(0,500)}`):o.debug("claude-adapter",`[claude output] ${t.slice(0,200).replace(/[^\x20-\x7e]/g,c=>`\\x${c.charCodeAt(0).toString(16).padStart(2,"0")}`)}`)}this.activeEvent&&this.markActiveEventActivity(this.activeEvent.eventId,this.activeEvent.sessionId);const i=t.match(/Session ID (\S+) is already in use/i);if(i){const a=i[1];this.sessionIdConflictDetected=!0;const c=[this.claudeProcess?.pid,this.claudeChildPid,this.claudePty?.pid].filter(l=>!!l&&l>0);ce(a,c)}const s=t.replace(/\[[0-9;?]*[ -/]*[@-~]/g," ").replace(/[^a-zA-Z]+/g," ").toLowerCase(),n=s.replace(/ /g,"");if(this.claudePty&&!this.startupChannelListening&&/trust.*folder|quick.*safety.*check/.test(n)&&(o.info("claude-adapter","Auto-accepting workspace trust dialog"),this.claudePty.write("1\r")),(/listeningforch\w*nelmessages/.test(n)||/nnelmessagesfrom/.test(n)||/inboundmessageswillbepushedintothissession/.test(n))&&(this.startupChannelListening||(this.startupChannelListeningAt=Date.now(),this.startupChannelListening=!0,this.sendDeferredModelSwitch()),this.clearPendingMcpFailureTimer()),!this.startupChannelListening&&/channels?arenotcurrentlyavailable/.test(n)&&(this.channelGateClosed=!0,o.warn("claude-adapter",'Claude reports "Channels are not currently available" \u2014 channel gate (tengu_harbor) is closed for this account/model; switching to single-turn fallback')),!this.mcpStartupFailureHandled&&s.includes("mcp server failed")){if(this.startupChannelListening){o.warn("claude-adapter","Claude reported MCP server failed for one channel, but channel listening is active; ignoring non-blocking failure");return}this.pendingMcpFailureTimer||(this.pendingMcpFailureTimer=setTimeout(()=>{this.pendingMcpFailureTimer=null,!(this.startupChannelListening||this.mcpStartupFailureHandled)&&this.markMcpStartupFailure()},Yt))}}checkFailurePatterns(e){/Session ID (\S+) is already in use/i.test(e)&&(this.sessionIdConflictDetected=!0),$t.some(t=>t.test(e))&&(o.error("claude-adapter",`Auth failure: ${e}`),this.authFailureUntil=Date.now()+Ae,this.activeEvent&&(this.bridgeCallbacks.sendStreamChunk(this.activeEvent.eventId,this.activeEvent.sessionId,`
|
|
9
9
|
|
|
10
10
|
Error: Claude authentication failed -- please re-login`,1,!1),this.bridgeCallbacks.sendEventResult(this.activeEvent.eventId,"failed","Claude authentication failed -- please re-login"),this.clearActiveEvent())),Rt.some(t=>t.test(e))&&(o.error("claude-adapter",`Usage limit: ${e}`),this.usageLimitUntil=Date.now()+Ae,this.activeEvent&&(this.bridgeCallbacks.sendStreamChunk(this.activeEvent.eventId,this.activeEvent.sessionId,`
|
|
11
11
|
|
|
12
12
|
Error: Claude usage limit reached -- waiting for reset`,1,!1),this.bridgeCallbacks.sendEventResult(this.activeEvent.eventId,"failed","Claude usage limit reached -- waiting for reset"),this.clearActiveEvent())),Dt.test(e)&&this.activeEvent&&(o.error("claude-adapter",`API format error (400 server_tool_use.id): ${e.slice(0,200)}`),this.activeEvent.apiFormatError=!0)}async ensureClaudePluginDir(){const e=this.resolveProjectRoot(),t=p(w().dataDir,"claude-plugin"),i=p(t,".claude-plugin"),s=p(i,"plugin.json"),n=p(e,".claude-plugin","plugin.json");await O(i,{recursive:!0});let r="";try{r=await T(n,"utf8")}catch{r=`${JSON.stringify({name:"grix-connector",version:"0.1.0",description:"Claude Code channel plugin for grix-connector.",license:"Apache-2.0"},null,2)}
|
|
13
13
|
`}let a="";try{a=await T(s,"utf8")}catch{}a!==r&&(await R(s,r,"utf8"),o.info("claude-adapter",`Wrote Claude plugin manifest: ${s}`));const c=p(e,"dist","scripts"),l=_=>`"${String(_).replace(/"/g,'\\"')}"`,u=_=>`${l(process.execPath)} ${l(p(c,_))}`,h=`${JSON.stringify({hooks:{SessionStart:[{hooks:[{type:"command",command:u("lifecycle-hook.js")}]}],Elicitation:[{hooks:[{type:"command",command:u("elicitation-hook.js"),timeout:630}]}],ElicitationResult:[{matcher:"",hooks:[{type:"command",command:u("lifecycle-hook.js")}]}],UserPromptSubmit:[{hooks:[{type:"command",command:u("user-prompt-submit-hook.js")}]}],PreToolUse:[{matcher:"ExitPlanMode",hooks:[{type:"command",command:u("approve-plan-hook.js")}]},{matcher:"",hooks:[{type:"command",command:u("lifecycle-hook.js")}]}],PostToolUse:[{matcher:"",hooks:[{type:"command",command:u("lifecycle-hook.js")}]}],PostToolUseFailure:[{matcher:"",hooks:[{type:"command",command:u("lifecycle-hook.js")}]}],PermissionRequest:[{matcher:"",hooks:[{type:"command",command:u("permission-hook.js"),timeout:330}]}],Notification:[{matcher:"idle_prompt",hooks:[{type:"command",command:u("notification-hook.js")}]}],PermissionDenied:[{matcher:"",hooks:[{type:"command",command:u("lifecycle-hook.js")}]}],Stop:[{hooks:[{type:"command",command:u("lifecycle-hook.js")}]}],StopFailure:[{hooks:[{type:"command",command:u("lifecycle-hook.js")}]}],PreCompact:[{hooks:[{type:"command",command:u("lifecycle-hook.js")}]}],PostCompact:[{hooks:[{type:"command",command:u("lifecycle-hook.js")}]}],ConfigChange:[{hooks:[{type:"command",command:u("lifecycle-hook.js")}]}]}},null,2)}
|
|
14
|
-
`,f=p(t,"hooks"),m=p(f,"hooks.json");await O(f,{recursive:!0});let v="";try{v=await T(m,"utf8")}catch{}v!==h&&(await R(m,h,"utf8"),o.info("claude-adapter",`Wrote Claude hooks config: ${m}`));const y=p(t,"skills"),F=wt(y);return F.length>0&&o.info("claude-adapter",`Synced connector skills to plugin: [${F.join(", ")}]`),t}async ensureStdioMcpServer(){const e=this.resolveProjectRoot(),t=this.resolveStdioServerPath(e);if(this.ensureStdioServerArtifact(e,t),!P(t))throw new Error(`MCP stdio server entry point not found: ${t}`);const i=this.getInternalApiUrl(),s=this.notifyPort,n=[t,"--handle-url",i,"--notify-port",String(s)],r=p(e,"dist","mcp","mcp-bridge-server.js"),a=this.internalApi?.mcpBridgeWsUrl,c=P(r)&&a?[r,"--ws-url",a]:void 0;c||o.warn("claude-adapter",`APP MCP bridge skipped (path=${P(r)} ws=${!!a})`);const l=ri(this.claudeCliSessionId);await ci(l,n,c),this.claudeMcpConfigPath=l,this.mcpServerReady=!0,this.startActivityTracking()}resolveProjectRoot(){const e=ct(import.meta.url);return ot(e,"..","..","..","..")}resolveStdioServerPath(e=this.resolveProjectRoot()){return p(e,"dist","mcp","stdio","server.js")}ensureStdioServerArtifact(e,t){if(P(t)||Ne)return;Ne=!0;const i=p(e,"node_modules","typescript","bin","tsc"),s=p(e,"tsconfig.json");if(!P(i)||!P(s))return;o.warn("claude-adapter",`MCP stdio server artifact missing, attempting build: ${t}`);const n=ee(process.execPath,[i,"-p",s],{cwd:e,env:process.env,encoding:"utf8",timeout:6e4});if(n.status!==0){const r=`${n.stderr??""}${n.stdout??""}`.trim();throw new Error(`MCP stdio server build failed: ${r||`exit ${n.status??"unknown"}`}`)}}getInternalApiUrl(){return this.internalApi?this.internalApi.url:process.env.GRIX_CONNECTOR_INTERNAL_API?process.env.GRIX_CONNECTOR_INTERNAL_API:`http://127.0.0.1:${this.internalApiPort}`}internalApiPort=0;notifyPort=0;notifySocket=null;async waitForNotifyPort(e){if(this.notifyPort<=0)return;const t=Date.now();for(;Date.now()-t<e;)try{await new Promise((i,s)=>{const n=ne.createConnection({host:"127.0.0.1",port:this.notifyPort},()=>{n.destroy(),i()});n.on("error",s),setTimeout(()=>{n.destroy(),s(new Error("probe timeout"))},2e3)});return}catch{await new Promise(i=>setTimeout(i,500))}throw new Error(`Notify port ${this.notifyPort} not ready within ${e}ms`)}async waitForChannelListening(e){const t=Date.now(),i=8e3;for(;Date.now()-t<e;){if(this.channelGateClosed)throw new Error("channels are not currently available (tengu_harbor gate closed)");if(this.startupChannelListening){const s=this.startupChannelListeningAt||Date.now(),n=Date.now()-s;if(n>=V)return;await new Promise(r=>setTimeout(r,V-n));return}if(this.alive&&this.notifyPort>0&&Date.now()-t>i){o.info("claude-adapter",`Channel listener fallback: notify port connected, assuming ready after ${Date.now()-t}ms (resume mode may skip "Listening" output)`),this.startupChannelListeningAt=Date.now(),this.startupChannelListening=!0,this.clearPendingMcpFailureTimer(),this.sendDeferredModelSwitch();return}await new Promise(s=>setTimeout(s,200))}throw new Error(`Claude channel listener not ready within ${e}ms`)}ptyAutoConfirmTimer=null;startPtyAutoConfirm(e){this.ptyAutoConfirmTimer&&clearInterval(this.ptyAutoConfirmTimer);let t=!1;const i=()=>{if(this.startupChannelListening||this.stopped){this.ptyAutoConfirmTimer&&(clearInterval(this.ptyAutoConfirmTimer),this.ptyAutoConfirmTimer=null);return}try{e.write("\r"),t||(o.info("claude-adapter","PTY auto-confirm: sending Enter for dev channels dialog"),t=!0)}catch{}};setTimeout(i,1e3),this.ptyAutoConfirmTimer=setInterval(i,2e3),setTimeout(()=>{this.ptyAutoConfirmTimer&&(clearInterval(this.ptyAutoConfirmTimer),this.ptyAutoConfirmTimer=null)},3e4).unref()}sendDeferredModelSwitch(){if(!this.deferredModelId)return;const e=this.deferredModelId;this.deferredModelId=null,setTimeout(()=>{const t=this.claudePty??this.claudeProcess?.stdin;if(!(!t||this.stopped))try{const i=`/model ${e}
|
|
14
|
+
`,f=p(t,"hooks"),m=p(f,"hooks.json");await O(f,{recursive:!0});let v="";try{v=await T(m,"utf8")}catch{}v!==h&&(await R(m,h,"utf8"),o.info("claude-adapter",`Wrote Claude hooks config: ${m}`));const y=p(t,"skills"),F=wt(y);return F.length>0&&o.info("claude-adapter",`Synced connector skills to plugin: [${F.join(", ")}]`),t}async ensureStdioMcpServer(){const e=this.resolveProjectRoot(),t=this.resolveStdioServerPath(e);if(this.ensureStdioServerArtifact(e,t),!P(t))throw new Error(`MCP stdio server entry point not found: ${t}`);const i=this.getInternalApiUrl(),s=this.notifyPort,n=[t,"--handle-url",i,"--notify-port",String(s)],r=p(e,"dist","mcp","mcp-bridge-server.js"),a=this.internalApi?.mcpBridgeWsUrl,c=P(r)&&a?[r,"--ws-url",a]:void 0;c||o.warn("claude-adapter",`APP MCP bridge skipped (path=${P(r)} ws=${!!a})`);const l=ri(this.claudeCliSessionId);await ci(l,n,c),this.claudeMcpConfigPath=l,this.mcpServerReady=!0,this.startActivityTracking()}resolveProjectRoot(){const e=ct(import.meta.url);return ot(e,"..","..","..","..")}resolveStdioServerPath(e=this.resolveProjectRoot()){return p(e,"dist","mcp","stdio","server.js")}ensureStdioServerArtifact(e,t){if(P(t)||Ne)return;Ne=!0;const i=p(e,"node_modules","typescript","bin","tsc"),s=p(e,"tsconfig.json");if(!P(i)||!P(s))return;o.warn("claude-adapter",`MCP stdio server artifact missing, attempting build: ${t}`);const n=ee(process.execPath,[i,"-p",s],{cwd:e,env:process.env,encoding:"utf8",timeout:6e4,windowsHide:!0});if(n.status!==0){const r=`${n.stderr??""}${n.stdout??""}`.trim();throw new Error(`MCP stdio server build failed: ${r||`exit ${n.status??"unknown"}`}`)}}getInternalApiUrl(){return this.internalApi?this.internalApi.url:process.env.GRIX_CONNECTOR_INTERNAL_API?process.env.GRIX_CONNECTOR_INTERNAL_API:`http://127.0.0.1:${this.internalApiPort}`}internalApiPort=0;notifyPort=0;notifySocket=null;async waitForNotifyPort(e){if(this.notifyPort<=0)return;const t=Date.now();for(;Date.now()-t<e;)try{await new Promise((i,s)=>{const n=ne.createConnection({host:"127.0.0.1",port:this.notifyPort},()=>{n.destroy(),i()});n.on("error",s),setTimeout(()=>{n.destroy(),s(new Error("probe timeout"))},2e3)});return}catch{await new Promise(i=>setTimeout(i,500))}throw new Error(`Notify port ${this.notifyPort} not ready within ${e}ms`)}async waitForChannelListening(e){const t=Date.now(),i=8e3;for(;Date.now()-t<e;){if(this.channelGateClosed)throw new Error("channels are not currently available (tengu_harbor gate closed)");if(this.startupChannelListening){const s=this.startupChannelListeningAt||Date.now(),n=Date.now()-s;if(n>=V)return;await new Promise(r=>setTimeout(r,V-n));return}if(this.alive&&this.notifyPort>0&&Date.now()-t>i){o.info("claude-adapter",`Channel listener fallback: notify port connected, assuming ready after ${Date.now()-t}ms (resume mode may skip "Listening" output)`),this.startupChannelListeningAt=Date.now(),this.startupChannelListening=!0,this.clearPendingMcpFailureTimer(),this.sendDeferredModelSwitch();return}await new Promise(s=>setTimeout(s,200))}throw new Error(`Claude channel listener not ready within ${e}ms`)}ptyAutoConfirmTimer=null;startPtyAutoConfirm(e){this.ptyAutoConfirmTimer&&clearInterval(this.ptyAutoConfirmTimer);let t=!1;const i=()=>{if(this.startupChannelListening||this.stopped){this.ptyAutoConfirmTimer&&(clearInterval(this.ptyAutoConfirmTimer),this.ptyAutoConfirmTimer=null);return}try{e.write("\r"),t||(o.info("claude-adapter","PTY auto-confirm: sending Enter for dev channels dialog"),t=!0)}catch{}};setTimeout(i,1e3),this.ptyAutoConfirmTimer=setInterval(i,2e3),setTimeout(()=>{this.ptyAutoConfirmTimer&&(clearInterval(this.ptyAutoConfirmTimer),this.ptyAutoConfirmTimer=null)},3e4).unref()}sendDeferredModelSwitch(){if(!this.deferredModelId)return;const e=this.deferredModelId;this.deferredModelId=null,setTimeout(()=>{const t=this.claudePty??this.claudeProcess?.stdin;if(!(!t||this.stopped))try{const i=`/model ${e}
|
|
15
15
|
`;"write"in t?t.write(i):t.write(i,()=>{}),o.info("claude-adapter",`Deferred model switch: /model ${e}`)}catch{}},3e3)}async waitForWindowsChannelReady(e){const t=Date.now();let i=!1;const s=setInterval(()=>{if(this.startupChannelListening){clearInterval(s);return}try{this.claudePty?(this.claudePty.write("\r"),i||(o.info("claude-adapter","Windows PTY: sending Enter to auto-confirm dev channels dialog"),i=!0)):this.claudeProcess?.stdin?.writable&&(this.claudeProcess.stdin.write("\r"),i||(o.info("claude-adapter","Windows shell: sending Enter to auto-confirm dev channels dialog"),i=!0))}catch{}},3e3);try{for(;Date.now()-t<e;){if(this.channelGateClosed)throw new Error("channels are not currently available (tengu_harbor gate closed)");if(this.startupChannelListening){const a=this.startupChannelListeningAt||Date.now(),c=Date.now()-a;if(c>=V)return;await new Promise(l=>setTimeout(l,V-c));return}const n=Date.now()-t,r=this.claudePty?Vt:12e3;if(this.alive&&this.mcpServerReady&&n>r){o.info("claude-adapter",`Windows ${this.claudePty?"PTY":"shell"} fallback: assuming channel ready after ${n}ms (no stdout detection, MCP server connected)`),this.startupChannelListeningAt=Date.now(),this.startupChannelListening=!0,this.clearPendingMcpFailureTimer(),this.sendDeferredModelSwitch();return}await new Promise(a=>setTimeout(a,500))}}finally{clearInterval(s)}throw new Error(`Windows channel ready check timed out within ${e}ms`)}pushNotification(e,t){if(!this.mcpServerReady||this.notifyPort<=0)return;const i=JSON.stringify({jsonrpc:"2.0",method:e,params:t});!this.notifySocket||this.notifySocket.destroyed?(this.notifySocket=ne.createConnection({host:"127.0.0.1",port:this.notifyPort},()=>{this.notifySocket.write(i+`
|
|
16
16
|
`)}),this.notifySocket.on("error",s=>{o.error("claude-adapter",`Notify socket error: ${s.message}`),this.notifySocket=null}),this.notifySocket.on("close",()=>{this.notifySocket=null})):this.notifySocket.write(i+`
|
|
17
17
|
`)}stopMcpServer(){if(this.mcpServerReady=!1,this.notifySocket){try{this.notifySocket.destroy()}catch{}this.notifySocket=null}if(this.mcpServerProcess){try{this.mcpServerProcess.kill("SIGTERM")}catch{}this.mcpServerProcess=null}}releaseNotifyPortReservation(){Kt(this.notifyPort),this.notifyPort=0}killClaudeProcess(e){const t=this.claudeProcess,i=this.claudePty,s=this.claudeChildPid;if(e==="single-turn fallback isolation"&&(t&&this.singleTurnFallbackProcesses.add(t),i&&this.singleTurnFallbackPtys.add(i)),this.claudeProcess=null,this.claudePty=null,this.claudeChildPid=0,this.spawnPromise=null,this.alive=!1,this.stopMcpServer(),this.releaseNotifyPortReservation(),this.mcpChannelBroken=!1,this.channelGateClosed=!1,this.startupChannelListening=!1,this.startupChannelListeningAt=0,this.deferredModelId=null,this.clearPendingMcpFailureTimer(),this.sessionIdConflictDetected=!1,this.pendingPermissions.size>0){const n=w(),r=new W(n.permissionRequestsDir);for(const[a]of this.pendingPermissions)r.resolveRequest(a,"deny").catch(()=>{});this.pendingPermissions.clear()}if(i)try{i.kill()}catch{}if(t?.pid&&U(t,"SIGTERM"),s>0)try{process.kill(s,"SIGTERM")}catch{}if(t?.pid||i||s>0){const n=s,r=t,a=i;setTimeout(()=>{if(a)try{a.kill()}catch{}if(r?.pid&&U(r,"SIGKILL"),n>0)try{process.kill(n,"SIGKILL")}catch{}},5e3).unref()}o.info("claude-adapter",`Claude process killed (reason=${e}, pid=${s}, expectPid=${t?.pid})`)}tryRecoverSessionIdConflict(){if(!this.sessionIdConflictDetected||this.stopped||!this.activeEvent||this.isEventAnswered())return!1;const e=this.activeEvent.eventId;if(this.sessionIdConflictRetriedEventIds.has(e))return this.sessionIdConflictDetected=!1,!1;this.sessionIdConflictRetriedEventIds.add(e),this.sessionIdConflictDetected=!1;const t=this.activeEvent.rawEvent;return o.warn("claude-adapter",`Detected Claude session-id conflict, auto-retrying event once: ${e}`),this.clearActiveEventIdleTimer(),this.clearActiveEventHardTimer(),this.activeEvent=null,this.stopComposing(),this.deliverInboundEvent(t),!0}recoverMalformedToolCall(e){if(this.stopped||!this.activeEvent||this.isEventAnswered()||!this.claudeCliSessionId||!this.claudeSessionCwd)return"none";const t=q(this.claudeCliSessionId,this.claudeSessionCwd,this.activeEvent.jsonlBaseOffset);if(!t||t.stopReason!=="stop_sequence"||t.text.trim()!==jt)return"none";if(this.malformedToolRetriedEventIds.has(e))return o.warn("claude-adapter",`Malformed tool-call retry exhausted for ${e}, sending clean fallback`),"exhausted";this.malformedToolRetriedEventIds.add(e);const i={...this.activeEvent.rawEvent,content:Nt};return o.warn("claude-adapter",`Detected malformed tool-call, silently retrying event once: ${e}`),this.clearActiveEventIdleTimer(),this.clearActiveEventHardTimer(),this.activeEvent=null,this.stopComposing(),this.deliverInboundEvent(i),"retried"}async handleInternalInvoke(e,t,i,s){if(e==="event_tool_call"){const n=String(t.tool_name??""),r=St(n,{...t.arguments??{}}),a=this.getEventToolHandle();if(this.activeEvent){const l=this.activeEvent,u=String(r.event_id??"").trim();u===""?r.event_id=l.eventId:(n==="grix_reply"||n==="grix_complete")&&u!==l.eventId&&(o.warn("claude-adapter",`${n}: event_id mismatch, correcting: supplied=${u}, active=${l.eventId}`),r.event_id=l.eventId);const h=String(r.session_id??"").trim();if(n==="grix_reply"?(h&&h!==l.sessionId&&o.warn("claude-adapter",`${n}: session_id mismatch, correcting: supplied=${h}, active=${l.sessionId}`),r.session_id=l.sessionId):h===""&&(r.session_id=l.sessionId),n==="grix_reply"&&String(r.quoted_message_id??"").trim()===""){const f=String(l.rawEvent?.msg_id??"").trim();f&&(r.quoted_message_id=f)}n==="grix_reply"&&o.info("claude-adapter",`grix_reply quote: rawEvent.msg_id=${String(l.rawEvent?.msg_id??"")||"(none)"} -> quoted_message_id=${String(r.quoted_message_id??"")||"(none)"}`)}else if(this.lastClearedEvent&&n==="grix_reply"){const l=this.lastClearedEvent;Date.now()-l.ts<Wt&&(String(r.session_id??"").trim()===""&&(r.session_id=l.sessionId),r.event_id="",o.info("claude-adapter",`Late grix_reply fallback: sending direct message for cleared event ${l.eventId} (cleared ${(Date.now()-l.ts)/1e3}s ago)`))}n==="grix_reply"&&String(r.event_id??"").trim()!==""&&this.completedEventIds.has(String(r.event_id??"").trim())&&(o.info("claude-adapter",`Late grix_reply fallback: sending direct message for completed event ${String(r.event_id??"").trim()}`),r.event_id="");const c=yt(n,r);if(!c.valid)throw new Error(`\u53C2\u6570\u6821\u9A8C\u5931\u8D25: ${c.error}`);if(a.status!=="ready")throw new Error(`\u8FDE\u63A5\u4E0D\u53EF\u7528: \u5F53\u524D\u72B6\u6001\u4E3A ${a.status}`);if(gt(n)){const l=this.activeEvent;l&&(l.toolCallInFlight=!0);try{if(i?.aborted)throw new Error("invoke aborted by timeout");const u=vt(a,n,r);if(u.isError)throw new Error(u.content[0]?.text??"event tool failed");if(i?.aborted)throw new Error("invoke aborted by timeout after send");return this.postProcessEventToolCall(n,r),JSON.parse(u.content[0]?.text??"null")}finally{if(l&&(l.toolCallInFlight=!1,l.pendingStopHook&&this.activeEvent===l)){const u=l.pendingStopHook;l.pendingStopHook=void 0,this.finalizeActiveEvent(u)}}}throw new Error(`\u672A\u77E5\u4E8B\u4EF6\u5DE5\u5177: ${n}`)}return this.bridgeCallbacks.agentInvoke(e,t,s)}getEventToolHandle(){const e=this;return{status:"ready",getStatusSnapshot:()=>({status:"ready",connectedAt:Date.now(),reconnectAttempts:0}),sendEventAck:t=>{e.bridgeCallbacks.sendEventAck(t.event_id,t.session_id??"")},sendStreamChunk:t=>{e.bridgeCallbacks.sendStreamChunk(t.event_id??"",t.session_id,t.delta_content??"",Number(t.chunk_seq??0)||1,t.is_finish===!0,t.client_msg_id,t.quoted_message_id)},sendMsg:t=>{if(typeof e.bridgeCallbacks.sendDirectMessage!="function"){o.warn("claude-adapter","sendDirectMessage callback not provided, dropping direct message");return}e.bridgeCallbacks.sendDirectMessage({sessionId:t.session_id,clientMsgId:t.client_msg_id,content:t.content,quotedMessageId:t.quoted_message_id})},sendEventResult:t=>{e.bridgeCallbacks.sendEventResult(t.event_id,t.status,t.msg,t.code)},sendSessionActivitySet:t=>{}}}postProcessEventToolCall(e,t){const i=String(t.event_id??"").trim();if(!i||this.activeEvent?.eventId!==i){o.warn("claude-adapter",`postProcessEventToolCall: event_id mismatch (tool=${e}, eventId=${i}, activeEventId=${this.activeEvent?.eventId??"none"})`);return}if(e==="grix_complete"){this.completedEventIds.set(i,Date.now()),this.clearActiveEvent();return}if(e==="grix_reply"){const s=!this.activeEvent.replied;this.activeEvent.replied=!0;const n=String(t.text??"");n.length>0&&(this.activeEvent.lastReplyText=n),this.markActiveEventActivity(i,String(t.session_id??"").trim()||void 0),s&&(this.activeEvent.repliedAt=Date.now(),this.startPostReplyDeadline(i),this.startPostReplyJsonlWatcher(i))}}resolveSessionRuntime(){if(!this.runtimeResolver)return{};try{return this.runtimeResolver(this.sessionId)??{}}catch(e){throw new Error(`resolve session runtime failed: ${e instanceof Error?e.message:String(e)}`)}}async validatePluginDir(e){const t=String(e??"").trim();if(!t)return;let i;try{i=await Ee(t)}catch{throw new Error(`pluginDir is not accessible: ${t}`)}if(!i.isDirectory())throw new Error(`pluginDir is not a directory: ${t}`);const s=p(t,".mcp.json");try{(await Ee(s)).isFile()&&(await ye(s),o.info("claude-adapter",`Removed conflicting .mcp.json from pluginDir: ${s}`))}catch{}}async ensureWorkspaceTrust(e){const t=p(I(),".claude.json");try{const i=await T(t,"utf8"),s=JSON.parse(i);if(s.projects?.[e]?.hasTrustDialogAccepted===!0)return;s.projects||(s.projects={}),s.projects[e]||(s.projects[e]={}),s.projects[e].hasTrustDialogAccepted=!0,await R(t,JSON.stringify(s),"utf8"),o.info("claude-adapter",`Pre-trusted workspace: ${e}`)}catch(i){o.warn("claude-adapter",`Failed to pre-trust workspace ${e}: ${i}`)}}async ensureSkipDangerousPermissionPrompt(){const e=p(I(),".claude","settings.json");try{let t={};try{t=JSON.parse(await T(e,"utf8"))}catch{}if(t.skipDangerousModePermissionPrompt===!0)return;t.skipDangerousModePermissionPrompt=!0,await O(p(I(),".claude"),{recursive:!0}),await R(e,JSON.stringify(t,null,2),"utf8"),o.info("claude-adapter","Set skipDangerousModePermissionPrompt=true in user settings to skip BypassPermissions dialog")}catch(t){o.warn("claude-adapter",`Failed to set skipDangerousModePermissionPrompt: ${t}`)}}async isChannelGateClosed(){if(this.channelGateClosed)return!0;try{const e=await T(p(I(),".claude.json"),"utf8"),i=JSON.parse(e).cachedGrowthBookFeatures;return!i||Object.keys(i).length===0?!1:i.tengu_harbor!==!0}catch{return!1}}async ensureClaudeOnboardingFlags(e){const t=p(I(),".claude.json");try{let i;try{const r=await T(t,"utf8");i=JSON.parse(r)}catch{i={}}let s=!1;i.hasCompletedOnboarding||(i.hasCompletedOnboarding=!0,i.lastOnboardingVersion||(i.lastOnboardingVersion="2.1.31"),s=!0),i.projects||(i.projects={});const n=i.projects;if(n[e]||(n[e]={}),n[e].hasCompletedProjectOnboarding||(n[e].hasCompletedProjectOnboarding=!0,s=!0),this.config.env?.ANTHROPIC_API_KEY===Pe){const r=Pe.slice(-20),a=i.customApiKeyResponses??{},c=a.approved??[],l=a.rejected??[];(!c.includes(r)||l.includes(r))&&(i.customApiKeyResponses={...a,approved:[...c.filter(u=>u!==r),r],rejected:l.filter(u=>u!==r)},s=!0)}if(!s)return;await R(t,JSON.stringify(i,null,2),"utf8"),o.info("claude-adapter",`Marked Claude onboarding complete: ${e}`)}catch(i){o.warn("claude-adapter",`Failed to mark Claude onboarding complete: ${i}`)}}async injectStatusLineSettings(e){try{const t=this.resolveProjectRoot(),i=p(t,"dist","scripts","status-line-forwarder.js"),s=p(e,".claude"),n=p(s,"settings.json");await O(s,{recursive:!0});let r={};try{r=JSON.parse(await T(n,"utf8"))}catch{}const c={type:"command",command:`node "${i.replace(/\\/g,"/")}"`,refreshInterval:10};r.statusLine=c;const l=`${JSON.stringify(r,null,2)}
|
|
18
|
-
`;await R(n,l,"utf8"),o.info("claude-adapter",`Injected statusLine settings: ${n}`)}catch(t){o.warn("claude-adapter",`Failed to inject statusLine settings: ${t instanceof Error?t.message:t}`)}}async ensureUserMcpServer(e,t,i){const s=this.resolveServerEntryPath(t),n=process.execPath,r=[s],a=p(I(),".claude.json");let c=null;try{const f=await T(a,"utf8");c=JSON.parse(f)?.mcpServers?.[M]??null}catch{}if(c&&String(c.type||"stdio").trim()==="stdio"&&String(c.command??"").trim()===n&&Array.isArray(c.args)&&c.args.length===r.length&&c.args.every((f,m)=>f===r[m]))return;o.info("claude-adapter",`Registering user-scoped MCP server: ${M} -> ${n} ${r.join(" ")}`);try{te(`${e} mcp remove -s user ${M}`,{encoding:"utf8",timeout:1e4,env:i,stdio:"pipe"})}catch{}const l=["mcp","add","--scope","user",M,"--",n,...r],u=process.platform==="win32"?'"':"'",h=te(`${e} ${l.map(f=>`${u}${f}${u}`).join(" ")}`,{encoding:"utf8",timeout:1e4,env:i,stdio:"pipe"});o.info("claude-adapter",`MCP server registered: ${h.trim()||"ok"}`)}resolveServerEntryPath(e){const t=p(e,"server","main.js");try{if(j(t))return t}catch{}const i=p(e,"dist","index.js");try{if(j(i))return i}catch{}throw new Error(`Cannot find grix-claude server entry in pluginDir: ${e}`)}clearActiveEvent(){const e=this.activeEvent;this.clearActiveEventIdleTimer(),this.clearActiveEventHardTimer(),this.clearActiveEventPostReplyTimer(),this.clearActiveEventPostReplyWatcher(),this.clearRescueSettleTimer(),e&&(this.snapshotAuditBoundary(e),this.sessionIdConflictRetriedEventIds.delete(e.eventId),this.malformedToolRetriedEventIds.delete(e.eventId),this.completedEventIds.set(e.eventId,Date.now()),this.lastClearedEvent={eventId:e.eventId,sessionId:e.sessionId,ts:Date.now()},this.emit(`reply:${e.sessionId}`,{status:"completed"}),this.emit("eventDone",e.eventId)),this.activeEvent=null,this.stopComposing()}startComposing(e,t){}stopComposing(){}clearComposingTimer(){}resolveHookSignalsPath(){const e=w(),t=this.claudeCliSessionId||this.sessionId;return p(e.dataDir,`hook-signals-${t}.json`)}startActivityTracking(){this.activityManager&&this.activityManager.stop();const e=this.resolveHookSignalsPath(),t=w().hookSignalsLogPath,i=new pt(e,t);o.info("claude-adapter",`Activity tracking started: watching ${e}`),this.activityManager=new ft({hookSignalStore:i,onActivity:s=>this.onHookActivity(s),onStop:()=>this.onClaudeTurnEnd("Stop"),onStopFailure:()=>this.onClaudeTurnEnd("StopFailure"),onCompactStart:()=>this.beginCompaction(),onCompactResult:()=>this.finishCompaction("post-compact-hook"),onPermissionRequest:(s,n)=>this.handlePermissionHookEvent(s,n)}),this.activityManager.start()}onClaudeTurnEnd(e){if(o.info("claude-adapter",`Hook activity: ${e}`),!this.activeEvent){this.stopHookBarrierSessionId&&(o.info("claude-adapter",`Late Stop hook after end_turn finalize \u2014 releasing barrier (session=${this.stopHookBarrierSessionId})`),this.clearStopHookBarrier());return}if(this.activeEvent.toolCallInFlight){o.info("claude-adapter",`Stop hook deferred: toolCallInFlight for ${this.activeEvent.eventId}`),this.activeEvent.pendingStopHook=e;return}if(this.activeEvent.singleTurnFallback&&!this.isEventAnswered()){o.info("claude-adapter",`${e} hook ignored for ${this.activeEvent.eventId}: single-turn fallback owns finalization`);return}this.finalizeActiveEvent(e)}forwardIncrementalText(e){const t=this.activeEvent;if(!t||t.eventId!==e||t.replied||!this.claudeCliSessionId||!this.claudeSessionCwd)return;const i=t.textForwardOffset??t.jsonlBaseOffset??0,{segments:s,newOffset:n}=Ct(this.claudeCliSessionId,this.claudeSessionCwd,i);if(t.textForwardOffset=n,s.length===0)return;const r=t.sessionId;for(const a of s){const c=a.trim();if(!c)continue;const l=`txt_${e}_${this.textForwardSeq++}`;this.bridgeCallbacks.sendStreamChunk(e,r,c,1,!1,l),this.bridgeCallbacks.sendStreamChunk(e,r,"",2,!0,l),t.textForwarded||(t.textForwarded=!0,t.repliedAt===void 0&&(t.repliedAt=Date.now()),this.startPostReplyDeadline(e),this.startPostReplyJsonlWatcher(e))}this.markActiveEventActivity(e,r),o.info("claude-adapter",`Forwarded ${s.length} plain-text segment(s) for ${e}`)}flushForwardedTail(e){this.activeEvent?.eventId===e&&this.activeEvent.textForwarded&&this.forwardIncrementalText(e)}isEventAnswered(e=this.activeEvent){return!!(e&&(e.replied||e.textForwarded))}attemptRescueFromJsonl(e,t){if(!this.claudeCliSessionId||!this.claudeSessionCwd)return o.info("claude-adapter",`Rescue skipped: no claudeCliSessionId or claudeSessionCwd for ${e}`),"none";const i=this.activeEvent?.jsonlBaseOffset,s=q(this.claudeCliSessionId,this.claudeSessionCwd,i);if(s?.metaOnly)return i===void 0?(o.warn("claude-adapter",`Rescue for ${e}: only CLI meta text found but jsonlBaseOffset is unset \u2014 cannot attribute it to this turn, treating as unanswered`),"none"):(o.info("claude-adapter",`Rescue skipped for ${e}: this turn wrote only CLI meta text, finalizing silently`),"silent");const n=s?.text||null;if(!n)return o.info("claude-adapter",`Rescue failed: no assistant text found in JSONL for ${e}`),"none";const r=`rescue_${e}_${Date.now()}`;return this.bridgeCallbacks.sendStreamChunk(e,t,n,1,!1,r),this.bridgeCallbacks.sendStreamChunk(e,t,"",2,!0,r),o.info("claude-adapter",`Rescue succeeded for event ${e}: sent ${n.length} chars from JSONL`),"sent"}warnUnsentFinalIfAny(e){if(!this.claudeCliSessionId||!this.claudeSessionCwd)return;const t=this.activeEvent?.jsonlBaseOffset,i=q(this.claudeCliSessionId,this.claudeSessionCwd,t);if(!i||i.stopReason!=="end_turn")return;const s=i.text?.trim()??"";if(!s)return;const n=(this.activeEvent?.lastReplyText??"").trim();n&&(s===n||n.includes(s))||o.warn("claude-adapter",`Unsent final end_turn text for ${e} (${s.length} chars) not delivered \u2014 \u5DF2\u5E94\u7B54\u8FC7\uFF0C\u6309\u7B56\u7565\u4E0D\u8865\u53D1\uFF08\u7591\u4F3C\u56DE\u5408\u6536\u5C3E\u81EA\u8FF0\uFF1B\u82E5\u786E\u4E3A\u6F0F\u53D1\u7ED3\u8BBA\u53EF\u636E\u6B64\u6392\u67E5\uFF09`)}finalizeActiveEvent(e){if(!this.activeEvent)return;const t=this.activeEvent.eventId,i=this.activeEvent.sessionId;if(this.isEventAnswered()&&this.activeEvent.jsonlBaseOffset!==void 0&&this.claudeCliSessionId&&this.claudeSessionCwd){const s=q(this.claudeCliSessionId,this.claudeSessionCwd,this.activeEvent.jsonlBaseOffset);if(!s||s.stopReason!=="end_turn")if(we(this.claudeCliSessionId,this.claudeSessionCwd,this.activeEvent.jsonlBaseOffset))o.info("claude-adapter",`Stop hook: terminal tool_result detected for ${t} (grix_reply as final action, no end_turn expected), finalizing as responded`);else{o.info("claude-adapter",`Stop hook suppressed for ${t}: no end_turn in JSONL after offset=${this.activeEvent.jsonlBaseOffset} \u2014 likely resume-drain hook, waiting for JSONL watcher`),this.markActiveEventActivity(t,i);return}}if(this.flushForwardedTail(t),!this.activeEvent.apiFormatError){if(this.activeEvent.replied||this.activeEvent.textForwarded){this.activeEvent.replied&&!this.activeEvent.textForwarded&&this.warnUnsentFinalIfAny(t),this.finishTurnEnd(t,i,e,!0,`replied=${this.activeEvent.replied}, textForwarded=${this.activeEvent.textForwarded}`);return}if(this.activeEvent.rescueSettleInFlight){o.info("claude-adapter",`${e} ignored for ${t}: rescue retry already in flight`);return}const s=this.recoverMalformedToolCall(t);if(s==="retried")return;if(s==="exhausted"){this.bridgeCallbacks.sendStreamChunk(t,i,Bt,1,!1,`mtc_${t}`),this.bridgeCallbacks.sendStreamChunk(t,i,"",2,!0,`mtc_${t}`),this.finishTurnEnd(t,i,e,!0,"malformed-toolcall-fallback");return}const n=this.attemptRescueFromJsonl(t,i);if(n!=="none"){this.finishTurnEnd(t,i,e,!0,n==="silent"?"no-response-requested":"rescued");return}this.beginRescueSettleWait(t,i,e);return}this.finishTurnEnd(t,i,e,!1,"apiFormatError")}beginRescueSettleWait(e,t,i){if(!this.activeEvent||this.activeEvent.eventId!==e)return;this.activeEvent.rescueSettleInFlight=!0;const s=Math.max(1,Math.ceil(Le/xe));let n=0;const r=()=>{if(this.rescueSettleTimer=null,this.stopped)return;const a=this.activeEvent;if(!a||a.eventId!==e)return;if(n++,a.replied||a.textForwarded){this.finishTurnEnd(e,t,i,!0,`replied=${a.replied}, textForwarded=${a.textForwarded} (settled during flush wait)`);return}if(a.apiFormatError){this.finishTurnEnd(e,t,i,!1,"apiFormatError (settled during flush wait)");return}const c=this.attemptRescueFromJsonl(e,t);if(c!=="none"){this.finishTurnEnd(e,t,i,!0,c==="silent"?"no-response-requested-after-settle":"rescued-after-settle");return}if(i==="Stop"&&this.isToolOnlyTurnConcluded(a)){this.finishTurnEnd(e,t,i,!0,`tool-only-turn (toolCalls=${a.toolActivityCount??0})`);return}if(n>=s){this.finishTurnEnd(e,t,i,!1,`rescue-failed-after-settle (waited ~${Math.round(Le/1e3)}s, no transcript output for this turn)`);return}this.markActiveEventActivity(e,t),this.rescueSettleTimer=setTimeout(r,xe)};this.rescueSettleTimer=setTimeout(r,Ot)}isToolOnlyTurnConcluded(e){return!e.toolActivityCount||e.jsonlBaseOffset===void 0||!this.claudeCliSessionId||!this.claudeSessionCwd?!1:re(this.claudeCliSessionId,this.claudeSessionCwd,e.jsonlBaseOffset).lastStopReason==="end_turn"}clearRescueSettleTimer(){this.rescueSettleTimer&&(clearTimeout(this.rescueSettleTimer),this.rescueSettleTimer=null)}finishTurnEnd(e,t,i,s,n){if(s)o.info("claude-adapter",`Stop hook received, finalizing event ${e} as responded (${n})`),this.bridgeCallbacks.sendEventResult(e,"responded");else{o.warn("claude-adapter",`Active event not confirmed when ${i}: ${e} (${n}), sending failed result`);const r=this.activeEvent?.apiFormatError?"Claude hit an API format error. Please start a new conversation (/grix open <dir>).":"Claude exited before completing its reply. Please try again.";this.bridgeCallbacks.sendStreamChunk(e,t,r,1,!1,`err_${e}`),this.bridgeCallbacks.sendStreamChunk(e,t,"",2,!0,`err_${e}`),this.bridgeCallbacks.sendEventResult(e,"failed",r,"agent_stop_failure")}this.clearActiveEvent(),this.armStopHookBarrier(t)}onHookActivity(e){const t=this.activeEvent?.sessionId;if(o.info("claude-adapter",`Hook activity: tool=${e?.tool_name??"(clear)"} session=${t??"(none)"}`),e&&this.activeEvent&&(this.activeEvent.toolActivityCount=(this.activeEvent.toolActivityCount??0)+1,this.markActiveEventActivity(this.activeEvent.eventId,this.activeEvent.sessionId),this.forwardIncrementalText(this.activeEvent.eventId),(this.activeEvent.replied||this.activeEvent.textForwarded)&&this.startPostReplyDeadline(this.activeEvent.eventId)),e&&!this.activeEvent&&!this.compacting&&this.noteSelfDrivenActivity(e),!!t)if(e){this.startComposing(t,e);const i=this.activeEvent;if(i){const s=e.event_name,n=e.tool_name,r=e.tool_input??"",a=n?Et(n):!1;if(n==="ExitPlanMode"&&s==="PreToolUse")o.info("claude-adapter","ExitPlanMode detected; waiting for user decision via permission card");else if(n==="AskUserQuestion"&&s==="PreToolUse")this.handleAskUserQuestion(i,r);else if(s==="PreToolUse"&&n)a||this.bridgeCallbacks.sendToolUse(i.eventId,i.sessionId,n,r);else if((s==="PostToolUse"||s==="PostToolUseFailure")&&n){if(n==="AskUserQuestion")return;!a&&s==="PostToolUseFailure"&&this.bridgeCallbacks.sendToolResult(i.eventId,i.sessionId,n,"(failed)")}}}else this.startComposing(t)}handlePermissionHookEvent(e,t){if(!this.activeEvent){o.warn("claude-adapter","PermissionRequest without active event, ignoring");return}if(e==="AskUserQuestion"){o.info("claude-adapter","Skip permission card for AskUserQuestion; handled by agent_question card flow");return}const i=this.activeEvent,s=w();new W(s.permissionRequestsDir).listPending().then(r=>{const a=r.length>0?r[r.length-1]:null;if(!a){o.warn("claude-adapter","No pending permission request found in store");return}const c=a.request_id;this.pendingPermissions.set(c,{eventId:i.eventId,sessionId:i.sessionId});const l=typeof t=="string"?t:"";if(e==="ExitPlanMode"){const f=It(l);f&&this.bridgeCallbacks.sendReply(i.eventId,i.sessionId,f)}const u=e==="ExitPlanMode"?"":l.slice(0,100),h=u?`${e}: ${u}`:e;this.bridgeCallbacks.sendPermissionCard({eventId:i.eventId,sessionId:i.sessionId,approvalId:c,toolName:e,toolTitle:h}),o.info("claude-adapter",`Sent permission card: approvalId=${c} tool=${e}`)}).catch(r=>{o.warn("claude-adapter",`Failed to send permission card: ${r}`)})}handleAskUserQuestion(e,t){const i=Ce(t);if(!i){o.warn("claude-adapter","Failed to parse AskUserQuestion input, skipping agent_question card");return}const s=w(),n=new _e(s.questionRequestsDir);n.listPending().then(async r=>{const a=[...r].reverse().filter(f=>String(f.session_id??"").trim()===this.claudeCliSessionId),c=a.find(f=>String(f.event_id??"").trim()===e.eventId)??a.find(f=>String(f.event_id??"").trim()==="")??a[0]??null,l=c?.request_id??`fallback-${Date.now()}`;c&&String(c.event_id??"").trim()===""&&await n.updateRequest(c.request_id,{event_id:e.eventId}),this.pendingQuestion.set(l,{eventId:e.eventId,sessionId:e.sessionId}),e.awaitingUserQuestion=!0;const h=(Number(c?.created_at??0)>0?Number(c?.created_at):Date.now())+Te;this.bridgeCallbacks.sendAgentQuestionCard(e.eventId,e.sessionId,{request_id:l,mode:"form",questions:i,expires_at:h}),o.info("claude-adapter",`Sent agent_question card: request_id=${l} questions=${i.length}`)}).catch(()=>{o.warn("claude-adapter","Failed to list pending questions from store")})}parseAskUserQuestions(e){return Ce(e)}parseAskUserQuestionInput(e){let t;try{t=JSON.parse(e)}catch{return null}const i=[],s=t.questions;if(!Array.isArray(s)||s.length===0)return null;for(let n=0;n<s.length;n++){const r=s[n];if(!r||typeof r!="object")continue;const a=String(r.header??r.question??`Question ${n+1}`),c=String(r.prompt??""),l=Array.isArray(r.options)?r.options:void 0,u=r.multiSelect===!0;i.push({header:a,prompt:c,...l&&l.length>0?{options:l}:{},...u?{multi_select:!0}:{}})}return i.length>0?{questions:i}:null}pruneCompletedEvents(){const e=Date.now()-Lt;for(const[t,i]of this.completedEventIds.entries())i<e&&this.completedEventIds.delete(t)}markActiveEventActivity(e,t){const i=this.activeEvent;i&&(e&&i.eventId!==e||t&&i.sessionId!==t||(this.resetActiveEventIdleTimer(i.eventId),this.isEventAnswered(i)||this.resetActiveEventHardTimer(i.eventId)))}clearActiveEventIdleTimer(){this.activeEventIdleTimer&&(clearTimeout(this.activeEventIdleTimer),this.activeEventIdleTimer=null)}clearActiveEventHardTimer(){this.activeEventHardTimer&&(clearTimeout(this.activeEventHardTimer),this.activeEventHardTimer=null)}shouldExtendByLiveness(e){const t=this.activeEvent;if(!t||t.eventId!==e||!this.claudeCliSessionId||!this.claudeSessionCwd||!this.claudeProcess&&!this.claudePty)return!1;if(this.lastPtyOutputAt>0&&Date.now()-this.lastPtyOutputAt<Me)return t.livenessExtendStartAt=Date.now(),!0;const i=re(this.claudeCliSessionId,this.claudeSessionCwd,t.jsonlBaseOffset);if(i.freshMs===null||!(t.jsonlBaseOffset===void 0?i.freshMs<ae:i.lastStopReason!==null?i.lastStopReason!=="end_turn":i.freshMs<ae))return!1;if(i.freshMs<ae)t.livenessExtendStartAt=Date.now();else{const n=t.livenessExtendStartAt??Date.now();if(t.livenessExtendStartAt===void 0&&(t.livenessExtendStartAt=n),Date.now()-n>Oe)return o.warn("claude-adapter",`Liveness extension budget exhausted for ${e} (no JSONL writes for ${Math.round((Date.now()-n)/6e4)}min), allowing close`),!1}return o.info("claude-adapter",`Liveness check: turn in progress for ${e} (lastStopReason=${i.lastStopReason??"none"}, freshMs=${i.freshMs}), extending`),!0}captureEventJsonlBaseOffset(e){this.captureAuditTranscriptStart(e);const t=this.activeEvent;if(!(!t||t.eventId!==e||t.jsonlBaseOffset!==void 0)&&!(!this.claudeCliSessionId||!this.claudeSessionCwd))try{const i=z(this.claudeCliSessionId,this.claudeSessionCwd);if(t.auditTranscriptPath===i&&t.auditTranscriptStartOffset!==void 0){t.jsonlBaseOffset=t.auditTranscriptStartOffset;return}t.jsonlBaseOffset=P(i)?b(i).size:0}catch{t.jsonlBaseOffset=0}}captureSingleTurnFallbackJsonlBaseOffset(e){this.captureAuditTranscriptStart(e);const t=this.activeEvent;if(!(!t||t.eventId!==e||t.jsonlBaseOffset!==void 0)&&!(!this.claudeCliSessionId||!this.claudeSessionCwd))try{const i=z(this.claudeCliSessionId,this.claudeSessionCwd);if(!P(i))return;if(t.auditTranscriptPath===i&&t.auditTranscriptStartOffset!==void 0){t.jsonlBaseOffset=t.auditTranscriptStartOffset;return}t.jsonlBaseOffset=b(i).size}catch{}}captureAuditTranscriptStart(e){const t=this.activeEvent;if(!t||t.eventId!==e||t.rawEvent?.audit?.enabled!==!0||t.auditTranscriptStartOffset!==void 0||!this.claudeCliSessionId)return;const i=this.claudeSessionCwd||process.cwd(),s=z(this.claudeCliSessionId,i);t.auditTranscriptPath=s,t.auditClaudeSessionId=this.claudeCliSessionId;try{t.auditTranscriptStartOffset=P(s)?b(s).size:0}catch{t.auditTranscriptStartOffset=0}const n=this.auditEvidencePaths;n&&(t.auditOtelLogsStartOffset=Y(n.logsPath),t.auditOtelTracesStartOffset=Y(n.tracesPath),n.rawProviderBody&&(t.auditRawBodyFilesBefore=Re(n.rawBodyDir)))}snapshotAuditBoundary(e){if(e.rawEvent?.audit?.enabled!==!0)return;if(e.auditTranscriptStartOffset===void 0||!e.auditTranscriptPath||!e.auditClaudeSessionId){this.completedAuditBoundaries.set(e.eventId,Object.freeze({adapterType:this.type}));return}let t=e.auditTranscriptStartOffset;try{t=P(e.auditTranscriptPath)?b(e.auditTranscriptPath).size:e.auditTranscriptStartOffset}catch{}const i=rt(e.auditTranscriptPath),s=this.auditEvidencePaths,n=new Set(e.auditRawBodyFilesBefore??[]),r=s?.rawProviderBody?Re(s.rawBodyDir).filter(c=>!n.has(c)):[],a=Object.freeze({adapterType:this.type,providerSessionId:e.auditClaudeSessionId,transcriptPath:e.auditTranscriptPath,transcriptStartOffset:e.auditTranscriptStartOffset,transcriptEndOffset:t,...e.auditSingleTurnFallback?{singleTurnFallback:!0}:{},subagentRoot:p(i,e.auditClaudeSessionId,"subagents"),...s?{otelLogsPath:s.logsPath,otelLogsStartOffset:e.auditOtelLogsStartOffset??0,otelLogsEndOffset:Y(s.logsPath),otelTracesPath:s.tracesPath,otelTracesStartOffset:e.auditOtelTracesStartOffset??0,otelTracesEndOffset:Y(s.tracesPath),rawApiBodyDirectory:s.rawBodyDir,rawApiBodyPaths:r}:{}});this.completedAuditBoundaries.set(e.eventId,a)}noteSelfDrivenActivity(e){!this.selfDrivenActive&&this.lastClearedEvent&&Date.now()-this.lastClearedEvent.ts<zt||(this.selfDrivenLastSignalAt=Date.now(),e&&(this.selfDrivenLabel=Jt(e)),!this.selfDrivenActive&&(this.selfDrivenActive=!0,o.info("claude-adapter",`Self-driven activity detected for session ${this.sessionId} (no active event), debouncing before reporting working state`),this.selfDrivenReportTimer=setTimeout(()=>{this.selfDrivenReportTimer=null,!(!this.selfDrivenActive||this.stopped||this.activeEvent)&&(this.selfDrivenReported=!0,o.info("claude-adapter",`Self-driven working state confirmed for session ${this.sessionId} (sustained >${Fe/1e3}s), reporting`),this.emit("sessionActivity",this.sessionId??"",!0,this.selfDrivenLabel??void 0))},Fe),this.selfDrivenReportTimer.unref(),this.selfDrivenSweepTimer=setInterval(()=>this.sweepSelfDriven(),qt),this.selfDrivenSweepTimer.unref()))}sweepSelfDriven(){if(!this.selfDrivenActive)return;if(this.stopped||this.activeEvent){this.stopSelfDriven();return}const e=Date.now()-this.selfDrivenLastSignalAt;if(this.claudeCliSessionId&&this.claudeSessionCwd){const t=re(this.claudeCliSessionId,this.claudeSessionCwd),i=t.lastStopReason!==null&&t.lastStopReason!=="end_turn",s=t.freshMs!==null&&t.freshMs<Oe,n=Pt(this.claudeCliSessionId,this.claudeSessionCwd)>0;if((i||n)&&s){this.selfDrivenReported&&this.emit("sessionActivity",this.sessionId??"",!0,this.selfDrivenLabel??void 0);return}}if(e<Ht){this.selfDrivenReported&&this.emit("sessionActivity",this.sessionId??"",!0,this.selfDrivenLabel??void 0);return}o.info("claude-adapter",`Self-driven activity ended for session ${this.sessionId} (quiet for ${Math.round(e/1e3)}s, no pending work)`),this.stopSelfDriven()}stopSelfDriven(){this.selfDrivenSweepTimer&&(clearInterval(this.selfDrivenSweepTimer),this.selfDrivenSweepTimer=null),this.selfDrivenReportTimer&&(clearTimeout(this.selfDrivenReportTimer),this.selfDrivenReportTimer=null),this.selfDrivenActive&&(this.selfDrivenActive=!1,this.selfDrivenLabel=null,this.selfDrivenReported&&(this.selfDrivenReported=!1,this.emit("sessionActivity",this.sessionId??"",!1)))}resetActiveEventIdleTimer(e){this.clearActiveEventIdleTimer(),this.activeEventIdleTimer=setTimeout(()=>{if(!this.stopped&&this.activeEvent?.eventId===e){if(this.activeEvent?.toolCallInFlight){o.info("claude-adapter",`Idle timeout skipped: toolCallInFlight for ${e}, resetting timer`),this.resetActiveEventIdleTimer(e);return}if(this.activeEvent?.awaitingUserQuestion){o.info("claude-adapter",`Idle timeout skipped: awaitingUserQuestion for ${e}, resetting timer`),this.resetActiveEventIdleTimer(e);return}if(this.pendingPermissions.size>0){o.info("claude-adapter",`Idle timeout skipped: pendingPermissions=${this.pendingPermissions.size} for ${e}, resetting timer`),this.resetActiveEventIdleTimer(e);return}if(this.shouldExtendByLiveness(e)){this.resetActiveEventIdleTimer(e);return}this.finalizeStuckActiveEvent(e,"idle")}},ke)}finalizeStuckActiveEvent(e,t){const i=this.isEventAnswered(),s=i?"responded":"failed",n=t==="idle"?ke:be,r=i?void 0:t==="idle"?`agent idle for ${n/1e3}s`:`agent exceeded max duration ${n/1e3}s`,a=i?void 0:t==="idle"?"agent_idle_timeout":"agent_hard_timeout";o.error("claude-adapter",`Active event ${t} timeout (${n/1e3}s): ${e}, answered=${i}, sending ${s}`),this.flushForwardedTail(e),this.completedEventIds.set(e,Date.now()),this.bridgeCallbacks.sendEventResult(e,s,r,a),this.clearActiveEvent(),s==="failed"&&this.emit("stuck")}resetActiveEventHardTimer(e){this.clearActiveEventHardTimer(),this.activeEventHardTimer=setTimeout(()=>{if(!this.stopped&&this.activeEvent?.eventId===e){if(this.shouldExtendByLiveness(e)){this.resetActiveEventHardTimer(e);return}this.finalizeStuckActiveEvent(e,"hard")}},be)}clearActiveEventPostReplyTimer(){this.activeEventPostReplyTimer&&(clearTimeout(this.activeEventPostReplyTimer),this.activeEventPostReplyTimer=null)}clearActiveEventPostReplyWatcher(){if(this.activeEventPostReplyWatcher){try{this.activeEventPostReplyWatcher.close()}catch{}this.activeEventPostReplyWatcher=null}this.activeEventPostReplyPoll&&(clearInterval(this.activeEventPostReplyPoll),this.activeEventPostReplyPoll=null)}startPostReplyJsonlWatcher(e){if(this.clearActiveEventPostReplyWatcher(),!this.claudeCliSessionId||!this.claudeSessionCwd)return;const t=z(this.claudeCliSessionId,this.claudeSessionCwd);if(!this.activeEvent?.sessionId)return;if(!P(t)){o.info("claude-adapter",`JSONL watcher skipped: file not yet available for ${e}`);return}const s=b(t).size;this.activeEvent&&(this.activeEvent.jsonlBaseOffset=s);let n=null;try{this.activeEventPostReplyWatcher=st(t,()=>{n&&clearTimeout(n),n=setTimeout(()=>this.finalizeIfEndTurn(e),300)}),this.activeEventPostReplyPoll=setInterval(()=>this.finalizeIfEndTurn(e),Ut),o.info("claude-adapter",`JSONL watcher started for ${e} at ${t} (baseOffset=${s})`)}catch(r){o.warn("claude-adapter",`JSONL watcher start failed for ${e}: ${r}`)}}finalizeIfEndTurn(e){if(this.stopped||this.activeEvent?.eventId!==e||!this.claudeCliSessionId||!this.claudeSessionCwd)return;const t=q(this.claudeCliSessionId,this.claudeSessionCwd,this.activeEvent.jsonlBaseOffset);if(t&&t.stopReason==="end_turn"){o.info("claude-adapter",`JSONL watcher: end_turn detected for ${e}, finalizing as responded`);const i=this.activeEvent.sessionId;this.flushForwardedTail(e),this.bridgeCallbacks.sendEventResult(e,"responded",void 0,void 0),this.clearActiveEvent(),this.armStopHookBarrier(i);return}if(this.activeEvent?.replied&&this.activeEvent.jsonlBaseOffset!==void 0&&we(this.claudeCliSessionId,this.claudeSessionCwd,this.activeEvent.jsonlBaseOffset,!0)){if(this.lastPtyOutputAt>0&&Date.now()-this.lastPtyOutputAt<Me)return;o.info("claude-adapter",`JSONL poll: terminal tool_result detected for ${e} (file quiet, no end_turn), finalizing as responded`),this.flushForwardedTail(e),this.bridgeCallbacks.sendEventResult(e,"responded",void 0,void 0),this.clearActiveEvent()}}startPostReplyDeadline(e){this.clearActiveEventPostReplyTimer(),this.clearActiveEventHardTimer(),this.activeEventPostReplyTimer=setTimeout(()=>{if(this.stopped||this.activeEvent?.eventId!==e||!this.activeEvent?.replied&&!this.activeEvent?.textForwarded)return;if(this.shouldExtendByLiveness(e)){this.startPostReplyDeadline(e);return}const t=this.activeEvent.repliedAt,i=Math.round((Date.now()-t)/1e3);o.info("claude-adapter",`Post-reply deadline reached for ${e} (${i}s since reply), completing as responded`),this.flushForwardedTail(e),this.bridgeCallbacks.sendEventResult(e,"responded",void 0,void 0),this.clearActiveEvent()},Ft)}beginCompaction(){this.compacting||(this.compacting=!0,o.info("claude-adapter","Compaction started; gating input"),this.emit("pauseIntake","compaction"),this.resetCompactingActivityTimer())}resetCompactingActivityTimer(){this.compactingTimer&&clearTimeout(this.compactingTimer),this.compactingTimer=setTimeout(()=>{o.warn("claude-adapter","Compaction stall: no PTY activity for 90s, emitting stuck"),this.emit("stuck")},9e4),this.compactingTimer.unref?.()}finishCompaction(e){if(!this.compacting)return;this.compacting=!1,this.compactingTimer&&(clearTimeout(this.compactingTimer),this.compactingTimer=null),o.info("claude-adapter",`Compaction finished (${e}); resuming intake`),this.emit("resumeIntake","compaction");const t=this.compactionDoneResolver;this.compactionDoneResolver=null,t?.(e)}armStopHookBarrier(e){this.stopHookBarrierTimer&&clearTimeout(this.stopHookBarrierTimer),this.stopHookBarrierSessionId=e,this.emit("pauseIntake","barrier"),this.stopHookBarrierTimer=setTimeout(()=>{this.stopHookBarrierSessionId===e&&(o.warn("claude-adapter",`Stop hook barrier timeout for session=${e}`),this.clearStopHookBarrier())},Qt)}clearStopHookBarrier(){const e=this.stopHookBarrierSessionId!==null;this.stopHookBarrierSessionId=null,this.stopHookBarrierTimer&&(clearTimeout(this.stopHookBarrierTimer),this.stopHookBarrierTimer=null),e&&this.emit("resumeIntake","barrier")}clearPendingMcpFailureTimer(){this.pendingMcpFailureTimer&&(clearTimeout(this.pendingMcpFailureTimer),this.pendingMcpFailureTimer=null)}markMcpStartupFailure(){if(!this.mcpStartupFailureHandled&&(this.mcpStartupFailureHandled=!0,this.mcpChannelBroken=!0,o.error("claude-adapter","Claude reported blocking MCP server startup failure"),this.activeEvent)){const e=this.activeEvent.eventId,t=this.activeEvent.sessionId;this.bridgeCallbacks.sendStreamChunk(e,t,`
|
|
18
|
+
`;await R(n,l,"utf8"),o.info("claude-adapter",`Injected statusLine settings: ${n}`)}catch(t){o.warn("claude-adapter",`Failed to inject statusLine settings: ${t instanceof Error?t.message:t}`)}}async ensureUserMcpServer(e,t,i){const s=this.resolveServerEntryPath(t),n=process.execPath,r=[s],a=p(I(),".claude.json");let c=null;try{const f=await T(a,"utf8");c=JSON.parse(f)?.mcpServers?.[M]??null}catch{}if(c&&String(c.type||"stdio").trim()==="stdio"&&String(c.command??"").trim()===n&&Array.isArray(c.args)&&c.args.length===r.length&&c.args.every((f,m)=>f===r[m]))return;o.info("claude-adapter",`Registering user-scoped MCP server: ${M} -> ${n} ${r.join(" ")}`);try{te(`${e} mcp remove -s user ${M}`,{encoding:"utf8",timeout:1e4,env:i,stdio:"pipe",windowsHide:!0})}catch{}const l=["mcp","add","--scope","user",M,"--",n,...r],u=process.platform==="win32"?'"':"'",h=te(`${e} ${l.map(f=>`${u}${f}${u}`).join(" ")}`,{encoding:"utf8",timeout:1e4,env:i,stdio:"pipe",windowsHide:!0});o.info("claude-adapter",`MCP server registered: ${h.trim()||"ok"}`)}resolveServerEntryPath(e){const t=p(e,"server","main.js");try{if(j(t))return t}catch{}const i=p(e,"dist","index.js");try{if(j(i))return i}catch{}throw new Error(`Cannot find grix-claude server entry in pluginDir: ${e}`)}clearActiveEvent(){const e=this.activeEvent;this.clearActiveEventIdleTimer(),this.clearActiveEventHardTimer(),this.clearActiveEventPostReplyTimer(),this.clearActiveEventPostReplyWatcher(),this.clearRescueSettleTimer(),e&&(this.snapshotAuditBoundary(e),this.sessionIdConflictRetriedEventIds.delete(e.eventId),this.malformedToolRetriedEventIds.delete(e.eventId),this.completedEventIds.set(e.eventId,Date.now()),this.lastClearedEvent={eventId:e.eventId,sessionId:e.sessionId,ts:Date.now()},this.emit(`reply:${e.sessionId}`,{status:"completed"}),this.emit("eventDone",e.eventId)),this.activeEvent=null,this.stopComposing()}startComposing(e,t){}stopComposing(){}clearComposingTimer(){}resolveHookSignalsPath(){const e=w(),t=this.claudeCliSessionId||this.sessionId;return p(e.dataDir,`hook-signals-${t}.json`)}startActivityTracking(){this.activityManager&&this.activityManager.stop();const e=this.resolveHookSignalsPath(),t=w().hookSignalsLogPath,i=new pt(e,t);o.info("claude-adapter",`Activity tracking started: watching ${e}`),this.activityManager=new ft({hookSignalStore:i,onActivity:s=>this.onHookActivity(s),onStop:()=>this.onClaudeTurnEnd("Stop"),onStopFailure:()=>this.onClaudeTurnEnd("StopFailure"),onCompactStart:()=>this.beginCompaction(),onCompactResult:()=>this.finishCompaction("post-compact-hook"),onPermissionRequest:(s,n)=>this.handlePermissionHookEvent(s,n)}),this.activityManager.start()}onClaudeTurnEnd(e){if(o.info("claude-adapter",`Hook activity: ${e}`),!this.activeEvent){this.stopHookBarrierSessionId&&(o.info("claude-adapter",`Late Stop hook after end_turn finalize \u2014 releasing barrier (session=${this.stopHookBarrierSessionId})`),this.clearStopHookBarrier());return}if(this.activeEvent.toolCallInFlight){o.info("claude-adapter",`Stop hook deferred: toolCallInFlight for ${this.activeEvent.eventId}`),this.activeEvent.pendingStopHook=e;return}if(this.activeEvent.singleTurnFallback&&!this.isEventAnswered()){o.info("claude-adapter",`${e} hook ignored for ${this.activeEvent.eventId}: single-turn fallback owns finalization`);return}this.finalizeActiveEvent(e)}forwardIncrementalText(e){const t=this.activeEvent;if(!t||t.eventId!==e||t.replied||!this.claudeCliSessionId||!this.claudeSessionCwd)return;const i=t.textForwardOffset??t.jsonlBaseOffset??0,{segments:s,newOffset:n}=Ct(this.claudeCliSessionId,this.claudeSessionCwd,i);if(t.textForwardOffset=n,s.length===0)return;const r=t.sessionId;for(const a of s){const c=a.trim();if(!c)continue;const l=`txt_${e}_${this.textForwardSeq++}`;this.bridgeCallbacks.sendStreamChunk(e,r,c,1,!1,l),this.bridgeCallbacks.sendStreamChunk(e,r,"",2,!0,l),t.textForwarded||(t.textForwarded=!0,t.repliedAt===void 0&&(t.repliedAt=Date.now()),this.startPostReplyDeadline(e),this.startPostReplyJsonlWatcher(e))}this.markActiveEventActivity(e,r),o.info("claude-adapter",`Forwarded ${s.length} plain-text segment(s) for ${e}`)}flushForwardedTail(e){this.activeEvent?.eventId===e&&this.activeEvent.textForwarded&&this.forwardIncrementalText(e)}isEventAnswered(e=this.activeEvent){return!!(e&&(e.replied||e.textForwarded))}attemptRescueFromJsonl(e,t){if(!this.claudeCliSessionId||!this.claudeSessionCwd)return o.info("claude-adapter",`Rescue skipped: no claudeCliSessionId or claudeSessionCwd for ${e}`),"none";const i=this.activeEvent?.jsonlBaseOffset,s=q(this.claudeCliSessionId,this.claudeSessionCwd,i);if(s?.metaOnly)return i===void 0?(o.warn("claude-adapter",`Rescue for ${e}: only CLI meta text found but jsonlBaseOffset is unset \u2014 cannot attribute it to this turn, treating as unanswered`),"none"):(o.info("claude-adapter",`Rescue skipped for ${e}: this turn wrote only CLI meta text, finalizing silently`),"silent");const n=s?.text||null;if(!n)return o.info("claude-adapter",`Rescue failed: no assistant text found in JSONL for ${e}`),"none";const r=`rescue_${e}_${Date.now()}`;return this.bridgeCallbacks.sendStreamChunk(e,t,n,1,!1,r),this.bridgeCallbacks.sendStreamChunk(e,t,"",2,!0,r),o.info("claude-adapter",`Rescue succeeded for event ${e}: sent ${n.length} chars from JSONL`),"sent"}warnUnsentFinalIfAny(e){if(!this.claudeCliSessionId||!this.claudeSessionCwd)return;const t=this.activeEvent?.jsonlBaseOffset,i=q(this.claudeCliSessionId,this.claudeSessionCwd,t);if(!i||i.stopReason!=="end_turn")return;const s=i.text?.trim()??"";if(!s)return;const n=(this.activeEvent?.lastReplyText??"").trim();n&&(s===n||n.includes(s))||o.warn("claude-adapter",`Unsent final end_turn text for ${e} (${s.length} chars) not delivered \u2014 \u5DF2\u5E94\u7B54\u8FC7\uFF0C\u6309\u7B56\u7565\u4E0D\u8865\u53D1\uFF08\u7591\u4F3C\u56DE\u5408\u6536\u5C3E\u81EA\u8FF0\uFF1B\u82E5\u786E\u4E3A\u6F0F\u53D1\u7ED3\u8BBA\u53EF\u636E\u6B64\u6392\u67E5\uFF09`)}finalizeActiveEvent(e){if(!this.activeEvent)return;const t=this.activeEvent.eventId,i=this.activeEvent.sessionId;if(this.isEventAnswered()&&this.activeEvent.jsonlBaseOffset!==void 0&&this.claudeCliSessionId&&this.claudeSessionCwd){const s=q(this.claudeCliSessionId,this.claudeSessionCwd,this.activeEvent.jsonlBaseOffset);if(!s||s.stopReason!=="end_turn")if(we(this.claudeCliSessionId,this.claudeSessionCwd,this.activeEvent.jsonlBaseOffset))o.info("claude-adapter",`Stop hook: terminal tool_result detected for ${t} (grix_reply as final action, no end_turn expected), finalizing as responded`);else{o.info("claude-adapter",`Stop hook suppressed for ${t}: no end_turn in JSONL after offset=${this.activeEvent.jsonlBaseOffset} \u2014 likely resume-drain hook, waiting for JSONL watcher`),this.markActiveEventActivity(t,i);return}}if(this.flushForwardedTail(t),!this.activeEvent.apiFormatError){if(this.activeEvent.replied||this.activeEvent.textForwarded){this.activeEvent.replied&&!this.activeEvent.textForwarded&&this.warnUnsentFinalIfAny(t),this.finishTurnEnd(t,i,e,!0,`replied=${this.activeEvent.replied}, textForwarded=${this.activeEvent.textForwarded}`);return}if(this.activeEvent.rescueSettleInFlight){o.info("claude-adapter",`${e} ignored for ${t}: rescue retry already in flight`);return}const s=this.recoverMalformedToolCall(t);if(s==="retried")return;if(s==="exhausted"){this.bridgeCallbacks.sendStreamChunk(t,i,Bt,1,!1,`mtc_${t}`),this.bridgeCallbacks.sendStreamChunk(t,i,"",2,!0,`mtc_${t}`),this.finishTurnEnd(t,i,e,!0,"malformed-toolcall-fallback");return}const n=this.attemptRescueFromJsonl(t,i);if(n!=="none"){this.finishTurnEnd(t,i,e,!0,n==="silent"?"no-response-requested":"rescued");return}this.beginRescueSettleWait(t,i,e);return}this.finishTurnEnd(t,i,e,!1,"apiFormatError")}beginRescueSettleWait(e,t,i){if(!this.activeEvent||this.activeEvent.eventId!==e)return;this.activeEvent.rescueSettleInFlight=!0;const s=Math.max(1,Math.ceil(Le/xe));let n=0;const r=()=>{if(this.rescueSettleTimer=null,this.stopped)return;const a=this.activeEvent;if(!a||a.eventId!==e)return;if(n++,a.replied||a.textForwarded){this.finishTurnEnd(e,t,i,!0,`replied=${a.replied}, textForwarded=${a.textForwarded} (settled during flush wait)`);return}if(a.apiFormatError){this.finishTurnEnd(e,t,i,!1,"apiFormatError (settled during flush wait)");return}const c=this.attemptRescueFromJsonl(e,t);if(c!=="none"){this.finishTurnEnd(e,t,i,!0,c==="silent"?"no-response-requested-after-settle":"rescued-after-settle");return}if(i==="Stop"&&this.isToolOnlyTurnConcluded(a)){this.finishTurnEnd(e,t,i,!0,`tool-only-turn (toolCalls=${a.toolActivityCount??0})`);return}if(n>=s){this.finishTurnEnd(e,t,i,!1,`rescue-failed-after-settle (waited ~${Math.round(Le/1e3)}s, no transcript output for this turn)`);return}this.markActiveEventActivity(e,t),this.rescueSettleTimer=setTimeout(r,xe)};this.rescueSettleTimer=setTimeout(r,Ot)}isToolOnlyTurnConcluded(e){return!e.toolActivityCount||e.jsonlBaseOffset===void 0||!this.claudeCliSessionId||!this.claudeSessionCwd?!1:re(this.claudeCliSessionId,this.claudeSessionCwd,e.jsonlBaseOffset).lastStopReason==="end_turn"}clearRescueSettleTimer(){this.rescueSettleTimer&&(clearTimeout(this.rescueSettleTimer),this.rescueSettleTimer=null)}finishTurnEnd(e,t,i,s,n){if(s)o.info("claude-adapter",`Stop hook received, finalizing event ${e} as responded (${n})`),this.bridgeCallbacks.sendEventResult(e,"responded");else{o.warn("claude-adapter",`Active event not confirmed when ${i}: ${e} (${n}), sending failed result`);const r=this.activeEvent?.apiFormatError?"Claude hit an API format error. Please start a new conversation (/grix open <dir>).":"Claude exited before completing its reply. Please try again.";this.bridgeCallbacks.sendStreamChunk(e,t,r,1,!1,`err_${e}`),this.bridgeCallbacks.sendStreamChunk(e,t,"",2,!0,`err_${e}`),this.bridgeCallbacks.sendEventResult(e,"failed",r,"agent_stop_failure")}this.clearActiveEvent(),this.armStopHookBarrier(t)}onHookActivity(e){const t=this.activeEvent?.sessionId;if(o.info("claude-adapter",`Hook activity: tool=${e?.tool_name??"(clear)"} session=${t??"(none)"}`),e&&this.activeEvent&&(this.activeEvent.toolActivityCount=(this.activeEvent.toolActivityCount??0)+1,this.markActiveEventActivity(this.activeEvent.eventId,this.activeEvent.sessionId),this.forwardIncrementalText(this.activeEvent.eventId),(this.activeEvent.replied||this.activeEvent.textForwarded)&&this.startPostReplyDeadline(this.activeEvent.eventId)),e&&!this.activeEvent&&!this.compacting&&this.noteSelfDrivenActivity(e),!!t)if(e){this.startComposing(t,e);const i=this.activeEvent;if(i){const s=e.event_name,n=e.tool_name,r=e.tool_input??"",a=n?Et(n):!1;if(n==="ExitPlanMode"&&s==="PreToolUse")o.info("claude-adapter","ExitPlanMode detected; waiting for user decision via permission card");else if(n==="AskUserQuestion"&&s==="PreToolUse")this.handleAskUserQuestion(i,r);else if(s==="PreToolUse"&&n)a||this.bridgeCallbacks.sendToolUse(i.eventId,i.sessionId,n,r);else if((s==="PostToolUse"||s==="PostToolUseFailure")&&n){if(n==="AskUserQuestion")return;!a&&s==="PostToolUseFailure"&&this.bridgeCallbacks.sendToolResult(i.eventId,i.sessionId,n,"(failed)")}}}else this.startComposing(t)}handlePermissionHookEvent(e,t){if(!this.activeEvent){o.warn("claude-adapter","PermissionRequest without active event, ignoring");return}if(e==="AskUserQuestion"){o.info("claude-adapter","Skip permission card for AskUserQuestion; handled by agent_question card flow");return}const i=this.activeEvent,s=w();new W(s.permissionRequestsDir).listPending().then(r=>{const a=r.length>0?r[r.length-1]:null;if(!a){o.warn("claude-adapter","No pending permission request found in store");return}const c=a.request_id;this.pendingPermissions.set(c,{eventId:i.eventId,sessionId:i.sessionId});const l=typeof t=="string"?t:"";if(e==="ExitPlanMode"){const f=It(l);f&&this.bridgeCallbacks.sendReply(i.eventId,i.sessionId,f)}const u=e==="ExitPlanMode"?"":l.slice(0,100),h=u?`${e}: ${u}`:e;this.bridgeCallbacks.sendPermissionCard({eventId:i.eventId,sessionId:i.sessionId,approvalId:c,toolName:e,toolTitle:h}),o.info("claude-adapter",`Sent permission card: approvalId=${c} tool=${e}`)}).catch(r=>{o.warn("claude-adapter",`Failed to send permission card: ${r}`)})}handleAskUserQuestion(e,t){const i=Ce(t);if(!i){o.warn("claude-adapter","Failed to parse AskUserQuestion input, skipping agent_question card");return}const s=w(),n=new _e(s.questionRequestsDir);n.listPending().then(async r=>{const a=[...r].reverse().filter(f=>String(f.session_id??"").trim()===this.claudeCliSessionId),c=a.find(f=>String(f.event_id??"").trim()===e.eventId)??a.find(f=>String(f.event_id??"").trim()==="")??a[0]??null,l=c?.request_id??`fallback-${Date.now()}`;c&&String(c.event_id??"").trim()===""&&await n.updateRequest(c.request_id,{event_id:e.eventId}),this.pendingQuestion.set(l,{eventId:e.eventId,sessionId:e.sessionId}),e.awaitingUserQuestion=!0;const h=(Number(c?.created_at??0)>0?Number(c?.created_at):Date.now())+Te;this.bridgeCallbacks.sendAgentQuestionCard(e.eventId,e.sessionId,{request_id:l,mode:"form",questions:i,expires_at:h}),o.info("claude-adapter",`Sent agent_question card: request_id=${l} questions=${i.length}`)}).catch(()=>{o.warn("claude-adapter","Failed to list pending questions from store")})}parseAskUserQuestions(e){return Ce(e)}parseAskUserQuestionInput(e){let t;try{t=JSON.parse(e)}catch{return null}const i=[],s=t.questions;if(!Array.isArray(s)||s.length===0)return null;for(let n=0;n<s.length;n++){const r=s[n];if(!r||typeof r!="object")continue;const a=String(r.header??r.question??`Question ${n+1}`),c=String(r.prompt??""),l=Array.isArray(r.options)?r.options:void 0,u=r.multiSelect===!0;i.push({header:a,prompt:c,...l&&l.length>0?{options:l}:{},...u?{multi_select:!0}:{}})}return i.length>0?{questions:i}:null}pruneCompletedEvents(){const e=Date.now()-Lt;for(const[t,i]of this.completedEventIds.entries())i<e&&this.completedEventIds.delete(t)}markActiveEventActivity(e,t){const i=this.activeEvent;i&&(e&&i.eventId!==e||t&&i.sessionId!==t||(this.resetActiveEventIdleTimer(i.eventId),this.isEventAnswered(i)||this.resetActiveEventHardTimer(i.eventId)))}clearActiveEventIdleTimer(){this.activeEventIdleTimer&&(clearTimeout(this.activeEventIdleTimer),this.activeEventIdleTimer=null)}clearActiveEventHardTimer(){this.activeEventHardTimer&&(clearTimeout(this.activeEventHardTimer),this.activeEventHardTimer=null)}shouldExtendByLiveness(e){const t=this.activeEvent;if(!t||t.eventId!==e||!this.claudeCliSessionId||!this.claudeSessionCwd||!this.claudeProcess&&!this.claudePty)return!1;if(this.lastPtyOutputAt>0&&Date.now()-this.lastPtyOutputAt<Me)return t.livenessExtendStartAt=Date.now(),!0;const i=re(this.claudeCliSessionId,this.claudeSessionCwd,t.jsonlBaseOffset);if(i.freshMs===null||!(t.jsonlBaseOffset===void 0?i.freshMs<ae:i.lastStopReason!==null?i.lastStopReason!=="end_turn":i.freshMs<ae))return!1;if(i.freshMs<ae)t.livenessExtendStartAt=Date.now();else{const n=t.livenessExtendStartAt??Date.now();if(t.livenessExtendStartAt===void 0&&(t.livenessExtendStartAt=n),Date.now()-n>Oe)return o.warn("claude-adapter",`Liveness extension budget exhausted for ${e} (no JSONL writes for ${Math.round((Date.now()-n)/6e4)}min), allowing close`),!1}return o.info("claude-adapter",`Liveness check: turn in progress for ${e} (lastStopReason=${i.lastStopReason??"none"}, freshMs=${i.freshMs}), extending`),!0}captureEventJsonlBaseOffset(e){this.captureAuditTranscriptStart(e);const t=this.activeEvent;if(!(!t||t.eventId!==e||t.jsonlBaseOffset!==void 0)&&!(!this.claudeCliSessionId||!this.claudeSessionCwd))try{const i=z(this.claudeCliSessionId,this.claudeSessionCwd);if(t.auditTranscriptPath===i&&t.auditTranscriptStartOffset!==void 0){t.jsonlBaseOffset=t.auditTranscriptStartOffset;return}t.jsonlBaseOffset=P(i)?b(i).size:0}catch{t.jsonlBaseOffset=0}}captureSingleTurnFallbackJsonlBaseOffset(e){this.captureAuditTranscriptStart(e);const t=this.activeEvent;if(!(!t||t.eventId!==e||t.jsonlBaseOffset!==void 0)&&!(!this.claudeCliSessionId||!this.claudeSessionCwd))try{const i=z(this.claudeCliSessionId,this.claudeSessionCwd);if(!P(i))return;if(t.auditTranscriptPath===i&&t.auditTranscriptStartOffset!==void 0){t.jsonlBaseOffset=t.auditTranscriptStartOffset;return}t.jsonlBaseOffset=b(i).size}catch{}}captureAuditTranscriptStart(e){const t=this.activeEvent;if(!t||t.eventId!==e||t.rawEvent?.audit?.enabled!==!0||t.auditTranscriptStartOffset!==void 0||!this.claudeCliSessionId)return;const i=this.claudeSessionCwd||process.cwd(),s=z(this.claudeCliSessionId,i);t.auditTranscriptPath=s,t.auditClaudeSessionId=this.claudeCliSessionId;try{t.auditTranscriptStartOffset=P(s)?b(s).size:0}catch{t.auditTranscriptStartOffset=0}const n=this.auditEvidencePaths;n&&(t.auditOtelLogsStartOffset=Y(n.logsPath),t.auditOtelTracesStartOffset=Y(n.tracesPath),n.rawProviderBody&&(t.auditRawBodyFilesBefore=Re(n.rawBodyDir)))}snapshotAuditBoundary(e){if(e.rawEvent?.audit?.enabled!==!0)return;if(e.auditTranscriptStartOffset===void 0||!e.auditTranscriptPath||!e.auditClaudeSessionId){this.completedAuditBoundaries.set(e.eventId,Object.freeze({adapterType:this.type}));return}let t=e.auditTranscriptStartOffset;try{t=P(e.auditTranscriptPath)?b(e.auditTranscriptPath).size:e.auditTranscriptStartOffset}catch{}const i=rt(e.auditTranscriptPath),s=this.auditEvidencePaths,n=new Set(e.auditRawBodyFilesBefore??[]),r=s?.rawProviderBody?Re(s.rawBodyDir).filter(c=>!n.has(c)):[],a=Object.freeze({adapterType:this.type,providerSessionId:e.auditClaudeSessionId,transcriptPath:e.auditTranscriptPath,transcriptStartOffset:e.auditTranscriptStartOffset,transcriptEndOffset:t,...e.auditSingleTurnFallback?{singleTurnFallback:!0}:{},subagentRoot:p(i,e.auditClaudeSessionId,"subagents"),...s?{otelLogsPath:s.logsPath,otelLogsStartOffset:e.auditOtelLogsStartOffset??0,otelLogsEndOffset:Y(s.logsPath),otelTracesPath:s.tracesPath,otelTracesStartOffset:e.auditOtelTracesStartOffset??0,otelTracesEndOffset:Y(s.tracesPath),rawApiBodyDirectory:s.rawBodyDir,rawApiBodyPaths:r}:{}});this.completedAuditBoundaries.set(e.eventId,a)}noteSelfDrivenActivity(e){!this.selfDrivenActive&&this.lastClearedEvent&&Date.now()-this.lastClearedEvent.ts<zt||(this.selfDrivenLastSignalAt=Date.now(),e&&(this.selfDrivenLabel=Jt(e)),!this.selfDrivenActive&&(this.selfDrivenActive=!0,o.info("claude-adapter",`Self-driven activity detected for session ${this.sessionId} (no active event), debouncing before reporting working state`),this.selfDrivenReportTimer=setTimeout(()=>{this.selfDrivenReportTimer=null,!(!this.selfDrivenActive||this.stopped||this.activeEvent)&&(this.selfDrivenReported=!0,o.info("claude-adapter",`Self-driven working state confirmed for session ${this.sessionId} (sustained >${Fe/1e3}s), reporting`),this.emit("sessionActivity",this.sessionId??"",!0,this.selfDrivenLabel??void 0))},Fe),this.selfDrivenReportTimer.unref(),this.selfDrivenSweepTimer=setInterval(()=>this.sweepSelfDriven(),qt),this.selfDrivenSweepTimer.unref()))}sweepSelfDriven(){if(!this.selfDrivenActive)return;if(this.stopped||this.activeEvent){this.stopSelfDriven();return}const e=Date.now()-this.selfDrivenLastSignalAt;if(this.claudeCliSessionId&&this.claudeSessionCwd){const t=re(this.claudeCliSessionId,this.claudeSessionCwd),i=t.lastStopReason!==null&&t.lastStopReason!=="end_turn",s=t.freshMs!==null&&t.freshMs<Oe,n=Pt(this.claudeCliSessionId,this.claudeSessionCwd)>0;if((i||n)&&s){this.selfDrivenReported&&this.emit("sessionActivity",this.sessionId??"",!0,this.selfDrivenLabel??void 0);return}}if(e<Ht){this.selfDrivenReported&&this.emit("sessionActivity",this.sessionId??"",!0,this.selfDrivenLabel??void 0);return}o.info("claude-adapter",`Self-driven activity ended for session ${this.sessionId} (quiet for ${Math.round(e/1e3)}s, no pending work)`),this.stopSelfDriven()}stopSelfDriven(){this.selfDrivenSweepTimer&&(clearInterval(this.selfDrivenSweepTimer),this.selfDrivenSweepTimer=null),this.selfDrivenReportTimer&&(clearTimeout(this.selfDrivenReportTimer),this.selfDrivenReportTimer=null),this.selfDrivenActive&&(this.selfDrivenActive=!1,this.selfDrivenLabel=null,this.selfDrivenReported&&(this.selfDrivenReported=!1,this.emit("sessionActivity",this.sessionId??"",!1)))}resetActiveEventIdleTimer(e){this.clearActiveEventIdleTimer(),this.activeEventIdleTimer=setTimeout(()=>{if(!this.stopped&&this.activeEvent?.eventId===e){if(this.activeEvent?.toolCallInFlight){o.info("claude-adapter",`Idle timeout skipped: toolCallInFlight for ${e}, resetting timer`),this.resetActiveEventIdleTimer(e);return}if(this.activeEvent?.awaitingUserQuestion){o.info("claude-adapter",`Idle timeout skipped: awaitingUserQuestion for ${e}, resetting timer`),this.resetActiveEventIdleTimer(e);return}if(this.pendingPermissions.size>0){o.info("claude-adapter",`Idle timeout skipped: pendingPermissions=${this.pendingPermissions.size} for ${e}, resetting timer`),this.resetActiveEventIdleTimer(e);return}if(this.shouldExtendByLiveness(e)){this.resetActiveEventIdleTimer(e);return}this.finalizeStuckActiveEvent(e,"idle")}},ke)}finalizeStuckActiveEvent(e,t){const i=this.isEventAnswered(),s=i?"responded":"failed",n=t==="idle"?ke:be,r=i?void 0:t==="idle"?`agent idle for ${n/1e3}s`:`agent exceeded max duration ${n/1e3}s`,a=i?void 0:t==="idle"?"agent_idle_timeout":"agent_hard_timeout";o.error("claude-adapter",`Active event ${t} timeout (${n/1e3}s): ${e}, answered=${i}, sending ${s}`),this.flushForwardedTail(e),this.completedEventIds.set(e,Date.now()),this.bridgeCallbacks.sendEventResult(e,s,r,a),this.clearActiveEvent(),s==="failed"&&this.emit("stuck")}resetActiveEventHardTimer(e){this.clearActiveEventHardTimer(),this.activeEventHardTimer=setTimeout(()=>{if(!this.stopped&&this.activeEvent?.eventId===e){if(this.shouldExtendByLiveness(e)){this.resetActiveEventHardTimer(e);return}this.finalizeStuckActiveEvent(e,"hard")}},be)}clearActiveEventPostReplyTimer(){this.activeEventPostReplyTimer&&(clearTimeout(this.activeEventPostReplyTimer),this.activeEventPostReplyTimer=null)}clearActiveEventPostReplyWatcher(){if(this.activeEventPostReplyWatcher){try{this.activeEventPostReplyWatcher.close()}catch{}this.activeEventPostReplyWatcher=null}this.activeEventPostReplyPoll&&(clearInterval(this.activeEventPostReplyPoll),this.activeEventPostReplyPoll=null)}startPostReplyJsonlWatcher(e){if(this.clearActiveEventPostReplyWatcher(),!this.claudeCliSessionId||!this.claudeSessionCwd)return;const t=z(this.claudeCliSessionId,this.claudeSessionCwd);if(!this.activeEvent?.sessionId)return;if(!P(t)){o.info("claude-adapter",`JSONL watcher skipped: file not yet available for ${e}`);return}const s=b(t).size;this.activeEvent&&(this.activeEvent.jsonlBaseOffset=s);let n=null;try{this.activeEventPostReplyWatcher=st(t,()=>{n&&clearTimeout(n),n=setTimeout(()=>this.finalizeIfEndTurn(e),300)}),this.activeEventPostReplyPoll=setInterval(()=>this.finalizeIfEndTurn(e),Ut),o.info("claude-adapter",`JSONL watcher started for ${e} at ${t} (baseOffset=${s})`)}catch(r){o.warn("claude-adapter",`JSONL watcher start failed for ${e}: ${r}`)}}finalizeIfEndTurn(e){if(this.stopped||this.activeEvent?.eventId!==e||!this.claudeCliSessionId||!this.claudeSessionCwd)return;const t=q(this.claudeCliSessionId,this.claudeSessionCwd,this.activeEvent.jsonlBaseOffset);if(t&&t.stopReason==="end_turn"){o.info("claude-adapter",`JSONL watcher: end_turn detected for ${e}, finalizing as responded`);const i=this.activeEvent.sessionId;this.flushForwardedTail(e),this.bridgeCallbacks.sendEventResult(e,"responded",void 0,void 0),this.clearActiveEvent(),this.armStopHookBarrier(i);return}if(this.activeEvent?.replied&&this.activeEvent.jsonlBaseOffset!==void 0&&we(this.claudeCliSessionId,this.claudeSessionCwd,this.activeEvent.jsonlBaseOffset,!0)){if(this.lastPtyOutputAt>0&&Date.now()-this.lastPtyOutputAt<Me)return;o.info("claude-adapter",`JSONL poll: terminal tool_result detected for ${e} (file quiet, no end_turn), finalizing as responded`),this.flushForwardedTail(e),this.bridgeCallbacks.sendEventResult(e,"responded",void 0,void 0),this.clearActiveEvent()}}startPostReplyDeadline(e){this.clearActiveEventPostReplyTimer(),this.clearActiveEventHardTimer(),this.activeEventPostReplyTimer=setTimeout(()=>{if(this.stopped||this.activeEvent?.eventId!==e||!this.activeEvent?.replied&&!this.activeEvent?.textForwarded)return;if(this.shouldExtendByLiveness(e)){this.startPostReplyDeadline(e);return}const t=this.activeEvent.repliedAt,i=Math.round((Date.now()-t)/1e3);o.info("claude-adapter",`Post-reply deadline reached for ${e} (${i}s since reply), completing as responded`),this.flushForwardedTail(e),this.bridgeCallbacks.sendEventResult(e,"responded",void 0,void 0),this.clearActiveEvent()},Ft)}beginCompaction(){this.compacting||(this.compacting=!0,o.info("claude-adapter","Compaction started; gating input"),this.emit("pauseIntake","compaction"),this.resetCompactingActivityTimer())}resetCompactingActivityTimer(){this.compactingTimer&&clearTimeout(this.compactingTimer),this.compactingTimer=setTimeout(()=>{o.warn("claude-adapter","Compaction stall: no PTY activity for 90s, emitting stuck"),this.emit("stuck")},9e4),this.compactingTimer.unref?.()}finishCompaction(e){if(!this.compacting)return;this.compacting=!1,this.compactingTimer&&(clearTimeout(this.compactingTimer),this.compactingTimer=null),o.info("claude-adapter",`Compaction finished (${e}); resuming intake`),this.emit("resumeIntake","compaction");const t=this.compactionDoneResolver;this.compactionDoneResolver=null,t?.(e)}armStopHookBarrier(e){this.stopHookBarrierTimer&&clearTimeout(this.stopHookBarrierTimer),this.stopHookBarrierSessionId=e,this.emit("pauseIntake","barrier"),this.stopHookBarrierTimer=setTimeout(()=>{this.stopHookBarrierSessionId===e&&(o.warn("claude-adapter",`Stop hook barrier timeout for session=${e}`),this.clearStopHookBarrier())},Qt)}clearStopHookBarrier(){const e=this.stopHookBarrierSessionId!==null;this.stopHookBarrierSessionId=null,this.stopHookBarrierTimer&&(clearTimeout(this.stopHookBarrierTimer),this.stopHookBarrierTimer=null),e&&this.emit("resumeIntake","barrier")}clearPendingMcpFailureTimer(){this.pendingMcpFailureTimer&&(clearTimeout(this.pendingMcpFailureTimer),this.pendingMcpFailureTimer=null)}markMcpStartupFailure(){if(!this.mcpStartupFailureHandled&&(this.mcpStartupFailureHandled=!0,this.mcpChannelBroken=!0,o.error("claude-adapter","Claude reported blocking MCP server startup failure"),this.activeEvent)){const e=this.activeEvent.eventId,t=this.activeEvent.sessionId;this.bridgeCallbacks.sendStreamChunk(e,t,`
|
|
19
19
|
|
|
20
20
|
Error: MCP server startup failed`,1,!1),this.bridgeCallbacks.sendEventResult(e,"failed","MCP server startup failed"),this.completedEventIds.set(e,Date.now()),this.clearActiveEvent()}}}class ti extends Se{adapterSessionId;constructor(e){super(),this.adapterSessionId=e}emitDone(e){this.emit("done",e)}emitError(e){if(this.listenerCount("error")===0){o.warn("claude-adapter",`Prompt handle error (no listeners): ${e.message}`);return}this.emit("error",e)}async cancel(){}}function S(d){if(d==null)return 0;const e=Number(d);return Number.isFinite(e)?e:0}function Ue(d){if(typeof d!="string"||!d.trim())return;let e;try{e=JSON.parse(d)}catch{return}if(!Array.isArray(e))return;const t=e.filter(i=>{if(!i||typeof i!="object")return!0;const s=String(i.content??"");return!He(s)});return JSON.stringify(t)}function He(d){const e=d.trim();return e?e.includes("\u4ECE\u7EBF\u4E0A\u8BB0\u5F55\u6062\u590D")||e.includes("\u672C\u4F1A\u8BDD\u7684\u672C\u5730\u5386\u53F2\u8BB0\u5F55\u5DF2\u4E22\u5931")||e.includes("\u65E0\u6CD5\u7EED\u63A5\u4E4B\u524D\u7684\u4E0A\u4E0B\u6587")||/历史.*丢失/.test(e)&&/新建话题|恢复|怎么处理|如何继续|选哪个/.test(e):!1}function X(d){return String(d).replace(/\\/g,"\\\\").replace(/\{/g,"\\{").replace(/\}/g,"\\}")}async function ii(d,e,t){const i=p(d,"claude.expect"),s=p(d,"claude.pid"),n=["log_user 1","set timeout -1","set startup_prompt_armed 1",`set cmd_fifo {${X(p(d,"cmd.fifo"))}}`,"file delete -force $cmd_fifo","exec mkfifo $cmd_fifo","set cmd_fd [open $cmd_fifo r+]","fconfigure $cmd_fd -blocking 0 -buffering line",`set claude_command [list {${X(e)}}${t.map(r=>` {${X(r)}}`).join("")}]`,"spawn -noecho {*}$claude_command",`set pid_file [open {${X(s)}} w]`,"puts $pid_file [exp_pid -i $spawn_id]","close $pid_file","after 500",'send -- "\\r"',"expect {"," -re {(?i)(Quick.*safety.*check|trust.*folder)} {",' if {$startup_prompt_armed} { send -- "1\\r"; after 300 }; exp_continue'," }"," -re {(?i)(I am using this for local development|Please use --channels|dangerously-load-development-channels)} {",' if {$startup_prompt_armed} { send -- "\\r"; after 300 }; exp_continue'," }"," -re {(?i)(Enter.*confirm|Press.*Enter|Hit.*Enter|Continue.*Enter)} {",' if {$startup_prompt_armed} { send -- "\\r"; after 300 }; exp_continue'," }"," -re {(?i)Listening.*channel messages.*server:grix} {"," set startup_prompt_armed 0"," }"," eof {}","}","proc handle_cmd {} {"," upvar cmd_fd cmd_fd cmd_fifo cmd_fifo"," if {[catch {gets $cmd_fd} __line]} { return }"," if {[eof $cmd_fd]} {"," catch {close $cmd_fd}"," if {[catch {set cmd_fd [open $cmd_fifo r+]} err]} { return }"," fconfigure $cmd_fd -blocking 0 -buffering line"," fileevent $cmd_fd readable handle_cmd",' } elseif {$__line ne ""} {',' send -- "$__line\\r"'," }","}","fileevent $cmd_fd readable handle_cmd","expect_background {"," -re .+ { }"," eof { set ::__done 1 }","}","set ::__done 0","vwait ::__done",""];return await R(s,"","utf8"),await R(i,n.join(`
|
|
21
21
|
`),"utf8"),{expectPath:i,pidPath:s}}async function si(d,e=50,t=100){for(let i=0;i<e;i++){try{const s=await T(d,"utf8"),n=parseInt(String(s).trim(),10);if(Number.isFinite(n)&&n>0)return n}catch{}await new Promise(s=>setTimeout(s,t))}return 0}function ni(d,e){return new Promise((t,i)=>{let s=!1;const n=a=>{s||(s=!0,d.off("error",r),a())},r=a=>n(()=>i(a));d.once("error",r),si(e).then(a=>n(()=>t(a))).catch(a=>n(()=>i(a)))})}function qe(d){return d.replace(/[/\\]/g,"-")}function ri(d){const e=w();return p(e.dataDir,"claude-mcp-configs",`${qe(d)}.json`)}function ai(d){const e=w();return p(e.dataDir,"claude-system-prompts",`${qe(d)}.txt`)}function oi(d){let e=Q.developmentChannels;const t=ee(d,["--dangerously-load-development-channels",`server:${M}`,"--version"],{encoding:"utf8",timeout:3e3,windowsHide:!0,...process.platform==="win32"?{shell:!0}:{}});t&&!t.error&&typeof t.status=="number"&&(e=t.status===0);try{const i=ee(d,["--help"],{encoding:"utf8",timeout:3e3,windowsHide:!0,...process.platform==="win32"?{shell:!0}:{}});if(!i)return{...Q,developmentChannels:e};const s=`${i.stdout??""}
|
|
22
22
|
${i.stderr??""}`;return s.trim()?{developmentChannels:e,maxTurns:s.includes("--max-turns")}:{...Q,developmentChannels:e}}catch(i){return o.warn("claude-adapter",`Claude capability probe failed, assuming modern CLI: ${i instanceof Error?i.message:String(i)}`),{...Q,developmentChannels:e}}}async function li(d){const e=p(d,"claude-extra-mcp-servers.json");let t;try{t=await T(e,"utf8")}catch{return{}}try{const s=JSON.parse(t)?.mcpServers;if(!s||typeof s!="object"||Array.isArray(s))return o.warn("claude-adapter",`Ignoring ${e}: missing "mcpServers" object`),{};const n={};for(const[r,a]of Object.entries(s)){const c=a;if(!c||typeof c!="object"||typeof c.command!="string"||!P(c.command)){o.warn("claude-adapter",`Skipping extra MCP server "${r}": command missing or not found`);continue}n[r]=a}return n}catch(i){return o.warn("claude-adapter",`Ignoring ${e}: invalid JSON (${i instanceof Error?i.message:String(i)})`),{}}}async function ci(d,e,t){const i=w();await O(p(i.dataDir,"claude-mcp-configs"),{recursive:!0});const n={...await li(i.configDir),[M]:{type:"stdio",command:process.execPath,args:e}};t&&(n["grix-app-bridge"]={type:"stdio",command:process.execPath,args:t});const a=`${JSON.stringify({mcpServers:n},null,2)}
|
|
23
23
|
`;let c="";try{c=await T(d,"utf8")}catch{}c!==a&&(await R(d,a,"utf8"),o.info("claude-adapter",`Wrote MCP config: ${d}`))}async function di(d,e){await O(p(w().dataDir,"claude-system-prompts"),{recursive:!0});const t=`${e}
|
|
24
|
-
`;let i="";try{i=await T(d,"utf8")}catch{}i!==t&&await R(d,t,"utf8")}function ce(d,e){if(process.platform==="win32")return;let t=!1;try{const i=te(`ps ax -o pid,command | grep -E -- '--(session-id|resume) ${d}' | grep -v grep`,{encoding:"utf8",timeout:5e3}).trim();if(i)for(const s of i.split(`
|
|
24
|
+
`;let i="";try{i=await T(d,"utf8")}catch{}i!==t&&await R(d,t,"utf8")}function ce(d,e){if(process.platform==="win32")return;let t=!1;try{const i=te(`ps ax -o pid,command | grep -E -- '--(session-id|resume) ${d}' | grep -v grep`,{encoding:"utf8",timeout:5e3,windowsHide:!0}).trim();if(i)for(const s of i.split(`
|
|
25
25
|
`)){const n=parseInt(s.trim(),10);if(n>0&&n!==process.pid&&!e.includes(n)){o.info("claude-adapter",`Killing stale Claude process PID=${n} holding session ${d}`);try{process.kill(n,"SIGTERM"),t=!0}catch{}}}}catch{}t||de(d)}function de(d){const e=p(I(),".claude"),t=[p(e,"session-env",d)];try{const i=p(e,"sessions");for(const s of se(i))if(s.endsWith(".json"))try{const n=p(i,s),r=JSON.parse(j(n,"utf8"));r&&r.sessionId===d&&t.push(n)}catch{}}catch{}for(const i of t)try{b(i),Ke(i,{recursive:!0,force:!0}),o.info("claude-adapter",`Removed stale session file: ${i}`)}catch{}}function qi(d,e){if(!e)return!1;const t=p(I(),".claude"),i=K(e),s=p(t,"projects",i,`${d}.jsonl`);try{return b(s),!0}catch{return!1}}function ze(d,e){if(!e)return!1;const t=p(I(),".claude","projects"),i=K(e),s=p(t,i),n=p(s,`${d}.jsonl`),r=`${d}.jsonl`,a=[];let c;try{c=se(t)}catch{return!1}for(const h of c){const f=p(t,h,r);try{const m=b(f);m.isFile()&&m.size>0&&a.push({path:f,size:m.size,mtimeMs:m.mtimeMs,lines:-1})}catch{}}if(a.length===0)return!1;let l=a[0];if(a.length>1){for(const h of a)h.lines=ue(h.path);a.sort((h,f)=>f.lines-h.lines||f.size-h.size||f.mtimeMs-h.mtimeMs),l=a[0]}if(l.path===n)return!0;const u=a.find(h=>h.path===n);if(u){const h=u.lines>=0?u.lines:ue(u.path),f=l.lines>=0?l.lines:ue(l.path);if(h>=f)return!0}try{return ve(s,{recursive:!0}),me(l.path,n),o.info("claude-adapter",`Reclaimed session ${d} history ${l.path} -> ${n} (${a.length} copies; cwd drift recovered via move)`),!0}catch(h){return o.warn("claude-adapter",`Reclaim session ${d} from ${l.path} failed: ${h.message}`),P(n)}}function Je(d,e){if(!e)return!1;const t=p(I(),".claude","projects",K(e),`${d}.jsonl`);let i="";try{i=j(t,"utf8")}catch{return!1}if(!He(i))return!1;const s=`${t}.polluted-history-recovery-${Date.now()}`;try{return me(t,s),de(d),o.warn("claude-adapter",`Quarantined polluted Claude transcript ${t} -> ${s}; continuing without local resume`),!0}catch(n){return o.warn("claude-adapter",`Failed to quarantine polluted Claude transcript ${t}: ${n instanceof Error?n.message:String(n)}`),!1}}function ue(d){try{const e=j(d);let t=0;for(let i=0;i<e.length;i++)e[i]===10&&t++;return t}catch{return 0}}function Ge(d,e){if(!e)return;const t=p(I(),".claude"),i=K(e),s=p(t,"projects",i,`${d}.jsonl`);let n;try{n=j(s)}catch{return}if(n.length===0)return;let r=-1;for(let l=n.length-1;l>=0;l--)if(n[l]===10){r=l;break}const a=r+1,c=n.subarray(a).toString("utf8").trim();if(c)try{JSON.parse(c)}catch{try{Ze(s,a),o.warn("claude-adapter",`repairJsonl: stripped ${n.length-a}B invalid trailing data from ${d}`)}catch(l){o.error("claude-adapter",`repairJsonl: failed to truncate ${d}: ${l instanceof Error?l.message:l}`)}}}function ui(){return process.env.ComSpec||p(process.env.SystemRoot||"C:\\Windows","System32","cmd.exe")}function K(d){const e=d.replace(/\\/g,"/").replace(/:\/\//g,"/");if(/^[a-zA-Z]:\//.test(e)||e.startsWith("//"))return e.replace(/[^a-zA-Z0-9]/g,"-");let i=e;try{i=Xe(e)}catch{}return i.replace(/[^a-zA-Z0-9]/g,"-")}export{Hi as ClaudeAdapter,K as deriveClaudeProjectDirName,li as loadClaudeExtraMcpServers,Ce as parseAskUserQuestionPayload,Mt as resolveExpectPath,ui as resolveWindowsShellCommand};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{execFileSync as N}from"node:child_process";import{readJSONFile as A}from"../../core/util/json-file.js";import{log as o}from"../../core/log/index.js";import{homedir as k}from"node:os";import{join as f}from"node:path";const c=[{id:"opus",displayName:"Opus"},{id:"sonnet",displayName:"Sonnet"},{id:"haiku",displayName:"Haiku"},{id:"claude-fable-5",displayName:"Claude Fable 5"},{id:"claude-opus-4-8",displayName:"Claude Opus 4.8"},{id:"claude-opus-4-7",displayName:"Claude Opus 4.7"},{id:"claude-opus-4-6",displayName:"Claude Opus 4.6"},{id:"claude-sonnet-4-6",displayName:"Claude Sonnet 4.6"},{id:"claude-sonnet-4-5",displayName:"Claude Sonnet 4.5"},{id:"claude-haiku-4-5-20251001",displayName:"Claude Haiku 4.5"}],T=3600*1e3,
|
|
1
|
+
import{execFileSync as N}from"node:child_process";import{readJSONFile as A}from"../../core/util/json-file.js";import{log as o}from"../../core/log/index.js";import{homedir as k}from"node:os";import{join as f}from"node:path";const c=[{id:"opus",displayName:"Opus"},{id:"sonnet",displayName:"Sonnet"},{id:"haiku",displayName:"Haiku"},{id:"claude-fable-5",displayName:"Claude Fable 5"},{id:"claude-opus-4-8",displayName:"Claude Opus 4.8"},{id:"claude-opus-4-7",displayName:"Claude Opus 4.7"},{id:"claude-opus-4-6",displayName:"Claude Opus 4.6"},{id:"claude-sonnet-4-6",displayName:"Claude Sonnet 4.6"},{id:"claude-sonnet-4-5",displayName:"Claude Sonnet 4.5"},{id:"claude-haiku-4-5-20251001",displayName:"Claude Haiku 4.5"}],T=3600*1e3,w=300*1e3;let e=null,i=0;function O(){const t=f(k(),".claude");return A(f(t,"settings.json"))?.env??{}}function C(t){return!(!t?.accessToken||t.expiresAt&&Date.now()>t.expiresAt-w)}function _(){if(process.platform==="darwin")try{const t=process.env.USER||process.env.LOGNAME||"";if(!t)return null;const r=N("security",["find-generic-password","-s","Claude Code-credentials","-a",t,"-w"],{encoding:"utf-8",timeout:5e3,windowsHide:!0}).trim(),l=JSON.parse(r)?.claudeAiOauth;return C(l)?l.accessToken:(l?.accessToken&&o.info("model-list","Claude OAuth token expired, skipping keychain"),null)}catch{return null}else try{const t=f(k(),".claude",".credentials.json"),a=A(t)?.claudeAiOauth;return C(a)?a.accessToken:(a?.accessToken&&o.info("model-list","Claude OAuth token expired, skipping credentials file"),null)}catch{return null}}async function H(){if(e&&Date.now()-i<T)return e;const t=O(),r=(t.ANTHROPIC_BASE_URL??"").trim()||"https://api.anthropic.com",a=(t.ANTHROPIC_AUTH_TOKEN??"").trim(),l=(t.ANTHROPIC_API_KEY??"").trim();let d=a||l,h=!!a;if(!d){const n=_();n&&(d=n,h=!0,o.info("model-list","Using Claude CLI OAuth token from Keychain for model list"))}if(!d)return o.info("model-list","No API credentials found, using fallback model list"),e=c,i=Date.now(),e;try{const n=`${r.replace(/\/+$/,"")}/v1/models`,u={"anthropic-version":"2023-06-01"};h?u.Authorization=`Bearer ${d}`:u["x-api-key"]=d;const y=new AbortController,g=setTimeout(()=>y.abort(),1e4),m=await fetch(n,{headers:u,signal:y.signal});if(clearTimeout(g),!m.ok)return o.warn("model-list",`API returned ${m.status}, using fallback model list`),e=c,i=Date.now(),e;const p=(await m.json()).data;return!Array.isArray(p)||p.length===0?(o.warn("model-list","API returned empty model list, using fallback"),e=c,i=Date.now(),e):(e=p.map(s=>({id:s.id,displayName:s.display_name||s.name||s.id})),i=Date.now(),o.info("model-list",`Fetched ${e.length} models from API: ${e.map(s=>s.id).join(", ")}`),e)}catch(n){return o.warn("model-list",`Failed to fetch models: ${n instanceof Error?n.message:n}, using fallback`),e=c,i=Date.now(),e}}function P(){return e??c}function v(){e=null,i=0}export{H as fetchAvailableModels,P as getCachedModels,O as readSettingsEnv,v as resetModelCache};
|