diff --git a/lib/index.js b/lib/index.js index 4dea218..fddce49 100644 --- a/lib/index.js +++ b/lib/index.js @@ -366,11 +366,11 @@ ${i.appendMessage}`:ut(e);return await this.#b(t,a,{replyTo:i.replyTo}).catch(() ${e.batchInputMessage}`:ut(A);await this.#b(t.message.chat_id,r,{replyTo:t.message.message_id}).catch(()=>{})}async waitForIdle(){for(;;){let t=[...this.#i.values(),...[...this.#n.values()].flatMap(o=>o.queue?[o.queue]:[]),...this.#g,...this.#I,...this.#p,...this.#V.values(),...[...this.#q.values()].map(o=>o.promise)];if(t.length===0||(await Promise.allSettled(t),this.#i.size===0&&this.#g.size===0&&this.#I.size===0&&this.#p.size===0&&this.#V.size===0&&this.#q.size===0&&![...this.#n.values()].some(o=>o.queue)))return}}#re(){let t=AbortSignal.timeout(this.#H);return this.#w?AbortSignal.any([this.#w,t]):t}#ne(t){return this.#n.has(t)||this.#D.hasPending(t)}async#xe(t,o,i,e,a){if(this.#w?.throwIfAborted(),this.#r.hasSeen(o))return;await this.#r.markSeen(o),this.#E.lastMessageAt=new Date().toISOString(),this.#E.messagesReceived+=1;let A=await a(lA(e.content)??"",this.#t,this.#r,i,{signal:this.#w,isDirect:t.message.chat_type==="p2p",hasImages:Nt(e),hasFiles:Dt(e),pendingInteraction:this.#ne(i),control:{owner:this,key:i}});A?.stopped&&await Promise.allSettled([this.#be(i),this.#D.closeRoute(i)]);for(let r of A?.messages??[A?.message])r&&await this.#b(t.message.chat_id,r,{replyTo:t.message.message_id});this.#E.lastError=null}async#Me(t,o,{alreadyRecorded:i=!1}={}){this.#w?.throwIfAborted();let e=t.message.message_id;if(!i){if(this.#r.hasSeen(e))return;await this.#r.markSeen(e),this.#E.lastMessageAt=new Date().toISOString(),this.#E.messagesReceived+=1}let a=pS(t,this.#e),A=a.content,r=Nt(a),s=Dt(a),g=Hr(a),c=!r&&!s&&A?A.trim():null;if(!A&&!r&&!s&&!g){await this.#b(t.message.chat_id,w("\u76EE\u524D\u652F\u6301\u6587\u5B57\u3001\u56FE\u7247\u548C\u6587\u4EF6\u6D88\u606F\u3002"),{replyTo:t.message.message_id});return}if(c!==null&&u5.test(c)){await this.#_e(t,c);return}if(c==="/help"){await this.#b(t.message.chat_id,e5(),{replyTo:t.message.message_id});return}if(E5.test(c)){await this.#ee(o,t.message.chat_id,{replyTo:t.message.message_id});return}if(c==="/new"){if(this.#i.has(o)||this.#ne(o)){await this.#b(t.message.chat_id,w("\u5F53\u524D\u4EFB\u52A1\u4ECD\u5728\u8FD0\u884C\uFF0C\u8BF7\u5148\u505C\u6B62\u4EFB\u52A1\u6216\u7B49\u5F85\u4EFB\u52A1\u5B8C\u6210\u540E\u518D\u5F00\u542F\u65B0\u4F1A\u8BDD\u3002"),{replyTo:t.message.message_id});return}await this.#r.clearSession(o),await this.#b(t.message.chat_id,w("\u5DF2\u5F00\u542F\u5168\u65B0 Harness \u4F1A\u8BDD\u3002"),{replyTo:t.message.message_id}),await this.#ee(o,t.message.chat_id,{replyTo:t.message.message_id});return}if(c==="/status"){await this.#rA(o,t.message.chat_id,t.message.message_id);return}if(c==="/compact"){let Q=await Ti(c,this.#t,this.#r,o,{signal:this.#w});Q&&await this.#b(t.message.chat_id,Q.message,{replyTo:t.message.message_id});return}if(wve.test(c)){let Q=c.replace(/^\/(?:sessionlist|sessions)/i,"").trim(),y=m_(Q);if(y.error){await this.#b(t.message.chat_id,y.error,{replyTo:t.message.message_id});return}await this.#de({chatId:t.message.chat_id,key:o,replyTo:t.message.message_id},y.selector||null,0,{limit:y.limit});return}if(Dve.test(c)){await this.#Re({chatId:t.message.chat_id,key:o,replyTo:t.message.message_id});return}if(kP.test(c)){let Q=(kP.exec(c)?.[1]??"").trim()||null;await this.#fe(o,t.message.chat_id,Q,{replyTo:t.message.message_id});return}if(vP.test(c)){let Q=(vP.exec(c)?.[1]??"").trim()||null;await this.#Qe(o,t.message.chat_id,Q,{replyTo:t.message.message_id});return}if(C5.test(c)){await this.#ye(o,t.message.chat_id,{replyTo:t.message.message_id});return}if(PP.test(c)){let y=PP.exec(c)[1]?.toLowerCase();if(y!=="on"&&y!=="off"){await this.#b(t.message.chat_id,w("\u7528\u6CD5\uFF1A/archived on\uFF08\u5305\u542B\u5F52\u6863\u4F1A\u8BDD\uFF09\u6216 /archived off\uFF08\u9690\u85CF\u5F52\u6863\u4F1A\u8BDD\uFF09"),{replyTo:t.message.message_id});return}typeof this.#r?.setIncludeArchivedSessions=="function"&&await this.#r.setIncludeArchivedSessions(y==="on"),await this.#b(t.message.chat_id,y==="on"?w("\u5DF2\u5F00\u542F\uFF1A\u4F1A\u8BDD\u5217\u8868\u5305\u542B\u5F52\u6863\u4F1A\u8BDD\u3002"):w("\u5DF2\u5173\u95ED\uFF1A\u4F1A\u8BDD\u5217\u8868\u9690\u85CF\u5F52\u6863\u4F1A\u8BDD\u3002"),{replyTo:t.message.message_id});return}if(I5.test(c)){let Q=this.#Ze(o);if(Q){await this.#Xe(Q,Number(c),{chatId:t.message.chat_id,key:o,event:t});return}}let I=c===null?null:await To(A,this.#t,o);if(I){for(let Q of I.messages??[I.message])await this.#b(t.message.chat_id,Q,{replyTo:t.message.message_id});return}let d=c===null?null:await Ti(c,this.#t,this.#r,o,{signal:this.#w});if(d){await this.#b(t.message.chat_id,d.message,{replyTo:t.message.message_id});return}this.#O.info?.(`[dsh-feishu] processing ${t.message.chat_type} message ${e}`);let B=t.batchSubmission??null,f=!1;try{let{receipt:Q,artifactSendErrors:y,textDeliveryErrors:m=0}=await this.#EA(t,o,a,{onAskComplete:B?()=>{f=this.#a.complete(o,B.token).completed}:void 0});return this.#E.messagesReplied+=1,this.#E.lastReplyAt=new Date().toISOString(),this.#E.lastError=null,m===0&&y===0&&bo(this.#E),Q}catch(Q){if(B&&!f){if(Q?.code==="turn-stopped")throw this.#a.complete(o,B.token),Q;let y=this.#a.fail(o,B.token);if(y.retained){let m=new Error(Q?.message??String(Q),{cause:Q});throw m.code=Q?.code,m.providerCode=Q?.providerCode,m.method=Q?.method,Number.isInteger(Q?.status)&&(m.status=Q.status),m.batchInputMessage=y.message,m}}throw Q}finally{await this.#be(o),await this.#D.closeRoute(o)}}async#_e(t,o){if(t?.message?.chat_type!=="p2p"){await this.#b(t.message.chat_id,w("\u4E3A\u907F\u514D\u6388\u6743\u94FE\u63A5\u66B4\u9732\uFF0C\u8BF7\u79C1\u804A\u673A\u5668\u4EBA\u53D1\u9001 /repair\u3002"));return}let i=zve(t);if(!i){await this.#b(t.message.chat_id,w("\u65E0\u6CD5\u8BC6\u522B\u5F53\u524D\u53D1\u9001\u8005\uFF0C\u672A\u53D1\u8D77\u4FEE\u590D\u3002"));return}if(!this.#F){await this.#b(t.message.chat_id,w("\u5F53\u524D Host \u7248\u672C\u6682\u4E0D\u652F\u6301\u804A\u5929\u5185\u4FEE\u590D\uFF0C\u8BF7\u5148\u66F4\u65B0\u63D2\u4EF6\u3002"));return}let e=Sve.exec(o);if(!e){await this.#b(t.message.chat_id,w("\u7528\u6CD5\uFF1A/repair\u3001/repair qr\u3001/repair status\u3001/repair cancel \u6216 /repair verify"));return}let a=e[1]?.toLowerCase()??"start",A=t.message.chat_id;if(a==="start"){await this.#Ye({actorOpenId:i,chatId:A});return}let r=this.#k;if(!r){await this.#b(A,w("\u5F53\u524D Runtime \u6CA1\u6709\u53EF\u6062\u590D\u7684\u4FEE\u590D\u4EFB\u52A1\u8BB0\u5F55\uFF08\u673A\u5668\u4EBA\u53EF\u80FD\u521A\u5B8C\u6210\u5BC6\u94A5\u66F4\u65B0\u5E76\u91CD\u542F\uFF09\u3002\u672C\u547D\u4EE4\u4E0D\u4F1A\u542F\u52A8\u65B0\u7684\u6388\u6743\uFF1B\u8BF7\u67E5\u770B\u673A\u5668\u4EBA\u53D1\u9001\u7684\u9A8C\u8BC1\u7ED3\u679C\uFF0C\u786E\u8BA4\u4E0A\u4E00\u6B21\u4EFB\u52A1\u5DF2\u7ED3\u675F\u540E\u518D\u53D1\u9001 /repair\u3002"));return}if(r.actorOpenId!==i){await this.#b(A,w("\u53E6\u4E00\u4F4D\u7528\u6237\u6B63\u5728\u4FEE\u590D\u8BE5\u673A\u5668\u4EBA\uFF0C\u672C\u6B21\u4E0D\u4F1A\u663E\u793A\u5176\u6388\u6743\u4FE1\u606F\u3002"));return}if(a==="cancel"){let g;try{let c=await this.#F.cancel(this.#le(r));g=vC(c,{botId:this.#T}),r.snapshot=g}catch{await this.#b(A,w("\u6682\u65F6\u65E0\u6CD5\u53D6\u6D88\u4FEE\u590D\u4EFB\u52A1\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5\u3002"));return}g.state==="cancelled"&&(r.stopped=!0,this.#K+=1),await this.#b(A,this.#oe(g));return}let s;try{s=await this.#Ee(r)}catch{await this.#b(A,w("\u6682\u65F6\u65E0\u6CD5\u67E5\u8BE2\u4FEE\u590D\u72B6\u6001\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5\u3002"));return}if(a==="qr"){if(!RP.has(s.state)||!r.verificationUrl){await this.#b(A,this.#oe(s,{verificationFocused:!0}));return}await this.#ze(A,r.verificationUrl,s);return}await this.#b(A,this.#oe(s,{verificationFocused:a==="verify"}))}#le(t){return{botId:this.#T,attemptId:t.attemptId,actorOpenId:t.actorOpenId,chatId:t.chatId}}async#Ye({actorOpenId:t,chatId:o}){let i=this.#k,e=!1;if(i&&RP.has(i.snapshot.state)){if(i.actorOpenId!==t){await this.#b(o,w("\u53E6\u4E00\u4F4D\u7528\u6237\u6B63\u5728\u4FEE\u590D\u8BE5\u673A\u5668\u4EBA\uFF0C\u672C\u6B21\u4E0D\u4F1A\u663E\u793A\u5176\u6388\u6743\u4FE1\u606F\u3002"));return}let r;try{r=await this.#Ee(i)}catch{await this.#b(o,w("\u6682\u65F6\u65E0\u6CD5\u67E5\u8BE2\u4FEE\u590D\u72B6\u6001\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5\u3002"));return}if(r.state==="saving"){await this.#b(o,this.#oe(r));return}if(B5.has(r.state)){let s;try{s=vC(await this.#F.cancel(this.#le(i)),{botId:this.#T}),i.snapshot=s}catch{await this.#b(o,w("\u6682\u65F6\u65E0\u6CD5\u53D6\u6D88\u65E7\u4FEE\u590D\u4EFB\u52A1\uFF0C\u672A\u751F\u6210\u65B0\u94FE\u63A5\uFF1B\u8BF7\u7A0D\u540E\u91CD\u8BD5\u3002"));return}if(!QS.has(s.state)){await this.#b(o,this.#oe(s));return}i.stopped=!0,this.#K+=1,e=s.state==="cancelled"}else QS.has(r.state)&&(i.stopped=!0,this.#K+=1)}let a;try{a=vC(await this.#F.start({botId:this.#T,actorOpenId:t,chatId:o}),{botId:this.#T}),a=await this.#je(a,{actorOpenId:t,chatId:o})}catch{await this.#b(o,w("\u4FEE\u590D\u6D41\u7A0B\u6682\u65F6\u5931\u8D25\uFF0C\u73B0\u6709\u673A\u5668\u4EBA\u8FDE\u63A5\u4E0D\u53D7\u5F71\u54CD\uFF1B\u8BF7\u7A0D\u540E\u53D1\u9001 /repair \u91CD\u8BD5\u3002"));return}let A={attemptId:a.attemptId,actorOpenId:t,chatId:o,snapshot:a,verificationUrl:null,stopped:!1,announcedSaving:!1,announcedTerminal:!1};if(this.#k=A,a.verificationUrl)try{A.verificationUrl=h5(a.verificationUrl,this.#M)}catch{A.stopped=!0,await this.#F.cancel(this.#le(A)).catch(()=>{}),await this.#b(o,w("\u98DE\u4E66\u8FD4\u56DE\u4E86\u65E0\u6CD5\u5B89\u5168\u9A8C\u8BC1\u7684\u6388\u6743\u94FE\u63A5\uFF0C\u5DF2\u4E2D\u6B62\u672C\u6B21\u4FEE\u590D\u3002"));return}if(QS.has(a.state)){A.announcedTerminal=!0,a.state!=="succeeded"&&await this.#b(o,this.#oe(a));return}if(!A.verificationUrl){A.stopped=!0,await this.#b(o,w("\u98DE\u4E66\u672A\u8FD4\u56DE\u6388\u6743\u94FE\u63A5\uFF0C\u5DF2\u4E2D\u6B62\u672C\u6B21\u4FEE\u590D\u3002"));return}await this.#qe(o,A.verificationUrl,a,{restarted:e}),this.#Je(A)}async#je(t,o){let i=t,e=Date.now()+this.#J;for(;!i.verificationUrl&&RP.has(i.state);){if(Date.now()>=e)throw new Error("Feishu repair link timed out");await p5(Math.min(100,this.#U),this.#w),i=vC(await this.#F.status({botId:this.#T,attemptId:i.attemptId,actorOpenId:o.actorOpenId,chatId:o.chatId}),{botId:this.#T})}return i}async#Ee(t){let o=vC(await this.#F.status(this.#le(t)),{botId:this.#T});if(o.attemptId!==t.attemptId)throw new Error("Feishu repair attempt changed unexpectedly");return t.snapshot=o,o.verificationUrl&&(t.verificationUrl=h5(o.verificationUrl,this.#M)),o}#Je(t){let o=++this.#K;(async()=>{for(;!t.stopped&&this.#k===t&&this.#K===o&&!this.#w?.aborted;){let i=Math.max(250,Math.min(1e4,t.snapshot.pollIntervalMs??this.#U));if(await p5(i,this.#w),t.stopped||this.#k!==t||this.#K!==o)return;let e=await this.#Ee(t);if(e.state==="saving"&&!t.announcedSaving&&(t.announcedSaving=!0,await this.#b(t.chatId,w("\u6388\u6743\u5DF2\u786E\u8BA4\uFF0C\u6B63\u5728\u53D1\u9001\u5E76\u7B49\u5F85\u6D4B\u8BD5\u6309\u94AE\u56DE\u8C03\uFF1B\u6536\u5230\u771F\u5B9E\u56DE\u8C03\u540E\u624D\u4F1A\u5B8C\u6210\u3002"))),QS.has(e.state)){t.stopped=!0,e.state!=="succeeded"&&!t.announcedTerminal&&(t.announcedTerminal=!0,await this.#b(t.chatId,this.#oe(e)));return}}})().catch(async()=>{this.#w?.aborted||t.stopped||this.#k!==t||(t.stopped=!0,this.#O.warn?.("[dsh-feishu] callback repair status monitoring failed"),await this.#b(t.chatId,w("\u4FEE\u590D\u72B6\u6001\u67E5\u8BE2\u4E2D\u65AD\uFF0C\u73B0\u6709\u673A\u5668\u4EBA\u8FDE\u63A5\u4E0D\u53D7\u5F71\u54CD\uFF1B\u53D1\u9001 /repair status \u91CD\u8BD5\u67E5\u8BE2\u3002")).catch(()=>{}))})}async#qe(t,o,i,{restarted:e=!1}={}){let a=i.remainingSeconds??(i.expiresAt?Math.max(0,Math.ceil((i.expiresAt-Date.now())/1e3)):null),A=a===null?w("\u94FE\u63A5\u4E3A\u77ED\u671F\u6709\u6548"):w("\u94FE\u63A5\u7EA6 {minutes} \u5206\u949F\u540E\u8FC7\u671F",{minutes:Math.max(1,Math.ceil(a/60))});await this.#b(t,[e?w("\u65E7\u6388\u6743\u94FE\u63A5\u5DF2\u4F5C\u5E9F\uFF0C\u5DF2\u751F\u6210\u65B0\u7684\u4FEE\u590D\u94FE\u63A5\u3002"):w("\u{1F527} \u51C6\u5907\u8865\u5168\u6743\u9650\u4E0E\u56DE\u8C03\u3002"),w("\u672C\u6B21\u4F1A\u589E\u91CF\u6DFB\u52A0\u5F53\u524D\u7F3A\u5C11\u9879\uFF1A\u5361\u7247\u56DE\u8C03 card.action.trigger\uFF1B\u98DE\u4E66\u663E\u793A\u4E3A\u201C\u83B7\u53D6\u5355\u804A\u3001\u7FA4\u7EC4\u6D88\u606F\u201D\u7684\u79DF\u6237\u6743\u9650 im:message:readonly\uFF08\u7528\u4E8E\u8BFB\u53D6\u7528\u6237\u6D88\u606F\u4E2D\u7684\u56FE\u7247\u6216\u6587\u4EF6\uFF09\uFF1Bim:resource\uFF08\u7528\u4E8E\u4E0A\u4F20\u673A\u5668\u4EBA\u53D1\u9001\u7684\u56FE\u7247\u6216\u6587\u4EF6\uFF09\uFF1B\u4EE5\u53CA\u539F\u751F\u547D\u4EE4\u9762\u677F\u6240\u9700\u7684 application:app_slash_command:read / write\u3002\u786E\u8BA4\u9875\u53EA\u4F1A\u663E\u793A\u5F53\u524D\u7F3A\u5C11\u7684\u9879\uFF1B\u82E5\u51FA\u73B0\u4E0A\u8FF0\u8303\u56F4\u4E4B\u5916\u7684\u914D\u7F6E\uFF0C\u8BF7\u53D6\u6D88\u3002"),"",w("\u5F53\u524D\u8BBE\u5907\u76F4\u63A5\u6253\u5F00\uFF1A"),o,"",w("\u82E5\u8981\u7528\u53E6\u4E00\u53F0\u8BBE\u5907\u626B\u7801\uFF0C\u53D1\u9001 /repair qr\u3002{expiry}\u3002",{expiry:A})].join(` `))}async#ze(t,o,i){try{let e=await yve.toBuffer(o,{errorCorrectionLevel:"M",margin:1,width:480,type:"png"}),a=await this.#e.im.v1.image.create({data:{image_type:"message",image:e}}),A=lA(a?.image_key)??lA(a?.data?.image_key);if(!A)throw new Error("Feishu QR upload returned no image key");let r=i.remainingSeconds??(i.expiresAt?Math.max(0,Math.ceil((i.expiresAt-Date.now())/1e3)):null),s=r===null?"":w("\uFF08\u5269\u4F59\u7EA6 {minutes} \u5206\u949F\uFF09",{minutes:Math.max(1,Math.ceil(r/60))});await this.#b(t,w("\u8BF7\u7528\u53E6\u4E00\u53F0\u8BBE\u5907\u626B\u7801\u5B8C\u6210\u6388\u6743{remaining}\u3002",{remaining:s}));let g=await this.#e.im.v1.message.create({params:{receive_id_type:"chat_id"},data:{receive_id:t,msg_type:"image",content:JSON.stringify({image_key:A})}});if(g?.code&&g.code!==0)throw new Error("Feishu QR message send failed")}catch{await this.#b(t,w(`\u4E8C\u7EF4\u7801\u6682\u65F6\u65E0\u6CD5\u53D1\u9001\uFF0C\u8BF7\u76F4\u63A5\u6253\u5F00\u6388\u6743\u94FE\u63A5\uFF1A -{url}`,{url:o}))}}#oe(t,{verificationFocused:o=!1}={}){if(t.state==="succeeded")return w("\u2705 \u4FEE\u590D\u5B8C\u6210\uFF1A\u5DF2\u5B9E\u6D4B\u6536\u5230 card.action.trigger\uFF0C\u83DC\u5355\u6309\u94AE\u73B0\u5728\u53EF\u7528\u3002");if(t.state==="expired"||t.error?.code==="expired_token")return w("\u6388\u6743\u94FE\u63A5\u5DF2\u8FC7\u671F\uFF1B\u5E73\u53F0\u672A\u8FD4\u56DE\u6210\u529F\u7ED3\u679C\uFF0C\u65E0\u6CD5\u786E\u8BA4\u5DF2\u4FEE\u590D\u3002\u53D1\u9001 /repair \u751F\u6210\u65B0\u94FE\u63A5\u3002");if(t.state==="cancelled"||t.error?.code==="abort")return w("\u5DF2\u53D6\u6D88\u672C\u6B21\u4FEE\u590D\u6388\u6743\uFF0C\u672A\u786E\u8BA4\u5B8C\u6210\u4FEE\u590D\u3002");if(t.error?.code==="access_denied")return w("\u4F60\u5DF2\u53D6\u6D88\u6216\u62D2\u7EDD\u6388\u6743\uFF0C\u6CA1\u6709\u786E\u8BA4\u4FEE\u590D\uFF1B\u53D1\u9001 /repair \u53EF\u91CD\u8BD5\u3002");if(t.error?.code==="card_action_probe_timeout"||t.error?.code==="card-action-probe-timeout")return w("\u6388\u6743\u5DF2\u63D0\u4EA4\uFF0C\u4F46\u672A\u6536\u5230\u6D4B\u8BD5\u6309\u94AE\u56DE\u8C03\u3002\u53EF\u80FD\u5C1A\u672A\u70B9\u51FB\u6216\u914D\u7F6E\u4ECD\u5728\u4F20\u64AD\uFF1B\u7A0D\u540E\u53D1\u9001 /repair verify \u67E5\u8BE2\uFF0C\u4E0D\u8981\u76F2\u76EE\u91CD\u590D\u6388\u6743\u3002");if(t.state==="error")return w("\u4FEE\u590D\u6D41\u7A0B\u6682\u65F6\u5931\u8D25\uFF0C\u73B0\u6709\u673A\u5668\u4EBA\u8FDE\u63A5\u4E0D\u53D7\u5F71\u54CD\uFF1B\u53D1\u9001 /repair \u53EF\u91CD\u8BD5\u3002");if(t.state==="saving")return w("\u6388\u6743\u5DF2\u786E\u8BA4\uFF0C\u6B63\u5728\u7B49\u5F85\u4E13\u7528\u6D4B\u8BD5\u6309\u94AE\u7684\u771F\u5B9E\u56DE\u8C03\uFF1B\u56DE\u8C03\u5230\u8FBE\u524D\u4E0D\u4F1A\u5BA3\u544A\u6210\u529F\u3002");if(o)return w("\u6388\u6743\u5C1A\u672A\u5B8C\u6210\uFF0C\u6682\u65F6\u4E0D\u80FD\u9A8C\u8BC1\u5361\u7247\u6309\u94AE\u3002\u8BF7\u5148\u6253\u5F00\u6388\u6743\u94FE\u63A5\u5E76\u786E\u8BA4\u3002");let i=t.remainingSeconds===null?"":w("\uFF0C\u5269\u4F59\u7EA6 {minutes} \u5206\u949F",{minutes:Math.max(1,Math.ceil(t.remainingSeconds/60))});return w("\u4FEE\u590D\u4EFB\u52A1\u6B63\u5728\u7B49\u5F85\u6388\u6743{remaining}\u3002\u53D1\u9001 /repair qr \u53EF\u83B7\u53D6\u4E8C\u7EF4\u7801\uFF0C/repair cancel \u53EF\u53D6\u6D88\u3002",{remaining:i})}onCardAction(t){let o=lA(t?.operator?.open_id)??lA(t?.operator?.user_id)??lA(t?.operator?.operator_id?.open_id)??lA(t?.operator?.operator_id?.user_id)??lA(t?.open_id)??lA(t?.user_id);if(!o)return Promise.resolve();if(!this.#o&&!this.#R.has("*")&&!this.#R.has(o))return this.#O.warn?.("[dsh-feishu] ignoring card action from an unallowed legacy sender"),Promise.resolve();let i=d5(t?.action?.value),e=d5(t?.action?.form_value),a=lA(i.action)??lA(t?.action?.action);if(!a)return Promise.resolve();let A=PC(t?.action?.option)??(a.endsWith("_pick")?Rg(t?.action?.options)[0]:null),r=[...new Set([...Rg(i.options),...Rg(t?.action?.options),...Rg(e[a]),...a==="watch_add"||a==="watch_remove"?Rg(t?.action?.option):[]])],s=a==="workspace_pick"&&typeof A=="string"?`workspace:${A}`:a==="session_pick"&&typeof A=="string"?`use:${A}`:a==="preset_pick"&&typeof A=="string"?`preset:select:${A}`:a==="model_pick"&&typeof A=="string"?`model:select:${A}`:a==="archive_pick"&&typeof A=="string"?`archive:${A}`:a==="steer_pick"&&typeof A=="string"?`steer:${A}`:a,g=lA(t?.context?.open_message_id)??lA(t?.open_message_id)??lA(t?.message_id),c=g?this.#G.get(g):null;if(!c){if(this.#o)return Promise.resolve();let F=lA(t?.context?.open_chat_id)??lA(t?.open_chat_id)??lA(t?.chat_id);return F&&this.#b(F,w("\u8FD9\u4E2A\u83DC\u5355\u5DF2\u8FC7\u671F\uFF0C\u8BF7\u56DE\u590D /m \u91CD\u65B0\u6253\u5F00\u3002"),{replyTo:g}).catch(()=>{}),Promise.resolve()}let I=c.key.startsWith("p2p:")?"direct":c.key.startsWith("group:")?"group":null,d=Oa(this.#o,{conversationType:I,senderIds:o,isCommand:!0});if(!d.allowed)return d.reason==="command-not-allowed"?this.#b(c.chatId,w(so)).catch(()=>{}):(this.#O.warn?.("[dsh-feishu] ignoring card action blocked by access policy"),Promise.resolve());this.#G.delete(g),this.#G.set(g,c);let B={...c,messageId:g,selections:r,operatorOpenId:o},f=lA(i.source),Q=lA(e.steer_text),y=lA(t?.event_id)??lA(t?.header?.event_id)??lA(t?.uuid)??lA(t?.header?.uuid),m=JSON.stringify({messageId:g,resolvedAction:s,option:A,multiValues:r,source:f,formText:Q}),T=s==="stop",_=s.startsWith("steer:")?s.slice(6):null,x=a==="steer"&&f==="quick"&&A!==null&&A!==NC||a==="steer"&&f==="form"&&Q!==null||_!==null&&_!==""&&!(_==="custom"||_===NC);return this.#We(B,m,async()=>{if(a==="steer"){if(f==="quick"&&A){if(A===NC){await this.#z(B.chatId,TP(),{key:B.key,updateMessageId:B.messageId});return}await this.#Ce(B,A);return}if(f==="form"){if(Q){await this.#Ce(B,Q);return}await this.#b(B.chatId,w("\u8BF7\u8F93\u5165\u8865\u5145\u6307\u4EE4\u540E\u518D\u63D0\u4EA4\u3002"),{replyTo:B.messageId??null});return}}await this.#se(s,{...B,actor:B.operatorOpenId})},{lane:T||x?"control":"regular",coalesceStop:T,eventId:y,operatorOpenId:o})}#Te(t=Date.now()){for(let[o,i]of this.#y)i<=t&&this.#y.delete(o);for(;this.#y.size>l5;){let o=this.#y.keys().next().value;if(o===void 0)break;this.#y.delete(o)}}#Ne(t,o=Date.now()){t&&(this.#y.delete(t),this.#y.set(t,o+Tve),this.#Te(o))}#Ve(t){let o=t.key,i=Date.now(),e=this.#m.get(o);if(e?.task||e&&i-e.at{}).finally(()=>{this.#p.delete(a);let A=this.#m.get(o);A?.task===a&&this.#m.set(o,{at:A.at,task:null})}),this.#m.delete(o),this.#m.set(o,{at:i,task:a}),this.#p.add(a);this.#m.size>200;){let A=this.#m.keys().next().value;if(A===void 0)break;this.#m.delete(A)}return a}#We(t,o,i,{lane:e="regular",coalesceStop:a=!1,eventId:A=null,operatorOpenId:r=null}={}){let s=t.key,g=A?`${s}\0${r??""}\0event:${A}`:null,c=g??`${s}\0${r??""}\0action:${o}`,I=Date.now();if(this.#Te(I),g&&(this.#y.get(g)??0)>I)return Promise.resolve();let d=this.#f.get(c);if(d)return d;if(a){let F=this.#Q.get(s);if(F){if(g)for(this.#S.delete(g),this.#S.set(g,F);this.#S.size>l5;){let L=this.#S.keys().next().value;if(L===void 0)break;this.#S.delete(L)}return F}}let B=e==="control",f=B?this.#u:this.#h,Q=B?this.#B:this.#C,y=B?_ve:Mve,m=Q.get(s)??0,T=a?y+1:y;if(m>=T)return this.#Ve(t);let _=f.get(s)??Promise.resolve();Q.set(s,m+1);let R,x=!1;return R=_.catch(()=>{}).then(async()=>{this.#w?.throwIfAborted(),x=!0,await i()}).catch(async F=>{this.#w?.aborted||await this.#Y(t.chatId,F,{logLabel:"card action",replyTo:t.messageId})}).finally(()=>{if(this.#f.get(c)===R&&this.#f.delete(c),x&&g&&this.#Ne(g),a&&this.#Q.get(s)===R&&this.#Q.delete(s),a){let L=Date.now();for(let[q,z]of this.#S)z===R&&(this.#S.delete(q),this.#Ne(q,L))}f.get(s)===R&&f.delete(s);let F=(Q.get(s)??1)-1;F>0?Q.set(s,F):Q.delete(s),this.#p.delete(R)}),this.#f.set(c,R),a&&this.#Q.set(s,R),this.#p.add(R),f.set(s,R),R}async#se(t,{chatId:o,key:i,messageId:e=null,sessionWorkspace:a=null,sessionPage:A=0,sessionLimit:r=null,selections:s=[],actor:g=null}){let c=I=>this.#b(o,I,{replyTo:e});if(t.startsWith("approve:")||t.startsWith("reject:")){let I=t.indexOf(":"),d=t.slice(I+1),B=t.startsWith("approve:")?"allowed-once":"rejected";await this.#D.submitByApprovalId(d,B,{actor:g})||await c(w("\u8BE5\u5BA1\u6279\u5DF2\u5904\u7406\u6216\u4E0D\u5B58\u5728\uFF0C\u65E0\u9700\u91CD\u590D\u64CD\u4F5C\u3002")).catch(()=>{});return}if(t.startsWith("answer:")){let I=t.slice(7),d=I.indexOf(":");if(d!==-1){let B=I.slice(0,d),f=I.slice(d+1),Q=f.indexOf(":"),y=Q===-1?f:f.slice(0,Q),m=Q===-1?"":f.slice(Q+1),T=this.#s.get(B),_=T?this.#n.get(T):null;_&&_.kind==="question"&&!_.submitting&&_.actor===g&&Number(y)===_.index?await this.#Ue(_,m,{chatId:o}):await c(RC()).catch(()=>{})}return}if(t==="sessions"||/^sessions:\d+$/.test(t)){let I=t==="sessions"?0:Number(t.slice(9));await this.#de({chatId:o,key:i,replyTo:e},a,I,{updateMessageId:e,limit:r});return}if(t==="workspaces"){await this.#Re({chatId:o,key:i,replyTo:e},{updateMessageId:e});return}if(t==="watchlist"){await this.#ye(i,o,{updateMessageId:e,replyTo:e});return}if(t==="watch_add"||t==="watch_remove"){if(s.length===0){await c(w("\u8BF7\u5148\u9009\u62E9\u81F3\u5C11\u4E00\u4E2A\u4F1A\u8BDD\u3002"));return}let I=0,d=0;if(t==="watch_add"){let f=new Map;try{f=await this.#dA(a)}catch(Q){this.#O.warn?.("[dsh-feishu] batch watch validation failed:",Q.message)}for(let Q of s){let y=f.get(Q);if(!y){d+=1;continue}let m=await this.#fe(i,o,Q,{notify:!1,validatedTarget:y,replyTo:e});m.changed?I+=1:m.ok||(d+=1)}}else for(let f of s){let Q=await this.#Qe(i,o,f,{notify:!1});Q.changed?I+=1:Q.ok||(d+=1)}let B=I>0&&d>0?t==="watch_add"?w("\u5DF2\u6279\u91CF\u5173\u6CE8 {count} \u4E2A\u4F1A\u8BDD\uFF0C\u53E6\u6709 {failed} \u4E2A\u672A\u6210\u529F\u3002",{count:I,failed:d}):w("\u5DF2\u53D6\u6D88\u5173\u6CE8 {count} \u4E2A\u4F1A\u8BDD\uFF0C\u53E6\u6709 {failed} \u4E2A\u672A\u6210\u529F\u3002",{count:I,failed:d}):I>0?t==="watch_add"?w("\u5DF2\u6279\u91CF\u5173\u6CE8 {count} \u4E2A\u4F1A\u8BDD\u3002",{count:I}):w("\u5DF2\u53D6\u6D88\u5173\u6CE8 {count} \u4E2A\u4F1A\u8BDD\u3002",{count:I}):d>0?w("\u6240\u9009\u4F1A\u8BDD\u5747\u672A\u5904\u7406\u6210\u529F\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5\u3002"):t==="watch_add"?w("\u6240\u9009\u4F1A\u8BDD\u5DF2\u5728\u5173\u6CE8\u5217\u8868\u4E2D\u3002"):w("\u6240\u9009\u4F1A\u8BDD\u5DF2\u4E0D\u5728\u5173\u6CE8\u5217\u8868\u4E2D\u3002");try{await this.#ye(i,o,{updateMessageId:e,replyTo:e})}catch(f){this.#O.warn?.("[dsh-feishu] watch list refresh failed:",f.message)}await c(B).catch(f=>{this.#O.warn?.("[dsh-feishu] watch batch summary failed:",f.message)});return}if(t==="new"){if(this.#i.has(i)||this.#ne(i)){await c(w("\u5F53\u524D\u4EFB\u52A1\u4ECD\u5728\u8FD0\u884C\uFF0C\u8BF7\u5148\u505C\u6B62\u4EFB\u52A1\u6216\u7B49\u5F85\u4EFB\u52A1\u5B8C\u6210\u540E\u518D\u5F00\u542F\u65B0\u4F1A\u8BDD\u3002"));return}await this.#r.clearSession(i),await c(w("\u5DF2\u5F00\u542F\u5168\u65B0 Harness \u4F1A\u8BDD\u3002")),await this.#ee(i,o,{updateMessageId:e,replyTo:e});return}if(t==="use:current"){let I=this.#r.sessionFor(i);if(typeof I!="string"||!I){await c(w("\u5F53\u524D\u6CA1\u6709\u7ED1\u5B9A\u7684\u4F1A\u8BDD\uFF0C\u8BF7\u5148\u4ECE\u4F1A\u8BDD\u5217\u8868\u9009\u62E9\u3002"));return}await c(w("\u5DF2\u5C31\u7EEA\uFF0C\u76F4\u63A5\u53D1\u6D88\u606F\u5373\u53EF\u7EE7\u7EED\u5F53\u524D\u4F1A\u8BDD\u3002"));return}if(t==="archive_toggle"||t==="archive:on"||t==="archive:off"){let I=t==="archive:on"?!0:t==="archive:off"?!1:!(this.#r?.includesArchivedSessions?.()??!1);await this.#r?.setIncludeArchivedSessions?.(I),await c(I?w("\u5DF2\u5F00\u542F\uFF1A\u4F1A\u8BDD\u5217\u8868\u5305\u542B\u5F52\u6863\u4F1A\u8BDD\u3002"):w("\u5DF2\u5173\u95ED\uFF1A\u4F1A\u8BDD\u5217\u8868\u9690\u85CF\u5F52\u6863\u4F1A\u8BDD\u3002")),await this.#ee(i,o,{updateMessageId:e,replyTo:e});return}if(t==="repair"){await c(w("\u4FEE\u590D\u9700\u5728\u79C1\u804A\u4E2D\u9A8C\u8BC1\u63A5\u5165\u8005\u8EAB\u4EFD\uFF0C\u8BF7\u76F4\u63A5\u53D1\u9001 /repair \u5F00\u59CB\u3002"));return}if(t==="compact"){await this.#oA(i,o,e);return}if(t==="stop"){await this.#nA(i,o,e);return}if(t==="steer"){await this.#sA(i,o,{updateMessageId:e});return}if(t.startsWith("steer:")){let I=t.slice(6);if(I==="custom"){await this.#z(o,TP(),{key:i,updateMessageId:e,replyTo:e});return}await this.#Ce({key:i,chatId:o,messageId:e},I);return}if(t==="presets"){await this.#AA(i,o,{updateMessageId:e});return}if(t==="models"){await this.#tA(i,o,{updateMessageId:e});return}if(t==="status"){await this.#aA(i,o,{updateMessageId:e});return}if(t==="help"){await this.#iA(i,o,{updateMessageId:e});return}if(t==="back_to_menu"){await this.#ee(i,o,{updateMessageId:e,replyTo:e});return}if(t==="preset_default"){await this.#ke(i,o,{updateMessageId:e});return}if(t.startsWith("preset:select:")){let I=t.slice(14);if(I===hS){await this.#ke(i,o,{updateMessageId:e});return}await this.#gA(i,o,I,{updateMessageId:e});return}if(t.startsWith("model:select:")){let I=t.slice(13);await this.#cA(i,o,I,{updateMessageId:e});return}if(t.startsWith("use:")){await this.#$e(i,o,t.slice(4),{updateMessageId:e,replyTo:e});return}if(t.startsWith("workspace:")){await this.#eA(i,o,t.slice(10),{updateMessageId:e,replyTo:e});return}if(t.startsWith("unwatch:")){(await this.#Qe(i,o,t.slice(8),{replyTo:e})).ok&&e&&await this.#de({chatId:o,key:i,replyTo:e},a,A,{updateMessageId:e,limit:r});return}if(t.startsWith("watch:")){let I=t.slice(6);(await this.#fe(i,o,I,{workspaceHint:a,replyTo:e})).ok&&e&&await this.#de({chatId:o,key:i,replyTo:e},a,A,{updateMessageId:e,limit:r})}}#ge(t,o){if(this.#x.size>=bve){let i=this.#x.keys().next().value;i!==void 0&&this.#x.delete(i)}this.#x.delete(t),this.#x.set(t,{...o,expiresAt:Date.now()+Ove})}#Ze(t){let o=this.#x.get(t);return o?o.expiresAtthis.#b(i,s,{replyTo:A});if(t.kind==="menu"){let g=["workspaces","new","sessions","status","repair","help"][o-1];if(!g){await r(w("\u83DC\u5355\u6CA1\u6709\u8FD9\u4E2A\u7F16\u53F7\uFF0C\u56DE\u590D /m \u91CD\u65B0\u6253\u5F00\u3002"));return}if(g==="repair"){await this.#_e(a,"/repair");return}await this.#se(g,{chatId:i,key:e,messageId:A});return}if(t.kind==="sessions"){let s=t.sessions[o-1];if(!s?.sessionId){await r(w("\u672C\u9875\u53EA\u6709 {count} \u4E2A\u4F1A\u8BDD\uFF0C\u56DE\u590D /sessionlist \u91CD\u65B0\u67E5\u770B\u3002",{count:t.sessions.length}));return}await this.#se(`use:${s.sessionId}`,{chatId:i,key:e,messageId:A});return}if(t.kind==="workspaces"){let s=t.paths[o-1];if(!s){await r(w("\u53EA\u6709 {count} \u4E2A\u5DE5\u4F5C\u533A\uFF0C\u56DE\u590D /workspacelist \u91CD\u65B0\u67E5\u770B\u3002",{count:t.paths.length}));return}await this.#se(`workspace:${s}`,{chatId:i,key:e,messageId:A});return}if(t.kind==="watches"){let s=t.entries[o-1];if(!s?.sessionId){await r(w("\u5173\u6CE8\u5217\u8868\u53EA\u6709 {count} \u4E2A\u4F1A\u8BDD\u3002",{count:t.entries.length}));return}await this.#se(`unwatch:${s.sessionId}`,{chatId:i,key:e,messageId:A})}}#ce(t){return this.#r?.includesArchivedSessions?.()===!1?t.filter(o=>o.archived!==!0):t}async#de({chatId:t,key:o,replyTo:i=null},e,a=0,{updateMessageId:A=null,limit:r=null}={}){try{let s=this.#re(),g=await S_(e??"",this.#t,{signal:s});if(g.error){await this.#b(t,g.error,{replyTo:i});return}let c=await this.#t.listWorkspaceSessions(g.workspace,{signal:s}),I=this.#ce(Array.isArray(c?.sessions)?c.sessions:[]),d=Number.isSafeInteger(r)&&r>0?r:null,B=d===null?I:I.slice(0,d),f=c?.workspace??g.workspace;if(B.length===0){await this.#b(t,w(`\u5DE5\u4F5C\u533A\uFF1A{workspace} +{url}`,{url:o}))}}#oe(t,{verificationFocused:o=!1}={}){if(t.state==="succeeded")return w("\u2705 \u4FEE\u590D\u5B8C\u6210\uFF1A\u5DF2\u5B9E\u6D4B\u6536\u5230 card.action.trigger\uFF0C\u83DC\u5355\u6309\u94AE\u73B0\u5728\u53EF\u7528\u3002");if(t.state==="expired"||t.error?.code==="expired_token")return w("\u6388\u6743\u94FE\u63A5\u5DF2\u8FC7\u671F\uFF1B\u5E73\u53F0\u672A\u8FD4\u56DE\u6210\u529F\u7ED3\u679C\uFF0C\u65E0\u6CD5\u786E\u8BA4\u5DF2\u4FEE\u590D\u3002\u53D1\u9001 /repair \u751F\u6210\u65B0\u94FE\u63A5\u3002");if(t.state==="cancelled"||t.error?.code==="abort")return w("\u5DF2\u53D6\u6D88\u672C\u6B21\u4FEE\u590D\u6388\u6743\uFF0C\u672A\u786E\u8BA4\u5B8C\u6210\u4FEE\u590D\u3002");if(t.error?.code==="access_denied")return w("\u4F60\u5DF2\u53D6\u6D88\u6216\u62D2\u7EDD\u6388\u6743\uFF0C\u6CA1\u6709\u786E\u8BA4\u4FEE\u590D\uFF1B\u53D1\u9001 /repair \u53EF\u91CD\u8BD5\u3002");if(t.error?.code==="card_action_probe_timeout"||t.error?.code==="card-action-probe-timeout")return w("\u6388\u6743\u5DF2\u63D0\u4EA4\uFF0C\u4F46\u672A\u6536\u5230\u6D4B\u8BD5\u6309\u94AE\u56DE\u8C03\u3002\u53EF\u80FD\u5C1A\u672A\u70B9\u51FB\u6216\u914D\u7F6E\u4ECD\u5728\u4F20\u64AD\uFF1B\u7A0D\u540E\u53D1\u9001 /repair verify \u67E5\u8BE2\uFF0C\u4E0D\u8981\u76F2\u76EE\u91CD\u590D\u6388\u6743\u3002");if(t.state==="error")return w("\u4FEE\u590D\u6D41\u7A0B\u6682\u65F6\u5931\u8D25\uFF0C\u73B0\u6709\u673A\u5668\u4EBA\u8FDE\u63A5\u4E0D\u53D7\u5F71\u54CD\uFF1B\u53D1\u9001 /repair \u53EF\u91CD\u8BD5\u3002");if(t.state==="saving")return w("\u6388\u6743\u5DF2\u786E\u8BA4\uFF0C\u6B63\u5728\u7B49\u5F85\u4E13\u7528\u6D4B\u8BD5\u6309\u94AE\u7684\u771F\u5B9E\u56DE\u8C03\uFF1B\u56DE\u8C03\u5230\u8FBE\u524D\u4E0D\u4F1A\u5BA3\u544A\u6210\u529F\u3002");if(o)return w("\u6388\u6743\u5C1A\u672A\u5B8C\u6210\uFF0C\u6682\u65F6\u4E0D\u80FD\u9A8C\u8BC1\u5361\u7247\u6309\u94AE\u3002\u8BF7\u5148\u6253\u5F00\u6388\u6743\u94FE\u63A5\u5E76\u786E\u8BA4\u3002");let i=t.remainingSeconds===null?"":w("\uFF0C\u5269\u4F59\u7EA6 {minutes} \u5206\u949F",{minutes:Math.max(1,Math.ceil(t.remainingSeconds/60))});return w("\u4FEE\u590D\u4EFB\u52A1\u6B63\u5728\u7B49\u5F85\u6388\u6743{remaining}\u3002\u53D1\u9001 /repair qr \u53EF\u83B7\u53D6\u4E8C\u7EF4\u7801\uFF0C/repair cancel \u53EF\u53D6\u6D88\u3002",{remaining:i})}onCardAction(t){let o=lA(t?.operator?.open_id)??lA(t?.operator?.user_id)??lA(t?.operator?.operator_id?.open_id)??lA(t?.operator?.operator_id?.user_id)??lA(t?.open_id)??lA(t?.user_id);if(!o)return Promise.resolve();if(!this.#o&&!this.#R.has("*")&&!this.#R.has(o))return this.#O.warn?.("[dsh-feishu] ignoring card action from an unallowed legacy sender"),Promise.resolve();let i=d5(t?.action?.value),e=d5(t?.action?.form_value),a=lA(i.action)??lA(t?.action?.action);if(!a)return Promise.resolve();let A=PC(t?.action?.option)??(a.endsWith("_pick")?Rg(t?.action?.options)[0]:null),r=[...new Set([...Rg(i.options),...Rg(t?.action?.options),...Rg(e[a]),...a==="watch_add"||a==="watch_remove"?Rg(t?.action?.option):[]])],s=a==="workspace_pick"&&typeof A=="string"?`workspace:${A}`:a==="session_pick"&&typeof A=="string"?`use:${A}`:a==="preset_pick"&&typeof A=="string"?`preset:select:${A}`:a==="model_pick"&&typeof A=="string"?`model:select:${A}`:a==="archive_pick"&&typeof A=="string"?`archive:${A}`:a==="steer_pick"&&typeof A=="string"?`steer:${A}`:a,g=lA(t?.context?.open_message_id)??lA(t?.open_message_id)??lA(t?.message_id),c=g?this.#G.get(g):null;if(!c){if(this.#o)return Promise.resolve();let L=lA(t?.context?.open_chat_id)??lA(t?.open_chat_id)??lA(t?.chat_id);return L&&this.#b(L,w("\u8FD9\u4E2A\u83DC\u5355\u5DF2\u8FC7\u671F\uFF0C\u8BF7\u56DE\u590D /m \u91CD\u65B0\u6253\u5F00\u3002"),{replyTo:g}).catch(()=>{}),Promise.resolve()}let I=c.key.startsWith("p2p:")?"direct":c.key.startsWith("group:")?"group":null,d=s.startsWith("approve:")||s.startsWith("reject:")||s.startsWith("answer:"),B=Oa(this.#o,{conversationType:I,senderIds:o,isCommand:!d});if(!B.allowed)return B.reason==="command-not-allowed"?this.#b(c.chatId,w(so)).catch(()=>{}):(this.#O.warn?.("[dsh-feishu] ignoring card action blocked by access policy"),Promise.resolve());this.#G.delete(g),this.#G.set(g,c);let f={...c,messageId:g,selections:r,operatorOpenId:o},Q=lA(i.source),y=lA(e.steer_text),m=lA(t?.event_id)??lA(t?.header?.event_id)??lA(t?.uuid)??lA(t?.header?.uuid),T=JSON.stringify({messageId:g,resolvedAction:s,option:A,multiValues:r,source:Q,formText:y}),_=s==="stop",R=s.startsWith("steer:")?s.slice(6):null,F=a==="steer"&&Q==="quick"&&A!==null&&A!==NC||a==="steer"&&Q==="form"&&y!==null||R!==null&&R!==""&&!(R==="custom"||R===NC);return this.#We(f,T,async()=>{if(a==="steer"){if(Q==="quick"&&A){if(A===NC){await this.#z(f.chatId,TP(),{key:f.key,updateMessageId:f.messageId});return}await this.#Ce(f,A);return}if(Q==="form"){if(y){await this.#Ce(f,y);return}await this.#b(f.chatId,w("\u8BF7\u8F93\u5165\u8865\u5145\u6307\u4EE4\u540E\u518D\u63D0\u4EA4\u3002"),{replyTo:f.messageId??null});return}}await this.#se(s,{...f,actor:f.operatorOpenId})},{lane:_||F?"control":"regular",coalesceStop:_,eventId:m,operatorOpenId:o})}#Te(t=Date.now()){for(let[o,i]of this.#y)i<=t&&this.#y.delete(o);for(;this.#y.size>l5;){let o=this.#y.keys().next().value;if(o===void 0)break;this.#y.delete(o)}}#Ne(t,o=Date.now()){t&&(this.#y.delete(t),this.#y.set(t,o+Tve),this.#Te(o))}#Ve(t){let o=t.key,i=Date.now(),e=this.#m.get(o);if(e?.task||e&&i-e.at{}).finally(()=>{this.#p.delete(a);let A=this.#m.get(o);A?.task===a&&this.#m.set(o,{at:A.at,task:null})}),this.#m.delete(o),this.#m.set(o,{at:i,task:a}),this.#p.add(a);this.#m.size>200;){let A=this.#m.keys().next().value;if(A===void 0)break;this.#m.delete(A)}return a}#We(t,o,i,{lane:e="regular",coalesceStop:a=!1,eventId:A=null,operatorOpenId:r=null}={}){let s=t.key,g=A?`${s}\0${r??""}\0event:${A}`:null,c=g??`${s}\0${r??""}\0action:${o}`,I=Date.now();if(this.#Te(I),g&&(this.#y.get(g)??0)>I)return Promise.resolve();let d=this.#f.get(c);if(d)return d;if(a){let F=this.#Q.get(s);if(F){if(g)for(this.#S.delete(g),this.#S.set(g,F);this.#S.size>l5;){let L=this.#S.keys().next().value;if(L===void 0)break;this.#S.delete(L)}return F}}let B=e==="control",f=B?this.#u:this.#h,Q=B?this.#B:this.#C,y=B?_ve:Mve,m=Q.get(s)??0,T=a?y+1:y;if(m>=T)return this.#Ve(t);let _=f.get(s)??Promise.resolve();Q.set(s,m+1);let R,x=!1;return R=_.catch(()=>{}).then(async()=>{this.#w?.throwIfAborted(),x=!0,await i()}).catch(async F=>{this.#w?.aborted||await this.#Y(t.chatId,F,{logLabel:"card action",replyTo:t.messageId})}).finally(()=>{if(this.#f.get(c)===R&&this.#f.delete(c),x&&g&&this.#Ne(g),a&&this.#Q.get(s)===R&&this.#Q.delete(s),a){let L=Date.now();for(let[q,z]of this.#S)z===R&&(this.#S.delete(q),this.#Ne(q,L))}f.get(s)===R&&f.delete(s);let F=(Q.get(s)??1)-1;F>0?Q.set(s,F):Q.delete(s),this.#p.delete(R)}),this.#f.set(c,R),a&&this.#Q.set(s,R),this.#p.add(R),f.set(s,R),R}async#se(t,{chatId:o,key:i,messageId:e=null,sessionWorkspace:a=null,sessionPage:A=0,sessionLimit:r=null,selections:s=[],actor:g=null}){let c=I=>this.#b(o,I,{replyTo:e});if(t.startsWith("approve:")||t.startsWith("reject:")){let I=t.indexOf(":"),d=t.slice(I+1),B=t.startsWith("approve:")?"allowed-once":"rejected";await this.#D.submitByApprovalId(d,B,{actor:g})||await c(w("\u8BE5\u5BA1\u6279\u5DF2\u5904\u7406\u6216\u4E0D\u5B58\u5728\uFF0C\u65E0\u9700\u91CD\u590D\u64CD\u4F5C\u3002")).catch(()=>{});return}if(t.startsWith("answer:")){let I=t.slice(7),d=I.indexOf(":");if(d!==-1){let B=I.slice(0,d),f=I.slice(d+1),Q=f.indexOf(":"),y=Q===-1?f:f.slice(0,Q),m=Q===-1?"":f.slice(Q+1),T=this.#s.get(B),_=T?this.#n.get(T):null;_&&_.kind==="question"&&!_.submitting&&_.actor===g&&Number(y)===_.index?await this.#Ue(_,m,{chatId:o}):await c(RC()).catch(()=>{})}return}if(t==="sessions"||/^sessions:\d+$/.test(t)){let I=t==="sessions"?0:Number(t.slice(9));await this.#de({chatId:o,key:i,replyTo:e},a,I,{updateMessageId:e,limit:r});return}if(t==="workspaces"){await this.#Re({chatId:o,key:i,replyTo:e},{updateMessageId:e});return}if(t==="watchlist"){await this.#ye(i,o,{updateMessageId:e,replyTo:e});return}if(t==="watch_add"||t==="watch_remove"){if(s.length===0){await c(w("\u8BF7\u5148\u9009\u62E9\u81F3\u5C11\u4E00\u4E2A\u4F1A\u8BDD\u3002"));return}let I=0,d=0;if(t==="watch_add"){let f=new Map;try{f=await this.#dA(a)}catch(Q){this.#O.warn?.("[dsh-feishu] batch watch validation failed:",Q.message)}for(let Q of s){let y=f.get(Q);if(!y){d+=1;continue}let m=await this.#fe(i,o,Q,{notify:!1,validatedTarget:y,replyTo:e});m.changed?I+=1:m.ok||(d+=1)}}else for(let f of s){let Q=await this.#Qe(i,o,f,{notify:!1});Q.changed?I+=1:Q.ok||(d+=1)}let B=I>0&&d>0?t==="watch_add"?w("\u5DF2\u6279\u91CF\u5173\u6CE8 {count} \u4E2A\u4F1A\u8BDD\uFF0C\u53E6\u6709 {failed} \u4E2A\u672A\u6210\u529F\u3002",{count:I,failed:d}):w("\u5DF2\u53D6\u6D88\u5173\u6CE8 {count} \u4E2A\u4F1A\u8BDD\uFF0C\u53E6\u6709 {failed} \u4E2A\u672A\u6210\u529F\u3002",{count:I,failed:d}):I>0?t==="watch_add"?w("\u5DF2\u6279\u91CF\u5173\u6CE8 {count} \u4E2A\u4F1A\u8BDD\u3002",{count:I}):w("\u5DF2\u53D6\u6D88\u5173\u6CE8 {count} \u4E2A\u4F1A\u8BDD\u3002",{count:I}):d>0?w("\u6240\u9009\u4F1A\u8BDD\u5747\u672A\u5904\u7406\u6210\u529F\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5\u3002"):t==="watch_add"?w("\u6240\u9009\u4F1A\u8BDD\u5DF2\u5728\u5173\u6CE8\u5217\u8868\u4E2D\u3002"):w("\u6240\u9009\u4F1A\u8BDD\u5DF2\u4E0D\u5728\u5173\u6CE8\u5217\u8868\u4E2D\u3002");try{await this.#ye(i,o,{updateMessageId:e,replyTo:e})}catch(f){this.#O.warn?.("[dsh-feishu] watch list refresh failed:",f.message)}await c(B).catch(f=>{this.#O.warn?.("[dsh-feishu] watch batch summary failed:",f.message)});return}if(t==="new"){if(this.#i.has(i)||this.#ne(i)){await c(w("\u5F53\u524D\u4EFB\u52A1\u4ECD\u5728\u8FD0\u884C\uFF0C\u8BF7\u5148\u505C\u6B62\u4EFB\u52A1\u6216\u7B49\u5F85\u4EFB\u52A1\u5B8C\u6210\u540E\u518D\u5F00\u542F\u65B0\u4F1A\u8BDD\u3002"));return}await this.#r.clearSession(i),await c(w("\u5DF2\u5F00\u542F\u5168\u65B0 Harness \u4F1A\u8BDD\u3002")),await this.#ee(i,o,{updateMessageId:e,replyTo:e});return}if(t==="use:current"){let I=this.#r.sessionFor(i);if(typeof I!="string"||!I){await c(w("\u5F53\u524D\u6CA1\u6709\u7ED1\u5B9A\u7684\u4F1A\u8BDD\uFF0C\u8BF7\u5148\u4ECE\u4F1A\u8BDD\u5217\u8868\u9009\u62E9\u3002"));return}await c(w("\u5DF2\u5C31\u7EEA\uFF0C\u76F4\u63A5\u53D1\u6D88\u606F\u5373\u53EF\u7EE7\u7EED\u5F53\u524D\u4F1A\u8BDD\u3002"));return}if(t==="archive_toggle"||t==="archive:on"||t==="archive:off"){let I=t==="archive:on"?!0:t==="archive:off"?!1:!(this.#r?.includesArchivedSessions?.()??!1);await this.#r?.setIncludeArchivedSessions?.(I),await c(I?w("\u5DF2\u5F00\u542F\uFF1A\u4F1A\u8BDD\u5217\u8868\u5305\u542B\u5F52\u6863\u4F1A\u8BDD\u3002"):w("\u5DF2\u5173\u95ED\uFF1A\u4F1A\u8BDD\u5217\u8868\u9690\u85CF\u5F52\u6863\u4F1A\u8BDD\u3002")),await this.#ee(i,o,{updateMessageId:e,replyTo:e});return}if(t==="repair"){await c(w("\u4FEE\u590D\u9700\u5728\u79C1\u804A\u4E2D\u9A8C\u8BC1\u63A5\u5165\u8005\u8EAB\u4EFD\uFF0C\u8BF7\u76F4\u63A5\u53D1\u9001 /repair \u5F00\u59CB\u3002"));return}if(t==="compact"){await this.#oA(i,o,e);return}if(t==="stop"){await this.#nA(i,o,e);return}if(t==="steer"){await this.#sA(i,o,{updateMessageId:e});return}if(t.startsWith("steer:")){let I=t.slice(6);if(I==="custom"){await this.#z(o,TP(),{key:i,updateMessageId:e,replyTo:e});return}await this.#Ce({key:i,chatId:o,messageId:e},I);return}if(t==="presets"){await this.#AA(i,o,{updateMessageId:e});return}if(t==="models"){await this.#tA(i,o,{updateMessageId:e});return}if(t==="status"){await this.#aA(i,o,{updateMessageId:e});return}if(t==="help"){await this.#iA(i,o,{updateMessageId:e});return}if(t==="back_to_menu"){await this.#ee(i,o,{updateMessageId:e,replyTo:e});return}if(t==="preset_default"){await this.#ke(i,o,{updateMessageId:e});return}if(t.startsWith("preset:select:")){let I=t.slice(14);if(I===hS){await this.#ke(i,o,{updateMessageId:e});return}await this.#gA(i,o,I,{updateMessageId:e});return}if(t.startsWith("model:select:")){let I=t.slice(13);await this.#cA(i,o,I,{updateMessageId:e});return}if(t.startsWith("use:")){await this.#$e(i,o,t.slice(4),{updateMessageId:e,replyTo:e});return}if(t.startsWith("workspace:")){await this.#eA(i,o,t.slice(10),{updateMessageId:e,replyTo:e});return}if(t.startsWith("unwatch:")){(await this.#Qe(i,o,t.slice(8),{replyTo:e})).ok&&e&&await this.#de({chatId:o,key:i,replyTo:e},a,A,{updateMessageId:e,limit:r});return}if(t.startsWith("watch:")){let I=t.slice(6);(await this.#fe(i,o,I,{workspaceHint:a,replyTo:e})).ok&&e&&await this.#de({chatId:o,key:i,replyTo:e},a,A,{updateMessageId:e,limit:r})}}#ge(t,o){if(this.#x.size>=bve){let i=this.#x.keys().next().value;i!==void 0&&this.#x.delete(i)}this.#x.delete(t),this.#x.set(t,{...o,expiresAt:Date.now()+Ove})}#Ze(t){let o=this.#x.get(t);return o?o.expiresAtthis.#b(i,s,{replyTo:A});if(t.kind==="menu"){let g=["workspaces","new","sessions","status","repair","help"][o-1];if(!g){await r(w("\u83DC\u5355\u6CA1\u6709\u8FD9\u4E2A\u7F16\u53F7\uFF0C\u56DE\u590D /m \u91CD\u65B0\u6253\u5F00\u3002"));return}if(g==="repair"){await this.#_e(a,"/repair");return}await this.#se(g,{chatId:i,key:e,messageId:A});return}if(t.kind==="sessions"){let s=t.sessions[o-1];if(!s?.sessionId){await r(w("\u672C\u9875\u53EA\u6709 {count} \u4E2A\u4F1A\u8BDD\uFF0C\u56DE\u590D /sessionlist \u91CD\u65B0\u67E5\u770B\u3002",{count:t.sessions.length}));return}await this.#se(`use:${s.sessionId}`,{chatId:i,key:e,messageId:A});return}if(t.kind==="workspaces"){let s=t.paths[o-1];if(!s){await r(w("\u53EA\u6709 {count} \u4E2A\u5DE5\u4F5C\u533A\uFF0C\u56DE\u590D /workspacelist \u91CD\u65B0\u67E5\u770B\u3002",{count:t.paths.length}));return}await this.#se(`workspace:${s}`,{chatId:i,key:e,messageId:A});return}if(t.kind==="watches"){let s=t.entries[o-1];if(!s?.sessionId){await r(w("\u5173\u6CE8\u5217\u8868\u53EA\u6709 {count} \u4E2A\u4F1A\u8BDD\u3002",{count:t.entries.length}));return}await this.#se(`unwatch:${s.sessionId}`,{chatId:i,key:e,messageId:A})}}#ce(t){return this.#r?.includesArchivedSessions?.()===!1?t.filter(o=>o.archived!==!0):t}async#de({chatId:t,key:o,replyTo:i=null},e,a=0,{updateMessageId:A=null,limit:r=null}={}){try{let s=this.#re(),g=await S_(e??"",this.#t,{signal:s});if(g.error){await this.#b(t,g.error,{replyTo:i});return}let c=await this.#t.listWorkspaceSessions(g.workspace,{signal:s}),I=this.#ce(Array.isArray(c?.sessions)?c.sessions:[]),d=Number.isSafeInteger(r)&&r>0?r:null,B=d===null?I:I.slice(0,d),f=c?.workspace??g.workspace;if(B.length===0){await this.#b(t,w(`\u5DE5\u4F5C\u533A\uFF1A{workspace} \u8BE5\u5DE5\u4F5C\u533A\u6682\u65E0\u4F1A\u8BDD\u3002`,{workspace:f}),{replyTo:i});return}let Q=Math.ceil(B.length/vc),y=Number.isSafeInteger(a)&&a>0?Math.min(a,Q-1):0,m=new Set((this.#r.watchEntries?.(o)??[]).map(_=>_.sessionId)),T=B.slice(y*vc,(y+1)*vc);this.#ge(o,{kind:"sessions",sessions:T.map(_=>({..._,watched:m.has(_.sessionId)}))}),await this.#z(t,r5(f,B,y,B.length,m),{key:o,updateMessageId:A,replyTo:i,sessionWorkspace:g.workspace,sessionPage:y,sessionLimit:d})}catch(s){await this.#Y(t,s,{logLabel:"session list",replyTo:i})}}async#Re({chatId:t,key:o,replyTo:i=null},{updateMessageId:e=null}={}){try{let{current:a,paths:A}=await Nd(this.#t,{signal:this.#re()});this.#ge(o,{kind:"workspaces",paths:A}),await this.#z(t,a5(A,a),{key:o,updateMessageId:e,replyTo:i})}catch(a){await this.#Y(t,a,{logLabel:"workspace list",replyTo:i})}}async#$e(t,o,i,{updateMessageId:e=null,replyTo:a=null}={}){try{let A=await this.#t.bindWorkspaceSession(t,i),r=String(A?.title??"").replace(/\s+/gu," ").trim()||w("\u6682\u65E0\u6807\u9898");await this.#b(o,[w(`\u5DF2\u7ED1\u5B9A\u4F1A\u8BDD\u300C{title}\u300D ID\uFF1A{id}`,{title:r,id:A?.sessionId??i}),w("\u53D1\u9001 /history \u67E5\u770B\u6700\u8FD1\u5BF9\u8BDD\u3002")].join(` `),{replyTo:a}),await this.#ee(t,o,{updateMessageId:e,replyTo:a})}catch(A){await this.#Y(o,A,{logLabel:"session binding",replyTo:a,userMessage:w("\u7ED1\u5B9A\u5931\u8D25\uFF1A{message}",{message:kC(A)})})}}async#eA(t,o,i,{updateMessageId:e=null,replyTo:a=null}={}){try{let A=await this.#t.switchWorkspace(i);await this.#b(o,w("\u5DE5\u4F5C\u533A\u5DF2\u5207\u6362\u4E3A\uFF1A{workspace}",{workspace:A}),{replyTo:a}),await this.#ee(t,o,{updateMessageId:e,replyTo:a})}catch(A){await this.#Y(o,A,{logLabel:"workspace switch",replyTo:a,userMessage:w("\u5207\u6362\u5931\u8D25\uFF1A{message}",{message:kC(A)})})}}#ue(t,o,i){if(!(!i.key||!t)&&(this.#G.delete(t),this.#G.set(t,{key:i.key,chatId:o,sessionWorkspace:typeof i.sessionWorkspace=="string"&&i.sessionWorkspace?i.sessionWorkspace:null,sessionPage:Number.isSafeInteger(i.sessionPage)&&i.sessionPage>=0?i.sessionPage:0,sessionLimit:Number.isSafeInteger(i.sessionLimit)&&i.sessionLimit>0?i.sessionLimit:null}),this.#G.size>200)){let e=this.#G.keys().next().value;e!==void 0&&this.#G.delete(e)}}async#z(t,o,i={}){let e=lA(i.updateMessageId),a=lA(i.replyTo);if(e)try{let g=await this.#e.im.v1.message.patch({path:{message_id:e},data:{content:o}});if(g?.code&&g.code!==0)throw new Error(`Feishu card update failed: ${g.msg||g.code}`);return this.#ue(e,t,i),e}catch(g){this.#O.warn?.("[dsh-feishu] card update failed:",g?.code??g?.message??g,"sending new")}let A=o;if(a)try{let g=await this.#e.im.v1.message.reply({path:{message_id:a},data:{msg_type:"interactive",content:A}});if(g?.code&&g.code!==0)throw new Error(`Feishu card reply failed: ${g.msg||g.code}`);let c=lA(g?.data?.message_id);if(c)return this.#ue(c,t,i),c}catch(g){this.#O.warn?.("[dsh-feishu] threaded card reply failed; sending a plain card:",g?.message??String(g))}let r=await this.#e.im.v1.message.create({params:{receive_id_type:"chat_id"},data:{receive_id:t,msg_type:"interactive",content:o}});if(r?.code&&r.code!==0)throw new Error(`Feishu card send failed: ${r.msg||r.code}`);let s=lA(r?.data?.message_id);return this.#ue(s,t,i),s}async#ee(t,o,{updateMessageId:i=null,replyTo:e=null}={}){let a=null,A=null;try{let F=this.#r.sessionFor(t);if(typeof F=="string"&&F){a=F;let L=this.#t.workspaceSession?.(F);A=lA(L?.title)??lA(L?.name)??lA(L?.displayName)}}catch{}let r=this.#re(),s=Nd(this.#t,{signal:r}).catch(()=>{let F=typeof this.#t.currentWorkspace=="function"?this.#t.currentWorkspace():null;return{current:F,paths:F?[F]:[]}}),g=(async()=>{let F=typeof this.#t.currentWorkspace=="function"?this.#t.currentWorkspace():null;if(!F||typeof this.#t.listWorkspaceSessions!="function")return[];try{let L=await this.#t.listWorkspaceSessions(F,{signal:r});return this.#ce(Array.isArray(L?.sessions)?L.sessions:[])}catch{return[]}})(),c=(async()=>{try{let F=await this.#t.agentPresetSettings({signal:r});return{...F.agentPresetCatalog,_currentId:F.agentPreset}}catch{return null}})(),I=(async()=>{try{if(a){let F=this.#t.workspaceSession?.(a);if(typeof F?.models=="function")return await F.models({signal:r})}return await this.#t.listModels({signal:r})}catch{return null}})(),[d,B,f,Q]=await Promise.all([s,g,c,I]),y=Array.isArray(d.paths)?d.paths:[],m=d.current??null,T=B.find(F=>F.sessionId===a),_=a?lA(T?.title)??lA(T?.name)??A??a:null,R=B.map(F=>({id:F.sessionId,title:F.title??F.name??F.sessionId})).slice(0,20);a&&(R=R.filter(F=>F.id!==a),R.unshift({id:a,title:_??a}),R=R.slice(0,20));let x=this.#r?.includesArchivedSessions?.()??!1;this.#ge(t,{kind:"menu",chatId:o}),await this.#z(o,W6({workspaces:y,currentWorkspace:m,currentSession:a?{id:a,title:_}:null,sessions:R,archiveVisible:x,presetCatalog:f,modelCatalog:Q}),{key:t,updateMessageId:i,replyTo:e})}async#yA(t,o){try{if(typeof this.#t.workspaceSession=="function"){let e=this.#t.workspaceSession(o);if(e&&typeof e=="object"){let a=lA(e.title)??lA(e.name)??lA(e.displayName);if(a)return a}}let i=typeof this.#t.currentWorkspace=="function"?this.#t.currentWorkspace():null;if(i&&typeof this.#t.listWorkspaceSessions=="function"){let e=await this.#t.listWorkspaceSessions(i),a=(Array.isArray(e?.sessions)?e.sessions:[]).find(A=>A.sessionId===o);if(a){let A=lA(a.title)??lA(a.name);if(A)return A}}}catch{}return null}async#AA(t,o,{updateMessageId:i=null}={}){try{let e=await this.#t.agentPresetSettings({signal:this.#re()}),a=e.agentPresetCatalog;a._currentId=e.agentPreset,await this.#z(o,Z6(a),{key:t,updateMessageId:i})}catch(e){await this.#Y(o,e,{logLabel:"preset card",replyTo:i})}}async#tA(t,o,{updateMessageId:i=null}={}){try{let e=this.#re();await this.#t.ensureRunning({signal:e});let a=this.#r?.sessionFor?.(t),A;if(typeof a=="string"&&a){let r=this.#t.workspaceSession(a);r?.models&&(A=await r.models({signal:e}))}A||(A=await this.#t.listModels({signal:e})),await this.#z(o,X6(A),{key:t,updateMessageId:i})}catch(e){await this.#Y(o,e,{logLabel:"model card",replyTo:i})}}async#rA(t,o,i=null){try{await this.#t.ensureRunning({signal:this.#w});let e=[w("\u8FDE\u63A5\u6B63\u5E38")],a=typeof this.#t.currentWorkspace=="function"?this.#t.currentWorkspace():null;a&&e.push(w("\u5DE5\u4F5C\u533A\uFF1A{workspace}",{workspace:a}));let A=typeof this.#t.agentPresetSettings=="function"?await this.#t.agentPresetSettings({signal:this.#w}).catch(()=>null):null;if(A){let r=A.agentPresetCatalog?.items?.find(s=>s.id===A.agentPreset);e.push(w("\u9884\u8BBE\uFF1A{preset}",{preset:r?`${r.label}\uFF08${r.id}\uFF09`:A.agentPreset||w("\u8DDF\u968F\u9ED8\u8BA4")}))}await this.#b(o,e.join(` -`),{replyTo:i})}catch(e){await this.#Y(o,e,{logLabel:"status text",replyTo:i})}}async#aA(t,o,{updateMessageId:i=null}={}){try{let e=this.#re();await this.#t.ensureRunning({signal:e});let a={connected:!0,workspace:null,preset:null,model:null,sessionCount:0};try{let A=typeof this.#t.currentWorkspace=="function"?this.#t.currentWorkspace():null;a.workspace=A||w("\u672A\u77E5")}catch{}try{let A=await this.#t.agentPresetSettings({signal:e}),r=A.agentPresetCatalog.items.find(s=>s.id===A.agentPreset);a.preset=r?`${r.label}\uFF08${r.id}\uFF09`:A.agentPreset||w("\u8DDF\u968F\u9ED8\u8BA4")}catch{}try{let A=this.#r?.sessionFor?.(t);if(typeof A=="string"&&A){let r=this.#t.workspaceSession(A);if(r?.models){let s=await r.models({signal:e});s.current&&(a.model=`${s.current.provider}/${s.current.model}`)}}}catch{}try{let A=typeof this.#t.currentWorkspace=="function"?this.#t.currentWorkspace():null;if(A){let r=await this.#t.listWorkspaceSessions(A,{signal:e});Array.isArray(r?.sessions)&&(a.sessionCount=r.sessions.length)}}catch{}await this.#z(o,$6(a),{key:t,updateMessageId:i})}catch(e){await this.#Y(o,e,{logLabel:"status card",replyTo:i})}}async#iA(t,o,{updateMessageId:i=null}={}){await this.#z(o,A5(jve.map(e=>w(e))),{key:t,updateMessageId:i})}async#oA(t,o,i=null){try{let e=await Ti("/compact",this.#t,this.#r,t,{signal:this.#w});await this.#b(o,e?.message||w("\u4E0A\u4E0B\u6587\u538B\u7F29\u5931\u8D25\u3002"),{replyTo:i})}catch(e){await this.#Y(o,e,{logLabel:"compact",replyTo:i})}}async#nA(t,o,i=null){try{let e=await Ni("/stop",this.#t,this.#r,t,{signal:this.#w,control:{owner:this,key:t}});e?.stopped&&await Promise.allSettled([this.#be(t),this.#D.closeRoute(t)]),await this.#b(o,e?.message||w("/stop \u6267\u884C\u5B8C\u6210\u3002"),{replyTo:i})}catch(e){await this.#Y(o,e,{logLabel:"stop",replyTo:i})}}async#sA(t,o,{updateMessageId:i=null}={}){let e=!!this.#r.sessionFor?.(t);this.#ge(t,{kind:"steer"}),await this.#z(o,s5({hasSession:e}),{key:t,updateMessageId:i})}async#Ce(t,o){let{key:i,chatId:e}=t,a=await Ni(`/steer ${o}`,this.#t,this.#r,i,{signal:this.#w,pendingInteraction:this.#ne(i),control:{owner:this,key:i}});await this.#b(e,a?.message||w("\u5DF2\u63D0\u4EA4\u8865\u5145\u6307\u4EE4\u3002"),{replyTo:t.messageId??null})}async#ke(t,o,{updateMessageId:i=null,replyTo:e=null}={}){try{let a=await Ri("/preset --default",this.#t,this.#r,t,{signal:this.#w});for(let A of a?.messages??[a?.message])A&&await this.#b(o,A,{replyTo:e})}catch(a){await this.#Y(o,a,{logLabel:"preset reset",replyTo:i});return}try{await this.#ee(t,o,{updateMessageId:i,replyTo:e})}catch(a){this.#O.warn?.("[dsh-feishu] menu refresh failed after preset reset:",a.message)}}async#gA(t,o,i,{updateMessageId:e=null,replyTo:a=null}={}){try{let A=/^\d+$/u.test(i)?`id:${i}`:i,r=await Ri(`/preset ${A}`,this.#t,this.#r,t,{signal:this.#w});for(let s of r?.messages??[r?.message])s&&await this.#b(o,s,{replyTo:a})}catch(A){await this.#Y(o,A,{logLabel:"preset selection",replyTo:e});return}try{await this.#ee(t,o,{updateMessageId:e,replyTo:a})}catch(A){this.#O.warn?.("[dsh-feishu] menu refresh failed after preset select:",A.message)}}async#cA(t,o,i,{updateMessageId:e=null,replyTo:a=null}={}){try{let A=await oo(`/model ${i}`,this.#t,this.#r,t,{signal:this.#w,pendingInteraction:this.#ne(t),control:{owner:this,key:t}});for(let r of A?.messages??[A?.message])r&&await this.#b(o,r,{replyTo:a})}catch(A){await this.#Y(o,A,{logLabel:"model selection",replyTo:e});return}try{await this.#ee(t,o,{updateMessageId:e,replyTo:a})}catch(A){this.#O.warn?.("[dsh-feishu] menu refresh failed after model select:",A.message)}}#ve(){if(this.#L||typeof this.#t?.watchHarnessEvents!="function"||this.#w?.aborted)return;let t=this.#w??new AbortController().signal;try{this.#L=this.#t.watchHarnessEvents({signal:t,onSessionEvent:o=>this.#pA(o),onReconnect:()=>{this.#hA()}}),Promise.resolve(this.#L).catch(o=>{t.aborted||this.#O.warn?.("[dsh-feishu] event watcher stopped:",o.message)})}catch(o){this.#L=null,this.#O.warn?.("[dsh-feishu] event watcher failed to start:",o.message)}}#Pe(t,o){let i=this.#V.get(t)??Promise.resolve(),e;return e=i.then(o,o).catch(a=>{this.#w?.aborted||this.#O.warn?.("[dsh-feishu] completion event failed:",a.message)}).finally(()=>{this.#V.get(t)===e&&this.#V.delete(t)}),this.#V.set(t,e),e}#Be(t=Date.now()){let o=0;for(let[i,e]of this.#v){for(let[a,A]of e)(!Number.isSafeInteger(A?.arrivalAt)||A.arrivalAt<0||A.arrivalAt+Nve<=t)&&e.delete(a);for(;e.size>kve;){let a=e.keys().next().value;if(a===void 0)break;e.delete(a)}e.size===0?this.#v.delete(i):o+=e.size}for(;this.#v.size>Rve;){let i=this.#v.keys().next().value;if(i===void 0)break;o-=this.#v.get(i)?.size??0,this.#v.delete(i)}for(;o>vve;){let i=this.#v.keys().next().value;if(i===void 0)break;let e=this.#v.get(i),a=e?.keys().next().value;if(a===void 0){this.#v.delete(i);continue}e.delete(a),o-=1,e.size===0&&this.#v.delete(i)}}#IA(t,o,i=Date.now()){this.#Be(i);let e=this.#v.get(t);e||(e=new Map);let a=o?.data?.reason,A=typeof a=="string"?a:typeof a?.kind=="string"?{kind:a.kind}:null;e.delete(o.seq),e.set(o.seq,{arrivalAt:i,event:{type:"turn/end",seq:o.seq,...Number.isSafeInteger(o.time)&&o.time>=0?{time:o.time}:{},data:{reason:A}}}),this.#v.delete(t),this.#v.set(t,e),this.#Be(i)}async#lA(t,{workspaceHint:o=null,signal:i=this.#w}={}){if(typeof t!="string"||t==="")return{error:w("\u7528\u6CD5\uFF1A/watch ")};let e=/^\d{1,4}$/.test(t)?Number(t):null,a=typeof this.#t?.currentWorkspace=="function"?this.#t.currentWorkspace():null,A=async s=>{let g=await this.#t.listWorkspaceSessions(s,{signal:i});return Array.isArray(g?.sessions)?g.sessions:[]};if(e!==null){if(!a)return{error:w("\u5F53\u524D\u673A\u5668\u4EBA\u6CA1\u6709\u53EF\u7528\u7684\u5DE5\u4F5C\u533A\uFF0C\u65E0\u6CD5\u6309\u5E8F\u53F7\u89E3\u6790\u4F1A\u8BDD\u3002")};let s=this.#ce(await A(a)),g=s[e-1];return g?.sessionId?{sessionId:g.sessionId,title:g.title??w("\u6682\u65E0\u6807\u9898"),workspace:a,...Nn(g.lastSeq)?{lastSeq:g.lastSeq}:{}}:{error:w("\u5F53\u524D\u5DE5\u4F5C\u533A\u53EA\u6709 {count} \u4E2A\u4F1A\u8BDD\u3002",{count:s.length})}}let r;if(lA(o))r=[o];else{let s=typeof this.#t?.listWorkspaces=="function"?(await this.#t.listWorkspaces({signal:i})).filter(g=>g!==a):[];r=[a,...s].filter(Boolean)}for(let s of r){let c=(await A(s)).find(I=>I.sessionId===t);if(c)return{sessionId:t,title:c.title??w("\u6682\u65E0\u6807\u9898"),workspace:s,...Nn(c.lastSeq)?{lastSeq:c.lastSeq}:{}}}return{error:w("\u6CA1\u6709\u627E\u5230\u8FD9\u4E2A\u4F1A\u8BDD\uFF0C\u8BF7\u7528 /sessionlist \u67E5\u770B\u53EF\u7528\u4F1A\u8BDD\u3002")}}async#dA(t){let o=lA(t)??(typeof this.#t?.currentWorkspace=="function"?lA(this.#t.currentWorkspace()):null);if(!o||typeof this.#t?.listWorkspaceSessions!="function")return new Map;let i=await this.#t.listWorkspaceSessions(o,{signal:this.#re()});return new Map(this.#ce(Array.isArray(i?.sessions)?i.sessions:[]).filter(e=>lA(e?.sessionId)).map(e=>[e.sessionId,{sessionId:e.sessionId,title:e.title??e.name??w("\u6682\u65E0\u6807\u9898"),workspace:o,...Nn(e.lastSeq)?{lastSeq:e.lastSeq}:{}}]))}#Ge(t){let o=this.#q.get(t);if(o)return o.requested=!0,o.promise;let i={requested:!0,promise:null};return this.#q.set(t,i),i.promise=Promise.resolve().then(async()=>{try{do i.requested=!1,await this.#Pe(t,()=>this.#Le(t));while(i.requested&&!this.#w?.aborted)}finally{this.#q.get(t)===i&&this.#q.delete(t)}}),i.promise}async#fe(t,o,i,{notify:e=!0,validatedTarget:a=null,workspaceHint:A=null,replyTo:r=null}={}){let s=Date.now(),g=async y=>{e&&await this.#b(o,y,{replyTo:r}).catch(m=>{this.#O.warn?.("[dsh-feishu] watch notification failed:",m.message)})};if(this.#ve(),typeof this.#r?.setWatch!="function")return await g(w("\u5F53\u524D\u72B6\u6001\u5B58\u50A8\u4E0D\u652F\u6301\u5173\u6CE8\u3002")),{ok:!1,changed:!1,reason:"unsupported"};let c;try{c=a?.sessionId===i?a:await this.#lA(i,{workspaceHint:A,signal:A?this.#re():this.#w})}catch(y){return await g(w("\u65E0\u6CD5\u89E3\u6790\u4F1A\u8BDD\uFF1A{message}",{message:kC(y)})),{ok:!1,changed:!1,reason:"resolve"}}if(c.error)return await g(c.error),{ok:!1,changed:!1,reason:"not-found"};let I=this.#r.watchEntries?.(t)??[],d=I.find(y=>y.sessionId===c.sessionId);if(!d&&I.length>=BS)return await g(w("\u6BCF\u4E2A\u804A\u5929\u6700\u591A\u5173\u6CE8 {count} \u4E2A\u4F1A\u8BDD\u3002",{count:BS})),{ok:!1,changed:!1,reason:"limit"};let B=Nn(d?.lastSeq)?d.lastSeq:Nn(c.lastSeq)?c.lastSeq:null,Q=SS(d)??(d?null:s);try{await this.#r.setWatch(t,{sessionId:c.sessionId,title:c.title,chatId:o,lastSeq:B,...Q!==null?{watchStartedAt:Q}:{},...r?{replyToMessageId:r}:d?.replyToMessageId?{replyToMessageId:d.replyToMessageId}:{}})}catch(y){return await g(w("\u5173\u6CE8\u5931\u8D25\uFF1A{message}",{message:kC(y)})),{ok:!1,changed:!1,reason:"persist"}}return(!d||Q!==null||!Nn(B))&&this.#Ge(c.sessionId),await g(w("\u5DF2\u5173\u6CE8\u4F1A\u8BDD\u300C{title}\u300D\uFF0C\u4EFB\u52A1\u5B8C\u6210\u4F1A\u63A8\u9001\u7ED3\u679C\u3002",{title:String(c.title).replace(/\s+/gu," ")})),{ok:!0,changed:!d,entry:this.#r.watchEntry?.(t,c.sessionId)}}async#Qe(t,o,i,{notify:e=!0,replyTo:a=null}={}){let A=async g=>{e&&await this.#b(o,g,{replyTo:a}).catch(c=>{this.#O.warn?.("[dsh-feishu] unwatch notification failed:",c.message)})};if(typeof this.#r?.removeWatch!="function")return{ok:!1,changed:!1,reason:"unsupported"};let r=this.#r.watchEntries?.(t)??[],s=typeof i=="string"&&/^\d{1,4}$/.test(i)?r[Number(i)-1]:r.find(g=>g.sessionId===i);if(!s)return await A(w("\u5173\u6CE8\u5217\u8868\u91CC\u6CA1\u6709\u8FD9\u4E2A\u4F1A\u8BDD\uFF0C\u56DE\u590D /watchlist \u67E5\u770B\u3002")),{ok:!0,changed:!1,reason:"absent"};try{await this.#r.removeWatch(t,s.sessionId),this.#W.delete(`${t}\0${s.sessionId}`)}catch(g){return await A(w("\u53D6\u6D88\u5931\u8D25\uFF1A{message}",{message:kC(g)})),{ok:!1,changed:!1,reason:"persist"}}return await A(w("\u5DF2\u53D6\u6D88\u5173\u6CE8\u300C{title}\u300D\u3002",{title:String(s.title??"").replace(/\s+/gu," ")})),{ok:!0,changed:!0,entry:s}}async#ye(t,o,{updateMessageId:i=null,replyTo:e=null}={}){let a=this.#r.watchEntries?.(t)??[],A=[],r=null;try{if(r=typeof this.#t?.currentWorkspace=="function"?this.#t.currentWorkspace():null,r&&typeof this.#t?.listWorkspaceSessions=="function"){let s=await this.#t.listWorkspaceSessions(r,{signal:this.#re()});A=this.#ce(Array.isArray(s?.sessions)?s.sessions:[]).map(g=>({sessionId:g.sessionId,title:g.title??g.name??g.sessionId}))}}catch{}this.#ge(t,{kind:"watches",entries:a}),await this.#z(o,i5(a,A),{key:t,updateMessageId:i,replyTo:e,sessionWorkspace:r})}#pA({sessionId:t,event:o}){this.#w?.aborted||!t||!o||typeof o!="object"||o.type!=="turn/end"||!GC(o.seq)||(this.#IA(t,o),this.#Pe(t,async()=>{let i=this.#r.keysWatching?.(t)??[],e=i.some(A=>mS(this.#r.watchEntry?.(A,t))),a=i.some(A=>this.#W.has(`${A}\0${t}`));(e||a)&&await this.#Le(t),await this.#Fe(t,o)}))}async#Fe(t,o,{keys:i=null}={}){if(this.#w?.aborted||typeof this.#r?.keysWatching!="function")return;let e=o?.data?.reason?.kind??o?.data?.reason??null,a=i??this.#r.keysWatching(t);for(let A of a){if(this.#w?.aborted)return;let r=this.#r.watchEntry?.(A,t),s=`${A}\0${t}`,g=this.#W.get(s);if(GC(g)&&Nn(r?.lastSeq)&&r.lastSeq>=g&&(this.#W.delete(s),g=void 0),!(!r?.chatId||mS(r)||Nn(r.lastSeq)&&r.lastSeq>=o.seq||GC(g)&&o.seq>g))try{await this.#z(r.chatId,o5(t,r.title,e),{key:A,replyTo:r.replyToMessageId??null});let c=this.#r.watchEntry?.(A,t);if(!c||c.chatId!==r.chatId||mS(c)||Nn(c.lastSeq)&&c.lastSeq>=o.seq)continue;await this.#r.setWatch(A,{...c,lastSeq:o.seq}),g===o.seq&&this.#W.delete(s)}catch(c){this.#W.set(s,GC(g)?Math.min(g,o.seq):o.seq),this.#O.warn?.("[dsh-feishu] completion push failed:",c.message)}}}async#Le(t){if(!(this.#w?.aborted||typeof this.#t?.rpc!="function"))try{let o=await this.#t.rpc("session.history",{sessionId:t,maxMessages:20},3e4,{signal:this.#w}),i=qve(o);this.#Be();let e=this.#v.get(t)??new Map,a=new Map(i.map(g=>[g.seq,g]));for(let[g,c]of e)a.has(g)||a.set(g,c.event);let A=[...a.values()].sort((g,c)=>g.seq-c.seq),r=A.at(-1)?.seq??-1,s=typeof this.#r?.keysWatching=="function"?this.#r.keysWatching(t):[];for(let g of s){let c=this.#r.watchEntry?.(g,t);if(!mS(c))continue;let I=SS(c),d=Nn(c.lastSeq)?c.lastSeq:-1,B=I===null?null:A.find(T=>{if(T.seq<=d)return!1;let _=Number.isSafeInteger(T.time)&&T.time>=0?T.time:e.get(T.seq)?.arrivalAt;return Number.isSafeInteger(_)&&_>=I}),f=B?Math.max(d,B.seq-1):Math.max(d,r),Q=this.#r.watchEntry?.(g,t);if(!Q||Q.chatId!==c.chatId||Q.lastSeq!==c.lastSeq||SS(Q)!==I)continue;let{watchStartedAt:y,...m}=Q;await this.#r.setWatch(g,{...m,lastSeq:f})}for(let g of A)g.type==="turn/end"&&await this.#Fe(t,g,{keys:s})}catch(o){this.#w?.aborted||this.#O.warn?.(`[dsh-feishu] watch compensation failed for ${t}:`,o.message)}}async#hA(){let t=typeof this.#r?.watchedSessionIds=="function"?this.#r.watchedSessionIds():[];this.#w?.aborted||await Promise.allSettled(t.map(o=>this.#Ge(o)))}#me(t,o,i){return{timeoutMs:this.#_,signal:this.#w,control:{owner:this,key:o},onInteraction:e=>this.#CA(e,{key:o,actor:Vp(t),chatId:t.message.chat_id,requiresMention:t.message.chat_type!=="p2p",replyToMessageId:t.message.message_id}),onInteractionResolved:e=>this.#BA(e),files:i}}async#Se(t,o,{deliveryId:i,presentation:e,replyTo:a=null}){let A=[];for(let r of q6(o)){this.#w?.throwIfAborted();let s=await this.#b(t,r,{replyTo:a});s&&A.push(s)}return $t({deliveryId:i,presentation:e,providerMessageIds:A})}async#pe(t,o,i=[],e){let a=await Go({artifacts:i,baseReceipt:e,deliveryId:e?.deliveryId??i[0]?.deliveryKey??o,aggregatePresentation:e?"feishu-text-and-files":"feishu-files",channelKey:"feishu",signal:this.#w,sendImage:typeof this.#A?.sendImage=="function"?A=>this.#A.sendImage(t,A,{replyTo:o,signal:this.#w}):void 0,sendFile:typeof this.#A?.sendFile=="function"?A=>this.#A.sendFile(t,A,{replyTo:o,signal:this.#w}):void 0,onFailure:(A,r)=>rt(this.#E,r,{userMessage:Jve(A?.fileName,r),reason:r?.code}),sendFailureNotice:async(A,r,s)=>({messageId:await this.#b(t,ut(s))}),logger:this.#O});return this.#E.artifactsSent=(this.#E.artifactsSent??0)+a.artifactsSent,this.#E.artifactSendErrors=(this.#E.artifactSendErrors??0)+a.artifactSendErrors,a.receipt?{receipt:a.receipt,failureNoticeVisible:a.failureNoticeVisible,artifactSendErrors:a.artifactSendErrors}:{receipt:$t({deliveryId:o,presentation:"feishu-files"}),failureNoticeVisible:a.failureNoticeVisible,artifactSendErrors:a.artifactSendErrors}}async#EA(t,o,i,{onAskComplete:e}={}){let a=t.message.chat_id,A=t.message.message_id,r=i.content,s=!1,g=()=>{s||(s=!0,e?.())},c=Nt(i)||Hr(i)?await Fo(i,{signal:this.#w}):void 0,I=this.#c.get(A),d=!1;if(I){let T=c??r;c=Ro(T,I,()=>({channel:"feishu",senderId:Vp(t),chatId:t.message.chat_id,threadId:t.message.thread_id})),d=c!==T}if(!this.#A?.stream){let{answer:T,artifacts:_=[]}=await _i({harness:this.#t,state:this.#r,key:o,text:r,content:c,contextEnhanced:d,createOptions:{signal:this.#w},existsOptions:{signal:this.#w},askOptions:this.#me(t,o,i.files)});g();let R,x=null;try{R=await this.#Se(a,yS(T,_),{deliveryId:A,presentation:"feishu-text",replyTo:A})}catch(q){x=Ha(q),this.#O.warn?.("[dsh-feishu] final text delivery failed; continuing with result files:",q)}let F=await this.#pe(a,A,_,R),L=F.receipt.artifacts.some(({outcome:q})=>q==="sent"||q==="unknown");if(x&&!L&&!F.failureNoticeVisible)throw x;return x&&F.artifactSendErrors===0&&rt(this.#E,x),this.#E.streamFallbacks=(this.#E.streamFallbacks??0)+1,{...F,textDeliveryErrors:x?1:0}}let B=!1,f="",Q=[],y;try{y=await this.#A.stream(a,{markdown:async T=>{B=!0;let _=this.#me(t,o,i.files),R={..._,onInteraction:async F=>{(F?.kind==="question"||F?.kind==="approval")&&typeof T?.rotate=="function"&&await T.rotate(),await _.onInteraction(F)},onUpdate:async F=>{await T.setContent(this.#fA(F)),this.#E.streamUpdates=(this.#E.streamUpdates??0)+1}},x=await _i({harness:this.#t,state:this.#r,key:o,text:r,content:c,contextEnhanced:d,createOptions:{signal:this.#w},existsOptions:{signal:this.#w},askOptions:R});g(),f=x.answer,Q=x.artifacts??[],await T.setContent(yS(f,Q))}},{replyTo:A})}catch(T){if(this.#E.streamErrors=(this.#E.streamErrors??0)+1,f||Q.length>0){this.#O.warn?.("[dsh-feishu] native stream failed after generation; sending final text:",T.message);let z,H=null;try{z=await this.#Se(a,yS(f,Q),{deliveryId:A,presentation:"feishu-text-fallback",replyTo:A})}catch(U){H=Ha(U),this.#O.warn?.("[dsh-feishu] fallback text delivery failed; continuing with result files:",U)}let G=await this.#pe(a,A,Q,z),V=G.receipt.artifacts.some(({outcome:U})=>U==="sent"||U==="unknown");if(H&&!V&&!G.failureNoticeVisible)throw H;return H&&G.artifactSendErrors===0&&rt(this.#E,H),this.#E.streamFallbacks=(this.#E.streamFallbacks??0)+1,{...G,textDeliveryErrors:H?1:0}}if(B)throw T;this.#O.warn?.("[dsh-feishu] native stream unavailable; using text fallback:",T.message);let{answer:_,artifacts:R=[]}=await _i({harness:this.#t,state:this.#r,key:o,text:r,content:c,contextEnhanced:d,createOptions:{signal:this.#w},existsOptions:{signal:this.#w},askOptions:this.#me(t,o,i.files)});g();let x,F=null;try{x=await this.#Se(a,yS(_,R),{deliveryId:A,presentation:"feishu-text-fallback",replyTo:A})}catch(z){F=Ha(z),this.#O.warn?.("[dsh-feishu] fallback text delivery failed; continuing with result files:",z)}let L=await this.#pe(a,A,R,x),q=L.receipt.artifacts.some(({outcome:z})=>z==="sent"||z==="unknown");if(F&&!q&&!L.failureNoticeVisible)throw F;return F&&L.artifactSendErrors===0&&rt(this.#E,F),this.#E.streamFallbacks=(this.#E.streamFallbacks??0)+1,{...L,textDeliveryErrors:F?1:0}}let m=await this.#pe(a,A,Q,$t({deliveryId:A,presentation:"feishu-cardkit",providerMessageIds:Ur(y)}));return this.#E.streamResponses=(this.#E.streamResponses??0)+1,m}async#uA(t,o,i,e,a){this.#w?.throwIfAborted();let A=this.#n.get(i),r=e.claimedReplyMessageId===o;if(!A||A!==e||A.submitting)return this.#we(t,i)?this.#De(t,o):r&&(!A||A!==e)?this.#De(t,o):this.#j(t,o,i,a,{releaseMessageId:!1,finalize:!1});if(this.#r.hasSeen(o))return;await this.#r.markSeen(o),this.#E.lastMessageAt=new Date().toISOString(),this.#E.messagesReceived+=1;let s=qp(t);if(!s){await this.#b(t.message.chat_id,w("\u8BF7\u7528\u6587\u5B57\u56DE\u7B54\u5F53\u524D\u95EE\u9898\u3002"));return}let g=this.#n.get(i);if(!g||g!==e||g.submitting){if(this.#we(t,i)){await this.#b(t.message.chat_id,RC(),{replyTo:t.message.message_id}).catch(()=>{});return}if(r&&(!g||g!==e)){await this.#b(t.message.chat_id,RC(),{replyTo:t.message.message_id});return}return this.#j(t,o,i,a,{releaseMessageId:!1,alreadyRecorded:!0,finalize:!1})}if(g.chatId=t.message.chat_id,g.needsPresentation){try{await this.#he(g)}catch{this.#E.lastError="\u98DE\u4E66\u4EA4\u4E92\u95EE\u9898\u53D1\u9001\u5931\u8D25\u3002",this.#O.error?.("[dsh-feishu] failed to retry an interaction question"),g.interaction.reconnect?.()}return}g.questions[g.index]&&await this.#Ue(g,s,{chatId:t.message.chat_id,messageId:o})}async#Ue(t,o,{chatId:i,messageId:e}={}){let a=t.questions[t.index];if(!a)return;if(t.chatId=i??t.chatId,t.answers.push(bi(a,o)),t.index+=1,t.index{});return}t.submitting=!1,t.answers.pop(),t.index-=1,this.#E.lastError="\u56DE\u7B54\u63D0\u4EA4\u5931\u8D25\u3002",this.#O.error?.("[dsh-feishu] failed to answer a Harness interaction"),i&&await this.#b(i,w("\u56DE\u7B54\u63D0\u4EA4\u5931\u8D25\uFF0C\u8BF7\u91CD\u65B0\u53D1\u9001\u5F53\u524D\u95EE\u9898\u7684\u7B54\u6848\u3002")).catch(()=>{})}}async#CA(t,{key:o,actor:i,chatId:e,requiresMention:a,replyToMessageId:A}){if(await this.#D.handleRequested(t,{key:o,actor:i,requiresMention:a,send:I=>this.#b(e,I,{replyTo:A}),...this.#Z?{render:async I=>{await this.#z(e,g5({toolName:I.toolCall?.name??I.payload?.toolName,operation:Kve(I.toolCall),reason:I.payload?.reason,approvalId:I.approvalId}),{key:o,replyTo:A}).catch(async()=>{await this.#b(e,I.text,{replyTo:A})})}}:{}})||t?.kind!=="question")return;let r=t?.payload?.questions,s=typeof t?.interactionId=="string"?t.interactionId:t?.rpcId;if(typeof t.rpcId!="string"||typeof s!="string"||typeof t.sessionId!="string"||!Array.isArray(r)||r.length===0||r.some(I=>!Oi(I))){this.#O.warn?.("[dsh-feishu] ignored an invalid Harness question interaction");return}if(t.recovered===!0){await t.respond({ok:!1,error:{code:"cancelled",message:"Feishu safely cancelled an interaction left by an earlier client.",details:{}}}),await this.#b(e,w("\u68C0\u6D4B\u5230\u8FD9\u4E2A Session \u4E2D\u9057\u7559\u7684\u5F85\u56DE\u7B54\u95EE\u9898\uFF0C\u5DF2\u5B89\u5168\u53D6\u6D88\u5E76\u7EE7\u7EED\u5904\u7406\u4F60\u521A\u624D\u7684\u6D88\u606F\u3002"),{replyTo:A}).catch(()=>{});return}let g=this.#n.get(o);if(g?.interactionId===s){g.interaction=t,g.needsPresentation&&await this.#he(g);return}if(this.#s.has(s))return;if(g){await t.respond({ok:!1,error:{code:"cancelled",message:"Feishu is already handling another user interaction.",details:{}}});return}let c={kind:"question",interactionId:s,sessionId:t.sessionId,interaction:t,key:o,actor:i,requiresMention:a,questions:r,answers:[],index:0,chatId:e,replyToMessageId:A,queue:null,claimedReplyMessageId:null,submitting:!1,needsPresentation:!0,questionMessageIds:new Set,inactive:!1};this.#n.set(o,c),this.#s.set(c.interactionId,o),await this.#he(c)}async#BA(t){if(await this.#D.handleResolved(t))return;let o=t?.interactionId;if(t?.kind!=="question"||typeof o!="string")return;let i=this.#s.get(o);if(!i)return;let e=this.#n.get(i);e&&this.#Ie(i,e),this.#Oe(i,o)}async#he(t){let o=t.questions[t.index];if(!o)return;let i=Array.isArray(o?.options)?o.options:[],e=this.#Z&&i.length>0&&o.multiSelect!==!0,a;e?a=await this.#z(t.chatId,c5({interactionId:t.interactionId,header:o.header,question:o.question,detail:o.detail,options:i,index:t.index,total:t.questions.length}),{key:t.key,replyTo:t.replyToMessageId}).catch(async()=>{await this.#b(t.chatId,ci(o,t.index,t.questions.length,{requiresMention:t.requiresMention}),{replyTo:t.replyToMessageId})}):a=await this.#b(t.chatId,ci(o,t.index,t.questions.length,{requiresMention:t.requiresMention}),{replyTo:t.replyToMessageId}),a&&(t.questionMessageIds.add(a),t.inactive&&this.#Ie(t.key,t)),t.needsPresentation=!1}#Ie(t,o){let i=Date.now()+mve;for(let e of o.questionMessageIds??[])this.#d.set(e,{key:t,expiresAt:i})}#we(t,o){let i=Date.now();for(let[e,a]of this.#d)a.expiresAt<=i&&this.#d.delete(e);for(let e of[t?.message?.parent_id,t?.message?.root_id]){let a=this.#d.get(e);if(a?.key===o&&a.expiresAt>i)return!0}return!1}async#De(t,o){this.#r.hasSeen(o)||(await this.#r.markSeen(o),this.#E.lastMessageAt=new Date().toISOString(),this.#E.messagesReceived+=1,await this.#b(t.message.chat_id,RC(),{replyTo:t.message.message_id}).catch(()=>{}))}#He(t,o){let i=this.#n.get(t);return!i||o!==void 0&&i.interactionId!==o?null:(this.#n.delete(t),this.#s.delete(i.interactionId),i.inactive=!0,i)}#Oe(t,o){return this.#He(t,o)!==null}async#be(t){let o=this.#He(t);if(!(!o||o.kind!=="question")){this.#Ie(t,o);try{await o.interaction.respond({ok:!1,error:{code:"cancelled",message:"The Feishu interaction ended before the user answered.",details:{}}},{signal:AbortSignal.timeout(5e3)})}catch(i){i?.code!=="interaction-not-pending"&&this.#O.warn?.("[dsh-feishu] failed to cancel a pending Harness interaction")}}}#fA(t){return t.type==="text"&&t.text?t.text:t.type==="tool"?t.name==="web_search"?w("_\u6B63\u5728\u641C\u7D22\u7F51\u7EDC\u5E76\u6574\u7406\u4FE1\u606F\u2026_"):w("_\u6B63\u5728\u4F7F\u7528 {name}\u2026_",{name:t.name||w("\u5DE5\u5177")}):w("_{text}_",{text:t.text||w("\u6B63\u5728\u5904\u7406\u2026")})}#QA(t){return ay({adapter:this.#A,target:t,reactions:{processing:"OnIt",success:"DONE",error:"ERROR"},status:this.#E,logger:this.#O,label:"feishu"})}#Ke(t,o){o.clear()}#ie(t,o,i){i==="ERROR"?o.error():o.success()}async#b(t,o,{replyTo:i}={}){let e=JSON.stringify({text:o});if(i)try{let A=await this.#e.im.v1.message.reply({path:{message_id:i},data:{msg_type:"text",content:e}});if(A?.code&&A.code!==0)throw new Error(`Feishu reply failed: ${A.msg||A.code}`);return lA(A?.data?.message_id)}catch(A){this.#O.warn?.("[dsh-feishu] threaded reply failed; falling back to a plain message:",A?.message??String(A))}let a=await this.#e.im.v1.message.create({params:{receive_id_type:"chat_id"},data:{receive_id:t,msg_type:"text",content:e}});if(a?.code&&a.code!==0)throw new Error(`Feishu send failed: ${a.msg||a.code}`);return lA(a?.data?.message_id)}};import{createHash as Zve}from"node:crypto";var y5="stream_md",Xve="\u5DF2\u8FDE\u63A5 DeepSeek Harness\uFF0C\u6B63\u5728\u601D\u8003\u2026",LC=28e3,DS=12e4,m5=new Map([[99991672,["artifact-permission-required","Feishu file delivery requires the im:resource permission."]],[234006,["artifact-too-large","The result file exceeds Feishu's size limit."]],[234010,["artifact-empty","Feishu does not accept empty files."]],[230017,["artifact-provider-rejected","Feishu rejected the uploaded file ownership."]],[230020,["artifact-rate-limited","Feishu temporarily rate-limited file delivery."]],[230049,["artifact-delivery-uncertain","Feishu could not confirm the file message result."]],[230055,["artifact-provider-rejected","Feishu rejected the file message type."]]]);function yl(n,t){if(t?.code&&t.code!==0)throw new Error(`${n} failed: ${t.msg||t.code}`);return t}function S5(n){let t=[n],o=new Set,i;for(;t.length>0;){let e=t.shift();if(!e||o.has(e))continue;if(typeof e=="object"&&o.add(e),Array.isArray(e)){t.push(...e);continue}let a=Number(e?.code);if(Number.isFinite(a)&&a!==0){if(m5.has(a))return a;i??=a}t.push(e?.response?.data,e?.data,e?.error,e?.cause)}return i}function FC(n,t,o,{uncertain:i=!1}={}){let e=o==null?void 0:Number(o),a=Number.isFinite(e)&&e!==0?e:S5(t),A=Number.isFinite(a)?["artifact-provider-rejected",`Feishu rejected file ${n}.`]:i?["artifact-delivery-uncertain","Feishu could not confirm the file message result."]:["artifact-provider-failed",`Feishu file ${n} failed.`],[r,s]=m5.get(a)??A,g=new Error(s,{cause:t});return g.code=r,Number.isFinite(a)&&(g.providerCode=a),g}function f5(n,t){if(!Number.isInteger(n)||n<1||n>DS)throw new TypeError(`${t} must be an integer between 1 and ${DS}`);return n}function FP(n){return n?.reason??new DOMException("The operation was aborted","AbortError")}function $ve(n){let t=new Error(`Feishu file ${n} timed out.`);return t.code="provider-timeout",t}function Q5(n,{signal:t,timeoutMs:o,stage:i}){t?.throwIfAborted();let e=new AbortController,a=t?AbortSignal.any([t,e.signal]):e.signal;return new Promise((A,r)=>{let s=!1,g=(d,B)=>{s||(s=!0,clearTimeout(I),a.removeEventListener("abort",c),d(B))},c=()=>g(r,t?.aborted?FP(t):$ve(i)),I=setTimeout(()=>e.abort(),o);a.addEventListener("abort",c,{once:!0}),Promise.resolve().then(()=>n(a)).then(d=>g(A,d),d=>g(r,d)),a.aborted&&c()})}function ePe(n,t,o){let i=`${n.deliveryKey}\0${t}`;return`dshim_${Zve("sha256").update(o==="file"?i:`${i}\0${o}`).digest("hex").slice(0,40)}`}function APe(n){let t=String(n??"").replace(/\s+/g," ").trim();return t.length<=50?t:`${LP(t,49)}\u2026`}function LP(n,t){let o=Math.min(n.length,t),i=n.charCodeAt(o-1),e=n.charCodeAt(o);return i>=55296&&i<=56319&&e>=56320&&e<=57343&&(o-=1),n.slice(0,o)}function GP(n){if(n.length<=LC)return n;let t=` +`),{replyTo:i})}catch(e){await this.#Y(o,e,{logLabel:"status text",replyTo:i})}}async#aA(t,o,{updateMessageId:i=null}={}){try{let e=this.#re();await this.#t.ensureRunning({signal:e});let a={connected:!0,workspace:null,preset:null,model:null,sessionCount:0};try{let A=typeof this.#t.currentWorkspace=="function"?this.#t.currentWorkspace():null;a.workspace=A||w("\u672A\u77E5")}catch{}try{let A=await this.#t.agentPresetSettings({signal:e}),r=A.agentPresetCatalog.items.find(s=>s.id===A.agentPreset);a.preset=r?`${r.label}\uFF08${r.id}\uFF09`:A.agentPreset||w("\u8DDF\u968F\u9ED8\u8BA4")}catch{}try{let A=this.#r?.sessionFor?.(t);if(typeof A=="string"&&A){let r=this.#t.workspaceSession(A);if(r?.models){let s=await r.models({signal:e});s.current&&(a.model=`${s.current.provider}/${s.current.model}`)}}}catch{}try{let A=typeof this.#t.currentWorkspace=="function"?this.#t.currentWorkspace():null;if(A){let r=await this.#t.listWorkspaceSessions(A,{signal:e});Array.isArray(r?.sessions)&&(a.sessionCount=r.sessions.length)}}catch{}await this.#z(o,$6(a),{key:t,updateMessageId:i})}catch(e){await this.#Y(o,e,{logLabel:"status card",replyTo:i})}}async#iA(t,o,{updateMessageId:i=null}={}){await this.#z(o,A5(jve.map(e=>w(e))),{key:t,updateMessageId:i})}async#oA(t,o,i=null){try{let e=await Ti("/compact",this.#t,this.#r,t,{signal:this.#w});await this.#b(o,e?.message||w("\u4E0A\u4E0B\u6587\u538B\u7F29\u5931\u8D25\u3002"),{replyTo:i})}catch(e){await this.#Y(o,e,{logLabel:"compact",replyTo:i})}}async#nA(t,o,i=null){try{let e=await Ni("/stop",this.#t,this.#r,t,{signal:this.#w,control:{owner:this,key:t}});e?.stopped&&await Promise.allSettled([this.#be(t),this.#D.closeRoute(t)]),await this.#b(o,e?.message||w("/stop \u6267\u884C\u5B8C\u6210\u3002"),{replyTo:i})}catch(e){await this.#Y(o,e,{logLabel:"stop",replyTo:i})}}async#sA(t,o,{updateMessageId:i=null}={}){let e=!!this.#r.sessionFor?.(t);this.#ge(t,{kind:"steer"}),await this.#z(o,s5({hasSession:e}),{key:t,updateMessageId:i})}async#Ce(t,o){let{key:i,chatId:e}=t,a=await Ni(`/steer ${o}`,this.#t,this.#r,i,{signal:this.#w,pendingInteraction:this.#ne(i),control:{owner:this,key:i}});await this.#b(e,a?.message||w("\u5DF2\u63D0\u4EA4\u8865\u5145\u6307\u4EE4\u3002"),{replyTo:t.messageId??null})}async#ke(t,o,{updateMessageId:i=null,replyTo:e=null}={}){try{let a=await Ri("/preset --default",this.#t,this.#r,t,{signal:this.#w});for(let A of a?.messages??[a?.message])A&&await this.#b(o,A,{replyTo:e})}catch(a){await this.#Y(o,a,{logLabel:"preset reset",replyTo:i});return}try{await this.#ee(t,o,{updateMessageId:i,replyTo:e})}catch(a){this.#O.warn?.("[dsh-feishu] menu refresh failed after preset reset:",a.message)}}async#gA(t,o,i,{updateMessageId:e=null,replyTo:a=null}={}){try{let A=/^\d+$/u.test(i)?`id:${i}`:i,r=await Ri(`/preset ${A}`,this.#t,this.#r,t,{signal:this.#w});for(let s of r?.messages??[r?.message])s&&await this.#b(o,s,{replyTo:a})}catch(A){await this.#Y(o,A,{logLabel:"preset selection",replyTo:e});return}try{await this.#ee(t,o,{updateMessageId:e,replyTo:a})}catch(A){this.#O.warn?.("[dsh-feishu] menu refresh failed after preset select:",A.message)}}async#cA(t,o,i,{updateMessageId:e=null,replyTo:a=null}={}){try{let A=await oo(`/model ${i}`,this.#t,this.#r,t,{signal:this.#w,pendingInteraction:this.#ne(t),control:{owner:this,key:t}});for(let r of A?.messages??[A?.message])r&&await this.#b(o,r,{replyTo:a})}catch(A){await this.#Y(o,A,{logLabel:"model selection",replyTo:e});return}try{await this.#ee(t,o,{updateMessageId:e,replyTo:a})}catch(A){this.#O.warn?.("[dsh-feishu] menu refresh failed after model select:",A.message)}}#ve(){if(this.#L||typeof this.#t?.watchHarnessEvents!="function"||this.#w?.aborted)return;let t=this.#w??new AbortController().signal;try{this.#L=this.#t.watchHarnessEvents({signal:t,onSessionEvent:o=>this.#pA(o),onReconnect:()=>{this.#hA()}}),Promise.resolve(this.#L).catch(o=>{t.aborted||this.#O.warn?.("[dsh-feishu] event watcher stopped:",o.message)})}catch(o){this.#L=null,this.#O.warn?.("[dsh-feishu] event watcher failed to start:",o.message)}}#Pe(t,o){let i=this.#V.get(t)??Promise.resolve(),e;return e=i.then(o,o).catch(a=>{this.#w?.aborted||this.#O.warn?.("[dsh-feishu] completion event failed:",a.message)}).finally(()=>{this.#V.get(t)===e&&this.#V.delete(t)}),this.#V.set(t,e),e}#Be(t=Date.now()){let o=0;for(let[i,e]of this.#v){for(let[a,A]of e)(!Number.isSafeInteger(A?.arrivalAt)||A.arrivalAt<0||A.arrivalAt+Nve<=t)&&e.delete(a);for(;e.size>kve;){let a=e.keys().next().value;if(a===void 0)break;e.delete(a)}e.size===0?this.#v.delete(i):o+=e.size}for(;this.#v.size>Rve;){let i=this.#v.keys().next().value;if(i===void 0)break;o-=this.#v.get(i)?.size??0,this.#v.delete(i)}for(;o>vve;){let i=this.#v.keys().next().value;if(i===void 0)break;let e=this.#v.get(i),a=e?.keys().next().value;if(a===void 0){this.#v.delete(i);continue}e.delete(a),o-=1,e.size===0&&this.#v.delete(i)}}#IA(t,o,i=Date.now()){this.#Be(i);let e=this.#v.get(t);e||(e=new Map);let a=o?.data?.reason,A=typeof a=="string"?a:typeof a?.kind=="string"?{kind:a.kind}:null;e.delete(o.seq),e.set(o.seq,{arrivalAt:i,event:{type:"turn/end",seq:o.seq,...Number.isSafeInteger(o.time)&&o.time>=0?{time:o.time}:{},data:{reason:A}}}),this.#v.delete(t),this.#v.set(t,e),this.#Be(i)}async#lA(t,{workspaceHint:o=null,signal:i=this.#w}={}){if(typeof t!="string"||t==="")return{error:w("\u7528\u6CD5\uFF1A/watch ")};let e=/^\d{1,4}$/.test(t)?Number(t):null,a=typeof this.#t?.currentWorkspace=="function"?this.#t.currentWorkspace():null,A=async s=>{let g=await this.#t.listWorkspaceSessions(s,{signal:i});return Array.isArray(g?.sessions)?g.sessions:[]};if(e!==null){if(!a)return{error:w("\u5F53\u524D\u673A\u5668\u4EBA\u6CA1\u6709\u53EF\u7528\u7684\u5DE5\u4F5C\u533A\uFF0C\u65E0\u6CD5\u6309\u5E8F\u53F7\u89E3\u6790\u4F1A\u8BDD\u3002")};let s=this.#ce(await A(a)),g=s[e-1];return g?.sessionId?{sessionId:g.sessionId,title:g.title??w("\u6682\u65E0\u6807\u9898"),workspace:a,...Nn(g.lastSeq)?{lastSeq:g.lastSeq}:{}}:{error:w("\u5F53\u524D\u5DE5\u4F5C\u533A\u53EA\u6709 {count} \u4E2A\u4F1A\u8BDD\u3002",{count:s.length})}}let r;if(lA(o))r=[o];else{let s=typeof this.#t?.listWorkspaces=="function"?(await this.#t.listWorkspaces({signal:i})).filter(g=>g!==a):[];r=[a,...s].filter(Boolean)}for(let s of r){let c=(await A(s)).find(I=>I.sessionId===t);if(c)return{sessionId:t,title:c.title??w("\u6682\u65E0\u6807\u9898"),workspace:s,...Nn(c.lastSeq)?{lastSeq:c.lastSeq}:{}}}return{error:w("\u6CA1\u6709\u627E\u5230\u8FD9\u4E2A\u4F1A\u8BDD\uFF0C\u8BF7\u7528 /sessionlist \u67E5\u770B\u53EF\u7528\u4F1A\u8BDD\u3002")}}async#dA(t){let o=lA(t)??(typeof this.#t?.currentWorkspace=="function"?lA(this.#t.currentWorkspace()):null);if(!o||typeof this.#t?.listWorkspaceSessions!="function")return new Map;let i=await this.#t.listWorkspaceSessions(o,{signal:this.#re()});return new Map(this.#ce(Array.isArray(i?.sessions)?i.sessions:[]).filter(e=>lA(e?.sessionId)).map(e=>[e.sessionId,{sessionId:e.sessionId,title:e.title??e.name??w("\u6682\u65E0\u6807\u9898"),workspace:o,...Nn(e.lastSeq)?{lastSeq:e.lastSeq}:{}}]))}#Ge(t){let o=this.#q.get(t);if(o)return o.requested=!0,o.promise;let i={requested:!0,promise:null};return this.#q.set(t,i),i.promise=Promise.resolve().then(async()=>{try{do i.requested=!1,await this.#Pe(t,()=>this.#Le(t));while(i.requested&&!this.#w?.aborted)}finally{this.#q.get(t)===i&&this.#q.delete(t)}}),i.promise}async#fe(t,o,i,{notify:e=!0,validatedTarget:a=null,workspaceHint:A=null,replyTo:r=null}={}){let s=Date.now(),g=async y=>{e&&await this.#b(o,y,{replyTo:r}).catch(m=>{this.#O.warn?.("[dsh-feishu] watch notification failed:",m.message)})};if(this.#ve(),typeof this.#r?.setWatch!="function")return await g(w("\u5F53\u524D\u72B6\u6001\u5B58\u50A8\u4E0D\u652F\u6301\u5173\u6CE8\u3002")),{ok:!1,changed:!1,reason:"unsupported"};let c;try{c=a?.sessionId===i?a:await this.#lA(i,{workspaceHint:A,signal:A?this.#re():this.#w})}catch(y){return await g(w("\u65E0\u6CD5\u89E3\u6790\u4F1A\u8BDD\uFF1A{message}",{message:kC(y)})),{ok:!1,changed:!1,reason:"resolve"}}if(c.error)return await g(c.error),{ok:!1,changed:!1,reason:"not-found"};let I=this.#r.watchEntries?.(t)??[],d=I.find(y=>y.sessionId===c.sessionId);if(!d&&I.length>=BS)return await g(w("\u6BCF\u4E2A\u804A\u5929\u6700\u591A\u5173\u6CE8 {count} \u4E2A\u4F1A\u8BDD\u3002",{count:BS})),{ok:!1,changed:!1,reason:"limit"};let B=Nn(d?.lastSeq)?d.lastSeq:Nn(c.lastSeq)?c.lastSeq:null,Q=SS(d)??(d?null:s);try{await this.#r.setWatch(t,{sessionId:c.sessionId,title:c.title,chatId:o,lastSeq:B,...Q!==null?{watchStartedAt:Q}:{},...r?{replyToMessageId:r}:d?.replyToMessageId?{replyToMessageId:d.replyToMessageId}:{}})}catch(y){return await g(w("\u5173\u6CE8\u5931\u8D25\uFF1A{message}",{message:kC(y)})),{ok:!1,changed:!1,reason:"persist"}}return(!d||Q!==null||!Nn(B))&&this.#Ge(c.sessionId),await g(w("\u5DF2\u5173\u6CE8\u4F1A\u8BDD\u300C{title}\u300D\uFF0C\u4EFB\u52A1\u5B8C\u6210\u4F1A\u63A8\u9001\u7ED3\u679C\u3002",{title:String(c.title).replace(/\s+/gu," ")})),{ok:!0,changed:!d,entry:this.#r.watchEntry?.(t,c.sessionId)}}async#Qe(t,o,i,{notify:e=!0,replyTo:a=null}={}){let A=async g=>{e&&await this.#b(o,g,{replyTo:a}).catch(c=>{this.#O.warn?.("[dsh-feishu] unwatch notification failed:",c.message)})};if(typeof this.#r?.removeWatch!="function")return{ok:!1,changed:!1,reason:"unsupported"};let r=this.#r.watchEntries?.(t)??[],s=typeof i=="string"&&/^\d{1,4}$/.test(i)?r[Number(i)-1]:r.find(g=>g.sessionId===i);if(!s)return await A(w("\u5173\u6CE8\u5217\u8868\u91CC\u6CA1\u6709\u8FD9\u4E2A\u4F1A\u8BDD\uFF0C\u56DE\u590D /watchlist \u67E5\u770B\u3002")),{ok:!0,changed:!1,reason:"absent"};try{await this.#r.removeWatch(t,s.sessionId),this.#W.delete(`${t}\0${s.sessionId}`)}catch(g){return await A(w("\u53D6\u6D88\u5931\u8D25\uFF1A{message}",{message:kC(g)})),{ok:!1,changed:!1,reason:"persist"}}return await A(w("\u5DF2\u53D6\u6D88\u5173\u6CE8\u300C{title}\u300D\u3002",{title:String(s.title??"").replace(/\s+/gu," ")})),{ok:!0,changed:!0,entry:s}}async#ye(t,o,{updateMessageId:i=null,replyTo:e=null}={}){let a=this.#r.watchEntries?.(t)??[],A=[],r=null;try{if(r=typeof this.#t?.currentWorkspace=="function"?this.#t.currentWorkspace():null,r&&typeof this.#t?.listWorkspaceSessions=="function"){let s=await this.#t.listWorkspaceSessions(r,{signal:this.#re()});A=this.#ce(Array.isArray(s?.sessions)?s.sessions:[]).map(g=>({sessionId:g.sessionId,title:g.title??g.name??g.sessionId}))}}catch{}this.#ge(t,{kind:"watches",entries:a}),await this.#z(o,i5(a,A),{key:t,updateMessageId:i,replyTo:e,sessionWorkspace:r})}#pA({sessionId:t,event:o}){this.#w?.aborted||!t||!o||typeof o!="object"||o.type!=="turn/end"||!GC(o.seq)||(this.#IA(t,o),this.#Pe(t,async()=>{let i=this.#r.keysWatching?.(t)??[],e=i.some(A=>mS(this.#r.watchEntry?.(A,t))),a=i.some(A=>this.#W.has(`${A}\0${t}`));(e||a)&&await this.#Le(t),await this.#Fe(t,o)}))}async#Fe(t,o,{keys:i=null}={}){if(this.#w?.aborted||typeof this.#r?.keysWatching!="function")return;let e=o?.data?.reason?.kind??o?.data?.reason??null,a=i??this.#r.keysWatching(t);for(let A of a){if(this.#w?.aborted)return;let r=this.#r.watchEntry?.(A,t),s=`${A}\0${t}`,g=this.#W.get(s);if(GC(g)&&Nn(r?.lastSeq)&&r.lastSeq>=g&&(this.#W.delete(s),g=void 0),!(!r?.chatId||mS(r)||Nn(r.lastSeq)&&r.lastSeq>=o.seq||GC(g)&&o.seq>g))try{await this.#z(r.chatId,o5(t,r.title,e),{key:A,replyTo:r.replyToMessageId??null});let c=this.#r.watchEntry?.(A,t);if(!c||c.chatId!==r.chatId||mS(c)||Nn(c.lastSeq)&&c.lastSeq>=o.seq)continue;await this.#r.setWatch(A,{...c,lastSeq:o.seq}),g===o.seq&&this.#W.delete(s)}catch(c){this.#W.set(s,GC(g)?Math.min(g,o.seq):o.seq),this.#O.warn?.("[dsh-feishu] completion push failed:",c.message)}}}async#Le(t){if(!(this.#w?.aborted||typeof this.#t?.rpc!="function"))try{let o=await this.#t.rpc("session.history",{sessionId:t,maxMessages:20},3e4,{signal:this.#w}),i=qve(o);this.#Be();let e=this.#v.get(t)??new Map,a=new Map(i.map(g=>[g.seq,g]));for(let[g,c]of e)a.has(g)||a.set(g,c.event);let A=[...a.values()].sort((g,c)=>g.seq-c.seq),r=A.at(-1)?.seq??-1,s=typeof this.#r?.keysWatching=="function"?this.#r.keysWatching(t):[];for(let g of s){let c=this.#r.watchEntry?.(g,t);if(!mS(c))continue;let I=SS(c),d=Nn(c.lastSeq)?c.lastSeq:-1,B=I===null?null:A.find(T=>{if(T.seq<=d)return!1;let _=Number.isSafeInteger(T.time)&&T.time>=0?T.time:e.get(T.seq)?.arrivalAt;return Number.isSafeInteger(_)&&_>=I}),f=B?Math.max(d,B.seq-1):Math.max(d,r),Q=this.#r.watchEntry?.(g,t);if(!Q||Q.chatId!==c.chatId||Q.lastSeq!==c.lastSeq||SS(Q)!==I)continue;let{watchStartedAt:y,...m}=Q;await this.#r.setWatch(g,{...m,lastSeq:f})}for(let g of A)g.type==="turn/end"&&await this.#Fe(t,g,{keys:s})}catch(o){this.#w?.aborted||this.#O.warn?.(`[dsh-feishu] watch compensation failed for ${t}:`,o.message)}}async#hA(){let t=typeof this.#r?.watchedSessionIds=="function"?this.#r.watchedSessionIds():[];this.#w?.aborted||await Promise.allSettled(t.map(o=>this.#Ge(o)))}#me(t,o,i){return{timeoutMs:this.#_,signal:this.#w,control:{owner:this,key:o},onInteraction:e=>this.#CA(e,{key:o,actor:Vp(t),chatId:t.message.chat_id,requiresMention:t.message.chat_type!=="p2p",replyToMessageId:t.message.message_id}),onInteractionResolved:e=>this.#BA(e),files:i}}async#Se(t,o,{deliveryId:i,presentation:e,replyTo:a=null}){let A=[];for(let r of q6(o)){this.#w?.throwIfAborted();let s=await this.#b(t,r,{replyTo:a});s&&A.push(s)}return $t({deliveryId:i,presentation:e,providerMessageIds:A})}async#pe(t,o,i=[],e){let a=await Go({artifacts:i,baseReceipt:e,deliveryId:e?.deliveryId??i[0]?.deliveryKey??o,aggregatePresentation:e?"feishu-text-and-files":"feishu-files",channelKey:"feishu",signal:this.#w,sendImage:typeof this.#A?.sendImage=="function"?A=>this.#A.sendImage(t,A,{replyTo:o,signal:this.#w}):void 0,sendFile:typeof this.#A?.sendFile=="function"?A=>this.#A.sendFile(t,A,{replyTo:o,signal:this.#w}):void 0,onFailure:(A,r)=>rt(this.#E,r,{userMessage:Jve(A?.fileName,r),reason:r?.code}),sendFailureNotice:async(A,r,s)=>({messageId:await this.#b(t,ut(s))}),logger:this.#O});return this.#E.artifactsSent=(this.#E.artifactsSent??0)+a.artifactsSent,this.#E.artifactSendErrors=(this.#E.artifactSendErrors??0)+a.artifactSendErrors,a.receipt?{receipt:a.receipt,failureNoticeVisible:a.failureNoticeVisible,artifactSendErrors:a.artifactSendErrors}:{receipt:$t({deliveryId:o,presentation:"feishu-files"}),failureNoticeVisible:a.failureNoticeVisible,artifactSendErrors:a.artifactSendErrors}}async#EA(t,o,i,{onAskComplete:e}={}){let a=t.message.chat_id,A=t.message.message_id,r=i.content,s=!1,g=()=>{s||(s=!0,e?.())},c=Nt(i)||Hr(i)?await Fo(i,{signal:this.#w}):void 0,I=this.#c.get(A),d=!1;if(I){let T=c??r;c=Ro(T,I,()=>({channel:"feishu",senderId:Vp(t),chatId:t.message.chat_id,threadId:t.message.thread_id})),d=c!==T}if(!this.#A?.stream){let{answer:T,artifacts:_=[]}=await _i({harness:this.#t,state:this.#r,key:o,text:r,content:c,contextEnhanced:d,createOptions:{signal:this.#w},existsOptions:{signal:this.#w},askOptions:this.#me(t,o,i.files)});g();let R,x=null;try{R=await this.#Se(a,yS(T,_),{deliveryId:A,presentation:"feishu-text",replyTo:A})}catch(q){x=Ha(q),this.#O.warn?.("[dsh-feishu] final text delivery failed; continuing with result files:",q)}let F=await this.#pe(a,A,_,R),L=F.receipt.artifacts.some(({outcome:q})=>q==="sent"||q==="unknown");if(x&&!L&&!F.failureNoticeVisible)throw x;return x&&F.artifactSendErrors===0&&rt(this.#E,x),this.#E.streamFallbacks=(this.#E.streamFallbacks??0)+1,{...F,textDeliveryErrors:x?1:0}}let B=!1,f="",Q=[],y;try{y=await this.#A.stream(a,{markdown:async T=>{B=!0;let _=this.#me(t,o,i.files),R={..._,onInteraction:async F=>{(F?.kind==="question"||F?.kind==="approval")&&typeof T?.rotate=="function"&&await T.rotate(),await _.onInteraction(F)},onUpdate:async F=>{await T.setContent(this.#fA(F)),this.#E.streamUpdates=(this.#E.streamUpdates??0)+1}},x=await _i({harness:this.#t,state:this.#r,key:o,text:r,content:c,contextEnhanced:d,createOptions:{signal:this.#w},existsOptions:{signal:this.#w},askOptions:R});g(),f=x.answer,Q=x.artifacts??[],await T.setContent(yS(f,Q))}},{replyTo:A})}catch(T){if(this.#E.streamErrors=(this.#E.streamErrors??0)+1,f||Q.length>0){this.#O.warn?.("[dsh-feishu] native stream failed after generation; sending final text:",T.message);let z,H=null;try{z=await this.#Se(a,yS(f,Q),{deliveryId:A,presentation:"feishu-text-fallback",replyTo:A})}catch(U){H=Ha(U),this.#O.warn?.("[dsh-feishu] fallback text delivery failed; continuing with result files:",U)}let G=await this.#pe(a,A,Q,z),V=G.receipt.artifacts.some(({outcome:U})=>U==="sent"||U==="unknown");if(H&&!V&&!G.failureNoticeVisible)throw H;return H&&G.artifactSendErrors===0&&rt(this.#E,H),this.#E.streamFallbacks=(this.#E.streamFallbacks??0)+1,{...G,textDeliveryErrors:H?1:0}}if(B)throw T;this.#O.warn?.("[dsh-feishu] native stream unavailable; using text fallback:",T.message);let{answer:_,artifacts:R=[]}=await _i({harness:this.#t,state:this.#r,key:o,text:r,content:c,contextEnhanced:d,createOptions:{signal:this.#w},existsOptions:{signal:this.#w},askOptions:this.#me(t,o,i.files)});g();let x,F=null;try{x=await this.#Se(a,yS(_,R),{deliveryId:A,presentation:"feishu-text-fallback",replyTo:A})}catch(z){F=Ha(z),this.#O.warn?.("[dsh-feishu] fallback text delivery failed; continuing with result files:",z)}let L=await this.#pe(a,A,R,x),q=L.receipt.artifacts.some(({outcome:z})=>z==="sent"||z==="unknown");if(F&&!q&&!L.failureNoticeVisible)throw F;return F&&L.artifactSendErrors===0&&rt(this.#E,F),this.#E.streamFallbacks=(this.#E.streamFallbacks??0)+1,{...L,textDeliveryErrors:F?1:0}}let m=await this.#pe(a,A,Q,$t({deliveryId:A,presentation:"feishu-cardkit",providerMessageIds:Ur(y)}));return this.#E.streamResponses=(this.#E.streamResponses??0)+1,m}async#uA(t,o,i,e,a){this.#w?.throwIfAborted();let A=this.#n.get(i),r=e.claimedReplyMessageId===o;if(!A||A!==e||A.submitting)return this.#we(t,i)?this.#De(t,o):r&&(!A||A!==e)?this.#De(t,o):this.#j(t,o,i,a,{releaseMessageId:!1,finalize:!1});if(this.#r.hasSeen(o))return;await this.#r.markSeen(o),this.#E.lastMessageAt=new Date().toISOString(),this.#E.messagesReceived+=1;let s=qp(t);if(!s){await this.#b(t.message.chat_id,w("\u8BF7\u7528\u6587\u5B57\u56DE\u7B54\u5F53\u524D\u95EE\u9898\u3002"));return}let g=this.#n.get(i);if(!g||g!==e||g.submitting){if(this.#we(t,i)){await this.#b(t.message.chat_id,RC(),{replyTo:t.message.message_id}).catch(()=>{});return}if(r&&(!g||g!==e)){await this.#b(t.message.chat_id,RC(),{replyTo:t.message.message_id});return}return this.#j(t,o,i,a,{releaseMessageId:!1,alreadyRecorded:!0,finalize:!1})}if(g.chatId=t.message.chat_id,g.needsPresentation){try{await this.#he(g)}catch{this.#E.lastError="\u98DE\u4E66\u4EA4\u4E92\u95EE\u9898\u53D1\u9001\u5931\u8D25\u3002",this.#O.error?.("[dsh-feishu] failed to retry an interaction question"),g.interaction.reconnect?.()}return}g.questions[g.index]&&await this.#Ue(g,s,{chatId:t.message.chat_id,messageId:o})}async#Ue(t,o,{chatId:i,messageId:e}={}){let a=t.questions[t.index];if(!a)return;if(t.chatId=i??t.chatId,t.answers.push(bi(a,o)),t.index+=1,t.index{});return}t.submitting=!1,t.answers.pop(),t.index-=1,this.#E.lastError="\u56DE\u7B54\u63D0\u4EA4\u5931\u8D25\u3002",this.#O.error?.("[dsh-feishu] failed to answer a Harness interaction"),i&&await this.#b(i,w("\u56DE\u7B54\u63D0\u4EA4\u5931\u8D25\uFF0C\u8BF7\u91CD\u65B0\u53D1\u9001\u5F53\u524D\u95EE\u9898\u7684\u7B54\u6848\u3002")).catch(()=>{})}}async#CA(t,{key:o,actor:i,chatId:e,requiresMention:a,replyToMessageId:A}){if(await this.#D.handleRequested(t,{key:o,actor:i,requiresMention:a,send:I=>this.#b(e,I,{replyTo:A}),...this.#Z?{render:async I=>{await this.#z(e,g5({toolName:I.toolCall?.name??I.payload?.toolName,operation:Kve(I.toolCall),reason:I.payload?.reason,approvalId:I.approvalId}),{key:o,replyTo:A}).catch(async()=>{await this.#b(e,I.text,{replyTo:A})})}}:{}})||t?.kind!=="question")return;let r=t?.payload?.questions,s=typeof t?.interactionId=="string"?t.interactionId:t?.rpcId;if(typeof t.rpcId!="string"||typeof s!="string"||typeof t.sessionId!="string"||!Array.isArray(r)||r.length===0||r.some(I=>!Oi(I))){this.#O.warn?.("[dsh-feishu] ignored an invalid Harness question interaction");return}if(t.recovered===!0){await t.respond({ok:!1,error:{code:"cancelled",message:"Feishu safely cancelled an interaction left by an earlier client.",details:{}}}),await this.#b(e,w("\u68C0\u6D4B\u5230\u8FD9\u4E2A Session \u4E2D\u9057\u7559\u7684\u5F85\u56DE\u7B54\u95EE\u9898\uFF0C\u5DF2\u5B89\u5168\u53D6\u6D88\u5E76\u7EE7\u7EED\u5904\u7406\u4F60\u521A\u624D\u7684\u6D88\u606F\u3002"),{replyTo:A}).catch(()=>{});return}let g=this.#n.get(o);if(g?.interactionId===s){g.interaction=t,g.needsPresentation&&await this.#he(g);return}if(this.#s.has(s))return;if(g){await t.respond({ok:!1,error:{code:"cancelled",message:"Feishu is already handling another user interaction.",details:{}}});return}let c={kind:"question",interactionId:s,sessionId:t.sessionId,interaction:t,key:o,actor:i,requiresMention:a,questions:r,answers:[],index:0,chatId:e,replyToMessageId:A,queue:null,claimedReplyMessageId:null,submitting:!1,needsPresentation:!0,questionMessageIds:new Set,inactive:!1};this.#n.set(o,c),this.#s.set(c.interactionId,o),await this.#he(c)}async#BA(t){if(await this.#D.handleResolved(t))return;let o=t?.interactionId;if(t?.kind!=="question"||typeof o!="string")return;let i=this.#s.get(o);if(!i)return;let e=this.#n.get(i);e&&this.#Ie(i,e),this.#Oe(i,o)}async#he(t){let o=t.questions[t.index];if(!o)return;let i=Array.isArray(o?.options)?o.options:[],e=this.#Z&&i.length>0&&o.multiSelect!==!0,a;e?a=await this.#z(t.chatId,c5({interactionId:t.interactionId,header:o.header,question:o.question,detail:o.detail,options:i,index:t.index,total:t.questions.length}),{key:t.key,replyTo:t.replyToMessageId}).catch(async()=>this.#b(t.chatId,ci(o,t.index,t.questions.length,{requiresMention:t.requiresMention}),{replyTo:t.replyToMessageId})):a=await this.#b(t.chatId,ci(o,t.index,t.questions.length,{requiresMention:t.requiresMention}),{replyTo:t.replyToMessageId}),a&&(t.questionMessageIds.add(a),t.inactive&&this.#Ie(t.key,t)),t.needsPresentation=!1}#Ie(t,o){let i=Date.now()+mve;for(let e of o.questionMessageIds??[])this.#d.set(e,{key:t,expiresAt:i})}#we(t,o){let i=Date.now();for(let[e,a]of this.#d)a.expiresAt<=i&&this.#d.delete(e);for(let e of[t?.message?.parent_id,t?.message?.root_id]){let a=this.#d.get(e);if(a?.key===o&&a.expiresAt>i)return!0}return!1}async#De(t,o){this.#r.hasSeen(o)||(await this.#r.markSeen(o),this.#E.lastMessageAt=new Date().toISOString(),this.#E.messagesReceived+=1,await this.#b(t.message.chat_id,RC(),{replyTo:t.message.message_id}).catch(()=>{}))}#He(t,o){let i=this.#n.get(t);return!i||o!==void 0&&i.interactionId!==o?null:(this.#n.delete(t),this.#s.delete(i.interactionId),i.inactive=!0,i)}#Oe(t,o){return this.#He(t,o)!==null}async#be(t){let o=this.#He(t);if(!(!o||o.kind!=="question")){this.#Ie(t,o);try{await o.interaction.respond({ok:!1,error:{code:"cancelled",message:"The Feishu interaction ended before the user answered.",details:{}}},{signal:AbortSignal.timeout(5e3)})}catch(i){i?.code!=="interaction-not-pending"&&this.#O.warn?.("[dsh-feishu] failed to cancel a pending Harness interaction")}}}#fA(t){return t.type==="text"&&t.text?t.text:t.type==="tool"?t.name==="web_search"?w("_\u6B63\u5728\u641C\u7D22\u7F51\u7EDC\u5E76\u6574\u7406\u4FE1\u606F\u2026_"):w("_\u6B63\u5728\u4F7F\u7528 {name}\u2026_",{name:t.name||w("\u5DE5\u5177")}):w("_{text}_",{text:t.text||w("\u6B63\u5728\u5904\u7406\u2026")})}#QA(t){return ay({adapter:this.#A,target:t,reactions:{processing:"OnIt",success:"DONE",error:"ERROR"},status:this.#E,logger:this.#O,label:"feishu"})}#Ke(t,o){o.clear()}#ie(t,o,i){i==="ERROR"?o.error():o.success()}async#b(t,o,{replyTo:i}={}){let e=JSON.stringify({text:o});if(i)try{let A=await this.#e.im.v1.message.reply({path:{message_id:i},data:{msg_type:"text",content:e}});if(A?.code&&A.code!==0)throw new Error(`Feishu reply failed: ${A.msg||A.code}`);return lA(A?.data?.message_id)}catch(A){this.#O.warn?.("[dsh-feishu] threaded reply failed; falling back to a plain message:",A?.message??String(A))}let a=await this.#e.im.v1.message.create({params:{receive_id_type:"chat_id"},data:{receive_id:t,msg_type:"text",content:e}});if(a?.code&&a.code!==0)throw new Error(`Feishu send failed: ${a.msg||a.code}`);return lA(a?.data?.message_id)}};import{createHash as Zve}from"node:crypto";var y5="stream_md",Xve="\u5DF2\u8FDE\u63A5 DeepSeek Harness\uFF0C\u6B63\u5728\u601D\u8003\u2026",LC=28e3,DS=12e4,m5=new Map([[99991672,["artifact-permission-required","Feishu file delivery requires the im:resource permission."]],[234006,["artifact-too-large","The result file exceeds Feishu's size limit."]],[234010,["artifact-empty","Feishu does not accept empty files."]],[230017,["artifact-provider-rejected","Feishu rejected the uploaded file ownership."]],[230020,["artifact-rate-limited","Feishu temporarily rate-limited file delivery."]],[230049,["artifact-delivery-uncertain","Feishu could not confirm the file message result."]],[230055,["artifact-provider-rejected","Feishu rejected the file message type."]]]);function yl(n,t){if(t?.code&&t.code!==0)throw new Error(`${n} failed: ${t.msg||t.code}`);return t}function S5(n){let t=[n],o=new Set,i;for(;t.length>0;){let e=t.shift();if(!e||o.has(e))continue;if(typeof e=="object"&&o.add(e),Array.isArray(e)){t.push(...e);continue}let a=Number(e?.code);if(Number.isFinite(a)&&a!==0){if(m5.has(a))return a;i??=a}t.push(e?.response?.data,e?.data,e?.error,e?.cause)}return i}function FC(n,t,o,{uncertain:i=!1}={}){let e=o==null?void 0:Number(o),a=Number.isFinite(e)&&e!==0?e:S5(t),A=Number.isFinite(a)?["artifact-provider-rejected",`Feishu rejected file ${n}.`]:i?["artifact-delivery-uncertain","Feishu could not confirm the file message result."]:["artifact-provider-failed",`Feishu file ${n} failed.`],[r,s]=m5.get(a)??A,g=new Error(s,{cause:t});return g.code=r,Number.isFinite(a)&&(g.providerCode=a),g}function f5(n,t){if(!Number.isInteger(n)||n<1||n>DS)throw new TypeError(`${t} must be an integer between 1 and ${DS}`);return n}function FP(n){return n?.reason??new DOMException("The operation was aborted","AbortError")}function $ve(n){let t=new Error(`Feishu file ${n} timed out.`);return t.code="provider-timeout",t}function Q5(n,{signal:t,timeoutMs:o,stage:i}){t?.throwIfAborted();let e=new AbortController,a=t?AbortSignal.any([t,e.signal]):e.signal;return new Promise((A,r)=>{let s=!1,g=(d,B)=>{s||(s=!0,clearTimeout(I),a.removeEventListener("abort",c),d(B))},c=()=>g(r,t?.aborted?FP(t):$ve(i)),I=setTimeout(()=>e.abort(),o);a.addEventListener("abort",c,{once:!0}),Promise.resolve().then(()=>n(a)).then(d=>g(A,d),d=>g(r,d)),a.aborted&&c()})}function ePe(n,t,o){let i=`${n.deliveryKey}\0${t}`;return`dshim_${Zve("sha256").update(o==="file"?i:`${i}\0${o}`).digest("hex").slice(0,40)}`}function APe(n){let t=String(n??"").replace(/\s+/g," ").trim();return t.length<=50?t:`${LP(t,49)}\u2026`}function LP(n,t){let o=Math.min(n.length,t),i=n.charCodeAt(o-1),e=n.charCodeAt(o);return i>=55296&&i<=56319&&e>=56320&&e<=57343&&(o-=1),n.slice(0,o)}function GP(n){if(n.length<=LC)return n;let t=` ${w("\u5185\u5BB9\u8F83\u957F\uFF0C\u751F\u6210\u5B8C\u6210\u540E\u5C06\u5206\u6BB5\u53D1\u9001\u5B8C\u6574\u56DE\u7B54\u3002")}`;return LP(n,LC-t.length)+t}function tPe(n){let t=[],o=n;for(;o.length>LC;){let i=LP(o,LC),e=i.lastIndexOf(` `)+1;e(A&&(a=await this.#o(t,i.replyTo),e.push(a),A=!1),a),g={get messageId(){return a.messageId},rotate:async()=>{if(!A){A=!0;try{await this.#i(a,`${GP(r)} diff --git a/src/channels/feishu/bridge.mjs b/src/channels/feishu/bridge.mjs index 1df9dbc..b0f076a 100644 --- a/src/channels/feishu/bridge.mjs +++ b/src/channels/feishu/bridge.mjs @@ -1677,10 +1677,15 @@ export class FeishuHarnessBridge { } const conversationType = route.key.startsWith('p2p:') ? 'direct' : route.key.startsWith('group:') ? 'group' : null; + const isInteractionResponse = resolvedAction.startsWith('approve:') + || resolvedAction.startsWith('reject:') + || resolvedAction.startsWith('answer:'); const access = evaluateInboundAccess(this.#accessPolicy, { conversationType, senderIds: operatorOpenId, - isCommand: true, + // These buttons are the card equivalent of an ordinary approval or + // question reply. Every other card action remains command-gated. + isCommand: !isInteractionResponse, }); if (!access.allowed) { if (access.reason === 'command-not-allowed') { @@ -3878,7 +3883,7 @@ export class FeishuHarnessBridge { // Fall back to the plain-text question if the card cannot be sent. // If the text send also fails, let the error propagate so the pending // question is not marked as presented and the existing retry logic runs. - await this.#send( + return this.#send( pending.chatId, harnessQuestionText(question, pending.index, pending.questions.length, { requiresMention: pending.requiresMention, diff --git a/test/channels/feishu/bridge.test.mjs b/test/channels/feishu/bridge.test.mjs index 8973aa6..8d9ff66 100644 --- a/test/channels/feishu/bridge.test.mjs +++ b/test/channels/feishu/bridge.test.mjs @@ -1966,7 +1966,9 @@ test('an approval is presented as an interactive card with approve and reject bu }, state: fixture.state, status: bridgeStatus(), - allowedSenderOpenIds: new Set(['ou_user']), + accessPolicy: directAccessPolicy({ + users: [{ id: 'ou_user', canExecuteCommands: false }], + }), }); const turn = bridge.accept(event('approval-card-start', '执行危险命令')); @@ -1987,6 +1989,10 @@ test('an approval is presented as an interactive card with approve and reject bu assert.equal(decisions.length, 0); await bridge.onCardAction(cardActionEvent('om_card_1', 'approve:approval-card-id', 'ou_user')); + await eventually( + () => decisions.length === 1, + 'an allowed ordinary-message user could not approve their own interaction', + ); await turn; assert.deepEqual(decisions, [{ ok: true, @@ -2039,7 +2045,9 @@ test('approval card reject button submits a rejected outcome', async () => { }, state: fixture.state, status: bridgeStatus(), - allowedSenderOpenIds: new Set(['ou_user']), + accessPolicy: directAccessPolicy({ + users: [{ id: 'ou_user', canExecuteCommands: false }], + }), }); const turn = bridge.accept(event('approval-reject-start', '覆盖文件')); @@ -2049,6 +2057,10 @@ test('approval card reject button submits a rejected outcome', async () => { ); await bridge.onCardAction(cardActionEvent('om_card_1', 'reject:approval-reject-id', 'ou_user')); + await eventually( + () => decisions.length === 1, + 'an allowed ordinary-message user could not reject their own interaction', + ); await turn; assert.deepEqual(decisions, [{ ok: true, @@ -2064,6 +2076,7 @@ test('a single-choice question is presented as a card with option buttons by def const fixture = stateFixture([['p2p:ou_user', 'session-question-card']]); const sent = []; const submitted = deferred(); + let submittedResult; const bridge = new FeishuHarnessBridge({ client: cardClient(async (outgoing) => sent.push(outgoing)), harness: { @@ -2086,6 +2099,7 @@ test('a single-choice question is presented as a card with option buttons by def }], }, respond: async (result) => { + submittedResult = result; submitted.resolve(result); return { accepted: true }; }, @@ -2096,7 +2110,9 @@ test('a single-choice question is presented as a card with option buttons by def }, state: fixture.state, status: bridgeStatus(), - allowedSenderOpenIds: new Set(['ou_user']), + accessPolicy: directAccessPolicy({ + users: [{ id: 'ou_user', canExecuteCommands: false }], + }), }); const turn = bridge.accept(event('question-card-start', '请先调用 ask_user_question')); @@ -2115,6 +2131,10 @@ test('a single-choice question is presented as a card with option buttons by def await bridge.onCardAction( cardActionEvent('om_card_1', 'answer:question-card-id:0:测试环境', 'ou_user'), ); + await eventually( + () => submittedResult !== undefined, + 'an allowed ordinary-message user could not answer their own interaction', + ); await turn; assert.deepEqual(await submitted.promise, { ok: true, @@ -2203,6 +2223,86 @@ test('an interaction card falls back to plain text when the card send fails', as }]); }); +test('a resolved question remembers the text fallback message after its card send fails', async () => { + const fixture = stateFixture([['p2p:ou_user', 'session-question-fallback-thread']]); + const sent = []; + const asked = []; + const resolved = deferred(); + let resolveInteraction; + const client = { + im: { v1: { message: { + create: async (request) => { + if (request.data.msg_type === 'interactive') throw new Error('card disabled'); + const messageId = `om_question_fallback_${sent.length + 1}`; + sent.push({ + messageId, + text: JSON.parse(request.data.content).text, + }); + return { code: 0, data: { message_id: messageId } }; + }, + } } }, + }; + const bridge = new FeishuHarnessBridge({ + client, + harness: { + sessionExists: async () => true, + createSession: async () => assert.fail('the existing session should be reused'), + ask: async (sessionId, text, options) => { + asked.push(text); + if (asked.length > 1) return 'late fallback reply was handled as a new prompt'; + await options.onInteraction({ + kind: 'question', + interactionId: 'question-fallback-thread', + rpcId: 'question-fallback-thread', + sessionId, + payload: { + type: 'question/requested', + sessionId, + questions: [{ + id: 'environment', + question: '请选择环境', + options: [{ label: '测试环境' }, { label: '生产环境' }], + }], + }, + respond: async () => ({ accepted: true }), + }); + resolveInteraction = async () => { + await options.onInteractionResolved({ + kind: 'question', + interactionId: 'question-fallback-thread', + sessionId, + outcome: 'answered', + }); + resolved.resolve(); + }; + await resolved.promise; + return '已由其他客户端完成'; + }, + }, + state: fixture.state, + status: bridgeStatus(), + allowedSenderOpenIds: new Set(['ou_user']), + }); + + const first = bridge.accept(event('question-fallback-start', '启动卡片降级问题')); + await eventually(() => ( + typeof resolveInteraction === 'function' + && sent.some(({ text }) => text.includes('请选择环境')) + )); + const questionMessageId = sent.find(({ text }) => text.includes('请选择环境')).messageId; + await resolveInteraction(); + await first; + + await bridge.accept(event('question-fallback-late', '1', { + root_id: 'question-fallback-start', + parent_id: questionMessageId, + thread_id: 'omt_question_fallback', + })); + + assert.deepEqual(asked, ['启动卡片降级问题']); + assert.equal(sent.some(({ text }) => text.includes('已在其他客户端处理')), true); +}); + test('a different allowed group member cannot approve or answer an interaction card', async () => { const fixture = stateFixture([['group:oc_group', 'session-group-actor']]); const sent = [];