@nelsonlongxiang/dsh-open-a2a-net 0.6.24 → 0.6.25
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/lib/exposure-store.js +82 -0
- package/lib/index.js +273 -37
- package/lib/types/exposure-store.d.ts +45 -0
- package/lib/types/index.d.ts +5 -0
- package/package.json +218 -218
- package/lib/types/client.d.ts +0 -73
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* #19 exposure tiers (ruling final, D3/D4): per-session visibility tier on
|
|
3
|
+
* the A2A network — 'linked' (the default floor: visible to linked callers
|
|
4
|
+
* per the W3 gate) or 'fleet' (the member's voluntary opt-UP: visible to
|
|
5
|
+
* any fleet peer). v1 carries exactly these two tiers (D5: no private
|
|
6
|
+
* residue below linked).
|
|
7
|
+
*
|
|
8
|
+
* D3: joins default to linked — existing members transfer automatically
|
|
9
|
+
* (tier absence IS linked, so no migration is needed); a member may raise
|
|
10
|
+
* itself to fleet and lower itself back (fleet is an opt-up, linked is the
|
|
11
|
+
* floor, never a ceiling decision taken from the member).
|
|
12
|
+
*
|
|
13
|
+
* D4 (成文, zero group knobs): a GROUP's visibility equals its STRICTEST
|
|
14
|
+
* member tier — one linked member keeps the group linked-level. The rule
|
|
15
|
+
* binds every future group surface (canvas listing, fleet feeds); v1 has
|
|
16
|
+
* no group-level tier API by design, so the rule needs no enforcement
|
|
17
|
+
* seam yet.
|
|
18
|
+
* @module @nelsonlongxiang/dsh-open-a2a-net/exposure-store
|
|
19
|
+
*/
|
|
20
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
21
|
+
import { dirname } from 'node:path';
|
|
22
|
+
export class ExposureStore {
|
|
23
|
+
path;
|
|
24
|
+
tiers = new Map();
|
|
25
|
+
/**
|
|
26
|
+
* @param path - persistence file (`<dsh-home>/a2a/exposure.json`); empty = no persistence.
|
|
27
|
+
*/
|
|
28
|
+
constructor(path) {
|
|
29
|
+
this.path = path;
|
|
30
|
+
if (this.path === '')
|
|
31
|
+
return;
|
|
32
|
+
if (!existsSync(this.path))
|
|
33
|
+
return;
|
|
34
|
+
try {
|
|
35
|
+
const snapshot = JSON.parse(readFileSync(this.path, 'utf8'));
|
|
36
|
+
const tiers = snapshot?.tiers;
|
|
37
|
+
if (tiers === undefined || typeof tiers !== 'object')
|
|
38
|
+
return;
|
|
39
|
+
for (const [id, tier] of Object.entries(tiers)) {
|
|
40
|
+
if (tier === 'fleet')
|
|
41
|
+
this.tiers.set(id, 'fleet'); // linked is the absence default
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
// A corrupt store is not fatal: everyone degrades to linked (the default floor).
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* The session's tier — 'linked' unless explicitly raised to 'fleet'
|
|
50
|
+
* (D3: existing members transfer as linked automatically).
|
|
51
|
+
*/
|
|
52
|
+
tierOf(id) {
|
|
53
|
+
return this.tiers.get(id) === 'fleet' ? 'fleet' : 'linked';
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Set the session's tier. Members may opt up to fleet and back down to
|
|
57
|
+
* linked; there is no tier below linked (D5) and no group-level knob
|
|
58
|
+
* (D4 — group visibility derives from the strictest member).
|
|
59
|
+
*/
|
|
60
|
+
setTier(id, tier) {
|
|
61
|
+
if (tier === 'fleet')
|
|
62
|
+
this.tiers.set(id, 'fleet');
|
|
63
|
+
else
|
|
64
|
+
this.tiers.delete(id);
|
|
65
|
+
this.persist();
|
|
66
|
+
}
|
|
67
|
+
persist() {
|
|
68
|
+
if (this.path === '')
|
|
69
|
+
return;
|
|
70
|
+
try {
|
|
71
|
+
mkdirSync(dirname(this.path), { recursive: true });
|
|
72
|
+
const tiers = {};
|
|
73
|
+
for (const [id, tier] of this.tiers)
|
|
74
|
+
tiers[id] = tier;
|
|
75
|
+
const snapshot = { tiers };
|
|
76
|
+
writeFileSync(this.path, JSON.stringify(snapshot), { mode: 0o600 });
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
// An unwritable home must not break tier changes; memory-only degradation.
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
package/lib/index.js
CHANGED
|
@@ -33,6 +33,7 @@ import { decodeSessionLog, extractTranscript, TRANSCRIPT_DEFAULT_LIMIT, TRANSCRI
|
|
|
33
33
|
import { LayoutStore } from "./layout-store.js";
|
|
34
34
|
import { fileURLToPath } from 'node:url';
|
|
35
35
|
import { JoinedSessions } from "./joined-store.js";
|
|
36
|
+
import { ExposureStore } from "./exposure-store.js";
|
|
36
37
|
import { TeamMembershipStore } from "./team-store.js";
|
|
37
38
|
import { PeerStore } from "./peer-store.js";
|
|
38
39
|
import { SelfReferralFilter } from "./self-suppress.js";
|
|
@@ -125,6 +126,15 @@ export const Config = s.object({
|
|
|
125
126
|
* the legacy whole-store basis (fleet migration window).
|
|
126
127
|
*/
|
|
127
128
|
referralTeamResolution: s.boolean().default(false),
|
|
129
|
+
/**
|
|
130
|
+
* #19 D2 (ruling final, owner-level knob): how much of a session team's
|
|
131
|
+
* activity EXCERPT the card face publishes in sessionTeams descriptions.
|
|
132
|
+
* 'full' (default) keeps the verbatim tail excerpt; 'masked' truncates
|
|
133
|
+
* to a shape-only prefix; 'off' omits the description field entirely.
|
|
134
|
+
* Titles and team handles stay in every tier — the knob is the excerpt,
|
|
135
|
+
* not the listing.
|
|
136
|
+
*/
|
|
137
|
+
exposureExcerpt: s.union(['full', 'masked', 'off']).default('full'),
|
|
128
138
|
/**
|
|
129
139
|
* gh#9+gh#10 OriginClaim window: absent envelopes still walk N3 during
|
|
130
140
|
* the window (counted as legacyInbound); closing it rejects them like
|
|
@@ -545,6 +555,11 @@ export function apply(ctx, config) {
|
|
|
545
555
|
// host restarts silently dropped every join. The store remounts the node
|
|
546
556
|
// whenever the Agent comes back, until the user leaves.
|
|
547
557
|
const joinedSessions = new JoinedSessions(join(home, 'a2a', 'joined.json'));
|
|
558
|
+
// #19 D3/D4: per-session exposure tiers — 'linked' floor (the absence
|
|
559
|
+
// default, so existing members transfer automatically) with a voluntary
|
|
560
|
+
// 'fleet' opt-up. D4 (成文): group visibility = strictest member tier;
|
|
561
|
+
// v1 ships no group-level knob by design.
|
|
562
|
+
const exposureStore = new ExposureStore(join(home, 'a2a', 'exposure.json'));
|
|
548
563
|
// S2: node-declared team memberships — the roster half of the
|
|
549
564
|
// team-roster iteration. Joins ride the allowlist-gated tools; the store
|
|
550
565
|
// is the durable declaration the card publishes and the state face reads.
|
|
@@ -1033,6 +1048,19 @@ export function apply(ctx, config) {
|
|
|
1033
1048
|
}
|
|
1034
1049
|
return 'no activity yet';
|
|
1035
1050
|
}
|
|
1051
|
+
/**
|
|
1052
|
+
* #19 D2: apply the owner-level excerpt knob to a session-team
|
|
1053
|
+
* description at the card face. 'full' keeps the verbatim excerpt;
|
|
1054
|
+
* 'masked' reduces to a shape-only prefix (16 chars — enough to
|
|
1055
|
+
* recognize, not enough to read); 'off' omits the field entirely.
|
|
1056
|
+
*/
|
|
1057
|
+
function exposureDescription(description) {
|
|
1058
|
+
if (config.exposureExcerpt === 'off')
|
|
1059
|
+
return undefined;
|
|
1060
|
+
if (config.exposureExcerpt === 'masked')
|
|
1061
|
+
return `${description.slice(0, 16)}${description.length > 16 ? '…' : ''}`;
|
|
1062
|
+
return description;
|
|
1063
|
+
}
|
|
1036
1064
|
/**
|
|
1037
1065
|
* Join one live top-level session: its team dispatches direct routes and
|
|
1038
1066
|
* rides the announced card from this moment.
|
|
@@ -1388,6 +1416,9 @@ export function apply(ctx, config) {
|
|
|
1388
1416
|
joined: sessionNodes.has(String(agent.id)),
|
|
1389
1417
|
live: true,
|
|
1390
1418
|
teams: [...teamMemberships.teamsOf(String(agent.id))],
|
|
1419
|
+
// #19 D3: the session's exposure tier (linked floor /
|
|
1420
|
+
// fleet opt-up) — readable on the state face.
|
|
1421
|
+
exposure: exposureStore.tierOf(String(agent.id)),
|
|
1391
1422
|
...(canvasStore.teamsOf(String(agent.id)).length > 0 ? { canvasTeams: [...canvasStore.teamsOf(String(agent.id))] } : {}),
|
|
1392
1423
|
...(groupOf(String(agent.id)) !== undefined ? { group: groupOf(String(agent.id)) } : {}),
|
|
1393
1424
|
}));
|
|
@@ -1904,7 +1935,17 @@ export function apply(ctx, config) {
|
|
|
1904
1935
|
void named;
|
|
1905
1936
|
};
|
|
1906
1937
|
const claim = await verifyOriginClaim(req);
|
|
1907
|
-
|
|
1938
|
+
// W3 (#18 half-side, 0.6.25): a VERIFIED envelope proves the
|
|
1939
|
+
// caller holds a real node key — it does not prove a relationship
|
|
1940
|
+
// with THIS host. The full card (sessionTeams/rosters =
|
|
1941
|
+
// collaboration facts) additionally requires a LINK (same-host
|
|
1942
|
+
// membership, canvas remote ref, or the caller's own card seeding
|
|
1943
|
+
// us — see callerLinkedToThisHost). An unlinked-but-verified
|
|
1944
|
+
// stranger gets the MINIMAL discovery card, #18 X1's shape.
|
|
1945
|
+
// Interaction with the OriginClaim narrowing: the caller is a
|
|
1946
|
+
// bare zone; linkage judges that zone face against our canvas
|
|
1947
|
+
// remote refs, joined handles, and the caller card's peers.
|
|
1948
|
+
const named = claim.ok && callerLinkedToThisHost(claim.caller, claim.ok ? claim.callerUrl : '', claim.ok ? claim.card : undefined);
|
|
1908
1949
|
// gh#7 G1 (review-seat R2 ruling): the card serves two faces.
|
|
1909
1950
|
// A NAMED caller — ?caller_session= or the X-A2A-Caller header in
|
|
1910
1951
|
// the zone or zone/id8 shape — gets the FULL card. An anonymous
|
|
@@ -1943,11 +1984,19 @@ export function apply(ctx, config) {
|
|
|
1943
1984
|
return false;
|
|
1944
1985
|
}
|
|
1945
1986
|
};
|
|
1987
|
+
// #19 D2: the excerpt knob rides the served-fresh rows at the
|
|
1988
|
+
// card face — live nodes (nodeMetadataOf description) and cold
|
|
1989
|
+
// rows alike. 'off' omits the field (undefined drops out).
|
|
1990
|
+
const exposureRow = (team, meta) => {
|
|
1991
|
+
const { description, ...rest } = meta;
|
|
1992
|
+
const excerpt = exposureDescription(description ?? '');
|
|
1993
|
+
return { team, ...rest, ...(excerpt !== undefined ? { description: excerpt } : {}) };
|
|
1994
|
+
};
|
|
1946
1995
|
const sessionTeams = [
|
|
1947
|
-
...[...sessionNodes.values()].filter(isJoinedNode).map(agent => (
|
|
1996
|
+
...[...sessionNodes.values()].filter(isJoinedNode).map(agent => exposureRow(sessionTeamOf(agent), nodeMetadataOf(agent))),
|
|
1948
1997
|
...joinedSessions.list()
|
|
1949
1998
|
.filter(id => !liveRoots.has(id) && isArchived?.(id) !== true)
|
|
1950
|
-
.map(id => (
|
|
1999
|
+
.map(id => exposureRow(`${config.team}/${id8(id)}`, { name: `${session}-${id8(id)}`, description: 'cold — not loaded; routing here wakes the session' })),
|
|
1951
2000
|
];
|
|
1952
2001
|
const memberships = teamMemberships.list().flatMap(entry => entry.teams.map(team => ({ node: `${config.team}/${id8(entry.session)}`, team })));
|
|
1953
2002
|
const body = JSON.stringify({
|
|
@@ -2528,15 +2577,168 @@ ${message}`;
|
|
|
2528
2577
|
if (card.team !== caller) {
|
|
2529
2578
|
return { ok: false, reason: 'not-derivable' };
|
|
2530
2579
|
}
|
|
2531
|
-
return { ok: true, caller };
|
|
2580
|
+
return { ok: true, caller, callerUrl, card };
|
|
2581
|
+
}
|
|
2582
|
+
/**
|
|
2583
|
+
* W3 (#18 half-side): is a VERIFIED caller LINKED to this host? Key
|
|
2584
|
+
* possession proves the caller is a real host; it says nothing about a
|
|
2585
|
+
* relationship with US. The full card carries collaboration facts
|
|
2586
|
+
* (sessionTeams/rosters), so it additionally requires a link:
|
|
2587
|
+
* 1. same-host membership — the caller's card URL origin equals OUR
|
|
2588
|
+
* card URL origin (the loopback self-fetch face: true self is an
|
|
2589
|
+
* ORIGIN equality; zone-name equality carries no identity in a
|
|
2590
|
+
* single-zone fleet), or one of our session pseudo-handles;
|
|
2591
|
+
* 2. a canvas remote ref — the EXACT handle only (#19 D1, ruling
|
|
2592
|
+
* final: the zone-sibling variant was a blanket fallback that any
|
|
2593
|
+
* one ref opened to a whole zone's hosts in single-zone fleets; the
|
|
2594
|
+
* operator names THAT node, that node links);
|
|
2595
|
+
* 3. BILATERAL seeding (review M1 fix): our config.peers contains the
|
|
2596
|
+
* caller's URL **and** the caller's card (fetched fresh during
|
|
2597
|
+
* verification) carries our origin among its peers — both
|
|
2598
|
+
* operators made the gesture, each on their own side. The single-
|
|
2599
|
+
* sided card-claims variant was a hole the reviewer's adversarial
|
|
2600
|
+
* probe walked through (one config.peers line → full card with
|
|
2601
|
+
* zero commitment from us): peers is an unsigned served-fresh
|
|
2602
|
+
* field, exactly the self-description trust class N3/V5 abolished.
|
|
2603
|
+
* Our operator's config anchors; the card shadow corroborates.
|
|
2604
|
+
*/
|
|
2605
|
+
function callerLinkedToThisHost(caller, callerUrl, callerCard) {
|
|
2606
|
+
// Basis 1, same-host membership (review second-round M-fix): "self" is
|
|
2607
|
+
// an ORIGIN equality, not a zone-name equality. `caller === config.team`
|
|
2608
|
+
// granted unconditionally in a single-zone fleet (the deployment
|
|
2609
|
+
// reality: every host named 'dsh') — any verified caller naming the
|
|
2610
|
+
// shared zone string was "us". True self is the loopback self-fetch
|
|
2611
|
+
// face: the caller's card URL origin equals OUR card URL origin.
|
|
2612
|
+
try {
|
|
2613
|
+
if (callerUrl !== '' && new URL(callerUrl).origin === new URL(ownCardUrl).origin)
|
|
2614
|
+
return true;
|
|
2615
|
+
}
|
|
2616
|
+
catch { /* malformed callerUrl — falls through to the record bases */ }
|
|
2617
|
+
for (const id of joinedSessions.list()) {
|
|
2618
|
+
if (`${config.team}/${id8(id)}` === caller)
|
|
2619
|
+
return true;
|
|
2620
|
+
}
|
|
2621
|
+
// #19 D1 (ruling final): canvas remote refs admit by EXACT HANDLE only.
|
|
2622
|
+
// The zone-sibling variant (`refZone === zone`) was a blanket fallback —
|
|
2623
|
+
// in a single-zone fleet any one ref linked the whole zone face, the
|
|
2624
|
+
// exact trust shape the exposure ruling removed. Exact handle in, the
|
|
2625
|
+
// operator named THAT node.
|
|
2626
|
+
for (const name of canvasStore.list()) {
|
|
2627
|
+
for (const ref of canvasStore.remoteMembersOf(name)) {
|
|
2628
|
+
if (ref === caller)
|
|
2629
|
+
return true;
|
|
2630
|
+
}
|
|
2631
|
+
}
|
|
2632
|
+
// Basis 3, BILATERAL (review M1 fix): OUR config.peers seeds a URL
|
|
2633
|
+
// whose origin matches the caller's card URL, AND the caller's card
|
|
2634
|
+
// reciprocates by carrying OUR origin among its peers — both
|
|
2635
|
+
// operators made the gesture, each on their own side. The single-
|
|
2636
|
+
// sided card-claims variant was a hole the reviewer's adversarial
|
|
2637
|
+
// probe walked through (one config.peers line on the CALLER → full
|
|
2638
|
+
// card with zero commitment from us): peers is an unsigned
|
|
2639
|
+
// served-fresh field, exactly the self-description trust class
|
|
2640
|
+
// N3/V5 abolished. Our operator's own config anchors; the card
|
|
2641
|
+
// shadow only corroborates.
|
|
2642
|
+
if (callerCard !== undefined && ownCardUrl !== '') {
|
|
2643
|
+
const ourOrigin = new URL(ownCardUrl).origin;
|
|
2644
|
+
const callerOrigin = new URL(callerUrl).origin;
|
|
2645
|
+
const weSeedThem = config.peers.some(peer => {
|
|
2646
|
+
try {
|
|
2647
|
+
return new URL(peer).origin === callerOrigin;
|
|
2648
|
+
}
|
|
2649
|
+
catch {
|
|
2650
|
+
return false;
|
|
2651
|
+
}
|
|
2652
|
+
});
|
|
2653
|
+
const theySeedUs = (callerCard.peers ?? []).some(peer => {
|
|
2654
|
+
try {
|
|
2655
|
+
return new URL(peer).origin === ourOrigin;
|
|
2656
|
+
}
|
|
2657
|
+
catch {
|
|
2658
|
+
return false;
|
|
2659
|
+
}
|
|
2660
|
+
});
|
|
2661
|
+
if (weSeedThem && theySeedUs)
|
|
2662
|
+
return true;
|
|
2663
|
+
}
|
|
2664
|
+
return false;
|
|
2532
2665
|
}
|
|
2533
2666
|
/* ── Inbound admission shared by the peer faces (/a2a/direct,
|
|
2534
|
-
/a2a/log)
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2667
|
+
/a2a/log) and — since the boundary-hardening wave (W1) — the LOCAL
|
|
2668
|
+
dispatch candidate: caller_session is the protocol contract.
|
|
2669
|
+
Anonymous is refused, and admission is decided by THIS host's own
|
|
2670
|
+
records — never by the caller's self-description (gh#7 N3, V3/V5).
|
|
2671
|
+
Returns the refusal detail, or undefined when admissible. */
|
|
2672
|
+
/**
|
|
2673
|
+
* W1/W2 shared-team admission core (pure over the host's stores): a
|
|
2674
|
+
* same-zone zone/id8 caller passes when it holds a team this host's
|
|
2675
|
+
* records also hold. The caller's basis is its roster declarations UNION
|
|
2676
|
+
* its canvas memberships (W2②: canvas teammates share a team by being in
|
|
2677
|
+
* the team — the roster is not the only link record); the host's basis is
|
|
2678
|
+
* the routable set (bare zone, joined handles, canvas names) union other
|
|
2679
|
+
* sessions' declarations. Used by both the inbound faces and the local
|
|
2680
|
+
* dispatch candidate so the two paths can never diverge.
|
|
2681
|
+
*/
|
|
2682
|
+
function sameZoneSharedTeamRefusal(caller) {
|
|
2683
|
+
const declared = new Set();
|
|
2684
|
+
let callerSessionId;
|
|
2685
|
+
for (const entry of teamMemberships.list()) {
|
|
2686
|
+
if (`${config.team}/${id8(entry.session)}` === caller) {
|
|
2687
|
+
callerSessionId = entry.session;
|
|
2688
|
+
for (const t of entry.teams)
|
|
2689
|
+
declared.add(t);
|
|
2690
|
+
}
|
|
2691
|
+
}
|
|
2692
|
+
// W2②: canvas membership is a link record too — the caller's session
|
|
2693
|
+
// sitting in a canvas team declares that team as much as a roster line.
|
|
2694
|
+
if (callerSessionId === undefined) {
|
|
2695
|
+
for (const id of joinedSessions.list()) {
|
|
2696
|
+
if (`${config.team}/${id8(id)}` === caller) {
|
|
2697
|
+
callerSessionId = id;
|
|
2698
|
+
break;
|
|
2699
|
+
}
|
|
2700
|
+
}
|
|
2701
|
+
}
|
|
2702
|
+
if (callerSessionId !== undefined) {
|
|
2703
|
+
for (const name of canvasStore.teamsOf(callerSessionId))
|
|
2704
|
+
declared.add(canvasTeamOf(name));
|
|
2705
|
+
}
|
|
2706
|
+
const routable = new Set([config.team]);
|
|
2707
|
+
for (const id of joinedSessions.list())
|
|
2708
|
+
routable.add(`${config.team}/${id8(id)}`);
|
|
2709
|
+
for (const name of canvasStore.list())
|
|
2710
|
+
routable.add(canvasTeamOf(name));
|
|
2711
|
+
const otherLocalDeclarations = new Set();
|
|
2712
|
+
for (const entry of teamMemberships.list()) {
|
|
2713
|
+
if (`${config.team}/${id8(entry.session)}` === caller)
|
|
2714
|
+
continue;
|
|
2715
|
+
for (const t of entry.teams)
|
|
2716
|
+
otherLocalDeclarations.add(t);
|
|
2717
|
+
}
|
|
2718
|
+
const shared = [...declared].some(t => routable.has(t) || otherLocalDeclarations.has(t));
|
|
2719
|
+
if (shared)
|
|
2720
|
+
return undefined;
|
|
2721
|
+
return declared.size === 0
|
|
2722
|
+
? `caller "${caller}" declares no team membership on this host's roster — a teamless node has no network (S3 phase-3 admission). Declare a shared team on both ends, or route through a teamed member.`
|
|
2723
|
+
: `caller "${caller}" declares teams [${[...declared].join(', ')}] — none is routable on this node (shared-team admission). Declare a shared team on both ends, or route through a teamed member.`;
|
|
2724
|
+
}
|
|
2725
|
+
/**
|
|
2726
|
+
* W1: does a caller label fall under the joined-face admission gate? The
|
|
2727
|
+
* zone/id8 shape with THIS zone names a joined session node — the face
|
|
2728
|
+
* the boundary-hardening wave closes. The initiator face (bare node
|
|
2729
|
+
* team) and foreign-zone labels are judged elsewhere (F1b host-face
|
|
2730
|
+
* rules; the foreign branch below) and stay untouched.
|
|
2731
|
+
*/
|
|
2732
|
+
const joinedFaceCallerShape = /^[a-z0-9-]+\/[0-9a-f]{8}$/i;
|
|
2733
|
+
function joinedFaceAdmissionRefusal(callerSession) {
|
|
2734
|
+
if (callerSession === undefined || callerSession === '')
|
|
2735
|
+
return undefined;
|
|
2736
|
+
if (!joinedFaceCallerShape.test(callerSession))
|
|
2737
|
+
return undefined;
|
|
2738
|
+
if (callerSession.slice(0, callerSession.indexOf('/')) !== config.team)
|
|
2739
|
+
return undefined;
|
|
2740
|
+
return sameZoneSharedTeamRefusal(callerSession);
|
|
2741
|
+
}
|
|
2540
2742
|
function a2aCallerRefusal(caller) {
|
|
2541
2743
|
if (!config.teamScopeRouting)
|
|
2542
2744
|
return undefined;
|
|
@@ -2551,33 +2753,10 @@ ${message}`;
|
|
|
2551
2753
|
if (zoneShaped) {
|
|
2552
2754
|
const callerZone = caller.slice(0, caller.indexOf('/'));
|
|
2553
2755
|
if (callerZone === config.team) {
|
|
2554
|
-
// Same-zone handle:
|
|
2555
|
-
//
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
if (`${config.team}/${id8(entry.session)}` === caller)
|
|
2559
|
-
for (const t of entry.teams)
|
|
2560
|
-
declared.add(t);
|
|
2561
|
-
}
|
|
2562
|
-
const routable = new Set([config.team]);
|
|
2563
|
-
for (const id of joinedSessions.list())
|
|
2564
|
-
routable.add(`${config.team}/${id8(id)}`);
|
|
2565
|
-
for (const name of canvasStore.list())
|
|
2566
|
-
routable.add(canvasTeamOf(name));
|
|
2567
|
-
const otherLocalDeclarations = new Set();
|
|
2568
|
-
for (const entry of teamMemberships.list()) {
|
|
2569
|
-
if (`${config.team}/${id8(entry.session)}` === caller)
|
|
2570
|
-
continue;
|
|
2571
|
-
for (const t of entry.teams)
|
|
2572
|
-
otherLocalDeclarations.add(t);
|
|
2573
|
-
}
|
|
2574
|
-
const shared = [...declared].some(t => routable.has(t) || otherLocalDeclarations.has(t));
|
|
2575
|
-
if (!shared) {
|
|
2576
|
-
return declared.size === 0
|
|
2577
|
-
? `caller "${caller}" declares no team membership on this host's roster — a teamless node has no network (S3 phase-3 admission). Declare a shared team on both ends, or route through a teamed member.`
|
|
2578
|
-
: `caller "${caller}" declares teams [${[...declared].join(', ')}] — none is routable on this node (shared-team admission). Declare a shared team on both ends, or route through a teamed member.`;
|
|
2579
|
-
}
|
|
2580
|
-
return undefined;
|
|
2756
|
+
// Same-zone handle: the W1/W2 shared admission core — roster
|
|
2757
|
+
// declarations UNION canvas memberships against this host's
|
|
2758
|
+
// records (canvas names are link records too; see the helper).
|
|
2759
|
+
return sameZoneSharedTeamRefusal(caller);
|
|
2581
2760
|
}
|
|
2582
2761
|
// Foreign-zone handle (N3/N4): admitted ONLY through this host's
|
|
2583
2762
|
// operator-established cross-host links — a canvas remote member ref
|
|
@@ -3877,6 +4056,51 @@ ${message}`;
|
|
|
3877
4056
|
return { ok: true, team, id, teams: [...teamMemberships.teamsOf(id)] };
|
|
3878
4057
|
},
|
|
3879
4058
|
}));
|
|
4059
|
+
ctx.tools.register(defineTool({
|
|
4060
|
+
name: 'a2a_exposure',
|
|
4061
|
+
description: '#19 exposure tiers: set THIS session\'s network visibility. \'linked\' (the default '
|
|
4062
|
+
+ 'floor) — visible to linked callers per the card-face link gate; \'fleet\' — a voluntary '
|
|
4063
|
+
+ 'opt-UP making the session\'s team visible to any fleet peer. Members may raise and lower '
|
|
4064
|
+
+ 'their own tier freely; nothing exists below linked (no private tier in v1), and group '
|
|
4065
|
+
+ 'visibility always derives from the strictest member (no group knobs). Requires the '
|
|
4066
|
+
+ 'session to be joined to the network.',
|
|
4067
|
+
parameters: {
|
|
4068
|
+
tier: { type: 'string', enum: ['linked', 'fleet'], required: true, description: 'The exposure tier to set for this session.' },
|
|
4069
|
+
},
|
|
4070
|
+
output: {
|
|
4071
|
+
schema: {
|
|
4072
|
+
type: 'object',
|
|
4073
|
+
additionalProperties: false,
|
|
4074
|
+
properties: {
|
|
4075
|
+
ok: { type: 'boolean', required: true },
|
|
4076
|
+
id: { type: 'string' },
|
|
4077
|
+
tier: { type: 'string' },
|
|
4078
|
+
error: { type: 'string', description: 'Refusal reason when ok is false (not joined, or no agent context).' },
|
|
4079
|
+
},
|
|
4080
|
+
},
|
|
4081
|
+
render: (_args, value) => [{
|
|
4082
|
+
type: 'text',
|
|
4083
|
+
text: value.ok === true
|
|
4084
|
+
? `Session ${String(value.id)} exposure tier set to ${String(value.tier)}`
|
|
4085
|
+
: `Exposure refused: ${String(value.error)}`,
|
|
4086
|
+
}],
|
|
4087
|
+
},
|
|
4088
|
+
presentCall: () => ({ card: 'generic', title: 'A2A exposure tier', kind: 'other', rawInput: null }),
|
|
4089
|
+
execute: async (args, exec) => {
|
|
4090
|
+
const agent = exec.agent;
|
|
4091
|
+
if (agent === undefined) {
|
|
4092
|
+
return { ok: false, error: 'no agent context — the exposure tier is a per-session fact; call from a session' };
|
|
4093
|
+
}
|
|
4094
|
+
const id = String(agent.id);
|
|
4095
|
+
if (!joinedSessions.has(id)) {
|
|
4096
|
+
return { ok: false, error: 'session is not joined to the A2A network — join first (the sidebar control or the host join route)' };
|
|
4097
|
+
}
|
|
4098
|
+
const tier = args.tier === 'fleet' ? 'fleet' : 'linked';
|
|
4099
|
+
exposureStore.setTier(id, tier);
|
|
4100
|
+
logger.info(`a2a: ${id8(id)} exposure tier -> ${tier}`);
|
|
4101
|
+
return { ok: true, id, tier };
|
|
4102
|
+
},
|
|
4103
|
+
}));
|
|
3880
4104
|
ctx.tools.register(defineTool({
|
|
3881
4105
|
name: 'a2a_report',
|
|
3882
4106
|
description: 'Report a task or status to this host\'s A2A network — the ONE ingress a session WITHOUT network '
|
|
@@ -4452,6 +4676,18 @@ ${message}`;
|
|
|
4452
4676
|
*/
|
|
4453
4677
|
async function dispatchLocalCandidate(team, message, callerSession, contextId, signal, asyncMode = false, taskIdFromCaller, callbackAddress) {
|
|
4454
4678
|
const webServer = ctx.get('webServer');
|
|
4679
|
+
// W1 (boundary hardening): the local in-process candidate is an
|
|
4680
|
+
// admission seam like any other — a JOINED session (zone/id8 caller
|
|
4681
|
+
// face) must clear the same shared-team admission /a2a/direct applies,
|
|
4682
|
+
// or a teamless local node could route to any session team by riding
|
|
4683
|
+
// the cheapest path. The initiator face (bare node team) and
|
|
4684
|
+
// foreign-zone labels are NOT gated here (F1b host-face rules, R-C
|
|
4685
|
+
// pending) — only the joined face closes.
|
|
4686
|
+
const joinedRefusal = joinedFaceAdmissionRefusal(callerSession);
|
|
4687
|
+
if (joinedRefusal !== undefined) {
|
|
4688
|
+
recordActivity('out', team, callerSession ?? 'local', false);
|
|
4689
|
+
return { ok: false, error: joinedRefusal, code: -32000 };
|
|
4690
|
+
}
|
|
4455
4691
|
const canvasName = parseCanvasTeamName(team);
|
|
4456
4692
|
let teamIsLocal = team === config.team
|
|
4457
4693
|
|| resolveAgentForTeam(team) !== undefined
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* #19 exposure tiers (ruling final, D3/D4): per-session visibility tier on
|
|
3
|
+
* the A2A network — 'linked' (the default floor: visible to linked callers
|
|
4
|
+
* per the W3 gate) or 'fleet' (the member's voluntary opt-UP: visible to
|
|
5
|
+
* any fleet peer). v1 carries exactly these two tiers (D5: no private
|
|
6
|
+
* residue below linked).
|
|
7
|
+
*
|
|
8
|
+
* D3: joins default to linked — existing members transfer automatically
|
|
9
|
+
* (tier absence IS linked, so no migration is needed); a member may raise
|
|
10
|
+
* itself to fleet and lower itself back (fleet is an opt-up, linked is the
|
|
11
|
+
* floor, never a ceiling decision taken from the member).
|
|
12
|
+
*
|
|
13
|
+
* D4 (成文, zero group knobs): a GROUP's visibility equals its STRICTEST
|
|
14
|
+
* member tier — one linked member keeps the group linked-level. The rule
|
|
15
|
+
* binds every future group surface (canvas listing, fleet feeds); v1 has
|
|
16
|
+
* no group-level tier API by design, so the rule needs no enforcement
|
|
17
|
+
* seam yet.
|
|
18
|
+
* @module @nelsonlongxiang/dsh-open-a2a-net/exposure-store
|
|
19
|
+
*/
|
|
20
|
+
/** The two v1 tiers (D5: nothing below linked exists). */
|
|
21
|
+
export type ExposureTier = 'linked' | 'fleet';
|
|
22
|
+
/** The persisted tier document (whole-file read/write, small by design). */
|
|
23
|
+
export interface ExposureSnapshot {
|
|
24
|
+
readonly tiers?: Readonly<Record<string, ExposureTier>>;
|
|
25
|
+
}
|
|
26
|
+
export declare class ExposureStore {
|
|
27
|
+
private readonly path;
|
|
28
|
+
private tiers;
|
|
29
|
+
/**
|
|
30
|
+
* @param path - persistence file (`<dsh-home>/a2a/exposure.json`); empty = no persistence.
|
|
31
|
+
*/
|
|
32
|
+
constructor(path: string);
|
|
33
|
+
/**
|
|
34
|
+
* The session's tier — 'linked' unless explicitly raised to 'fleet'
|
|
35
|
+
* (D3: existing members transfer as linked automatically).
|
|
36
|
+
*/
|
|
37
|
+
tierOf(id: string): ExposureTier;
|
|
38
|
+
/**
|
|
39
|
+
* Set the session's tier. Members may opt up to fleet and back down to
|
|
40
|
+
* linked; there is no tier below linked (D5) and no group-level knob
|
|
41
|
+
* (D4 — group visibility derives from the strictest member).
|
|
42
|
+
*/
|
|
43
|
+
setTier(id: string, tier: ExposureTier): void;
|
|
44
|
+
private persist;
|
|
45
|
+
}
|
package/lib/types/index.d.ts
CHANGED
|
@@ -103,6 +103,11 @@ export interface Config {
|
|
|
103
103
|
* self-asserted already-linked name must not pass on shape alone).
|
|
104
104
|
*/
|
|
105
105
|
readonly originClaimWindow: boolean;
|
|
106
|
+
/**
|
|
107
|
+
* #19 D2: card-face sessionTeams excerpt exposure — 'full' | 'masked'
|
|
108
|
+
* | 'off' (owner-level; default full). See the schema comment.
|
|
109
|
+
*/
|
|
110
|
+
readonly exposureExcerpt: 'full' | 'masked' | 'off';
|
|
106
111
|
/**
|
|
107
112
|
* Override for the OriginClaim callerUrl anchor. Loopback-bound hosts
|
|
108
113
|
* behind a pocket tunnel must name the card URL peers can actually
|
package/package.json
CHANGED
|
@@ -1,218 +1,218 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@nelsonlongxiang/dsh-open-a2a-net",
|
|
3
|
-
"version": "0.6.
|
|
4
|
-
"description": "Open A2A network plugin for DeepSeek Harness: signed agent cards, decentralized peer/zone discovery, direct routing model tools, and joinable session nodes with a sidebar control",
|
|
5
|
-
"type": "module",
|
|
6
|
-
"main": "lib/index.js",
|
|
7
|
-
"types": "lib/types/index.d.ts",
|
|
8
|
-
"exports": {
|
|
9
|
-
".": {
|
|
10
|
-
"types": "./lib/types/index.d.ts",
|
|
11
|
-
"default": "./lib/index.js"
|
|
12
|
-
},
|
|
13
|
-
"./invariant": {
|
|
14
|
-
"types": "./lib/types/invariant.d.ts",
|
|
15
|
-
"default": "./lib/invariant.js"
|
|
16
|
-
},
|
|
17
|
-
"./client": {
|
|
18
|
-
"types": "./lib/types/client/index.d.ts",
|
|
19
|
-
"default": "./lib/client.js"
|
|
20
|
-
},
|
|
21
|
-
"./card": {
|
|
22
|
-
"types": "./lib/types/card.d.ts",
|
|
23
|
-
"default": "./lib/card.js"
|
|
24
|
-
},
|
|
25
|
-
"./receipt": {
|
|
26
|
-
"types": "./lib/types/receipt.d.ts",
|
|
27
|
-
"default": "./lib/receipt.js"
|
|
28
|
-
},
|
|
29
|
-
"./transport-caps": {
|
|
30
|
-
"types": "./lib/types/transport-caps.d.ts",
|
|
31
|
-
"default": "./lib/transport-caps.js"
|
|
32
|
-
},
|
|
33
|
-
"./a2a-client": {
|
|
34
|
-
"types": "./lib/types/a2a-client.d.ts",
|
|
35
|
-
"default": "./lib/a2a-client.js"
|
|
36
|
-
},
|
|
37
|
-
"./cordis.patch.yml": "./cordis.patch.yml",
|
|
38
|
-
"./package.json": "./package.json"
|
|
39
|
-
},
|
|
40
|
-
"files": [
|
|
41
|
-
"lib",
|
|
42
|
-
"assets/nexusDist",
|
|
43
|
-
"assets/stageDist",
|
|
44
|
-
"images",
|
|
45
|
-
"README_zh.md",
|
|
46
|
-
"cordis.patch.yml",
|
|
47
|
-
"README.md",
|
|
48
|
-
"nexus-stage/src",
|
|
49
|
-
"nexus-stage/scripts",
|
|
50
|
-
"nexus-stage/package.json",
|
|
51
|
-
"nexus-stage/package-lock.json",
|
|
52
|
-
"nexus-stage/tsconfig.json",
|
|
53
|
-
"nexus-stage/vite.config.ts"
|
|
54
|
-
],
|
|
55
|
-
"packageManager": "pnpm@11.28.5",
|
|
56
|
-
"engines": {
|
|
57
|
-
"pnpm": ">=11.24 <12"
|
|
58
|
-
},
|
|
59
|
-
"dsh": {
|
|
60
|
-
"bundle": {
|
|
61
|
-
"patch": "./cordis.patch.yml"
|
|
62
|
-
},
|
|
63
|
-
"client": {
|
|
64
|
-
"platform": "web",
|
|
65
|
-
"inject": [
|
|
66
|
-
"@deepseek-ai/dsh-client-runtime",
|
|
67
|
-
"@deepseek-ai/dsh-client-locale",
|
|
68
|
-
"@deepseek-ai/dsh-client-ui-slots",
|
|
69
|
-
"@deepseek-ai/dsh-client-ui-sidebar"
|
|
70
|
-
]
|
|
71
|
-
}
|
|
72
|
-
},
|
|
73
|
-
"scripts": {
|
|
74
|
-
"prepare": "pnpm build",
|
|
75
|
-
"build": "tsc -p tsconfig.json && tsc -p tsconfig.client.json && tsdown && node scripts/copy-skill.mjs",
|
|
76
|
-
"typecheck": "tsc -p tsconfig.json --noEmit && tsc -p tsconfig.client.json --noEmit",
|
|
77
|
-
"test": "vitest run",
|
|
78
|
-
"test:unit": "vitest run tests/unit",
|
|
79
|
-
"test:wire": "vitest run tests/wire",
|
|
80
|
-
"test:mesh": "vitest run tests/mesh",
|
|
81
|
-
"test:live": "vitest run tests/live",
|
|
82
|
-
"test:build-layout": "vitest run tests/gates/build-layout.spec.ts",
|
|
83
|
-
"verify:nexus": "node scripts/verify-nexus-ci.mjs && npm --prefix nexus-stage run build && npm --prefix nexus-stage run test && git diff --exit-code -- assets/nexusDist/assets && git diff --ignore-cr-at-eol --exit-code -- assets/nexusDist/index.html",
|
|
84
|
-
"verify": "pnpm typecheck && pnpm build && pnpm test && pnpm test:build-layout && pnpm verify:nexus"
|
|
85
|
-
},
|
|
86
|
-
"peerDependencies": {
|
|
87
|
-
"@deepseek-ai/cordis": ">=4.0.1",
|
|
88
|
-
"@deepseek-ai/cordis-plugin-loader": ">=1.0.2",
|
|
89
|
-
"@deepseek-ai/cordis-plugin-timer": ">=1.1.3",
|
|
90
|
-
"@deepseek-ai/dsh-agent": ">=0.1.7-alpha.2",
|
|
91
|
-
"@deepseek-ai/dsh-api-session-controller": ">=0.1.7-alpha.2",
|
|
92
|
-
"@deepseek-ai/dsh-client-locale": ">=0.1.7-alpha.2",
|
|
93
|
-
"@deepseek-ai/dsh-client-runtime": ">=0.1.1-rc.2",
|
|
94
|
-
"@deepseek-ai/dsh-client-ui-primitives": ">=0.1.7-alpha.2",
|
|
95
|
-
"@deepseek-ai/dsh-client-ui-sidebar": ">=0.1.7-alpha.2",
|
|
96
|
-
"@deepseek-ai/dsh-client-ui-slots": ">=0.1.7-alpha.2",
|
|
97
|
-
"@deepseek-ai/dsh-home-paths": ">=0.1.7-alpha.2",
|
|
98
|
-
"@deepseek-ai/dsh-host-webserver": ">=0.1.7-alpha.2",
|
|
99
|
-
"@deepseek-ai/dsh-invariants": ">=0.1.7-alpha.2",
|
|
100
|
-
"@deepseek-ai/dsh-llm": ">=0.1.7-alpha.2",
|
|
101
|
-
"@deepseek-ai/dsh-session": ">=0.1.7-alpha.2",
|
|
102
|
-
"@deepseek-ai/dsh-session-persistence": ">=0.1.7-alpha.2",
|
|
103
|
-
"@deepseek-ai/dsh-session-title": ">=0.1.7-alpha.2",
|
|
104
|
-
"@deepseek-ai/dsh-tools": ">=0.1.7-alpha.2",
|
|
105
|
-
"@deepseek-ai/schemastery": ">=3.18.1",
|
|
106
|
-
"react": "^18.2.0"
|
|
107
|
-
},
|
|
108
|
-
"peerDependenciesMeta": {
|
|
109
|
-
"@deepseek-ai/cordis": {
|
|
110
|
-
"optional": true
|
|
111
|
-
},
|
|
112
|
-
"@deepseek-ai/cordis-plugin-loader": {
|
|
113
|
-
"optional": true
|
|
114
|
-
},
|
|
115
|
-
"@deepseek-ai/cordis-plugin-timer": {
|
|
116
|
-
"optional": true
|
|
117
|
-
},
|
|
118
|
-
"@deepseek-ai/dsh-agent": {
|
|
119
|
-
"optional": true
|
|
120
|
-
},
|
|
121
|
-
"@deepseek-ai/dsh-client-locale": {
|
|
122
|
-
"optional": true
|
|
123
|
-
},
|
|
124
|
-
"@deepseek-ai/dsh-client-runtime": {
|
|
125
|
-
"optional": true
|
|
126
|
-
},
|
|
127
|
-
"@deepseek-ai/dsh-client-ui-primitives": {
|
|
128
|
-
"optional": true
|
|
129
|
-
},
|
|
130
|
-
"@deepseek-ai/dsh-client-ui-sidebar": {
|
|
131
|
-
"optional": true
|
|
132
|
-
},
|
|
133
|
-
"@deepseek-ai/dsh-client-ui-slots": {
|
|
134
|
-
"optional": true
|
|
135
|
-
},
|
|
136
|
-
"@deepseek-ai/dsh-api-session-controller": {
|
|
137
|
-
"optional": true
|
|
138
|
-
},
|
|
139
|
-
"@deepseek-ai/dsh-home-paths": {
|
|
140
|
-
"optional": true
|
|
141
|
-
},
|
|
142
|
-
"@deepseek-ai/dsh-host-webserver": {
|
|
143
|
-
"optional": true
|
|
144
|
-
},
|
|
145
|
-
"@deepseek-ai/dsh-invariants": {
|
|
146
|
-
"optional": true
|
|
147
|
-
},
|
|
148
|
-
"@deepseek-ai/dsh-llm": {
|
|
149
|
-
"optional": true
|
|
150
|
-
},
|
|
151
|
-
"@deepseek-ai/dsh-session": {
|
|
152
|
-
"optional": true
|
|
153
|
-
},
|
|
154
|
-
"@deepseek-ai/dsh-session-persistence": {
|
|
155
|
-
"optional": true
|
|
156
|
-
},
|
|
157
|
-
"@deepseek-ai/dsh-session-title": {
|
|
158
|
-
"optional": true
|
|
159
|
-
},
|
|
160
|
-
"@deepseek-ai/dsh-tools": {
|
|
161
|
-
"optional": true
|
|
162
|
-
},
|
|
163
|
-
"@deepseek-ai/schemastery": {
|
|
164
|
-
"optional": true
|
|
165
|
-
},
|
|
166
|
-
"react": {
|
|
167
|
-
"optional": true
|
|
168
|
-
}
|
|
169
|
-
},
|
|
170
|
-
"devDependencies": {
|
|
171
|
-
"@deepseek-ai/dsh-client-store": "0.2.0-rc.2",
|
|
172
|
-
"@deepseek-ai/dsh-client-ui-primitives": "0.2.0-rc.2",
|
|
173
|
-
"@deepseek-ai/dsh-scope": "0.0.1-rc.1",
|
|
174
|
-
"@deepseek-ai/dsh-system-prompt": "0.1.0-rc.6",
|
|
175
|
-
"@deepseek-ai/dsh-util-code-language": "0.1.7-rc.2",
|
|
176
|
-
"@deepseek-ai/dsh-util-workspace-path": "0.1.2-alpha.2",
|
|
177
|
-
"@shikijs/langs": "^4.5.0",
|
|
178
|
-
"@testing-library/react": "^16.3.0",
|
|
179
|
-
"@types/node": "^24.13.3",
|
|
180
|
-
"@types/react": "~18.3.1",
|
|
181
|
-
"@types/react-dom": "^18.3.7",
|
|
182
|
-
"anser": "^2.3.5",
|
|
183
|
-
"clsx": "^2.1.1",
|
|
184
|
-
"diff": "^9.0.0",
|
|
185
|
-
"jsdom": "^26.1.0",
|
|
186
|
-
"katex": "^0.19.0",
|
|
187
|
-
"lightningcss": "^1.33.0",
|
|
188
|
-
"mdast-util-from-markdown": "^2.1.0",
|
|
189
|
-
"mdast-util-gfm": "^3.1.0",
|
|
190
|
-
"mdast-util-math": "^3.0.0",
|
|
191
|
-
"micromark-core-commonmark": "^2.0.4",
|
|
192
|
-
"micromark-extension-gfm": "^3.0.0",
|
|
193
|
-
"micromark-extension-math": "^3.1.0",
|
|
194
|
-
"micromark-factory-space": "^2.1.0",
|
|
195
|
-
"micromark-util-character": "^2.1.1",
|
|
196
|
-
"micromark-util-classify-character": "^2.0.1",
|
|
197
|
-
"micromark-util-sanitize-uri": "^2.0.1",
|
|
198
|
-
"micromark-util-symbol": "^2.0.1",
|
|
199
|
-
"react": "^18.2.0",
|
|
200
|
-
"react-dom": "^18.2.0",
|
|
201
|
-
"shiki": "^4.5.0",
|
|
202
|
-
"simple-icons": "^16.35.0",
|
|
203
|
-
"tsdown": "0.22.2",
|
|
204
|
-
"typescript": "^5.9.3",
|
|
205
|
-
"vitest": "^4.1.8"
|
|
206
|
-
},
|
|
207
|
-
"pnpm": {
|
|
208
|
-
"overrides": {
|
|
209
|
-
"@deepseek-ai/cordis": "4.0.5",
|
|
210
|
-
"@deepseek-ai/cordis-plugin-loader": "1.0.5"
|
|
211
|
-
}
|
|
212
|
-
},
|
|
213
|
-
"license": "MIT",
|
|
214
|
-
"repository": {
|
|
215
|
-
"type": "git",
|
|
216
|
-
"url": "git+https://gitee.com/NelsonLongXiang/dsh-open-a2a-net.git"
|
|
217
|
-
}
|
|
218
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@nelsonlongxiang/dsh-open-a2a-net",
|
|
3
|
+
"version": "0.6.25",
|
|
4
|
+
"description": "Open A2A network plugin for DeepSeek Harness: signed agent cards, decentralized peer/zone discovery, direct routing model tools, and joinable session nodes with a sidebar control",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "lib/index.js",
|
|
7
|
+
"types": "lib/types/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./lib/types/index.d.ts",
|
|
11
|
+
"default": "./lib/index.js"
|
|
12
|
+
},
|
|
13
|
+
"./invariant": {
|
|
14
|
+
"types": "./lib/types/invariant.d.ts",
|
|
15
|
+
"default": "./lib/invariant.js"
|
|
16
|
+
},
|
|
17
|
+
"./client": {
|
|
18
|
+
"types": "./lib/types/client/index.d.ts",
|
|
19
|
+
"default": "./lib/client.js"
|
|
20
|
+
},
|
|
21
|
+
"./card": {
|
|
22
|
+
"types": "./lib/types/card.d.ts",
|
|
23
|
+
"default": "./lib/card.js"
|
|
24
|
+
},
|
|
25
|
+
"./receipt": {
|
|
26
|
+
"types": "./lib/types/receipt.d.ts",
|
|
27
|
+
"default": "./lib/receipt.js"
|
|
28
|
+
},
|
|
29
|
+
"./transport-caps": {
|
|
30
|
+
"types": "./lib/types/transport-caps.d.ts",
|
|
31
|
+
"default": "./lib/transport-caps.js"
|
|
32
|
+
},
|
|
33
|
+
"./a2a-client": {
|
|
34
|
+
"types": "./lib/types/a2a-client.d.ts",
|
|
35
|
+
"default": "./lib/a2a-client.js"
|
|
36
|
+
},
|
|
37
|
+
"./cordis.patch.yml": "./cordis.patch.yml",
|
|
38
|
+
"./package.json": "./package.json"
|
|
39
|
+
},
|
|
40
|
+
"files": [
|
|
41
|
+
"lib",
|
|
42
|
+
"assets/nexusDist",
|
|
43
|
+
"assets/stageDist",
|
|
44
|
+
"images",
|
|
45
|
+
"README_zh.md",
|
|
46
|
+
"cordis.patch.yml",
|
|
47
|
+
"README.md",
|
|
48
|
+
"nexus-stage/src",
|
|
49
|
+
"nexus-stage/scripts",
|
|
50
|
+
"nexus-stage/package.json",
|
|
51
|
+
"nexus-stage/package-lock.json",
|
|
52
|
+
"nexus-stage/tsconfig.json",
|
|
53
|
+
"nexus-stage/vite.config.ts"
|
|
54
|
+
],
|
|
55
|
+
"packageManager": "pnpm@11.28.5",
|
|
56
|
+
"engines": {
|
|
57
|
+
"pnpm": ">=11.24 <12"
|
|
58
|
+
},
|
|
59
|
+
"dsh": {
|
|
60
|
+
"bundle": {
|
|
61
|
+
"patch": "./cordis.patch.yml"
|
|
62
|
+
},
|
|
63
|
+
"client": {
|
|
64
|
+
"platform": "web",
|
|
65
|
+
"inject": [
|
|
66
|
+
"@deepseek-ai/dsh-client-runtime",
|
|
67
|
+
"@deepseek-ai/dsh-client-locale",
|
|
68
|
+
"@deepseek-ai/dsh-client-ui-slots",
|
|
69
|
+
"@deepseek-ai/dsh-client-ui-sidebar"
|
|
70
|
+
]
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
"scripts": {
|
|
74
|
+
"prepare": "pnpm build",
|
|
75
|
+
"build": "tsc -p tsconfig.json && tsc -p tsconfig.client.json && tsdown && node scripts/copy-skill.mjs",
|
|
76
|
+
"typecheck": "tsc -p tsconfig.json --noEmit && tsc -p tsconfig.client.json --noEmit",
|
|
77
|
+
"test": "vitest run",
|
|
78
|
+
"test:unit": "vitest run tests/unit",
|
|
79
|
+
"test:wire": "vitest run tests/wire",
|
|
80
|
+
"test:mesh": "vitest run tests/mesh",
|
|
81
|
+
"test:live": "vitest run tests/live",
|
|
82
|
+
"test:build-layout": "vitest run tests/gates/build-layout.spec.ts",
|
|
83
|
+
"verify:nexus": "node scripts/verify-nexus-ci.mjs && npm --prefix nexus-stage run build && npm --prefix nexus-stage run test && git diff --exit-code -- assets/nexusDist/assets && git diff --ignore-cr-at-eol --exit-code -- assets/nexusDist/index.html",
|
|
84
|
+
"verify": "pnpm typecheck && pnpm build && pnpm test && pnpm test:build-layout && pnpm verify:nexus"
|
|
85
|
+
},
|
|
86
|
+
"peerDependencies": {
|
|
87
|
+
"@deepseek-ai/cordis": ">=4.0.1",
|
|
88
|
+
"@deepseek-ai/cordis-plugin-loader": ">=1.0.2",
|
|
89
|
+
"@deepseek-ai/cordis-plugin-timer": ">=1.1.3",
|
|
90
|
+
"@deepseek-ai/dsh-agent": ">=0.1.7-alpha.2",
|
|
91
|
+
"@deepseek-ai/dsh-api-session-controller": ">=0.1.7-alpha.2",
|
|
92
|
+
"@deepseek-ai/dsh-client-locale": ">=0.1.7-alpha.2",
|
|
93
|
+
"@deepseek-ai/dsh-client-runtime": ">=0.1.1-rc.2",
|
|
94
|
+
"@deepseek-ai/dsh-client-ui-primitives": ">=0.1.7-alpha.2",
|
|
95
|
+
"@deepseek-ai/dsh-client-ui-sidebar": ">=0.1.7-alpha.2",
|
|
96
|
+
"@deepseek-ai/dsh-client-ui-slots": ">=0.1.7-alpha.2",
|
|
97
|
+
"@deepseek-ai/dsh-home-paths": ">=0.1.7-alpha.2",
|
|
98
|
+
"@deepseek-ai/dsh-host-webserver": ">=0.1.7-alpha.2",
|
|
99
|
+
"@deepseek-ai/dsh-invariants": ">=0.1.7-alpha.2",
|
|
100
|
+
"@deepseek-ai/dsh-llm": ">=0.1.7-alpha.2",
|
|
101
|
+
"@deepseek-ai/dsh-session": ">=0.1.7-alpha.2",
|
|
102
|
+
"@deepseek-ai/dsh-session-persistence": ">=0.1.7-alpha.2",
|
|
103
|
+
"@deepseek-ai/dsh-session-title": ">=0.1.7-alpha.2",
|
|
104
|
+
"@deepseek-ai/dsh-tools": ">=0.1.7-alpha.2",
|
|
105
|
+
"@deepseek-ai/schemastery": ">=3.18.1",
|
|
106
|
+
"react": "^18.2.0"
|
|
107
|
+
},
|
|
108
|
+
"peerDependenciesMeta": {
|
|
109
|
+
"@deepseek-ai/cordis": {
|
|
110
|
+
"optional": true
|
|
111
|
+
},
|
|
112
|
+
"@deepseek-ai/cordis-plugin-loader": {
|
|
113
|
+
"optional": true
|
|
114
|
+
},
|
|
115
|
+
"@deepseek-ai/cordis-plugin-timer": {
|
|
116
|
+
"optional": true
|
|
117
|
+
},
|
|
118
|
+
"@deepseek-ai/dsh-agent": {
|
|
119
|
+
"optional": true
|
|
120
|
+
},
|
|
121
|
+
"@deepseek-ai/dsh-client-locale": {
|
|
122
|
+
"optional": true
|
|
123
|
+
},
|
|
124
|
+
"@deepseek-ai/dsh-client-runtime": {
|
|
125
|
+
"optional": true
|
|
126
|
+
},
|
|
127
|
+
"@deepseek-ai/dsh-client-ui-primitives": {
|
|
128
|
+
"optional": true
|
|
129
|
+
},
|
|
130
|
+
"@deepseek-ai/dsh-client-ui-sidebar": {
|
|
131
|
+
"optional": true
|
|
132
|
+
},
|
|
133
|
+
"@deepseek-ai/dsh-client-ui-slots": {
|
|
134
|
+
"optional": true
|
|
135
|
+
},
|
|
136
|
+
"@deepseek-ai/dsh-api-session-controller": {
|
|
137
|
+
"optional": true
|
|
138
|
+
},
|
|
139
|
+
"@deepseek-ai/dsh-home-paths": {
|
|
140
|
+
"optional": true
|
|
141
|
+
},
|
|
142
|
+
"@deepseek-ai/dsh-host-webserver": {
|
|
143
|
+
"optional": true
|
|
144
|
+
},
|
|
145
|
+
"@deepseek-ai/dsh-invariants": {
|
|
146
|
+
"optional": true
|
|
147
|
+
},
|
|
148
|
+
"@deepseek-ai/dsh-llm": {
|
|
149
|
+
"optional": true
|
|
150
|
+
},
|
|
151
|
+
"@deepseek-ai/dsh-session": {
|
|
152
|
+
"optional": true
|
|
153
|
+
},
|
|
154
|
+
"@deepseek-ai/dsh-session-persistence": {
|
|
155
|
+
"optional": true
|
|
156
|
+
},
|
|
157
|
+
"@deepseek-ai/dsh-session-title": {
|
|
158
|
+
"optional": true
|
|
159
|
+
},
|
|
160
|
+
"@deepseek-ai/dsh-tools": {
|
|
161
|
+
"optional": true
|
|
162
|
+
},
|
|
163
|
+
"@deepseek-ai/schemastery": {
|
|
164
|
+
"optional": true
|
|
165
|
+
},
|
|
166
|
+
"react": {
|
|
167
|
+
"optional": true
|
|
168
|
+
}
|
|
169
|
+
},
|
|
170
|
+
"devDependencies": {
|
|
171
|
+
"@deepseek-ai/dsh-client-store": "0.2.0-rc.2",
|
|
172
|
+
"@deepseek-ai/dsh-client-ui-primitives": "0.2.0-rc.2",
|
|
173
|
+
"@deepseek-ai/dsh-scope": "0.0.1-rc.1",
|
|
174
|
+
"@deepseek-ai/dsh-system-prompt": "0.1.0-rc.6",
|
|
175
|
+
"@deepseek-ai/dsh-util-code-language": "0.1.7-rc.2",
|
|
176
|
+
"@deepseek-ai/dsh-util-workspace-path": "0.1.2-alpha.2",
|
|
177
|
+
"@shikijs/langs": "^4.5.0",
|
|
178
|
+
"@testing-library/react": "^16.3.0",
|
|
179
|
+
"@types/node": "^24.13.3",
|
|
180
|
+
"@types/react": "~18.3.1",
|
|
181
|
+
"@types/react-dom": "^18.3.7",
|
|
182
|
+
"anser": "^2.3.5",
|
|
183
|
+
"clsx": "^2.1.1",
|
|
184
|
+
"diff": "^9.0.0",
|
|
185
|
+
"jsdom": "^26.1.0",
|
|
186
|
+
"katex": "^0.19.0",
|
|
187
|
+
"lightningcss": "^1.33.0",
|
|
188
|
+
"mdast-util-from-markdown": "^2.1.0",
|
|
189
|
+
"mdast-util-gfm": "^3.1.0",
|
|
190
|
+
"mdast-util-math": "^3.0.0",
|
|
191
|
+
"micromark-core-commonmark": "^2.0.4",
|
|
192
|
+
"micromark-extension-gfm": "^3.0.0",
|
|
193
|
+
"micromark-extension-math": "^3.1.0",
|
|
194
|
+
"micromark-factory-space": "^2.1.0",
|
|
195
|
+
"micromark-util-character": "^2.1.1",
|
|
196
|
+
"micromark-util-classify-character": "^2.0.1",
|
|
197
|
+
"micromark-util-sanitize-uri": "^2.0.1",
|
|
198
|
+
"micromark-util-symbol": "^2.0.1",
|
|
199
|
+
"react": "^18.2.0",
|
|
200
|
+
"react-dom": "^18.2.0",
|
|
201
|
+
"shiki": "^4.5.0",
|
|
202
|
+
"simple-icons": "^16.35.0",
|
|
203
|
+
"tsdown": "0.22.2",
|
|
204
|
+
"typescript": "^5.9.3",
|
|
205
|
+
"vitest": "^4.1.8"
|
|
206
|
+
},
|
|
207
|
+
"pnpm": {
|
|
208
|
+
"overrides": {
|
|
209
|
+
"@deepseek-ai/cordis": "4.0.5",
|
|
210
|
+
"@deepseek-ai/cordis-plugin-loader": "1.0.5"
|
|
211
|
+
}
|
|
212
|
+
},
|
|
213
|
+
"license": "MIT",
|
|
214
|
+
"repository": {
|
|
215
|
+
"type": "git",
|
|
216
|
+
"url": "git+https://gitee.com/NelsonLongXiang/dsh-open-a2a-net.git"
|
|
217
|
+
}
|
|
218
|
+
}
|
package/lib/types/client.d.ts
DELETED
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* In-process decentralized A2A client: the outbound peer surface — verified
|
|
3
|
-
* agent-card fetches (discovery) and direct routes to a peer's
|
|
4
|
-
* `/a2a/direct` endpoint. Timing and fetch seams are constructor-injected so
|
|
5
|
-
* tests drive them without a network.
|
|
6
|
-
* @module @nelsonlongxiang/dsh-open-a2a-net/client
|
|
7
|
-
*/
|
|
8
|
-
import type { A2aPeerCard, A2aRouteResult } from './types.ts';
|
|
9
|
-
/** One-shot delayed callback returning its disposer. */
|
|
10
|
-
export type A2aSchedule = (callback: () => void, delayMs: number) => () => void;
|
|
11
|
-
/** Minimal fetch face; the first HTTP-level failure rejects. */
|
|
12
|
-
export type A2aFetch = (url: string, init: {
|
|
13
|
-
readonly method: 'GET' | 'POST';
|
|
14
|
-
readonly headers: Record<string, string>;
|
|
15
|
-
readonly body?: string;
|
|
16
|
-
readonly signal?: AbortSignal;
|
|
17
|
-
}) => Promise<{
|
|
18
|
-
readonly ok: boolean;
|
|
19
|
-
readonly status: number;
|
|
20
|
-
readonly text: () => Promise<string>;
|
|
21
|
-
}>;
|
|
22
|
-
/** Constructor facts; every seam is explicit so tests inject doubles. */
|
|
23
|
-
export interface A2aClientOptions {
|
|
24
|
-
/** `X-API-Key` value sent on peer requests; empty omits the header. */
|
|
25
|
-
readonly apiKey: string;
|
|
26
|
-
/** This node's caller label stamped on outbound routes. */
|
|
27
|
-
readonly sessionId: string;
|
|
28
|
-
/** Delayed-callback seam (HTTP timeouts). */
|
|
29
|
-
readonly schedule: A2aSchedule;
|
|
30
|
-
/** HTTP seam. */
|
|
31
|
-
readonly fetch: A2aFetch;
|
|
32
|
-
}
|
|
33
|
-
/**
|
|
34
|
-
* The A2A peer client. One instance serves the whole fiber's outbound
|
|
35
|
-
* card fetches and direct routes; it owns no connection state.
|
|
36
|
-
*/
|
|
37
|
-
export declare class A2aClient {
|
|
38
|
-
private readonly options;
|
|
39
|
-
/** @param options - resolved caller facts and injected seams. */
|
|
40
|
-
constructor(options: A2aClientOptions);
|
|
41
|
-
private headers;
|
|
42
|
-
private http;
|
|
43
|
-
/**
|
|
44
|
-
* Fetch one peer's agent card (discovery): team, session, capabilities.
|
|
45
|
-
* The card is verified at parse — unsigned, badly signed, or expired cards
|
|
46
|
-
* are rejected here, so a stale peer disappears from discovery without any
|
|
47
|
-
* background cleanup.
|
|
48
|
-
* @param baseUrl - the peer's base URL.
|
|
49
|
-
* @returns the verified card, or `undefined` when unreachable, malformed, or rejected.
|
|
50
|
-
*/
|
|
51
|
-
fetchCard(baseUrl: string): Promise<A2aPeerCard | undefined>;
|
|
52
|
-
/**
|
|
53
|
-
* Extract the reply text from a route result member: the wire carries a
|
|
54
|
-
* plain string, a `{text}` object, or anything else JSON-encoded.
|
|
55
|
-
* @param inner - the raw `result` member of a route response.
|
|
56
|
-
* @param fallback - what a nullish member encodes as.
|
|
57
|
-
* @returns the reply text.
|
|
58
|
-
*/
|
|
59
|
-
private replyText;
|
|
60
|
-
/**
|
|
61
|
-
* Route directly to a peer node's `/a2a/direct` endpoint. The peer steers
|
|
62
|
-
* the message into its live agent (or the named session team's agent) and
|
|
63
|
-
* answers within its own final-reply budget.
|
|
64
|
-
* @param baseUrl - the peer's base URL.
|
|
65
|
-
* @param team - target team name (the peer's own or a session team).
|
|
66
|
-
* @param message - request text.
|
|
67
|
-
* @param contextId - context id from a prior reply, for multi-turn.
|
|
68
|
-
* @param signal - caller cancellation.
|
|
69
|
-
* @param callerSession - caller label overriding the node's label (the calling session's node label).
|
|
70
|
-
* @returns the canonical route result (success or explicit failure).
|
|
71
|
-
*/
|
|
72
|
-
routeDirect(baseUrl: string, team: string, message: string, contextId?: string, signal?: AbortSignal, callerSession?: string): Promise<A2aRouteResult>;
|
|
73
|
-
}
|