@nelsonlongxiang/dsh-open-a2a-net 0.6.34 → 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.34-474a60a\src\src\client\A2aControl.module.css.mjs
26
- const css = ".YPQWgG_wrap{max-width:100%;display:inline-flex;position:relative}.YPQWgG_trigger{align-items:center;gap:4px;display:inline-flex;position:relative}.YPQWgG_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 YPQWgG_badgePulse;position:absolute;top:-6px;right:-8px}@keyframes YPQWgG_badgePulse{0%,to{box-shadow:0 0 #4c8bf573}50%{box-shadow:0 0 0 4px #4c8bf500}}@media (prefers-reduced-motion:reduce){.YPQWgG_unreadBadge{animation:none}}.YPQWgG_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}.YPQWgG_title{font-size:13px;font-weight:600}.YPQWgG_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}.YPQWgG_stageRow{border-top:1px solid var(--dsh-border,#2a2a30);align-items:center;gap:8px;margin-top:4px;padding-top:8px;display:flex}.YPQWgG_stageTitle{text-transform:uppercase;letter-spacing:.04em;opacity:.65;font-size:11px;font-weight:600}.YPQWgG_stageLinks{gap:6px;display:flex}.YPQWgG_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}.YPQWgG_stageLink:hover,.YPQWgG_stageLink:focus-visible{border-color:var(--dsh-accent,#4c8bf5)}.YPQWgG_empty{opacity:.7;font-size:12px}.YPQWgG_row{border:1px solid #0000;border-radius:8px;align-items:center;gap:10px;padding:8px;display:flex}.YPQWgG_joined{border-color:var(--dsh-border,#2a2a30)}.YPQWgG_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}.YPQWgG_facts:hover{background:var(--dsh-surface-hover,#ffffff0f)}.YPQWgG_nameRow{align-items:center;gap:6px;display:flex}.YPQWgG_stateDot{border-radius:50%;flex:none;width:7px;height:7px}.YPQWgG_stateDot.YPQWgG_live{background:var(--dsh-accent,#4c8bf5)}.YPQWgG_stateDot.YPQWgG_live.YPQWgG_joined{background:var(--dsh-success,#3fb96f)}.YPQWgG_stateDot.YPQWgG_cold{background:var(--dsh-warning,#d9a13c)}.YPQWgG_name{text-overflow:ellipsis;white-space:nowrap;font-size:13px;font-weight:500;overflow:hidden}.YPQWgG_description{opacity:.7;text-overflow:ellipsis;white-space:nowrap;font-size:12px;overflow:hidden}.YPQWgG_team{opacity:.55;font-size:11px;font-family:var(--dsh-mono,monospace)}.YPQWgG_note{opacity:.6;font-size:11px}.YPQWgG_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}.YPQWgG_searchInput:focus{outline:1px solid var(--dsh-accent,#4c8bf5)}.YPQWgG_sessionGroup{flex-direction:column;gap:2px;display:flex}.YPQWgG_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}.YPQWgG_groupChevron{transition:transform .12s;display:inline-block}.YPQWgG_groupChevronClosed{transform:rotate(-90deg)}.YPQWgG_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}.YPQWgG_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}.YPQWgG_groupButton:hover{border-color:var(--dsh-border,#2a2a30);opacity:1}.YPQWgG_groupPicker{border:1px dashed var(--dsh-border,#2a2a30);border-radius:8px;flex-wrap:wrap;gap:6px;width:100%;padding:8px;display:flex;position:relative}.YPQWgG_groupOption{border:1px solid var(--dsh-border,#2a2a30);color:inherit;cursor:pointer;background:0 0;border-radius:999px;padding:2px 8px;font-size:11px}.YPQWgG_groupOptionActive{border-color:var(--dsh-accent,#4c8bf5);color:var(--dsh-accent,#4c8bf5)}.YPQWgG_groupNew{align-items:center;gap:6px;width:100%;margin-top:2px;display:flex}.YPQWgG_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}.YPQWgG_peerGrid{flex-wrap:wrap;gap:6px;display:flex}.YPQWgG_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}.YPQWgG_peerHost{text-overflow:ellipsis;white-space:nowrap;max-width:160px;overflow:hidden}.YPQWgG_peerScore{font-family:var(--dsh-mono,monospace);opacity:.6}.YPQWgG_activityList{flex-direction:column;gap:2px;display:flex}.YPQWgG_activityRow{border-radius:4px;align-items:center;gap:8px;padding:2px 4px;font-size:11px;display:flex}.YPQWgG_activityRow[data-ok=false]{opacity:.55}.YPQWgG_activityDir{flex:none;font-weight:600}.YPQWgG_activityDir.YPQWgG_in{color:var(--dsh-success,#3fb96f)}.YPQWgG_activityDir.YPQWgG_out{color:var(--dsh-accent,#4c8bf5)}.YPQWgG_activityTeam{text-overflow:ellipsis;white-space:nowrap;min-width:0;font-family:var(--dsh-mono,monospace);opacity:.8;flex:1;overflow:hidden}.YPQWgG_activityPeer{text-overflow:ellipsis;white-space:nowrap;opacity:.55;max-width:90px;overflow:hidden}.YPQWgG_activityTime{opacity:.5;font-family:var(--dsh-mono,monospace);flex:none}.YPQWgG_activityRow[data-jump=true]{cursor:pointer}.YPQWgG_activityRow[data-jump=true]:hover{background:var(--dsh-surface-hover,#ffffff0f)}.YPQWgG_fresh{animation:1.2s ease-out 3 YPQWgG_freshFlash}@keyframes YPQWgG_freshFlash{0%{background:#4c8bf538}to{background:0 0}}@media (prefers-reduced-motion:reduce){.YPQWgG_fresh{background:#4c8bf51f;animation:none}}.YPQWgG_inFlightList{flex-direction:column;gap:2px;margin-bottom:4px;display:flex}.YPQWgG_inFlightRow{border:1px dashed var(--dsh-border,#2a2a30);border-radius:4px;align-items:center;gap:8px;padding:3px 4px;font-size:11px;display:flex}.YPQWgG_inFlightPulse{background:var(--dsh-accent,#4c8bf5);border-radius:50%;flex:none;width:7px;height:7px;animation:1.1s ease-in-out infinite YPQWgG_flightPulse}@keyframes YPQWgG_flightPulse{0%,to{opacity:1;transform:scale(1)}50%{opacity:.5;transform:scale(.55)}}@media (prefers-reduced-motion:reduce){.YPQWgG_inFlightPulse{animation:none}}.YPQWgG_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}.YPQWgG_inFlightStale{opacity:.55}.YPQWgG_canvasCreate{align-items:center;gap:6px;margin-bottom:4px;display:flex}.YPQWgG_canvasCreate .YPQWgG_groupInput{flex:1;min-width:0}.YPQWgG_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}.YPQWgG_canvasChip>span:not([class]){white-space:nowrap;text-overflow:ellipsis;min-width:0;overflow:hidden}.YPQWgG_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}.YPQWgG_canvasChipRemove:hover{color:var(--dsh-color-text-primary,#eee)}.YPQWgG_canvasChipRemove{transition:color .14s ease-out}@media (prefers-reduced-motion:reduce){.YPQWgG_canvasChipRemove{transition:none}}.YPQWgG_canvasChipRemove:focus-visible{outline:2px solid var(--dsh-color-primary,#4f8cff);outline-offset:1px;border-radius:4px}.YPQWgG_canvasActions{gap:6px;margin-top:4px;display:flex}.YPQWgG_inFlightStale .YPQWgG_inFlightPulse{opacity:.4;animation:none}.YPQWgG_failMark{color:#f87171;flex-shrink:0;font-size:11px}.YPQWgG_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": "YPQWgG_activityDir",
37
- "activityList": "YPQWgG_activityList",
38
- "activityPeer": "YPQWgG_activityPeer",
39
- "activityRow": "YPQWgG_activityRow",
40
- "activityTeam": "YPQWgG_activityTeam",
41
- "activityTime": "YPQWgG_activityTime",
42
- "badgePulse": "YPQWgG_badgePulse",
43
- "canvasActions": "YPQWgG_canvasActions",
44
- "canvasChip": "YPQWgG_canvasChip",
45
- "canvasChipRemove": "YPQWgG_canvasChipRemove",
46
- "canvasCreate": "YPQWgG_canvasCreate",
47
- "cold": "YPQWgG_cold",
48
- "description": "YPQWgG_description",
49
- "empty": "YPQWgG_empty",
50
- "facts": "YPQWgG_facts",
51
- "failMark": "YPQWgG_failMark",
52
- "flightPulse": "YPQWgG_flightPulse",
53
- "fresh": "YPQWgG_fresh",
54
- "freshFlash": "YPQWgG_freshFlash",
55
- "groupButton": "YPQWgG_groupButton",
56
- "groupChevron": "YPQWgG_groupChevron",
57
- "groupChevronClosed": "YPQWgG_groupChevronClosed",
58
- "groupHead": "YPQWgG_groupHead",
59
- "groupInput": "YPQWgG_groupInput",
60
- "groupNew": "YPQWgG_groupNew",
61
- "groupOption": "YPQWgG_groupOption",
62
- "groupOptionActive": "YPQWgG_groupOptionActive",
63
- "groupPicker": "YPQWgG_groupPicker",
64
- "groupTag": "YPQWgG_groupTag",
65
- "in": "YPQWgG_in",
66
- "inFlightList": "YPQWgG_inFlightList",
67
- "inFlightPulse": "YPQWgG_inFlightPulse",
68
- "inFlightRow": "YPQWgG_inFlightRow",
69
- "inFlightStale": "YPQWgG_inFlightStale",
70
- "joined": "YPQWgG_joined",
71
- "live": "YPQWgG_live",
72
- "name": "YPQWgG_name",
73
- "nameRow": "YPQWgG_nameRow",
74
- "note": "YPQWgG_note",
75
- "out": "YPQWgG_out",
76
- "peerChip": "YPQWgG_peerChip",
77
- "peerGrid": "YPQWgG_peerGrid",
78
- "peerHost": "YPQWgG_peerHost",
79
- "peerScore": "YPQWgG_peerScore",
80
- "popover": "YPQWgG_popover",
81
- "row": "YPQWgG_row",
82
- "searchInput": "YPQWgG_searchInput",
83
- "sectionTitle": "YPQWgG_sectionTitle",
84
- "sessionGroup": "YPQWgG_sessionGroup",
85
- "stageLink": "YPQWgG_stageLink",
86
- "stageLinks": "YPQWgG_stageLinks",
87
- "stageRow": "YPQWgG_stageRow",
88
- "stageTitle": "YPQWgG_stageTitle",
89
- "staleNote": "YPQWgG_staleNote",
90
- "stateDot": "YPQWgG_stateDot",
91
- "team": "YPQWgG_team",
92
- "title": "YPQWgG_title",
93
- "trigger": "YPQWgG_trigger",
94
- "unreadBadge": "YPQWgG_unreadBadge",
95
- "versionTag": "YPQWgG_versionTag",
96
- "wrap": "YPQWgG_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
@@ -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))
@@ -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 } : {}),
@@ -469,8 +471,9 @@ export function registerAdminFaces(webServer, d) {
469
471
  return '';
470
472
  } })();
