@nelsonlongxiang/dsh-open-a2a-net 0.6.35 → 0.6.36

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/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.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}";
25
+ //#region \0dsh-css:D:\workspace\dsh-open-a2a-net\tmp\release-0.6.36-51b18cc\src\src\client\A2aControl.module.css.mjs
26
+ const css = ".KfHpVG_wrap{max-width:100%;display:inline-flex;position:relative}.KfHpVG_trigger{align-items:center;gap:4px;display:inline-flex;position:relative}.KfHpVG_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 KfHpVG_badgePulse;position:absolute;top:-6px;right:-8px}@keyframes KfHpVG_badgePulse{0%,to{box-shadow:0 0 #4c8bf573}50%{box-shadow:0 0 0 4px #4c8bf500}}@media (prefers-reduced-motion:reduce){.KfHpVG_unreadBadge{animation:none}}.KfHpVG_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}.KfHpVG_title{font-size:13px;font-weight:600}.KfHpVG_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}.KfHpVG_stageRow{border-top:1px solid var(--dsh-border,#2a2a30);align-items:center;gap:8px;margin-top:4px;padding-top:8px;display:flex}.KfHpVG_stageTitle{text-transform:uppercase;letter-spacing:.04em;opacity:.65;font-size:11px;font-weight:600}.KfHpVG_stageLinks{gap:6px;display:flex}.KfHpVG_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}.KfHpVG_stageLink:hover,.KfHpVG_stageLink:focus-visible{border-color:var(--dsh-accent,#4c8bf5)}.KfHpVG_empty{opacity:.7;font-size:12px}.KfHpVG_row{border:1px solid #0000;border-radius:8px;align-items:center;gap:10px;padding:8px;display:flex}.KfHpVG_joined{border-color:var(--dsh-border,#2a2a30)}.KfHpVG_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}.KfHpVG_facts:hover{background:var(--dsh-surface-hover,#ffffff0f)}.KfHpVG_nameRow{align-items:center;gap:6px;display:flex}.KfHpVG_stateDot{border-radius:50%;flex:none;width:7px;height:7px}.KfHpVG_stateDot.KfHpVG_live{background:var(--dsh-accent,#4c8bf5)}.KfHpVG_stateDot.KfHpVG_live.KfHpVG_joined{background:var(--dsh-success,#3fb96f)}.KfHpVG_stateDot.KfHpVG_cold{background:var(--dsh-warning,#d9a13c)}.KfHpVG_name{text-overflow:ellipsis;white-space:nowrap;font-size:13px;font-weight:500;overflow:hidden}.KfHpVG_description{opacity:.7;text-overflow:ellipsis;white-space:nowrap;font-size:12px;overflow:hidden}.KfHpVG_team{opacity:.55;font-size:11px;font-family:var(--dsh-mono,monospace)}.KfHpVG_note{opacity:.6;font-size:11px}.KfHpVG_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}.KfHpVG_searchInput:focus{outline:1px solid var(--dsh-accent,#4c8bf5)}.KfHpVG_sessionGroup{flex-direction:column;gap:2px;display:flex}.KfHpVG_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}.KfHpVG_groupChevron{transition:transform .12s;display:inline-block}.KfHpVG_groupChevronClosed{transform:rotate(-90deg)}.KfHpVG_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}.KfHpVG_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}.KfHpVG_groupButton:hover{border-color:var(--dsh-border,#2a2a30);opacity:1}.KfHpVG_groupPicker{border:1px dashed var(--dsh-border,#2a2a30);border-radius:8px;flex-wrap:wrap;gap:6px;width:100%;padding:8px;display:flex;position:relative}.KfHpVG_groupOption{border:1px solid var(--dsh-border,#2a2a30);color:inherit;cursor:pointer;background:0 0;border-radius:999px;padding:2px 8px;font-size:11px}.KfHpVG_groupOptionActive{border-color:var(--dsh-accent,#4c8bf5);color:var(--dsh-accent,#4c8bf5)}.KfHpVG_groupNew{align-items:center;gap:6px;width:100%;margin-top:2px;display:flex}.KfHpVG_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}.KfHpVG_peerGrid{flex-wrap:wrap;gap:6px;display:flex}.KfHpVG_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}.KfHpVG_peerHost{text-overflow:ellipsis;white-space:nowrap;max-width:160px;overflow:hidden}.KfHpVG_peerScore{font-family:var(--dsh-mono,monospace);opacity:.6}.KfHpVG_activityList{flex-direction:column;gap:2px;display:flex}.KfHpVG_activityRow{border-radius:4px;align-items:center;gap:8px;padding:2px 4px;font-size:11px;display:flex}.KfHpVG_activityRow[data-ok=false]{opacity:.55}.KfHpVG_activityDir{flex:none;font-weight:600}.KfHpVG_activityDir.KfHpVG_in{color:var(--dsh-success,#3fb96f)}.KfHpVG_activityDir.KfHpVG_out{color:var(--dsh-accent,#4c8bf5)}.KfHpVG_activityTeam{text-overflow:ellipsis;white-space:nowrap;min-width:0;font-family:var(--dsh-mono,monospace);opacity:.8;flex:1;overflow:hidden}.KfHpVG_activityPeer{text-overflow:ellipsis;white-space:nowrap;opacity:.55;max-width:90px;overflow:hidden}.KfHpVG_activityTime{opacity:.5;font-family:var(--dsh-mono,monospace);flex:none}.KfHpVG_activityRow[data-jump=true]{cursor:pointer}.KfHpVG_activityRow[data-jump=true]:hover{background:var(--dsh-surface-hover,#ffffff0f)}.KfHpVG_fresh{animation:1.2s ease-out 3 KfHpVG_freshFlash}@keyframes KfHpVG_freshFlash{0%{background:#4c8bf538}to{background:0 0}}@media (prefers-reduced-motion:reduce){.KfHpVG_fresh{background:#4c8bf51f;animation:none}}.KfHpVG_inFlightList{flex-direction:column;gap:2px;margin-bottom:4px;display:flex}.KfHpVG_inFlightRow{border:1px dashed var(--dsh-border,#2a2a30);border-radius:4px;align-items:center;gap:8px;padding:3px 4px;font-size:11px;display:flex}.KfHpVG_inFlightPulse{background:var(--dsh-accent,#4c8bf5);border-radius:50%;flex:none;width:7px;height:7px;animation:1.1s ease-in-out infinite KfHpVG_flightPulse}@keyframes KfHpVG_flightPulse{0%,to{opacity:1;transform:scale(1)}50%{opacity:.5;transform:scale(.55)}}@media (prefers-reduced-motion:reduce){.KfHpVG_inFlightPulse{animation:none}}.KfHpVG_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}.KfHpVG_inFlightStale{opacity:.55}.KfHpVG_canvasCreate{align-items:center;gap:6px;margin-bottom:4px;display:flex}.KfHpVG_canvasCreate .KfHpVG_groupInput{flex:1;min-width:0}.KfHpVG_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}.KfHpVG_canvasChip>span:not([class]){white-space:nowrap;text-overflow:ellipsis;min-width:0;overflow:hidden}.KfHpVG_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}.KfHpVG_canvasChipRemove:hover{color:var(--dsh-color-text-primary,#eee)}.KfHpVG_canvasChipRemove{transition:color .14s ease-out}@media (prefers-reduced-motion:reduce){.KfHpVG_canvasChipRemove{transition:none}}.KfHpVG_canvasChipRemove:focus-visible{outline:2px solid var(--dsh-color-primary,#4f8cff);outline-offset:1px;border-radius:4px}.KfHpVG_canvasActions{gap:6px;margin-top:4px;display:flex}.KfHpVG_inFlightStale .KfHpVG_inFlightPulse{opacity:.4;animation:none}.KfHpVG_failMark{color:#f87171;flex-shrink:0;font-size:11px}.KfHpVG_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": "_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"
36
+ "activityDir": "KfHpVG_activityDir",
37
+ "activityList": "KfHpVG_activityList",
38
+ "activityPeer": "KfHpVG_activityPeer",
39
+ "activityRow": "KfHpVG_activityRow",
40
+ "activityTeam": "KfHpVG_activityTeam",
41
+ "activityTime": "KfHpVG_activityTime",
42
+ "badgePulse": "KfHpVG_badgePulse",
43
+ "canvasActions": "KfHpVG_canvasActions",
44
+ "canvasChip": "KfHpVG_canvasChip",
45
+ "canvasChipRemove": "KfHpVG_canvasChipRemove",
46
+ "canvasCreate": "KfHpVG_canvasCreate",
47
+ "cold": "KfHpVG_cold",
48
+ "description": "KfHpVG_description",
49
+ "empty": "KfHpVG_empty",
50
+ "facts": "KfHpVG_facts",
51
+ "failMark": "KfHpVG_failMark",
52
+ "flightPulse": "KfHpVG_flightPulse",
53
+ "fresh": "KfHpVG_fresh",
54
+ "freshFlash": "KfHpVG_freshFlash",
55
+ "groupButton": "KfHpVG_groupButton",
56
+ "groupChevron": "KfHpVG_groupChevron",
57
+ "groupChevronClosed": "KfHpVG_groupChevronClosed",
58
+ "groupHead": "KfHpVG_groupHead",
59
+ "groupInput": "KfHpVG_groupInput",
60
+ "groupNew": "KfHpVG_groupNew",
61
+ "groupOption": "KfHpVG_groupOption",
62
+ "groupOptionActive": "KfHpVG_groupOptionActive",
63
+ "groupPicker": "KfHpVG_groupPicker",
64
+ "groupTag": "KfHpVG_groupTag",
65
+ "in": "KfHpVG_in",
66
+ "inFlightList": "KfHpVG_inFlightList",
67
+ "inFlightPulse": "KfHpVG_inFlightPulse",
68
+ "inFlightRow": "KfHpVG_inFlightRow",
69
+ "inFlightStale": "KfHpVG_inFlightStale",
70
+ "joined": "KfHpVG_joined",
71
+ "live": "KfHpVG_live",
72
+ "name": "KfHpVG_name",
73
+ "nameRow": "KfHpVG_nameRow",
74
+ "note": "KfHpVG_note",
75
+ "out": "KfHpVG_out",
76
+ "peerChip": "KfHpVG_peerChip",
77
+ "peerGrid": "KfHpVG_peerGrid",
78
+ "peerHost": "KfHpVG_peerHost",
79
+ "peerScore": "KfHpVG_peerScore",
80
+ "popover": "KfHpVG_popover",
81
+ "row": "KfHpVG_row",
82
+ "searchInput": "KfHpVG_searchInput",
83
+ "sectionTitle": "KfHpVG_sectionTitle",
84
+ "sessionGroup": "KfHpVG_sessionGroup",
85
+ "stageLink": "KfHpVG_stageLink",
86
+ "stageLinks": "KfHpVG_stageLinks",
87
+ "stageRow": "KfHpVG_stageRow",
88
+ "stageTitle": "KfHpVG_stageTitle",
89
+ "staleNote": "KfHpVG_staleNote",
90
+ "stateDot": "KfHpVG_stateDot",
91
+ "team": "KfHpVG_team",
92
+ "title": "KfHpVG_title",
93
+ "trigger": "KfHpVG_trigger",
94
+ "unreadBadge": "KfHpVG_unreadBadge",
95
+ "versionTag": "KfHpVG_versionTag",
96
+ "wrap": "KfHpVG_wrap"
97
97
  };
