From 6b176d90532925452aa413c116b60d78985cb6d8 Mon Sep 17 00:00:00 2001 From: "jonah.fu" Date: Sun, 23 Aug 2026 22:26:49 +0800 Subject: [PATCH] fix(qq): stop flashing the status frame during tool runs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The shared Harness client emits a '正在整理结果…' status update after every tool/result. Pushing each one into the QQ stream message made multi-tool turns repeatedly flash that notice instead of showing real progress. Skip status frames in the QQ onUpdate handler so the stream keeps the last meaningful frame (tool name or generated text), which matches the official dsh-qqbot behaviour of not pushing tool-noise. --- lib/index.js | 2 +- src/channels/qq/qq-bridge.mjs | 4 +++- test/channels/qq/bridge.test.mjs | 36 ++++++++++++++++++++++++++++++++ 3 files changed, 40 insertions(+), 2 deletions(-) diff --git a/lib/index.js b/lib/index.js index 38897ff..4d67693 100644 --- a/lib/index.js +++ b/lib/index.js @@ -241,7 +241,7 @@ Generate a new one`,"\u4E8C\u7EF4\u7801\u56FE\u7247\u6B63\u5728\u751F\u6210\u202 `),a=[],A="",t=!1,s=[],g=E=>{if(E.length<=i){if(!A){A=E;return}let f=`${A} ${E}`;f.length>i?(a.push(A),A=E):A=f;return}A&&(a.push(A),A="");let B=E;for(;B.length>i;)a.push(B.slice(0,i)),B=B.slice(i);A=B},c=()=>{if(s.length===0)return;let E=s.join(` `);s=[],g(E)},I=E=>{let B=E;for(;B.length>i;)A&&(a.push(A),A=""),a.push(B.slice(0,i)),B=B.slice(i);g(B)};for(let E of e){if(Kbe.test(E)){c(),!t&&A&&(a.push(A),A=""),t=!t,I(E);continue}if(t){I(E);continue}if(Ybe.test(E)){s.push(E);continue}c(),I(E)}return c(),A&&a.push(A),a}function jbe(){let n=Date.now()%1e8,r=Math.floor(Math.random()*65536);return(n^r)%65536}async function m8(n,r,o,{logger:i}={}){let e=xbe(o),a=[];for(let A of e){if(typeof n?.send=="function")try{a.push(await n.send({target:r,msgType:2,markdown:{content:A},extra:{msg_seq:jbe()}}));continue}catch(t){i?.warn?.("[dsh-im:qq] markdown delivery failed; retrying as plain text:",t)}a.push(await n.sendText(r,A))}return a}var Py="\u8FD9\u4E2A\u95EE\u9898\u5DF2\u5728\u5176\u4ED6\u5BA2\u6237\u7AEF\u5904\u7406\uFF0C\u65E0\u9700\u518D\u6B21\u56DE\u7B54\u3002",S8=12e4,Jbe=Object.freeze([".myqcloud.com",".qpic.cn",".qq.com",".qq.com.cn",".tencentcos.com",".ugcimg.cn"]),qbe=/\.(?:gif|jpe?g|png|webp)$/i,zbe=["QQ \u673A\u5668\u4EBA\u5DF2\u8FDE\u63A5 DeepSeek Harness\u3002","","\u76F4\u63A5\u53D1\u9001\u6587\u5B57\u6216\u56FE\u7247\u5373\u53EF\u7EE7\u7EED\u5F53\u524D\u4F1A\u8BDD\u3002","/new \u5F00\u542F\u4E00\u4E2A\u5168\u65B0\u4F1A\u8BDD","/compact \u538B\u7F29\u5F53\u524D\u4F1A\u8BDD\u7684\u8F83\u65E9\u4E0A\u4E0B\u6587","/workspace \u5DE5\u4F5C\u533A\u7EDD\u5BF9\u8DEF\u5F84 \u5207\u6362\u5DE5\u4F5C\u533A","/workspacelist \u5217\u51FA\u5DE5\u4F5C\u533A\u7EDD\u5BF9\u8DEF\u5F84","/sessionlist [\u5DE5\u4F5C\u533A\u5E8F\u53F7\u6216\u7EDD\u5BF9\u8DEF\u5F84] \u5217\u51FA\u4F1A\u8BDD ID \u548C\u6807\u9898","/session Session ID \u6216\u5F53\u524D\u5DE5\u4F5C\u533A\u5E8F\u53F7 \u5C06\u5F53\u524D\u804A\u5929\u7ED1\u5B9A\u5230\u6307\u5B9A\u4F1A\u8BDD","/models \u6309\u5E8F\u53F7\u5217\u51FA\u6240\u6709\u53EF\u7528\u6A21\u578B","/model [\u5E8F\u53F7\u6216\u5B8C\u6574\u6A21\u578BID] \u67E5\u770B\u6216\u5207\u6362\u5F53\u524D\u4F1A\u8BDD\u6A21\u578B","\u793A\u4F8B\uFF1A\u5148\u53D1 /models\uFF0C\u518D\u53D1 /model 2","/presetlist \u6309\u5E8F\u53F7\u5217\u51FA\u53EF\u7528 Agent Preset","/preset [\u5E8F\u53F7\u6216\u5B8C\u6574ID] \u67E5\u770B\u6216\u8BBE\u7F6E\u5F53\u524D\u673A\u5668\u4EBA Agent Preset","\u7EAF\u6570\u5B57 ID\uFF1A/preset id:","/preset --default \u8DDF\u968F Host \u9ED8\u8BA4","/stop \u505C\u6B62\u5F53\u524D\u4EFB\u52A1","/steer \u8865\u5145\u6307\u4EE4 \u7EA0\u504F\u5F53\u524D\u4EFB\u52A1","/status \u68C0\u67E5\u8FDE\u63A5\u72B6\u6001","/help \u663E\u793A\u672C\u5E2E\u52A9"].join(` -`);function Vbe(n){return`${n.kind}:${n.kind==="group"?n.groupOpenid:n.senderId}`}function AC(n){return typeof n?.content=="string"?n.content.trim():""}function w8(n){let r=Oi(n?.content_type??n?.contentType);return r?r.split(";",1)[0].trim().toLowerCase():null}function O8(n){return w8(n)?.startsWith("image/")===!0||qbe.test(Oi(n?.filename)??"")}function Gy(n){return Array.isArray(n?.attachments)&&n.attachments.some(O8)}function Wbe(n,{fetchImpl:r=fetch}={}){if(typeof r!="function")throw new TypeError("fetchImpl must be a function");let o=[];for(let i of n?.attachments??[]){if(!O8(i))continue;let e=Oi(i?.url),a=Oi(i?.filename)??void 0,A=w8(i),t=Number(i?.size);o.push({...a?{name:a}:{},...A?.startsWith("image/")?{mediaType:A}:{},...Number.isFinite(t)&&t>=0?{size:t}:{},load:({signal:s,maxBytes:g})=>{if(!e)throw new Error("QQ image attachment has no download URL");return Ui(e,{fetchImpl:r,signal:s,maxBytes:g,allowedHosts:Jbe})}})}return{content:AC(n),images:o}}function Oi(n){return typeof n=="string"&&n.trim()?n.trim():null}function Zbe(n,r){let o=String(n??"\u7ED3\u679C\u6587\u4EF6").replace(/[\r\n]+/g," ").trim()||"\u7ED3\u679C\u6587\u4EF6";if(r?.name==="UploadDailyLimitExceededError")return`\u7ED3\u679C\u6587\u4EF6\u300C${o}\u300D\u5DF2\u751F\u6210\uFF0C\u4F46 QQ \u4ECA\u65E5\u6587\u4EF6\u4E0A\u4F20\u989D\u5EA6\u5DF2\u7528\u5B8C\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5\u3002`;switch(r?.code){case"artifact-delivery-uncertain":return`\u7ED3\u679C\u6587\u4EF6\u300C${o}\u300D\u7684\u53D1\u9001\u7ED3\u679C\u672A\u80FD\u786E\u8BA4\uFF0C\u8BF7\u5148\u68C0\u67E5\u804A\u5929\u5185\u662F\u5426\u5DF2\u6536\u5230\uFF0C\u4E0D\u8981\u7ACB\u5373\u91CD\u8BD5\u3002`;case"artifact-permission-required":return`\u7ED3\u679C\u6587\u4EF6\u300C${o}\u300D\u5DF2\u751F\u6210\uFF0C\u4F46\u5F53\u524D QQ \u673A\u5668\u4EBA\u6CA1\u6709\u6587\u4EF6\u6D88\u606F\u6743\u9650\u3002`;case"artifact-too-large":return`\u7ED3\u679C\u6587\u4EF6\u300C${o}\u300D\u8D85\u8FC7\u5F53\u524D QQ \u673A\u5668\u4EBA\u53EF\u53D1\u9001\u7684\u6587\u4EF6\u5927\u5C0F\uFF0C\u672A\u53D1\u9001\u3002`;case"artifact-empty":return`\u7ED3\u679C\u6587\u4EF6\u300C${o}\u300D\u4E3A\u7A7A\uFF0CQQ \u4E0D\u5141\u8BB8\u53D1\u9001\u7A7A\u6587\u4EF6\u3002`;case"artifact-changed":case"artifact-invalid":case"artifact-unavailable":return`\u7ED3\u679C\u6587\u4EF6\u300C${o}\u300D\u6682\u65F6\u65E0\u6CD5\u8BFB\u53D6\u6216\u51C6\u5907\u53D1\u9001\uFF0C\u8BF7\u786E\u8BA4\u6587\u4EF6\u4ECD\u53EF\u8BBF\u95EE\u540E\u91CD\u8BD5\u3002`;case"artifact-rate-limited":return`\u7ED3\u679C\u6587\u4EF6\u300C${o}\u300D\u6682\u65F6\u88AB QQ \u9650\u6D41\uFF0C\u672A\u80FD\u53D1\u9001\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5\u3002`;case"artifact-provider-rejected":return`\u7ED3\u679C\u6587\u4EF6\u300C${o}\u300D\u5DF2\u751F\u6210\uFF0C\u4F46 QQ \u62D2\u7EDD\u4E86\u8BE5\u6587\u4EF6\u6216\u6587\u4EF6\u6D88\u606F\u3002`;default:return`\u7ED3\u679C\u6587\u4EF6\u300C${o}\u300D\u5DF2\u751F\u6210\uFF0C\u4F46\u6682\u65F6\u672A\u80FD\u901A\u8FC7 QQ \u53D1\u9001\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5\u3002`}}function Xbe(n,r){return typeof n=="string"&&n.trim()?n:r.length>0?"\u7ED3\u679C\u6587\u4EF6\u5DF2\u751F\u6210\u3002":n}function D8(n,{dispatched:r=!1}={}){if(n?.code?.startsWith?.("artifact-")||n?.name==="UploadDailyLimitExceededError")return n;let o=Number(n?.httpStatus),i=new Error("QQ file delivery failed",{cause:n});return o===401||o===403?i.code="artifact-permission-required":o===413?i.code="artifact-too-large":o===429?i.code="artifact-rate-limited":o===400||o===404?i.code="artifact-provider-rejected":i.code=r?"artifact-delivery-uncertain":"artifact-provider-failed",i}function b8(n){return n?.reason instanceof Error?n.reason:new DOMException("The operation was aborted","AbortError")}function $be(n,r){return r?(r.throwIfAborted(),new Promise((o,i)=>{let e=!1,a=(t,s)=>{e||(e=!0,r.removeEventListener("abort",A),t(s))},A=()=>a(i,b8(r));r.addEventListener("abort",A,{once:!0}),Promise.resolve(n).then(t=>a(o,t),t=>a(i,t)),r.aborted&&A()})):n}function eMe(n,r){return r.questions[r.index]&&Oi(n?.senderId)===r.actor&&(n.kind!=="group"||n.rawEventType==="GROUP_AT_MESSAGE_CREATE")&&!Gy(n)&&Oi(AC(n))}function ik(){return{messagesReceived:0,messagesReplied:0,messagesRejected:0,artifactsSent:0,artifactSendErrors:0,lastMessageAt:null,lastReplyAt:null,lastRejectedAt:null,lastError:null}}var Fy=class{#e;#A;#t;#r;#o;#i;#I;#s;#l;#a;#n=new Map;#g=new Map;#c=new Map;#p=new Set;#h=new Set;#d=new Set;#E;constructor({bot:r,ownerUserOpenid:o,harness:i,state:e,status:a=ik(),logger:A=console,replyTimeoutMs:t=6e5,signal:s,fetchImpl:g=fetch,fileUploadTimeoutMs:c=S8}){if(!r||typeof r.sendText!="function")throw new TypeError("QQ bot client is required");if(!o)throw new TypeError("QQ scanner identity is required");if(!i||!e)throw new TypeError("Harness client and state store are required");if(typeof g!="function")throw new TypeError("fetchImpl must be a function");if(!Number.isInteger(c)||c<1)throw new TypeError("fileUploadTimeoutMs must be a positive integer");this.#e=r,this.#A=o,this.#t=i,this.#r=e,this.#o=a,this.#i=A,this.#I=t,this.#s=s,this.#l=g,this.#a=Math.min(c,S8),this.#E=new qa({label:"qq",logger:A})}get status(){return structuredClone(this.#o)}accept(r){if(this.#s?.aborted)return Promise.resolve();let o=Oi(r?.messageId),i=Oi(r?.senderId);if(!o||!i||r?.senderIsBot===!0||!["c2c","group"].includes(r?.kind)||this.#r.hasSeen(o)||this.#p.has(o))return Promise.resolve();let e=Vbe(r);this.#p.add(o),r.kind==="c2c"&&(this.#A==="*"||i===this.#A)&&r.replyTarget?.scope==="c2c"&&Oi(r.replyTarget.targetId)===i&&Li(this.#r,r.replyTarget);let a=this.#g.get(e),A=AC(r),t=hi(A)?xi:Ci(A)?Ji:ui(A)?qi:null,s=this.#A==="*"||i===this.#A,g=r.kind!=="group"||r.rawEventType==="GROUP_AT_MESSAGE_CREATE";if(t&&s&&g){let I;return I=this.#u(r,o,e,A,t).catch(E=>{if(!(E?.code==="turn-stopped"||this.#s?.aborted))return this.#o.lastError=E?.message??String(E),this.#i.error?.("[dsh-im:qq] failed to process a command:",E),this.#e.sendText(r.replyTarget,"\u6D88\u606F\u5904\u7406\u5931\u8D25\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5\u3002").catch(()=>{})}).finally(()=>{this.#p.delete(o),this.#d.delete(I)}),this.#d.add(I),I}let c=this.#E.claimReply({key:e,actor:i,messageId:o,text:Gy(r)?"":AC(r),addressed:r.kind!=="group"||r.rawEventType==="GROUP_AT_MESSAGE_CREATE",hasPendingQuestion:!!a,questionCompletion:a?.submitting||a?.claimedReplyMessageId?a.queue:null,isQuestionPending:()=>this.#g.has(e),send:I=>this.#e.sendText(r.replyTarget,I)});if(c){let I;return I=c.process(async()=>this.#r.hasSeen(o)?!1:(await this.#r.markSeen(o),this.#o.messagesReceived+=1,this.#o.lastMessageAt=new Date().toISOString(),!0)).finally(()=>{this.#p.delete(o),this.#h.delete(I)}),this.#h.add(I),I}if(a&&i!==a.actor)return this.#C(r,o,e);if(a?.submitting||a?.claimedReplyMessageId)return this.#C(r,o,e);if(a){eMe(r,a)&&(a.claimedReplyMessageId=o);let E=(a.queue??Promise.resolve()).catch(()=>{}).then(()=>this.#m(r,o,e,a)).catch(B=>this.#O(r,o,B)).finally(()=>{this.#p.delete(o),a.claimedReplyMessageId===o&&(a.claimedReplyMessageId=null),a.queue===E&&(a.queue=null)});return a.queue=E,E}return this.#C(r,o,e)}#C(r,o,i,{releaseMessageId:e=!0,alreadyRecorded:a=!1}={}){let t=(this.#n.get(i)??Promise.resolve()).catch(()=>{}).then(()=>this.#Q(r,i,{alreadyRecorded:a})).finally(()=>{e&&this.#p.delete(o),this.#n.get(i)===t&&this.#n.delete(i)});return this.#n.set(i,t),t}async waitForIdle(){await Promise.allSettled([...this.#n.values(),...[...this.#g.values()].flatMap(r=>r.queue?[r.queue]:[]),...this.#h,...this.#d])}async#u(r,o,i,e,a){if(this.#s?.throwIfAborted(),this.#r.hasSeen(o))return;await this.#r.markSeen(o),this.#o.messagesReceived+=1,this.#o.lastMessageAt=new Date().toISOString();let A=await a(e,this.#t,this.#r,i,{signal:this.#s,hasImages:Gy(r),pendingInteraction:this.#g.has(i)||this.#E.hasPending(i),control:{owner:this,key:i}});A?.stopped&&await Promise.allSettled([this.#N(i),this.#E.closeRoute(i)]);for(let t of A?.messages??[A?.message])t&&await this.#e.sendText(r.replyTarget,t);this.#o.lastError=null}async#B(r,o,i=[],e=null){if(i.length===0)return{receipt:e,failureNoticeVisible:!1};let a=e?[e]:[],A=!1;for(let t of i){this.#s?.throwIfAborted();try{if(typeof this.#e.sendFile!="function"){let I=new Error("QQ file delivery is unavailable");throw I.code="artifact-provider-unavailable",I}let s=await zi(t,{signal:this.#s});this.#s?.throwIfAborted();let g;try{let I=AbortSignal.timeout(this.#a),E=this.#s?AbortSignal.any([this.#s,I]):I,B=this.#e.sendFile(r,{buffer:s.bytes},{fileName:s.fileName,onProgress:()=>this.#s?.throwIfAborted()});ln(s,B),g=await $be(B,E)}catch(I){throw this.#s?.aborted?b8(this.#s):D8(I,{dispatched:!0})}this.#s?.throwIfAborted();let c=Oi(g?.message?.id);a.push(wt({deliveryId:s.deliveryKey,presentation:"qq-file",providerMessageIds:c?[c]:[],artifacts:[{artifactId:s.artifactId,outcome:"sent"}]})),this.#o.artifactsSent=(this.#o.artifactsSent??0)+1}catch(s){if(this.#s?.aborted)throw s;let g=D8(s);this.#o.artifactSendErrors=(this.#o.artifactSendErrors??0)+1,this.#i.warn?.(`[dsh-im:qq] result file delivery failed (${g?.code??g?.name??"unknown"})`);let c=[];try{let I=await this.#e.sendText(r,Zbe(t?.fileName,g));A=!0,c=ha(I)}catch(I){if(this.#s?.aborted)throw I;this.#i.warn?.("[dsh-im:qq] unable to send the safe result-file failure notice")}a.push(Wi({artifactId:t?.artifactId??"unknown",deliveryId:t?.deliveryKey??t?.artifactId??"unknown",error:g,providerMessageIds:c}))}finally{Vi(t)}}return{receipt:Zi({deliveryId:o,presentation:e?"qq-text-and-files":"qq-files",receipts:a}),failureNoticeVisible:A}}async#Q(r,o,{alreadyRecorded:i=!1}={}){if(this.#s?.aborted)return;let e=Oi(r?.messageId),a=Oi(r?.senderId);if(!e||!a||r.senderIsBot===!0||!["c2c","group"].includes(r.kind))return;if(!i){if(this.#r.hasSeen(e))return;this.#o.messagesReceived+=1,this.#o.lastMessageAt=new Date().toISOString()}if(this.#A!=="*"&&a!==this.#A){this.#o.messagesRejected+=1,this.#o.lastRejectedAt=new Date().toISOString();return}if(r.kind==="group"&&r.rawEventType!=="GROUP_AT_MESSAGE_CREATE")return;let A=r.replyTarget,t=Wbe(r,{fetchImpl:this.#l}),s=t.content,g=pr(t),c=null;try{if(!s&&!g){await this.#e.sendText(A,"\u76EE\u524D\u652F\u6301\u6587\u5B57\u548C\u56FE\u7247\u6D88\u606F\u3002"),await this.#r.markSeen(e);return}let I=s.toLowerCase();if(!g&&I==="/help"){await this.#e.sendText(A,zbe),await this.#r.markSeen(e);return}if(!g&&I==="/status"){await this.#t.ensureRunning({signal:this.#s}),await this.#e.sendText(A,"QQ \u673A\u5668\u4EBA\u4E0E DeepSeek Harness \u8FDE\u63A5\u6B63\u5E38\u3002"),await this.#r.markSeen(e);return}if(!g&&I==="/new"){await this.#r.clearSession(o),await this.#e.sendText(A,"\u5DF2\u5F00\u542F\u65B0\u4F1A\u8BDD\u3002\u8BF7\u53D1\u9001\u4F60\u7684\u95EE\u9898\u3002"),await this.#r.markSeen(e);return}let E=g?null:await ji(s,this.#t,o);if(E){for(let H of E.messages??[E.message])await this.#e.sendText(A,H);await this.#r.markSeen(e);return}let B=g?null:await Yi(s,this.#t,this.#r,o,{signal:this.#s});if(B){await this.#e.sendText(A,B.message),await this.#r.markSeen(e);return}let f=g?await Hi(t,{signal:this.#s}):void 0,Q=!1;if(r.kind==="c2c"&&A?.msgId&&typeof this.#e.openStream=="function")try{c=this.#e.openStream({target:A})}catch(H){this.#i.warn?.("[dsh-im:qq] unable to start a QQ stream; using a text reply:",H)}let y,m=[];try{({answer:y,artifacts:m=[]}=await Va({harness:this.#t,state:this.#r,key:o,...g?{content:f}:{text:s},createOptions:{signal:this.#s},existsOptions:{signal:this.#s},askOptions:{timeoutMs:this.#I,signal:this.#s,control:{owner:this,key:o},onUpdate:c?async H=>{let j=H.type==="text"?H.text:H.type==="tool"?`\u6B63\u5728\u4F7F\u7528${H.name}\u2026`:H.text;j&&await c.update(j)}:void 0,onInteraction:H=>this.#f(H,{key:o,actor:a,target:A,requiresMention:r.kind==="group"}),onInteractionResolved:H=>this.#y(H)}}))}finally{await Promise.allSettled([this.#N(o),this.#E.closeRoute(o)])}this.#s?.throwIfAborted();let N=Xbe(y,m),_=null,v=null;try{if(c)try{await c.update(N),await c.complete(),Q=!0,_=wt({deliveryId:e,presentation:"qq-text",providerMessageIds:ha(c)})}catch(H){c.cancel?.(),this.#i.warn?.("[dsh-im:qq] QQ stream finalization failed; using a text reply:",H)}if(!Q){let H=await m8(this.#e,A,N,{logger:this.#i});_=wt({deliveryId:e,presentation:"qq-text",providerMessageIds:H.flatMap(j=>ha(j))})}}catch(H){v=H,this.#i.warn?.("[dsh-im:qq] final text delivery failed; continuing with result files:",H)}let K=await this.#B(A,e,m,_),x=K.receipt?.artifacts?.some(({outcome:H})=>H==="sent"||H==="unknown");if(v&&!x&&!K.failureNoticeVisible)throw v;return await this.#r.markSeen(e),this.#o.messagesReplied+=1,this.#o.lastReplyAt=new Date().toISOString(),this.#o.lastError=null,K.receipt}catch(I){if(I?.code==="turn-stopped"){if(c){try{await c.cancel?.()}catch(E){this.#i.warn?.("[dsh-im:qq] unable to cancel a stopped QQ stream:",E)}try{await this.#e.sendText(A,"\u5DF2\u505C\u6B62\u3002")}catch(E){this.#i.warn?.("[dsh-im:qq] unable to announce a stopped QQ turn:",E)}}await this.#r.markSeen(e);return}if(c?.cancel?.(),this.#s?.aborted)return;this.#o.lastError=I?.message??String(I),this.#i.error?.("[dsh-im:qq] failed to process an inbound message:",I);try{await this.#e.sendText(A,Ki(I)??"\u6D88\u606F\u5904\u7406\u5931\u8D25\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5\u3002"),await this.#r.markSeen(e)}catch(E){this.#i.error?.("[dsh-im:qq] failed to send the safe error reply:",E)}}}async#m(r,o,i,e){this.#s?.throwIfAborted();let a=this.#g.get(i),A=e.claimedReplyMessageId===o;if(!a||a!==e||a.submitting)return A&&(!a||a!==e)?this.#w(r,o):this.#C(r,o,i,{releaseMessageId:!1});if(this.#r.hasSeen(o)||(await this.#r.markSeen(o),this.#o.messagesReceived+=1,this.#o.lastMessageAt=new Date().toISOString(),r.kind==="group"&&r.rawEventType!=="GROUP_AT_MESSAGE_CREATE"))return;let t=Oi(AC(r));if(!t||Gy(r)){await this.#e.sendText(r.replyTarget,"\u8BF7\u7528\u6587\u5B57\u56DE\u7B54\u5F53\u524D\u95EE\u9898\u3002");return}let s=this.#g.get(i);if(!s||s!==e||s.submitting){if(A&&(!s||s!==e)){await this.#e.sendText(r.replyTarget,Py);return}return this.#C(r,o,i,{releaseMessageId:!1,alreadyRecorded:!0})}if(s.target=r.replyTarget,s.needsPresentation){try{await this.#D(s)}catch{this.#o.lastError="QQ \u4EA4\u4E92\u95EE\u9898\u53D1\u9001\u5931\u8D25\u3002",this.#i.error?.("[dsh-im:qq] failed to retry an interaction question"),s.interaction.reconnect?.();return}let c=this.#g.get(i);if(!c||c!==e||c.submitting){if(A&&(!c||c!==e)){await this.#e.sendText(r.replyTarget,Py).catch(()=>{});return}return this.#C(r,o,i,{releaseMessageId:!1,alreadyRecorded:!0})}}let g=s.questions[s.index];if(g){if(s.answers.push(Ja(g,t)),s.index+=1,s.index{});return}if(this.#g.get(i)!==s)return;s.submitting=!1,s.answers.pop(),s.index-=1,this.#o.lastError="\u56DE\u7B54\u63D0\u4EA4\u5931\u8D25\u3002",this.#i.error?.("[dsh-im:qq] failed to answer a Harness interaction"),await this.#e.sendText(s.target,"\u56DE\u7B54\u63D0\u4EA4\u5931\u8D25\uFF0C\u8BF7\u91CD\u65B0\u53D1\u9001\u5F53\u524D\u95EE\u9898\u7684\u7B54\u6848\u3002").catch(()=>{})}}}async#f(r,{key:o,actor:i,target:e,requiresMention:a}){if(r?.kind==="approval")return this.#E.handleRequested(r,{key:o,actor:i,requiresMention:a,send:c=>this.#e.sendText(e,c)});if(r?.kind!=="question")return;let A=r?.payload?.questions,t=typeof r?.interactionId=="string"?r.interactionId:r?.rpcId;if(typeof r?.rpcId!="string"||typeof t!="string"||typeof r.sessionId!="string"||!Array.isArray(A)||A.length===0||A.some(c=>!xa(c))){this.#i.warn?.("[dsh-im:qq] ignored an invalid Harness question interaction");return}if(r.recovered===!0){await r.respond({ok:!1,error:{code:"cancelled",message:"QQ safely cancelled an interaction left by an earlier client.",details:{}}}),await this.#e.sendText(e,"\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").catch(()=>{});return}let s=this.#g.get(o);if(s?.interactionId===t){s.interaction=r,s.needsPresentation&&await this.#D(s);return}if(this.#c.has(t))return;if(s){await r.respond({ok:!1,error:{code:"cancelled",message:"QQ is already handling another user interaction.",details:{}}});return}let g={kind:"question",interactionId:t,sessionId:r.sessionId,interaction:r,actor:i,requiresMention:a,questions:A,answers:[],index:0,target:e,queue:null,claimedReplyMessageId:null,presentationPromise:null,submitting:!1,needsPresentation:!0};this.#g.set(o,g),this.#c.set(t,o),await this.#D(g)}async#y(r){if(r?.kind==="approval"){await this.#E.handleResolved(r);return}let o=r?.interactionId;if(r?.kind!=="question"||typeof o!="string")return;let i=this.#c.get(o);i&&this.#M(i,o)}#D(r){if(!r.needsPresentation)return Promise.resolve();if(r.presentationPromise)return r.presentationPromise;let o=r.questions[r.index];if(!o)return Promise.resolve();let i=this.#e.sendText(r.target,ja(o,r.index,r.questions.length,{requiresMention:r.requiresMention})).then(()=>{r.needsPresentation=!1}).finally(()=>{r.presentationPromise===i&&(r.presentationPromise=null)});return r.presentationPromise=i,i}async#w(r,o){this.#r.hasSeen(o)||(await this.#r.markSeen(o),this.#o.messagesReceived+=1,this.#o.lastMessageAt=new Date().toISOString(),await this.#e.sendText(r.replyTarget,Py).catch(()=>{}))}#R(r,o){let i=this.#g.get(r);return!i||o!==void 0&&i.interactionId!==o?null:(this.#g.delete(r),this.#c.delete(i.interactionId),i)}#M(r,o){return this.#R(r,o)!==null}async#N(r){let o=this.#R(r);if(!(!o||o.kind!=="question"))try{await o.interaction.respond({ok:!1,error:{code:"cancelled",message:"The QQ interaction ended before the user answered.",details:{}}},{signal:AbortSignal.timeout(5e3)})}catch(i){i?.code!=="interaction-not-pending"&&this.#i.warn?.("[dsh-im:qq] failed to cancel a pending Harness interaction")}}async#O(r,o,i){this.#s?.aborted||(this.#o.lastError=i?.message??String(i),this.#i.error?.("[dsh-im:qq] failed to process an interaction reply:",i),this.#r.hasSeen(o)||await this.#r.markSeen(o).catch(()=>{}),await this.#e.sendText(r.replyTarget,"\u6D88\u606F\u5904\u7406\u5931\u8D25\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5\u3002").catch(()=>{}))}};function rMe(){let n=new Error("QQ WebSocket did not become ready in time");return n.code="connect-timeout",n}function aMe(){return{startedAt:null,ready:!1,qqConnectionState:"idle",harnessReachable:!1,lastCheckedAt:null,lastConnectedAt:null,lastError:null,...ik()}}var tC=class{#e;#A;#t;#r;#o;#i;#I;#s;#l;#a=aMe();#n=null;#g=null;#c=null;#p=null;#h=null;constructor({config:r,appSecret:o,harness:i,state:e,logger:a=console,replyTimeoutMs:A=6e5,connectTimeoutMs:t=2e4,createBot:s=c=>new AMe(c),typingMiddleware:g=tMe}){if(!r||!o||!i||!e)throw new TypeError("QqRuntime requires config, app secret, Harness, and state");this.#e=r,this.#A=o,this.#t=i,this.#r=e,this.#o=a,this.#i=A,this.#I=t,this.#s=s,this.#l=g}get status(){return structuredClone(this.#a)}async sendConnectionTest(r){if(!this.#a.ready||!this.#n)throw ar("QQ\u673A\u5668\u4EBA");let o=typeof this.#e.ownerUserOpenid=="string"?this.#e.ownerUserOpenid.trim():"",i=nd(this.#r),e=i?.scope==="c2c"&&typeof i.targetId=="string"?i.targetId.trim():"",a=e?{scope:"c2c",targetId:e}:o&&o!=="*"?{scope:"c2c",targetId:o}:null;if(!a)throw ar("QQ\u673A\u5668\u4EBA");return await this.#n.sendText(a,r),{sent:!0}}async start(){return this.#a.ready&&this.#n?this.status:this.#h?this.#h:(this.#h=this.#d().finally(()=>{this.#h=null}),this.#h)}async#d(){await this.stop(),this.#a.startedAt=new Date().toISOString(),this.#a.qqConnectionState="connecting",this.#a.lastError=null,await this.#t.ensureRunning(),this.#a.harnessReachable=!0;let r={error:(...E)=>this.#o.error?.(...E),warn:(...E)=>this.#o.warn?.(...E),info:(...E)=>this.#o.info?.(...E),debug:()=>{}},o=this.#s({appId:this.#e.appId,appSecret:this.#A,accountId:this.#e.botId,logger:r,transport:"websocket",tokenPrefetch:"sync"});if(!o||typeof o.start!="function"||typeof o.stop!="function")throw new TypeError("QQ bot factory returned an invalid client");let i=new AbortController;this.#c=i,this.#n=o,this.#g=new Fy({bot:o,ownerUserOpenid:this.#e.ownerUserOpenid,harness:this.#t,state:this.#r,status:this.#a,logger:this.#o,replyTimeoutMs:this.#i,signal:i.signal}),o.use?.(this.#l({keepAlive:!0,predicate:E=>this.#e.ownerUserOpenid==="*"||E?.message?.senderId===this.#e.ownerUserOpenid}));let e,a,A=new Promise((E,B)=>{e=E,a=B}),t=()=>{let E=Date.now();this.#a.ready=!0,this.#a.qqConnectionState="connected",this.#a.lastCheckedAt=E,this.#a.lastConnectedAt=E,this.#a.lastError=null,e()},s=E=>{this.#a.ready?(this.#a.lastError=E?.message??String(E),this.#o.warn?.(`[dsh-im:qq] bot ${this.#e.botId} connection error:`,E)):a(E)},g=(E,B)=>{let f=this.#g?.accept(B);f&&f.catch(Q=>{i.signal.aborted||this.#o.error?.(`[dsh-im:qq] bot ${this.#e.botId} message handling failed:`,Q)})};o.on("ready",t),o.on("resumed",t),o.on("error",s),o.on("message",g);let c=Promise.resolve().then(()=>o.start(i.signal));this.#p=c,c.catch(E=>{i.signal.aborted||(a(E),this.#a.ready=!1,this.#a.qqConnectionState="failed",this.#a.lastError=E?.message??String(E),this.#o.error?.(`[dsh-im:qq] bot ${this.#e.botId} connection stopped:`,E))});let I;try{return await Promise.race([A,new Promise((E,B)=>{I=setTimeout(()=>B(rMe()),this.#I)})]),this.#a.ready=!0,this.#a.qqConnectionState="connected",this.#a.lastCheckedAt=Date.now(),this.#a.lastConnectedAt=Date.now(),this.status}catch(E){throw this.#a.ready=!1,this.#a.qqConnectionState="failed",this.#a.lastError=E?.message??String(E),await this.stop(),E}finally{clearTimeout(I)}}async stop(){let r=this.#n,o=this.#g,i=this.#p;this.#c?.abort(),this.#c=null,this.#n=null,this.#g=null,this.#p=null;try{r?.stop()}catch(e){this.#o.warn?.(`[dsh-im:qq] bot ${this.#e.botId} failed to stop cleanly:`,e)}return await Promise.race([i?.catch(()=>{})??Promise.resolve(),new Promise(e=>setTimeout(e,2e3))]),await o?.waitForIdle(),this.#a.ready=!1,this.#a.qqConnectionState="idle",this.status}};import{startQrConnect as iMe}from"@tencent-connect/qqbot-connector";var Ly=class{#e;#A;constructor({start:r=iMe,source:o="deepseek-harness"}={}){if(typeof r!="function")throw new TypeError("QQ QR connector is required");this.#e=r,this.#A=o}start(r,{signal:o}={}){if(!r||typeof r.onSuccess!="function"||typeof r.onFailure!="function")throw new TypeError("QQ QR callbacks are required");return this.#e(r,{displayQrCodeToConsole:!1,source:this.#A,signal:o})}};import{mkdir as oMe,readFile as nMe,rename as sMe,unlink as gMe,writeFile as cMe}from"node:fs/promises";import{dirname as IMe}from"node:path";var Uy=Object.freeze({version:1,sessions:{},seenMessageIds:[]});function lMe(n){if(!n||typeof n!="object")return structuredClone(Uy);let r={};if(n.sessions&&typeof n.sessions=="object"&&!Array.isArray(n.sessions))for(let[o,i]of Object.entries(n.sessions))typeof o=="string"&&typeof i=="string"&&i&&(r[o]=i);return{version:1,sessions:r,seenMessageIds:Array.isArray(n.seenMessageIds)?n.seenMessageIds.filter(o=>typeof o=="string").slice(-1e3):[]}}var Hy=class{#e;#A=structuredClone(Uy);#t=Promise.resolve();constructor(r){this.#e=r}async load(){try{this.#A=lMe(JSON.parse(await nMe(this.#e,"utf8")))}catch(r){if(r?.code!=="ENOENT")throw r;this.#A=structuredClone(Uy),await this.#r()}return this}sessionFor(r){return this.#A.sessions[r]??null}async setSession(r,o){this.#A.sessions[r]=o,await this.#r()}async clearSession(r){delete this.#A.sessions[r],await this.#r()}async clearSessions(){this.#A.sessions={},await this.#r()}hasSeen(r){return this.#A.seenMessageIds.includes(r)}async markSeen(r){this.hasSeen(r)||(this.#A.seenMessageIds.push(r),this.#A.seenMessageIds.length>1e3&&this.#A.seenMessageIds.splice(0,this.#A.seenMessageIds.length-1e3),await this.#r())}snapshot(){return structuredClone(this.#A)}async remove(){try{await gMe(this.#e)}catch(r){if(r?.code!=="ENOENT")throw r}this.#A=structuredClone(Uy)}async#r(){let r=`${JSON.stringify(this.#A,null,2)} +`);function Vbe(n){return`${n.kind}:${n.kind==="group"?n.groupOpenid:n.senderId}`}function AC(n){return typeof n?.content=="string"?n.content.trim():""}function w8(n){let r=Oi(n?.content_type??n?.contentType);return r?r.split(";",1)[0].trim().toLowerCase():null}function O8(n){return w8(n)?.startsWith("image/")===!0||qbe.test(Oi(n?.filename)??"")}function Gy(n){return Array.isArray(n?.attachments)&&n.attachments.some(O8)}function Wbe(n,{fetchImpl:r=fetch}={}){if(typeof r!="function")throw new TypeError("fetchImpl must be a function");let o=[];for(let i of n?.attachments??[]){if(!O8(i))continue;let e=Oi(i?.url),a=Oi(i?.filename)??void 0,A=w8(i),t=Number(i?.size);o.push({...a?{name:a}:{},...A?.startsWith("image/")?{mediaType:A}:{},...Number.isFinite(t)&&t>=0?{size:t}:{},load:({signal:s,maxBytes:g})=>{if(!e)throw new Error("QQ image attachment has no download URL");return Ui(e,{fetchImpl:r,signal:s,maxBytes:g,allowedHosts:Jbe})}})}return{content:AC(n),images:o}}function Oi(n){return typeof n=="string"&&n.trim()?n.trim():null}function Zbe(n,r){let o=String(n??"\u7ED3\u679C\u6587\u4EF6").replace(/[\r\n]+/g," ").trim()||"\u7ED3\u679C\u6587\u4EF6";if(r?.name==="UploadDailyLimitExceededError")return`\u7ED3\u679C\u6587\u4EF6\u300C${o}\u300D\u5DF2\u751F\u6210\uFF0C\u4F46 QQ \u4ECA\u65E5\u6587\u4EF6\u4E0A\u4F20\u989D\u5EA6\u5DF2\u7528\u5B8C\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5\u3002`;switch(r?.code){case"artifact-delivery-uncertain":return`\u7ED3\u679C\u6587\u4EF6\u300C${o}\u300D\u7684\u53D1\u9001\u7ED3\u679C\u672A\u80FD\u786E\u8BA4\uFF0C\u8BF7\u5148\u68C0\u67E5\u804A\u5929\u5185\u662F\u5426\u5DF2\u6536\u5230\uFF0C\u4E0D\u8981\u7ACB\u5373\u91CD\u8BD5\u3002`;case"artifact-permission-required":return`\u7ED3\u679C\u6587\u4EF6\u300C${o}\u300D\u5DF2\u751F\u6210\uFF0C\u4F46\u5F53\u524D QQ \u673A\u5668\u4EBA\u6CA1\u6709\u6587\u4EF6\u6D88\u606F\u6743\u9650\u3002`;case"artifact-too-large":return`\u7ED3\u679C\u6587\u4EF6\u300C${o}\u300D\u8D85\u8FC7\u5F53\u524D QQ \u673A\u5668\u4EBA\u53EF\u53D1\u9001\u7684\u6587\u4EF6\u5927\u5C0F\uFF0C\u672A\u53D1\u9001\u3002`;case"artifact-empty":return`\u7ED3\u679C\u6587\u4EF6\u300C${o}\u300D\u4E3A\u7A7A\uFF0CQQ \u4E0D\u5141\u8BB8\u53D1\u9001\u7A7A\u6587\u4EF6\u3002`;case"artifact-changed":case"artifact-invalid":case"artifact-unavailable":return`\u7ED3\u679C\u6587\u4EF6\u300C${o}\u300D\u6682\u65F6\u65E0\u6CD5\u8BFB\u53D6\u6216\u51C6\u5907\u53D1\u9001\uFF0C\u8BF7\u786E\u8BA4\u6587\u4EF6\u4ECD\u53EF\u8BBF\u95EE\u540E\u91CD\u8BD5\u3002`;case"artifact-rate-limited":return`\u7ED3\u679C\u6587\u4EF6\u300C${o}\u300D\u6682\u65F6\u88AB QQ \u9650\u6D41\uFF0C\u672A\u80FD\u53D1\u9001\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5\u3002`;case"artifact-provider-rejected":return`\u7ED3\u679C\u6587\u4EF6\u300C${o}\u300D\u5DF2\u751F\u6210\uFF0C\u4F46 QQ \u62D2\u7EDD\u4E86\u8BE5\u6587\u4EF6\u6216\u6587\u4EF6\u6D88\u606F\u3002`;default:return`\u7ED3\u679C\u6587\u4EF6\u300C${o}\u300D\u5DF2\u751F\u6210\uFF0C\u4F46\u6682\u65F6\u672A\u80FD\u901A\u8FC7 QQ \u53D1\u9001\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5\u3002`}}function Xbe(n,r){return typeof n=="string"&&n.trim()?n:r.length>0?"\u7ED3\u679C\u6587\u4EF6\u5DF2\u751F\u6210\u3002":n}function D8(n,{dispatched:r=!1}={}){if(n?.code?.startsWith?.("artifact-")||n?.name==="UploadDailyLimitExceededError")return n;let o=Number(n?.httpStatus),i=new Error("QQ file delivery failed",{cause:n});return o===401||o===403?i.code="artifact-permission-required":o===413?i.code="artifact-too-large":o===429?i.code="artifact-rate-limited":o===400||o===404?i.code="artifact-provider-rejected":i.code=r?"artifact-delivery-uncertain":"artifact-provider-failed",i}function b8(n){return n?.reason instanceof Error?n.reason:new DOMException("The operation was aborted","AbortError")}function $be(n,r){return r?(r.throwIfAborted(),new Promise((o,i)=>{let e=!1,a=(t,s)=>{e||(e=!0,r.removeEventListener("abort",A),t(s))},A=()=>a(i,b8(r));r.addEventListener("abort",A,{once:!0}),Promise.resolve(n).then(t=>a(o,t),t=>a(i,t)),r.aborted&&A()})):n}function eMe(n,r){return r.questions[r.index]&&Oi(n?.senderId)===r.actor&&(n.kind!=="group"||n.rawEventType==="GROUP_AT_MESSAGE_CREATE")&&!Gy(n)&&Oi(AC(n))}function ik(){return{messagesReceived:0,messagesReplied:0,messagesRejected:0,artifactsSent:0,artifactSendErrors:0,lastMessageAt:null,lastReplyAt:null,lastRejectedAt:null,lastError:null}}var Fy=class{#e;#A;#t;#r;#o;#i;#I;#s;#l;#a;#n=new Map;#g=new Map;#c=new Map;#p=new Set;#h=new Set;#d=new Set;#E;constructor({bot:r,ownerUserOpenid:o,harness:i,state:e,status:a=ik(),logger:A=console,replyTimeoutMs:t=6e5,signal:s,fetchImpl:g=fetch,fileUploadTimeoutMs:c=S8}){if(!r||typeof r.sendText!="function")throw new TypeError("QQ bot client is required");if(!o)throw new TypeError("QQ scanner identity is required");if(!i||!e)throw new TypeError("Harness client and state store are required");if(typeof g!="function")throw new TypeError("fetchImpl must be a function");if(!Number.isInteger(c)||c<1)throw new TypeError("fileUploadTimeoutMs must be a positive integer");this.#e=r,this.#A=o,this.#t=i,this.#r=e,this.#o=a,this.#i=A,this.#I=t,this.#s=s,this.#l=g,this.#a=Math.min(c,S8),this.#E=new qa({label:"qq",logger:A})}get status(){return structuredClone(this.#o)}accept(r){if(this.#s?.aborted)return Promise.resolve();let o=Oi(r?.messageId),i=Oi(r?.senderId);if(!o||!i||r?.senderIsBot===!0||!["c2c","group"].includes(r?.kind)||this.#r.hasSeen(o)||this.#p.has(o))return Promise.resolve();let e=Vbe(r);this.#p.add(o),r.kind==="c2c"&&(this.#A==="*"||i===this.#A)&&r.replyTarget?.scope==="c2c"&&Oi(r.replyTarget.targetId)===i&&Li(this.#r,r.replyTarget);let a=this.#g.get(e),A=AC(r),t=hi(A)?xi:Ci(A)?Ji:ui(A)?qi:null,s=this.#A==="*"||i===this.#A,g=r.kind!=="group"||r.rawEventType==="GROUP_AT_MESSAGE_CREATE";if(t&&s&&g){let I;return I=this.#u(r,o,e,A,t).catch(E=>{if(!(E?.code==="turn-stopped"||this.#s?.aborted))return this.#o.lastError=E?.message??String(E),this.#i.error?.("[dsh-im:qq] failed to process a command:",E),this.#e.sendText(r.replyTarget,"\u6D88\u606F\u5904\u7406\u5931\u8D25\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5\u3002").catch(()=>{})}).finally(()=>{this.#p.delete(o),this.#d.delete(I)}),this.#d.add(I),I}let c=this.#E.claimReply({key:e,actor:i,messageId:o,text:Gy(r)?"":AC(r),addressed:r.kind!=="group"||r.rawEventType==="GROUP_AT_MESSAGE_CREATE",hasPendingQuestion:!!a,questionCompletion:a?.submitting||a?.claimedReplyMessageId?a.queue:null,isQuestionPending:()=>this.#g.has(e),send:I=>this.#e.sendText(r.replyTarget,I)});if(c){let I;return I=c.process(async()=>this.#r.hasSeen(o)?!1:(await this.#r.markSeen(o),this.#o.messagesReceived+=1,this.#o.lastMessageAt=new Date().toISOString(),!0)).finally(()=>{this.#p.delete(o),this.#h.delete(I)}),this.#h.add(I),I}if(a&&i!==a.actor)return this.#C(r,o,e);if(a?.submitting||a?.claimedReplyMessageId)return this.#C(r,o,e);if(a){eMe(r,a)&&(a.claimedReplyMessageId=o);let E=(a.queue??Promise.resolve()).catch(()=>{}).then(()=>this.#m(r,o,e,a)).catch(B=>this.#O(r,o,B)).finally(()=>{this.#p.delete(o),a.claimedReplyMessageId===o&&(a.claimedReplyMessageId=null),a.queue===E&&(a.queue=null)});return a.queue=E,E}return this.#C(r,o,e)}#C(r,o,i,{releaseMessageId:e=!0,alreadyRecorded:a=!1}={}){let t=(this.#n.get(i)??Promise.resolve()).catch(()=>{}).then(()=>this.#Q(r,i,{alreadyRecorded:a})).finally(()=>{e&&this.#p.delete(o),this.#n.get(i)===t&&this.#n.delete(i)});return this.#n.set(i,t),t}async waitForIdle(){await Promise.allSettled([...this.#n.values(),...[...this.#g.values()].flatMap(r=>r.queue?[r.queue]:[]),...this.#h,...this.#d])}async#u(r,o,i,e,a){if(this.#s?.throwIfAborted(),this.#r.hasSeen(o))return;await this.#r.markSeen(o),this.#o.messagesReceived+=1,this.#o.lastMessageAt=new Date().toISOString();let A=await a(e,this.#t,this.#r,i,{signal:this.#s,hasImages:Gy(r),pendingInteraction:this.#g.has(i)||this.#E.hasPending(i),control:{owner:this,key:i}});A?.stopped&&await Promise.allSettled([this.#N(i),this.#E.closeRoute(i)]);for(let t of A?.messages??[A?.message])t&&await this.#e.sendText(r.replyTarget,t);this.#o.lastError=null}async#B(r,o,i=[],e=null){if(i.length===0)return{receipt:e,failureNoticeVisible:!1};let a=e?[e]:[],A=!1;for(let t of i){this.#s?.throwIfAborted();try{if(typeof this.#e.sendFile!="function"){let I=new Error("QQ file delivery is unavailable");throw I.code="artifact-provider-unavailable",I}let s=await zi(t,{signal:this.#s});this.#s?.throwIfAborted();let g;try{let I=AbortSignal.timeout(this.#a),E=this.#s?AbortSignal.any([this.#s,I]):I,B=this.#e.sendFile(r,{buffer:s.bytes},{fileName:s.fileName,onProgress:()=>this.#s?.throwIfAborted()});ln(s,B),g=await $be(B,E)}catch(I){throw this.#s?.aborted?b8(this.#s):D8(I,{dispatched:!0})}this.#s?.throwIfAborted();let c=Oi(g?.message?.id);a.push(wt({deliveryId:s.deliveryKey,presentation:"qq-file",providerMessageIds:c?[c]:[],artifacts:[{artifactId:s.artifactId,outcome:"sent"}]})),this.#o.artifactsSent=(this.#o.artifactsSent??0)+1}catch(s){if(this.#s?.aborted)throw s;let g=D8(s);this.#o.artifactSendErrors=(this.#o.artifactSendErrors??0)+1,this.#i.warn?.(`[dsh-im:qq] result file delivery failed (${g?.code??g?.name??"unknown"})`);let c=[];try{let I=await this.#e.sendText(r,Zbe(t?.fileName,g));A=!0,c=ha(I)}catch(I){if(this.#s?.aborted)throw I;this.#i.warn?.("[dsh-im:qq] unable to send the safe result-file failure notice")}a.push(Wi({artifactId:t?.artifactId??"unknown",deliveryId:t?.deliveryKey??t?.artifactId??"unknown",error:g,providerMessageIds:c}))}finally{Vi(t)}}return{receipt:Zi({deliveryId:o,presentation:e?"qq-text-and-files":"qq-files",receipts:a}),failureNoticeVisible:A}}async#Q(r,o,{alreadyRecorded:i=!1}={}){if(this.#s?.aborted)return;let e=Oi(r?.messageId),a=Oi(r?.senderId);if(!e||!a||r.senderIsBot===!0||!["c2c","group"].includes(r.kind))return;if(!i){if(this.#r.hasSeen(e))return;this.#o.messagesReceived+=1,this.#o.lastMessageAt=new Date().toISOString()}if(this.#A!=="*"&&a!==this.#A){this.#o.messagesRejected+=1,this.#o.lastRejectedAt=new Date().toISOString();return}if(r.kind==="group"&&r.rawEventType!=="GROUP_AT_MESSAGE_CREATE")return;let A=r.replyTarget,t=Wbe(r,{fetchImpl:this.#l}),s=t.content,g=pr(t),c=null;try{if(!s&&!g){await this.#e.sendText(A,"\u76EE\u524D\u652F\u6301\u6587\u5B57\u548C\u56FE\u7247\u6D88\u606F\u3002"),await this.#r.markSeen(e);return}let I=s.toLowerCase();if(!g&&I==="/help"){await this.#e.sendText(A,zbe),await this.#r.markSeen(e);return}if(!g&&I==="/status"){await this.#t.ensureRunning({signal:this.#s}),await this.#e.sendText(A,"QQ \u673A\u5668\u4EBA\u4E0E DeepSeek Harness \u8FDE\u63A5\u6B63\u5E38\u3002"),await this.#r.markSeen(e);return}if(!g&&I==="/new"){await this.#r.clearSession(o),await this.#e.sendText(A,"\u5DF2\u5F00\u542F\u65B0\u4F1A\u8BDD\u3002\u8BF7\u53D1\u9001\u4F60\u7684\u95EE\u9898\u3002"),await this.#r.markSeen(e);return}let E=g?null:await ji(s,this.#t,o);if(E){for(let H of E.messages??[E.message])await this.#e.sendText(A,H);await this.#r.markSeen(e);return}let B=g?null:await Yi(s,this.#t,this.#r,o,{signal:this.#s});if(B){await this.#e.sendText(A,B.message),await this.#r.markSeen(e);return}let f=g?await Hi(t,{signal:this.#s}):void 0,Q=!1;if(r.kind==="c2c"&&A?.msgId&&typeof this.#e.openStream=="function")try{c=this.#e.openStream({target:A})}catch(H){this.#i.warn?.("[dsh-im:qq] unable to start a QQ stream; using a text reply:",H)}let y,m=[];try{({answer:y,artifacts:m=[]}=await Va({harness:this.#t,state:this.#r,key:o,...g?{content:f}:{text:s},createOptions:{signal:this.#s},existsOptions:{signal:this.#s},askOptions:{timeoutMs:this.#I,signal:this.#s,control:{owner:this,key:o},onUpdate:c?async H=>{let j=H.type==="text"?H.text:H.type==="tool"?`\u6B63\u5728\u4F7F\u7528${H.name}\u2026`:null;j&&await c.update(j)}:void 0,onInteraction:H=>this.#f(H,{key:o,actor:a,target:A,requiresMention:r.kind==="group"}),onInteractionResolved:H=>this.#y(H)}}))}finally{await Promise.allSettled([this.#N(o),this.#E.closeRoute(o)])}this.#s?.throwIfAborted();let N=Xbe(y,m),_=null,v=null;try{if(c)try{await c.update(N),await c.complete(),Q=!0,_=wt({deliveryId:e,presentation:"qq-text",providerMessageIds:ha(c)})}catch(H){c.cancel?.(),this.#i.warn?.("[dsh-im:qq] QQ stream finalization failed; using a text reply:",H)}if(!Q){let H=await m8(this.#e,A,N,{logger:this.#i});_=wt({deliveryId:e,presentation:"qq-text",providerMessageIds:H.flatMap(j=>ha(j))})}}catch(H){v=H,this.#i.warn?.("[dsh-im:qq] final text delivery failed; continuing with result files:",H)}let K=await this.#B(A,e,m,_),x=K.receipt?.artifacts?.some(({outcome:H})=>H==="sent"||H==="unknown");if(v&&!x&&!K.failureNoticeVisible)throw v;return await this.#r.markSeen(e),this.#o.messagesReplied+=1,this.#o.lastReplyAt=new Date().toISOString(),this.#o.lastError=null,K.receipt}catch(I){if(I?.code==="turn-stopped"){if(c){try{await c.cancel?.()}catch(E){this.#i.warn?.("[dsh-im:qq] unable to cancel a stopped QQ stream:",E)}try{await this.#e.sendText(A,"\u5DF2\u505C\u6B62\u3002")}catch(E){this.#i.warn?.("[dsh-im:qq] unable to announce a stopped QQ turn:",E)}}await this.#r.markSeen(e);return}if(c?.cancel?.(),this.#s?.aborted)return;this.#o.lastError=I?.message??String(I),this.#i.error?.("[dsh-im:qq] failed to process an inbound message:",I);try{await this.#e.sendText(A,Ki(I)??"\u6D88\u606F\u5904\u7406\u5931\u8D25\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5\u3002"),await this.#r.markSeen(e)}catch(E){this.#i.error?.("[dsh-im:qq] failed to send the safe error reply:",E)}}}async#m(r,o,i,e){this.#s?.throwIfAborted();let a=this.#g.get(i),A=e.claimedReplyMessageId===o;if(!a||a!==e||a.submitting)return A&&(!a||a!==e)?this.#w(r,o):this.#C(r,o,i,{releaseMessageId:!1});if(this.#r.hasSeen(o)||(await this.#r.markSeen(o),this.#o.messagesReceived+=1,this.#o.lastMessageAt=new Date().toISOString(),r.kind==="group"&&r.rawEventType!=="GROUP_AT_MESSAGE_CREATE"))return;let t=Oi(AC(r));if(!t||Gy(r)){await this.#e.sendText(r.replyTarget,"\u8BF7\u7528\u6587\u5B57\u56DE\u7B54\u5F53\u524D\u95EE\u9898\u3002");return}let s=this.#g.get(i);if(!s||s!==e||s.submitting){if(A&&(!s||s!==e)){await this.#e.sendText(r.replyTarget,Py);return}return this.#C(r,o,i,{releaseMessageId:!1,alreadyRecorded:!0})}if(s.target=r.replyTarget,s.needsPresentation){try{await this.#D(s)}catch{this.#o.lastError="QQ \u4EA4\u4E92\u95EE\u9898\u53D1\u9001\u5931\u8D25\u3002",this.#i.error?.("[dsh-im:qq] failed to retry an interaction question"),s.interaction.reconnect?.();return}let c=this.#g.get(i);if(!c||c!==e||c.submitting){if(A&&(!c||c!==e)){await this.#e.sendText(r.replyTarget,Py).catch(()=>{});return}return this.#C(r,o,i,{releaseMessageId:!1,alreadyRecorded:!0})}}let g=s.questions[s.index];if(g){if(s.answers.push(Ja(g,t)),s.index+=1,s.index{});return}if(this.#g.get(i)!==s)return;s.submitting=!1,s.answers.pop(),s.index-=1,this.#o.lastError="\u56DE\u7B54\u63D0\u4EA4\u5931\u8D25\u3002",this.#i.error?.("[dsh-im:qq] failed to answer a Harness interaction"),await this.#e.sendText(s.target,"\u56DE\u7B54\u63D0\u4EA4\u5931\u8D25\uFF0C\u8BF7\u91CD\u65B0\u53D1\u9001\u5F53\u524D\u95EE\u9898\u7684\u7B54\u6848\u3002").catch(()=>{})}}}async#f(r,{key:o,actor:i,target:e,requiresMention:a}){if(r?.kind==="approval")return this.#E.handleRequested(r,{key:o,actor:i,requiresMention:a,send:c=>this.#e.sendText(e,c)});if(r?.kind!=="question")return;let A=r?.payload?.questions,t=typeof r?.interactionId=="string"?r.interactionId:r?.rpcId;if(typeof r?.rpcId!="string"||typeof t!="string"||typeof r.sessionId!="string"||!Array.isArray(A)||A.length===0||A.some(c=>!xa(c))){this.#i.warn?.("[dsh-im:qq] ignored an invalid Harness question interaction");return}if(r.recovered===!0){await r.respond({ok:!1,error:{code:"cancelled",message:"QQ safely cancelled an interaction left by an earlier client.",details:{}}}),await this.#e.sendText(e,"\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").catch(()=>{});return}let s=this.#g.get(o);if(s?.interactionId===t){s.interaction=r,s.needsPresentation&&await this.#D(s);return}if(this.#c.has(t))return;if(s){await r.respond({ok:!1,error:{code:"cancelled",message:"QQ is already handling another user interaction.",details:{}}});return}let g={kind:"question",interactionId:t,sessionId:r.sessionId,interaction:r,actor:i,requiresMention:a,questions:A,answers:[],index:0,target:e,queue:null,claimedReplyMessageId:null,presentationPromise:null,submitting:!1,needsPresentation:!0};this.#g.set(o,g),this.#c.set(t,o),await this.#D(g)}async#y(r){if(r?.kind==="approval"){await this.#E.handleResolved(r);return}let o=r?.interactionId;if(r?.kind!=="question"||typeof o!="string")return;let i=this.#c.get(o);i&&this.#M(i,o)}#D(r){if(!r.needsPresentation)return Promise.resolve();if(r.presentationPromise)return r.presentationPromise;let o=r.questions[r.index];if(!o)return Promise.resolve();let i=this.#e.sendText(r.target,ja(o,r.index,r.questions.length,{requiresMention:r.requiresMention})).then(()=>{r.needsPresentation=!1}).finally(()=>{r.presentationPromise===i&&(r.presentationPromise=null)});return r.presentationPromise=i,i}async#w(r,o){this.#r.hasSeen(o)||(await this.#r.markSeen(o),this.#o.messagesReceived+=1,this.#o.lastMessageAt=new Date().toISOString(),await this.#e.sendText(r.replyTarget,Py).catch(()=>{}))}#R(r,o){let i=this.#g.get(r);return!i||o!==void 0&&i.interactionId!==o?null:(this.#g.delete(r),this.#c.delete(i.interactionId),i)}#M(r,o){return this.#R(r,o)!==null}async#N(r){let o=this.#R(r);if(!(!o||o.kind!=="question"))try{await o.interaction.respond({ok:!1,error:{code:"cancelled",message:"The QQ interaction ended before the user answered.",details:{}}},{signal:AbortSignal.timeout(5e3)})}catch(i){i?.code!=="interaction-not-pending"&&this.#i.warn?.("[dsh-im:qq] failed to cancel a pending Harness interaction")}}async#O(r,o,i){this.#s?.aborted||(this.#o.lastError=i?.message??String(i),this.#i.error?.("[dsh-im:qq] failed to process an interaction reply:",i),this.#r.hasSeen(o)||await this.#r.markSeen(o).catch(()=>{}),await this.#e.sendText(r.replyTarget,"\u6D88\u606F\u5904\u7406\u5931\u8D25\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5\u3002").catch(()=>{}))}};function rMe(){let n=new Error("QQ WebSocket did not become ready in time");return n.code="connect-timeout",n}function aMe(){return{startedAt:null,ready:!1,qqConnectionState:"idle",harnessReachable:!1,lastCheckedAt:null,lastConnectedAt:null,lastError:null,...ik()}}var tC=class{#e;#A;#t;#r;#o;#i;#I;#s;#l;#a=aMe();#n=null;#g=null;#c=null;#p=null;#h=null;constructor({config:r,appSecret:o,harness:i,state:e,logger:a=console,replyTimeoutMs:A=6e5,connectTimeoutMs:t=2e4,createBot:s=c=>new AMe(c),typingMiddleware:g=tMe}){if(!r||!o||!i||!e)throw new TypeError("QqRuntime requires config, app secret, Harness, and state");this.#e=r,this.#A=o,this.#t=i,this.#r=e,this.#o=a,this.#i=A,this.#I=t,this.#s=s,this.#l=g}get status(){return structuredClone(this.#a)}async sendConnectionTest(r){if(!this.#a.ready||!this.#n)throw ar("QQ\u673A\u5668\u4EBA");let o=typeof this.#e.ownerUserOpenid=="string"?this.#e.ownerUserOpenid.trim():"",i=nd(this.#r),e=i?.scope==="c2c"&&typeof i.targetId=="string"?i.targetId.trim():"",a=e?{scope:"c2c",targetId:e}:o&&o!=="*"?{scope:"c2c",targetId:o}:null;if(!a)throw ar("QQ\u673A\u5668\u4EBA");return await this.#n.sendText(a,r),{sent:!0}}async start(){return this.#a.ready&&this.#n?this.status:this.#h?this.#h:(this.#h=this.#d().finally(()=>{this.#h=null}),this.#h)}async#d(){await this.stop(),this.#a.startedAt=new Date().toISOString(),this.#a.qqConnectionState="connecting",this.#a.lastError=null,await this.#t.ensureRunning(),this.#a.harnessReachable=!0;let r={error:(...E)=>this.#o.error?.(...E),warn:(...E)=>this.#o.warn?.(...E),info:(...E)=>this.#o.info?.(...E),debug:()=>{}},o=this.#s({appId:this.#e.appId,appSecret:this.#A,accountId:this.#e.botId,logger:r,transport:"websocket",tokenPrefetch:"sync"});if(!o||typeof o.start!="function"||typeof o.stop!="function")throw new TypeError("QQ bot factory returned an invalid client");let i=new AbortController;this.#c=i,this.#n=o,this.#g=new Fy({bot:o,ownerUserOpenid:this.#e.ownerUserOpenid,harness:this.#t,state:this.#r,status:this.#a,logger:this.#o,replyTimeoutMs:this.#i,signal:i.signal}),o.use?.(this.#l({keepAlive:!0,predicate:E=>this.#e.ownerUserOpenid==="*"||E?.message?.senderId===this.#e.ownerUserOpenid}));let e,a,A=new Promise((E,B)=>{e=E,a=B}),t=()=>{let E=Date.now();this.#a.ready=!0,this.#a.qqConnectionState="connected",this.#a.lastCheckedAt=E,this.#a.lastConnectedAt=E,this.#a.lastError=null,e()},s=E=>{this.#a.ready?(this.#a.lastError=E?.message??String(E),this.#o.warn?.(`[dsh-im:qq] bot ${this.#e.botId} connection error:`,E)):a(E)},g=(E,B)=>{let f=this.#g?.accept(B);f&&f.catch(Q=>{i.signal.aborted||this.#o.error?.(`[dsh-im:qq] bot ${this.#e.botId} message handling failed:`,Q)})};o.on("ready",t),o.on("resumed",t),o.on("error",s),o.on("message",g);let c=Promise.resolve().then(()=>o.start(i.signal));this.#p=c,c.catch(E=>{i.signal.aborted||(a(E),this.#a.ready=!1,this.#a.qqConnectionState="failed",this.#a.lastError=E?.message??String(E),this.#o.error?.(`[dsh-im:qq] bot ${this.#e.botId} connection stopped:`,E))});let I;try{return await Promise.race([A,new Promise((E,B)=>{I=setTimeout(()=>B(rMe()),this.#I)})]),this.#a.ready=!0,this.#a.qqConnectionState="connected",this.#a.lastCheckedAt=Date.now(),this.#a.lastConnectedAt=Date.now(),this.status}catch(E){throw this.#a.ready=!1,this.#a.qqConnectionState="failed",this.#a.lastError=E?.message??String(E),await this.stop(),E}finally{clearTimeout(I)}}async stop(){let r=this.#n,o=this.#g,i=this.#p;this.#c?.abort(),this.#c=null,this.#n=null,this.#g=null,this.#p=null;try{r?.stop()}catch(e){this.#o.warn?.(`[dsh-im:qq] bot ${this.#e.botId} failed to stop cleanly:`,e)}return await Promise.race([i?.catch(()=>{})??Promise.resolve(),new Promise(e=>setTimeout(e,2e3))]),await o?.waitForIdle(),this.#a.ready=!1,this.#a.qqConnectionState="idle",this.status}};import{startQrConnect as iMe}from"@tencent-connect/qqbot-connector";var Ly=class{#e;#A;constructor({start:r=iMe,source:o="deepseek-harness"}={}){if(typeof r!="function")throw new TypeError("QQ QR connector is required");this.#e=r,this.#A=o}start(r,{signal:o}={}){if(!r||typeof r.onSuccess!="function"||typeof r.onFailure!="function")throw new TypeError("QQ QR callbacks are required");return this.#e(r,{displayQrCodeToConsole:!1,source:this.#A,signal:o})}};import{mkdir as oMe,readFile as nMe,rename as sMe,unlink as gMe,writeFile as cMe}from"node:fs/promises";import{dirname as IMe}from"node:path";var Uy=Object.freeze({version:1,sessions:{},seenMessageIds:[]});function lMe(n){if(!n||typeof n!="object")return structuredClone(Uy);let r={};if(n.sessions&&typeof n.sessions=="object"&&!Array.isArray(n.sessions))for(let[o,i]of Object.entries(n.sessions))typeof o=="string"&&typeof i=="string"&&i&&(r[o]=i);return{version:1,sessions:r,seenMessageIds:Array.isArray(n.seenMessageIds)?n.seenMessageIds.filter(o=>typeof o=="string").slice(-1e3):[]}}var Hy=class{#e;#A=structuredClone(Uy);#t=Promise.resolve();constructor(r){this.#e=r}async load(){try{this.#A=lMe(JSON.parse(await nMe(this.#e,"utf8")))}catch(r){if(r?.code!=="ENOENT")throw r;this.#A=structuredClone(Uy),await this.#r()}return this}sessionFor(r){return this.#A.sessions[r]??null}async setSession(r,o){this.#A.sessions[r]=o,await this.#r()}async clearSession(r){delete this.#A.sessions[r],await this.#r()}async clearSessions(){this.#A.sessions={},await this.#r()}hasSeen(r){return this.#A.seenMessageIds.includes(r)}async markSeen(r){this.hasSeen(r)||(this.#A.seenMessageIds.push(r),this.#A.seenMessageIds.length>1e3&&this.#A.seenMessageIds.splice(0,this.#A.seenMessageIds.length-1e3),await this.#r())}snapshot(){return structuredClone(this.#A)}async remove(){try{await gMe(this.#e)}catch(r){if(r?.code!=="ENOENT")throw r}this.#A=structuredClone(Uy)}async#r(){let r=`${JSON.stringify(this.#A,null,2)} `,o=this.#t.then(async()=>{await oMe(IMe(this.#e),{recursive:!0,mode:448});let i=`${this.#e}.tmp`;await cMe(i,r,{encoding:"utf8",mode:384}),await sMe(i,this.#e)});this.#t=o.then(()=>{},()=>{}),await o}};var M8=Object.freeze([250,1e3,3e3,5e3,1e4,3e4]);function pMe(n){if(!Array.isArray(n)||n.length===0)return[...M8];let r=n.filter(o=>Number.isFinite(o)&&o>=0);return r.length>0?r:[...M8]}var Ky=class{#e;#A;#t;#r;#o;#i;#I;#s=null;#l=null;#a=0;#n=!1;#g=!1;#c;#p;constructor({controller:r,harness:o,logger:i=console,retryDelaysMs:e,healthyIntervalMs:a=15e3,setTimeoutImpl:A=setTimeout,clearTimeoutImpl:t=clearTimeout}){if(!r||typeof r.initialize!="function"||typeof r.status!="function")throw new TypeError("ConnectionSupervisor requires a controller");if(!o||typeof o.ensureRunning!="function")throw new TypeError("ConnectionSupervisor requires a Harness client");this.#e=r,this.#A=o,this.#t=i,this.#r=pMe(e),this.#o=Number.isFinite(a)&&a>=0?a:15e3,this.#i=A,this.#I=t,this.#c=new Promise(s=>{this.#p=s})}get ready(){return this.#c}start(){return this.#g||this.#n?this:(this.#g=!0,this.#h(0),this)}async close(){this.#n||(this.#n=!0,this.#s!==null&&this.#I(this.#s),this.#s=null,await this.#l?.catch(()=>{}),this.#p?.(null),this.#p=null)}#h(r){this.#n||(this.#s=this.#i(()=>{this.#s=null,this.#d()},r),this.#s?.unref?.())}async#d(){if(this.#n||this.#l)return;let r=this.#E();this.#l=r;try{await r}finally{this.#l===r&&(this.#l=null)}}async#E(){try{if(await this.#A.ensureRunning(),this.#n)return;let r=await this.#e.initialize();if(this.#n)return;this.#p?.(r),this.#p=null;let{configured:o,connected:i}=r.totals;if(i65535)throw new Error("dsh-im QQ requires an initialized DSH webServer port");return new URL(`http://127.0.0.1:${o}`)}function CMe(n){let r=sI(n.dshHome??process.env.DSH_HOME??rC(dMe(),".dsh")),o=sI(n.dataDir??rC(r,"integrations","dsh-qq"));return{config:sI(n.configPath??rC(o,"config.json")),bots:sI(n.botsDir??rC(o,"bots")),workspaces:sI(n.workspacesPath??rC(o,"workspaces.json"))}}async function nk(n,r={},o={}){if(!n?.credentials)throw new TypeError("dsh-im QQ requires ctx.credentials");if(!n?.webServer)throw new TypeError("dsh-im QQ requires ctx.webServer");let i=o.ConfigStore??Ty,e=o.StateStore??Hy,a=o.HarnessClient??ky,A=o.Controller??eC,t=o.Runtime??tC,s=o.QrAuth??Ly,g=o.createConnectionSupervisor??ok,c=typeof n.logger=="function"?n.logger("dsh-im:qq"):n.logger??console,I=()=>da(n),E=CMe(r),B=await new i(E.config).load(),f=sI(r.workspace??process.cwd()),Q=o.WorkspaceStore??Hr,y=o.workspaces??await new Q(E.workspaces,{defaultWorkspace:f}).load(),m=B.list();await y.reconcile(m.map(M=>M.botId)),await Promise.all(m.map(M=>y.ensure(M.botId,{defaultAgentPreset:r.agentPreset})));let N=typeof B.remove=="function"?Ca(B,{workspaces:y}):B,_=o.qrAuth??new s({source:r.qrSource??"deepseek-harness"}),v=new Map,K=M=>sI(E.bots,M,"state.json"),x=async M=>{let G=v.get(M);return G||(G=await new e(K(M)).load(),v.set(M,G)),G},H=fa(n,o.commandExecutor),{controlExecutor:j,sessionMaintenanceExecutor:q}=Qa(n,{controlExecutor:o.controlExecutor,sessionMaintenanceExecutor:o.sessionMaintenanceExecutor}),Y=new a({baseUrl:hMe(n.webServer,r.harnessBaseUrl),workspace:f,autostart:!1,dshBin:r.dshBin??"dsh",...H?{commandExecutor:H}:{},...j?{controlExecutor:j}:{},...q?{sessionMaintenanceExecutor:q}:{}}),k=new A({qrAuth:_,credentials:n.credentials,configStore:N,logger:c,createRuntime:async({botId:M,config:G,appSecret:ee})=>{let Ce=await x(M);await y.ensure(M,{defaultAgentPreset:r.agentPreset});let de=ua(Y,{botId:M,workspaces:y,state:Ce,agentPresetCatalog:I});return new t({config:G,appSecret:ee,harness:de.harness,state:de.state,replyTimeoutMs:r.replyTimeoutMs??6e5,connectTimeoutMs:r.connectTimeoutMs??2e4,logger:{error:(..._e)=>c.error?.(`[${M}]`,..._e),warn:(..._e)=>c.warn?.(`[${M}]`,..._e),info:(..._e)=>c.info?.(`[${M}]`,..._e),debug:(..._e)=>c.debug?.(`[${M}]`,..._e)}})},deleteState:async({botId:M})=>{let G=v.get(M);if(v.delete(M),G&&typeof G.remove=="function")await G.remove();else try{await EMe(K(M))}catch(ee){if(ee?.code!=="ENOENT")throw ee}}}),W=Ba(k,{workspaces:y,stateFor:x,agentPresetCatalog:I}),L=g({controller:W,harness:Y,logger:c,retryDelaysMs:r.retryDelaysMs,healthyIntervalMs:r.healthyIntervalMs}).start();return{controller:W,ready:L.ready,async close(){await L.close(),await W.close(),Y.stopManagedProcess()}}}import uMe from"qrcode";var N8="/qq",wr=Object.freeze({status:"connection.status",beginProvisioning:"provision.begin",pollProvisioning:"provision.poll",cancelProvisioning:"provision.cancel",bindCredentials:"bot.bind-credentials",reconnectBot:"bot.reconnect",deleteBot:"bot.delete",setWorkspace:Za,setAgentPreset:Xa}),T8=Object.freeze(Object.values(wr)),BMe=new Set(["appSecret","app_secret","secretRef","ownerUserOpenid","userOpenid","verificationUrl"]);function ck(n){return n!==null&&typeof n=="object"&&!Array.isArray(n)}function Cp(n,r){return ck(n)&&Object.keys(n).every(o=>r.includes(o))}function sk(n){return typeof n=="string"&&/^[A-Za-z0-9_-]{1,128}$/.test(n)}function _8(n,r){return typeof n=="string"&&n.trim().length>0&&n.length<=r}function fMe(n,r){return ck(r)?n===wr.status?Cp(r,[])?null:"connection.status does not accept fields.":n===wr.beginProvisioning?Cp(r,["locale"])&&(r.locale===void 0||r.locale==="zh-CN")?null:"provision.begin received unsupported fields.":[wr.pollProvisioning,wr.cancelProvisioning].includes(n)?Cp(r,["attemptId"])&&sk(r.attemptId)?null:`${n} requires an attemptId.`:n===wr.bindCredentials?Cp(r,["appId","appSecret"])&&_8(r.appId,256)&&_8(r.appSecret,1024)?null:"bot.bind-credentials requires AppID and AppSecret.":n===wr.reconnectBot?Cp(r,["botId","sendTest"])&&sk(r.botId)&&(r.sendTest===void 0||r.sendTest===!0)?null:"bot.reconnect requires a botId and accepts only sendTest=true.":n===wr.deleteBot?Cp(r,["botId","confirm"])&&sk(r.botId)&&r.confirm===!0?null:"bot.delete requires a botId and confirm=true.":n===wr.setWorkspace?ya(r)?null:"\u8BF7\u8F93\u5165\u5DE5\u4F5C\u533A\u7EDD\u5BF9\u8DEF\u5F84\u3002":n===wr.setAgentPreset?Sa(r)?null:"\u8BF7\u9009\u62E9 Agent Preset\u3002":"Unknown QQ endpoint.":"Payload must be an object."}function Bp(n){if(Array.isArray(n))return n.map(Bp);if(!ck(n))return n;let r={};for(let[o,i]of Object.entries(n))BMe.has(o)||(r[o]=Bp(i));return r}async function QMe(n){return uMe.toDataURL(n,{type:"image/png",errorCorrectionLevel:"M",margin:2,width:320})}async function gk(n,r){return!n||typeof n.verificationUrl!="string"?Bp(n):Bp({...n,qrCodeDataUrl:await r(n.verificationUrl)})}async function up(n,r){let o=structuredClone(n);return o?.provisioning&&(o.provisioning=await gk(o.provisioning,r)),Bp(o)}function R8(n,{encodeQr:r=QMe}={}){for(let e of["status","startProvisioning","registrationStatus","cancelProvisioning","bindCredentials","reconnectBot","deleteBot"])if(typeof n?.[e]!="function")throw new TypeError(`A complete QQ controller is required (${e})`);let o=new Map,i=e=>{let a=o.get(e);return a||(o.size>=16&&o.delete(o.keys().next().value),a=Promise.resolve().then(()=>r(e)),o.set(e,a)),a};return async(e,a,A)=>{if(A?.aborted)return{ok:!1,error:{code:"cancelled",message:"The request was cancelled."}};if(!T8.includes(e))return{ok:!1,error:{code:"bad-request",message:"Unknown QQ endpoint."}};let t=fMe(e,a);if(t)return{ok:!1,error:{code:"bad-request",message:t}};try{let s;if(e===wr.status)s=await up(await n.status(),i);else if(e===wr.beginProvisioning)s=await gk(await n.startProvisioning(),i);else if(e===wr.pollProvisioning){let g=await n.registrationStatus(a.attemptId);if(!g)return{ok:!1,error:{code:"bad-request",message:"The provisioning attempt no longer exists."}};s=await gk(g,i)}else if(e===wr.cancelProvisioning)s=Bp(await n.cancelProvisioning(a.attemptId));else if(e===wr.bindCredentials)s=await up(await n.bindCredentials(a),i);else if(e===wr.reconnectBot){let g=await n.reconnectBot(a.botId);if(A?.aborted)return{ok:!1,error:{code:"cancelled",message:"The request was cancelled."}};let c;if(a.sendTest===!0)if(!(g?.bots?.some(E=>E?.botId===a.botId&&E?.connected===!0)===!0)||typeof n.sendConnectionTest!="function")c=Vt(ar("QQ\u673A\u5668\u4EBA"));else{let E=null;try{await n.sendConnectionTest(a.botId)}catch(B){E=B}c=Vt(E)}s=await up({...g,...c?{testMessage:c}:{}},i)}else if(e===wr.setWorkspace){if(typeof n.updateWorkspace!="function")throw new Error("Workspace update is unavailable");s=await up(await n.updateWorkspace(a.botId,a.workspace),i)}else if(e===wr.setAgentPreset){if(typeof n.updateAgentPreset!="function")throw new Error("Agent preset update is unavailable");s=await up(await n.updateAgentPreset(a.botId,a.agentPreset),i)}else s=await up(await n.deleteBot(a.botId),i);return A?.aborted?{ok:!1,error:{code:"cancelled",message:"The request was cancelled."}}:{ok:!0,value:s}}catch(s){let g=ma(s);return A?.aborted?{ok:!1,error:{code:"cancelled",message:"The request was cancelled."}}:{ok:!1,error:g??{code:"qq-operation-failed",message:"QQ \u64CD\u4F5C\u5931\u8D25\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5\u3002"}}}}}function Yy(n,r,o,i){if(!n?.connection?.rpc||typeof n.connection.rpc.handle!="function")throw new TypeError("DSH Host Connection RPC is required");return n.connection.rpc.handle(N8,R8(r,o),{authority:hr(i)})}async function k8(n,r={}){if(r?.controller)return Yy(n,r.controller,r.rpcOptions,r.rpcAuthority);let o=await nk(n,r,r.internals),i=Yy(n,o.controller,r.rpcOptions,r.rpcAuthority);return n.effect(()=>async()=>o.close(),"dsh-im: close QQ bot connections"),i}import{unlink as t_e}from"node:fs/promises";import{resolve as j8}from"node:path";import{createHash as yMe}from"node:crypto";import{mkdir as mMe,readFile as SMe,rename as DMe,unlink as wMe,writeFile as OMe}from"node:fs/promises";import{dirname as bMe}from"node:path";var Ik=Object.freeze({version:1,bots:Object.freeze([])}),v8=/^slack_[a-f0-9]{24}$/,MMe=/^DSH_SLACK_BOT_TOKEN_[A-F0-9]{24}$/,_Me=/^DSH_SLACK_APP_TOKEN_[A-F0-9]{24}$/;function go(n){return typeof n=="string"&&n.trim()?n.trim():null}function lk(n){let r=go(n);if(!r)throw new TypeError("platformId is required");let o=yMe("sha256").update(r).digest("hex").slice(0,24),i=o.toUpperCase();return{botId:`slack_${o}`,botTokenRef:`DSH_SLACK_BOT_TOKEN_${i}`,appTokenRef:`DSH_SLACK_APP_TOKEN_${i}`}}function pk(n){let r=go(n)??"",[o,i]=r.split(":");return o&&i?`${o.slice(0,5)}\u2022\u2022\u2022 \xB7 ${i.slice(0,5)}\u2022\u2022\u2022`:r?`${r.slice(0,6)}\u2022\u2022\u2022\u2022`:"Slack\u673A\u5668\u4EBA"}var xy=class{#e;#A=Ik;#t=Promise.resolve();constructor(r){this.#e=r}async load(){try{let r=this.#o(JSON.parse(await SMe(this.#e,"utf8")));if(!r)throw new Error("dsh-im Slack config contains invalid bot data");this.#A=r}catch(r){if(r?.code!=="ENOENT")throw r;this.#A=Ik}return this}list(){return structuredClone(this.#A.bots)}get(r){let o=this.#A.bots.find(i=>i.botId===r);return o?structuredClone(o):null}getByPlatformId(r){let o=this.#A.bots.find(i=>i.platformId===r);return o?structuredClone(o):null}async save(r){let o=this.#r(r);if(!o)throw new Error("Refusing to persist incomplete Slack bot data");return this.#i(i=>{if(i.find(A=>A.botId!==o.botId&&(A.platformId===o.platformId||A.botTokenRef===o.botTokenRef||A.appTokenRef===o.appTokenRef)))throw new Error("Duplicate Slack bot identity");let a=i.findIndex(A=>A.botId===o.botId);return a===-1?i.push(o):i[a]=o,structuredClone(o)})}async remove(r){if(!v8.test(r))throw new TypeError("Invalid Slack bot id");return this.#i(o=>{let i=o.findIndex(e=>e.botId===r);return i===-1?null:structuredClone(o.splice(i,1)[0])})}async clear(){let r=this.#t.then(async()=>{try{await wMe(this.#e)}catch(o){if(o?.code!=="ENOENT")throw o}this.#A=Ik});this.#t=r.then(()=>{},()=>{}),await r}#r(r){if(!r||typeof r!="object")return null;let o=go(r.botId),i=go(r.platformId),e=go(r.botTokenRef),a=go(r.appTokenRef),A=go(r.name);if(!o||!i||!e||!a||!A||!v8.test(o)||!MMe.test(e)||!_Me.test(a))return null;let t=lk(i);return t.botId!==o||t.botTokenRef!==e||t.appTokenRef!==a?null:Object.freeze({botId:o,platformId:i,botTokenRef:e,appTokenRef:a,name:A,username:go(r.username),teamId:go(r.teamId),teamName:go(r.teamName),createdAt:go(r.createdAt)??new Date().toISOString(),connectedAt:go(r.connectedAt)})}#o(r){if(!r||r.version!==1||!Array.isArray(r.bots))return null;let o=r.bots.map(A=>this.#r(A));if(o.some(A=>A===null))return null;let i=new Set,e=new Set,a=new Set;for(let A of o){if(i.has(A.botId)||e.has(A.platformId)||a.has(A.botTokenRef)||a.has(A.appTokenRef))return null;i.add(A.botId),e.add(A.platformId),a.add(A.botTokenRef),a.add(A.appTokenRef)}return Object.freeze({version:1,bots:Object.freeze(o)})}async#i(r){let o,i=this.#t.then(async()=>{let e=[...this.#A.bots];o=r(e);let a=Object.freeze({version:1,bots:Object.freeze(e)});await mMe(bMe(this.#e),{recursive:!0,mode:448});let A=`${this.#e}.tmp`;await OMe(A,`${JSON.stringify(a,null,2)} `,{encoding:"utf8",mode:384}),await DMe(A,this.#e),this.#A=a});return this.#t=i.then(()=>{},()=>{}),await i,o}};var NMe="https://slack.com/api/",oC="files.slack.com",TMe="slack.com",P8="/files-pri/",RMe=Object.freeze([oC]),kMe="/upload/",vMe=12e4;function On(n){return typeof n=="string"&&n.trim()?n.trim():null}function G8(n,r){let o=AbortSignal.timeout(r);return n?AbortSignal.any([n,o]):o}function aC(n){return n?.reason instanceof Error?n.reason:new DOMException("The operation was aborted","AbortError")}function PMe(n,r){if(!Number.isInteger(n)||n<1)throw new TypeError(`${r} must be a positive integer`);return n}function F8(n,r){return r?.providerCode!==void 0&&(n.providerCode=r.providerCode),Number.isInteger(r?.status)&&(n.status=r.status),n}function gI(n){let r="artifact-provider-failed",o="Slack file upload preparation failed";n?.code==="slack-file_upload_size_restricted"?(r="artifact-too-large",o="Slack rejected the result file because it is too large"):n?.code==="slack-missing-scope"?(r="artifact-permission-required",o="Slack file delivery requires the files:write scope"):n?.code?.startsWith?.("slack-")&&(r="artifact-provider-rejected",o="Slack rejected file upload preparation");let i=new Error(o,{cause:n});return i.code=r,F8(i,n)}function GMe(n){let r=new Error("Slack file completion result is uncertain",{cause:n});return r.code="artifact-delivery-uncertain",F8(r,n)}function FMe(n){return Number.isInteger(n)&&n>=300&&n<400}async function Ek(n){try{await n?.body?.cancel?.()}catch{}}function LMe(n){let r=new URL(n);if(r.protocol!=="https:")throw new Error("Image download URL must use HTTPS");if(r.username||r.password||r.port&&r.port!=="443")throw new Error("Image download URL is not hosted by the messaging platform");if(r.hostname===TMe&&r.pathname.startsWith(P8)&&(r.hostname=oC),r.hostname!==oC||!r.pathname.startsWith(P8))throw new Error("Image download URL is not hosted by the messaging platform");return r.hash="",r}function UMe(n){let r=new URL(n);if(r.protocol!=="https:"||r.username||r.password||r.port&&r.port!=="443"||r.hostname!==oC||!r.pathname.startsWith(kMe))throw new Error("Slack returned an unsafe file upload URL");return r.hash="",r}function HMe(n,r){let o=n?.headers?.get?.("location");if(!o)return null;try{return new URL(o,r)}catch{return null}}function KMe(n){let r=n?.headers?.get?.("x-oauth-scopes");return r==null?null:new Set(r.split(",").map(o=>o.trim()).filter(Boolean))}function YMe(n){return n?.protocol==="https:"&&!n.username&&!n.password&&(!n.port||n.port==="443")&&n.hostname.endsWith(".slack.com")&&n.hostname!==oC&&n.pathname==="/"&&n.searchParams.has("redir")}function xMe(n,r){return new Promise((o,i)=>{if(r?.aborted){i(r.reason??new DOMException("Aborted","AbortError"));return}let e=setTimeout(o,n);e?.unref?.(),r?.addEventListener("abort",()=>{clearTimeout(e),i(r.reason??new DOMException("Aborted","AbortError"))},{once:!0})})}function Mg(n,r){let o=On(n);if(!o||!/^[A-Z][A-Z0-9]{4,30}$/i.test(o))throw new TypeError(`Invalid Slack ${r}`);return o}function fp(n,r){let o=On(n);if(!o)throw new TypeError(`Slack ${r} is required`);return o}function iC(n,{trim:r=!0}={}){let o=typeof n=="string"?n:"",i=r?o.trim():o;if(!i)throw new TypeError("Slack message text is required");return i.replace(/<@([A-Z0-9]+)>/gi,"@$1").replace(/]*)?>/gi,"@$1")}function jMe(n,r,o){let i=On(r?.error)??"unknown_error",e=new Error(`Slack ${n} failed: ${i.replaceAll("_"," ")}`);return e.providerCode=i,["invalid_auth","not_authed","token_revoked","account_inactive"].includes(i)?e.code=o==="app"?"slack-invalid-app-token":"slack-invalid-bot-token":i==="missing_scope"?e.code="slack-missing-scope":i==="method_not_supported_for_channel_type"||i==="channel_type_not_supported"||i==="deprecated_endpoint"?e.code="slack-stream-unavailable":e.code=`slack-${i}`,e}function L8(n){return typeof n=="string"&&/^xoxb-[A-Za-z0-9-]{16,}$/.test(n.trim())}function U8(n){return typeof n=="string"&&/^xapp-[A-Za-z0-9-]{16,}$/.test(n.trim())}var nC=class{#e;#A;#t;#r;#o=null;#i;constructor({botToken:r,appToken:o,fetchImpl:i=fetch,baseUrl:e=NMe,fileUploadTimeoutMs:a=vMe}){if(r!==void 0&&!L8(r))throw new TypeError("Slack Bot Token is invalid");if(o!==void 0&&!U8(o))throw new TypeError("Slack App Token is invalid");if(!r&&!o)throw new TypeError("SlackApi requires a token");if(typeof i!="function")throw new TypeError("SlackApi requires fetch");this.#e=r?.trim(),this.#A=o?.trim(),this.#t=i,this.#r=new URL(e),this.#i=PMe(a,"fileUploadTimeoutMs")}authTest(r={}){return this.#I("auth.test",{...r,tokenKind:"bot"})}openConnection(r={}){return this.#I("apps.connections.open",{...r,tokenKind:"app",body:void 0})}postMessage({channelId:r,text:o,threadTs:i,signal:e}){return this.#I("chat.postMessage",{tokenKind:"bot",signal:e,body:{channel:Mg(r,"channel id"),text:iC(o),...i?{thread_ts:On(i)}:{},mrkdwn:!0,link_names:!1,unfurl_links:!1,unfurl_media:!1}})}updateMessage({channelId:r,ts:o,text:i,signal:e}){return this.#I("chat.update",{tokenKind:"bot",signal:e,body:{channel:Mg(r,"channel id"),ts:fp(o,"message timestamp"),text:iC(i),parse:"none",link_names:!1}})}startStream({channelId:r,threadTs:o,recipientTeamId:i,recipientUserId:e,markdownText:a,signal:A}){return this.#I("chat.startStream",{tokenKind:"bot",signal:A,body:{channel:Mg(r,"channel id"),thread_ts:fp(o,"thread timestamp"),...i?{recipient_team_id:Mg(i,"team id")}:{},...e?{recipient_user_id:Mg(e,"user id")}:{},...On(a)?{markdown_text:iC(a)}:{}}})}appendStream({channelId:r,ts:o,markdownText:i,signal:e}){return this.#I("chat.appendStream",{tokenKind:"bot",signal:e,body:{channel:Mg(r,"channel id"),ts:fp(o,"stream timestamp"),markdown_text:iC(i,{trim:!1})}})}stopStream({channelId:r,ts:o,markdownText:i,signal:e}){return this.#I("chat.stopStream",{tokenKind:"bot",signal:e,body:{channel:Mg(r,"channel id"),ts:fp(o,"stream timestamp"),...On(i)?{markdown_text:iC(i,{trim:!1})}:{}}})}async uploadFile({channelId:r,threadTs:o,file:i,signal:e}){if(!i||typeof i!="object"||typeof i.fileName!="string"||!i.fileName||!Buffer.isBuffer(i.bytes))throw new TypeError("A Slack file is required");if(e?.aborted)throw aC(e);let a=G8(e,this.#i),A;try{A=await this.#I("files.getUploadURLExternal",{tokenKind:"bot",signal:a,timeoutMs:this.#i,body:{filename:i.fileName,length:i.bytes.byteLength}})}catch(I){throw e?.aborted?aC(e):gI(I)}let t,s;try{t=fp(A?.file_id,"file id"),s=UMe(A?.upload_url)}catch(I){throw gI(I)}let g;try{g=await this.#t(s,{method:"POST",headers:{"content-type":i.mediaType??"application/octet-stream"},body:i.bytes,signal:a,redirect:"error"})}catch(I){throw e?.aborted?aC(e):gI(I)}if(!Number.isInteger(g?.status))throw gI(new Error("Slack file upload returned an invalid response"));if(g.status!==200){await Ek(g);let I=new Error(`Slack file upload failed with HTTP ${g.status}`);throw I.status=g.status,g.status===413?I.code="artifact-too-large":g.status===429?I.code="artifact-rate-limited":I.code="artifact-provider-rejected",I}await Ek(g);let c;try{c={files:[{id:t,title:i.fileName}],channel_id:Mg(r,"channel id"),...o?{thread_ts:fp(o,"thread timestamp")}:{}}}catch(I){throw gI(I)}if(a.aborted)throw e?.aborted?aC(e):gI(a.reason);try{let I=await this.#I("files.completeUploadExternal",{tokenKind:"bot",signal:a,timeoutMs:this.#i,retry:!1,body:c});if(!Array.isArray(I?.files)||!I.files.some(E=>On(E?.id)===t))throw new Error("Slack file completion did not confirm the uploaded file");return I}catch(I){throw e?.aborted?aC(e):I?.code==="slack-missing-scope"?gI(I):GMe(I)}}async downloadFile({url:r,signal:o,maxBytes:i}){if(!this.#e)throw new TypeError("Slack bot token is required for file download");let e=LMe(r);return Ui(e,{fetchImpl:async(A,t)=>{let s=await this.#t(A,t);if(!FMe(s?.status))return s;let g=HMe(s,A);if(this.#o&&!this.#o.has("files:read")||YMe(g))throw await Ek(s),new It("slack-file-access-required","Slack redirected a private file request to the workspace because file access was not granted","Slack \u672A\u6388\u6743\u673A\u5668\u4EBA\u8BFB\u53D6\u8BE5\u6587\u4EF6\u3002\u8BF7\u4E3A\u5E94\u7528\u6DFB\u52A0 files:read \u540E\u91CD\u65B0\u5B89\u88C5\uFF0C\u518D\u91CD\u65B0\u53D1\u9001\u56FE\u7247\u3002");return s},headers:{authorization:`Bearer ${this.#e}`},signal:o,maxBytes:i,allowedHosts:RMe})}async#I(r,{tokenKind:o,body:i,signal:e,timeoutMs:a=15e3,retry:A=!0}){let t=o==="app"?this.#A:this.#e;if(!t)throw new TypeError(`Slack ${o} token is required for ${r}`);let s=r==="files.getUploadURLExternal",g;try{g=await this.#t(new URL(r,this.#r),{method:"POST",headers:{authorization:`Bearer ${t}`,"content-type":i===void 0||s?"application/x-www-form-urlencoded;charset=utf-8":"application/json;charset=utf-8","user-agent":"DeepSeek-Harness-dsh-im (https://github.com/xmanrui/dsh-im, 0.2.2)"},...i===void 0?{}:{body:s?new URLSearchParams(i).toString():JSON.stringify(i)},signal:G8(e,a),redirect:"error"})}catch(I){throw I?.name==="AbortError"||I?.name==="TimeoutError"?I:new Error(`Slack ${r} transport failed`)}if(o==="bot"){let I=KMe(g);I&&(this.#o=I)}let c;try{c=await g.json()}catch{throw new Error(`Slack ${r} returned invalid JSON`)}if(g.status===429&&A){let I=Number(g.headers.get("retry-after"))||1;return await xMe(Math.min(1e4,Math.max(100,I*1e3)),e),this.#I(r,{tokenKind:o,body:i,signal:e,timeoutMs:a,retry:!1})}if(!g.ok||c?.ok!==!0){let I=jMe(r,c,o);throw I.status=g.status,I}return c}};async function H8({botToken:n,appToken:r},o={}){if(!L8(n)){let t=new TypeError("Slack Bot Token \u5FC5\u987B\u4EE5 xoxb- \u5F00\u5934\u3002");throw t.code="slack-invalid-bot-token",t}if(!U8(r)){let t=new TypeError("Slack App Token \u5FC5\u987B\u4EE5 xapp- \u5F00\u5934\u3002");throw t.code="slack-invalid-app-token",t}let i=new nC({botToken:n,appToken:r,...o}),[e,a]=await Promise.all([i.authTest(),i.openConnection()]);if(!e?.team_id||!e?.user_id||!e?.bot_id)throw new Error("Slack Bot Token \u6CA1\u6709\u8FD4\u56DE\u5B8C\u6574\u7684\u673A\u5668\u4EBA\u8EAB\u4EFD\u3002");let A;try{A=new URL(a?.url)}catch{A=null}if(!A||A.protocol!=="wss:"){let t=new Error("Slack App Token \u65E0\u6CD5\u521B\u5EFA Socket Mode \u8FDE\u63A5\uFF0C\u8BF7\u786E\u8BA4\u5DF2\u542F\u7528 Socket Mode \u548C connections:write\u3002");throw t.code="slack-socket-mode",t}return{platformId:`${e.team_id}:${e.user_id}`,name:On(e.user)??"DeepSeek Harness",username:On(e.user),teamId:String(e.team_id),teamName:On(e.team)}}var dk=Object.freeze({key:"slack",label:"Slack",connectionLabel:" Socket Mode \u957F\u8FDE\u63A5"}),jy=class extends pn{constructor(r){super({descriptor:dk,...r})}};function _s(n){return typeof n=="string"&&n.trim()?n.trim():null}function Jy(n,r){return Object.freeze({code:n,message:r})}var sC=class{#e;#A;#t;#r;#o;#i;#I=new Map;#s=new Map;#l=new Map;#a=0;#n=!1;constructor({credentials:r,configStore:o,inspectCredentials:i=H8,createRuntime:e,deleteState:a=async()=>{},logger:A=console}){if(!r||typeof r.resolve!="function"||typeof r.set!="function"||typeof r.unset!="function")throw new TypeError("Slack requires the DSH credential provider");if(!o||typeof o.list!="function"||typeof o.save!="function"||typeof o.remove!="function")throw new TypeError("Slack requires a config store");if(typeof i!="function"||typeof e!="function")throw new TypeError("Slack controller dependencies are incomplete");this.#e=r,this.#A=o,this.#t=i,this.#r=e,this.#o=a,this.#i=A}async initialize(){if(this.#n)return this.status();for(let r of this.#A.list())await this.#d(r.botId,async()=>{if(this.#n||this.#I.get(r.botId)?.status?.ready)return;let o=await this.#p(r);if(!o){this.#s.set(r.botId,Jy("missing-token","Slack\u673A\u5668\u4EBA\u51ED\u636E\u7F3A\u5931\uFF0C\u8BF7\u79FB\u9664\u540E\u91CD\u65B0\u63A5\u5165\u3002"));return}try{await this.#g(r,o),this.#s.delete(r.botId)}catch(i){this.#s.set(r.botId,Jy("connection-failed","Slack Socket Mode \u8FDE\u63A5\u672A\u5C31\u7EEA\uFF0C\u63D2\u4EF6\u4F1A\u81EA\u52A8\u91CD\u8BD5\u3002")),this.#i.warn?.(`[dsh-im:slack] bot ${r.botId} failed to initialize:`,i)}finally{this.#E()}});return this.status()}async bindCredentials({botToken:r,appToken:o}={}){if(this.#n)throw new Error("Slack controller is closed");let i=_s(r),e=_s(o);if(!i||!e)throw new TypeError("Slack Bot Token and App Token are required");let a=await this.#t({botToken:i,appToken:e}),A=_s(a?.platformId),t=_s(a?.name);if(!A||!t)throw new Error("Slack returned an invalid bot identity");let s=lk(A);return await this.#d(s.botId,async()=>{if(this.#n)throw new Error("Slack controller is closed");let g=this.#A.getByPlatformId(A),c=await this.#e.resolve(s.botTokenRef).catch(()=>{}),I=await this.#e.resolve(s.appTokenRef).catch(()=>{}),E={...s,platformId:A,name:t,username:_s(a.username),teamId:_s(a.teamId),teamName:_s(a.teamName),createdAt:g?.createdAt??new Date().toISOString(),connectedAt:new Date().toISOString()};try{await this.#e.set(s.botTokenRef,i),await this.#e.set(s.appTokenRef,e),await this.#A.save(E)}catch(B){throw await Promise.all([this.#h(s.botTokenRef,c),this.#h(s.appTokenRef,I)]),B}try{await this.#g(E,{botToken:i,appToken:e}),this.#s.delete(s.botId)}catch(B){this.#s.set(s.botId,Jy("connection-failed","Slack\u673A\u5668\u4EBA\u5DF2\u63A5\u5165\uFF0CSocket Mode \u8FDE\u63A5\u6682\u672A\u5C31\u7EEA\u3002")),this.#i.warn?.(`[dsh-im:slack] bot ${s.botId} credential connection failed:`,B)}this.#E()}),this.status()}async reconnectBot(r){let o=this.#A.get(r);if(!o)throw new Error("Unknown Slack bot");return await this.#d(r,async()=>{let i=await this.#p(o);if(!i)throw new Error("Slack bot credentials are missing");try{await this.#g(o,i),this.#s.delete(r)}catch(e){throw this.#s.set(r,Jy("connection-failed","Slack Socket Mode \u8FDE\u63A5\u4ECD\u672A\u5C31\u7EEA\uFF0C\u8BF7\u68C0\u67E5\u4E24\u4E2A Token\u3002")),e}finally{this.#E()}}),this.status()}async sendConnectionTest(r){let o=this.#A.get(r);if(!o)throw new Error("Unknown Slack bot");return this.#d(r,async()=>{let i=this.#I.get(r);if(!i?.status?.ready||typeof i.sendConnectionTest!="function"){let e=new Error("Slack\u673A\u5668\u4EBA\u5C1A\u672A\u8FDE\u63A5");throw e.code="test-target-unavailable",e}return await i.sendConnectionTest(Ia(`${o.name}\uFF08${pk(o.platformId)}\uFF09`,"Slack\u673A\u5668\u4EBA")),{sent:!0}})}async deleteBot(r){let o=this.#A.get(r);if(!o)throw new Error("Unknown Slack bot");return await this.#d(r,async()=>{let i=await this.#e.resolve(o.botTokenRef).catch(()=>{}),e=await this.#e.resolve(o.appTokenRef).catch(()=>{});await this.#c(r);try{await this.#e.unset(o.botTokenRef),await this.#e.unset(o.appTokenRef),await this.#A.remove(r)}catch(a){throw await Promise.all([this.#h(o.botTokenRef,i),this.#h(o.appTokenRef,e)]),i?.value&&e?.value&&await this.#g(o,{botToken:i.value,appToken:e.value}).catch(()=>{}),new Error("Unable to remove the Slack bot safely.",{cause:a})}await this.#o({botId:r,config:o}).catch(a=>{this.#i.warn?.(`[dsh-im:slack] bot ${r} state cleanup failed:`,a)}),this.#s.delete(r),this.#E()}),this.status()}status(){let r=this.#A.list().map(i=>{let e=this.#I.get(i.botId)?.status??null,a=e?.ready===!0&&e.connectionState==="connected"&&e.harnessReachable===!0,A=a?"connected":e?.connectionState==="connecting"?"connecting":this.#s.has(i.botId)||e?.connectionState==="failed"?"error":"offline";return{botId:i.botId,state:A,connected:a,configured:!0,bot:{name:i.name,username:i.username,teamName:i.teamName,idMasked:pk(i.platformId)},health:{status:a?"healthy":A==="error"?"error":"offline",summary:a?`Slack${dk.connectionLabel}\u8FD0\u884C\u6B63\u5E38`:A==="error"?"Slack\u8FDE\u63A5\u672A\u5C31\u7EEA\uFF0C\u63D2\u4EF6\u4F1A\u81EA\u52A8\u91CD\u8BD5":"Slack\u8FDE\u63A5\u5F53\u524D\u79BB\u7EBF",lastCheckedAt:e?.lastCheckedAt??null,lastConnectedAt:e?.lastConnectedAt??null},stats:{messagesReceived:e?.messagesReceived??0,messagesReplied:e?.messagesReplied??0},error:structuredClone(this.#s.get(i.botId)??null)}}),o=r.filter(i=>i.connected).length;return{schemaVersion:1,revision:this.#a,state:r.length===0?"disconnected":o===r.length?"connected":o>0?"degraded":"offline",bots:r,totals:{configured:r.length,connected:o}}}async close(){this.#n||(this.#n=!0,await Promise.allSettled([...this.#l.values()]),await Promise.allSettled([...this.#I.keys()].map(r=>this.#c(r))))}async#g(r,{botToken:o,appToken:i}){if(this.#n)throw new Error("Slack controller is closed");if(await this.#c(r.botId),this.#n)throw new Error("Slack controller is closed");let e=await this.#r({botId:r.botId,config:r,botToken:o,appToken:i});if(!e||typeof e.start!="function"||typeof e.stop!="function")throw new TypeError("createRuntime returned an invalid Slack runtime");this.#I.set(r.botId,e);try{await e.start()}catch(a){throw await e.stop().catch(()=>{}),this.#I.delete(r.botId),a}}async#c(r){let o=this.#I.get(r);this.#I.delete(r),await o?.stop().catch(i=>{this.#i.warn?.(`[dsh-im:slack] bot ${r} failed to stop cleanly:`,i)})}async#p(r){let[o,i]=await Promise.all([this.#e.resolve(r.botTokenRef).catch(()=>{}),this.#e.resolve(r.appTokenRef).catch(()=>{})]),e=_s(o?.value),a=_s(i?.value);return e&&a?{botToken:e,appToken:a}:null}async#h(r,o){o?.value?await this.#e.set(r,o.value).catch(()=>{}):await this.#e.unset(r).catch(()=>{})}#d(r,o){let a=(this.#l.get(r)??Promise.resolve()).catch(()=>{}).then(o).finally(()=>{this.#l.get(r)===a&&this.#l.delete(r)});return this.#l.set(r,a),a}#E(){this.#a+=1}};var qy=class extends ir{constructor(r){super({...r,rpcIdPrefix:"slack",logPrefix:"dsh-slack"})}};var K8=Object.freeze([1e3,3e3,5e3,1e4,3e4]),Ck=38e3,Y8=11e3,JMe=new Set(["image/jpeg","image/png","image/webp","image/gif"]);function hk(n,r,o){if(typeof n.addEventListener=="function")n.addEventListener(r,o);else if(typeof n.on=="function")n.on(r,o);else throw new TypeError("Slack WebSocket does not support events")}function qMe(n){let r=n?.data??n;return typeof r=="string"?r:Buffer.isBuffer(r)?r.toString("utf8"):r instanceof ArrayBuffer?Buffer.from(r).toString("utf8"):ArrayBuffer.isView(r)?Buffer.from(r.buffer,r.byteOffset,r.byteLength).toString("utf8"):null}function zMe(n){let r=new URL(n);if(r.protocol!=="wss:")throw new Error("Slack returned an insecure Socket Mode URL");return r.href}function VMe(n){return typeof n=="string"?n.replaceAll("&","&").replaceAll("<","<").replaceAll(">",">"):""}function WMe(n,r){return VMe(n).replace(new RegExp(`<@${r}>`,"gi"),"").trim()}function ZMe(n,r){let o=typeof n?.mimetype=="string"?n.mimetype.toLowerCase():"",i=typeof n?.url_private_download=="string"&&n.url_private_download?n.url_private_download:n?.url_private;return!JMe.has(o)||typeof i!="string"||!i?null:{name:typeof n.name=="string"?n.name:void 0,mediaType:o,size:Number.isSafeInteger(n.size)&&n.size>=0?n.size:void 0,load:e=>r(i,e)}}function XMe(n,r,{loadFile:o=async()=>{throw new Error("Slack file downloader is unavailable")}}={}){let i=n?.event;if(!i||!n?.event_id||!i.channel||!i.user||!i.ts)return null;let e=i.type==="message"&&i.channel_type==="im",a=i.type==="app_mention";if(!e&&!a||i.subtype&&i.subtype!=="file_share"||i.bot_id||i.app_id)return null;let A=String(i.thread_ts??i.ts);return{messageId:String(n.event_id),senderId:String(i.user),senderIsBot:String(i.user)===String(r),kind:e?"direct":"group",conversationId:e?String(i.channel):`${i.channel}:${A}`,content:WMe(i.text??"",r),images:Array.isArray(i.files)?i.files.map(t=>ZMe(t,o)).filter(Boolean):[],addressed:e||a,replyTarget:{channelId:String(i.channel),threadTs:A,recipientUserId:String(i.user),recipientTeamId:String(i.user_team??n.team_id??"")},connectionTestTarget:{channelId:String(i.channel)}}}function $Me(n){return typeof n=="string"&&/^正在使用.+…$/.test(n.trim())}async function x8(n,r,o,i,e){if(i)for(let a=0;a{let y=Xi(Q,Ck)[0]??"";if(!y||!y.startsWith(A))return;let m=y.slice(A.length);m&&(await x8(n,r,a,m,o),A=y)},f=()=>{I||c||s!==null||g||!t||(s=setTimeout(()=>{s=null;let Q=t;t="",g=B(Q).catch(y=>{c=!0,i.warn?.("[dsh-im:slack] streaming append failed:",y)}).finally(()=>{g=null,f()})},350),s?.unref?.())};return{messageId:a,get providerMessageIds(){return[...E]},update(Q){I||c||typeof Q!="string"||!Q.trim()||$Me(Q)||(t=Q,f())},async finish(Q){if(I)throw new Error("Slack message stream is already closed");I=!0,s!==null&&clearTimeout(s),s=null,t="",await g?.catch(()=>{});let y=Xi(Q,Ck),m=y[0]??"\u5904\u7406\u5B8C\u6210\u3002";!c&&m.startsWith(A)?(await x8(n,r,a,m.slice(A.length),o),await n.stopStream({channelId:r.channelId,ts:a,signal:o})):(await n.stopStream({channelId:r.channelId,ts:a,signal:o}).catch(()=>{}),await n.updateMessage({channelId:r.channelId,ts:a,text:m,signal:o}));for(let N of y.slice(1)){let _=await n.postMessage({channelId:r.channelId,threadTs:r.threadTs,text:N,signal:o});typeof _?.ts=="string"&&_.ts&&E.push(_.ts)}},cancel(){I=!0,t="",s!==null&&clearTimeout(s),s=null,n.stopStream({channelId:r.channelId,ts:a,signal:o}).catch(()=>{})}}}var uk=class{#e;#A;#t;constructor({api:r,signal:o,logger:i}){this.#e=r,this.#A=o,this.#t=i}async sendText(r,o){let i=Xi(o,Ck),e=[];for(let a of i){let A=await this.#e.postMessage({channelId:r.channelId,threadTs:r.threadTs,text:a,signal:this.#A});typeof A?.ts=="string"&&A.ts&&e.push(A.ts)}return{providerMessageIds:e}}openStream(r){return e_e({api:this.#e,target:r,signal:this.#A,logger:this.#t})}sendFile(r,o){return this.#e.uploadFile({channelId:r.channelId,threadTs:r.threadTs,file:o,signal:this.#A})}};function A_e(){return{startedAt:null,ready:!1,connectionState:"idle",harnessReachable:!1,lastCheckedAt:null,lastConnectedAt:null,lastError:null,...fi()}}var gC=class{#e;#A;#t;#r;#o;#i;#I;#s;#l;#a;#n=A_e();#g=null;#c=null;#p=null;#h=null;#d=null;#E=null;#C=0;#u=0;#B=!0;#Q=null;constructor({config:r,botToken:o,appToken:i,harness:e,state:a,logger:A=console,replyTimeoutMs:t=6e5,connectTimeoutMs:s=2e4,createApi:g=I=>new nC(I),createWebSocket:c=I=>new WebSocket(I)}){if(!r||!o||!i||!e||!a)throw new TypeError("SlackRuntime requires config, both tokens, Harness, and state");if(typeof c!="function")throw new TypeError("SlackRuntime requires WebSocket");this.#e=r,this.#A=o,this.#t=i,this.#r=e,this.#o=a,this.#i=A,this.#I=t,this.#s=s,this.#l=g,this.#a=c}get status(){return structuredClone(this.#n)}async sendConnectionTest(r){if(!this.#n.ready||!this.#c){let o=new Error("Slack bot is not connected");throw o.code="test-target-unavailable",o}return this.#c.sendConnectionTest(r)}async start(){return this.#n.ready&&this.#h?this.status:this.#Q?this.#Q:(this.#Q=this.#m().finally(()=>{this.#Q=null}),this.#Q)}async#m(){await this.stop(),this.#B=!1,this.#C=0,this.#n.startedAt=new Date().toISOString(),this.#n.connectionState="connecting",this.#n.lastError=null,await this.#r.ensureRunning(),this.#n.harnessReachable=!0;let r=new AbortController;this.#p=r;let o=this.#l({botToken:this.#A,appToken:this.#t});this.#g=o;try{let i=await o.authTest({signal:r.signal});if(`${i?.team_id}:${i?.user_id}`!==this.#e.platformId)throw new Error("Slack Bot Token identity does not match the saved bot");let e=new uk({api:o,signal:r.signal,logger:this.#i});this.#c=new jy({bot:e,harness:this.#r,state:this.#o,status:this.#n,logger:this.#i,replyTimeoutMs:this.#I,signal:r.signal});let a;try{await Promise.race([this.#f(),new Promise((A,t)=>{a=setTimeout(()=>t(new Error("Slack Socket Mode did not become ready in time")),this.#s),a?.unref?.()})])}finally{clearTimeout(a)}return this.status}catch(i){throw this.#n.ready=!1,this.#n.connectionState="failed",this.#n.lastError=i?.message??String(i),await this.stop(),i}}async#f(){if(this.#B)throw new Error("Slack runtime is stopped");let r=await this.#g.openConnection({signal:this.#p?.signal});return this.#y(r?.url)}#y(r){if(this.#B)return Promise.reject(new Error("Slack runtime is stopped"));let o=++this.#u,i=this.#a(zMe(r));this.#h=i;let e=!1;return new Promise((a,A)=>{let t=s=>{if(e||o!==this.#u)return;e=!0,this.#d=s?.connection_info?.app_id??null,this.#C=0;let g=Date.now();this.#n.ready=!0,this.#n.connectionState="connected",this.#n.lastCheckedAt=g,this.#n.lastConnectedAt=g,this.#n.lastError=null,a()};hk(i,"message",s=>{if(o!==this.#u||this.#B)return;let g=qMe(s);if(!g)return;let c;try{c=JSON.parse(g)}catch{this.#i.warn?.("[dsh-im:slack] ignored malformed Socket Mode JSON");return}if(c.type==="hello"){t(c);return}if(c.envelope_id&&i.readyState===1&&(i.send(JSON.stringify({envelope_id:c.envelope_id})),this.#n.lastCheckedAt=Date.now()),c.type==="disconnect"){i.close(4e3,"Slack requested reconnect");return}if(c.type!=="events_api"||c.payload?.type!=="event_callback"||this.#d&&c.payload.api_app_id&&c.payload.api_app_id!==this.#d)return;let I=XMe(c.payload,this.#e.platformId.split(":")[1],{loadFile:(B,f)=>this.#g.downloadFile({url:B,...f})}),E=this.#c;I&&E&&E.accept(I).catch(B=>{o!==this.#u||this.#B||this.#i.error?.(`[dsh-im:slack] bot ${this.#e.botId} message handling failed:`,B)})}),hk(i,"close",(s={})=>{if(o!==this.#u)return;if(this.#h===i&&(this.#h=null),this.#B){e||A(new DOMException("Stopped","AbortError"));return}let g=Number(s.code)||0,c=new Error(`Slack Socket Mode closed (${g||"unknown"})`);this.#n.ready=!1,this.#n.connectionState="connecting",this.#n.lastError=c.message,e||(e=!0,A(c)),this.#D()}),hk(i,"error",()=>{o!==this.#u||this.#B||(this.#n.lastError="Slack Socket Mode WebSocket error")})})}#D(){if(this.#B||this.#E!==null)return;let r=K8[Math.min(this.#C,K8.length-1)];this.#C+=1,this.#E=setTimeout(()=>{this.#E=null,this.#f().catch(o=>{this.#B||(this.#i.warn?.("[dsh-im:slack] Socket Mode reconnect failed:",o),this.#D())})},r),this.#E?.unref?.()}async stop(){this.#B=!0,this.#u+=1,this.#p?.abort(),this.#p=null,this.#E!==null&&clearTimeout(this.#E),this.#E=null;let r=this.#h,o=this.#c;this.#h=null,this.#c=null,this.#g=null,this.#d=null;try{r&&r.readyState<2&&r.close(1e3,"Plugin stopped")}catch(i){this.#i.warn?.(`[dsh-im:slack] bot ${this.#e.botId} failed to close Socket Mode:`,i)}return await Promise.race([o?.waitForIdle()??Promise.resolve(),new Promise(i=>setTimeout(i,2e3))]),this.#n.ready=!1,this.#n.connectionState="idle",this.status}};var zy=class extends En{};async function Bk(n,r={},o={}){if(!n?.credentials)throw new TypeError("dsh-im slack requires ctx.credentials");if(!n?.webServer)throw new TypeError("dsh-im slack requires ctx.webServer");let i=o.ConfigStore??xy,e=o.StateStore??zy,a=o.HarnessClient??qy,A=o.Controller??sC,t=o.Runtime??gC,s=o.createConnectionSupervisor??Cl,g=typeof n.logger=="function"?n.logger("dsh-im:slack"):n.logger??console,c=()=>da(n),I=Qb(r,"slack"),E=await new i(I.config).load(),B=j8(r.workspace??process.cwd()),f=o.WorkspaceStore??Hr,Q=o.workspaces??await new f(I.workspaces,{defaultWorkspace:B}).load(),y=E.list();await Q.reconcile(y.map(W=>W.botId)),await Promise.all(y.map(W=>Q.ensure(W.botId,{defaultAgentPreset:r.agentPreset})));let m=typeof E.remove=="function"?Ca(E,{workspaces:Q}):E,N=new Map,_=W=>j8(I.bots,W,"state.json"),v=async W=>{let L=N.get(W);return L||(L=await new e(_(W)).load(),N.set(W,L)),L},K=fa(n,o.commandExecutor),{controlExecutor:x,sessionMaintenanceExecutor:H}=Qa(n,{controlExecutor:o.controlExecutor,sessionMaintenanceExecutor:o.sessionMaintenanceExecutor}),j=new a({baseUrl:Md(n.webServer,r.harnessBaseUrl),workspace:B,autostart:!1,dshBin:r.dshBin??"dsh",...K?{commandExecutor:K}:{},...x?{controlExecutor:x}:{},...H?{sessionMaintenanceExecutor:H}:{}}),q=new A({credentials:n.credentials,configStore:m,logger:g,...o.inspectCredentials?{inspectCredentials:o.inspectCredentials}:{},createRuntime:async({botId:W,config:L,botToken:M,appToken:G})=>{let ee=await v(W);await Q.ensure(W,{defaultAgentPreset:r.agentPreset});let Ce=ua(j,{botId:W,workspaces:Q,state:ee,agentPresetCatalog:c});return new t({config:L,botToken:M,appToken:G,harness:Ce.harness,state:Ce.state,replyTimeoutMs:r.replyTimeoutMs??6e5,connectTimeoutMs:r.connectTimeoutMs??2e4,logger:{error:(...de)=>g.error?.(`[${W}]`,...de),warn:(...de)=>g.warn?.(`[${W}]`,...de),info:(...de)=>g.info?.(`[${W}]`,...de),debug:(...de)=>g.debug?.(`[${W}]`,...de)}})},deleteState:async({botId:W})=>{let L=N.get(W);if(N.delete(W),L&&typeof L.remove=="function")await L.remove();else try{await t_e(_(W))}catch(M){if(M?.code!=="ENOENT")throw M}}}),Y=Ba(q,{workspaces:Q,stateFor:v,agentPresetCatalog:c}),k=s({channel:"slack",controller:Y,harness:j,logger:g,retryDelaysMs:r.retryDelaysMs,healthyIntervalMs:r.healthyIntervalMs}).start();return{controller:Y,ready:k.ready,async close(){await k.close(),await Y.close(),j.stopManagedProcess()}}}var q8="/slack",bi=Object.freeze({status:"connection.status",bindCredentials:"bot.bind-credentials",reconnectBot:"bot.reconnect",deleteBot:"bot.delete",setWorkspace:Za,setAgentPreset:Xa}),z8=Object.freeze(Object.values(bi)),r_e=new Set(["token","botToken","appToken","botTokenRef","appTokenRef","tokenRef","platformId","secret","secretRef"]);function Qk(n){return n!==null&&typeof n=="object"&&!Array.isArray(n)}function Vy(n,r){return Qk(n)&&Object.keys(n).every(o=>r.includes(o))}function J8(n){return typeof n=="string"&&/^slack_[a-f0-9]{24}$/.test(n)}function a_e(n){return typeof n=="string"&&/^xoxb-[A-Za-z0-9-]{16,}$/.test(n.trim())&&n.length<=4096}function i_e(n){return typeof n=="string"&&/^xapp-[A-Za-z0-9-]{16,}$/.test(n.trim())&&n.length<=4096}function o_e(n,r){return Qk(r)?n===bi.status?Vy(r,[])?null:"connection.status does not accept fields.":n===bi.bindCredentials?Vy(r,["botToken","appToken"])&&a_e(r.botToken)&&i_e(r.appToken)?null:"bot.bind-credentials requires xoxb Bot Token and xapp App Token.":n===bi.reconnectBot?Vy(r,["botId","sendTest"])&&J8(r.botId)&&(r.sendTest===void 0||typeof r.sendTest=="boolean")?null:"bot.reconnect requires a botId.":n===bi.deleteBot?Vy(r,["botId","confirm"])&&J8(r.botId)&&r.confirm===!0?null:"bot.delete requires a botId and confirm=true.":n===bi.setWorkspace?ya(r)?null:"\u8BF7\u8F93\u5165\u5DE5\u4F5C\u533A\u7EDD\u5BF9\u8DEF\u5F84\u3002":n===bi.setAgentPreset?Sa(r)?null:"\u8BF7\u9009\u62E9 Agent Preset\u3002":"Unknown Slack endpoint.":"Payload must be an object."}function fk(n){if(Array.isArray(n))return n.map(fk);if(!Qk(n))return n;let r={};for(let[o,i]of Object.entries(n))r_e.has(o)||(r[o]=fk(i));return r}function n_e(n){let r=ma(n);return r||(n?.code==="slack-invalid-bot-token"?{code:"invalid-bot-token",message:"Slack Bot Token \u65E0\u6548\uFF0C\u8BF7\u786E\u8BA4\u4F7F\u7528\u4EE5 xoxb- \u5F00\u5934\u7684\u4EE4\u724C\u3002"}:n?.code==="slack-invalid-app-token"?{code:"invalid-app-token",message:"Slack App Token \u65E0\u6548\uFF0C\u8BF7\u786E\u8BA4\u4F7F\u7528\u4EE5 xapp- \u5F00\u5934\u7684\u4EE4\u724C\u3002"}:n?.code==="slack-missing-scope"?{code:"missing-scope",message:"Slack \u5E94\u7528\u6743\u9650\u4E0D\u5B8C\u6574\uFF0C\u8BF7\u91CD\u65B0\u5BFC\u5165 Manifest \u5E76\u5B89\u88C5\u5230\u5DE5\u4F5C\u533A\u3002"}:n?.code==="slack-socket-mode"?{code:"socket-mode-unavailable",message:n.message}:{code:"slack-operation-failed",message:"Slack \u64CD\u4F5C\u5931\u8D25\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5\u3002"})}function V8(n){for(let r of["status","bindCredentials","reconnectBot","deleteBot"])if(typeof n?.[r]!="function")throw new TypeError(`A complete Slack controller is required (${r})`);return async(r,o,i)=>{if(i?.aborted)return{ok:!1,error:{code:"cancelled",message:"The request was cancelled."}};if(!z8.includes(r))return{ok:!1,error:{code:"bad-request",message:"Unknown Slack endpoint."}};let e=o_e(r,o);if(e)return{ok:!1,error:{code:"bad-request",message:e}};try{let a;if(r===bi.status)a=await n.status();else if(r===bi.bindCredentials)a=await n.bindCredentials(o);else if(r===bi.reconnectBot){if(a=await n.reconnectBot(o.botId),i?.aborted)return{ok:!1,error:{code:"cancelled",message:"The request was cancelled."}};if(o.sendTest===!0){let A=null;try{if(a?.bots?.find(t=>t?.botId===o.botId)?.connected!==!0){let t=new Error("Bot is not connected");throw t.code="test-target-unavailable",t}if(typeof n.sendConnectionTest!="function"){let t=new Error("Connection test is unavailable");throw t.code="test-target-unavailable",t}await n.sendConnectionTest(o.botId)}catch(t){A=t}a={...a,testMessage:Vt(A)}}}else if(r===bi.setWorkspace){if(typeof n.updateWorkspace!="function")throw new Error("Workspace update is unavailable");a=await n.updateWorkspace(o.botId,o.workspace)}else if(r===bi.setAgentPreset){if(typeof n.updateAgentPreset!="function")throw new Error("Agent preset update is unavailable");a=await n.updateAgentPreset(o.botId,o.agentPreset)}else a=await n.deleteBot(o.botId);return i?.aborted?{ok:!1,error:{code:"cancelled",message:"The request was cancelled."}}:{ok:!0,value:fk(a)}}catch(a){return i?.aborted?{ok:!1,error:{code:"cancelled",message:"The request was cancelled."}}:{ok:!1,error:n_e(a)}}}}function Wy(n,r,o){if(!n?.connection?.rpc||typeof n.connection.rpc.handle!="function")throw new TypeError("DSH Host Connection RPC is required");return n.connection.rpc.handle(q8,V8(r),{authority:hr(o)})}async function W8(n,r={}){if(r?.controller)return Wy(n,r.controller,r.rpcAuthority);let o=await Bk(n,r,r.internals??{}),i=Wy(n,o.controller,r.rpcAuthority);return n.effect(()=>async()=>o.close(),"dsh-im: close Slack bot connections"),i}var Z8=Object.freeze({botPrefix:"telegram",tokenRefPrefix:"DSH_TELEGRAM_BOT_TOKEN"}),cC=Object.freeze({compatible:"compatible",privateAllowlist:"private-allowlist"}),s_e=/^[1-9]\d{0,15}$/;function X8(n){if(n===void 0)return Object.freeze([]);if(!Array.isArray(n))throw new TypeError("allowedUsers must be an array of numeric Telegram User IDs");let r=n.map(o=>{let i=typeof o=="number"&&Number.isSafeInteger(o)?String(o):typeof o=="string"?o.trim():"";if(!s_e.test(i))throw new TypeError("allowedUsers contains an invalid Telegram User ID");return i});return Object.freeze([...new Set(r)])}function _g(n={}){if(!n||typeof n!="object"||Array.isArray(n))throw new TypeError("Telegram access policy must be an object");let r=n.accessMode??cC.compatible;if(!Object.values(cC).includes(r))throw new TypeError("Telegram accessMode must be compatible or private-allowlist");return Object.freeze({accessMode:r,allowedUsers:X8(n.allowedUsers)})}function g_e(n){let r=Object.hasOwn(n,"accessMode"),o=Object.hasOwn(n,"allowedUsers");if(!r&&!o)return{};try{let i=_g(n);return{...r?{accessMode:i.accessMode}:{},...o||r?{allowedUsers:i.allowedUsers}:{}}}catch{return null}}function $8(n){return yd(n,Z8)}function ez(n){return LB(n,"Telegram\u673A\u5668\u4EBA")}var Zy=class extends dl{constructor(r){super(r,{channel:"Telegram",...Z8,normalizeBotExtension:g_e})}async save(r){let o=r?.platformId?this.getByPlatformId(String(r.platformId)):null;return super.save({...o,...r})}};var Xy=class extends ir{constructor(r){super({...r,rpcIdPrefix:"telegram",logPrefix:"dsh-telegram"})}};var c_e="https://api.telegram.org/",I_e=Object.freeze(["api.telegram.org"]),l_e=12e4;function Ng(n){return typeof n=="string"&&n.trim()?n.trim():null}function Az(n,r){let o=AbortSignal.timeout(r);return n?AbortSignal.any([n,o]):o}function tz(n){return n?.reason instanceof Error?n.reason:new DOMException("The operation was aborted","AbortError")}function p_e(n,r){if(!Number.isInteger(n)||n<1)throw new TypeError(`${r} must be a positive integer`);return n}function rz(n,r){return r?.providerCode!==void 0&&(n.providerCode=r.providerCode),r?.retry_after!==void 0&&(n.retry_after=r.retry_after,n.retryAfter=r.retry_after),Number.isInteger(r?.status)&&(n.status=r.status),n}function E_e(n){let r=Number(n?.providerCode),o=Number(n?.status),i=Ng(n?.message)??"",e="artifact-provider-rejected",a="Telegram rejected the document.";r===401||r===403||o===401||o===403?(e="artifact-permission-required",a="Telegram denied permission to send the document."):r===413||o===413||/(?:file|request|entity).{0,20}(?:too (?:big|large)|size limit)|too (?:big|large)/i.test(i)?(e="artifact-too-large",a="The document exceeds Telegram's size limit."):r===429||o===429?(e="artifact-rate-limited",a="Telegram rate-limited document delivery."):(r>=500||o>=500)&&(e="artifact-delivery-uncertain",a="Telegram document delivery result is uncertain.");let A=new Error(a,{cause:n});return A.code=e,rz(A,n)}function d_e(n){let r=new Error("Telegram document delivery result is uncertain",{cause:n});return r.code="artifact-delivery-uncertain",rz(r,n)}function h_e(n){return typeof n=="string"&&/^\d{5,20}:[A-Za-z0-9_-]{20,}$/.test(n.trim())}var C_e=/^[a-z0-9_]{1,32}$/;function u_e(n){return!!n&&typeof n=="object"&&!Array.isArray(n)&&typeof n.command=="string"&&C_e.test(n.command)&&typeof n.description=="string"&&n.description.length>=1&&n.description.length<=256}var yk=Object.freeze({type:"commands"}),IC=class{#e;#A;#t;#r;constructor({token:r,fetchImpl:o=fetch,baseUrl:i=c_e,fileUploadTimeoutMs:e=l_e}){if(!h_e(r))throw new TypeError("Telegram Bot Token is invalid");if(typeof o!="function")throw new TypeError("TelegramApi requires fetch");this.#e=r.trim(),this.#A=o,this.#t=new URL(i),this.#r=p_e(e,"fileUploadTimeoutMs")}async getMe(r={}){return this.#o("getMe",{},r)}async getWebhookInfo(r={}){return this.#o("getWebhookInfo",{},r)}async getUpdates({offset:r,timeout:o=25,signal:i}={}){let e={timeout:o,limit:100,allowed_updates:["message"],...Number.isSafeInteger(r)?{offset:r}:{}};return this.#o("getUpdates",e,{signal:i,timeoutMs:Math.max(1e4,(o+10)*1e3)})}async getFile({fileId:r,signal:o}={}){let i=Ng(r);if(!i||!/^[A-Za-z0-9_-]{1,512}$/.test(i))throw new TypeError("Telegram file id is invalid");return this.#o("getFile",{file_id:i},{signal:o})}async downloadFile({fileId:r,signal:o,maxBytes:i}={}){let e=await this.getFile({fileId:r,signal:o}),a=Ng(e?.file_path);if(!a||a.startsWith("/")||a.includes("\\")||a.includes("?")||a.includes("#"))throw new Error("Telegram returned an invalid file path");let A;try{A=a.split("/").map(s=>decodeURIComponent(s))}catch{throw new Error("Telegram returned an invalid file path")}if(A.some(s=>!s||s==="."||s===".."))throw new Error("Telegram returned an invalid file path");let t=new URL(this.#t);return t.pathname=`/file/bot${this.#e}/${a}`,Ui(t,{fetchImpl:this.#A,signal:o,maxBytes:i,allowedHosts:I_e})}async sendMessage({chatId:r,text:o,replyToMessageId:i,messageThreadId:e,signal:a}){return this.#o("sendMessage",{chat_id:r,text:o,link_preview_options:{is_disabled:!0},...i?{reply_parameters:{message_id:i,allow_sending_without_reply:!0}}:{},...e?{message_thread_id:e}:{}},{signal:a})}async sendDocument({chatId:r,file:o,replyToMessageId:i,messageThreadId:e,signal:a}){if(!o||typeof o!="object"||typeof o.fileName!="string"||!o.fileName||!Buffer.isBuffer(o.bytes))throw new TypeError("A Telegram document is required");let A=new FormData;if(A.append("chat_id",String(r)),A.append("document",new Blob([o.bytes],{type:o.mediaType??"application/octet-stream"}),o.fileName),i&&A.append("reply_parameters",JSON.stringify({message_id:i,allow_sending_without_reply:!0})),e&&A.append("message_thread_id",String(e)),a?.aborted)throw tz(a);let t=Az(a,this.#r);try{return await this.#o("sendDocument",A,{signal:t,timeoutMs:this.#r,multipart:!0})}catch(s){throw a?.aborted?tz(a):s?.code?.startsWith?.("telegram-")?E_e(s):d_e(s)}}async editMessageText({chatId:r,messageId:o,text:i,signal:e}){return this.#o("editMessageText",{chat_id:r,message_id:o,text:i,link_preview_options:{is_disabled:!0}},{signal:e})}async sendChatAction({chatId:r,messageThreadId:o,signal:i}){return this.#o("sendChatAction",{chat_id:r,action:"typing",...o?{message_thread_id:o}:{}},{signal:i})}async setMyCommands({commands:r,scope:o,languageCode:i,signal:e}={}){if(!Array.isArray(r)||r.length===0||r.some(a=>!u_e(a)))throw new TypeError("Telegram bot commands are invalid");return this.#o("setMyCommands",{commands:r,...o?{scope:o}:{},...Ng(i)?{language_code:Ng(i)}:{}},{signal:e})}async setChatMenuButton({menuButton:r=yk,signal:o}={}){if(!r||typeof r!="object"||Array.isArray(r))throw new TypeError("Telegram menu button is invalid");return this.#o("setChatMenuButton",{menu_button:r},{signal:o})}async#o(r,o,{signal:i,timeoutMs:e=15e3,multipart:a=!1}={}){let A=new URL(this.#t);A.pathname=`${A.pathname.replace(/\/$/,"")}/bot${this.#e}/${r}`;let t;try{t=await this.#A(A,{method:"POST",...a?{}:{headers:{"content-type":"application/json"}},body:a?o:JSON.stringify(o),signal:Az(i,e),redirect:"error"})}catch(g){throw g?.name==="AbortError"||g?.name==="TimeoutError"?g:new Error(`Telegram ${r} transport failed`)}let s;try{s=await t.json()}catch{let g=new Error(`Telegram ${r} returned invalid JSON`);throw g.status=t?.status,g}if(!t.ok||s?.ok!==!0){let g=Ng(s?.description),c=new Error(g??`Telegram ${r} failed`);c.code=Number.isInteger(s?.error_code)?`telegram-${s.error_code}`:"telegram-api-error",c.status=t.status,Number.isInteger(s?.error_code)&&(c.providerCode=s.error_code);let I=Number(s?.parameters?.retry_after);throw Number.isFinite(I)&&I>=0&&(c.retry_after=I,c.retryAfter=I),c}return s.result}};async function az(n,r={}){let i=await new IC({token:n,...r}).getMe();if(!i?.id||i?.is_bot!==!0)throw new Error("Telegram token does not belong to a bot");return{platformId:String(i.id),name:Ng([i.first_name,i.last_name].filter(Boolean).join(" "))??"Telegram\u673A\u5668\u4EBA",username:Ng(i.username)}}var mk=Object.freeze({key:"telegram",label:"Telegram",connectionLabel:" Bot API \u957F\u8F6E\u8BE2"}),$y=class extends pn{constructor(r){super({descriptor:mk,...r})}};var lC=class extends hl{#e;constructor(r){super({...r,descriptor:mk,inspectToken:r.inspectToken??az,deriveIdentity:$8,maskPlatformId:ez}),this.#e=r.configStore}status(){let r=super.status();return{...r,bots:r.bots.map(o=>{let i=this.#e.get(o.botId),e=_g(i??{});return{...o,accessPolicy:e}})}}async setAccessPolicy(r,o){let i=_g(o);return this.updateBotConfig(r,e=>({...e,...i}))}};var B_e=Object.freeze([{command:"new",description:"\u5F00\u542F\u4E00\u4E2A\u5168\u65B0\u4F1A\u8BDD"},{command:"compact",description:"\u538B\u7F29\u5F53\u524D\u4F1A\u8BDD\u7684\u8F83\u65E9\u4E0A\u4E0B\u6587"},{command:"workspace",description:"\u5207\u6362\u5DE5\u4F5C\u533A"},{command:"workspacelist",description:"\u5217\u51FA\u5DE5\u4F5C\u533A\u7EDD\u5BF9\u8DEF\u5F84"},{command:"sessionlist",description:"\u5217\u51FA\u4F1A\u8BDD ID \u548C\u6807\u9898"},{command:"session",description:"\u5C06\u5F53\u524D\u804A\u5929\u7ED1\u5B9A\u5230\u6307\u5B9A\u4F1A\u8BDD"},{command:"models",description:"\u6309\u5E8F\u53F7\u5217\u51FA\u6240\u6709\u53EF\u7528\u6A21\u578B"},{command:"model",description:"\u67E5\u770B\u6216\u5207\u6362\u5F53\u524D\u4F1A\u8BDD\u6A21\u578B"},{command:"presetlist",description:"\u5217\u51FA\u53EF\u7528 Agent Preset"},{command:"preset",description:"\u67E5\u770B\u6216\u8BBE\u7F6E\u65B0\u4F1A\u8BDD Agent Preset"},{command:"stop",description:"\u505C\u6B62\u5F53\u524D\u4EFB\u52A1"},{command:"steer",description:"\u7EA0\u504F\u5F53\u524D\u4EFB\u52A1"},{command:"status",description:"\u68C0\u67E5\u8FDE\u63A5\u72B6\u6001"},{command:"help",description:"\u663E\u793A\u5E2E\u52A9"}]);function f_e(n){return n.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}function Q_e(n,r){return r?[[n?.text,n?.entities],[n?.caption,n?.caption_entities]].some(([o,i])=>typeof o=="string"&&Array.isArray(i)&&i.some(e=>e?.type!=="mention"||!Number.isInteger(e.offset)||!Number.isInteger(e.length)?!1:o.slice(e.offset,e.offset+e.length).toLowerCase()===`@${r.toLowerCase()}`)):!1}function y_e(n,r){return!r||typeof n!="string"?n:n.replace(new RegExp(`@${f_e(r)}\\b`,"ig"),"").trim()}var m_e=new Set(["image/jpeg","image/png","image/webp","image/gif"]),S_e=new Map([[".jpg","image/jpeg"],[".jpeg","image/jpeg"],[".png","image/png"],[".webp","image/webp"],[".gif","image/gif"]]);function D_e(n){let r=n?.mime_type??n?.mimetype,o=typeof r=="string"?r.toLowerCase():"";if(m_e.has(o))return o;let i=typeof n?.file_name=="string"?n.file_name.toLowerCase():"";for(let[e,a]of S_e)if(i.endsWith(e))return a;return null}function oz(n){return Number.isSafeInteger(n)&&n>=0?n:void 0}function iz(n){return oz(n?.file_size)??(Number(n?.width)||0)*(Number(n?.height)||0)}function w_e(n,r){let o,i,e;if(Array.isArray(n?.photo)&&n.photo.length>0)o=n.photo.reduce((a,A)=>iz(A)>iz(a)?A:a),i="image/jpeg",e=`${o.file_unique_id??o.file_id??"telegram-photo"}.jpg`;else if(n?.document){let a=D_e(n.document);if(!a)return null;o=n.document,i=a,e=typeof o.file_name=="string"?o.file_name:void 0}return!o||typeof o.file_id!="string"?null:{name:e,mediaType:i,size:oz(o.file_size),load:a=>r(o.file_id,a)}}function O_e(n,{botId:r,username:o,loadFile:i=async()=>{throw new Error("Telegram file downloader is unavailable")}}){let e=n?.message,a=e?.chat?.id,A=e?.from?.id,t=e?.message_id;if(!Number.isSafeInteger(n?.update_id)||a===void 0||A===void 0||!Number.isSafeInteger(t)||!["private","group","supergroup"].includes(e.chat?.type))return null;let s=e.chat.type==="private",g=s||String(e.reply_to_message?.from?.id??"")===String(r)||Q_e(e,o),c=Number.isSafeInteger(e.message_thread_id)?e.message_thread_id:void 0,I=w_e(e,i);return{messageId:String(n.update_id),senderId:String(A),senderIsBot:e.from?.is_bot===!0,kind:s?"direct":"group",conversationId:c===void 0?String(a):`${a}:${c}`,content:y_e(e.text??e.caption??"",o),images:I?[I]:[],addressed:g,replyTarget:{chatId:a,replyToMessageId:t,messageThreadId:c},connectionTestTarget:{chatId:a,messageThreadId:c}}}function b_e(n,{accessMode:r=cC.compatible,allowedPrivateUserIds:o=new Set}={}){return r!==cC.privateAllowlist?!0:n?.kind==="direct"&&o instanceof Set&&o.has(String(n.senderId))}var Sk=class{#e;#A;constructor({api:r,signal:o}){this.#e=r,this.#A=o}async sendText(r,o){let i=Xi(o,4e3),e=[];for(let[a,A]of i.entries()){let t=await this.#e.sendMessage({chatId:r.chatId,text:A,replyToMessageId:a===0?r.replyToMessageId:void 0,messageThreadId:r.messageThreadId,signal:this.#A});Number.isSafeInteger(t?.message_id)&&e.push(String(t.message_id))}return{providerMessageIds:e}}sendTyping(r){return this.#e.sendChatAction({chatId:r.chatId,messageThreadId:r.messageThreadId,signal:this.#A})}sendFile(r,o){return this.#e.sendDocument({chatId:r.chatId,file:o,replyToMessageId:r.replyToMessageId,messageThreadId:r.messageThreadId,signal:this.#A})}async openStream(r){return YB({limit:4e3,create:async i=>(await this.#e.sendMessage({chatId:r.chatId,text:i,replyToMessageId:r.replyToMessageId,messageThreadId:r.messageThreadId,signal:this.#A})).message_id,edit:(i,e)=>this.#e.editMessageText({chatId:r.chatId,messageId:i,text:e,signal:this.#A}),sendRemainder:i=>this.#e.sendMessage({chatId:r.chatId,text:i,messageThreadId:r.messageThreadId,signal:this.#A}),messageIdForResult:i=>i?.message_id}).start()}};function M_e(){return{startedAt:null,ready:!1,connectionState:"idle",harnessReachable:!1,lastCheckedAt:null,lastConnectedAt:null,lastError:null,...fi()}}var pC=class{#e;#A;#t;#r;#o;#i;#I;#s;#l;#a=M_e();#n=null;#g=null;#c=null;#p=null;#h=null;constructor({config:r,token:o,harness:i,state:e,logger:a=console,replyTimeoutMs:A=6e5,createApi:t=s=>new IC(s)}){if(!r||!o||!i||!e)throw new TypeError("TelegramRuntime requires config, token, Harness, and state");this.#e=r,this.#A=o,this.#t=i,this.#r=e,this.#o=a,this.#i=A,this.#I=t;let s=_g(r);this.#s=s.accessMode,this.#l=new Set(s.allowedUsers)}get status(){return structuredClone(this.#a)}async sendConnectionTest(r){if(!this.#a.ready||!this.#g){let o=new Error("Telegram bot is not connected");throw o.code="test-target-unavailable",o}return this.#g.sendConnectionTest(r)}async start(){return this.#a.ready&&this.#p?this.status:this.#h?this.#h:(this.#h=this.#d().finally(()=>{this.#h=null}),this.#h)}async#d(){await this.stop(),this.#a.startedAt=new Date().toISOString(),this.#a.connectionState="connecting",this.#a.lastError=null,await this.#t.ensureRunning(),this.#a.harnessReachable=!0;let r=new AbortController;this.#c=r;let o=this.#I({token:this.#A});this.#n=o;try{let i=await o.getMe({signal:r.signal});if(String(i?.id??"")!==this.#e.platformId||i?.is_bot!==!0)throw new Error("Telegram token identity does not match the saved bot");let e=await o.getWebhookInfo({signal:r.signal});if(typeof e?.url=="string"&&e.url){let s=new Error("\u8BE5 Telegram \u673A\u5668\u4EBA\u5DF2\u914D\u7F6E Webhook\uFF0C\u8BF7\u5148\u5728\u539F\u670D\u52A1\u4E2D\u79FB\u9664 Webhook \u540E\u91CD\u8BD5\u3002");throw s.code="webhook-configured",s}try{await o.setMyCommands({commands:B_e,signal:r.signal}),await o.setChatMenuButton({menuButton:yk,signal:r.signal})}catch(s){this.#o.warn?.(`[dsh-im:telegram] bot ${this.#e.botId} command menu setup failed:`,s)}let a=new Sk({api:o,signal:r.signal});this.#g=new $y({bot:a,harness:this.#t,state:this.#r,status:this.#a,logger:this.#o,replyTimeoutMs:this.#i,signal:r.signal});let A=this.#r.cursor();if(A===null){let s=await o.getUpdates({offset:-1,timeout:0,signal:r.signal});A=s.length>0?s.at(-1).update_id+1:0,await this.#r.setCursor(A)}let t=Date.now();return this.#a.ready=!0,this.#a.connectionState="connected",this.#a.lastCheckedAt=t,this.#a.lastConnectedAt=t,this.#p=this.#E(A,r.signal),this.#p.catch(s=>{r.signal.aborted||(this.#a.ready=!1,this.#a.connectionState="failed",this.#a.lastError=s?.message??String(s),this.#o.error?.(`[dsh-im:telegram] bot ${this.#e.botId} polling stopped:`,s))}),this.status}catch(i){throw this.#a.ready=!1,this.#a.connectionState="failed",this.#a.lastError=i?.message??String(i),await this.stop(),i}}async#E(r,o){let i=r;for(;!o.aborted;){let e=await this.#n.getUpdates({offset:i,timeout:25,signal:o});this.#a.lastCheckedAt=Date.now();for(let a of e){if(o.aborted)return;let A=O_e(a,{botId:this.#e.platformId,username:this.#e.username,loadFile:(t,s)=>this.#n.downloadFile({fileId:t,...s})});A&&b_e(A,{accessMode:this.#s,allowedPrivateUserIds:this.#l})?this.#g.accept(A).catch(t=>{o.aborted||this.#o.error?.(`[dsh-im:telegram] bot ${this.#e.botId} message handling failed:`,t)}):A&&(this.#a.messagesRejected+=1,this.#a.lastRejectedAt=new Date().toISOString()),i=a.update_id+1,await this.#r.setCursor(i)}}}async stop(){let r=this.#p,o=this.#g;return this.#c?.abort(),this.#c=null,this.#p=null,this.#n=null,this.#g=null,await Promise.race([r?.catch(()=>{})??Promise.resolve(),new Promise(i=>setTimeout(i,2e3))]),await Promise.race([o?.waitForIdle()??Promise.resolve(),new Promise(i=>setTimeout(i,2e3))]),this.#a.ready=!1,this.#a.connectionState="idle",this.status}};var em=class extends En{};function Dk(n,r={},o={}){return JB(n,r,o,{channel:"telegram",ConfigStore:Zy,StateStore:em,HarnessClient:Xy,Controller:lC,Runtime:pC})}var nz="/telegram",wk=Object.freeze({...Da,setAccessPolicy:"bot.access-policy.set"}),__e=Object.freeze(Object.values(wk));function sz(n){if(typeof n?.setAccessPolicy!="function")throw new TypeError("A complete Telegram controller is required (setAccessPolicy)");let r=zB(n,{channel:"Telegram"});return async(o,i,e)=>{if(o!==wk.setAccessPolicy)return r(o,i,e);if(e?.aborted)return{ok:!1,error:{code:"cancelled",message:"The request was cancelled."}};let a=i&&typeof i=="object"&&!Array.isArray(i)?Object.keys(i):[];if(a.length!==3||!a.every(t=>["botId","accessMode","allowedUsers"].includes(t))||typeof i.botId!="string"||!/^[A-Za-z0-9_-]{1,128}$/.test(i.botId))return{ok:!1,error:{code:"bad-request",message:"bot.access-policy.set requires a valid policy."}};let A;try{A=_g(i)}catch{return{ok:!1,error:{code:"bad-request",message:"\u8BF7\u8F93\u5165\u6709\u6548\u7684 Telegram \u8BBF\u95EE\u6A21\u5F0F\u548C\u6570\u5B57 User ID\u3002"}}}try{let t=await n.setAccessPolicy(i.botId,A);return e?.aborted?{ok:!1,error:{code:"cancelled",message:"The request was cancelled."}}:{ok:!0,value:t}}catch{return e?.aborted?{ok:!1,error:{code:"cancelled",message:"The request was cancelled."}}:{ok:!1,error:{code:"telegram-operation-failed",message:"Telegram \u64CD\u4F5C\u5931\u8D25\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5\u3002"}}}}}function Am(n,r,o){if(!n?.connection?.rpc||typeof n.connection.rpc.handle!="function")throw new TypeError("DSH Host Connection RPC is required");return n.connection.rpc.handle(nz,sz(r),{authority:hr(o)})}async function gz(n,r={}){if(r?.controller)return Am(n,r.controller,r.rpcAuthority);let o=await Dk(n,r,r.internals??{}),i=Am(n,o.controller,r.rpcAuthority);return n.effect(()=>async()=>o.close(),"dsh-im: close Telegram bot connections"),i}import{unlink as dNe}from"node:fs/promises";import{homedir as hNe}from"node:os";import{join as CC,resolve as EI}from"node:path";import{createHash as N_e}from"node:crypto";import{mkdir as T_e,readFile as R_e,rename as k_e,unlink as v_e,writeFile as P_e}from"node:fs/promises";import{dirname as G_e}from"node:path";var Ok=Object.freeze({version:1,bots:Object.freeze([])});function cI(n){return typeof n=="string"&&n.trim()?n.trim():null}function cz(n){let r=cI(n);return r&&/^wecom_[a-f0-9]{24}$/.test(r)?r:null}function F_e(n){let r=cI(n);return r&&/^DSH_WECOM_BOT_SECRET_[A-F0-9]{24}$/.test(r)?r:null}function rm(n){let r=cI(n);if(!r)throw new TypeError("Enterprise WeChat bot ID is required");let o=N_e("sha256").update(r).digest("hex").slice(0,24);return{botId:`wecom_${o}`,secretRef:`DSH_WECOM_BOT_SECRET_${o.toUpperCase()}`}}function bk(n){let r=cI(n)??"";return r?r.length<=10?`${r.slice(0,3)}\u2022\u2022\u2022`:`${r.slice(0,6)}\u2022\u2022\u2022\u2022${r.slice(-4)}`:"\u4F01\u4E1A\u5FAE\u4FE1\u673A\u5668\u4EBA"}function Iz(n){if(!n||typeof n!="object")return null;let r=cz(n.botId),o=cI(n.remoteBotId),i=F_e(n.secretRef);if(!r||!o||!i)return null;let e=rm(o);return e.botId!==r||e.secretRef!==i?null:Object.freeze({botId:r,remoteBotId:o,secretRef:i,createdAt:cI(n.createdAt)??new Date().toISOString(),connectedAt:cI(n.connectedAt)})}function L_e(n){if(!n||n.version!==1||!Array.isArray(n.bots))return null;let r=n.bots.map(Iz);if(r.some(a=>a===null))return null;let o=new Set,i=new Set,e=new Set;for(let a of r){if(o.has(a.botId)||i.has(a.remoteBotId)||e.has(a.secretRef))return null;o.add(a.botId),i.add(a.remoteBotId),e.add(a.secretRef)}return Object.freeze({version:1,bots:Object.freeze(r)})}var tm=class{#e;#A=Ok;#t=Promise.resolve();constructor(r){this.#e=r}async load(){try{let r=L_e(JSON.parse(await R_e(this.#e,"utf8")));if(!r)throw new Error("dsh-im Enterprise WeChat config contains invalid bot data");this.#A=r}catch(r){if(r?.code!=="ENOENT")throw r;this.#A=Ok}return this}list(){return structuredClone(this.#A.bots)}get(r){let o=this.#A.bots.find(i=>i.botId===r);return o?structuredClone(o):null}getByRemoteBotId(r){let o=this.#A.bots.find(i=>i.remoteBotId===r);return o?structuredClone(o):null}async save(r){let o=Iz(r);if(!o)throw new Error("Refusing to persist incomplete Enterprise WeChat bot data");return this.#r(i=>{let e=i.find(t=>t.remoteBotId===o.remoteBotId&&t.botId!==o.botId),a=i.find(t=>t.secretRef===o.secretRef&&t.botId!==o.botId);if(e||a)throw new Error("Duplicate Enterprise WeChat bot identity");let A=i.findIndex(t=>t.botId===o.botId);return A===-1?i.push(o):i[A]=o,structuredClone(o)})}async remove(r){if(!cz(r))throw new TypeError("Invalid Enterprise WeChat bot ID");return this.#r(o=>{let i=o.findIndex(a=>a.botId===r);if(i===-1)return null;let[e]=o.splice(i,1);return structuredClone(e)})}async clear(){let r=this.#t.then(async()=>{try{await v_e(this.#e)}catch(o){if(o?.code!=="ENOENT")throw o}this.#A=Ok});this.#t=r.then(()=>{},()=>{}),await r}async#r(r){let o,i=this.#t.then(async()=>{let e=[...this.#A.bots];o=r(e);let a=Object.freeze({version:1,bots:Object.freeze(e)});await this.#o(a),this.#A=a});return this.#t=i.then(()=>{},()=>{}),await i,o}async#o(r){await T_e(G_e(this.#e),{recursive:!0,mode:448});let o=`${this.#e}.tmp`;await P_e(o,`${JSON.stringify(r,null,2)} `,{encoding:"utf8",mode:384}),await k_e(o,this.#e)}};var am=class extends ir{constructor(r){super({...r,rpcIdPrefix:"wecom",logPrefix:"dsh-wecom"})}};var U_e="https://work.weixin.qq.com/ai/qc/generate",H_e="https://work.weixin.qq.com/ai/qc/query_result";function lz(){return process.platform==="win32"?2:process.platform==="linux"?3:1}function Qp(n){return typeof n=="string"&&n.trim()?n.trim():null}function K_e(n){let r=Qp(n);if(!r)return null;try{let o=new URL(r);return o.protocol==="https:"&&o.hostname==="work.weixin.qq.com"&&(!o.port||o.port==="443")?o.href:null}catch{return null}}function Y_e(n,r){return n?AbortSignal.any([n,AbortSignal.timeout(r)]):AbortSignal.timeout(r)}async function pz(n,r,o){let i=await n(r,{method:"GET",redirect:"error",signal:Y_e(o,1e4),headers:{accept:"application/json"}});if(!i.ok)throw new Error(`Enterprise WeChat QR service returned HTTP ${i.status}`);return i.json()}var im=class{#e;#A;#t;#r;constructor({fetch:r=globalThis.fetch,clock:o=()=>Date.now(),source:i="deepseek-harness",platform:e=lz()}={}){if(typeof r!="function")throw new TypeError("A fetch implementation is required");this.#e=r,this.#A=o,this.#t=i,this.#r=[1,2,3].includes(e)?e:lz()}async start({signal:r}={}){let o=new URL(U_e);o.searchParams.set("source",this.#t),o.searchParams.set("plat",String(this.#r));let i=await pz(this.#e,o,r),e=Qp(i?.data?.scode),a=K_e(i?.data?.auth_url);if(!e||!a)throw new Error("Enterprise WeChat QR service returned invalid data");return{scode:e,verificationUrl:a,expiresAt:this.#A()+3e5,pollIntervalMs:3e3}}async poll({scode:r,signal:o}={}){let i=Qp(r);if(!i)throw new TypeError("Enterprise WeChat QR poll code is required");let e=new URL(H_e);e.searchParams.set("scode",i);let a=await pz(this.#e,e,o),A=Qp(a?.data?.status)?.toLowerCase();if(A==="success"){let t=Qp(a?.data?.bot_info?.botid),s=Qp(a?.data?.bot_info?.secret);if(!t||!s)throw new Error("Enterprise WeChat QR result omitted bot credentials");return{status:"success",remoteBotId:t,secret:s}}return["expired","timeout"].includes(A)?{status:"expired"}:["fail","failed","error"].includes(A)?{status:"failed"}:{status:"waiting"}}};import{mkdir as x_e,readFile as j_e,rename as J_e,unlink as q_e,writeFile as z_e}from"node:fs/promises";import{dirname as V_e}from"node:path";var om=Object.freeze({version:1,sessions:{},seenMessageIds:[]});function W_e(n){if(!n||typeof n!="object")return structuredClone(om);let r={};if(n.sessions&&typeof n.sessions=="object"&&!Array.isArray(n.sessions))for(let[o,i]of Object.entries(n.sessions))typeof o=="string"&&typeof i=="string"&&i&&(r[o]=i);return{version:1,sessions:r,seenMessageIds:Array.isArray(n.seenMessageIds)?n.seenMessageIds.filter(o=>typeof o=="string").slice(-1e3):[]}}var nm=class{#e;#A=structuredClone(om);#t=Promise.resolve();constructor(r){this.#e=r}async load(){try{this.#A=W_e(JSON.parse(await j_e(this.#e,"utf8")))}catch(r){if(r?.code!=="ENOENT")throw r;this.#A=structuredClone(om),await this.#r()}return this}sessionFor(r){return this.#A.sessions[r]??null}async setSession(r,o){this.#A.sessions[r]=o,await this.#r()}async clearSession(r){delete this.#A.sessions[r],await this.#r()}async clearSessions(){this.#A.sessions={},await this.#r()}hasSeen(r){return this.#A.seenMessageIds.includes(r)}async markSeen(r){this.hasSeen(r)||(this.#A.seenMessageIds.push(r),this.#A.seenMessageIds.length>1e3&&this.#A.seenMessageIds.splice(0,this.#A.seenMessageIds.length-1e3),await this.#r())}async remove(){try{await q_e(this.#e)}catch(r){if(r?.code!=="ENOENT")throw r}this.#A=structuredClone(om)}async#r(){let r=`${JSON.stringify(this.#A,null,2)} diff --git a/src/channels/qq/qq-bridge.mjs b/src/channels/qq/qq-bridge.mjs index 9ac40fd..7660829 100644 --- a/src/channels/qq/qq-bridge.mjs +++ b/src/channels/qq/qq-bridge.mjs @@ -619,11 +619,13 @@ export class QqHarnessBridge { signal: this.#signal, control: { owner: this, key }, onUpdate: stream ? async (update) => { + // status 帧(如工具结束后的“正在整理结果…”)不推送: + // 保持上一帧(工具名或已生成文本),避免中间过程反复闪提示。 const progress = update.type === 'text' ? update.text : update.type === 'tool' ? `正在使用${update.name}…` - : update.text; + : null; if (progress) await stream.update(progress); } : undefined, onInteraction: (interaction) => this.#handleInteraction(interaction, { diff --git a/test/channels/qq/bridge.test.mjs b/test/channels/qq/bridge.test.mjs index 786a636..ad66d4e 100644 --- a/test/channels/qq/bridge.test.mjs +++ b/test/channels/qq/bridge.test.mjs @@ -458,6 +458,42 @@ test('QQ private messages stream Harness snapshots and finalize once', async () assert.equal(bridge.status.messagesReplied, 1); }); +test('QQ progress stream keeps the last frame when a tool finishes', async () => { + const frames = []; + const bridge = new QqHarnessBridge({ + bot: { + sendText: async () => {}, + openStream: () => ({ + update: async (text) => frames.push(text), + complete: async () => frames.push('DONE'), + cancel() {}, + }), + }, + ownerUserOpenid: 'owner-openid', + harness: { + sessionExists: async () => true, + ask: async (_session, _text, { onUpdate }) => { + await onUpdate({ type: 'tool', name: 'bash' }); + // 工具结束后 Harness 客户端会下发 status 帧“正在整理结果…”。 + await onUpdate({ type: 'status', text: '正在整理结果…' }); + await onUpdate({ type: 'tool', name: 'read' }); + await onUpdate({ type: 'status', text: '正在整理结果…' }); + return '最终回答'; + }, + }, + state: { + hasSeen: () => false, + markSeen: async () => {}, + sessionFor: () => 'session-status', + setSession: async () => {}, + clearSession: async () => {}, + }, + }); + + await bridge.accept(message({ messageId: 'msg-status-frame' })); + assert.deepEqual(frames, ['正在使用bash…', '正在使用read…', '最终回答', 'DONE']); +}); + test('QQ delivers final group answers as markdown messages', async () => { const sentText = []; const markdownCalls = [];