471
473
  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);
474
+ const homeCleared = out.ok && canvasStore.clearCliHomeForLocal(out.handle);
472
475
  payload = JSON.stringify(out.ok
473
- ? { ok: true, handle: out.handle, expiresAt: out.expiresAt, cardUrl: `${d.ownCardUrl}/a2a/cli/${encodeURIComponent(out.handle)}` }
476
+ ? { ok: true, handle: out.handle, expiresAt: out.expiresAt, ...(homeCleared ? { homeCleared: true } : {}), cardUrl: `${d.ownCardUrl}/a2a/cli/${encodeURIComponent(out.handle)}` }
474
477
  : { ok: false, error: out.error });
475
478
  }
476
479
  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
- const writeHead = res.writeHead.bind(res);
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) => {
@@ -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
- const echoed = taskLedger.resolveEcho(message, judge);
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
  }
@@ -2022,6 +2025,7 @@ export function apply(ctx, config) {
2022
2025
  // placeholder as one on the same-node loop-back.
2023
2026
  if (placeholder === true)
2024
2027
  return;
2028
+ taskLedger.markAnswered(taskId, sessionTeamOf(target));
2025
2029
  // Idempotency: a row THIS ledger tracks must still be pending
2026
2030
  // (settled/dead rows never re-send). A foreign task id (an inbound
2027
2031
  // route the caller tracks on ITS node) has no local row — the final
@@ -3314,6 +3318,10 @@ export function apply(ctx, config) {
3314
3318
  // node team when it has joined, else the node label), so the receiver
3315
3319
  // can answer with one a2a_route instead of an unroutable display label.
3316
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));
3317
3325
  // S3 (team-scoped routing, phase-2 default ON per the 2026-09-01 owner