98
98
  //#endregion
99
99
  //#region lib/client/A2aControl.js
@@ -202,6 +202,7 @@ export function registerAdminFaces(webServer, d) {
202
202
  .map(task => ({
203
203
  taskId: task.taskId, team: task.team, peer: task.peer, startedAt: task.startedAt, status: task.status,
204
204
  direction: task.direction ?? 'outbound', receiptExpected: task.receiptExpected !== false,
205
+ ...(task.answeredAt !== undefined ? { answeredAt: task.answeredAt } : {}),
205
206
  // The dispatch brief rides every row: a task id and a team
206
207
  // say nothing about WHAT is owed — supervision cannot tell
207
208
  // a nudge from an abandon without it.
@@ -220,7 +221,7 @@ export function registerAdminFaces(webServer, d) {
220
221
  tasksStaleCount: taskLedger.list().filter(task => task.status === 'pending' && (task.receiptExpected === false || Date.now() - task.startedAt >= 60 * 60_000)).length,
221
222
  tasksDead: taskLedger.list()
222
223
  .filter(task => task.status === 'dead')
223
- .map(task => ({ taskId: task.taskId, team: task.team, peer: task.peer, startedAt: task.startedAt, deadAt: task.deadAt ?? task.startedAt, reason: task.reason ?? 'unspecified', ...(task.brief !== undefined ? { brief: task.brief } : {}), ...(task.direction !== undefined ? { direction: task.direction } : {}), ...(task.sender !== undefined ? { sender: task.sender } : {}), ...(task.auth !== undefined ? { auth: task.auth } : task.direction === 'inbound' ? { authLegacy: true } : {}) })),
224
+ .map(task => ({ taskId: task.taskId, team: task.team, peer: task.peer, startedAt: task.startedAt, deadAt: task.deadAt ?? task.startedAt, reason: task.reason ?? 'unspecified', ...(task.answeredAt !== undefined ? { answeredAt: task.answeredAt } : {}), ...(task.brief !== undefined ? { brief: task.brief } : {}), ...(task.direction !== undefined ? { direction: task.direction } : {}), ...(task.sender !== undefined ? { sender: task.sender } : {}), ...(task.auth !== undefined ? { auth: task.auth } : task.direction === 'inbound' ? { authLegacy: true } : {}) })),
224
225
  // gh#31: per-task outcomes for rows that LEFT the ledger — the
225
226
  // chat drawer used to guess (settled, abandoned or cancelled).
226
227
  // Bounded to the archive (≤64), newest first, with the
@@ -243,6 +244,7 @@ export function registerAdminFaces(webServer, d) {
243
244
  peer: row.peer,
244
245
  startedAt: row.startedAt,
245
246
  resolvedAt: row.resolvedAt,
247
+ ...(row.answeredAt !== undefined ? { answeredAt: row.answeredAt } : {}),
246
248
  outcome: row.outcome ?? 'unclassified',
247
249
  endedBy: archivedEndedBy(row),
248
250
  ...(row.direction !== undefined ? { direction: row.direction } : {}),
package/lib/index.js CHANGED
@@ -2025,6 +2025,7 @@ export function apply(ctx, config) {
2025
2025
  // placeholder as one on the same-node loop-back.
2026
2026
  if (placeholder === true)
2027
2027
  return;
2028
+ taskLedger.markAnswered(taskId, sessionTeamOf(target));
2028
2029
  // Idempotency: a row THIS ledger tracks must still be pending
2029
2030
  // (settled/dead rows never re-send). A foreign task id (an inbound
2030
2031
  // route the caller tracks on ITS node) has no local row — the final
@@ -3317,6 +3318,10 @@ export function apply(ctx, config) {
3317
3318
  // node team when it has joined, else the node label), so the receiver
3318
3319
  // can answer with one a2a_route instead of an unroutable display label.
3319
3320
  a2aJoinGateRefusal(agent === undefined ? undefined : { agent });
3321
+ // A target-authored receipt attempt answers its own work even if scope/transport later refuses delivery.
3322
+ const answer = parseReceipt(args.message);
3323
+ if (agent !== undefined && answer !== null)
3324
+ taskLedger.markAnswered(answer.taskId, sessionTeamOf(agent));
3320
3325
  // S3 (team-scoped routing, phase-2 default ON per the 2026-09-01 owner
3321
3326
  // ruling; set false to restore the open outbound seam): a session
3322
3327
  // caller must have declared membership in the target team before it
@@ -1,9 +1,9 @@
1
1
  import { parseReceipt } from "./receipt.js";
2
2
  export const WIRE_ERROR_QUEUE_FULL = -32009;
3
3
  export const DEFAULT_TARGET_QUEUE_CAP = 3;
4
- /** How many receipt-owing tasks this host holds pending for `team`. */
4
+ /** How many receipt-owing tasks this host holds pending and unanswered for `team`. */
5
5
  export function queuedFor(rows, team) {
6
- return rows.filter(row => row.status === 'pending' && row.receiptExpected !== false &&
6
+ return rows.filter(row => row.status === 'pending' && row.receiptExpected !== false && row.answeredAt === undefined &&
7
7
  (row.direction === 'inbound' || row.peer === 'local') &&
8
8
  (row.team === team || row.deliveredTo === team)).length;
9
9
  }
@@ -19,7 +19,7 @@ export function queueFullRefusal(rows, team, message, cap, mailbox, ownTask) {
19
19
  const queued = queuedFor(rows, team);
20
20
  if (queued < limit)
21
21
  return undefined;
22
- return `target-queue-full (gh#224): "${team}" already has ${String(queued)} task(s) waiting for a receipt (cap ${String(limit)}) — nothing was delivered. Wait for a receipt from it (a2a inbox / a2a_tasks), or send again later with a NEW task id (this one is recorded as failed); do not resend in a loop. A task that never gets a receipt turns dead after taskStaleTtlMs and stops counting.`;
22
+ return `target-queue-full (gh#224): "${team}" already has ${String(queued)} task(s) not yet answered by the target (cap ${String(limit)}) — nothing was delivered. Wait for a receipt from it (a2a inbox / a2a_tasks), or send again later with a NEW task id (this one is recorded as failed); do not resend in a loop. A task that never gets a receipt turns dead after taskStaleTtlMs and stops counting.`;
23
23
  }
24
24
  /** Wire-face adapter in the flood guard's shape: a refused fresh key is FAILED, not stranded pending. */
25
25
  export function createQueueGuard(rows, cap, ownTask, outcomes, recordActivity) {
@@ -40,6 +40,10 @@ polling `a2a_teams` cannot see another host's ledger.
40
40
  when known. `archivedCount` and `archivedSplit` (count per outcome) cover
41
41
  the whole archive.
42
42
 
43
+ ## Queue capacity is not receipt debt
44
+
45
+ A pending row with `answeredAt` records that its actual local target produced a task-attributed final or attempted its own formal receipt. It no longer occupies the `targetQueueCap` limit, even if the caller CLI expired and receipt delivery failed. It is **still pending receipt debt**: do not abandon it just to free capacity, or claim the sender received it. Dead-letter expiry, late receipts and lifetime conservation continue unchanged. State session `queued` and the board both use the same unanswered-row count; unfinished work still counts against the default cap of three. Unrelated agents, unrelated finals and host timeout placeholders cannot mark an answer.
46
+
43
47
  ## Idempotency
44
48
 
45
49
  A route carries a fingerprint (caller + message + team + wait-mode). Re-
@@ -117,9 +117,10 @@ function withAuth(raw, entry, now) {
117
117
  // #139: an outbound row carries only its local-candidate admission.
118
118
  return auth.ingress === 'local' && entry.peer === 'local' ? { auth } : {};
119
119
  }
120
- /** #139: the routing facts settlement reads, copied between a row and its archive entry. */
120
+ /** Routing and answer facts copied unchanged between a row and its archive entry. */
121
121
  function routingOf(row) {
122
122
  return {
123
+ ...(row.answeredAt !== undefined ? { answeredAt: row.answeredAt } : {}),
123
124
  ...(row.forwarded === true ? { forwarded: true } : {}),
124
125
  ...(row.peerKey !== undefined ? { peerKey: row.peerKey } : {}),
125
126
  ...(row.peerHost !== undefined ? { peerHost: row.peerHost } : {}),
@@ -152,6 +153,7 @@ function withRouting(entry) {
152
153
  const fact = (v) => typeof v === 'string' && v !== '' && v.length <= ROUTING_FACT_CAP;
153
154
  const remoteOutbound = entry.direction === 'outbound' && typeof entry.peer === 'string' && entry.peer !== 'local' && !entry.peer.startsWith('local:');
154
155
  return {
156
+ ...(typeof entry.answeredAt === 'number' && Number.isFinite(entry.answeredAt) && entry.answeredAt >= 0 ? { answeredAt: entry.answeredAt } : {}),
155
157
  ...(remoteOutbound && entry.forwarded === true ? { forwarded: true } : {}),
156
158
  ...(remoteOutbound && fact(entry.peerKey) ? { peerKey: entry.peerKey } : {}),
157
159
  ...(remoteOutbound && fact(entry.peerHost) ? { peerHost: entry.peerHost } : {}),
@@ -369,6 +371,15 @@ export class TaskLedger {
369
371
  this.trimOwed();
370
372
  this.persist();
371
373
  }
374
+ /** Record an answer only from the local member that actually received this row; never settle or change counters. */
375
+ markAnswered(taskId, target) {
376
+ const row = this.tasks.find(entry => entry.taskId === taskId);
377
+ if (row === undefined || !(row.direction === 'inbound' || row.peer === 'local') || (row.deliveredTo ?? row.team) !== target || row.answeredAt !== undefined)
378
+ return false;
379
+ row.answeredAt = this.now();
380
+ this.persist();
381
+ return true;
382
+ }
372
383
  /**
373
384
  * v0.5.23 (async-stall): whether a tracked task still owes its receipt.
374
385
  * The async nudge consults this — a resolved or dead-lettered task
package/lib/tools/info.js CHANGED
@@ -453,7 +453,7 @@ export function createInfoTools(deps) {
453
453
  description: 'Read-only three-tier ledger of routed A2A tasks: pending rows still owed a receipt, rows '
454
454
  + 'auto-dead-lettered past the stale TTL, and the bounded archive of correlated '
455
455
  + '`[A2A receipt] task <task_id>` outcomes. Use it to reconcile dispatched async work instead of '
456
- + 're-routing to ask for progress.',
456
+ + 're-routing to ask for progress. answeredAt means the local target answered, not that the receipt arrived; that row stays owed but no longer occupies target queue capacity.',
457
457
  parameters: {},
458
458
  output: {
459
459
  // gh#198: the host's own verdict on whether this result came from outside this host, persisted on tool/result.
@@ -478,6 +478,7 @@ export function createInfoTools(deps) {
478
478
  brief: { type: 'string' },
479
479
  status: { type: 'string', required: true },
480
480
  deadAt: { type: 'number' },
481
+ answeredAt: { type: 'number' },
481
482
  direction: { type: 'string', enum: ['inbound', 'outbound'] },
482
483
  receiptExpected: { type: 'boolean' },
483
484
  forwarded: { type: 'boolean' },
@@ -571,11 +572,12 @@ export function createInfoTools(deps) {
571
572
  ok: true,
572
573
  // Project the declared fields only: ledger rows also carry internal audit facts (the #114 auth record,
573
574
  // #139 peerKey/peerHost) the output schema does not admit — spreading the row failed every call (0.6.32+).
574
- tasks: taskLedger.list().map(({ taskId, team, peer, startedAt, contextId, brief, status, deadAt, direction, receiptExpected, forwarded, deliveredTo, reason, sender }) => ({
575
+ tasks: taskLedger.list().map(({ taskId, team, peer, startedAt, contextId, brief, status, deadAt, answeredAt, direction, receiptExpected, forwarded, deliveredTo, reason, sender }) => ({
575
576
  taskId, team, peer, startedAt, status,
576
577
  ...(contextId !== undefined ? { contextId } : {}),
577
578
  ...(brief !== undefined ? { brief } : {}),
578
579
  ...(deadAt !== undefined ? { deadAt } : {}),
580
+ ...(answeredAt !== undefined ? { answeredAt } : {}),
579
581
  ...(direction !== undefined ? { direction } : {}),
580
582
  ...(receiptExpected !== undefined ? { receiptExpected } : {}),
581
583
  ...(forwarded !== undefined ? { forwarded } : {}),
@@ -3,7 +3,7 @@
3
3
  * busy session. This counts what the target already owes and refuses a NEW delivery once the
4
4
  * count reaches the cap, visibly and before anything is steered or booked.
5
5
  *
6
- * Counted ("queued for T"): rows this host's ledger still holds pending for T that expect a
6
+ * Counted ("queued for T"): unanswered rows this host's ledger still holds pending for T that expect a
7
7
  * receipt — inbound rows born at /a2a/direct (addressed to T, or delivered to T), and same-host
8
8
  * routes (peer 'local') to T. Never refused: a receipt for a task this host's ledger holds
9
9
  * (terminal; refusing one strands that row — a genuine receipt always returns to the host that
@@ -22,9 +22,10 @@ export interface QueueRow {
22
22
  readonly status: string;
23
23
  readonly direction?: string;
24
24
  readonly receiptExpected?: boolean;
25
+ readonly answeredAt?: number;
25
26
  readonly deliveredTo?: string;
26
27
  }
27
- /** How many receipt-owing tasks this host holds pending for `team`. */
28
+ /** How many receipt-owing tasks this host holds pending and unanswered for `team`. */
28
29
  export declare function queuedFor(rows: readonly QueueRow[], team: string): number;
29
30
  /** The refusal text when `team`'s queue is full, else undefined. `cap <= 0` disables the guard. */
30
31
  export declare function queueFullRefusal(rows: readonly QueueRow[], team: string, message: string, cap: number, mailbox: boolean, ownTask: (taskId: string) => boolean): string | undefined;
@@ -139,6 +139,8 @@ export interface TaskRecord {
139
139
  readonly peerHost?: string;
140
140
  /** #139: the member that actually received the task (canvas / bare-zone deliveries). */
141
141
  readonly deliveredTo?: string;
142
+ /** Local target has produced an answer; still owed until receipt delivery/settlement, but no longer queued. */
143
+ answeredAt?: number;
142
144
  status: TaskStatus;
143
145
  /** When the row was swept to dead-letter (absent while pending). */
144
146
  deadAt?: number;
@@ -194,6 +196,8 @@ export interface ArchivedRecord {
194
196
  readonly peerKey?: string;
195
197
  readonly peerHost?: string;
196
198
  readonly deliveredTo?: string;
199
+ /** Local answer time retained as audit metadata, independent of resolution time. */
200
+ readonly answeredAt?: number;
197
201
  /** #114 S4: who settled the row (receipt and echo ends), and their chain role. */
198
202
  readonly settledBy?: SettledBy;
199
203
  }
@@ -377,6 +381,8 @@ export declare class TaskLedger {
377
381
  * @param contextId - the delivery's conversation id, kept for follow-up routes (empty omits it).
378
382
  */
379
383
  trackInbound(taskId: string, team: string, from: string, contextId?: string, receiptExpected?: boolean, brief?: string, auth?: TaskAuthorization, deliveredTo?: string): void;
384
+ /** Record an answer only from the local member that actually received this row; never settle or change counters. */
385
+ markAnswered(taskId: string, target: string): boolean;
380
386
  /**
381
387
  * v0.5.23 (async-stall): whether a tracked task still owes its receipt.
382
388
  * The async nudge consults this — a resolved or dead-lettered task
@@ -83,11 +83,16 @@ host_prefix = 'dsh'
83
83
  for _t in _BASE.get('canvas', {}).get('teams', []):
84
84
  teams[_t['name']] = [m['id'] for m in _t.get('members', [])]
85
85
  for _m in _t.get('members', []):
86
- member_rows[_m['id']] = _m
86
+ member_rows[_m['id']] = _m # every member row, not only the first
87
87
  _p = _m.get('team', '').split('/')[0]
88
- if _p:
88
+ if _p and '/' not in _m['id']:
89
89
  host_prefix = _p
90
- break
90
+
91
+
92
+ def valid_remote_ref(mid):
93
+ """Mirror of canvas-store.validRemoteMemberRef: a '/'-qualified handle, no whitespace, no empty side."""
94
+ return (isinstance(mid, str) and '/' in mid and mid == mid.strip() and not any(c.isspace() for c in mid)
95
+ and not mid.startswith('/') and not mid.endswith('/') and '//' not in mid)
91
96
 
92
97
 
93
98
  def valid_name(raw):
@@ -118,7 +123,9 @@ def build_state(demo: bool = False) -> bytes:
118
123
  for mid in ids:
119
124
  row = member_rows.get(mid)
120
125
  if row is None:
121
- row = {'id': mid, 'team': f'{host_prefix}/{mid[:8]}', 'joined': True, 'live': True}
126
+ # A remote ref renders as the real state face does: { id, team: the handle, remote: true }.
127
+ row = ({'id': mid, 'team': mid, 'remote': True} if '/' in mid
128
+ else {'id': mid, 'team': f'{host_prefix}/{mid[8:16] if mid.startswith("session-") else mid[:8]}', 'joined': True, 'live': True})
122
129
  member_rows[mid] = row
123
130
  members.append(row)
124
131
  team_rows.append({'name': name, 'team': f'{host_prefix}/canvas/{name}', 'members': members})
@@ -208,8 +215,9 @@ class MockHost(SimpleHTTPRequestHandler):
208
215
  elif action == 'add-member':
209
216
  name = valid_name(body.get('name'))
210
217
  mid = body.get('id')
211
- if name is None or not isinstance(mid, str) or mid not in joined_ids:
212
- self._json(b'{"ok":false,"error":"name and a joined session id are required"}')
218
+ # Dual member shape, as the real host: a joined local session id, or a remote team handle.
219
+ if name is None or not isinstance(mid, str) or not (mid in joined_ids or valid_remote_ref(mid)):
220
+ self._json(b'{"ok":false,"error":"name and a member (joined session id, or a zone-qualified remote team handle like zone/a1b2c3d4) are required"}')
213
221
  return
214
222
  roster = teams.setdefault(name, [])
215
223
  if mid in roster:
@@ -43,15 +43,17 @@ export function createCardElement(id: string): HTMLDivElement {
43
43
 
44
44
  /**
45
45
  * The right-hand chip. Peer cards show the link badge (gh#20 W-A: linked green / stranger gray, then the
46
- * verified tick/cross; an absent link field from an older host renders the neutral 'peer'); session cards show
47
- * their first membership priority `P<n>`. Writes only on change.
46
+ * verified tick/cross; an absent link field from an older host renders the neutral 'peer'). A remote MEMBER card
47
+ * (gh#38) is one session on that host, not the host: it reads 成员, colored and ticked by its host's link
48
+ * (it used to fall through to 'peer' and looked like a host card). Session cards show their first membership
49
+ * priority `P<n>`. Writes only on change.
48
50
  */
49
51
  export function paintPrio(prioEl: HTMLElement, n: WorldNode, info: { link?: 'linked' | 'stranger'; verified?: boolean } | undefined): void {
50
52
  if (n.remote === true) {
51
53
  const cls = info?.link === 'linked' ? 'lnk-linked' : info?.link === 'stranger' ? 'lnk-stranger' : ''
52
54
  if (prioEl.className !== `prio mono ${cls}`.trim()) prioEl.className = `prio mono ${cls}`.trim()
53
55
  const mark = info === undefined ? '' : info.verified === true ? '✓' : '✗'
54
- const badge = (info?.link ?? 'peer') + mark
56
+ const badge = (n.id.startsWith('member-') ? '成员' : info?.link ?? 'peer') + mark
55
57
  if (prioEl.textContent !== badge) prioEl.textContent = badge
56
58
  } else {
57
59
  if (prioEl.className !== 'prio mono') prioEl.className = 'prio mono'
@@ -431,7 +431,9 @@ export function createPlanningView(deps: PlanningDeps): PlanningView {
431
431
  if (subEl.textContent !== subText) subEl.textContent = subText
432
432
  const tip = unavailable ? `${txt.tip} · ${UNREACHABLE_HINT}` : txt.tip
433
433
  if (el.title !== tip) el.title = tip
434
- paintPrio(el.querySelector<HTMLElement>('.prio')!, n, n.remote === true ? peerInfoByHost.get(host) : undefined)
434
+ // A member card's chip follows the link of the host that publishes its handle.
435
+ const linkHost = n.id.startsWith('member-') ? memberHostByHandle.get(n.remoteHandle ?? '') : host
436
+ paintPrio(el.querySelector<HTMLElement>('.prio')!, n, n.remote === true && linkHost !== undefined ? peerInfoByHost.get(linkHost) : undefined)
435
437
  }
436
438
  for (const [id, el] of [...nodeEls]) {
437
439
  if (!seen.has(id)) { el.remove(); nodeEls.delete(id) }
@@ -454,6 +456,7 @@ export function createPlanningView(deps: PlanningDeps): PlanningView {
454
456
  const peerScoreByText = new Map<string, number | undefined>()
455
457
  /** gh#20 W-A: per-host link/verified info for the peer badges. */
456
458
  const peerInfoByHost = new Map<string, { link?: 'linked' | 'stranger'; verified?: boolean }>()
459
+ let memberHostByHandle: ReadonlyMap<string, string> = new Map()
457
460
 
458
461
  function renderFederation(): void {
459
462
  federation.render(clock())
@@ -1202,6 +1205,7 @@ export function createPlanningView(deps: PlanningDeps): PlanningView {
1202
1205
  // excluded from bounds, so the column cannot feed back into itself).
1203
1206
  const peerSeen = new Set<string>()
1204
1207
  lastRemoteTeams = groupRemoteTeams(input.remoteTeams ?? [])
1208
+ memberHostByHandle = new Map([...lastRemoteTeams].flatMap(([h, rows]) => rows.map(row => [row.team, h] as const)))
1205
1209
  lastWork = new Map(input.sessions.map(s => [s.id, { status: s.status, queued: s.queued }]))
1206
1210
  reachability = unreachableRefs(input)
1207
1211
  peerScoreByText.clear()
@@ -1226,7 +1230,9 @@ export function createPlanningView(deps: PlanningDeps): PlanningView {
1226
1230
  // team row's zone/id8 handle (canvas remote refs are exact handles
1227
1231
  // per the D1 ruling). Absent until the first remote team row lands.
1228
1232
  const host = peerHostOf(p.url)
1229
- const handle = lastRemoteTeams.get(host)?.[0]?.team
1233
+ // Owner report 2026-11-04: the host's rows now lead with its bare process team (e.g. 'dsh'), which the
1234
+ // server reads as a local session id and refuses — take the first zone/id8 SESSION handle instead.
1235
+ const handle = lastRemoteTeams.get(host)?.find(row => row.team.includes('/'))?.team
1230
1236
  model.upsertNode({
1231
1237
  id, x: base.x, y: base.y,
1232
1238
  label: p.url, team: '', name: p.url, peerUrl: p.url,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nelsonlongxiang/dsh-open-a2a-net",
3
- "version": "0.6.35",
3
+ "version": "0.6.36",
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",