@agentunion/fastaun-browser 0.5.6 → 0.5.8
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/CHANGELOG.md +74 -0
- package/_packed_docs/CHANGELOG.md +74 -0
- package/_packed_docs/INDEX.md +12 -11
- package/_packed_docs/KITE_DOCS_GUIDE.md +4 -3
- package/_packed_docs/agent.md/SCHEMA.md +83 -58
- package/_packed_docs/agent.md/examples/codeagent-claudecode.md +1 -1
- package/_packed_docs/agent.md/examples/openclaw-lobster.md +1 -1
- package/_packed_docs/agent.md/examples/signed-openclaw-lobster.md +1 -1
- package/_packed_docs/audit/AUN/346/234/215/345/212/241Go/345/214/226/351/207/215/346/236/204/347/262/276/347/273/206/345/214/226/346/272/220/347/240/201/345/256/241/346/237/245-20260718.md +3 -3
- package/_packed_docs/aun/345/205/254/347/275/221/346/265/213/350/257/225/350/277/220/350/241/214/346/214/207/345/215/227.md +7 -0
- package/_packed_docs/aun/345/210/206/345/270/203/345/274/217/346/265/213/350/257/225/350/277/220/350/241/214/346/214/207/345/215/227.md +422 -331
- package/_packed_docs/aun/346/265/213/350/257/225/350/277/220/350/241/214/346/214/207/345/215/227.md +598 -398
- package/_packed_docs/group-message-rpc-alignment-gaps.md +741 -0
- package/_packed_docs/message-online-push-alignment.md +572 -0
- package/_packed_docs/protocol/06-/346/234/215/345/212/241/345/215/217/350/256/256.md +1 -1
- package/_packed_docs/sdk/02-WebSocket/345/215/217/350/256/256.md +10 -10
- package/_packed_docs/sdk/03-/346/240/270/345/277/203/346/246/202/345/277/265.md +2 -2
- package/_packed_docs/sdk/04-/350/277/236/346/216/245/344/270/216/350/256/244/350/257/201.md +30 -10
- package/_packed_docs/sdk/06-API/346/211/213/345/206/214.md +23 -20
- package/_packed_docs/sdk/07-/351/224/231/350/257/257/345/244/204/347/220/206.md +3 -3
- package/_packed_docs/sdk/08-/346/234/200/344/275/263/345/256/236/350/267/265.md +5 -6
- package/_packed_docs/sdk/09-group-rpc-manual.md +11 -7
- package/_packed_docs/sdk/09-message-rpc-manual.md +5 -3
- package/_packed_docs/sdk/09-storage-rpc-manual.md +14 -3
- package/_packed_docs/sdk/AUN_DOCS_GUIDE.md +19 -17
- package/_packed_docs/sdk/CHANGELOG-0.5.6.md +73 -0
- package/_packed_docs/sdk/INDEX.md +22 -22
- package/_packed_docs/sdk/README.md +6 -5
- package/_packed_docs//345/217/221/345/270/203/346/212/245/345/221/212-0.5.6.md +260 -0
- package/dist/agent-md-schema.d.ts +14 -0
- package/dist/agent-md-schema.d.ts.map +1 -0
- package/dist/agent-md-schema.js +103 -0
- package/dist/agent-md-schema.js.map +1 -0
- package/dist/agent-md.d.ts +7 -0
- package/dist/agent-md.d.ts.map +1 -1
- package/dist/agent-md.js +229 -10
- package/dist/agent-md.js.map +1 -1
- package/dist/aid-store.d.ts +5 -1
- package/dist/aid-store.d.ts.map +1 -1
- package/dist/aid-store.js +26 -4
- package/dist/aid-store.js.map +1 -1
- package/dist/aid.d.ts +2 -0
- package/dist/aid.d.ts.map +1 -1
- package/dist/aid.js +29 -7
- package/dist/aid.js.map +1 -1
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +1 -0
- package/dist/auth.js.map +1 -1
- package/dist/bundle.js +29895 -21968
- package/dist/cert-utils.d.ts +1 -0
- package/dist/cert-utils.d.ts.map +1 -1
- package/dist/cert-utils.js +36 -16
- package/dist/cert-utils.js.map +1 -1
- package/dist/client/delivery.d.ts +33 -5
- package/dist/client/delivery.d.ts.map +1 -1
- package/dist/client/delivery.js +597 -220
- package/dist/client/delivery.js.map +1 -1
- package/dist/client/lifecycle.d.ts +15 -1
- package/dist/client/lifecycle.d.ts.map +1 -1
- package/dist/client/lifecycle.js +452 -151
- package/dist/client/lifecycle.js.map +1 -1
- package/dist/client/rpc-pipeline.d.ts +17 -6
- package/dist/client/rpc-pipeline.d.ts.map +1 -1
- package/dist/client/rpc-pipeline.js +284 -115
- package/dist/client/rpc-pipeline.js.map +1 -1
- package/dist/client/runtime.d.ts +1 -3
- package/dist/client/runtime.d.ts.map +1 -1
- package/dist/client/runtime.js +4 -7
- package/dist/client/runtime.js.map +1 -1
- package/dist/client/v2-e2ee.d.ts +20 -2
- package/dist/client/v2-e2ee.d.ts.map +1 -1
- package/dist/client/v2-e2ee.js +398 -114
- package/dist/client/v2-e2ee.js.map +1 -1
- package/dist/client.d.ts +22 -10
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +396 -104
- package/dist/client.js.map +1 -1
- package/dist/facades.d.ts +23 -1
- package/dist/facades.d.ts.map +1 -1
- package/dist/facades.js +51 -7
- package/dist/facades.js.map +1 -1
- package/dist/group-id.d.ts.map +1 -1
- package/dist/group-id.js +2 -17
- package/dist/group-id.js.map +1 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/seq-tracker.d.ts.map +1 -1
- package/dist/seq-tracker.js +8 -2
- package/dist/seq-tracker.js.map +1 -1
- package/dist/tools/cross-sdk-agent.js +23 -1
- package/dist/tools/cross-sdk-agent.js.map +1 -1
- package/dist/transport.d.ts.map +1 -1
- package/dist/transport.js +8 -1
- package/dist/transport.js.map +1 -1
- package/dist/validators.d.ts.map +1 -1
- package/dist/validators.js +8 -14
- package/dist/validators.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +6 -5
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { normalizeSlotId, slotIsolationKey } from '../config.js';
|
|
2
|
-
import { ClientSignatureError, ConnectionError, PermissionError, ValidationError } from '../errors.js';
|
|
2
|
+
import { ClientSignatureError, ConnectionError, PermissionError, TimeoutError, ValidationError } from '../errors.js';
|
|
3
3
|
import { validateAIDFormat, validateGroupIDFormat } from '../validators.js';
|
|
4
4
|
import { p1363ToDer, pemToArrayBuffer, uint8ToBase64 } from '../crypto.js';
|
|
5
5
|
import { ConnectionState, isJsonObject, } from '../types.js';
|
|
@@ -119,9 +119,10 @@ async function signingCertFingerprint(certPem) {
|
|
|
119
119
|
return await cached;
|
|
120
120
|
}
|
|
121
121
|
const PULL_GATE_STALE_MS = 30000;
|
|
122
|
+
const PULL_GATE_OPERATION_TIMEOUT_MS = 3000;
|
|
122
123
|
const NON_IDEMPOTENT_TIMEOUT = 35;
|
|
123
124
|
const NON_IDEMPOTENT_METHODS = new Set([
|
|
124
|
-
'message.send', 'group.send', 'group.create', 'group.invite',
|
|
125
|
+
'message.send', 'mail.send', 'group.send', 'group.create', 'group.invite',
|
|
125
126
|
'group.kick', 'group.remove_member', 'group.leave', 'group.dissolve',
|
|
126
127
|
'group.set_settings',
|
|
127
128
|
'group.update_announcement', 'group.update_rules',
|
|
@@ -168,7 +169,7 @@ function isTransientV2PullFallbackError(error) {
|
|
|
168
169
|
export class RpcPipeline {
|
|
169
170
|
runtime;
|
|
170
171
|
pullGateStates = new Map();
|
|
171
|
-
|
|
172
|
+
inlineRealtimeScopes = new Set();
|
|
172
173
|
constructor(runtime) {
|
|
173
174
|
this.runtime = runtime;
|
|
174
175
|
}
|
|
@@ -198,6 +199,8 @@ export class RpcPipeline {
|
|
|
198
199
|
};
|
|
199
200
|
const skipSendResultEnvelope = Boolean(p._skip_send_result_envelope);
|
|
200
201
|
delete p._skip_send_result_envelope;
|
|
202
|
+
const deferGroupReadyPostprocess = Boolean(p._defer_group_ready_postprocess);
|
|
203
|
+
delete p._defer_group_ready_postprocess;
|
|
201
204
|
if (method === 'message.send') {
|
|
202
205
|
const encrypt = p.encrypt !== undefined ? p.encrypt : true;
|
|
203
206
|
delete p.encrypt;
|
|
@@ -249,15 +252,11 @@ export class RpcPipeline {
|
|
|
249
252
|
}
|
|
250
253
|
const pullGateKey = this.pullGateKeyForCall(method, p);
|
|
251
254
|
if (pullGateKey) {
|
|
252
|
-
|
|
253
|
-
client._clientLog.debug(`pull skipped while processing pull response: method=${method} key=${pullGateKey}`);
|
|
254
|
-
return this.emptyPullResultForCall(method);
|
|
255
|
-
}
|
|
256
|
-
return await this.runPullSerialized(pullGateKey, async () => await this.callImplInner(method, p, skipSendResultEnvelope), this.pullGateBackgroundForCall(method, p));
|
|
255
|
+
return await this.runPullSerialized(pullGateKey, async () => await this.callImplInner(method, p, skipSendResultEnvelope, deferGroupReadyPostprocess), rpcBackground && this.pullGateBackgroundForCall(method, p));
|
|
257
256
|
}
|
|
258
|
-
return await runWithRpcPriority(() => this.callImplInner(method, p, skipSendResultEnvelope));
|
|
257
|
+
return await runWithRpcPriority(() => this.callImplInner(method, p, skipSendResultEnvelope, deferGroupReadyPostprocess));
|
|
259
258
|
}
|
|
260
|
-
async callImplInner(method, p, skipSendResultEnvelope = false) {
|
|
259
|
+
async callImplInner(method, p, skipSendResultEnvelope = false, deferGroupReadyPostprocess = false) {
|
|
261
260
|
const client = this.runtime.client;
|
|
262
261
|
if (method === 'message.v2.pull' || (method === 'message.pull' && hasMatchingV2Session(client))) {
|
|
263
262
|
try {
|
|
@@ -265,7 +264,10 @@ export class RpcPipeline {
|
|
|
265
264
|
client._clientLog.debug(`call route: ${method} -> V2 pull`);
|
|
266
265
|
if (p.window_mode === 'tail')
|
|
267
266
|
return await client._v2E2EE.pullV2TailInternal(p);
|
|
268
|
-
const messages = await client._pullV2(Number(p.after_seq ?? 0) || 0, Number(p.limit ?? 50) || 50, {
|
|
267
|
+
const messages = await client._pullV2(Number(p.after_seq ?? 0) || 0, Number(p.limit ?? 50) || 50, {
|
|
268
|
+
gateLocked: true,
|
|
269
|
+
force: p.force === true,
|
|
270
|
+
});
|
|
269
271
|
return { messages };
|
|
270
272
|
}
|
|
271
273
|
catch (exc) {
|
|
@@ -290,9 +292,11 @@ export class RpcPipeline {
|
|
|
290
292
|
if (p.window_mode === 'tail')
|
|
291
293
|
return await client._v2E2EE.pullGroupV2TailInternal(p);
|
|
292
294
|
const hasExplicitAfterSeq = 'after_seq' in p || 'after_message_seq' in p;
|
|
293
|
-
const cursorParams =
|
|
294
|
-
const ownsCursor = Object.keys(cursorParams).length === 0 ||
|
|
295
|
-
const pullOpts = {
|
|
295
|
+
const cursorParams = this.explicitGroupCursorParams(p);
|
|
296
|
+
const ownsCursor = Object.keys(cursorParams).length === 0 || this.groupCursorTargetsCurrentInstance(cursorParams);
|
|
297
|
+
const pullOpts = {
|
|
298
|
+
gateLocked: true,
|
|
299
|
+
};
|
|
296
300
|
if (hasExplicitAfterSeq)
|
|
297
301
|
pullOpts.explicitAfterSeq = true;
|
|
298
302
|
if (p.force === true)
|
|
@@ -317,8 +321,8 @@ export class RpcPipeline {
|
|
|
317
321
|
if ((method === 'group.v2.ack' || method === 'group.ack_messages') && p.group_id && (method === 'group.v2.ack' || hasMatchingV2Session(client))) {
|
|
318
322
|
await client._ensureV2SessionReady('group.ack_messages');
|
|
319
323
|
client._clientLog.debug(`call route: ${method} -> V2 ack`);
|
|
320
|
-
const cursorParams =
|
|
321
|
-
const ownsCursor = Object.keys(cursorParams).length === 0 ||
|
|
324
|
+
const cursorParams = this.explicitGroupCursorParams(p);
|
|
325
|
+
const ownsCursor = Object.keys(cursorParams).length === 0 || this.groupCursorTargetsCurrentInstance(cursorParams);
|
|
322
326
|
if (!ownsCursor) {
|
|
323
327
|
return await client._rawGroupAckMessages(p);
|
|
324
328
|
}
|
|
@@ -327,11 +331,15 @@ export class RpcPipeline {
|
|
|
327
331
|
await this.applyClientSignature(method, p);
|
|
328
332
|
const callTimeout = NON_IDEMPOTENT_METHODS.has(method) ? NON_IDEMPOTENT_TIMEOUT : undefined;
|
|
329
333
|
const rpcBackground = client._backgroundRpcDepth > 0;
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
334
|
+
const invokeTransport = () => {
|
|
335
|
+
const request = callTimeout
|
|
336
|
+
? client._transport.call(method, p, callTimeout, undefined, rpcBackground)
|
|
337
|
+
: client._transport.call(method, p, undefined, undefined, rpcBackground);
|
|
338
|
+
return this.bindActivePullCancellation(method, p, request);
|
|
339
|
+
};
|
|
340
|
+
let result = await this.transportCallWithIdentityRecovery(invokeTransport, method, p);
|
|
333
341
|
const sendResultModeSource = result;
|
|
334
|
-
result = await this.postprocessResult(method, p, result);
|
|
342
|
+
result = await this.postprocessResult(method, p, result, { skipGroupState: deferGroupReadyPostprocess });
|
|
335
343
|
if (!skipSendResultEnvelope) {
|
|
336
344
|
result = client._delivery.attachSendResultEnvelope(method, p, result, Boolean(p.encrypted), sendResultModeSource);
|
|
337
345
|
}
|
|
@@ -561,23 +569,58 @@ export class RpcPipeline {
|
|
|
561
569
|
return true;
|
|
562
570
|
}
|
|
563
571
|
pullGateName(key) {
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
if (key.startsWith('group:'))
|
|
567
|
-
return 'group_message';
|
|
568
|
-
if (key.startsWith('group_event:'))
|
|
569
|
-
return 'group_event';
|
|
570
|
-
return `other:${key}`;
|
|
572
|
+
void key;
|
|
573
|
+
return 'pull';
|
|
571
574
|
}
|
|
572
575
|
pullGateState(key) {
|
|
573
576
|
const name = this.pullGateName(key);
|
|
574
577
|
let state = this.pullGateStates.get(name);
|
|
575
578
|
if (!state) {
|
|
576
|
-
state = { active: null, foreground: [], background: [], byKey: new Map() };
|
|
579
|
+
state = { active: null, foreground: [], background: [], byKey: new Map(), lifecycle: new Map(), watchdog: null };
|
|
577
580
|
this.pullGateStates.set(name, state);
|
|
581
|
+
this.startPullGateWatchdog(state, name);
|
|
578
582
|
}
|
|
579
583
|
return state;
|
|
580
584
|
}
|
|
585
|
+
pullGateOperationTimeoutMs() {
|
|
586
|
+
const client = this.runtime.client;
|
|
587
|
+
const configured = Number(client._PULL_GATE_OPERATION_TIMEOUT_MS
|
|
588
|
+
?? client._PULL_GATE_TIMEOUT_MS
|
|
589
|
+
?? client._PULL_GATE_QUEUE_TIMEOUT_MS);
|
|
590
|
+
return Number.isFinite(configured) && configured > 0 ? configured : PULL_GATE_OPERATION_TIMEOUT_MS;
|
|
591
|
+
}
|
|
592
|
+
startPullGateWatchdog(state, name) {
|
|
593
|
+
if (state.watchdog)
|
|
594
|
+
return;
|
|
595
|
+
const interval = Math.min(50, Math.max(1, Math.floor(this.pullGateOperationTimeoutMs() / 4)));
|
|
596
|
+
const timer = setInterval(() => this.checkPullGate(state, name), interval);
|
|
597
|
+
state.watchdog = timer;
|
|
598
|
+
timer.unref?.();
|
|
599
|
+
}
|
|
600
|
+
checkPullGate(state, name) {
|
|
601
|
+
const active = state.active;
|
|
602
|
+
if (!active) {
|
|
603
|
+
this.drainPullGate(state);
|
|
604
|
+
return;
|
|
605
|
+
}
|
|
606
|
+
if (!active.pullingStartedAt || Date.now() - active.pullingStartedAt < this.pullGateOperationTimeoutMs())
|
|
607
|
+
return;
|
|
608
|
+
if (active.timedOut)
|
|
609
|
+
return;
|
|
610
|
+
active.timedOut = true;
|
|
611
|
+
active.cancel?.();
|
|
612
|
+
const err = new TimeoutError(`pull gate timeout: ${active.namespace}`, { retryable: true });
|
|
613
|
+
this.runtime.client._clientLog?.warn(`pull gate watchdog timeout: gate=${name} key=${active.key}`);
|
|
614
|
+
state.lifecycle.set(active.namespace, 'idle');
|
|
615
|
+
if (state.active === active)
|
|
616
|
+
state.active = null;
|
|
617
|
+
if (state.byKey.get(active.key) === active)
|
|
618
|
+
state.byKey.delete(active.key);
|
|
619
|
+
active.resolve = () => { };
|
|
620
|
+
active.reject(err);
|
|
621
|
+
this.drainPullGate(state);
|
|
622
|
+
this.runtime.client._delivery?.onPullGateIdle?.(active.namespace);
|
|
623
|
+
}
|
|
581
624
|
realtimePullNamespace(key) {
|
|
582
625
|
for (const marker of ['|tail|', '|forward|']) {
|
|
583
626
|
const index = key.indexOf(marker);
|
|
@@ -590,6 +633,22 @@ export class RpcPipeline {
|
|
|
590
633
|
const index = key.indexOf('|');
|
|
591
634
|
return index < 0 ? key : key.slice(0, index);
|
|
592
635
|
}
|
|
636
|
+
bindActivePullCancellation(method, params, request) {
|
|
637
|
+
const cancel = request.cancel;
|
|
638
|
+
const key = this.pullGateKeyForCall(method, params);
|
|
639
|
+
if (typeof cancel !== 'function' || !key)
|
|
640
|
+
return request;
|
|
641
|
+
const state = this.pullGateStates.get(this.pullGateName(key));
|
|
642
|
+
const active = state?.active;
|
|
643
|
+
if (!active || active.namespace !== this.pullScopeKey(key) || active.timedOut)
|
|
644
|
+
return request;
|
|
645
|
+
const boundCancel = () => cancel.call(request);
|
|
646
|
+
active.cancel = boundCancel;
|
|
647
|
+
return request.finally(() => {
|
|
648
|
+
if (active.cancel === boundCancel)
|
|
649
|
+
active.cancel = null;
|
|
650
|
+
});
|
|
651
|
+
}
|
|
593
652
|
isPullResponseProcessing(key) {
|
|
594
653
|
const responseKeys = this.runtime.client._pullResponseKeys;
|
|
595
654
|
return Boolean(key) && (responseKeys?.get(this.pullScopeKey(key)) ?? 0) > 0;
|
|
@@ -599,33 +658,12 @@ export class RpcPipeline {
|
|
|
599
658
|
return { events: [], count: 0 };
|
|
600
659
|
return { messages: [], count: 0 };
|
|
601
660
|
}
|
|
602
|
-
trackBypassedRealtimePull(key, delta) {
|
|
603
|
-
const ns = this.realtimePullNamespace(key);
|
|
604
|
-
if (!ns)
|
|
605
|
-
return;
|
|
606
|
-
const next = Math.max(0, (this.bypassedRealtimePulls.get(ns) ?? 0) + delta);
|
|
607
|
-
if (next === 0)
|
|
608
|
-
this.bypassedRealtimePulls.delete(ns);
|
|
609
|
-
else
|
|
610
|
-
this.bypassedRealtimePulls.set(ns, next);
|
|
611
|
-
}
|
|
612
|
-
async executeBypassedPull(key, operation, background) {
|
|
613
|
-
this.trackBypassedRealtimePull(key, 1);
|
|
614
|
-
try {
|
|
615
|
-
return await this.executePullOperation(operation, background);
|
|
616
|
-
}
|
|
617
|
-
finally {
|
|
618
|
-
this.trackBypassedRealtimePull(key, -1);
|
|
619
|
-
}
|
|
620
|
-
}
|
|
621
661
|
hasPendingRealtimePull(ns) {
|
|
622
662
|
const normalized = String(ns ?? '').trim();
|
|
623
663
|
if (!normalized || (!normalized.startsWith('p2p:') && !normalized.startsWith('group:')))
|
|
624
664
|
return false;
|
|
625
665
|
if (this.isPullResponseProcessing(normalized))
|
|
626
666
|
return true;
|
|
627
|
-
if ((this.bypassedRealtimePulls.get(normalized) ?? 0) > 0)
|
|
628
|
-
return true;
|
|
629
667
|
const state = this.pullGateStates.get(this.pullGateName(normalized));
|
|
630
668
|
if (!state)
|
|
631
669
|
return false;
|
|
@@ -638,6 +676,36 @@ export class RpcPipeline {
|
|
|
638
676
|
|| state.foreground.some(matches)
|
|
639
677
|
|| state.background.some(matches);
|
|
640
678
|
}
|
|
679
|
+
hasPullActivity(namespace, includePending = true) {
|
|
680
|
+
const ns = String(namespace ?? '').trim();
|
|
681
|
+
if ((!ns.startsWith('p2p:') && !ns.startsWith('group:'))
|
|
682
|
+
|| this.inlineRealtimeScopes.has(ns))
|
|
683
|
+
return true;
|
|
684
|
+
const client = this.runtime.client;
|
|
685
|
+
if ((client._pullResponseKeys?.get?.(ns) ?? 0) > 0)
|
|
686
|
+
return true;
|
|
687
|
+
const pendingP2p = client._pendingP2pPullUpper;
|
|
688
|
+
const pendingGroup = client._pendingGroupPullUpper;
|
|
689
|
+
if (includePending && ((pendingP2p?.get(ns) ?? 0) > 0 || (pendingGroup?.get(ns) ?? 0) > 0
|
|
690
|
+
|| client._delivery?.hasPendingPull?.(ns)))
|
|
691
|
+
return true;
|
|
692
|
+
for (const marker of client._gapFillDone ?? []) {
|
|
693
|
+
const value = String(marker ?? '').trim();
|
|
694
|
+
if (ns.startsWith('p2p:') && (value.startsWith('p2p_pull:') || value.startsWith('p2p_pull_retry:')))
|
|
695
|
+
return true;
|
|
696
|
+
if (ns.startsWith('group:') && value.startsWith(`group_pull:${ns}`))
|
|
697
|
+
return true;
|
|
698
|
+
}
|
|
699
|
+
for (const [key, gate] of client._pullGates?.entries?.() ?? []) {
|
|
700
|
+
if (gate?.inflight && this.pullScopeKey(key) === ns)
|
|
701
|
+
return true;
|
|
702
|
+
}
|
|
703
|
+
const state = this.pullGateStates.get(this.pullGateName(ns));
|
|
704
|
+
if (!state)
|
|
705
|
+
return false;
|
|
706
|
+
const matches = (job) => job !== null && job.namespace === ns;
|
|
707
|
+
return matches(state.active) || state.foreground.some(matches) || state.background.some(matches);
|
|
708
|
+
}
|
|
641
709
|
async tryRunInlineRealtime(ns, seq, operation) {
|
|
642
710
|
const normalized = String(ns ?? '').trim();
|
|
643
711
|
if ((!normalized.startsWith('p2p:') && !normalized.startsWith('group:'))
|
|
@@ -645,8 +713,10 @@ export class RpcPipeline {
|
|
|
645
713
|
return { acquired: false };
|
|
646
714
|
}
|
|
647
715
|
const client = this.runtime.client;
|
|
648
|
-
if (this.
|
|
649
|
-
|
|
716
|
+
if (this.hasPullActivity(normalized)) {
|
|
717
|
+
return { acquired: false };
|
|
718
|
+
}
|
|
719
|
+
if (this.isPullResponseProcessing(normalized)) {
|
|
650
720
|
return { acquired: false };
|
|
651
721
|
}
|
|
652
722
|
for (const [key, gate] of client._pullGates?.entries?.() ?? []) {
|
|
@@ -660,28 +730,40 @@ export class RpcPipeline {
|
|
|
660
730
|
}
|
|
661
731
|
const guard = {
|
|
662
732
|
key,
|
|
733
|
+
namespace: normalized,
|
|
663
734
|
background: false,
|
|
735
|
+
running: true,
|
|
736
|
+
suspended: false,
|
|
664
737
|
inline: true,
|
|
665
738
|
operation,
|
|
666
739
|
promise: Promise.resolve(undefined),
|
|
667
740
|
resolve: () => { },
|
|
668
741
|
reject: () => { },
|
|
669
742
|
timer: null,
|
|
743
|
+
resume: null,
|
|
744
|
+
resumeResolve: null,
|
|
745
|
+
pullingStartedAt: 0,
|
|
746
|
+
timedOut: false,
|
|
747
|
+
cancel: null,
|
|
670
748
|
};
|
|
671
|
-
|
|
749
|
+
this.inlineRealtimeScopes.add(normalized);
|
|
672
750
|
try {
|
|
673
751
|
return { acquired: true, value: await operation() };
|
|
674
752
|
}
|
|
675
753
|
finally {
|
|
754
|
+
this.inlineRealtimeScopes.delete(normalized);
|
|
676
755
|
if (state.active === guard)
|
|
677
756
|
state.active = null;
|
|
678
757
|
this.drainPullGate(state);
|
|
758
|
+
this.runtime.client._delivery?.onPullGateIdle?.(normalized);
|
|
679
759
|
this.armQueuedPullTimers(state);
|
|
680
760
|
}
|
|
681
761
|
}
|
|
682
762
|
tryAcquirePullGate(key) {
|
|
683
763
|
if (!key)
|
|
684
764
|
return 0;
|
|
765
|
+
if (this.inlineRealtimeScopes.has(this.pullScopeKey(key)))
|
|
766
|
+
return null;
|
|
685
767
|
const serialized = this.pullGateStates.get(this.pullGateName(key));
|
|
686
768
|
if (serialized?.active?.inline === true)
|
|
687
769
|
return null;
|
|
@@ -710,30 +792,41 @@ export class RpcPipeline {
|
|
|
710
792
|
gate.startedAt = 0;
|
|
711
793
|
}
|
|
712
794
|
async runPullSerialized(key, operation, background = false) {
|
|
713
|
-
if (key && this.isPullResponseProcessing(key)) {
|
|
714
|
-
this.runtime.client._clientLog.debug(`pull skipped while processing pull response: key=${key}`);
|
|
715
|
-
return [];
|
|
716
|
-
}
|
|
717
795
|
if (!key)
|
|
718
796
|
return await this.executePullOperation(operation, background);
|
|
719
797
|
const state = this.pullGateState(key);
|
|
720
|
-
const
|
|
721
|
-
const
|
|
798
|
+
const namespace = this.pullScopeKey(key);
|
|
799
|
+
const active = state.active?.namespace === namespace ? state.active : null;
|
|
800
|
+
const queuedForeground = state.foreground.find((job) => job.namespace === namespace);
|
|
801
|
+
const queuedBackground = state.background.find((job) => job.namespace === namespace);
|
|
802
|
+
const existing = background
|
|
803
|
+
? (active ?? queuedForeground ?? queuedBackground)
|
|
804
|
+
: (queuedForeground ?? (active && !active.background ? active : null) ?? queuedBackground);
|
|
722
805
|
if (existing) {
|
|
723
|
-
if (state.active
|
|
724
|
-
|
|
725
|
-
|
|
806
|
+
if (!background && existing.background && existing !== state.active) {
|
|
807
|
+
const index = state.background.indexOf(existing);
|
|
808
|
+
if (index >= 0)
|
|
809
|
+
state.background.splice(index, 1);
|
|
810
|
+
existing.background = false;
|
|
811
|
+
state.foreground.push(existing);
|
|
812
|
+
}
|
|
813
|
+
return await existing.promise;
|
|
726
814
|
}
|
|
727
815
|
let resolve;
|
|
728
816
|
let reject;
|
|
729
817
|
const promise = new Promise((res, rej) => { resolve = res; reject = rej; });
|
|
730
|
-
|
|
818
|
+
void promise.catch(() => { });
|
|
819
|
+
const job = {
|
|
820
|
+
key, namespace, background, running: false, suspended: false, operation, promise, resolve, reject, timer: null,
|
|
821
|
+
resume: null, resumeResolve: null,
|
|
822
|
+
pullingStartedAt: 0,
|
|
823
|
+
timedOut: false,
|
|
824
|
+
cancel: null,
|
|
825
|
+
};
|
|
731
826
|
(background ? state.background : state.foreground).push(job);
|
|
732
827
|
state.byKey.set(key, job);
|
|
733
828
|
this.drainPullGate(state);
|
|
734
|
-
|
|
735
|
-
job.timer = setTimeout(() => this.bypassQueuedPull(state, job), timeoutMs);
|
|
736
|
-
}
|
|
829
|
+
this.checkPullGate(state, this.pullGateName(key));
|
|
737
830
|
return await promise;
|
|
738
831
|
}
|
|
739
832
|
async executePullOperation(operation, background) {
|
|
@@ -741,65 +834,86 @@ export class RpcPipeline {
|
|
|
741
834
|
return await this.runtime.client._withBackgroundRpc(operation);
|
|
742
835
|
return await operation();
|
|
743
836
|
}
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
let settled = false;
|
|
747
|
-
const timer = setTimeout(() => {
|
|
748
|
-
if (settled)
|
|
749
|
-
return;
|
|
750
|
-
settled = true;
|
|
751
|
-
this.runtime.client._clientLog.warn(`pull gate coalesced wait timeout, bypass: gate=${this.pullGateName(key)} key=${key}`);
|
|
752
|
-
void this.executeBypassedPull(key, operation, background).then(resolve, reject);
|
|
753
|
-
}, timeoutMs);
|
|
754
|
-
shared.then((value) => { if (!settled) {
|
|
755
|
-
settled = true;
|
|
756
|
-
clearTimeout(timer);
|
|
757
|
-
resolve(value);
|
|
758
|
-
} }, (error) => { if (!settled) {
|
|
759
|
-
settled = true;
|
|
760
|
-
clearTimeout(timer);
|
|
761
|
-
reject(error);
|
|
762
|
-
} });
|
|
763
|
-
});
|
|
764
|
-
}
|
|
765
|
-
bypassQueuedPull(state, job) {
|
|
766
|
-
if (state.active === job)
|
|
837
|
+
async yieldPullGate(key, nextKey, background) {
|
|
838
|
+
if (!key)
|
|
767
839
|
return;
|
|
768
|
-
const
|
|
769
|
-
const
|
|
770
|
-
|
|
840
|
+
const state = this.pullGateState(key);
|
|
841
|
+
const namespace = this.pullScopeKey(key);
|
|
842
|
+
const job = state.active?.namespace === namespace
|
|
843
|
+
? state.active
|
|
844
|
+
: [...state.foreground, ...state.background].find((candidate) => candidate.namespace === namespace);
|
|
845
|
+
if (!job || state.active !== job)
|
|
771
846
|
return;
|
|
772
|
-
|
|
847
|
+
const replacementKey = String(nextKey || key);
|
|
773
848
|
if (state.byKey.get(job.key) === job)
|
|
774
849
|
state.byKey.delete(job.key);
|
|
775
|
-
job.
|
|
776
|
-
|
|
777
|
-
|
|
850
|
+
job.key = replacementKey;
|
|
851
|
+
job.namespace = this.pullScopeKey(replacementKey);
|
|
852
|
+
if (background !== undefined)
|
|
853
|
+
job.background = background;
|
|
854
|
+
state.byKey.set(job.key, job);
|
|
855
|
+
state.lifecycle.set(namespace, 'pulled');
|
|
856
|
+
job.suspended = true;
|
|
857
|
+
job.resume = new Promise((resolve) => { job.resumeResolve = resolve; });
|
|
858
|
+
state.active = null;
|
|
859
|
+
(job.background ? state.background : state.foreground).push(job);
|
|
860
|
+
this.drainPullGate(state);
|
|
861
|
+
if (job.resume)
|
|
862
|
+
await job.resume;
|
|
863
|
+
}
|
|
864
|
+
getPullLifecycle(namespace) {
|
|
865
|
+
const ns = this.pullScopeKey(namespace);
|
|
866
|
+
if (!ns)
|
|
867
|
+
return 'idle';
|
|
868
|
+
return this.pullGateStates.get(this.pullGateName(ns))?.lifecycle.get(ns) ?? 'idle';
|
|
869
|
+
}
|
|
870
|
+
setPullLifecycle(namespace, lifecycle) {
|
|
871
|
+
const ns = this.pullScopeKey(namespace);
|
|
872
|
+
if (!ns)
|
|
873
|
+
return;
|
|
874
|
+
const state = this.pullGateStates.get(this.pullGateName(ns));
|
|
875
|
+
if (!state || state.active?.namespace !== ns)
|
|
876
|
+
return;
|
|
877
|
+
state.lifecycle.set(ns, lifecycle);
|
|
778
878
|
}
|
|
779
879
|
drainPullGate(state) {
|
|
780
880
|
if (state.active)
|
|
781
881
|
return;
|
|
782
|
-
const
|
|
882
|
+
const takeRunnable = (queue) => {
|
|
883
|
+
const index = queue.findIndex((candidate) => !this.inlineRealtimeScopes.has(candidate.namespace));
|
|
884
|
+
return index >= 0 ? queue.splice(index, 1)[0] : undefined;
|
|
885
|
+
};
|
|
886
|
+
const job = takeRunnable(state.foreground) ?? takeRunnable(state.background);
|
|
783
887
|
if (!job)
|
|
784
888
|
return;
|
|
785
889
|
state.active = job;
|
|
786
890
|
if (job.timer)
|
|
787
891
|
clearTimeout(job.timer);
|
|
892
|
+
job.pullingStartedAt = Date.now();
|
|
893
|
+
job.timedOut = false;
|
|
894
|
+
state.lifecycle.set(job.namespace, 'pulling');
|
|
895
|
+
const resumeResolve = job.resumeResolve;
|
|
896
|
+
job.resume = null;
|
|
897
|
+
job.resumeResolve = null;
|
|
898
|
+
if (job.suspended) {
|
|
899
|
+
job.suspended = false;
|
|
900
|
+
resumeResolve?.();
|
|
901
|
+
return;
|
|
902
|
+
}
|
|
903
|
+
job.running = true;
|
|
788
904
|
void this.executePullOperation(job.operation, job.background).then(job.resolve, job.reject).finally(() => {
|
|
905
|
+
if (job.timedOut)
|
|
906
|
+
return;
|
|
907
|
+
state.lifecycle.set(job.namespace, 'idle');
|
|
789
908
|
if (state.active === job)
|
|
790
909
|
state.active = null;
|
|
791
910
|
if (state.byKey.get(job.key) === job)
|
|
792
911
|
state.byKey.delete(job.key);
|
|
793
912
|
this.drainPullGate(state);
|
|
913
|
+
this.runtime.client._delivery?.onPullGateIdle?.(job.namespace);
|
|
794
914
|
});
|
|
795
915
|
}
|
|
796
|
-
armQueuedPullTimers(
|
|
797
|
-
const timeoutMs = Math.max(0, Number(this.runtime.client._PULL_GATE_QUEUE_TIMEOUT_MS ?? 3_000) || 0);
|
|
798
|
-
for (const job of [...state.foreground, ...state.background]) {
|
|
799
|
-
if (!job.timer)
|
|
800
|
-
job.timer = setTimeout(() => this.bypassQueuedPull(state, job), timeoutMs);
|
|
801
|
-
}
|
|
802
|
-
}
|
|
916
|
+
armQueuedPullTimers(_state) { }
|
|
803
917
|
async rawCall(method, params, options) {
|
|
804
918
|
const client = this.runtime.client;
|
|
805
919
|
const payload = { ...(params ?? {}) };
|
|
@@ -810,18 +924,32 @@ export class RpcPipeline {
|
|
|
810
924
|
await this.applyClientSignature(method, payload);
|
|
811
925
|
}
|
|
812
926
|
const timeout = options?.timeout;
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
927
|
+
const invokeTransport = () => {
|
|
928
|
+
let request;
|
|
929
|
+
if (rpcBackground)
|
|
930
|
+
request = client._transport.call(method, payload, timeout, options?.trace, true);
|
|
931
|
+
else if (options?.trace !== undefined)
|
|
932
|
+
request = client._transport.call(method, payload, timeout, options.trace);
|
|
933
|
+
else if (timeout !== undefined)
|
|
934
|
+
request = client._transport.call(method, payload, timeout);
|
|
935
|
+
else
|
|
936
|
+
request = client._transport.call(method, payload);
|
|
937
|
+
return this.bindActivePullCancellation(method, payload, request);
|
|
938
|
+
};
|
|
939
|
+
return await this.transportCallWithIdentityRecovery(invokeTransport, method, payload);
|
|
940
|
+
}
|
|
941
|
+
async transportCallWithIdentityRecovery(operation, method, params) {
|
|
942
|
+
try {
|
|
943
|
+
return await operation();
|
|
818
944
|
}
|
|
819
|
-
|
|
820
|
-
|
|
945
|
+
catch (err) {
|
|
946
|
+
const recover = this.runtime.client._recoverIdentityAdmission;
|
|
947
|
+
if (typeof recover !== 'function' || !await recover.call(this.runtime.client, err, method, params))
|
|
948
|
+
throw err;
|
|
949
|
+
return await operation();
|
|
821
950
|
}
|
|
822
|
-
return await client._transport.call(method, payload);
|
|
823
951
|
}
|
|
824
|
-
async postprocessResult(method, params, result) {
|
|
952
|
+
async postprocessResult(method, params, result, options = {}) {
|
|
825
953
|
const client = this.runtime.client;
|
|
826
954
|
let next = result;
|
|
827
955
|
if ((method === 'group.send' || method === 'group.v2.send') && isJsonObject(next)) {
|
|
@@ -839,12 +967,40 @@ export class RpcPipeline {
|
|
|
839
967
|
next = await client._decryptMessageThoughts(next);
|
|
840
968
|
}
|
|
841
969
|
if (method === 'message.pull' && isJsonObject(next)) {
|
|
842
|
-
|
|
970
|
+
const isTail = String(next.window_mode ?? params.window_mode ?? '').trim().toLowerCase() === 'tail';
|
|
971
|
+
if (!isTail && client._aid && client._seqTracker) {
|
|
972
|
+
const ns = `p2p:${client._aid}`;
|
|
973
|
+
const afterSeq = Math.max(0, Number(params.after_seq ?? 0) || 0);
|
|
974
|
+
await client._delivery.commitPlainForwardPage(ns, afterSeq, 'message.pull', next);
|
|
975
|
+
}
|
|
976
|
+
else {
|
|
977
|
+
this.postprocessMessagePull(params, next);
|
|
978
|
+
}
|
|
843
979
|
}
|
|
844
980
|
if (method === 'group.pull' && isJsonObject(next)) {
|
|
845
|
-
|
|
981
|
+
if (Array.isArray(next.messages)) {
|
|
982
|
+
next.messages = next.messages.map((message) => normalizeGroupMentionMode(message));
|
|
983
|
+
}
|
|
984
|
+
const gid = String(params.group_id ?? params.group_aid ?? '').trim();
|
|
985
|
+
const explicitCursorParams = this.explicitGroupCursorParams(params);
|
|
986
|
+
const ownsCursor = Object.keys(explicitCursorParams).length === 0
|
|
987
|
+
|| this.groupCursorTargetsCurrentInstance(explicitCursorParams);
|
|
988
|
+
const isTail = String(next.window_mode ?? params.window_mode ?? '').trim().toLowerCase() === 'tail';
|
|
989
|
+
if (!ownsCursor) {
|
|
990
|
+
client._clientLog.debug(`group.pull external cursor skips local Forward commit: group=${gid}`);
|
|
991
|
+
}
|
|
992
|
+
else if (!isTail && gid && client._seqTracker) {
|
|
993
|
+
const ns = `group:${gid}`;
|
|
994
|
+
const afterSeq = Math.max(0, Number(params.after_message_seq ?? params.after_seq ?? 0) || 0);
|
|
995
|
+
await client._delivery.commitPlainForwardPage(ns, afterSeq, 'group.pull', next, gid);
|
|
996
|
+
}
|
|
997
|
+
else {
|
|
998
|
+
this.postprocessGroupPull(params, next);
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
if (!options.skipGroupState) {
|
|
1002
|
+
next = await client._groupState.postprocessResult(method, params, next);
|
|
846
1003
|
}
|
|
847
|
-
next = await client._groupState.postprocessResult(method, params, next);
|
|
848
1004
|
return next;
|
|
849
1005
|
}
|
|
850
1006
|
_sleep(ms) {
|
|
@@ -960,6 +1116,19 @@ export class RpcPipeline {
|
|
|
960
1116
|
}
|
|
961
1117
|
return cursorParams;
|
|
962
1118
|
}
|
|
1119
|
+
explicitGroupCursorParams(params) {
|
|
1120
|
+
const value = params._group_cursor_params;
|
|
1121
|
+
if (!isJsonObject(value))
|
|
1122
|
+
return {};
|
|
1123
|
+
return { ...value };
|
|
1124
|
+
}
|
|
1125
|
+
groupCursorTargetsCurrentInstance(params) {
|
|
1126
|
+
const client = this.runtime.client;
|
|
1127
|
+
const deviceId = String(params.device_id ?? '').trim();
|
|
1128
|
+
const slotId = String(params.slot_id ?? '').trim();
|
|
1129
|
+
return (!deviceId || deviceId === (client._deviceId ?? ''))
|
|
1130
|
+
&& (!slotId || slotId === (client._slotId ?? ''));
|
|
1131
|
+
}
|
|
963
1132
|
validateMessageRecipient(toAid) {
|
|
964
1133
|
if (isGroupServiceAid(toAid)) {
|
|
965
1134
|
throw new ValidationError('message.send receiver cannot be group.{issuer}; use group.send instead');
|