3318
3326
  // ruling; set false to restore the open outbound seam): a session
3319
3327
  // caller must have declared membership in the target team before it
@@ -3814,7 +3822,8 @@ export function apply(ctx, config) {
3814
3822
  async function dispatchAnywhere(team, message, callerSession, contextId, signal, asyncMode, taskId, peerCaller) {
3815
3823
  // A local online household is an owner-established fact and receives locally.
3816
3824
  // Otherwise only the pinned home may receive; never fall back to directory claims.
3817
- if (CLI_HANDLE_RE.test(team) && parseReceipt(message) !== null && cliDeposits.get(team) === undefined) {
3825
+ const cliReceipt = CLI_HANDLE_RE.test(team) && parseReceipt(message) !== null;
3826
+ if (cliReceipt && cliDeposits.get(team) === undefined) {
3818
3827
  const home = canvasStore.cliHomeOf(team);
3819
3828
  if (home === undefined)
3820
3829
  throw new Error(`CLI receipt home is not pinned for ${team}`);
@@ -3832,6 +3841,8 @@ export function apply(ctx, config) {
3832
3841
  throw new Error(local.error);
3833
3842
  return local;
3834
3843
  }
3844
+ if (cliReceipt)
3845
+ throw new Error(`local CLI household expired before delivery to ${team}; directory fallback refused`);
3835
3846
  const failures = [];
3836
3847
  const candidates = await directoryPeerCandidates(memoizedCardFetch(), team, failures);
3837
3848
  if (candidates.length === 0) {
@@ -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) {
@@ -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
@@ -36,6 +40,10 @@ polling `a2a_teams` cannot see another host's ledger.
36
40
  when known. `archivedCount` and `archivedSplit` (count per outcome) cover
37
41
  the whole archive.
38
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
+
39
47
  ## Idempotency
40
48
 
41
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 } : {}),
@@ -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;
@@ -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;
@@ -0,0 +1,3 @@
1
+ import type { ServerResponse } from 'node:http';
2
+ /** Add structured fields to any JSON response exit and recompute its Content-Length. */
3
+ export declare function withResponseFields(res: ServerResponse, fields: Readonly<Record<string, unknown>>): void;
@@ -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