diff --git a/lib/index.js b/lib/index.js index 1c76f1c..7c4d1a3 100644 --- a/lib/index.js +++ b/lib/index.js @@ -200,7 +200,7 @@ ${a.join(` `,r);e10?`\uFF08\u7B2C ${o+1}/${A} \u9875\uFF09`:""}`)},...a.flatMap((s,g)=>{let c=`${g+1}. ${T0(s.title)}${s.archived===!0?"\uFF08\u5DF2\u5F52\u6863\uFF09":""}`;return[Xa(c,`use:${s.sessionId}`),Xa("\u{1F441} \u5173\u6CE8",`watch:${s.sessionId}`)]})];return o>0&&t.push(Xa("\u25C0 \u4E0A\u4E00\u9875",`sessions:${o-1}`)),o+1Xa(`${e+1}. ${i}${i===r?"\uFF08\u5F53\u524D\uFF09":""}`,`workspace:${i}`))];return ll("\u{1F5C2} \u5DE5\u4F5C\u533A",o)}function sj(){return["\u{1F916} \u52A9\u624B\u83DC\u5355\uFF08\u56DE\u590D\u6570\u5B57\u5373\u53EF\uFF0C\u65E0\u9700\u8BB0\u547D\u4EE4\uFF09","","1 \xB7 /sessionlist \u5217\u51FA\u4F1A\u8BDD\uFF08\u56DE\u590D\u6570\u5B57\u7ED1\u5B9A\uFF09","2 \xB7 /workspacelist \u5217\u51FA\u5DE5\u4F5C\u533A\uFF08\u56DE\u590D\u6570\u5B57\u5207\u6362\uFF09","3 \xB7 /new \u5F00\u542F\u65B0\u4F1A\u8BDD","4 \xB7 /status \u8FDE\u63A5\u72B6\u6001","5 \xB7 /help \u672C\u5E2E\u52A9","6 \xB7 /repair \u4FEE\u590D\u5361\u7247\u6309\u94AE\uFF08\u8BF7\u56DE\u590D\u6570\u5B57 6\uFF09","","\u76F4\u63A5\u53D1\u9001\u6587\u5B57/\u56FE\u7247\u5373\u7EE7\u7EED\u5F53\u524D\u4F1A\u8BDD\u3002","/session ID \u6216\u5E8F\u53F7 \u7ED1\u5B9A\u5DF2\u6709\u4F1A\u8BDD","/compact \u538B\u7F29\u4E0A\u4E0B\u6587","/workspace \u7EDD\u5BF9\u8DEF\u5F84 \u5207\u6362\u5DE5\u4F5C\u533A"].join(` `)}function gj(n){let r=n.length===0?[{tag:"div",text:Si("\u5F53\u524D\u6CA1\u6709\u5173\u6CE8\u7684\u4F1A\u8BDD\u3002\n`/watch ` \u5173\u6CE8\u540E\uFF0C\u4EFB\u52A1\u5B8C\u6210\u4F1A\u81EA\u52A8\u63A8\u9001\u3002")}]:[{tag:"div",text:Si("\u4EFB\u52A1\u5B8C\u6210\u4F1A\u81EA\u52A8\u63A8\u9001\uFF0C\u56DE\u590D\u6570\u5B57\u6216\u70B9\u6309\u94AE\u53D6\u6D88\u5173\u6CE8\uFF1A")},...n.map((o,i)=>Xa(`${i+1}. ${T0(o.title)}`,`unwatch:${o.sessionId}`))];return ll("\u{1F441} \u5173\u6CE8\u5217\u8868",r)}function cj(n,r,o){let i=o==="stopped"?"\u5DF2\u505C\u6B62":o==="aborted"?"\u5DF2\u4E2D\u6B62":"\u5DF2\u5B8C\u6210";return ll("\u2705 \u4EFB\u52A1\u5B8C\u6210",[{tag:"div",text:Si(`**${T0(r)}** -\`${n}\``)},{tag:"div",text:Si(`**\u72B6\u6001**\uFF1A${i}`)},Xa("\u6253\u5F00\u4F1A\u8BDD\u5217\u8868","sessions"),Xa("\u5DE5\u4F5C\u533A","workspaces"),{tag:"div",text:Si("\u7ED1\u5B9A\u8BE5\u4F1A\u8BDD\u540E\u53EF\u7EE7\u7EED\u8FFD\u95EE\uFF0C\u8F93\u5165\u6587\u5B57\u5373\u53EF\u3002")}])}import{mkdir as gfe,readFile as cfe,rename as Ife,writeFile as lfe}from"node:fs/promises";import{dirname as Efe}from"node:path";var pfe=Object.freeze({version:1,sessions:{},seenMessageIds:[],watches:{},chatTargets:{},includeArchivedSessions:!0}),_Q=20;function R0(n){return n&&typeof n=="object"&&typeof n.sessionId=="string"&&n.sessionId.length>0&&typeof n.chatId=="string"&&n.chatId.length>0}var MQ=class{#e;#A=structuredClone(pfe);#r=Promise.resolve();constructor(r){this.#e=r}async load(){try{let r=JSON.parse(await cfe(this.#e,"utf8"));this.#A={version:1,sessions:r.sessions&&typeof r.sessions=="object"?r.sessions:{},seenMessageIds:Array.isArray(r.seenMessageIds)?r.seenMessageIds.slice(-1e3):[],watches:r.watches&&typeof r.watches=="object"?r.watches:{},chatTargets:r.chatTargets&&typeof r.chatTargets=="object"?r.chatTargets:{},includeArchivedSessions:typeof r.includeArchivedSessions=="boolean"?r.includeArchivedSessions:!0}}catch(r){if(r?.code!=="ENOENT")throw r;await this.#t()}return this}sessionFor(r){return this.#A.sessions[r]??null}async setSession(r,o){this.#A.sessions[r]=o,await this.#t()}async clearSession(r){delete this.#A.sessions[r],await this.#t()}async clearSessions(){this.#A.sessions={},await this.#t()}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.#t())}snapshot(){return structuredClone(this.#A)}watchEntries(r){let o=this.#A.watches[r];return Array.isArray(o)?o.filter(R0):[]}watchEntry(r,o){return this.watchEntries(r).find(i=>i.sessionId===o)??null}async setWatch(r,o){let i=this.#A.watches[r]??[],e=i.findIndex(a=>a.sessionId===o.sessionId);e===-1?(i.length>=_Q&&i.shift(),i.push(o)):i[e]=o,this.#A.watches[r]=i,await this.#t()}async removeWatch(r,o){let i=this.#A.watches[r]??[];this.#A.watches[r]=i.filter(e=>e.sessionId!==o),await this.#t()}async clearWatches(r){delete this.#A.watches[r],await this.#t()}keysWatching(r){return Object.entries(this.#A.watches).filter(([,o])=>Array.isArray(o)&&o.some(i=>R0(i)&&i.sessionId===r)).map(([o])=>o)}watchedSessionIds(){let r=new Set;for(let o of Object.values(this.#A.watches))if(Array.isArray(o))for(let i of o)R0(i)&&r.add(i.sessionId);return[...r]}sessionKeysFor(r){return Object.entries(this.#A.sessions).filter(([,o])=>o===r).map(([o])=>o)}chatTargetFor(r){let o=this.#A.chatTargets[r];return typeof o=="string"&&o.length>0?o:null}async setChatTarget(r,o){this.#A.chatTargets[r]!==o&&(this.#A.chatTargets[r]=o,await this.#t())}includesArchivedSessions(){return this.#A.includeArchivedSessions!==!1}async setIncludeArchivedSessions(r){this.#A.includeArchivedSessions=r!==!1,await this.#t()}async#t(){let r=JSON.stringify(this.#A,null,2)+` +\`${n}\``)},{tag:"div",text:Si(`**\u72B6\u6001**\uFF1A${i}`)},Xa("\u6253\u5F00\u4F1A\u8BDD\u5217\u8868","sessions"),Xa("\u5DE5\u4F5C\u533A","workspaces"),{tag:"div",text:Si("\u7ED1\u5B9A\u8BE5\u4F1A\u8BDD\u540E\u53EF\u7EE7\u7EED\u8FFD\u95EE\uFF0C\u8F93\u5165\u6587\u5B57\u5373\u53EF\u3002")}])}import{mkdir as gfe,readFile as cfe,rename as Ife,writeFile as lfe}from"node:fs/promises";import{dirname as Efe}from"node:path";var pfe=Object.freeze({version:1,sessions:{},seenMessageIds:[],watches:{},chatTargets:{},includeArchivedSessions:!1}),_Q=20;function R0(n){return n&&typeof n=="object"&&typeof n.sessionId=="string"&&n.sessionId.length>0&&typeof n.chatId=="string"&&n.chatId.length>0}var MQ=class{#e;#A=structuredClone(pfe);#r=Promise.resolve();constructor(r){this.#e=r}async load(){try{let r=JSON.parse(await cfe(this.#e,"utf8"));this.#A={version:1,sessions:r.sessions&&typeof r.sessions=="object"?r.sessions:{},seenMessageIds:Array.isArray(r.seenMessageIds)?r.seenMessageIds.slice(-1e3):[],watches:r.watches&&typeof r.watches=="object"?r.watches:{},chatTargets:r.chatTargets&&typeof r.chatTargets=="object"?r.chatTargets:{},includeArchivedSessions:typeof r.includeArchivedSessions=="boolean"?r.includeArchivedSessions:!1}}catch(r){if(r?.code!=="ENOENT")throw r;await this.#t()}return this}sessionFor(r){return this.#A.sessions[r]??null}async setSession(r,o){this.#A.sessions[r]=o,await this.#t()}async clearSession(r){delete this.#A.sessions[r],await this.#t()}async clearSessions(){this.#A.sessions={},await this.#t()}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.#t())}snapshot(){return structuredClone(this.#A)}watchEntries(r){let o=this.#A.watches[r];return Array.isArray(o)?o.filter(R0):[]}watchEntry(r,o){return this.watchEntries(r).find(i=>i.sessionId===o)??null}async setWatch(r,o){let i=this.#A.watches[r]??[],e=i.findIndex(a=>a.sessionId===o.sessionId);e===-1?(i.length>=_Q&&i.shift(),i.push(o)):i[e]=o,this.#A.watches[r]=i,await this.#t()}async removeWatch(r,o){let i=this.#A.watches[r]??[];this.#A.watches[r]=i.filter(e=>e.sessionId!==o),await this.#t()}async clearWatches(r){delete this.#A.watches[r],await this.#t()}keysWatching(r){return Object.entries(this.#A.watches).filter(([,o])=>Array.isArray(o)&&o.some(i=>R0(i)&&i.sessionId===r)).map(([o])=>o)}watchedSessionIds(){let r=new Set;for(let o of Object.values(this.#A.watches))if(Array.isArray(o))for(let i of o)R0(i)&&r.add(i.sessionId);return[...r]}sessionKeysFor(r){return Object.entries(this.#A.sessions).filter(([,o])=>o===r).map(([o])=>o)}chatTargetFor(r){let o=this.#A.chatTargets[r];return typeof o=="string"&&o.length>0?o:null}async setChatTarget(r,o){this.#A.chatTargets[r]!==o&&(this.#A.chatTargets[r]=o,await this.#t())}includesArchivedSessions(){return this.#A.includeArchivedSessions===!0}async setIncludeArchivedSessions(r){this.#A.includeArchivedSessions=r===!0,await this.#t()}async#t(){let r=JSON.stringify(this.#A,null,2)+` `;this.#r=this.#r.then(async()=>{await gfe(Efe(this.#e),{recursive:!0,mode:448});let o=`${this.#e}.tmp`;await lfe(o,r,{encoding:"utf8",mode:384}),await Ife(o,this.#e)}),await this.#r}};var NQ="\u8FD9\u4E2A\u95EE\u9898\u5DF2\u5728\u5176\u4ED6\u5BA2\u6237\u7AEF\u5904\u7406\uFF0C\u65E0\u9700\u518D\u6B21\u56DE\u7B54\u3002",hfe=30*6e4,dfe=/^\/m(?:enu)?$/i,ufe=/^\/repair(?:\s|$)/i,Bfe=/^\/repair(?:\s+(qr|status|cancel|verify))?\s*$/i,Ij=/^\/watch(?:\s+([^\s]+))?$/i,lj=/^\/unwatch(?:\s+([^\s]+))?$/i,Qfe=/^\/watchlist$/i,ffe=/^\/sessionlist(?:\s|$)/i,yfe=/^\/workspacelist$/i,mfe=/^\d{1,2}$/,Sfe=10*6e4,Dfe=50,wfe=15e3,Ofe=1e3,TQ=new Set(["starting","qr_ready","polling","slow_down","domain_switched","saving"]),Ej=new Set(["succeeded","expired","cancelled","error"]),bfe=new Set(["accounts.feishu.cn","open.feishu.cn","accounts.larksuite.com","open.larksuite.com"]),Mfe=["\u5317\u6C47\u661F\u6CB3 AIOS \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","/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","/repair \u4FEE\u590D\u5361\u7247\u6309\u94AE\u56DE\u8C03","/m\uFF08\u6216 /menu\uFF09 \u6253\u5F00\u4EA4\u4E92\u5361\u7247\u83DC\u5355","/watch [Session ID \u6216\u5E8F\u53F7] \u5173\u6CE8\u4F1A\u8BDD\uFF0C\u4EFB\u52A1\u5B8C\u6210\u81EA\u52A8\u63A8\u9001","/unwatch [Session ID \u6216\u5E8F\u53F7] \u53D6\u6D88\u5173\u6CE8","/watchlist \u67E5\u770B\u5173\u6CE8\u5217\u8868","/archived on|off \u4F1A\u8BDD\u5217\u8868\u662F\u5426\u5305\u542B\u5F52\u6863\u4F1A\u8BDD","/help \u663E\u793A\u672C\u5E2E\u52A9"].join(` `),pj=/^\/archived(?:\s+(on|off))?$/i;function PC(n){switch(n?.code){case"workspace-not-absolute":return"\u5DE5\u4F5C\u533A\u5FC5\u987B\u662F\u7EDD\u5BF9\u8DEF\u5F84\u3002";case"workspace-not-found":return"\u5DE5\u4F5C\u533A\u8DEF\u5F84\u4E0D\u5B58\u5728\u3002";case"workspace-not-directory":return"\u5DE5\u4F5C\u533A\u8DEF\u5F84\u5FC5\u987B\u6307\u5411\u4E00\u4E2A\u76EE\u5F55\u3002";case"workspace-bot-not-found":return"\u673A\u5668\u4EBA\u6B63\u5728\u79FB\u9664\u6216\u5DF2\u91CD\u65B0\u63A5\u5165\uFF0C\u65E0\u6CD5\u64CD\u4F5C\u539F\u4F1A\u8BDD\u7684\u5DE5\u4F5C\u533A\u3002";default:return"\u64CD\u4F5C\u5931\u8D25\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5\u3002"}}function it(n){return typeof n=="string"&&n.trim()?n.trim():null}function kQ(n){return it(n?.sender?.sender_id?.open_id)??it(n?.sender?.sender_id?.user_id)}function _fe(n){return it(n?.sender?.sender_id?.open_id)}function Cj(n,r){return new Promise((o,i)=>{if(r?.aborted){i(r.reason??new DOMException("Aborted","AbortError"));return}let e=setTimeout(a,n);e.unref?.();function a(){r?.removeEventListener("abort",A),o()}function A(){clearTimeout(e),i(r.reason??new DOMException("Aborted","AbortError"))}r?.addEventListener("abort",A,{once:!0})})}function RQ(n,{botId:r}={}){let o=n&&typeof n=="object"?n:{},i=o.registration&&typeof o.registration=="object"?o.registration:o,e=it(i.operation)??it(o.operation);if(e&&e!=="callback_repair")throw new Error("The active Feishu operation is not a callback repair");let a=it(i.botId)??it(o.botId);if(r&&a&&a!==r)throw new Error("The Feishu repair belongs to another bot");let A=it(i.state),t=i.attemptId??i.attempt,s=typeof t=="string"||Number.isFinite(t)?String(t):null;if(!A||!s)throw new Error("Feishu returned an invalid repair status");let g=it(i.verificationUrl)??it(i.qrCodeUrl),c=Number(i.expiresAt),l=Number(i.remainingSeconds),p=Number(i.pollIntervalMs)||Number(i.pollIntervalSeconds)*1e3;return{state:A,attemptId:s,botId:a,verificationUrl:g,expiresAt:Number.isFinite(c)?c:null,remainingSeconds:Number.isFinite(l)?l:null,pollIntervalMs:Number.isFinite(p)&&p>0?p:null,error:i.error&&typeof i.error=="object"?{code:it(i.error.code),message:it(i.error.message)}:null}}function hj(n,r){let o=new URL(n);if(o.protocol!=="https:"||!bfe.has(o.hostname))throw new Error("Feishu returned an untrusted repair URL");if(o.searchParams.get("tp")!=="sdk"||o.searchParams.get("clientID")!==r||o.searchParams.has("createOnly"))throw new Error("Feishu returned an invalid existing-app repair URL");if(o.toString().includes("{{client_id}}")||o.toString().includes("%7B%7Bclient_id%7D%7D"))throw new Error("Feishu returned an unresolved client id placeholder");return o.toString()}function Nfe(n,r){return r.needsPresentation!==!0&&r.questions[r.index]&&kQ(n)===r.actor&&n?.message?.message_type==="text"&&it(vC(n))}function Tfe(n){for(let r of["messagesReceived","messagesReplied","messagesRejected"])n[r]??=0;n.lastMessageAt??=null,n.lastReplyAt??=null,n.lastRejectedAt??=null,n.lastError??=null}var vQ=class{#e;#A;#r;#t;#s=new Map;#i=new Map;#E=new Map;#n=new Map;#I=new Set;#a=new Set;#o=new Set;#c;#g;#l;#h;#C;#p;#u;#B;#d;#f;#S=null;#Q=0;#w;#y;#D=new Map;#M=new Map;#_=null;#T=null;#O=new Set;#b=new Map;constructor({client:r,channel:o,harness:i,state:e,status:a,allowedSenderOpenIds:A=new Set,botId:t,appId:s,repair:g,repairOwnerOpenIds:c,repairPollIntervalMs:l=Ofe,repairLinkWaitMs:p=wfe,replyTimeoutMs:B=6e5,logger:Q=console,signal:f}){if(!r||!i||!e||!a)throw new TypeError("Feishu bridge dependencies are required");if(g!=null){if(!g||typeof g.start!="function"||typeof g.status!="function"||typeof g.cancel!="function")throw new TypeError("Feishu repair capability requires start/status/cancel");if(!it(t)||!it(s))throw new TypeError("Feishu repair capability requires botId and appId")}if(!Number.isFinite(l)||l<=0||!Number.isFinite(p)||p<=0)throw new TypeError("Feishu repair timing values must be positive numbers");this.#e=r,this.#A=o,this.#r=i,this.#t=e,this.#g=a,this.#l=A,this.#u=it(t),this.#B=it(s),this.#d=g??null;let y=c??A;this.#f=new Set([...y??[]].filter(m=>typeof m=="string"&&m&&m!=="*")),this.#w=l,this.#y=p,this.#h=B,this.#C=Q,this.#c=new ma({label:"Feishu",logger:Q}),this.#p=f,Tfe(this.#g),typeof this.#r?.watchHarnessEvents=="function"&&queueMicrotask(()=>this.#ge())}accept(r){if(this.#p?.aborted)return Promise.resolve();let o=it(r?.message?.message_id);if(!o||tj(r))return Promise.resolve();if(!rj(r,this.#l))return this.#g.messagesRejected+=1,this.#g.lastRejectedAt=new Date().toISOString(),this.#C.warn?.("[dsh-feishu] ignored a message from a sender outside the allowlist"),Promise.resolve();if(this.#t.hasSeen(o)||this.#I.has(o))return Promise.resolve();let i;try{i=Aj(r)}catch{return this.#g.messagesRejected+=1,this.#g.lastRejectedAt=new Date().toISOString(),Promise.resolve()}if(r.message.chat_type==="p2p"){let l=it(r.message.chat_id);l&&oi(this.#t,{chatId:l})}this.#I.add(o);let e=this.#Ce(o,"OnIt"),a=_0(r,this.#e),A=it(a.content)??"",t=Ka(A)?li:Ya(A)?pi:null,s=r?.message?.chat_type==="p2p"||Array.isArray(r?.message?.mentions)&&r.message.mentions.length>0;if(t&&s){let l=this.#L(r,o,i,a,t),p;return p=l.then(()=>this.#z(o,e,"DONE")).catch(B=>this.#N(r,o,e,B)).finally(()=>{this.#I.delete(o),this.#o.delete(p)}),this.#o.add(p),p}if(this.#ie(r,i))return Promise.resolve().then(()=>this.#oe(r,o)).then(()=>this.#z(o,e,"DONE")).catch(p=>this.#N(r,o,e,p)).finally(()=>this.#I.delete(o));let g=this.#i.get(i),c=this.#c.claimReply({key:i,actor:kQ(r),messageId:o,text:vC(r)??"",addressed:r?.message?.chat_type==="p2p"||Array.isArray(r?.message?.mentions)&&r.message.mentions.length>0,hasPendingQuestion:!!g,questionCompletion:g?.submitting||g?.claimedReplyMessageId?g.queue:null,isQuestionPending:()=>this.#i.has(i),send:l=>this.#m(r.message.chat_id,l)});if(c){let l=c.process(async()=>this.#t.hasSeen(o)?!1:(await this.#t.markSeen(o),this.#g.lastMessageAt=new Date().toISOString(),this.#g.messagesReceived+=1,!0)),p;return p=l.then(()=>this.#z(o,e,"DONE")).catch(B=>this.#N(r,o,e,B)).finally(()=>{this.#I.delete(o),this.#a.delete(p)}),this.#a.add(p),p}if(g&&kQ(r)!==g.actor)return this.#k(r,o,i,e);if(g?.submitting||g?.claimedReplyMessageId)return this.#k(r,o,i,e);if(g){Nfe(r,g)&&(g.claimedReplyMessageId=o);let p=(g.queue??Promise.resolve()).catch(()=>{}).then(()=>this.#me(r,o,i,g,e));g.queue=p;let B=()=>{g.claimedReplyMessageId===o&&(g.claimedReplyMessageId=null),g.queue===p&&(g.queue=null)},Q;return Q=p.then(()=>(B(),this.#z(o,e,"DONE")),f=>(B(),this.#N(r,o,e,f))).finally(()=>{B(),this.#I.delete(o),this.#a.delete(Q)}),this.#a.add(Q),Q}return this.#k(r,o,i,e)}#k(r,o,i,e,{releaseMessageId:a=!0,alreadyRecorded:A=!1,finalize:t=!0}={}){let g=(this.#s.get(i)??Promise.resolve()).catch(()=>{}).then(()=>this.#v(r,i,{alreadyRecorded:A})),c=t?g.then(()=>this.#z(o,e,"DONE")).catch(p=>this.#N(r,o,e,p)):g,l;return l=c.finally(()=>{a&&this.#I.delete(o),this.#s.get(i)===l&&this.#s.delete(i)}),this.#s.set(i,l),l}async#N(r,o,i,e){if(e?.code==="turn-stopped"){await this.#se(o,i);return}if(this.#p?.aborted){await this.#se(o,i);return}this.#C.error?.("[dsh-feishu] message handling failed:",e?.message??String(e)),this.#g.lastError=e?.message??String(e),await this.#z(o,i,"ERROR"),await this.#m(r.message.chat_id,gi(e)??"\u5904\u7406\u5931\u8D25\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5\u3002\u5982\u679C\u95EE\u9898\u6301\u7EED\uFF0C\u8BF7\u5728 DeepSeek Harness \u7684\u98DE\u4E66\u63D2\u4EF6\u9875\u9762\u68C0\u67E5\u8FDE\u63A5\u72B6\u6001\u3002").catch(()=>{})}async waitForIdle(){await Promise.allSettled([...this.#s.values(),...[...this.#i.values()].flatMap(r=>r.queue?[r.queue]:[]),...this.#a,...this.#o])}async#L(r,o,i,e,a){if(this.#p?.throwIfAborted(),this.#t.hasSeen(o))return;await this.#t.markSeen(o),this.#g.lastMessageAt=new Date().toISOString(),this.#g.messagesReceived+=1;let A=await a(it(e.content)??"",this.#r,this.#t,i,{signal:this.#p,hasImages:Jt(e),pendingInteraction:this.#i.has(i)||this.#c.hasPending(i),control:{owner:this,key:i}});A?.stopped&&await Promise.allSettled([this.#pe(i),this.#c.closeRoute(i)]);for(let t of A?.messages??[A?.message])t&&await this.#m(r.message.chat_id,t);this.#g.lastError=null}async#v(r,o,{alreadyRecorded:i=!1}={}){this.#p?.throwIfAborted();let e=r.message.message_id;if(!i){if(this.#t.hasSeen(e))return;await this.#t.markSeen(e),this.#g.lastMessageAt=new Date().toISOString(),this.#g.messagesReceived+=1}let a=_0(r,this.#e),A=a.content,t=Jt(a),s=r.message.message_type==="text"&&!t?A:null;if(this.#he(o,r.message.chat_id),!A&&!t){await this.#m(r.message.chat_id,"\u76EE\u524D\u652F\u6301\u6587\u5B57\u548C\u56FE\u7247\u6D88\u606F\u3002");return}if(s!==null&&ufe.test(s)){await this.#K(r,s);return}if(s==="/help"){await this.#m(r.message.chat_id,Mfe);return}if(dfe.test(s)){this.#j(o,{kind:"menu",chatId:r.message.chat_id}),await this.#Z(r.message.chat_id,aj(),{key:o});return}if(s==="/new"){await this.#t.clearSession(o),await this.#m(r.message.chat_id,"\u5DF2\u5F00\u542F\u5168\u65B0 Harness \u4F1A\u8BDD\u3002");return}if(s==="/status"){await this.#r.ensureRunning({signal:this.#p}),await this.#m(r.message.chat_id,"\u98DE\u4E66\u673A\u5668\u4EBA\u4E0E DeepSeek Harness \u8FDE\u63A5\u6B63\u5E38\u3002");return}if(ffe.test(s)){let l=s.slice(12).trim()||null;await this.#W({chatId:r.message.chat_id,key:o},l,0);return}if(yfe.test(s)){await this.#q({chatId:r.message.chat_id,key:o});return}if(Ij.test(s)){let l=(Ij.exec(s)?.[1]??"").trim()||null;await this.#ce(o,r.message.chat_id,l);return}if(lj.test(s)){let l=(lj.exec(s)?.[1]??"").trim()||null;await this.#Ie(o,r.message.chat_id,l);return}if(Qfe.test(s)){await this.#ue(o,r.message.chat_id);return}if(pj.test(s)){let p=pj.exec(s)[1]?.toLowerCase();if(p!=="on"&&p!=="off"){await this.#m(r.message.chat_id,"\u7528\u6CD5\uFF1A/archived on\uFF08\u5305\u542B\u5F52\u6863\u4F1A\u8BDD\uFF09\u6216 /archived off\uFF08\u9690\u85CF\u5F52\u6863\u4F1A\u8BDD\uFF09");return}typeof this.#t?.setIncludeArchivedSessions=="function"&&await this.#t.setIncludeArchivedSessions(p==="on"),await this.#m(r.message.chat_id,p==="on"?"\u5DF2\u5F00\u542F\uFF1A\u4F1A\u8BDD\u5217\u8868\u5305\u542B\u5F52\u6863\u4F1A\u8BDD\u3002":"\u5DF2\u5173\u95ED\uFF1A\u4F1A\u8BDD\u5217\u8868\u9690\u85CF\u5F52\u6863\u4F1A\u8BDD\u3002");return}if(mfe.test(s)){let l=this.#te(o);if(l){await this.#re(l,Number(s),{chatId:r.message.chat_id,key:o,event:r});return}}let g=s===null?null:await Ei(A,this.#r,o);if(g){for(let l of g.messages??[g.message])await this.#m(r.message.chat_id,l);return}let c=s===null?null:await Ii(s,this.#r,this.#t,o,{signal:this.#p});if(c){await this.#m(r.message.chat_id,c.message);return}this.#C.info?.(`[dsh-feishu] processing ${r.message.chat_type} message ${e}`);try{await this.#ye(r,o,a),this.#g.messagesReplied+=1,this.#g.lastReplyAt=new Date().toISOString(),this.#g.lastError=null}finally{await this.#pe(o),await this.#c.closeRoute(o)}}async#K(r,o){if(r?.message?.chat_type!=="p2p"){await this.#m(r.message.chat_id,"\u4E3A\u907F\u514D\u6388\u6743\u94FE\u63A5\u66B4\u9732\uFF0C\u8BF7\u79C1\u804A\u673A\u5668\u4EBA\u53D1\u9001 /repair\u3002");return}let i=_fe(r);if(!i||!this.#f.has(i)){await this.#m(r.message.chat_id,this.#f.size===0?"\u5F53\u524D\u673A\u5668\u4EBA\u6CA1\u6709\u53EF\u9A8C\u8BC1\u7684\u63A5\u5165\u8005\u8EAB\u4EFD\uFF0C\u4E0D\u80FD\u4ECE\u804A\u5929\u53D1\u8D77\u4FEE\u590D\uFF1B\u8BF7\u5148\u5728\u63D2\u4EF6\u9875\u8BBE\u7F6E\u7BA1\u7406\u5458\u3002":"\u6B64\u64CD\u4F5C\u53EA\u80FD\u7531\u673A\u5668\u4EBA\u63A5\u5165\u8005\u5728\u79C1\u804A\u4E2D\u53D1\u8D77\uFF0C\u672A\u8FDB\u884C\u4EFB\u4F55\u4FEE\u6539\u3002");return}if(!this.#d){await this.#m(r.message.chat_id,"\u5F53\u524D Host \u7248\u672C\u6682\u4E0D\u652F\u6301\u804A\u5929\u5185\u4FEE\u590D\uFF0C\u8BF7\u5148\u66F4\u65B0\u63D2\u4EF6\u3002");return}let e=Bfe.exec(o);if(!e){await this.#m(r.message.chat_id,"\u7528\u6CD5\uFF1A/repair\u3001/repair qr\u3001/repair status\u3001/repair cancel \u6216 /repair verify");return}let a=e[1]?.toLowerCase()??"start",A=r.message.chat_id;if(a==="start"){await this.#P({actorOpenId:i,chatId:A});return}let t=this.#S;if(!t){await this.#m(A,"\u5F53\u524D Runtime \u6CA1\u6709\u53EF\u6062\u590D\u7684\u4FEE\u590D\u4EFB\u52A1\u8BB0\u5F55\uFF08\u673A\u5668\u4EBA\u53EF\u80FD\u521A\u5B8C\u6210\u5BC6\u94A5\u66F4\u65B0\u5E76\u91CD\u542F\uFF09\u3002\u672C\u547D\u4EE4\u4E0D\u4F1A\u542F\u52A8\u65B0\u7684\u6388\u6743\uFF1B\u8BF7\u67E5\u770B\u673A\u5668\u4EBA\u53D1\u9001\u7684\u9A8C\u8BC1\u7ED3\u679C\uFF0C\u786E\u8BA4\u4E0A\u4E00\u6B21\u4EFB\u52A1\u5DF2\u7ED3\u675F\u540E\u518D\u53D1\u9001 /repair\u3002");return}if(t.actorOpenId!==i){await this.#m(A,"\u53E6\u4E00\u4F4D\u7BA1\u7406\u5458\u6B63\u5728\u4FEE\u590D\u8BE5\u673A\u5668\u4EBA\uFF0C\u672C\u6B21\u4E0D\u4F1A\u663E\u793A\u5176\u6388\u6743\u4FE1\u606F\u3002");return}if(a==="cancel"){let g;try{let c=await this.#d.cancel(this.#G(t));g=RQ(c,{botId:this.#u}),t.snapshot=g}catch{await this.#m(A,"\u6682\u65F6\u65E0\u6CD5\u53D6\u6D88\u4FEE\u590D\u4EFB\u52A1\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5\u3002");return}g.state==="cancelled"&&(t.stopped=!0,this.#Q+=1),await this.#m(A,this.#U(g));return}let s;try{s=await this.#F(t)}catch{await this.#m(A,"\u6682\u65F6\u65E0\u6CD5\u67E5\u8BE2\u4FEE\u590D\u72B6\u6001\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5\u3002");return}if(a==="qr"){if(!TQ.has(s.state)||!t.verificationUrl){await this.#m(A,this.#U(s,{verificationFocused:!0}));return}await this.#Ae(A,t.verificationUrl,s);return}await this.#m(A,this.#U(s,{verificationFocused:a==="verify"}))}#G(r){return{botId:this.#u,attemptId:r.attemptId,actorOpenId:r.actorOpenId,chatId:r.chatId}}async#P({actorOpenId:r,chatId:o}){let i=this.#S;if(i&&TQ.has(i.snapshot.state)){if(i.actorOpenId!==r){await this.#m(o,"\u53E6\u4E00\u4F4D\u7BA1\u7406\u5458\u6B63\u5728\u4FEE\u590D\u8BE5\u673A\u5668\u4EBA\uFF0C\u672C\u6B21\u4E0D\u4F1A\u663E\u793A\u5176\u6388\u6743\u4FE1\u606F\u3002");return}try{let A=await this.#F(i);if(TQ.has(A.state)&&i.verificationUrl){await this.#x(o,i.verificationUrl,A,{existing:!0});return}}catch{await this.#m(o,"\u6682\u65F6\u65E0\u6CD5\u67E5\u8BE2\u4FEE\u590D\u72B6\u6001\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5\u3002");return}}let e;try{e=RQ(await this.#d.start({botId:this.#u,actorOpenId:r,chatId:o}),{botId:this.#u}),e=await this.#V(e,{actorOpenId:r,chatId:o})}catch{await this.#m(o,"\u4FEE\u590D\u6D41\u7A0B\u6682\u65F6\u5931\u8D25\uFF0C\u73B0\u6709\u673A\u5668\u4EBA\u8FDE\u63A5\u4E0D\u53D7\u5F71\u54CD\uFF1B\u8BF7\u7A0D\u540E\u53D1\u9001 /repair \u91CD\u8BD5\u3002");return}let a={attemptId:e.attemptId,actorOpenId:r,chatId:o,snapshot:e,verificationUrl:null,stopped:!1,announcedSaving:!1,announcedTerminal:!1};if(this.#S=a,e.verificationUrl)try{a.verificationUrl=hj(e.verificationUrl,this.#B)}catch{a.stopped=!0,await this.#d.cancel(this.#G(a)).catch(()=>{}),await this.#m(o,"\u98DE\u4E66\u8FD4\u56DE\u4E86\u65E0\u6CD5\u5B89\u5168\u9A8C\u8BC1\u7684\u6388\u6743\u94FE\u63A5\uFF0C\u5DF2\u4E2D\u6B62\u672C\u6B21\u4FEE\u590D\u3002");return}if(Ej.has(e.state)){a.announcedTerminal=!0,e.state!=="succeeded"&&await this.#m(o,this.#U(e));return}if(!a.verificationUrl){a.stopped=!0,await this.#m(o,"\u98DE\u4E66\u672A\u8FD4\u56DE\u6388\u6743\u94FE\u63A5\uFF0C\u5DF2\u4E2D\u6B62\u672C\u6B21\u4FEE\u590D\u3002");return}await this.#x(o,a.verificationUrl,e),this.#Y(a)}async#V(r,o){let i=r,e=Date.now()+this.#y;for(;!i.verificationUrl&&TQ.has(i.state);){if(Date.now()>=e)throw new Error("Feishu repair link timed out");await Cj(Math.min(100,this.#w),this.#p),i=RQ(await this.#d.status({botId:this.#u,attemptId:i.attemptId,actorOpenId:o.actorOpenId,chatId:o.chatId}),{botId:this.#u})}return i}async#F(r){let o=RQ(await this.#d.status(this.#G(r)),{botId:this.#u});if(o.attemptId!==r.attemptId)throw new Error("Feishu repair attempt changed unexpectedly");return r.snapshot=o,o.verificationUrl&&(r.verificationUrl=hj(o.verificationUrl,this.#B)),o}#Y(r){let o=++this.#Q;(async()=>{for(;!r.stopped&&this.#S===r&&this.#Q===o&&!this.#p?.aborted;){let i=Math.max(250,Math.min(1e4,r.snapshot.pollIntervalMs??this.#w));if(await Cj(i,this.#p),r.stopped||this.#S!==r||this.#Q!==o)return;let e=await this.#F(r);if(e.state==="saving"&&!r.announcedSaving&&(r.announcedSaving=!0,await this.#m(r.chatId,"\u6388\u6743\u5DF2\u786E\u8BA4\uFF0C\u6B63\u5728\u53D1\u9001\u5E76\u7B49\u5F85\u6D4B\u8BD5\u6309\u94AE\u56DE\u8C03\uFF1B\u6536\u5230\u771F\u5B9E\u56DE\u8C03\u540E\u624D\u4F1A\u5B8C\u6210\u3002")),Ej.has(e.state)){r.stopped=!0,e.state!=="succeeded"&&!r.announcedTerminal&&(r.announcedTerminal=!0,await this.#m(r.chatId,this.#U(e)));return}}})().catch(async()=>{this.#p?.aborted||r.stopped||this.#S!==r||(r.stopped=!0,this.#C.warn?.("[dsh-feishu] callback repair status monitoring failed"),await this.#m(r.chatId,"\u4FEE\u590D\u72B6\u6001\u67E5\u8BE2\u4E2D\u65AD\uFF0C\u73B0\u6709\u673A\u5668\u4EBA\u8FDE\u63A5\u4E0D\u53D7\u5F71\u54CD\uFF1B\u53D1\u9001 /repair status \u91CD\u8BD5\u67E5\u8BE2\u3002").catch(()=>{}))})}async#x(r,o,i,{existing:e=!1}={}){let a=i.remainingSeconds??(i.expiresAt?Math.max(0,Math.ceil((i.expiresAt-Date.now())/1e3)):null),A=a===null?"\u94FE\u63A5\u4E3A\u77ED\u671F\u6709\u6548":`\u94FE\u63A5\u7EA6 ${Math.max(1,Math.ceil(a/60))} \u5206\u949F\u540E\u8FC7\u671F`;await this.#m(r,[e?"\u5DF2\u6709\u4E00\u4E2A\u4FEE\u590D\u4EFB\u52A1\u5728\u7B49\u5F85\u6388\u6743\u3002":"\u{1F527} \u51C6\u5907\u4FEE\u590D\u5361\u7247\u6309\u94AE\u3002","\u672C\u6B21\u53EA\u4F1A\u589E\u91CF\u6DFB\u52A0 card.action.trigger\u3002\u8BF7\u6838\u5BF9\u786E\u8BA4\u9875\u53EA\u663E\u793A\u8FD9\u4E00\u9879\uFF1B\u82E5\u51FA\u73B0\u5176\u4ED6\u6743\u9650\u6216\u4E8B\u4EF6\uFF0C\u8BF7\u53D6\u6D88\u3002","","\u5F53\u524D\u8BBE\u5907\u76F4\u63A5\u6253\u5F00\uFF1A",o,"",`\u82E5\u8981\u7528\u53E6\u4E00\u53F0\u8BBE\u5907\u626B\u7801\uFF0C\u53D1\u9001 /repair qr\u3002${A}\u3002`].join(` `))}async#Ae(r,o,i){try{let e=await Cfe.toBuffer(o,{errorCorrectionLevel:"M",margin:1,width:480,type:"png"}),a=await this.#e.im.v1.image.create({data:{image_type:"message",image:e}}),A=it(a?.image_key)??it(a?.data?.image_key);if(!A)throw new Error("Feishu QR upload returned no image key");let t=i.remainingSeconds??(i.expiresAt?Math.max(0,Math.ceil((i.expiresAt-Date.now())/1e3)):null);await this.#m(r,`\u8BF7\u7528\u53E6\u4E00\u53F0\u8BBE\u5907\u626B\u7801\u5B8C\u6210\u6388\u6743${t===null?"":`\uFF08\u5269\u4F59\u7EA6 ${Math.max(1,Math.ceil(t/60))} \u5206\u949F\uFF09`}\u3002`);let s=await this.#e.im.v1.message.create({params:{receive_id_type:"chat_id"},data:{receive_id:r,msg_type:"image",content:JSON.stringify({image_key:A})}});if(s?.code&&s.code!==0)throw new Error("Feishu QR message send failed")}catch{await this.#m(r,`\u4E8C\u7EF4\u7801\u6682\u65F6\u65E0\u6CD5\u53D1\u9001\uFF0C\u8BF7\u76F4\u63A5\u6253\u5F00\u6388\u6743\u94FE\u63A5\uFF1A diff --git a/src/channels/feishu/state-store.mjs b/src/channels/feishu/state-store.mjs index 6bdeedc..722a90d 100644 --- a/src/channels/feishu/state-store.mjs +++ b/src/channels/feishu/state-store.mjs @@ -7,7 +7,7 @@ const EMPTY_STATE = Object.freeze({ seenMessageIds: [], watches: {}, chatTargets: {}, - includeArchivedSessions: true, + includeArchivedSessions: false, }); /** One conversation key may watch at most this many sessions. */ @@ -41,7 +41,7 @@ export class StateStore { chatTargets: parsed.chatTargets && typeof parsed.chatTargets === 'object' ? parsed.chatTargets : {}, includeArchivedSessions: typeof parsed.includeArchivedSessions === 'boolean' ? parsed.includeArchivedSessions - : true, + : false, }; } catch (error) { if (error?.code !== 'ENOENT') throw error; @@ -161,11 +161,11 @@ export class StateStore { // ── Session-list archived policy (per bot) ─────────────────────────────── includesArchivedSessions() { - return this.#state.includeArchivedSessions !== false; + return this.#state.includeArchivedSessions === true; } async setIncludeArchivedSessions(include) { - this.#state.includeArchivedSessions = include !== false; + this.#state.includeArchivedSessions = include === true; await this.#persist(); } diff --git a/test/channels/feishu/bridge.test.mjs b/test/channels/feishu/bridge.test.mjs index 95f1ac8..2e60be5 100644 --- a/test/channels/feishu/bridge.test.mjs +++ b/test/channels/feishu/bridge.test.mjs @@ -2544,7 +2544,7 @@ test('reconnect compensation replays missed turn/end and dedups duplicates', asy assert.match(JSON.stringify(cards[1]), /watched-session/); }); -test('/archived off hides archived sessions from the list and watch index', async () => { +test('archived sessions are hidden by default; /archived on reveals them', async () => { const { state } = await watchStoreFixture(); const work = join(tmpdir(), 'dsh-im-archived-test-work'); mkdirSync(work, { recursive: true }); @@ -2571,8 +2571,7 @@ test('/archived off hides archived sessions from the list and watch index', asyn }); bridge._sent = sent; - await bridge.accept(event('archived-off', '/archived off', { senderOpenId: 'ou_owner' })); - await bridge.waitForIdle(); + // Default: hidden. The explicit toggle re-enables inclusion for the card check. assert.equal(state.includesArchivedSessions(), false); await bridge.accept(event('sessions-arch', '/sessionlist', { senderOpenId: 'ou_owner' })); @@ -2585,4 +2584,11 @@ test('/archived off hides archived sessions from the list and watch index', asyn await bridge.waitForIdle(); assert.ok(state.watchEntry('p2p:ou_owner', 'live-session')); assert.equal(state.watchEntry('p2p:ou_owner', 'old-session'), null); + + await bridge.accept(event('archived-on', '/archived on', { senderOpenId: 'ou_owner' })); + await bridge.waitForIdle(); + await bridge.accept(event('sessions-arch-2', '/sessionlist', { senderOpenId: 'ou_owner' })); + await bridge.waitForIdle(); + const useOn = useActionsFromCard(cards.at(-1)); + assert.deepEqual(useOn, ['live-session', 'old-session'], '/archived on restores archived sessions'); });