@sleep2agi/agent-network 2.3.0-preview.2 → 2.3.0-preview.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,31 @@
1
+ export interface WakeLogEntryShape {
2
+ ts?: string;
3
+ status?: string;
4
+ summary?: string;
5
+ task_id?: string;
6
+ }
7
+ export interface WakeLogGoalShape {
8
+ goal_id: string;
9
+ progress_log?: WakeLogEntryShape[];
10
+ }
11
+ export interface WakeLogRenderOpts {
12
+ /** Trim to the last N entries. undefined = all. */
13
+ tail?: number;
14
+ }
15
+ /**
16
+ * Format progress_log as a JSON object suitable for `--json` output.
17
+ * Pure. Never reads/writes filesystem. Legacy goals without
18
+ * progress_log render as empty array (not error).
19
+ */
20
+ export declare function renderWakeLogJson(goal: WakeLogGoalShape, opts?: WakeLogRenderOpts): {
21
+ goal_id: string;
22
+ total: number;
23
+ returned: number;
24
+ entries: WakeLogEntryShape[];
25
+ };
26
+ /**
27
+ * Format progress_log as a table for human console output.
28
+ * Column layout mirrors printGoalShow so the two commands read
29
+ * consistently. Empty log renders a single "(none)" line, not an error.
30
+ */
31
+ export declare function renderWakeLogText(goal: WakeLogGoalShape, opts?: WakeLogRenderOpts): string;
@@ -1 +1 @@
1
- const a0_0x238205=a0_0x2b7c;function a0_0x2b7c(_0x175c5d,_0x2926b1){_0x175c5d=_0x175c5d-0x85;const _0x23a12f=a0_0x23a1();let _0x2b7c69=_0x23a12f[_0x175c5d];return _0x2b7c69;}(function(_0xb48d31,_0x3723fb){const _0x480c8f=a0_0x2b7c,_0x326564=_0xb48d31();while(!![]){try{const _0x381c99=-parseInt(_0x480c8f(0x9e))/0x1+parseInt(_0x480c8f(0xc3))/0x2+-parseInt(_0x480c8f(0x94))/0x3+-parseInt(_0x480c8f(0xa3))/0x4+-parseInt(_0x480c8f(0xab))/0x5+-parseInt(_0x480c8f(0xd4))/0x6*(parseInt(_0x480c8f(0xcb))/0x7)+parseInt(_0x480c8f(0xcf))/0x8;if(_0x381c99===_0x3723fb)break;else _0x326564['push'](_0x326564['shift']());}catch(_0x27405c){_0x326564['push'](_0x326564['shift']());}}}(a0_0x23a1,0x8127b));import{EventEmitter as a0_0x215187}from'events';import{hostname as a0_0x57b3e8}from'os';class T extends a0_0x215187{[a0_0x238205(0xad)];[a0_0x238205(0xb4)];[a0_0x238205(0xc0)];['agent'];[a0_0x238205(0xd2)];[a0_0x238205(0xae)];[a0_0x238205(0x99)];[a0_0x238205(0x9f)];[a0_0x238205(0xaf)];[a0_0x238205(0xd3)]=!0x1;constructor(_0x385ded){const _0x35e6f0=a0_0x238205;super();if(this[_0x35e6f0(0xad)]=_0x385ded['url'][_0x35e6f0(0xc5)](/\/$/,''),this['alias']=_0x385ded[_0x35e6f0(0xb4)],this[_0x35e6f0(0xc0)]=_0x385ded[_0x35e6f0(0xc0)],this[_0x35e6f0(0xb8)]=_0x385ded[_0x35e6f0(0xb8)]||'sdk',this[_0x35e6f0(0xd2)]=_0x35e6f0(0x92)+_0x385ded[_0x35e6f0(0xb4)]+'-'+Date[_0x35e6f0(0xb1)]()['toString'](0x24),this['heartbeatInterval']=_0x385ded[_0x35e6f0(0xae)]??0x2bf20,this[_0x35e6f0(0x99)]=_0x385ded[_0x35e6f0(0x99)]??0xbb8,_0x385ded['autoConnect']!==!0x1)this[_0x35e6f0(0xa2)]();}[a0_0x238205(0xac)](_0x4840fe){const _0x52658d=a0_0x238205;console['log']('['+new Date()[_0x52658d(0xa5)]()[_0x52658d(0xaa)](0x0,0x8)+_0x52658d(0x91)+this[_0x52658d(0xb4)]+']\x20'+_0x4840fe);}async[a0_0x238205(0x96)](_0x517436,_0x2a96c8){const _0x1d3e28=a0_0x238205;let _0x38450b={'Content-Type':_0x1d3e28(0xbf),'Accept':_0x1d3e28(0xa8)};if(this[_0x1d3e28(0xc0)])_0x38450b[_0x1d3e28(0x93)]=_0x1d3e28(0xbe)+this[_0x1d3e28(0xc0)];let _0x3cecd1=await(await fetch(this[_0x1d3e28(0xad)]+_0x1d3e28(0x8a),{'method':_0x1d3e28(0x8c),'headers':_0x38450b,'body':JSON[_0x1d3e28(0xd8)]({'jsonrpc':'2.0','id':Date['now'](),'method':_0x1d3e28(0xc7),'params':{'name':_0x517436,'arguments':_0x2a96c8}})}))[_0x1d3e28(0xa0)](),_0x431d3c=_0x3cecd1['match'](/data: (.+)/),_0x3783ee=_0x431d3c?JSON[_0x1d3e28(0xcc)](_0x431d3c[0x1]):JSON[_0x1d3e28(0xcc)](_0x3cecd1),_0xcfafca=_0x3783ee?.['result']?.[_0x1d3e28(0xbc)]?.[0x0]?.[_0x1d3e28(0xa0)];return _0xcfafca?JSON[_0x1d3e28(0xcc)](_0xcfafca):_0x3783ee;}async[a0_0x238205(0xa2)](){const _0x1bc965=a0_0x238205;if(this[_0x1bc965(0xd3)])return;this[_0x1bc965(0xd3)]=!0x0,await this[_0x1bc965(0xa1)](_0x1bc965(0xa6)),this[_0x1bc965(0xac)](_0x1bc965(0xd7)),this['heartbeatTimer']=setInterval(()=>{const _0x1760d2=_0x1bc965;this['status'](_0x1760d2(0xa6))[_0x1760d2(0xb2)](_0x44a3b6=>this[_0x1760d2(0xac)](_0x1760d2(0xd1)+_0x44a3b6['message']));},this[_0x1bc965(0xae)]),this[_0x1bc965(0xc9)]();}async[a0_0x238205(0x8e)](){const _0x51fa1c=a0_0x238205;if(this[_0x51fa1c(0xd3)]=!0x1,this['sseAbort']?.[_0x51fa1c(0xb7)](),this['heartbeatTimer'])clearInterval(this[_0x51fa1c(0x9f)]);await this[_0x51fa1c(0xa1)]('offline')[_0x51fa1c(0xb2)](()=>{}),this[_0x51fa1c(0xac)]('disconnected');}async[a0_0x238205(0x86)](_0x372dfe,_0x44cc24,_0x22957a='normal'){const _0x29c4b8=a0_0x238205;return this[_0x29c4b8(0x96)](_0x29c4b8(0x8b),{'alias':_0x372dfe,'task':_0x44cc24,'priority':_0x22957a,'from_session':this['alias']});}async['message'](_0x989e93,_0x493467){const _0x12fe94=a0_0x238205;return this[_0x12fe94(0x96)](_0x12fe94(0xd6),{'alias':_0x989e93,'message':_0x493467,'from_session':this['alias']});}async[a0_0x238205(0xa9)](_0x42872c,_0x35fb9b,_0x2c4a30=a0_0x238205(0xca)){const _0x2b0c57=a0_0x238205;return this['call'](_0x2b0c57(0xba),{'in_reply_to':_0x42872c,'text':_0x35fb9b,'status':_0x2c4a30});}async[a0_0x238205(0xa1)](_0x3a3f60,_0x2e5e21){const _0x4098ec=a0_0x238205;return this[_0x4098ec(0x96)](_0x4098ec(0xcd),{'resume_id':this[_0x4098ec(0xd2)],'alias':this[_0x4098ec(0xb4)],'status':_0x3a3f60,'server':a0_0x57b3e8(),'hostname':a0_0x57b3e8(),'agent':this[_0x4098ec(0xb8)],'project_dir':process['cwd'](),..._0x2e5e21});}async[a0_0x238205(0x89)](){return this['call']('get_all_status',{});}async[a0_0x238205(0x87)](_0x275aea,_0x5a5e2f){const _0x3ed4fc=a0_0x238205;return this[_0x3ed4fc(0x96)](_0x3ed4fc(0x87),{'message':_0x275aea,'filter_server':_0x5a5e2f?.[_0x3ed4fc(0xd9)],'filter_status':_0x5a5e2f?.[_0x3ed4fc(0xa1)]});}async['connectSSE'](){const _0x12de34=a0_0x238205;let _0x2fe4c8=encodeURIComponent(this['alias']),_0x3a9f03=this[_0x12de34(0xad)]+_0x12de34(0xb0)+_0x2fe4c8,_0x1aecc9=this[_0x12de34(0x99)];while(this[_0x12de34(0xd3)]){try{this[_0x12de34(0xaf)]=new AbortController();let _0x2cddf1={'Accept':_0x12de34(0x9d)};if(this[_0x12de34(0xc0)])_0x2cddf1[_0x12de34(0x93)]=_0x12de34(0xbe)+this[_0x12de34(0xc0)];let _0x33eda3=await fetch(_0x3a9f03,{'headers':_0x2cddf1,'signal':this[_0x12de34(0xaf)]['signal']});if(!_0x33eda3['ok']||!_0x33eda3['body']){this[_0x12de34(0xac)]('SSE\x20failed:\x20'+_0x33eda3[_0x12de34(0xa1)]),await this[_0x12de34(0xb3)](_0x1aecc9),_0x1aecc9=Math['min'](_0x1aecc9*1.5,0xea60);continue;}_0x1aecc9=this[_0x12de34(0x99)];let _0x341aac=_0x33eda3['body'][_0x12de34(0x8f)](),_0x35cc8e=new TextDecoder(),_0x4b108b='';while(this['running']){let {done:_0x24050f,value:_0x1bfb06}=await _0x341aac[_0x12de34(0x88)]();if(_0x24050f)break;_0x4b108b+=_0x35cc8e[_0x12de34(0x85)](_0x1bfb06,{'stream':!0x0});let _0x5e200b=_0x4b108b[_0x12de34(0x8d)]('\x0a');_0x4b108b=_0x5e200b[_0x12de34(0x9b)]()||'';for(let _0x384271 of _0x5e200b){if(!_0x384271[_0x12de34(0xb5)](_0x12de34(0xc1)))continue;try{let _0x3a961e=JSON[_0x12de34(0xcc)](_0x384271[_0x12de34(0xaa)](0x6));if(_0x3a961e[_0x12de34(0x95)]===_0x12de34(0x9a)){this[_0x12de34(0xac)]('SSE\x20connected'),this[_0x12de34(0x98)]('connected');continue;}if(_0x3a961e['type']===_0x12de34(0xa4)||_0x3a961e[_0x12de34(0x95)]===_0x12de34(0xce)||_0x3a961e[_0x12de34(0x95)]===_0x12de34(0x87))await this['processInbox']();}catch{}}}}catch(_0x571d98){if(_0x571d98[_0x12de34(0xa7)]===_0x12de34(0xc2))break;this[_0x12de34(0x98)](_0x12de34(0xbb),_0x571d98),this[_0x12de34(0xac)](_0x12de34(0xb9)+_0x571d98[_0x12de34(0x9c)]);}if(this[_0x12de34(0xd3)])this['emit'](_0x12de34(0xbd)),this[_0x12de34(0xac)]('SSE\x20reconnecting\x20in\x20'+_0x1aecc9/0x3e8+_0x12de34(0xc6)),await this[_0x12de34(0xb3)](_0x1aecc9),_0x1aecc9=Math[_0x12de34(0xb6)](_0x1aecc9*1.5,0xea60);}}async['processInbox'](){const _0x17178b=a0_0x238205;try{let _0x48b1bc=(await this[_0x17178b(0x96)](_0x17178b(0xd0),{'alias':this[_0x17178b(0xb4)],'limit':0xa}))?.[_0x17178b(0x90)]||[];for(let _0x34f496 of _0x48b1bc)await this['call'](_0x17178b(0xc8),{'alias':this[_0x17178b(0xb4)],'message_id':_0x34f496['id']}),this['log']('←\x20'+_0x34f496[_0x17178b(0xd5)]+':\x20'+_0x34f496[_0x17178b(0xbc)][_0x17178b(0xaa)](0x0,0x3c)),this[_0x17178b(0x98)](_0x17178b(0x97),_0x34f496),this[_0x17178b(0x98)]('message',_0x34f496);}catch(_0x7e4517){this['log'](_0x17178b(0xc4)+_0x7e4517['message']);}}[a0_0x238205(0xb3)](_0x53c25e){return new Promise(_0xb360dd=>setTimeout(_0xb360dd,_0x53c25e));}}var $=T;function a0_0x23a1(){const _0x5453aa=['data:\x20','AbortError','299620tuVXUi','inbox\x20error:\x20','replace','s...','tools/call','ack_inbox','connectSSE','completed','13237xRPZks','parse','report_status','new_message','27442120rKbIIY','get_inbox','heartbeat\x20failed:\x20','resumeId','running','1614SnCWod','from_session','send_message','registered','stringify','server','decode','send','broadcast','read','getAllStatus','/mcp','send_task','POST','split','disconnect','getReader','messages',']\x20[commhub:','sdk-','Authorization','1888005nrYQPX','type','call','task','emit','reconnectDelay','connected','pop','message','text/event-stream','848563eiDPPz','heartbeatTimer','text','status','connect','359936YELHsR','new_task','toTimeString','idle','name','application/json,\x20text/event-stream','reply','slice','4872475dnIcop','log','url','heartbeatInterval','sseAbort','/events/','now','catch','sleep','alias','startsWith','min','abort','agent','SSE\x20error:\x20','send_reply','error','content','disconnected','Bearer\x20','application/json','token'];a0_0x23a1=function(){return _0x5453aa;};return a0_0x23a1();}export{$ as default,T as CommHub};
1
+ const a0_0x1f50c6=a0_0x4ee1;function a0_0x4ee1(_0x2809d3,_0x3799ca){_0x2809d3=_0x2809d3-0x120;const _0x1f8847=a0_0x1f88();let _0x4ee1fb=_0x1f8847[_0x2809d3];return _0x4ee1fb;}(function(_0x167052,_0x5f3ad0){const _0x2664f2=a0_0x4ee1,_0x124bd5=_0x167052();while(!![]){try{const _0x1c3dd2=-parseInt(_0x2664f2(0x126))/0x1+-parseInt(_0x2664f2(0x162))/0x2+parseInt(_0x2664f2(0x124))/0x3+-parseInt(_0x2664f2(0x140))/0x4*(-parseInt(_0x2664f2(0x15a))/0x5)+-parseInt(_0x2664f2(0x165))/0x6+parseInt(_0x2664f2(0x14f))/0x7*(parseInt(_0x2664f2(0x130))/0x8)+parseInt(_0x2664f2(0x159))/0x9*(-parseInt(_0x2664f2(0x143))/0xa);if(_0x1c3dd2===_0x5f3ad0)break;else _0x124bd5['push'](_0x124bd5['shift']());}catch(_0x16d6a2){_0x124bd5['push'](_0x124bd5['shift']());}}}(a0_0x1f88,0xd8c74));import{EventEmitter as a0_0x3a8bbc}from'events';import{hostname as a0_0x331afb}from'os';class T extends a0_0x3a8bbc{['url'];['alias'];[a0_0x1f50c6(0x12e)];[a0_0x1f50c6(0x170)];[a0_0x1f50c6(0x142)];[a0_0x1f50c6(0x129)];[a0_0x1f50c6(0x132)];[a0_0x1f50c6(0x122)];[a0_0x1f50c6(0x123)];['running']=!0x1;constructor(_0x3e04aa){const _0x22b43f=a0_0x1f50c6;super();if(this[_0x22b43f(0x14a)]=_0x3e04aa['url'][_0x22b43f(0x163)](/\/$/,''),this['alias']=_0x3e04aa[_0x22b43f(0x12f)],this[_0x22b43f(0x12e)]=_0x3e04aa['token'],this[_0x22b43f(0x170)]=_0x3e04aa[_0x22b43f(0x170)]||_0x22b43f(0x139),this['resumeId']='sdk-'+_0x3e04aa['alias']+'-'+Date['now']()[_0x22b43f(0x16d)](0x24),this[_0x22b43f(0x129)]=_0x3e04aa['heartbeatInterval']??0x2bf20,this[_0x22b43f(0x132)]=_0x3e04aa[_0x22b43f(0x132)]??0xbb8,_0x3e04aa['autoConnect']!==!0x1)this[_0x22b43f(0x171)]();}[a0_0x1f50c6(0x120)](_0x251ee8){const _0x27f1f2=a0_0x1f50c6;console['log']('['+new Date()[_0x27f1f2(0x14d)]()[_0x27f1f2(0x131)](0x0,0x8)+_0x27f1f2(0x167)+this['alias']+']\x20'+_0x251ee8);}async[a0_0x1f50c6(0x15c)](_0x15d7fa,_0x5053a8){const _0x35ab40=a0_0x1f50c6;let _0x5b7acb={'Content-Type':'application/json','Accept':_0x35ab40(0x156)};if(this[_0x35ab40(0x12e)])_0x5b7acb[_0x35ab40(0x15e)]=_0x35ab40(0x172)+this[_0x35ab40(0x12e)];let _0xe9da6f=await(await fetch(this[_0x35ab40(0x14a)]+_0x35ab40(0x136),{'method':_0x35ab40(0x13d),'headers':_0x5b7acb,'body':JSON['stringify']({'jsonrpc':_0x35ab40(0x121),'id':Date['now'](),'method':_0x35ab40(0x151),'params':{'name':_0x15d7fa,'arguments':_0x5053a8}})}))[_0x35ab40(0x12d)](),_0x5b5403=_0xe9da6f[_0x35ab40(0x13b)](/data: (.+)/),_0x1c12dd=_0x5b5403?JSON['parse'](_0x5b5403[0x1]):JSON[_0x35ab40(0x137)](_0xe9da6f),_0x2e3450=_0x1c12dd?.[_0x35ab40(0x155)]?.['content']?.[0x0]?.[_0x35ab40(0x12d)];return _0x2e3450?JSON[_0x35ab40(0x137)](_0x2e3450):_0x1c12dd;}async[a0_0x1f50c6(0x171)](){const _0x300348=a0_0x1f50c6;if(this['running'])return;this[_0x300348(0x154)]=!0x0,await this[_0x300348(0x161)]('idle'),this[_0x300348(0x120)](_0x300348(0x15f)),this[_0x300348(0x122)]=setInterval(()=>{const _0x3c59c4=_0x300348;this[_0x3c59c4(0x161)](_0x3c59c4(0x12a))[_0x3c59c4(0x14c)](_0xd326dc=>this[_0x3c59c4(0x120)](_0x3c59c4(0x16e)+_0xd326dc['message']));},this[_0x300348(0x129)]),this[_0x300348(0x128)]();}async['disconnect'](){const _0x2a2d8b=a0_0x1f50c6;if(this['running']=!0x1,this[_0x2a2d8b(0x123)]?.[_0x2a2d8b(0x157)](),this['heartbeatTimer'])clearInterval(this[_0x2a2d8b(0x122)]);await this[_0x2a2d8b(0x161)]('offline')[_0x2a2d8b(0x14c)](()=>{}),this['log']('disconnected');}async['send'](_0x48589b,_0x3ddc58,_0x325cde=a0_0x1f50c6(0x150)){const _0xc933=a0_0x1f50c6;return this[_0xc933(0x15c)](_0xc933(0x16c),{'alias':_0x48589b,'task':_0x3ddc58,'priority':_0x325cde,'from_session':this['alias']});}async['message'](_0x250c83,_0x5292dc){const _0x481cab=a0_0x1f50c6;return this['call'](_0x481cab(0x16b),{'alias':_0x250c83,'message':_0x5292dc,'from_session':this[_0x481cab(0x12f)]});}async[a0_0x1f50c6(0x149)](_0x53a517,_0x2f8b4d,_0x5ba494='completed'){const _0x34c163=a0_0x1f50c6;return this[_0x34c163(0x15c)](_0x34c163(0x164),{'in_reply_to':_0x53a517,'text':_0x2f8b4d,'status':_0x5ba494});}async['status'](_0x4fd77c,_0x2e121f){const _0xeabaa6=a0_0x1f50c6;return this[_0xeabaa6(0x15c)](_0xeabaa6(0x138),{'resume_id':this[_0xeabaa6(0x142)],'alias':this[_0xeabaa6(0x12f)],'status':_0x4fd77c,'server':a0_0x331afb(),'hostname':a0_0x331afb(),'agent':this[_0xeabaa6(0x170)],'project_dir':process[_0xeabaa6(0x168)](),..._0x2e121f});}async['getAllStatus'](){const _0x227a65=a0_0x1f50c6;return this[_0x227a65(0x15c)](_0x227a65(0x158),{});}async[a0_0x1f50c6(0x144)](_0x1889d0,_0x3e42bd){const _0x2b53c7=a0_0x1f50c6;return this[_0x2b53c7(0x15c)](_0x2b53c7(0x144),{'message':_0x1889d0,'filter_server':_0x3e42bd?.[_0x2b53c7(0x135)],'filter_status':_0x3e42bd?.['status']});}async[a0_0x1f50c6(0x128)](){const _0x12f622=a0_0x1f50c6;let _0x317d88=encodeURIComponent(this[_0x12f622(0x12f)]),_0x1c71a5=this[_0x12f622(0x14a)]+'/events/'+_0x317d88,_0xcccd39=this[_0x12f622(0x132)];while(this[_0x12f622(0x154)]){try{this[_0x12f622(0x123)]=new AbortController();let _0x7e7e18={'Accept':_0x12f622(0x13a)};if(this[_0x12f622(0x12e)])_0x7e7e18[_0x12f622(0x15e)]=_0x12f622(0x172)+this[_0x12f622(0x12e)];let _0x5d99d8=await fetch(_0x1c71a5,{'headers':_0x7e7e18,'signal':this[_0x12f622(0x123)][_0x12f622(0x152)]});if(!_0x5d99d8['ok']||!_0x5d99d8[_0x12f622(0x16a)]){this[_0x12f622(0x120)]('SSE\x20failed:\x20'+_0x5d99d8[_0x12f622(0x161)]),await this[_0x12f622(0x146)](_0xcccd39),_0xcccd39=Math[_0x12f622(0x145)](_0xcccd39*1.5,0xea60);continue;}_0xcccd39=this[_0x12f622(0x132)];let _0x3c11fb=_0x5d99d8[_0x12f622(0x16a)][_0x12f622(0x13f)](),_0x38dd15=new TextDecoder(),_0x2a53da='';while(this['running']){let {done:_0x1d283f,value:_0x5d447b}=await _0x3c11fb['read']();if(_0x1d283f)break;_0x2a53da+=_0x38dd15[_0x12f622(0x13e)](_0x5d447b,{'stream':!0x0});let _0x4cdf56=_0x2a53da['split']('\x0a');_0x2a53da=_0x4cdf56[_0x12f622(0x15b)]()||'';for(let _0x237e5a of _0x4cdf56){if(!_0x237e5a['startsWith'](_0x12f622(0x133)))continue;try{let _0x6bfb3a=JSON[_0x12f622(0x137)](_0x237e5a[_0x12f622(0x131)](0x6));if(_0x6bfb3a[_0x12f622(0x148)]===_0x12f622(0x134)){this[_0x12f622(0x120)]('SSE\x20connected'),this[_0x12f622(0x125)](_0x12f622(0x134));continue;}if(_0x6bfb3a['type']==='new_task'||_0x6bfb3a['type']===_0x12f622(0x13c)||_0x6bfb3a[_0x12f622(0x148)]===_0x12f622(0x144))await this[_0x12f622(0x147)]();}catch{}}}}catch(_0x471d1b){if(_0x471d1b[_0x12f622(0x14e)]===_0x12f622(0x160))break;this[_0x12f622(0x125)](_0x12f622(0x12b),_0x471d1b),this['log'](_0x12f622(0x12c)+_0x471d1b[_0x12f622(0x14b)]);}if(this['running'])this['emit'](_0x12f622(0x169)),this[_0x12f622(0x120)](_0x12f622(0x141)+_0xcccd39/0x3e8+_0x12f622(0x16f)),await this[_0x12f622(0x146)](_0xcccd39),_0xcccd39=Math['min'](_0xcccd39*1.5,0xea60);}}async['processInbox'](){const _0x39ef4c=a0_0x1f50c6;try{let _0x2dfaf2=(await this[_0x39ef4c(0x15c)](_0x39ef4c(0x127),{'alias':this[_0x39ef4c(0x12f)],'limit':0xa}))?.['messages']||[];for(let _0x27005a of _0x2dfaf2)await this[_0x39ef4c(0x15c)](_0x39ef4c(0x15d),{'alias':this[_0x39ef4c(0x12f)],'message_id':_0x27005a['id']}),this[_0x39ef4c(0x120)]('←\x20'+_0x27005a[_0x39ef4c(0x166)]+':\x20'+_0x27005a[_0x39ef4c(0x153)][_0x39ef4c(0x131)](0x0,0x3c)),this[_0x39ef4c(0x125)]('task',_0x27005a),this[_0x39ef4c(0x125)](_0x39ef4c(0x14b),_0x27005a);}catch(_0x9f5f99){this[_0x39ef4c(0x120)]('inbox\x20error:\x20'+_0x9f5f99[_0x39ef4c(0x14b)]);}}['sleep'](_0xcb1bd3){return new Promise(_0x301af2=>setTimeout(_0x301af2,_0xcb1bd3));}}var $=T;export{$ as default,T as CommHub};function a0_0x1f88(){const _0x255ea8=['idle','error','SSE\x20error:\x20','text','token','alias','520280IgPZKY','slice','reconnectDelay','data:\x20','connected','server','/mcp','parse','report_status','sdk','text/event-stream','match','new_message','POST','decode','getReader','28uWeIAD','SSE\x20reconnecting\x20in\x20','resumeId','2527320noabwo','broadcast','min','sleep','processInbox','type','reply','url','message','catch','toTimeString','name','147vQnzmD','normal','tools/call','signal','content','running','result','application/json,\x20text/event-stream','abort','get_all_status','18OfRXfI','988170xuOMbC','pop','call','ack_inbox','Authorization','registered','AbortError','status','372378QfoHVJ','replace','send_reply','1033560ZwTilC','from_session',']\x20[commhub:','cwd','disconnected','body','send_message','send_task','toString','heartbeat\x20failed:\x20','s...','agent','connect','Bearer\x20','log','2.0','heartbeatTimer','sseAbort','2105607YIIVww','emit','1699205QyjICq','get_inbox','connectSSE','heartbeatInterval'];a0_0x1f88=function(){return _0x255ea8;};return a0_0x1f88();}
@@ -0,0 +1,14 @@
1
+ /** Parse the environ blob (NUL-separated key=val) and return the
2
+ * COMMHUB_ALIAS value if present, else null. Exported for testing. */
3
+ export declare function parseEnvironAlias(environBlob: string): string | null;
4
+ /** Read /proc/<pid>/environ and return the COMMHUB_ALIAS value.
5
+ * Any file-read error (not-a-Linux, permission denied, race with
6
+ * process exit) returns null — caller must not fail-closed on that
7
+ * (the process is stale/gone/inaccessible, not our problem). */
8
+ export declare function readEnvironAlias(pid: number): string | null;
9
+ /** Scan /proc for pids whose COMMHUB_ALIAS env matches any of the
10
+ * target aliases. Linux-only (procfs); returns null on non-Linux or
11
+ * when /proc is unreadable — caller MUST fail-closed on null per
12
+ * the same #180 R2 fail-closed contract as findNodeProcessesByAlias.
13
+ * Self-pid + init (PID 1) are excluded. */
14
+ export declare function findEnvironAliasMatches(aliases: Iterable<string>, selfPid: number): number[] | null;
@@ -0,0 +1,103 @@
1
+ /**
2
+ * Tiny loader that mirrors what `initTelegramChannel` does at boot —
3
+ * read the parsed access.json blob and produce both the raw allowFrom
4
+ * payload (handed to the resolver verbatim) AND a boot-time warning
5
+ * string for the empty / malformed case.
6
+ *
7
+ * Extracted so wiring-level regression tests (e.g. "channel state
8
+ * built from `{allowFrom: [123]}` MUST fail-closed when a message
9
+ * arrives") run without spinning up a real telegram channel + fs
10
+ * mocks. The previous bug — `.map(String)` rewriting [123] into
11
+ * ["123"] at init time — slipped past resolver-only unit tests
12
+ * because the resolver never saw the offending shape; the channel
13
+ * state did.
14
+ */
15
+ export interface LoadedTelegramAccess {
16
+ /** Raw value from access.json — handed verbatim to resolveTelegramAccess. */
17
+ allowFromRaw: unknown;
18
+ /** Boot-time warning string when allowFrom is empty / malformed; null otherwise. */
19
+ bootWarn: string | null;
20
+ }
21
+ export declare function loadTelegramAccess(opts: {
22
+ channelDir: string;
23
+ parsedAccess: {
24
+ allowFrom?: unknown;
25
+ } | null | undefined;
26
+ }): LoadedTelegramAccess;
27
+ /**
28
+ * Discriminated reason for an access decision. Callers can switch on
29
+ * `kind` for routing (e.g. `empty-fail-closed` warrants a louder boot
30
+ * warning than a simple `denied` mid-stream).
31
+ */
32
+ export type AccessKind = "wildcard-allow" | "explicit-allow" | "empty-fail-closed" | "denied";
33
+ export interface AccessDecision {
34
+ allow: boolean;
35
+ kind: AccessKind;
36
+ /** Short, machine-readable reason for logging. */
37
+ reason: string;
38
+ }
39
+ /**
40
+ * Normalise an allowFrom input into a deduped string[] suitable for
41
+ * the lookup checks. Accepts: a real string[] (the happy case), null /
42
+ * undefined (treated as empty), a non-array shape (defensive — treated
43
+ * as empty + reason surfaced).
44
+ */
45
+ export declare function normalizeAllowFrom(raw: unknown): {
46
+ list: string[];
47
+ malformed: boolean;
48
+ };
49
+ /**
50
+ * Resolve whether an inbound Telegram message should be processed.
51
+ *
52
+ * Fail-closed: empty / missing / malformed `allowFrom` always denies.
53
+ * Wildcard `"*"` always allows. Otherwise the sender's id OR username
54
+ * (after non-empty check) must match a list entry.
55
+ *
56
+ * @param opts.allowFrom the access.json `allowFrom` array
57
+ * @param opts.senderId numeric Telegram user id as string
58
+ * (callers already coerce; we trust)
59
+ * @param opts.senderUsername optional `@username` (no `@` prefix)
60
+ */
61
+ export declare function resolveTelegramAccess(opts: {
62
+ allowFrom: unknown;
63
+ senderId: string;
64
+ senderUsername?: string | null;
65
+ }): AccessDecision;
66
+ /**
67
+ * Resolve whether an inbound Feishu/Lark event should be processed.
68
+ *
69
+ * Same fail-closed contract as telegram. DM path checks `allowFrom`;
70
+ * group path checks `allowChats` first, then `groupPolicy` decides
71
+ * whether listed chats trigger the agent (`all` → trigger any message;
72
+ * `observe` → never trigger; `mention` → only on @-mention — caller
73
+ * still has to honour that flag, this helper just returns allow=true).
74
+ *
75
+ * @param opts.conversationType "dm" | "group" | "channel" | "thread"
76
+ * @param opts.allowFrom access.json `allowFrom` array
77
+ * @param opts.allowChats access.json `allowChats` array
78
+ * @param opts.senderId feishu open_id of the sender
79
+ * @param opts.conversationId feishu chat_id of the conversation
80
+ * @param opts.groupPolicy "all" | "mention" | "observe"
81
+ */
82
+ export declare function resolveFeishuAccess(opts: {
83
+ conversationType: "dm" | "group" | "channel" | "thread" | string;
84
+ allowFrom: unknown;
85
+ allowChats: unknown;
86
+ senderId: string;
87
+ conversationId: string;
88
+ groupPolicy: "all" | "mention" | "observe" | string;
89
+ }): AccessDecision;
90
+ /**
91
+ * Build the boot-time warning string when a channel starts with an
92
+ * empty / malformed allowlist. Caller emits this via `warn()` once at
93
+ * startup so operators see the fail-closed posture immediately rather
94
+ * than discovering it the first time a message gets denied.
95
+ *
96
+ * Returns `null` when the allowlist has at least one entry (no boot
97
+ * warning needed).
98
+ */
99
+ export declare function buildEmptyAllowlistWarn(opts: {
100
+ channel: string;
101
+ channelDir: string;
102
+ allowFrom: unknown;
103
+ }): string | null;
@@ -4,7 +4,7 @@ export declare class FeishuAdapter implements IMAdapter {
4
4
  readonly platform = "feishu";
5
5
  readonly ingressMode: IMIngressMode;
6
6
  private feishuConfig;
7
- private connectionName;
7
+ private connectionName_;
8
8
  private client;
9
9
  private wsClient;
10
10
  /**
@@ -17,6 +17,23 @@ export declare class FeishuAdapter implements IMAdapter {
17
17
  /** Where to persist downloaded inbound media (M5c). */
18
18
  private mediaDir;
19
19
  private health_;
20
+ /**
21
+ * Snapshot of the current `access.allowFrom` list (from access.json).
22
+ * Used by the bridge's rate-limiter to exempt operator-vouched explicit
23
+ * sender ids from the DM flood limit (2026-06-29: Vincent's multi-turn
24
+ * heavy work was tripping the 3-msg/60s DM limit; explicit-listed
25
+ * users are already operator-trusted via the access whitelist, no
26
+ * need to also flood-limit them). Returns `[]` before `init()`. The
27
+ * wildcard `["*"]` allowlist does NOT count as "explicit" — that's
28
+ * the public-channel shape and still needs flood protection.
29
+ */
30
+ getAllowFrom(): readonly string[];
31
+ /**
32
+ * Read-only accessor used by bridge to resolve per-connection paths
33
+ * (RFC-020 §15 outbound-marker validation needs the connection name
34
+ * to build the allowed per-conversation directory prefix).
35
+ */
36
+ get connectionName(): string;
20
37
  init(config: IMChannelConfig): Promise<void>;
21
38
  start(onEvent: OnEventHandler): Promise<void>;
22
39
  stop(): Promise<void>;
@@ -26,4 +43,92 @@ export declare class FeishuAdapter implements IMAdapter {
26
43
  edit(_target: IMConversationRef, messageId: string, message: NormalizedIMMessage): Promise<void>;
27
44
  health(): IMAdapterHealth;
28
45
  }
46
+ /**
47
+ * Heuristic — does this text look like it contains markdown syntax that
48
+ * Feishu's plain-text `msg_type:"text"` would render as literal source?
49
+ * Catches the patterns Vincent's heavy work produces: tables (`|...|`
50
+ * + separator row), fenced code blocks (`` ``` ``), ATX headings (`#`),
51
+ * bold/italic (`**text**` / `*text*`), unordered/ordered lists, inline
52
+ * code (`` `code` ``), markdown links (`[label](url)`).
53
+ *
54
+ * Returns true for at least one match; the adapter then upgrades to
55
+ * `msg_type:"interactive"` with a `markdown` element. Returns false for
56
+ * plain prose so the text path stays untouched (no perf cost, no
57
+ * behavior change for non-markdown replies).
58
+ *
59
+ * Conservative — single-character matches (e.g., a `|` in prose, one
60
+ * `*` for emphasis-of-one-word that Feishu would render OK as text)
61
+ * are NOT enough to trigger. We want false-positives < false-negatives
62
+ * (a false-positive upgrade renders fine; a false-negative shows raw
63
+ * `|` and `**`).
64
+ */
65
+ export declare function looksLikeMarkdown(text: string): boolean;
66
+ /**
67
+ * Parse Feishu `message_type: "post"` content into plain text. Post
68
+ * content is a nested structure:
69
+ *
70
+ * { title?: string,
71
+ * content: Array<Array<{ tag: "text"|"img"|"a"|"at"|"emotion", ... }>>
72
+ * }
73
+ *
74
+ * Each top-level array entry is a paragraph; each paragraph is an array
75
+ * of typed segments. We flatten by:
76
+ * - prepending title (if present) as `<title>\n\n`
77
+ * - joining paragraphs with `\n\n`
78
+ * - joining segments within a paragraph in order
79
+ * - tag=text → emit segment.text as-is
80
+ * - tag=a → emit `[label](href)` (markdown link)
81
+ * - tag=at → emit `@user_name` (fallback to `@<user_id>` if no name)
82
+ * - tag=img → emit `[图片]` placeholder (actual download via maybeAttachImages)
83
+ * - tag=emotion → emit `[emoji]`
84
+ * - unknown tag → skip
85
+ *
86
+ * @internal exported for unit tests.
87
+ */
88
+ export declare function parsePostContent(rawJson: string): string;
89
+ /**
90
+ * Walk a Feishu `post` content JSON and collect all `image_key` values
91
+ * from `tag: "img"` segments. Used by `maybeAttachImages` to schedule
92
+ * downloads for every image in a 图文混排 message.
93
+ *
94
+ * @internal exported for unit tests.
95
+ */
96
+ export declare function extractPostImageKeys(rawJson: string): string[];
97
+ /**
98
+ * Sanitize a Feishu-supplied `file_name` so it's safe to append to a
99
+ * filesystem path. Strips `/`, `\`, `..`, control characters, and NUL
100
+ * bytes. Empty / all-stripped input falls back to a placeholder that
101
+ * uses the message id, so a hostile client can never write outside the
102
+ * conversation's `<mediaDir>/<convKey>/` directory.
103
+ *
104
+ * NOT a full display-safety pass — the LLM still sees the sanitized
105
+ * bytes and shouldn't render them as HTML/etc. That's a Layer above.
106
+ */
107
+ export declare function sanitizeFileName(raw: string, fallback: string): string;
108
+ /**
109
+ * Feishu text-message practical chunk threshold (RFC-020 §16).
110
+ *
111
+ * The official `im.message.create`/`reply` content limit for
112
+ * `msg_type:text` is ~30 KB JSON-encoded (`{"text":"..."}`), comfortably
113
+ * under what any reasonable bot reply produces. We chunk below that
114
+ * limit at 4000 CHARACTERS — gives a roomy safety margin for multi-byte
115
+ * UTF-8 and lets us split at paragraph boundaries cleanly. Chosen
116
+ * conservatively after Vincent 2026-06-30 ask "issue 发文字" (i.e.
117
+ * never silently fall back to PNG for "long" plain-text replies — they
118
+ * just chunk into multiple messages).
119
+ *
120
+ * Single-message ceiling, NOT a per-second rate limit (that's separate;
121
+ * RFC-020 §4.4).
122
+ */
123
+ export declare const FEISHU_TEXT_SINGLE_LIMIT = 4000;
124
+ /**
125
+ * Split a long text into chunks ≤ `maxChars`. Tries paragraph boundaries
126
+ * (`\n\n`), then line boundaries (`\n`), then word boundaries (space),
127
+ * then hard byte split. Output preserves the original text content
128
+ * (sum of chunks == original, modulo the boundary character that gets
129
+ * consumed by the split).
130
+ *
131
+ * If the input is already short enough, returns a single-element array.
132
+ */
133
+ export declare function splitTextForFeishu(text: string, maxChars: number): string[];
29
134
  export {};
@@ -49,6 +49,13 @@ export interface FeishuBridgeOptions {
49
49
  export interface BridgeIncomingEnvelope {
50
50
  type: "event";
51
51
  event: NormalizedIMEvent;
52
+ /** Canonical outbound directory for this conversation (RFC-020 §15.1).
53
+ * Single source of truth — the agent-node injects this verbatim into
54
+ * the system prompt's "save files here" instruction, and the bridge
55
+ * whitelist accepts files only under this directory. Computed by the
56
+ * bridge from `event.conversation.conversationId` + `adapter
57
+ * .connectionName`. Trailing slash included. */
58
+ outboundDir?: string;
52
59
  }
53
60
  /** Parent → bridge: agent reply text for a previously-forwarded event. */
54
61
  export interface BridgeReplyEnvelope {
@@ -8,6 +8,31 @@ export interface FeishuAccessList {
8
8
  /** Feishu chat_ids the bot is permitted to listen in. */
9
9
  allowChats: string[];
10
10
  }
11
+ /**
12
+ * Outbound text-reply rendering mode (RFC-020 §16). Controls how
13
+ * `adapter.send` handles a text/markdown payload that doesn't already
14
+ * carry an `imagePath` / `files[]` (those upload routes are unaffected).
15
+ *
16
+ * - "plain" (DEFAULT): always send `msg_type:text`. Bot replies are
17
+ * fully copy-pasteable in Feishu; long replies are chunked into
18
+ * multiple text messages instead of being PNG-rendered. This is the
19
+ * correct default for issue/code/CLI bot replies where users want
20
+ * to grab the text. Vincent 2026-06-30 explicit ask: "issue 发文字".
21
+ *
22
+ * - "card": short markdown (bold, list, link, inline code) goes via
23
+ * schema 1.0 interactive card with `markdown` element — text stays
24
+ * copy-friendly + gets bolds/bullets styled. Heading/table/long
25
+ * fall back to plain text (no PNG). Suited to operators who want
26
+ * light formatting without losing copy.
27
+ *
28
+ * - "auto": preserve the pre-2026-06-30 behavior — markdown with
29
+ * headings / tables / >2000 chars is rendered to PNG via headless
30
+ * chromium (#329 path), short markdown goes to schema 1.0 card,
31
+ * plain text goes to msg_type:text. Highest fidelity at the cost
32
+ * of copy-paste. Opt-in for operators who genuinely need rendered
33
+ * tables / heading hierarchy in chat.
34
+ */
35
+ export type OutboundRenderMode = "plain" | "card" | "auto";
11
36
  export interface FeishuChannelConfig {
12
37
  appId: string;
13
38
  appSecret: string;
@@ -20,6 +45,12 @@ export interface FeishuChannelConfig {
20
45
  auditRaw: boolean;
21
46
  /** Per-task timeout in ms; default 5 min (RFC-020 §4.5). */
22
47
  taskTimeoutMs: number;
48
+ /**
49
+ * RFC-020 §16 outbound rendering mode for text replies. Default `"plain"`.
50
+ * See `OutboundRenderMode` for per-mode semantics. Channels that omit
51
+ * the field get `"plain"` — Vincent's "issue 发文字" default.
52
+ */
53
+ outboundRender: OutboundRenderMode;
23
54
  /**
24
55
  * Absolute path to the channel directory. The adapter writes downloaded
25
56
  * inbound media to `<channelDir>/media/` (M5c). Populated by the loader so
@@ -0,0 +1,88 @@
1
+ /**
2
+ * RFC-020 §17 — feishu inbound bridge → hub `/api/upload` integration.
3
+ *
4
+ * Vincent 2026-06-30: cross-machine agents (e.g. TM门户运维@toodadev2)
5
+ * delegated by the feishu-local bot can't read inbound attachments
6
+ * because the only thing they get is a host-local path that doesn't
7
+ * exist on the receiver's filesystem. #351 already wired agent-node's
8
+ * receiver to prefer `file_id` and pull via `GET /api/files/<id>` when
9
+ * present. This module is the SENDER half: the feishu bridge uploads
10
+ * each downloaded inbound file to the hub via `POST /api/upload`,
11
+ * gets back a `file_id`, and propagates it alongside the local path.
12
+ *
13
+ * Failure mode (load-bearing): every failure path returns `null` and
14
+ * the caller falls back to path-only. The bridge MUST NOT crash because
15
+ * the hub is down / overloaded / refused a 12 MiB cap. Single-host
16
+ * `agent-node` Read still works on the local path; only cross-host
17
+ * delegation degrades, no regression vs the pre-fix baseline.
18
+ *
19
+ * Concurrency cap: a single feishu message can carry many images; bursts
20
+ * to /api/upload would hammer the hub rate limit (60/hour per token).
21
+ * `uploadFilesToHubConcurrent` caps in-flight requests at `concurrency`
22
+ * (default 4) so a 20-image message takes 5 sequential rounds instead
23
+ * of 20 simultaneous open sockets + a likely rate-limit denial.
24
+ */
25
+ /**
26
+ * Hub upload limit, mirrors `server/src/uploads.ts:MAX_UPLOAD_BYTES`.
27
+ * Anything bigger is skipped at the bridge — the hub would 413 anyway.
28
+ */
29
+ export declare const HUB_UPLOAD_LIMIT_BYTES: number;
30
+ /** Default in-flight cap for `uploadFilesToHubConcurrent`. */
31
+ export declare const DEFAULT_UPLOAD_CONCURRENCY = 4;
32
+ /**
33
+ * One file uploaded to the hub. `mime` and `size` are from the local
34
+ * filesystem read; `file_id` and `path` come from the hub's response.
35
+ *
36
+ * `file_id` is the canonical cross-machine handle — receiving agent
37
+ * resolves to bytes via `GET /api/files/<file_id>`. `path` is the
38
+ * hub-machine-local absolute path the hub stored to; on the sender
39
+ * side it's purely informational (we already have the source file).
40
+ */
41
+ export interface HubUploadResult {
42
+ file_id: string;
43
+ path?: string;
44
+ mime?: string;
45
+ size?: number;
46
+ name?: string;
47
+ }
48
+ export interface HubUploadOpts {
49
+ /** Hub base URL, no trailing slash. Defaults to env. */
50
+ hubUrl: string;
51
+ /** Bearer token (ntok_ / utok_ / atok_). Defaults to env. */
52
+ authToken: string;
53
+ /** Optional MIME hint; sniffed by filename ext otherwise. */
54
+ mime?: string;
55
+ /** Optional override filename (defaults to basename(path)). */
56
+ name?: string;
57
+ /** Injectable for tests. Defaults to global fetch. */
58
+ fetch?: typeof fetch;
59
+ }
60
+ /**
61
+ * Upload one local file to `${hubUrl}/api/upload`. Returns the file
62
+ * descriptor on success, or `null` on any failure (caller falls back
63
+ * to path-only).
64
+ *
65
+ * - File must exist + be ≤ 12 MiB. Larger → skipped silently with
66
+ * a stderr warn; caller gets `null` and ships path-only.
67
+ * - Auth via `Authorization: Bearer <token>`.
68
+ * - `Content-Length` header is set by `fetch` when given a Blob body;
69
+ * the hub validates it before consuming bytes.
70
+ * - Multipart body with a single `file` field. The hub server
71
+ * reads `form.get("file")` and stores under a new file_id.
72
+ * - Any network error / non-2xx / malformed JSON response → null.
73
+ */
74
+ export declare function uploadToHub(filePath: string, opts: HubUploadOpts): Promise<HubUploadResult | null>;
75
+ /**
76
+ * Upload many files to the hub with a bounded concurrency window.
77
+ * Returns one result per input path, preserving order. A failed
78
+ * upload appears as `null` at that slot — caller composes path-only
79
+ * descriptors for those.
80
+ *
81
+ * Why this matters: one feishu post can carry an arbitrary number of
82
+ * images. Naive `Promise.all(paths.map(uploadToHub))` would open N
83
+ * simultaneous HTTPS sockets to the hub AND blow past the 60/hour
84
+ * per-token rate limit on a single message with >60 images. A
85
+ * concurrency cap of 4 means N/4 sequential rounds, predictable
86
+ * load, no head-of-line blocking on the agent's reply.
87
+ */
88
+ export declare function uploadFilesToHubConcurrent(filePaths: string[], opts: HubUploadOpts, concurrency?: number): Promise<(HubUploadResult | null)[]>;
@@ -0,0 +1,61 @@
1
+ /**
2
+ * RFC-020 §14 — markdown → image rendering for Feishu replies.
3
+ *
4
+ * Vincent 2026-06-29 path: Feishu's `markdown` card element doesn't
5
+ * render ATX headings or GFM tables (preview.7 caught only bold / list
6
+ * / link). Rather than partially support a moving subset of card
7
+ * elements, we render structured markdown to a PNG via headless
8
+ * chromium and send it through Feishu's image API (`im:resource:upload`
9
+ * scope). Pixel-perfect fidelity, zero schema fragility, text-not-
10
+ * copyable accepted as the tradeoff for "actually renders".
11
+ *
12
+ * Hybrid route (decided in adapter.send):
13
+ * - plain text (no markdown markers) → msg_type:"text"
14
+ * - markdown WITHOUT heading / table / long → msg_type:"interactive" (schema 1.0 card with `markdown` element — keeps copy/paste for short bold/list/link replies; preview.7 path)
15
+ * - markdown WITH heading / table / long → THIS PATH (msg_type:"image" with rendered PNG)
16
+ *
17
+ * Renderer choice: `puppeteer-core` (no bundled chromium) + system
18
+ * chromium (apt install in Docker). Rationale:
19
+ * - Pure-JS canvas-layout libraries (node-canvas, @napi-rs/canvas)
20
+ * cost 4-6h of manual paragraph wrapping + table cell measurement
21
+ * + Chinese width metrics; chromium does this natively.
22
+ * - puppeteer-core (5MB) + system chromium (~100MB) is smaller than
23
+ * full puppeteer (170MB) which bundles its own chromium.
24
+ * - Headless screenshot ~500ms after warmup. Bot's heavy turn is 20-
25
+ * 70s; rendering cost is in the noise.
26
+ *
27
+ * Chromium reuse: we keep a single browser instance hot across
28
+ * renderings to avoid the ~2-3s cold-start per call. Auto-close on
29
+ * idle is a follow-up — agent-node worker lifetime is bounded.
30
+ */
31
+ import { Buffer } from "node:buffer";
32
+ /**
33
+ * Close the shared browser. Called from adapter.stop() during graceful
34
+ * worker shutdown. Subsequent render calls re-launch.
35
+ */
36
+ export declare function closeBrowser(): Promise<void>;
37
+ /**
38
+ * Render markdown text to a PNG buffer.
39
+ *
40
+ * Width is fixed at 800px (IM-friendly); height auto-fits content via
41
+ * `fullPage:true`. Returns the raw PNG bytes — caller hands them to
42
+ * `lark.im.image.create({image: Readable.from(buffer)})`.
43
+ *
44
+ * Throws on chromium launch failure or page navigation failure.
45
+ * Caller is responsible for fallback (e.g., send the raw text as a
46
+ * code block in the schema 1.0 card path).
47
+ */
48
+ export declare function renderMarkdownToPng(text: string): Promise<Buffer>;
49
+ /**
50
+ * Decide whether `renderMarkdownToPng` should be invoked for this reply
51
+ * text, vs. falling back to msg_type:"text" / msg_type:"interactive"
52
+ * (schema 1.0 card). Locked by 通信龙 3f70044c — trigger image when:
53
+ * - text contains a markdown table (Feishu card can't render)
54
+ * - text contains an ATX heading (Feishu card can't render)
55
+ * - text length > 2000 chars (image is better than scrollable text wall)
56
+ *
57
+ * Returns false for short prose, single-line plain text, and short
58
+ * markdown lists / bold / link / inline-code which DO render fine in
59
+ * the schema 1.0 `markdown` card element.
60
+ */
61
+ export declare function shouldRenderAsImage(text: string): boolean;