@nelsonlongxiang/dsh-open-a2a-net 0.6.29 → 0.6.31
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/README.md +2 -0
- package/README_zh.md +2 -0
- package/assets/nexusDist/assets/index-Dz8N80tI.css +1 -0
- package/assets/nexusDist/assets/index-jm-cFAhB.js +9 -0
- package/assets/nexusDist/index.html +2 -2
- package/cli/a2a.mjs +624 -624
- package/lib/canvas-store.js +21 -1
- package/lib/client.js +64 -64
- package/lib/client.js.map +1 -1
- package/lib/faces/admin.js +12 -1
- package/lib/faces/canvas-notify.js +138 -0
- package/lib/faces/wire.js +669 -0
- package/lib/flood-guard.js +106 -0
- package/lib/index.js +69 -663
- package/lib/skill/SKILL.md +1 -0
- package/lib/skill/references/collaboration.md +376 -0
- package/lib/task-ledger.js +34 -4
- package/lib/types/canvas-store.d.ts +10 -1
- package/lib/types/faces/admin.d.ts +2 -1
- package/lib/types/faces/canvas-notify.d.ts +74 -0
- package/lib/types/faces/wire.d.ts +171 -0
- package/lib/types/flood-guard.d.ts +29 -0
- package/lib/types/runtime.d.ts +1 -0
- package/lib/types/task-ledger.d.ts +9 -1
- package/nexus-stage/src/canvas-ops.ts +16 -1
- package/nexus-stage/src/canvas-wire.ts +27 -19
- package/nexus-stage/src/main.ts +5 -1
- package/nexus-stage/src/planning-edges.ts +26 -0
- package/nexus-stage/src/planning-mirror.ts +31 -0
- package/nexus-stage/src/planning-overlap.ts +36 -11
- package/nexus-stage/src/planning-types.ts +1 -0
- package/nexus-stage/src/planning-undo.ts +68 -0
- package/nexus-stage/src/planning-view.ts +61 -37
- package/nexus-stage/src/planning.css +2 -0
- package/nexus-stage/src/world.ts +8 -20
- package/package.json +1 -1
- package/assets/nexusDist/assets/index-BqepxJ7E.css +0 -1
- package/assets/nexusDist/assets/index-CTWH6QRh.js +0 -9
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
export const WIRE_ERROR_RATE_LIMITED = -32008;
|
|
2
|
+
export const MAX_PARTS_PER_TRANSFER = 16;
|
|
3
|
+
// Matches the current L9 threshold; per-sender across ALL targets is stricter.
|
|
4
|
+
export const DEFAULT_RATE_PER_MINUTE = 16;
|
|
5
|
+
const WINDOW_MS = 60_000;
|
|
6
|
+
const TRANSFER_TTL_MS = 24 * 60 * 60_000;
|
|
7
|
+
const STATE_CAP = 1024;
|
|
8
|
+
function parsePart(message) {
|
|
9
|
+
const pos = message.indexOf('[JPFILE-PART');
|
|
10
|
+
if (pos === -1)
|
|
11
|
+
return null;
|
|
12
|
+
const match = /^\[JPFILE-PART v1\]\s*/.exec(message.slice(pos));
|
|
13
|
+
if (match === null)
|
|
14
|
+
return 'invalid';
|
|
15
|
+
try {
|
|
16
|
+
const p = JSON.parse(message.slice(pos + match[0].length));
|
|
17
|
+
if (p === null || typeof p !== 'object')
|
|
18
|
+
return 'invalid';
|
|
19
|
+
const e = p;
|
|
20
|
+
if (typeof e.transfer_id !== 'string' || e.transfer_id.length === 0 || e.transfer_id.length > 256 ||
|
|
21
|
+
typeof e.file_id !== 'string' || e.file_id.length === 0 || e.file_id.length > 256 ||
|
|
22
|
+
!Number.isSafeInteger(e.part_i) || !Number.isSafeInteger(e.part_n))
|
|
23
|
+
return 'invalid';
|
|
24
|
+
const i = e.part_i;
|
|
25
|
+
const n = e.part_n;
|
|
26
|
+
// Accept either zero- or one-based indexes; the UNIQUE accepted-parts
|
|
27
|
+
// count below is authoritative even when declared totals are dishonest.
|
|
28
|
+
if (n < 1 || i < 0 || i > n)
|
|
29
|
+
return 'invalid';
|
|
30
|
+
return { transferId: e.transfer_id, fileId: e.file_id, i, n };
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return 'invalid';
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
export class FloodGuard {
|
|
37
|
+
now;
|
|
38
|
+
senders = new Map();
|
|
39
|
+
transfers = new Map();
|
|
40
|
+
constructor(now = Date.now) {
|
|
41
|
+
this.now = now;
|
|
42
|
+
}
|
|
43
|
+
/** Check/admit one fresh task. No timer; maps and per-key sets are bounded.
|
|
44
|
+
* Active transfer entries NEVER evict to make space (which would reset a
|
|
45
|
+
* transfer's allowance); capacity exhaustion refuses visibly instead.
|
|
46
|
+
* Counts are boot-local: restart resets them, disclosed in the guide.
|
|
47
|
+
*/
|
|
48
|
+
check(caller, message) {
|
|
49
|
+
const now = this.now();
|
|
50
|
+
for (const [k, hits] of this.senders) {
|
|
51
|
+
const fresh = hits.filter(t => t > now - WINDOW_MS);
|
|
52
|
+
if (fresh.length === 0)
|
|
53
|
+
this.senders.delete(k);
|
|
54
|
+
else
|
|
55
|
+
this.senders.set(k, fresh);
|
|
56
|
+
}
|
|
57
|
+
for (const [k, value] of this.transfers)
|
|
58
|
+
if (now - value.since >= TRANSFER_TTL_MS)
|
|
59
|
+
this.transfers.delete(k);
|
|
60
|
+
const part = parsePart(message);
|
|
61
|
+
if (part === 'invalid')
|
|
62
|
+
return 'file-part-invalid';
|
|
63
|
+
const key = part === null ? '' : JSON.stringify([caller, part.transferId]);
|
|
64
|
+
const transfer = this.transfers.get(key);
|
|
65
|
+
const coordinate = part === null ? '' : JSON.stringify([part.fileId, part.i]);
|
|
66
|
+
if (part !== null) {
|
|
67
|
+
if (part.n > MAX_PARTS_PER_TRANSFER || (transfer?.parts.size ?? 0) >= MAX_PARTS_PER_TRANSFER)
|
|
68
|
+
return 'file-parts-exceeded';
|
|
69
|
+
if (transfer?.parts.has(coordinate))
|
|
70
|
+
return 'file-part-duplicate';
|
|
71
|
+
if (transfer === undefined && this.transfers.size >= STATE_CAP)
|
|
72
|
+
return 'flood-state-full';
|
|
73
|
+
}
|
|
74
|
+
const hits = this.senders.get(caller) ?? [];
|
|
75
|
+
if (hits.length >= DEFAULT_RATE_PER_MINUTE)
|
|
76
|
+
return 'sender-rate-limited';
|
|
77
|
+
if (hits.length === 0 && this.senders.size >= STATE_CAP)
|
|
78
|
+
return 'flood-state-full';
|
|
79
|
+
hits.push(now);
|
|
80
|
+
this.senders.set(caller, hits);
|
|
81
|
+
if (part !== null) {
|
|
82
|
+
const row = transfer ?? { since: now, parts: new Set() };
|
|
83
|
+
row.parts.add(coordinate);
|
|
84
|
+
this.transfers.set(key, row);
|
|
85
|
+
}
|
|
86
|
+
return undefined;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
/** Host-scoped adapter; rejected fresh keys are FAILED, not stranded pending.
|
|
90
|
+
* HTTP 200 retains the existing client's structured peer-error path.
|
|
91
|
+
*/
|
|
92
|
+
export function createFloodGuard(outcomes, recordActivity) {
|
|
93
|
+
const guard = new FloodGuard();
|
|
94
|
+
return (caller, message, team, taskId, res) => {
|
|
95
|
+
const reason = guard.check(caller, message);
|
|
96
|
+
if (reason === undefined)
|
|
97
|
+
return false;
|
|
98
|
+
const error = 'flood-guard: ' + reason + ' (gh#91): file data must use an independent channel; no delivery occurred';
|
|
99
|
+
outcomes.recordOutcome(taskId, { status: 'failed', error });
|
|
100
|
+
recordActivity('in', team, caller, false);
|
|
101
|
+
const payload = JSON.stringify({ error, reason, code: WIRE_ERROR_RATE_LIMITED, task_id: taskId, team, delivered: false, task_status: 'TASK_STATE_FAILED' });
|
|
102
|
+
res.writeHead(200, { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(payload) });
|
|
103
|
+
res.end(payload);
|
|
104
|
+
return true;
|
|
105
|
+
};
|
|
106
|
+
}
|