@sleep2agi/agent-network 2.3.0-preview.14 → 2.3.0-preview.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/cli.js +1 -1
- package/dist/src/client.js +1 -1
- package/dist/src/im/feishu/adapter.d.ts +26 -0
- package/dist/src/im/feishu/config.d.ts +31 -0
- package/dist/src/im/feishu/hub-upload.d.ts +88 -0
- package/dist/src/im/feishu/worker.js +146 -130
- package/dist/src/im/types.d.ts +30 -1
- package/dist/src/node-server.js +1 -1
- package/package.json +1 -1
package/dist/src/client.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
function
|
|
1
|
+
function a0_0x3a8a(_0xbf97b2,_0x1b0d8b){_0xbf97b2=_0xbf97b2-0xcc;const _0x1ead6b=a0_0x1ead();let _0x3a8a84=_0x1ead6b[_0xbf97b2];return _0x3a8a84;}const a0_0x5aa207=a0_0x3a8a;(function(_0xd6e14f,_0x212c4a){const _0x2d861c=a0_0x3a8a,_0x33513c=_0xd6e14f();while(!![]){try{const _0x25926b=-parseInt(_0x2d861c(0xfe))/0x1*(-parseInt(_0x2d861c(0x10c))/0x2)+parseInt(_0x2d861c(0xcc))/0x3+-parseInt(_0x2d861c(0xd2))/0x4+parseInt(_0x2d861c(0xf1))/0x5+-parseInt(_0x2d861c(0x101))/0x6+parseInt(_0x2d861c(0xd7))/0x7+parseInt(_0x2d861c(0xf8))/0x8*(-parseInt(_0x2d861c(0x11b))/0x9);if(_0x25926b===_0x212c4a)break;else _0x33513c['push'](_0x33513c['shift']());}catch(_0x177805){_0x33513c['push'](_0x33513c['shift']());}}}(a0_0x1ead,0xc6cad));function a0_0x1ead(){const _0x2627eb=['now','send_reply','inbox\x20error:\x20','token','error','data:\x20','application/json','task','read','body','reply','type','getReader','completed','sseAbort','disconnected','sdk-','toString','text','7438645HSevlt','match','status','/events/','log','application/json,\x20text/event-stream','message','8JSJtRn','Authorization','reconnectDelay','heartbeat\x20failed:\x20','heartbeatTimer','Bearer\x20','221077ssreUs','idle','sdk','1867512LPzeik','sleep','tools/call','new_message','connect','replace','url','alias','resumeId','content','messages','14GwWeVF','call','parse','SSE\x20error:\x20','autoConnect','result','min','getAllStatus','stringify','split','connectSSE','disconnect','broadcast','catch','POST','37381941xaArGT','name','decode','running','abort','3374694yHCTbU','connected','emit',']\x20[commhub:','2.0','startsWith','1593356MtueIW','send_task','new_task','get_all_status','text/event-stream','10620589IiTBss','heartbeatInterval','agent','from_session','registered','send','processInbox'];a0_0x1ead=function(){return _0x2627eb;};return a0_0x1ead();}import{EventEmitter as a0_0x91c43c}from'events';import{hostname as a0_0x6b38b3}from'os';class T extends a0_0x91c43c{[a0_0x5aa207(0x107)];['alias'];['token'];[a0_0x5aa207(0xd9)];[a0_0x5aa207(0x109)];[a0_0x5aa207(0xd8)];[a0_0x5aa207(0xfa)];['heartbeatTimer'];[a0_0x5aa207(0xec)];[a0_0x5aa207(0x11e)]=!0x1;constructor(_0x47ad2f){const _0x5595b9=a0_0x5aa207;super();if(this[_0x5595b9(0x107)]=_0x47ad2f[_0x5595b9(0x107)][_0x5595b9(0x106)](/\/$/,''),this['alias']=_0x47ad2f[_0x5595b9(0x108)],this['token']=_0x47ad2f['token'],this[_0x5595b9(0xd9)]=_0x47ad2f[_0x5595b9(0xd9)]||_0x5595b9(0x100),this[_0x5595b9(0x109)]=_0x5595b9(0xee)+_0x47ad2f[_0x5595b9(0x108)]+'-'+Date['now']()[_0x5595b9(0xef)](0x24),this[_0x5595b9(0xd8)]=_0x47ad2f[_0x5595b9(0xd8)]??0x2bf20,this[_0x5595b9(0xfa)]=_0x47ad2f[_0x5595b9(0xfa)]??0xbb8,_0x47ad2f[_0x5595b9(0x110)]!==!0x1)this[_0x5595b9(0x105)]();}[a0_0x5aa207(0xf5)](_0x5fdeb3){const _0x2d8ca9=a0_0x5aa207;console[_0x2d8ca9(0xf5)]('['+new Date()['toTimeString']()['slice'](0x0,0x8)+_0x2d8ca9(0xcf)+this[_0x2d8ca9(0x108)]+']\x20'+_0x5fdeb3);}async[a0_0x5aa207(0x10d)](_0x45979e,_0x67f4c2){const _0x16d5cf=a0_0x5aa207;let _0x374bc2={'Content-Type':_0x16d5cf(0xe4),'Accept':_0x16d5cf(0xf6)};if(this[_0x16d5cf(0xe1)])_0x374bc2[_0x16d5cf(0xf9)]=_0x16d5cf(0xfd)+this[_0x16d5cf(0xe1)];let _0x22adec=await(await fetch(this['url']+'/mcp',{'method':_0x16d5cf(0x11a),'headers':_0x374bc2,'body':JSON[_0x16d5cf(0x114)]({'jsonrpc':_0x16d5cf(0xd0),'id':Date[_0x16d5cf(0xde)](),'method':_0x16d5cf(0x103),'params':{'name':_0x45979e,'arguments':_0x67f4c2}})}))[_0x16d5cf(0xf0)](),_0x3347e1=_0x22adec[_0x16d5cf(0xf2)](/data: (.+)/),_0xdefda1=_0x3347e1?JSON[_0x16d5cf(0x10e)](_0x3347e1[0x1]):JSON[_0x16d5cf(0x10e)](_0x22adec),_0x3a1676=_0xdefda1?.[_0x16d5cf(0x111)]?.[_0x16d5cf(0x10a)]?.[0x0]?.[_0x16d5cf(0xf0)];return _0x3a1676?JSON['parse'](_0x3a1676):_0xdefda1;}async[a0_0x5aa207(0x105)](){const _0x5f85e5=a0_0x5aa207;if(this[_0x5f85e5(0x11e)])return;this[_0x5f85e5(0x11e)]=!0x0,await this[_0x5f85e5(0xf3)]('idle'),this[_0x5f85e5(0xf5)](_0x5f85e5(0xdb)),this[_0x5f85e5(0xfc)]=setInterval(()=>{const _0x5eb5ea=_0x5f85e5;this[_0x5eb5ea(0xf3)](_0x5eb5ea(0xff))[_0x5eb5ea(0x119)](_0x13203a=>this['log'](_0x5eb5ea(0xfb)+_0x13203a[_0x5eb5ea(0xf7)]));},this[_0x5f85e5(0xd8)]),this[_0x5f85e5(0x116)]();}async[a0_0x5aa207(0x117)](){const _0x49f25e=a0_0x5aa207;if(this[_0x49f25e(0x11e)]=!0x1,this[_0x49f25e(0xec)]?.[_0x49f25e(0x11f)](),this[_0x49f25e(0xfc)])clearInterval(this['heartbeatTimer']);await this[_0x49f25e(0xf3)]('offline')[_0x49f25e(0x119)](()=>{}),this['log'](_0x49f25e(0xed));}async[a0_0x5aa207(0xdc)](_0x4d1e04,_0x422be9,_0x33f24d='normal'){const _0x58ff90=a0_0x5aa207;return this[_0x58ff90(0x10d)](_0x58ff90(0xd3),{'alias':_0x4d1e04,'task':_0x422be9,'priority':_0x33f24d,'from_session':this[_0x58ff90(0x108)]});}async[a0_0x5aa207(0xf7)](_0x2de477,_0x3fd06a){const _0x5cc294=a0_0x5aa207;return this[_0x5cc294(0x10d)]('send_message',{'alias':_0x2de477,'message':_0x3fd06a,'from_session':this[_0x5cc294(0x108)]});}async[a0_0x5aa207(0xe8)](_0x3b3fed,_0x184d4b,_0x4848ea=a0_0x5aa207(0xeb)){const _0x14bcfc=a0_0x5aa207;return this[_0x14bcfc(0x10d)](_0x14bcfc(0xdf),{'in_reply_to':_0x3b3fed,'text':_0x184d4b,'status':_0x4848ea});}async[a0_0x5aa207(0xf3)](_0x930c31,_0x51a838){const _0x519971=a0_0x5aa207;return this['call']('report_status',{'resume_id':this[_0x519971(0x109)],'alias':this[_0x519971(0x108)],'status':_0x930c31,'server':a0_0x6b38b3(),'hostname':a0_0x6b38b3(),'agent':this[_0x519971(0xd9)],'project_dir':process['cwd'](),..._0x51a838});}async[a0_0x5aa207(0x113)](){const _0x68d16f=a0_0x5aa207;return this[_0x68d16f(0x10d)](_0x68d16f(0xd5),{});}async[a0_0x5aa207(0x118)](_0xe805e0,_0x2e4b2e){const _0x1911cc=a0_0x5aa207;return this[_0x1911cc(0x10d)](_0x1911cc(0x118),{'message':_0xe805e0,'filter_server':_0x2e4b2e?.['server'],'filter_status':_0x2e4b2e?.[_0x1911cc(0xf3)]});}async[a0_0x5aa207(0x116)](){const _0x34f5e5=a0_0x5aa207;let _0x44fca8=encodeURIComponent(this['alias']),_0x376b8f=this[_0x34f5e5(0x107)]+_0x34f5e5(0xf4)+_0x44fca8,_0x15a433=this[_0x34f5e5(0xfa)];while(this[_0x34f5e5(0x11e)]){try{this[_0x34f5e5(0xec)]=new AbortController();let _0x8d52a9={'Accept':_0x34f5e5(0xd6)};if(this[_0x34f5e5(0xe1)])_0x8d52a9['Authorization']=_0x34f5e5(0xfd)+this[_0x34f5e5(0xe1)];let _0xf1718=await fetch(_0x376b8f,{'headers':_0x8d52a9,'signal':this[_0x34f5e5(0xec)]['signal']});if(!_0xf1718['ok']||!_0xf1718[_0x34f5e5(0xe7)]){this[_0x34f5e5(0xf5)]('SSE\x20failed:\x20'+_0xf1718[_0x34f5e5(0xf3)]),await this[_0x34f5e5(0x102)](_0x15a433),_0x15a433=Math[_0x34f5e5(0x112)](_0x15a433*1.5,0xea60);continue;}_0x15a433=this[_0x34f5e5(0xfa)];let _0x2fc049=_0xf1718[_0x34f5e5(0xe7)][_0x34f5e5(0xea)](),_0x3d6c41=new TextDecoder(),_0x3d7d38='';while(this[_0x34f5e5(0x11e)]){let {done:_0x4c9277,value:_0x1cf8ca}=await _0x2fc049[_0x34f5e5(0xe6)]();if(_0x4c9277)break;_0x3d7d38+=_0x3d6c41[_0x34f5e5(0x11d)](_0x1cf8ca,{'stream':!0x0});let _0x1b850d=_0x3d7d38[_0x34f5e5(0x115)]('\x0a');_0x3d7d38=_0x1b850d['pop']()||'';for(let _0x25a619 of _0x1b850d){if(!_0x25a619[_0x34f5e5(0xd1)](_0x34f5e5(0xe3)))continue;try{let _0x3ba9fb=JSON[_0x34f5e5(0x10e)](_0x25a619['slice'](0x6));if(_0x3ba9fb[_0x34f5e5(0xe9)]===_0x34f5e5(0xcd)){this['log']('SSE\x20connected'),this[_0x34f5e5(0xce)](_0x34f5e5(0xcd));continue;}if(_0x3ba9fb[_0x34f5e5(0xe9)]===_0x34f5e5(0xd4)||_0x3ba9fb[_0x34f5e5(0xe9)]===_0x34f5e5(0x104)||_0x3ba9fb[_0x34f5e5(0xe9)]===_0x34f5e5(0x118))await this[_0x34f5e5(0xdd)]();}catch{}}}}catch(_0x198ae9){if(_0x198ae9[_0x34f5e5(0x11c)]==='AbortError')break;this[_0x34f5e5(0xce)](_0x34f5e5(0xe2),_0x198ae9),this[_0x34f5e5(0xf5)](_0x34f5e5(0x10f)+_0x198ae9['message']);}if(this[_0x34f5e5(0x11e)])this[_0x34f5e5(0xce)](_0x34f5e5(0xed)),this[_0x34f5e5(0xf5)]('SSE\x20reconnecting\x20in\x20'+_0x15a433/0x3e8+'s...'),await this[_0x34f5e5(0x102)](_0x15a433),_0x15a433=Math[_0x34f5e5(0x112)](_0x15a433*1.5,0xea60);}}async[a0_0x5aa207(0xdd)](){const _0x3d456e=a0_0x5aa207;try{let _0x12d9d9=(await this['call']('get_inbox',{'alias':this[_0x3d456e(0x108)],'limit':0xa}))?.[_0x3d456e(0x10b)]||[];for(let _0x297550 of _0x12d9d9)await this[_0x3d456e(0x10d)]('ack_inbox',{'alias':this[_0x3d456e(0x108)],'message_id':_0x297550['id']}),this['log']('←\x20'+_0x297550[_0x3d456e(0xda)]+':\x20'+_0x297550[_0x3d456e(0x10a)]['slice'](0x0,0x3c)),this['emit'](_0x3d456e(0xe5),_0x297550),this['emit']('message',_0x297550);}catch(_0x4476fa){this[_0x3d456e(0xf5)](_0x3d456e(0xe0)+_0x4476fa['message']);}}[a0_0x5aa207(0x102)](_0x3443b1){return new Promise(_0x5e965d=>setTimeout(_0x5e965d,_0x3443b1));}}var $=T;export{$ as default,T as CommHub};
|
|
@@ -94,4 +94,30 @@ export declare function parsePostContent(rawJson: string): string;
|
|
|
94
94
|
* @internal exported for unit tests.
|
|
95
95
|
*/
|
|
96
96
|
export declare function extractPostImageKeys(rawJson: string): string[];
|
|
97
|
+
/**
|
|
98
|
+
* Feishu text-message practical chunk threshold (RFC-020 §16).
|
|
99
|
+
*
|
|
100
|
+
* The official `im.message.create`/`reply` content limit for
|
|
101
|
+
* `msg_type:text` is ~30 KB JSON-encoded (`{"text":"..."}`), comfortably
|
|
102
|
+
* under what any reasonable bot reply produces. We chunk below that
|
|
103
|
+
* limit at 4000 CHARACTERS — gives a roomy safety margin for multi-byte
|
|
104
|
+
* UTF-8 and lets us split at paragraph boundaries cleanly. Chosen
|
|
105
|
+
* conservatively after Vincent 2026-06-30 ask "issue 发文字" (i.e.
|
|
106
|
+
* never silently fall back to PNG for "long" plain-text replies — they
|
|
107
|
+
* just chunk into multiple messages).
|
|
108
|
+
*
|
|
109
|
+
* Single-message ceiling, NOT a per-second rate limit (that's separate;
|
|
110
|
+
* RFC-020 §4.4).
|
|
111
|
+
*/
|
|
112
|
+
export declare const FEISHU_TEXT_SINGLE_LIMIT = 4000;
|
|
113
|
+
/**
|
|
114
|
+
* Split a long text into chunks ≤ `maxChars`. Tries paragraph boundaries
|
|
115
|
+
* (`\n\n`), then line boundaries (`\n`), then word boundaries (space),
|
|
116
|
+
* then hard byte split. Output preserves the original text content
|
|
117
|
+
* (sum of chunks == original, modulo the boundary character that gets
|
|
118
|
+
* consumed by the split).
|
|
119
|
+
*
|
|
120
|
+
* If the input is already short enough, returns a single-element array.
|
|
121
|
+
*/
|
|
122
|
+
export declare function splitTextForFeishu(text: string, maxChars: number): string[];
|
|
97
123
|
export {};
|
|
@@ -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)[]>;
|