@nelsonlongxiang/dsh-open-a2a-net 0.6.34 → 0.6.35
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 +1 -1
- package/README_zh.md +1 -1
- package/assets/nexusDist/assets/{index-D2BGWOeU.js → index-Du2E6DJL.js} +4 -4
- package/assets/nexusDist/index.html +1 -1
- package/cli/a2a.mjs +3 -1
- package/lib/canvas-store.js +23 -3
- package/lib/client.js +63 -63
- package/lib/faces/admin.js +3 -2
- package/lib/faces/wire.js +2 -24
- package/lib/flood-guard.js +7 -1
- package/lib/index.js +8 -2
- package/lib/response-fields.js +27 -0
- package/lib/skill/references/cli.md +6 -0
- package/lib/skill/references/pitfalls.md +4 -0
- package/lib/skill/references/routing.md +4 -0
- package/lib/types/canvas-store.d.ts +5 -0
- package/lib/types/flood-guard.d.ts +5 -0
- package/lib/types/response-fields.d.ts +3 -0
- package/nexus-stage/src/workbench.ts +6 -0
- package/package.json +1 -1
|
@@ -18,7 +18,7 @@ html,body{margin:0;height:100%;background:var(--n-bg);color:var(--n-ink);
|
|
|
18
18
|
font-family:"Segoe UI",system-ui,sans-serif;}
|
|
19
19
|
#app{width:100%;height:100%;position:relative}
|
|
20
20
|
</style>
|
|
21
|
-
<script type="module" crossorigin src="./assets/index-
|
|
21
|
+
<script type="module" crossorigin src="./assets/index-Du2E6DJL.js"></script>
|
|
22
22
|
<link rel="stylesheet" crossorigin href="./assets/index-z33lHtLw.css">
|
|
23
23
|
</head>
|
|
24
24
|
<body>
|
package/cli/a2a.mjs
CHANGED
|
@@ -959,13 +959,15 @@ async function main() {
|
|
|
959
959
|
const pending = s.tasks ?? []
|
|
960
960
|
const dead = s.tasksDead ?? []
|
|
961
961
|
const archived = s.archived ?? []
|
|
962
|
-
if (has('--json')) { console.log(JSON.stringify({ pending, dead, archived }, null, 2)); return }
|
|
962
|
+
if (has('--json')) { console.log(JSON.stringify({ pending, dead, archived, ...(s.settlement === undefined ? {} : { settlement: s.settlement }), ...(s.conservation === undefined ? {} : { conservation: s.conservation }) }, null, 2)); return }
|
|
963
963
|
console.log(`pending (${String(pending.length)}):`)
|
|
964
964
|
listCapped(pending, 20, t => ` ${String(t.taskId)} → ${String(t.team)} ${String(t.direction ?? '')}${senderTail(t)}`)
|
|
965
965
|
console.log(`dead (${String(dead.length)}):`)
|
|
966
966
|
listCapped(dead, 20, t => ` ${String(t.taskId)} → ${String(t.team)} (${String(t.reason ?? '?')})${senderTail(t)}`)
|
|
967
967
|
console.log(`archived (${String(archived.length)}):`)
|
|
968
968
|
listCapped(archived, 20, t => ` ${String(t.taskId)} ${String(t.outcome ?? '?')}${t.endedBy !== undefined && t.endedBy !== 'receipt' ? ` (${String(t.endedBy)})` : ''}${senderTail(t)}`)
|
|
969
|
+
if (s.conservation != null) console.log(`conservation: conserved=${String(s.conservation.conserved)} delta=${String(s.conservation.delta)}`)
|
|
970
|
+
if (s.settlement != null) console.log(`settlement: mode=${String(s.settlement.mode)} refusalsTotal=${String(s.settlement.refusalsTotal)} offChain=${String(s.settlement.offChain)}`)
|
|
969
971
|
return
|
|
970
972
|
}
|
|
971
973
|
if (cmd === 'status') {
|
package/lib/canvas-store.js
CHANGED
|
@@ -64,6 +64,7 @@ export class CanvasStore {
|
|
|
64
64
|
/** Team entries in creation order; members in add order (routing priority). */
|
|
65
65
|
teams = [];
|
|
66
66
|
cliHome = {};
|
|
67
|
+
cliHomeCleared = new Set();
|
|
67
68
|
path;
|
|
68
69
|
listeners = new Set();
|
|
69
70
|
/**
|
|
@@ -111,6 +112,11 @@ export class CanvasStore {
|
|
|
111
112
|
this.cliHome[handle] = home;
|
|
112
113
|
}
|
|
113
114
|
}
|
|
115
|
+
if (Array.isArray(snapshot.cliHomeCleared))
|
|
116
|
+
for (const handle of snapshot.cliHomeCleared) {
|
|
117
|
+
if (typeof handle === 'string' && CLI_HANDLE_RE.test(handle) && this.cliHome[handle] === undefined && this.teams.some(t => t.members.includes(handle)))
|
|
118
|
+
this.cliHomeCleared.add(handle);
|
|
119
|
+
}
|
|
114
120
|
}
|
|
115
121
|
catch {
|
|
116
122
|
// A corrupt store is not fatal: the user re-creates teams; routing
|
|
@@ -132,7 +138,7 @@ export class CanvasStore {
|
|
|
132
138
|
return;
|
|
133
139
|
try {
|
|
134
140
|
mkdirSync(dirname(this.path), { recursive: true });
|
|
135
|
-
const snapshot = { teams: this.teams.map(t => ({ name: t.name, members: [...t.members] })), ...(Object.keys(this.cliHome).length > 0 ? { cliHome: { ...this.cliHome } } : {}) };
|
|
141
|
+
const snapshot = { teams: this.teams.map(t => ({ name: t.name, members: [...t.members] })), ...(Object.keys(this.cliHome).length > 0 ? { cliHome: { ...this.cliHome } } : {}), ...(this.cliHomeCleared.size > 0 ? { cliHomeCleared: [...this.cliHomeCleared] } : {}) };
|
|
136
142
|
writeFileSync(this.path, JSON.stringify(snapshot), { mode: 0o600 });
|
|
137
143
|
}
|
|
138
144
|
catch {
|
|
@@ -156,6 +162,15 @@ export class CanvasStore {
|
|
|
156
162
|
/** Frozen, operator-linked CLI home; undefined means unknown or ambiguous. */
|
|
157
163
|
cliHomeOf(handle) { return this.cliHome[handle]; }
|
|
158
164
|
pinnedCliHomes() { return Object.entries(this.cliHome); }
|
|
165
|
+
wasCliHomeCleared(handle) { return this.cliHomeCleared.has(handle); }
|
|
166
|
+
clearCliHomeForLocal(handle) {
|
|
167
|
+
if (this.cliHome[handle] === undefined)
|
|
168
|
+
return false;
|
|
169
|
+
delete this.cliHome[handle];
|
|
170
|
+
this.cliHomeCleared.add(handle);
|
|
171
|
+
this.persist();
|
|
172
|
+
return true;
|
|
173
|
+
}
|
|
159
174
|
/**
|
|
160
175
|
* The named team's remote member refs (zone-qualified team handles) in
|
|
161
176
|
* routing-priority order — the failover tier after the local live/cold
|
|
@@ -218,6 +233,7 @@ export class CanvasStore {
|
|
|
218
233
|
if (entry.members.includes(id)) {
|
|
219
234
|
if (home !== undefined && this.cliHome[id] !== home) {
|
|
220
235
|
this.cliHome[id] = home;
|
|
236
|
+
this.cliHomeCleared.delete(id);
|
|
221
237
|
this.persist();
|
|
222
238
|
}
|
|
223
239
|
return true;
|
|
@@ -225,8 +241,10 @@ export class CanvasStore {
|
|
|
225
241
|
if (entry.members.length >= CANVAS_MEMBER_CAP)
|
|
226
242
|
return false;
|
|
227
243
|
entry.members.push(id);
|
|
228
|
-
if (home !== undefined)
|
|
244
|
+
if (home !== undefined) {
|
|
229
245
|
this.cliHome[id] = home;
|
|
246
|
+
this.cliHomeCleared.delete(id);
|
|
247
|
+
}
|
|
230
248
|
this.persist();
|
|
231
249
|
return true;
|
|
232
250
|
}
|
|
@@ -246,8 +264,10 @@ export class CanvasStore {
|
|
|
246
264
|
return true;
|
|
247
265
|
}
|
|
248
266
|
forgetUnusedHome(id) {
|
|
249
|
-
if (!this.teams.some(team => team.members.includes(id)))
|
|
267
|
+
if (!this.teams.some(team => team.members.includes(id))) {
|
|
250
268
|
delete this.cliHome[id];
|
|
269
|
+
this.cliHomeCleared.delete(id);
|
|
270
|
+
}
|
|
251
271
|
}
|
|
252
272
|
/**
|
|
253
273
|
* Drop one id from every team — the leave and archive paths: a session
|
package/lib/client.js
CHANGED
|
@@ -22,8 +22,8 @@ window.__ModuleLoader__.load({
|
|
|
22
22
|
return n;
|
|
23
23
|
}
|
|
24
24
|
//#endregion
|
|
25
|
-
//#region \0dsh-css:D:\workspace\dsh-open-a2a-net\tmp\release-0.6.
|
|
26
|
-
const css = ".
|
|
25
|
+
//#region \0dsh-css:D:\workspace\dsh-open-a2a-net\tmp\release-0.6.35-d5a66d9\src\src\client\A2aControl.module.css.mjs
|
|
26
|
+
const css = "._9zk42q_wrap{max-width:100%;display:inline-flex;position:relative}._9zk42q_trigger{align-items:center;gap:4px;display:inline-flex;position:relative}._9zk42q_unreadBadge{text-align:center;color:#fff;background:var(--dsh-accent,#4c8bf5);border-radius:7px;min-width:14px;height:14px;padding:0 3px;font-size:9px;line-height:14px;animation:2s ease-in-out infinite _9zk42q_badgePulse;position:absolute;top:-6px;right:-8px}@keyframes _9zk42q_badgePulse{0%,to{box-shadow:0 0 #4c8bf573}50%{box-shadow:0 0 0 4px #4c8bf500}}@media (prefers-reduced-motion:reduce){._9zk42q_unreadBadge{animation:none}}._9zk42q_popover{z-index:1100;border:1px solid var(--dsh-border,#2a2a30);background:var(--dsh-surface-raised,#1c1c22);border-radius:10px;flex-direction:column;gap:8px;width:320px;max-width:calc(100vw - 16px);max-height:60vh;padding:12px;display:flex;position:fixed;overflow-y:auto;box-shadow:0 8px 24px #00000059}._9zk42q_title{font-size:13px;font-weight:600}._9zk42q_sectionTitle{border-top:1px solid var(--dsh-border,#2a2a30);text-transform:uppercase;letter-spacing:.04em;opacity:.65;margin-top:4px;padding-top:8px;font-size:11px;font-weight:600}._9zk42q_stageRow{border-top:1px solid var(--dsh-border,#2a2a30);align-items:center;gap:8px;margin-top:4px;padding-top:8px;display:flex}._9zk42q_stageTitle{text-transform:uppercase;letter-spacing:.04em;opacity:.65;font-size:11px;font-weight:600}._9zk42q_stageLinks{gap:6px;display:flex}._9zk42q_stageLink{border:1px solid var(--dsh-border,#2a2a30);color:inherit;border-radius:999px;align-items:center;padding:3px 8px;font-size:11px;text-decoration:none;display:inline-flex}._9zk42q_stageLink:hover,._9zk42q_stageLink:focus-visible{border-color:var(--dsh-accent,#4c8bf5)}._9zk42q_empty{opacity:.7;font-size:12px}._9zk42q_row{border:1px solid #0000;border-radius:8px;align-items:center;gap:10px;padding:8px;display:flex}._9zk42q_joined{border-color:var(--dsh-border,#2a2a30)}._9zk42q_facts{text-align:left;min-width:0;color:inherit;font:inherit;cursor:pointer;background:0 0;border:none;border-radius:6px;flex-direction:column;flex:1;gap:2px;padding:2px 4px;display:flex}._9zk42q_facts:hover{background:var(--dsh-surface-hover,#ffffff0f)}._9zk42q_nameRow{align-items:center;gap:6px;display:flex}._9zk42q_stateDot{border-radius:50%;flex:none;width:7px;height:7px}._9zk42q_stateDot._9zk42q_live{background:var(--dsh-accent,#4c8bf5)}._9zk42q_stateDot._9zk42q_live._9zk42q_joined{background:var(--dsh-success,#3fb96f)}._9zk42q_stateDot._9zk42q_cold{background:var(--dsh-warning,#d9a13c)}._9zk42q_name{text-overflow:ellipsis;white-space:nowrap;font-size:13px;font-weight:500;overflow:hidden}._9zk42q_description{opacity:.7;text-overflow:ellipsis;white-space:nowrap;font-size:12px;overflow:hidden}._9zk42q_team{opacity:.55;font-size:11px;font-family:var(--dsh-mono,monospace)}._9zk42q_note{opacity:.6;font-size:11px}._9zk42q_searchInput{box-sizing:border-box;border:1px solid var(--dsh-border,#2a2a30);background:var(--dsh-surface,#141419);width:100%;color:inherit;border-radius:6px;padding:5px 8px;font-size:12px}._9zk42q_searchInput:focus{outline:1px solid var(--dsh-accent,#4c8bf5)}._9zk42q_sessionGroup{flex-direction:column;gap:2px;display:flex}._9zk42q_groupHead{color:inherit;font:inherit;opacity:.75;cursor:pointer;text-align:left;background:0 0;border:none;align-items:center;gap:6px;padding:4px 2px;font-size:11px;font-weight:600;display:flex}._9zk42q_groupChevron{transition:transform .12s;display:inline-block}._9zk42q_groupChevronClosed{transform:rotate(-90deg)}._9zk42q_groupTag{text-overflow:ellipsis;white-space:nowrap;border:1px solid var(--dsh-border,#2a2a30);opacity:.8;border-radius:999px;flex:none;max-width:90px;padding:0 6px;font-size:10px;line-height:16px;overflow:hidden}._9zk42q_groupButton{width:24px;height:24px;color:inherit;opacity:.55;cursor:pointer;background:0 0;border:1px solid #0000;border-radius:6px;flex:none;font-size:12px;line-height:1}._9zk42q_groupButton:hover{border-color:var(--dsh-border,#2a2a30);opacity:1}._9zk42q_groupPicker{border:1px dashed var(--dsh-border,#2a2a30);border-radius:8px;flex-wrap:wrap;gap:6px;width:100%;padding:8px;display:flex;position:relative}._9zk42q_groupOption{border:1px solid var(--dsh-border,#2a2a30);color:inherit;cursor:pointer;background:0 0;border-radius:999px;padding:2px 8px;font-size:11px}._9zk42q_groupOptionActive{border-color:var(--dsh-accent,#4c8bf5);color:var(--dsh-accent,#4c8bf5)}._9zk42q_groupNew{align-items:center;gap:6px;width:100%;margin-top:2px;display:flex}._9zk42q_groupInput{border:1px solid var(--dsh-border,#2a2a30);background:var(--dsh-surface,#141419);min-width:0;color:inherit;border-radius:6px;flex:1;padding:4px 8px;font-size:12px}._9zk42q_peerGrid{flex-wrap:wrap;gap:6px;display:flex}._9zk42q_peerChip{border:1px solid var(--dsh-border,#2a2a30);border-radius:999px;align-items:center;gap:6px;padding:3px 8px;font-size:11px;display:inline-flex}._9zk42q_peerHost{text-overflow:ellipsis;white-space:nowrap;max-width:160px;overflow:hidden}._9zk42q_peerScore{font-family:var(--dsh-mono,monospace);opacity:.6}._9zk42q_activityList{flex-direction:column;gap:2px;display:flex}._9zk42q_activityRow{border-radius:4px;align-items:center;gap:8px;padding:2px 4px;font-size:11px;display:flex}._9zk42q_activityRow[data-ok=false]{opacity:.55}._9zk42q_activityDir{flex:none;font-weight:600}._9zk42q_activityDir._9zk42q_in{color:var(--dsh-success,#3fb96f)}._9zk42q_activityDir._9zk42q_out{color:var(--dsh-accent,#4c8bf5)}._9zk42q_activityTeam{text-overflow:ellipsis;white-space:nowrap;min-width:0;font-family:var(--dsh-mono,monospace);opacity:.8;flex:1;overflow:hidden}._9zk42q_activityPeer{text-overflow:ellipsis;white-space:nowrap;opacity:.55;max-width:90px;overflow:hidden}._9zk42q_activityTime{opacity:.5;font-family:var(--dsh-mono,monospace);flex:none}._9zk42q_activityRow[data-jump=true]{cursor:pointer}._9zk42q_activityRow[data-jump=true]:hover{background:var(--dsh-surface-hover,#ffffff0f)}._9zk42q_fresh{animation:1.2s ease-out 3 _9zk42q_freshFlash}@keyframes _9zk42q_freshFlash{0%{background:#4c8bf538}to{background:0 0}}@media (prefers-reduced-motion:reduce){._9zk42q_fresh{background:#4c8bf51f;animation:none}}._9zk42q_inFlightList{flex-direction:column;gap:2px;margin-bottom:4px;display:flex}._9zk42q_inFlightRow{border:1px dashed var(--dsh-border,#2a2a30);border-radius:4px;align-items:center;gap:8px;padding:3px 4px;font-size:11px;display:flex}._9zk42q_inFlightPulse{background:var(--dsh-accent,#4c8bf5);border-radius:50%;flex:none;width:7px;height:7px;animation:1.1s ease-in-out infinite _9zk42q_flightPulse}@keyframes _9zk42q_flightPulse{0%,to{opacity:1;transform:scale(1)}50%{opacity:.5;transform:scale(.55)}}@media (prefers-reduced-motion:reduce){._9zk42q_inFlightPulse{animation:none}}._9zk42q_versionTag{color:var(--dsh-color-text-tertiary,#888);background:var(--dsh-color-bg-secondary,#8080801f);border-radius:999px;margin-left:auto;padding:1px 6px;font-size:10px;font-weight:500}._9zk42q_inFlightStale{opacity:.55}._9zk42q_canvasCreate{align-items:center;gap:6px;margin-bottom:4px;display:flex}._9zk42q_canvasCreate ._9zk42q_groupInput{flex:1;min-width:0}._9zk42q_canvasChip{max-width:100%;color:var(--dsh-color-text-secondary,#aaa);background:var(--dsh-color-bg-secondary,#8080801f);border-radius:999px;align-items:center;gap:4px;margin:2px 4px 2px 0;padding:1px 6px;font-size:11px;display:inline-flex}._9zk42q_canvasChip>span:not([class]){white-space:nowrap;text-overflow:ellipsis;min-width:0;overflow:hidden}._9zk42q_canvasChipRemove{cursor:pointer;min-width:24px;min-height:24px;color:var(--dsh-color-text-tertiary,#888);background:0 0;border:none;padding:2px 5px;font-size:11px;line-height:1}._9zk42q_canvasChipRemove:hover{color:var(--dsh-color-text-primary,#eee)}._9zk42q_canvasChipRemove{transition:color .14s ease-out}@media (prefers-reduced-motion:reduce){._9zk42q_canvasChipRemove{transition:none}}._9zk42q_canvasChipRemove:focus-visible{outline:2px solid var(--dsh-color-primary,#4f8cff);outline-offset:1px;border-radius:4px}._9zk42q_canvasActions{gap:6px;margin-top:4px;display:flex}._9zk42q_inFlightStale ._9zk42q_inFlightPulse{opacity:.4;animation:none}._9zk42q_failMark{color:#f87171;flex-shrink:0;font-size:11px}._9zk42q_staleNote{color:var(--dsh-text-muted,#94a3b8b3);padding:4px 8px;font-size:11px}";
|
|
27
27
|
const tagId = "@nelsonlongxiang/dsh-open-a2a-net/A2aControl.module.css";
|
|
28
28
|
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
|
|
29
29
|
const tag = document.createElement("style");
|
|
@@ -33,67 +33,67 @@ window.__ModuleLoader__.load({
|
|
|
33
33
|
document.head.appendChild(tag);
|
|
34
34
|
}
|
|
35
35
|
var A2aControl_module_css_default = {
|
|
36
|
-
"activityDir": "
|
|
37
|
-
"activityList": "
|
|
38
|
-
"activityPeer": "
|
|
39
|
-
"activityRow": "
|
|
40
|
-
"activityTeam": "
|
|
41
|
-
"activityTime": "
|
|
42
|
-
"badgePulse": "
|
|
43
|
-
"canvasActions": "
|
|
44
|
-
"canvasChip": "
|
|
45
|
-
"canvasChipRemove": "
|
|
46
|
-
"canvasCreate": "
|
|
47
|
-
"cold": "
|
|
48
|
-
"description": "
|
|
49
|
-
"empty": "
|
|
50
|
-
"facts": "
|
|
51
|
-
"failMark": "
|
|
52
|
-
"flightPulse": "
|
|
53
|
-
"fresh": "
|
|
54
|
-
"freshFlash": "
|
|
55
|
-
"groupButton": "
|
|
56
|
-
"groupChevron": "
|
|
57
|
-
"groupChevronClosed": "
|
|
58
|
-
"groupHead": "
|
|
59
|
-
"groupInput": "
|
|
60
|
-
"groupNew": "
|
|
61
|
-
"groupOption": "
|
|
62
|
-
"groupOptionActive": "
|
|
63
|
-
"groupPicker": "
|
|
64
|
-
"groupTag": "
|
|
65
|
-
"in": "
|
|
66
|
-
"inFlightList": "
|
|
67
|
-
"inFlightPulse": "
|
|
68
|
-
"inFlightRow": "
|
|
69
|
-
"inFlightStale": "
|
|
70
|
-
"joined": "
|
|
71
|
-
"live": "
|
|
72
|
-
"name": "
|
|
73
|
-
"nameRow": "
|
|
74
|
-
"note": "
|
|
75
|
-
"out": "
|
|
76
|
-
"peerChip": "
|
|
77
|
-
"peerGrid": "
|
|
78
|
-
"peerHost": "
|
|
79
|
-
"peerScore": "
|
|
80
|
-
"popover": "
|
|
81
|
-
"row": "
|
|
82
|
-
"searchInput": "
|
|
83
|
-
"sectionTitle": "
|
|
84
|
-
"sessionGroup": "
|
|
85
|
-
"stageLink": "
|
|
86
|
-
"stageLinks": "
|
|
87
|
-
"stageRow": "
|
|
88
|
-
"stageTitle": "
|
|
89
|
-
"staleNote": "
|
|
90
|
-
"stateDot": "
|
|
91
|
-
"team": "
|
|
92
|
-
"title": "
|
|
93
|
-
"trigger": "
|
|
94
|
-
"unreadBadge": "
|
|
95
|
-
"versionTag": "
|
|
96
|
-
"wrap": "
|
|
36
|
+
"activityDir": "_9zk42q_activityDir",
|
|
37
|
+
"activityList": "_9zk42q_activityList",
|
|
38
|
+
"activityPeer": "_9zk42q_activityPeer",
|
|
39
|
+
"activityRow": "_9zk42q_activityRow",
|
|
40
|
+
"activityTeam": "_9zk42q_activityTeam",
|
|
41
|
+
"activityTime": "_9zk42q_activityTime",
|
|
42
|
+
"badgePulse": "_9zk42q_badgePulse",
|
|
43
|
+
"canvasActions": "_9zk42q_canvasActions",
|
|
44
|
+
"canvasChip": "_9zk42q_canvasChip",
|
|
45
|
+
"canvasChipRemove": "_9zk42q_canvasChipRemove",
|
|
46
|
+
"canvasCreate": "_9zk42q_canvasCreate",
|
|
47
|
+
"cold": "_9zk42q_cold",
|
|
48
|
+
"description": "_9zk42q_description",
|
|
49
|
+
"empty": "_9zk42q_empty",
|
|
50
|
+
"facts": "_9zk42q_facts",
|
|
51
|
+
"failMark": "_9zk42q_failMark",
|
|
52
|
+
"flightPulse": "_9zk42q_flightPulse",
|
|
53
|
+
"fresh": "_9zk42q_fresh",
|
|
54
|
+
"freshFlash": "_9zk42q_freshFlash",
|
|
55
|
+
"groupButton": "_9zk42q_groupButton",
|
|
56
|
+
"groupChevron": "_9zk42q_groupChevron",
|
|
57
|
+
"groupChevronClosed": "_9zk42q_groupChevronClosed",
|
|
58
|
+
"groupHead": "_9zk42q_groupHead",
|
|
59
|
+
"groupInput": "_9zk42q_groupInput",
|
|
60
|
+
"groupNew": "_9zk42q_groupNew",
|
|
61
|
+
"groupOption": "_9zk42q_groupOption",
|
|
62
|
+
"groupOptionActive": "_9zk42q_groupOptionActive",
|
|
63
|
+
"groupPicker": "_9zk42q_groupPicker",
|
|
64
|
+
"groupTag": "_9zk42q_groupTag",
|
|
65
|
+
"in": "_9zk42q_in",
|
|
66
|
+
"inFlightList": "_9zk42q_inFlightList",
|
|
67
|
+
"inFlightPulse": "_9zk42q_inFlightPulse",
|
|
68
|
+
"inFlightRow": "_9zk42q_inFlightRow",
|
|
69
|
+
"inFlightStale": "_9zk42q_inFlightStale",
|
|
70
|
+
"joined": "_9zk42q_joined",
|
|
71
|
+
"live": "_9zk42q_live",
|
|
72
|
+
"name": "_9zk42q_name",
|
|
73
|
+
"nameRow": "_9zk42q_nameRow",
|
|
74
|
+
"note": "_9zk42q_note",
|
|
75
|
+
"out": "_9zk42q_out",
|
|
76
|
+
"peerChip": "_9zk42q_peerChip",
|
|
77
|
+
"peerGrid": "_9zk42q_peerGrid",
|
|
78
|
+
"peerHost": "_9zk42q_peerHost",
|
|
79
|
+
"peerScore": "_9zk42q_peerScore",
|
|
80
|
+
"popover": "_9zk42q_popover",
|
|
81
|
+
"row": "_9zk42q_row",
|
|
82
|
+
"searchInput": "_9zk42q_searchInput",
|
|
83
|
+
"sectionTitle": "_9zk42q_sectionTitle",
|
|
84
|
+
"sessionGroup": "_9zk42q_sessionGroup",
|
|
85
|
+
"stageLink": "_9zk42q_stageLink",
|
|
86
|
+
"stageLinks": "_9zk42q_stageLinks",
|
|
87
|
+
"stageRow": "_9zk42q_stageRow",
|
|
88
|
+
"stageTitle": "_9zk42q_stageTitle",
|
|
89
|
+
"staleNote": "_9zk42q_staleNote",
|
|
90
|
+
"stateDot": "_9zk42q_stateDot",
|
|
91
|
+
"team": "_9zk42q_team",
|
|
92
|
+
"title": "_9zk42q_title",
|
|
93
|
+
"trigger": "_9zk42q_trigger",
|
|
94
|
+
"unreadBadge": "_9zk42q_unreadBadge",
|
|
95
|
+
"versionTag": "_9zk42q_versionTag",
|
|
96
|
+
"wrap": "_9zk42q_wrap"
|
|
97
97
|
};
|
|
98
98
|
//#endregion
|
|
99
99
|
//#region lib/client/A2aControl.js
|
package/lib/faces/admin.js
CHANGED
|
@@ -152,7 +152,7 @@ export function registerAdminFaces(webServer, d) {
|
|
|
152
152
|
rows.push({ id: localId, team: `${config.team}/${id8(localId)}`, joined: sessionNodes.has(localId) || joinedSessions.has(localId), live: sessionNodes.has(localId) });
|
|
153
153
|
continue;
|
|
154
154
|
}
|
|
155
|
-
rows.push({ id, team: id, remote: true, ...(CLI_HANDLE_RE.test(id) ? { cliHome: canvasStore.cliHomeOf(id) ?? null, cliHeld: cliDeposits.directoryRows().some(row => row.team === id) } : {}) });
|
|
155
|
+
rows.push({ id, team: id, remote: true, ...(CLI_HANDLE_RE.test(id) ? { cliHome: canvasStore.cliHomeOf(id) ?? null, cliHeld: cliDeposits.directoryRows().some(row => row.team === id), ...(canvasStore.wasCliHomeCleared(id) ? { cliHomeCleared: true } : {}) } : {}) });
|
|
156
156
|
continue;
|
|
157
157
|
}
|
|
158
158
|
if (seenLocal.has(id))
|
|
@@ -469,8 +469,9 @@ export function registerAdminFaces(webServer, d) {
|
|
|
469
469
|
return '';
|
|
470
470
|
} })();
|
|
471
471
|
const out = name === config.team ? { ok: false, error: `a CLI identity may not be named after this host's zone "${config.team}" — choose another agent name` } : cliDeposits.deposit(card);
|
|
472
|
+
const homeCleared = out.ok && canvasStore.clearCliHomeForLocal(out.handle);
|
|
472
473
|
payload = JSON.stringify(out.ok
|
|
473
|
-
? { ok: true, handle: out.handle, expiresAt: out.expiresAt, cardUrl: `${d.ownCardUrl}/a2a/cli/${encodeURIComponent(out.handle)}` }
|
|
474
|
+
? { ok: true, handle: out.handle, expiresAt: out.expiresAt, ...(homeCleared ? { homeCleared: true } : {}), cardUrl: `${d.ownCardUrl}/a2a/cli/${encodeURIComponent(out.handle)}` }
|
|
474
475
|
: { ok: false, error: out.error });
|
|
475
476
|
}
|
|
476
477
|
else if (action === 'revoke') {
|
package/lib/faces/wire.js
CHANGED
|
@@ -7,6 +7,7 @@ import { briefOf } from "../task-ledger.js";
|
|
|
7
7
|
import { parseReceipt, taskIdRefusal } from "../receipt.js";
|
|
8
8
|
import { buildAuthorization, claimCovers, refusalOf } from "../task-auth.js";
|
|
9
9
|
import { isRootOriginUrl } from "../origin-claim.js";
|
|
10
|
+
import { withResponseFields } from "../response-fields.js";
|
|
10
11
|
import { WIRE_ERROR_PAYLOAD_TOO_LARGE, withinRouteBodyCap } from "../transport-caps.js";
|
|
11
12
|
/**
|
|
12
13
|
* #139 (design §4.4): add `settlement: {status:'refused', reason, task_id}`
|
|
@@ -16,30 +17,7 @@ import { WIRE_ERROR_PAYLOAD_TOO_LARGE, withinRouteBodyCap } from "../transport-c
|
|
|
16
17
|
* body is sent unchanged.
|
|
17
18
|
*/
|
|
18
19
|
export function withSettlementField(res, settlement) {
|
|
19
|
-
|
|
20
|
-
const end = res.end.bind(res);
|
|
21
|
-
let head;
|
|
22
|
-
res.writeHead = (status, headers) => {
|
|
23
|
-
head = { status, headers: { ...(headers ?? {}) } };
|
|
24
|
-
return res;
|
|
25
|
-
};
|
|
26
|
-
res.end = (body) => {
|
|
27
|
-
let out = body;
|
|
28
|
-
if (typeof body === 'string') {
|
|
29
|
-
try {
|
|
30
|
-
const parsed = JSON.parse(body);
|
|
31
|
-
if (parsed !== null && typeof parsed === 'object' && !Array.isArray(parsed))
|
|
32
|
-
out = JSON.stringify({ ...parsed, settlement });
|
|
33
|
-
}
|
|
34
|
-
catch { /* not JSON: unchanged */ }
|
|
35
|
-
}
|
|
36
|
-
if (head !== undefined) {
|
|
37
|
-
if (out !== undefined && 'Content-Length' in head.headers)
|
|
38
|
-
head.headers['Content-Length'] = Buffer.byteLength(out);
|
|
39
|
-
writeHead(head.status, head.headers);
|
|
40
|
-
}
|
|
41
|
-
return end(out);
|
|
42
|
-
};
|
|
20
|
+
withResponseFields(res, { settlement });
|
|
43
21
|
}
|
|
44
22
|
/** The origin of a card URL for the authorization record; the raw text, bounded, when it does not parse. */
|
|
45
23
|
const originOf = (url) => {
|
package/lib/flood-guard.js
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { withResponseFields } from "./response-fields.js";
|
|
2
|
+
export const FILE_CHANNEL_ADVICE = { reason: 'use-file-channel', message: 'Files must use the file channel, not session messages: a2a_file_send { paths, team }; CLI: a2a-net file send <path…> <team>.' };
|
|
3
|
+
export const fileChannelAdvice = (message) => message.includes('[JPFILE-PART') ? FILE_CHANNEL_ADVICE : undefined;
|
|
1
4
|
export const WIRE_ERROR_RATE_LIMITED = -32008;
|
|
2
5
|
export const MAX_PARTS_PER_TRANSFER = 16;
|
|
3
6
|
// Matches the current L9 threshold; per-sender across ALL targets is stricter.
|
|
@@ -92,10 +95,13 @@ export class FloodGuard {
|
|
|
92
95
|
export function createFloodGuard(outcomes, recordActivity) {
|
|
93
96
|
const guard = new FloodGuard();
|
|
94
97
|
return (caller, message, team, taskId, res) => {
|
|
98
|
+
const advice = fileChannelAdvice(message);
|
|
99
|
+
if (advice !== undefined)
|
|
100
|
+
withResponseFields(res, { advice });
|
|
95
101
|
const reason = guard.check(caller, message);
|
|
96
102
|
if (reason === undefined)
|
|
97
103
|
return false;
|
|
98
|
-
const error = 'flood-guard: ' + reason + ' (gh#91): file data must use an independent channel; no delivery occurred';
|
|
104
|
+
const error = 'flood-guard: ' + reason + ' (gh#91): file data must use an independent channel; no delivery occurred' + (advice === undefined ? '' : '; use-file-channel: ' + advice.message);
|
|
99
105
|
outcomes.recordOutcome(taskId, { status: 'failed', error });
|
|
100
106
|
recordActivity('in', team, caller, false);
|
|
101
107
|
const payload = JSON.stringify({ error, reason, code: WIRE_ERROR_RATE_LIMITED, task_id: taskId, team, delivered: false, task_status: 'TASK_STATE_FAILED' });
|
package/lib/index.js
CHANGED
|
@@ -762,7 +762,10 @@ export function apply(ctx, config) {
|
|
|
762
762
|
// message that still echoes a pending task id means the peer
|
|
763
763
|
// demonstrably responded to it, which settles the debt even when the
|
|
764
764
|
// receipt formality never rode the reply.
|
|
765
|
-
|
|
765
|
+
// #125 (owner data 2026-11-03: two production rows settled by a relay and by another task's receipt):
|
|
766
|
+
// an echo is weak evidence, so under observe it is judged as enforce — only an on-chain settler's echo
|
|
767
|
+
// settles. Formal receipt headers keep observing; settlementAuth 'off' still judges nothing.
|
|
768
|
+
const echoed = taskLedger.resolveEcho(message, judge !== undefined && !judge.enforce ? { ...judge, enforce: true } : judge);
|
|
766
769
|
if (echoed.length > 0) {
|
|
767
770
|
logger.info(`a2a: ${String(echoed.length)} pending task(s) settled by content echo (${echoed.join(', ')})`);
|
|
768
771
|
}
|
|
@@ -3814,7 +3817,8 @@ export function apply(ctx, config) {
|
|
|
3814
3817
|
async function dispatchAnywhere(team, message, callerSession, contextId, signal, asyncMode, taskId, peerCaller) {
|
|
3815
3818
|
// A local online household is an owner-established fact and receives locally.
|
|
3816
3819
|
// Otherwise only the pinned home may receive; never fall back to directory claims.
|
|
3817
|
-
|
|
3820
|
+
const cliReceipt = CLI_HANDLE_RE.test(team) && parseReceipt(message) !== null;
|
|
3821
|
+
if (cliReceipt && cliDeposits.get(team) === undefined) {
|
|
3818
3822
|
const home = canvasStore.cliHomeOf(team);
|
|
3819
3823
|
if (home === undefined)
|
|
3820
3824
|
throw new Error(`CLI receipt home is not pinned for ${team}`);
|
|
@@ -3832,6 +3836,8 @@ export function apply(ctx, config) {
|
|
|
3832
3836
|
throw new Error(local.error);
|
|
3833
3837
|
return local;
|
|
3834
3838
|
}
|
|
3839
|
+
if (cliReceipt)
|
|
3840
|
+
throw new Error(`local CLI household expired before delivery to ${team}; directory fallback refused`);
|
|
3835
3841
|
const failures = [];
|
|
3836
3842
|
const candidates = await directoryPeerCandidates(memoizedCardFetch(), team, failures);
|
|
3837
3843
|
if (candidates.length === 0) {
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/** Add structured fields to any JSON response exit and recompute its Content-Length. */
|
|
2
|
+
export function withResponseFields(res, fields) {
|
|
3
|
+
const writeHead = res.writeHead.bind(res);
|
|
4
|
+
const end = res.end.bind(res);
|
|
5
|
+
let head;
|
|
6
|
+
res.writeHead = (status, headers) => {
|
|
7
|
+
head = { status, headers: { ...(headers ?? {}) } };
|
|
8
|
+
return res;
|
|
9
|
+
};
|
|
10
|
+
res.end = (body) => {
|
|
11
|
+
let out = body;
|
|
12
|
+
if (typeof body === 'string') {
|
|
13
|
+
try {
|
|
14
|
+
const parsed = JSON.parse(body);
|
|
15
|
+
if (parsed !== null && typeof parsed === 'object' && !Array.isArray(parsed))
|
|
16
|
+
out = JSON.stringify({ ...parsed, ...fields });
|
|
17
|
+
}
|
|
18
|
+
catch { /* not JSON: unchanged */ }
|
|
19
|
+
}
|
|
20
|
+
if (head !== undefined) {
|
|
21
|
+
if (out !== undefined && 'Content-Length' in head.headers)
|
|
22
|
+
head.headers['Content-Length'] = Buffer.byteLength(out);
|
|
23
|
+
writeHead(head.status, head.headers);
|
|
24
|
+
}
|
|
25
|
+
return end(out);
|
|
26
|
+
};
|
|
27
|
+
}
|
|
@@ -42,8 +42,14 @@ model tools.
|
|
|
42
42
|
405 with an empty body. The fix is a host upgrade, not a retry.
|
|
43
43
|
- `a2a-net revoke` deregisters early.
|
|
44
44
|
|
|
45
|
+
## Tasks: ledger and host observability
|
|
46
|
+
|
|
47
|
+
`a2a-net tasks --json` returns the existing `pending`, `dead`, and `archived` arrays plus `settlement` and `conservation` copied unchanged from `/__dsh_a2a/state`. These blocks include the lifetime `settlement.refusalsTotal`, refusal ring and mode, and the host conservation verdict/counters/duplicate ids. Older hosts omit a block they do not expose; the CLI does not manufacture zero counters or a passing verdict. Plain `tasks` appends a conservation line (`conserved`, `delta`) and a settlement line (`mode`, `refusalsTotal`, `offChain`) when the respective block exists.
|
|
48
|
+
|
|
45
49
|
## Files (#91 file channel)
|
|
46
50
|
|
|
51
|
+
Files do not go through session messages: use the file channel below, never JPFILE-PART/base64 chunks in `route`. Legacy part replies carry `advice.reason: use-file-channel` with the send commands; up to 16 compatible parts are still allowed, while over-limit transfers keep `-32008` / `file-parts-exceeded`. The hint alone does not mean rejection.
|
|
52
|
+
|
|
47
53
|
- `a2a-net file send <path…> <team> [--note TEXT]` stages each file (≤64 MiB,
|
|
48
54
|
≤16 per send) on the home host by sha256, granted to `<team>` for 24h, and
|
|
49
55
|
routes ONE message with one `[A2A file] v1 sha256=… size=… name=… from=<home>`
|
|
@@ -9,6 +9,10 @@
|
|
|
9
9
|
- **adopt-style revive only works for previously materialized sessions** —
|
|
10
10
|
it is an idempotent revive, not a creation path.
|
|
11
11
|
|
|
12
|
+
## Files
|
|
13
|
+
|
|
14
|
+
- **Files do not go through session messages.** Use `a2a_file_send { paths, team }` (up to 16 files, 64 MiB each), then `a2a_file_fetch { ref }` on the received `[A2A file] v1 …` reference. CLI: `a2a-net file send <path…> <team>` and `a2a-net file fetch "<reference line>"`. Do not base64/chunk files into JPFILE-PART messages. During compatibility rollout, accepted legacy parts carry `advice.reason: use-file-channel`; over-limit parts still fail with `-32008` / `file-parts-exceeded` and the same advice. Do not resend rejected parts in a loop.
|
|
15
|
+
|
|
12
16
|
## Routing
|
|
13
17
|
|
|
14
18
|
- **Do not blindly re-dispatch after a sync timeout.** -32005 means the
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# Routing deep-dive
|
|
2
2
|
|
|
3
|
+
## Files use the file channel
|
|
4
|
+
|
|
5
|
+
Files do not go through session messages. Send with `a2a_file_send { paths, team }` (up to 16 files, 64 MiB each), not `a2a_route` with file bytes or JPFILE-PART chunks. The receiver uses `a2a_file_fetch { ref }` with the `[A2A file] v1 …` line. CLI: `a2a-net file send <path…> <team>` / `a2a-net file fetch "<reference line>"`. One reference message carries the transfer; ordinary routing and receipt rules below apply to that message. Legacy parts still allowed by the transitional cap receive `advice.reason: use-file-channel`; this hint is not a refusal or a completion receipt.
|
|
6
|
+
|
|
3
7
|
## The receipt contract
|
|
4
8
|
|
|
5
9
|
Every async route (`wait:false`) registers the task on the peer and returns
|
|
@@ -24,6 +24,8 @@ export interface CanvasSnapshot {
|
|
|
24
24
|
}>;
|
|
25
25
|
/** Pinned CLI handle -> linked home origin (only while some canvas team holds the handle). */
|
|
26
26
|
readonly cliHome?: Readonly<Record<string, string>>;
|
|
27
|
+
/** Local publish cleared an older pin; kept for the owner inspector until membership is removed or re-pinned. */
|
|
28
|
+
readonly cliHomeCleared?: readonly string[];
|
|
27
29
|
}
|
|
28
30
|
/** Upper bounds: a canvas taxonomy, not a database. */
|
|
29
31
|
export declare const CANVAS_TEAM_CAP = 64;
|
|
@@ -53,6 +55,7 @@ export declare class CanvasStore {
|
|
|
53
55
|
/** Team entries in creation order; members in add order (routing priority). */
|
|
54
56
|
private teams;
|
|
55
57
|
private cliHome;
|
|
58
|
+
private cliHomeCleared;
|
|
56
59
|
private readonly path;
|
|
57
60
|
private readonly listeners;
|
|
58
61
|
/**
|
|
@@ -80,6 +83,8 @@ export declare class CanvasStore {
|
|
|
80
83
|
/** Frozen, operator-linked CLI home; undefined means unknown or ambiguous. */
|
|
81
84
|
cliHomeOf(handle: string): string | undefined;
|
|
82
85
|
pinnedCliHomes(): ReadonlyArray<readonly [string, string]>;
|
|
86
|
+
wasCliHomeCleared(handle: string): boolean;
|
|
87
|
+
clearCliHomeForLocal(handle: string): boolean;
|
|
83
88
|
/**
|
|
84
89
|
* The named team's remote member refs (zone-qualified team handles) in
|
|
85
90
|
* routing-priority order — the failover tier after the local live/cold
|
|
@@ -6,6 +6,11 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import type { ServerResponse } from 'node:http';
|
|
8
8
|
import type { IdempotencyStore } from './idempotency-store.ts';
|
|
9
|
+
export declare const FILE_CHANNEL_ADVICE: {
|
|
10
|
+
readonly reason: "use-file-channel";
|
|
11
|
+
readonly message: "Files must use the file channel, not session messages: a2a_file_send { paths, team }; CLI: a2a-net file send <path…> <team>.";
|
|
12
|
+
};
|
|
13
|
+
export declare const fileChannelAdvice: (message: string) => typeof FILE_CHANNEL_ADVICE | undefined;
|
|
9
14
|
export declare const WIRE_ERROR_RATE_LIMITED = -32008;
|
|
10
15
|
export declare const MAX_PARTS_PER_TRANSFER = 16;
|
|
11
16
|
export declare const DEFAULT_RATE_PER_MINUTE = 16;
|
|
@@ -134,6 +134,7 @@ export interface CanvasMemberRow {
|
|
|
134
134
|
/** Present only for CLI refs: null means the linked home is ambiguous or unknown. */
|
|
135
135
|
readonly cliHome?: string | null
|
|
136
136
|
readonly cliHeld?: boolean
|
|
137
|
+
readonly cliHomeCleared?: boolean
|
|
137
138
|
}
|
|
138
139
|
|
|
139
140
|
/** One canvas team from /state `canvas.teams`. */
|
|
@@ -984,6 +985,11 @@ export function createWorkbench(deps: WorkbenchDeps): Workbench {
|
|
|
984
985
|
})
|
|
985
986
|
row.appendChild(remove)
|
|
986
987
|
card.appendChild(row)
|
|
988
|
+
if (member.cliHomeCleared === true) {
|
|
989
|
+
const notice = el('div', 'wb-hint')
|
|
990
|
+
notice.textContent = '本机落户已清除旧 home 钉;户口离线后也不会恢复旧钉。'
|
|
991
|
+
card.appendChild(notice)
|
|
992
|
+
}
|
|
987
993
|
if (member.cliHome === null && member.cliHeld !== true && deps.cliHomeOptions !== undefined) {
|
|
988
994
|
const key = JSON.stringify([fresh.name, member.id])
|
|
989
995
|
let form = cliHomeForms.get(key)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nelsonlongxiang/dsh-open-a2a-net",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.35",
|
|
4
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
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|