@nelsonlongxiang/dsh-open-a2a-net 0.6.37 → 0.6.38

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -100,7 +100,7 @@ a2a-net route dsh/1a2b3c4d "status?" # async by default; --s
100
100
  a2a-net inbox --new # unread mail; --wait blocks, --hook feeds a Claude Code hook
101
101
  ```
102
102
 
103
- By default it delivers only to teams on its home host. With `forwardDirect: true`, the home can forward an authenticated online CLI one hop to a linked peer target. Both operators must link: A links the receiving B member; B links the CLI handle and explicitly pins A as its home. Canvas targets require A to directly link every advertised receiving member, not merely rely on the peer canvas org. With scope and settlement enforcement on, the member-host auto-receipt settles A’s forwarded row and lands in the CLI inbox; `--sync` returns B’s reply. Commands, flags, environment
103
+ From 0.6.38, `forwardDirect` defaults to `true`: after a local miss the home can forward an authenticated online CLI one hop to a linked peer target. Both operators must link: A links the receiving B member; B links the CLI handle and explicitly pins A as its home. Canvas targets require A to directly link every advertised receiving member, not merely rely on the peer canvas org. With scope and settlement enforcement on, the member-host auto-receipt settles A’s forwarded row and lands in the CLI inbox; `--sync` returns B’s reply. Commands, flags, environment
104
104
  variables and exit codes: [`src/skill/references/cli.md`](src/skill/references/cli.md).
105
105
 
106
106
  ## Canvas teams (arbitrary grouping, v0.5.25)
@@ -114,7 +114,7 @@ A canvas team is a user-composed, named, multi-member routing group: **one atomi
114
114
  - For a CLI member marked “home 未确定”, open its canvas team inspector and choose “指定 home”. The host freshly reads cards, lists only linked advertisers, and reports unreadable-card counts. No home is selected automatically; confirming calls the existing local `add-member` with explicit `home`, which freshly validates again. A locally held household (live or offline tombstone) instead shows “本机持有,无需指定”, returns `held: true` with no choices, and rejects explicit home writes until that household is revoked. Online local households receive receipts first. A successful local publish removes a conflicting older pin and leaves a durable inspector notice; expiry during local receipt delivery fails closed instead of resolving another advertiser. This does not weaken automatic pinning or admission.
115
115
  - Membership requires a joined session (live node or remembered intent) — no routing backdoor over unjoined sessions; leaving the network (leave/archive) drops every canvas membership
116
116
  - A member may also be a **remote** zone-qualified team handle (`zone/a1b2c3d4`) the operator adds by hand — the link that admits that remote caller here and entitles it to the full card
117
- - `a2a_teams` lists canvas teams as local rows with member/live counts; `/__dsh_a2a/state` serves per-member `joined`/`live` flags for local members and `{ id, team, remote: true }` for remote ones
117
+ - `a2a_teams` lists local and in-scope remote canvas teams with owner URL and member count; CLI `teams` also includes local canvas and peerOrg, retaining same-name owners as separate rows. `a2a_whoami.teams.canvasDetails` and CLI `whoami --json` list local/remote memberships of this identity (legacy `teams.canvas` stays local-name-only). Owner URLs are discovery metadata, not a requirement to override `--home`. Failure guidance distinguishes visible-directory absence, known-owner missing prerequisites, and multiple owners (candidates, never guessed). Information scope, candidate selection and admission do not change; `/__dsh_a2a/state` serves per-member `joined`/`live` flags for local members and `{ id, team, remote: true }` for remote ones
118
118
 
119
119
  ## Native-teams bridge (node unification, P1) — deprecated
120
120
 
@@ -137,7 +137,7 @@ Row config overlays in the profile's patch layers; every key has a schema defaul
137
137
  | --- | --- | --- |
138
138
  | `announce` | `false` | Publish this node's agent card for peer discovery. |
139
139
  | `isolated` | `false` | Send nothing to any peer: card reads, directory sweeps, routes, remote log reads, canvas pings and fleet canvas-op relays are all refused (gh#193). `a2a/peers.json` is left untouched and refused fetches do not score peers; inbound faces keep answering; the state face shows `isolated: true`. For test and acceptance hosts. |
140
- | `forwardDirect` | `false` | Opt in to one-hop forwarding for an authenticated online local CLI after a local target miss; sender scope and the hop guard remain enforced. Link B receiving members at A, and link/pin the CLI home A at B; no second forwarding hop. |
140
+ | `forwardDirect` | `true` | From 0.6.38, one-hop forwarding is enabled by default for an authenticated online local CLI after a local target miss; sender scope and the hop guard remain enforced. Link B receiving members at A, and link/pin the CLI home A at B; no second forwarding hop. Set `forwardDirect: false` to restore local-only CLI delivery. |
141
141
  | `peers` | `[]` | Seed base URLs; referred peers are learned from their cards. |
142
142
  | `delegates` | `[]` | Zone delegations: `{ name, url, publicKey }` published on the card. |
143
143
  | `team` | `'dsh'` | Team name this node exposes for direct routing. |
package/README_zh.md CHANGED
@@ -61,7 +61,7 @@ a2a-net route dsh/1a2b3c4d "status?" # 默认异步;--sync
61
61
  a2a-net inbox --new # 未读来信;--wait 阻塞等待,--hook 供 Claude Code hook 使用
62
62
  ```
63
63
 
64
- 默认只投递 home 宿主上的团队;`forwardDirect: true` 时,home 可替已验签且在线的 CLI 向已链接的对端目标转发一跳。两边运营者必须各自链接:A 链接 B 的接收成员,B 链接 CLI 句柄并将 home 钉为 A。画布目标还要求 A 直接链接所有名片列出的接收成员,不能只依赖对端画布 org。作用域及结算 enforce 开启时,B 成员宿主的自动回执结清 A 的转发行并进入 CLI 信箱;`--sync` 返回 B 的回复。命令、参数、环境变量与退出码见 [`src/skill/references/cli.md`](src/skill/references/cli.md)。
64
+ 从 0.6.38 起 `forwardDirect` 默认 `true`;本机目标未找到时,home 可替已验签且在线的 CLI 向已链接的对端目标转发一跳。两边运营者必须各自链接:A 链接 B 的接收成员,B 链接 CLI 句柄并将 home 钉为 A。画布目标还要求 A 直接链接所有名片列出的接收成员,不能只依赖对端画布 org。作用域及结算 enforce 开启时,B 成员宿主的自动回执结清 A 的转发行并进入 CLI 信箱;`--sync` 返回 B 的回复。命令、参数、环境变量与退出码见 [`src/skill/references/cli.md`](src/skill/references/cli.md)。
65
65
 
66
66
  ## 画布队伍(任意组队,v0.5.25)
67
67
 
@@ -74,7 +74,7 @@ a2a-net inbox --new # 未读来信;--wait
74
74
  - CLI 成员标「home 未确定」时,打开其画布队详情并点「指定 home」。宿主现读名片,只列已链接且列出该句柄的主机,并显示不可读名片数;不会自动选中。确认后复用本机 `add-member` 的显式 `home`,提交时再次现读校验,不放宽自动钉或准入。本机持有在线户口或离线墓碑时显示「本机持有,无需指定」,候选返回 `held:true` 且为空,显式 home 写入提示先撤销本机户口;回执优先投在线本机信箱;本机 publish 成功会清除冲突旧钉并在面板提示,户口离线不复活旧钉;本地投递中途到期即报错,不回落对端目录。
75
75
  - 成员必须是 joined 会话(活节点或已记意图)——未加入会话没有路由后门;退出网络(leave/archive)自动清空全部画布成员关系
76
76
  - 成员也可以是操作者手工加入的**远端**带 zone 团队句柄(`zone/a1b2c3d4`)——这就是让该远端调用方在本机获准入、并有权拿完整卡片的链接
77
- - `a2a_teams` 以本地行列出画布队伍(含成员数/活数);`/__dsh_a2a/state` 对本地成员提供 `joined`/`live` 标记,对远端成员提供 `{ id, team, remote: true }`
77
+ - `a2a_teams` 列出本地和作用域内远端画布队,带属主 URL 和成员数;CLI `teams` 包含本地 canvas 和 peerOrg,同名不同属主各列一行。`a2a_whoami.teams.canvasDetails` 和 CLI `whoami --json` 列本身份本地/远端归属,旧 `teams.canvas` 本地名字数组兼容保留。URL 只作发现事实,不要求改 `--home`。失败提示区分可见目录无此队、已知属主但缺前提、同名多属主(列候选不猜);信息作用域、路由选择与准入不变;`/__dsh_a2a/state` 对本地成员提供 `joined`/`live` 标记,对远端成员提供 `{ id, team, remote: true }`
78
78
 
79
79
  ## Native-teams 桥(节点统一,P1)——已弃用
80
80
 
@@ -95,7 +95,7 @@ a2a-net inbox --new # 未读来信;--wait
95
95
  | --- | --- | --- |
96
96
  | `announce` | `false` | 发布本节点 agent 卡片供 peer 发现。 |
97
97
  | `isolated` | `false` | 不向任何对端发请求:取卡、目录扫描、路由、远端日志读取、canvas 变更推送、舰队 canvas-op 中继一律拒绝(gh#193)。`a2a/peers.json` 不动,被拒的请求也不给对端扣分;入站面照常应答;state 面显示 `isolated: true`。供测试与验收主机使用。 |
98
- | `forwardDirect` | `false` | 本机目标未找到时,允许替已验签且在线的本地CLI转发一跳;仍检查发送作用域和跳数预算。A 链接 B 接收成员,B 链接 CLI 并钉住 A;对端不会再转发。 |
98
+ | `forwardDirect` | `true` | 从 0.6.38 起默认开启,本机目标未找到时,允许替已验签且在线的本地CLI转发一跳;仍检查发送作用域和跳数预算。A 链接 B 接收成员,B 链接 CLI 并钉住 A;对端不会再转发;显式设 `forwardDirect: false` 恢复 CLI 仅投本机。 |
99
99
  | `peers` | `[]` | 种子 base URL;被引荐的 peers 从其卡片学习。 |
100
100
  | `delegates` | `[]` | 区域委托:`{ name, url, publicKey }` 发布在卡片上。 |
101
101
  | `team` | `'dsh'` | 本节点暴露给直连路由的团队名。 |
package/cli/a2a.mjs CHANGED
@@ -251,10 +251,13 @@ function keyHint(status) {
251
251
  return `host ${home()} answered ${String(status)} — ${apiKey() === undefined ? 'it is keyed: set $A2A_KEY (or pass --key)' : 'the key was refused: check $A2A_KEY / --key'}${pocket}`
252
252
  }
253
253
 
254
- async function state() {
254
+ async function state(optional = false) {
255
255
  const res = await homeFetch(`${home()}/__dsh_a2a/state`, { headers: keyHeaders() })
256
256
  if (res.status === 401 || res.status === 403) die(keyHint(res.status))
257
- if (res.status !== 200) die(`state face ${String(res.status)} on ${home()}`)
257
+ if (res.status !== 200) {
258
+ if (optional) return { error: `state face ${String(res.status)} on ${home()}` }
259
+ die(`state face ${String(res.status)} on ${home()}`)
260
+ }
258
261
  return await replyOf(res)
259
262
  }
260
263
 
@@ -870,6 +873,21 @@ async function printDelivery(verb, res) {
870
873
  console.log(`[${verb}] delivered → ${String(out.team)} · task ${String(out.task_id)} · ${receipt}`)
871
874
  }
872
875
 
876
+ /** Canvas rows come from the owner state face, not a new discovery or admission path. */
877
+ function stateCanvasTeams(s) {
878
+ const rows = []
879
+ const owner = String(s.ownerHost ?? home())
880
+ for (const team of s.canvas?.teams ?? []) rows.push({ team: String(team.team ?? String(s.zone ?? 'dsh') + '/canvas/' + team.name), name: String(team.name), kind: 'canvas', owner, memberCount: (team.members ?? []).length, where: 'local canvas', members: (team.members ?? []).map(m => typeof m === 'string' ? m : m.team ?? m.id) })
881
+ const seen = new Set()
882
+ for (const peer of s.peerOrg ?? []) for (const team of peer.org ?? []) {
883
+ const key = String(peer.url) + ':' + String(team.team)
884
+ if (seen.has(key)) continue
885
+ seen.add(key)
886
+ rows.push({ team: String(team.team), name: String(team.name), kind: 'canvas', owner: String(peer.url), memberCount: (team.members ?? []).length, where: 'remote canvas', members: team.members ?? [] })
887
+ }
888
+ return rows
889
+ }
890
+
873
891
  async function main() {
874
892
  if (cmd === 'help' || has('--help') || has('-h')) {
875
893
  console.log(USAGE)
@@ -925,25 +943,31 @@ async function main() {
925
943
  if (cmd === 'whoami') {
926
944
  const id = identity()
927
945
  const list = await control({ action: 'list' })
946
+ const s = await state(true)
947
+ const canvas = stateCanvasTeams(s).filter(team => team.members.includes(id.handle)).map(({ members, ...row }) => row)
948
+ if (has('--json')) { console.log(JSON.stringify({ handle: id.handle, host: home(), deposit: depositLine(list, id.handle, 'OFFLINE'), canvas, ...(s.error ? { canvasError: s.error } : {}) }, null, 2)); return }
928
949
  console.log(`handle ${id.handle}`)
929
950
  console.log(`identity ~/.a2a/cli.json (own Ed25519 key — the card's team = "${id.name}" is its own zone)`)
930
951
  console.log(`deposit ${depositLine(list, id.handle, 'OFFLINE (expired or never published — run: a2a publish)')}`)
931
952
  console.log(`host ${home()}`)
932
953
  console.log(`card url ${home()}/a2a/cli/${encodeURIComponent(id.handle)}`)
933
954
  console.log(`capability route via signed envelope; receive via mailbox (poll: a2a inbox)`)
955
+ if (s.error) console.log(`canvas UNKNOWN — ${String(s.error)}`)
956
+ for (const team of canvas) console.log(`canvas ${team.team} owner=${team.owner} members=${String(team.memberCount)}`)
934
957
  return
935
958
  }
936
959
  if (cmd === 'teams') {
937
960
  const s = await state()
938
961
  const q = (positionals()[0] ?? '').toLowerCase()
939
962
  const rows = [
940
- ...['dsh'].map(t => ({ team: String(t), name: String(s.session ?? 'host'), where: 'host' })),
963
+ ...[s.zone ?? 'dsh'].map(t => ({ team: String(t), name: String(s.session ?? 'host'), where: 'host' })),
941
964
  ...(s.sessions ?? []).map(x => ({ team: String(x.team ?? ''), name: String(x.seatName ?? x.name ?? x.label ?? ''), where: 'local' })),
942
965
  ...(s.remote ?? []).map(x => ({ team: String(x.team ?? ''), name: String(x.name ?? ''), where: `remote · ${String(x.via ?? '')}` })),
966
+ ...stateCanvasTeams(s).map(({ members, ...row }) => row),
943
967
  ]
944
968
  const hits = rows.filter(r => q === '' || r.team.toLowerCase().includes(q) || r.name.toLowerCase().includes(q))
945
969
  if (has('--json')) { console.log(JSON.stringify(hits, null, 2)); return }
946
- for (const r of hits) console.log(`${r.team.padEnd(28)} ${r.name.slice(0, 24).padEnd(26)} [${r.where}]`)
970
+ for (const r of hits) console.log(`${r.team.padEnd(28)} ${r.name.slice(0, 24).padEnd(26)} [${r.where}${r.kind === 'canvas' ? ` owner=${r.owner} members=${String(r.memberCount)}` : ''}]`)
947
971
  if (hits.length === 0) console.log(q === '' ? '(no teams)' : `(no team matches "${q}")`)
948
972
  return
949
973
  }
@@ -968,12 +992,14 @@ async function main() {
968
992
  listCapped(archived, 20, t => ` ${String(t.taskId)} ${String(t.outcome ?? '?')}${t.endedBy !== undefined && t.endedBy !== 'receipt' ? ` (${String(t.endedBy)})` : ''}${senderTail(t)}`)
969
993
  if (s.conservation != null) console.log(`conservation: conserved=${String(s.conservation.conserved)} delta=${String(s.conservation.delta)}`)
970
994
  if (s.settlement != null) console.log(`settlement: mode=${String(s.settlement.mode)} refusalsTotal=${String(s.settlement.refusalsTotal)} offChain=${String(s.settlement.offChain)}`)
995
+ if (s.settlement?.enforceRefused) console.log(`enforce refused: ${String(s.settlement.enforceRefused.reason)}; blockers=${JSON.stringify(s.settlement.enforceRefused.blockers)}`)
971
996
  return
972
997
  }
973
998
  if (cmd === 'status') {
974
999
  const s = await state()
975
1000
  const list = await control({ action: 'list' })
976
1001
  console.log(`host ${home()} version=${String(s.version ?? '?')} peers=${String((s.peers ?? []).length)}`)
1002
+ if (s.settlement?.enforceRefused) console.log(`settlement mode=${String(s.settlement.mode)}; enforce refused: ${String(s.settlement.enforceRefused.reason)}; blockers=${JSON.stringify(s.settlement.enforceRefused.blockers)}`)
977
1003
  for (const p of s.peers ?? []) console.log(` peer ${String(p.url).padEnd(34)} link=${String(p.link ?? 'stranger')}`)
978
1004
  const inflight = s.inFlight ?? []
979
1005
  if (inflight.length > 0) {
@@ -984,6 +1010,10 @@ async function main() {
984
1010
  // the address it dials is missing from the host's originClaimAudiences.
985
1011
  if (typeof s.unboundInbound === 'number') {
986
1012
  console.log(`claims unboundInbound=${String(s.unboundInbound)} boundMissInbound=${String(s.boundMissInbound ?? 0)} (dialled as ${claimTarget(home())?.audience ?? home()})`)
1013
+ // Who missed and by which origin: add that origin to the host's originClaimAudiences.
1014
+ for (const m of (Array.isArray(s.boundMissRecent) ? s.boundMissRecent : []).slice(0, 5)) {
1015
+ console.log(` miss ${String(m.caller)} dialled ${String(m.audience) || '(no origin)'} ×${String(m.count)}`)
1016
+ }
987
1017
  }
988
1018
  console.log(`deposit ${depositLine(list, identity().handle, 'OFFLINE')}`)
989
1019
  return
@@ -0,0 +1,29 @@
1
+ /** Read-only canvas ownership projection; never authorizes or selects a dispatch candidate. */
2
+ import { advertisedCanvasTeams } from "./peer-directory.js";
3
+ export function canvasOwners(local, ownUrl, peers) {
4
+ const rows = local.map(row => ({ ...row, owner: ownUrl, memberCount: row.members.length, local: true }));
5
+ const seen = new Set();
6
+ for (const { url, card } of peers)
7
+ for (const entry of card.expiresAt > Date.now() ? advertisedCanvasTeams(card) : []) {
8
+ const key = JSON.stringify([card.publicKey, card.session, entry.team]);
9
+ if (seen.has(key))
10
+ continue;
11
+ seen.add(key);
12
+ rows.push({ team: entry.team, name: entry.name, owner: url, memberCount: entry.members.length, local: false, members: entry.members, scopeMembers: entry.members.filter(ref => !(card.sessionTeams ?? []).some(session => session.team === ref)) });
13
+ }
14
+ return rows;
15
+ }
16
+ export function canvasMemberships(owners, handle, localId, resolveLocal) {
17
+ return owners.filter(row => ((row.scopeMembers ?? row.members).includes(handle) && (row.local || resolveLocal === undefined || resolveLocal(handle) === localId)) || (row.local && localId !== undefined && row.members.includes(localId))).map(({ members: _members, scopeMembers: _scopeMembers, ...row }) => row);
18
+ }
19
+ export function canvasFailure(team, owners, reason) {
20
+ if (!new RegExp('^.+/canvas/[^/]+$').test(team))
21
+ return reason;
22
+ const matches = owners.filter(row => row.team === team);
23
+ if (matches.length === 0)
24
+ return reason + ' — canvas-not-found: no matching team in the currently visible verified canvas directory (may be absent or outside your information scope; not proof of fleet-wide absence)';
25
+ const list = matches.map(row => row.owner + ' (' + row.team + ', members=' + row.memberCount + ')').join('; ');
26
+ if (matches.length > 1)
27
+ return reason + ' — canvas-multiple-owners: ' + list + ' — use an unambiguous operator-established team; do not guess a host';
28
+ return reason + ' — canvas-owner: ' + list + ' — existing refusal/prerequisite above still applies; an owner URL is discovery, not a direct-connect requirement or permission to bypass home';
29
+ }
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.37-d5df179\src\src\client\A2aControl.module.css.mjs
26
- const css = ".efpdCG_wrap{max-width:100%;display:inline-flex;position:relative}.efpdCG_trigger{align-items:center;gap:4px;display:inline-flex;position:relative}.efpdCG_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 efpdCG_badgePulse;position:absolute;top:-6px;right:-8px}@keyframes efpdCG_badgePulse{0%,to{box-shadow:0 0 #4c8bf573}50%{box-shadow:0 0 0 4px #4c8bf500}}@media (prefers-reduced-motion:reduce){.efpdCG_unreadBadge{animation:none}}.efpdCG_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}.efpdCG_title{font-size:13px;font-weight:600}.efpdCG_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}.efpdCG_stageRow{border-top:1px solid var(--dsh-border,#2a2a30);align-items:center;gap:8px;margin-top:4px;padding-top:8px;display:flex}.efpdCG_stageTitle{text-transform:uppercase;letter-spacing:.04em;opacity:.65;font-size:11px;font-weight:600}.efpdCG_stageLinks{gap:6px;display:flex}.efpdCG_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}.efpdCG_stageLink:hover,.efpdCG_stageLink:focus-visible{border-color:var(--dsh-accent,#4c8bf5)}.efpdCG_empty{opacity:.7;font-size:12px}.efpdCG_row{border:1px solid #0000;border-radius:8px;align-items:center;gap:10px;padding:8px;display:flex}.efpdCG_joined{border-color:var(--dsh-border,#2a2a30)}.efpdCG_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}.efpdCG_facts:hover{background:var(--dsh-surface-hover,#ffffff0f)}.efpdCG_nameRow{align-items:center;gap:6px;display:flex}.efpdCG_stateDot{border-radius:50%;flex:none;width:7px;height:7px}.efpdCG_stateDot.efpdCG_live{background:var(--dsh-accent,#4c8bf5)}.efpdCG_stateDot.efpdCG_live.efpdCG_joined{background:var(--dsh-success,#3fb96f)}.efpdCG_stateDot.efpdCG_cold{background:var(--dsh-warning,#d9a13c)}.efpdCG_name{text-overflow:ellipsis;white-space:nowrap;font-size:13px;font-weight:500;overflow:hidden}.efpdCG_description{opacity:.7;text-overflow:ellipsis;white-space:nowrap;font-size:12px;overflow:hidden}.efpdCG_team{opacity:.55;font-size:11px;font-family:var(--dsh-mono,monospace)}.efpdCG_note{opacity:.6;font-size:11px}.efpdCG_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}.efpdCG_searchInput:focus{outline:1px solid var(--dsh-accent,#4c8bf5)}.efpdCG_sessionGroup{flex-direction:column;gap:2px;display:flex}.efpdCG_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}.efpdCG_groupChevron{transition:transform .12s;display:inline-block}.efpdCG_groupChevronClosed{transform:rotate(-90deg)}.efpdCG_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}.efpdCG_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}.efpdCG_groupButton:hover{border-color:var(--dsh-border,#2a2a30);opacity:1}.efpdCG_groupPicker{border:1px dashed var(--dsh-border,#2a2a30);border-radius:8px;flex-wrap:wrap;gap:6px;width:100%;padding:8px;display:flex;position:relative}.efpdCG_groupOption{border:1px solid var(--dsh-border,#2a2a30);color:inherit;cursor:pointer;background:0 0;border-radius:999px;padding:2px 8px;font-size:11px}.efpdCG_groupOptionActive{border-color:var(--dsh-accent,#4c8bf5);color:var(--dsh-accent,#4c8bf5)}.efpdCG_groupNew{align-items:center;gap:6px;width:100%;margin-top:2px;display:flex}.efpdCG_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}.efpdCG_peerGrid{flex-wrap:wrap;gap:6px;display:flex}.efpdCG_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}.efpdCG_peerHost{text-overflow:ellipsis;white-space:nowrap;max-width:160px;overflow:hidden}.efpdCG_peerScore{font-family:var(--dsh-mono,monospace);opacity:.6}.efpdCG_activityList{flex-direction:column;gap:2px;display:flex}.efpdCG_activityRow{border-radius:4px;align-items:center;gap:8px;padding:2px 4px;font-size:11px;display:flex}.efpdCG_activityRow[data-ok=false]{opacity:.55}.efpdCG_activityDir{flex:none;font-weight:600}.efpdCG_activityDir.efpdCG_in{color:var(--dsh-success,#3fb96f)}.efpdCG_activityDir.efpdCG_out{color:var(--dsh-accent,#4c8bf5)}.efpdCG_activityTeam{text-overflow:ellipsis;white-space:nowrap;min-width:0;font-family:var(--dsh-mono,monospace);opacity:.8;flex:1;overflow:hidden}.efpdCG_activityPeer{text-overflow:ellipsis;white-space:nowrap;opacity:.55;max-width:90px;overflow:hidden}.efpdCG_activityTime{opacity:.5;font-family:var(--dsh-mono,monospace);flex:none}.efpdCG_activityRow[data-jump=true]{cursor:pointer}.efpdCG_activityRow[data-jump=true]:hover{background:var(--dsh-surface-hover,#ffffff0f)}.efpdCG_fresh{animation:1.2s ease-out 3 efpdCG_freshFlash}@keyframes efpdCG_freshFlash{0%{background:#4c8bf538}to{background:0 0}}@media (prefers-reduced-motion:reduce){.efpdCG_fresh{background:#4c8bf51f;animation:none}}.efpdCG_inFlightList{flex-direction:column;gap:2px;margin-bottom:4px;display:flex}.efpdCG_inFlightRow{border:1px dashed var(--dsh-border,#2a2a30);border-radius:4px;align-items:center;gap:8px;padding:3px 4px;font-size:11px;display:flex}.efpdCG_inFlightPulse{background:var(--dsh-accent,#4c8bf5);border-radius:50%;flex:none;width:7px;height:7px;animation:1.1s ease-in-out infinite efpdCG_flightPulse}@keyframes efpdCG_flightPulse{0%,to{opacity:1;transform:scale(1)}50%{opacity:.5;transform:scale(.55)}}@media (prefers-reduced-motion:reduce){.efpdCG_inFlightPulse{animation:none}}.efpdCG_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}.efpdCG_inFlightStale{opacity:.55}.efpdCG_canvasCreate{align-items:center;gap:6px;margin-bottom:4px;display:flex}.efpdCG_canvasCreate .efpdCG_groupInput{flex:1;min-width:0}.efpdCG_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}.efpdCG_canvasChip>span:not([class]){white-space:nowrap;text-overflow:ellipsis;min-width:0;overflow:hidden}.efpdCG_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}.efpdCG_canvasChipRemove:hover{color:var(--dsh-color-text-primary,#eee)}.efpdCG_canvasChipRemove{transition:color .14s ease-out}@media (prefers-reduced-motion:reduce){.efpdCG_canvasChipRemove{transition:none}}.efpdCG_canvasChipRemove:focus-visible{outline:2px solid var(--dsh-color-primary,#4f8cff);outline-offset:1px;border-radius:4px}.efpdCG_canvasActions{gap:6px;margin-top:4px;display:flex}.efpdCG_inFlightStale .efpdCG_inFlightPulse{opacity:.4;animation:none}.efpdCG_failMark{color:#f87171;flex-shrink:0;font-size:11px}.efpdCG_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.38-35f5893\src\src\client\A2aControl.module.css.mjs
26
+ const css = ".pFp7aa_wrap{max-width:100%;display:inline-flex;position:relative}.pFp7aa_trigger{align-items:center;gap:4px;display:inline-flex;position:relative}.pFp7aa_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 pFp7aa_badgePulse;position:absolute;top:-6px;right:-8px}@keyframes pFp7aa_badgePulse{0%,to{box-shadow:0 0 #4c8bf573}50%{box-shadow:0 0 0 4px #4c8bf500}}@media (prefers-reduced-motion:reduce){.pFp7aa_unreadBadge{animation:none}}.pFp7aa_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}.pFp7aa_title{font-size:13px;font-weight:600}.pFp7aa_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}.pFp7aa_stageRow{border-top:1px solid var(--dsh-border,#2a2a30);align-items:center;gap:8px;margin-top:4px;padding-top:8px;display:flex}.pFp7aa_stageTitle{text-transform:uppercase;letter-spacing:.04em;opacity:.65;font-size:11px;font-weight:600}.pFp7aa_stageLinks{gap:6px;display:flex}.pFp7aa_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}.pFp7aa_stageLink:hover,.pFp7aa_stageLink:focus-visible{border-color:var(--dsh-accent,#4c8bf5)}.pFp7aa_empty{opacity:.7;font-size:12px}.pFp7aa_row{border:1px solid #0000;border-radius:8px;align-items:center;gap:10px;padding:8px;display:flex}.pFp7aa_joined{border-color:var(--dsh-border,#2a2a30)}.pFp7aa_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}.pFp7aa_facts:hover{background:var(--dsh-surface-hover,#ffffff0f)}.pFp7aa_nameRow{align-items:center;gap:6px;display:flex}.pFp7aa_stateDot{border-radius:50%;flex:none;width:7px;height:7px}.pFp7aa_stateDot.pFp7aa_live{background:var(--dsh-accent,#4c8bf5)}.pFp7aa_stateDot.pFp7aa_live.pFp7aa_joined{background:var(--dsh-success,#3fb96f)}.pFp7aa_stateDot.pFp7aa_cold{background:var(--dsh-warning,#d9a13c)}.pFp7aa_name{text-overflow:ellipsis;white-space:nowrap;font-size:13px;font-weight:500;overflow:hidden}.pFp7aa_description{opacity:.7;text-overflow:ellipsis;white-space:nowrap;font-size:12px;overflow:hidden}.pFp7aa_team{opacity:.55;font-size:11px;font-family:var(--dsh-mono,monospace)}.pFp7aa_note{opacity:.6;font-size:11px}.pFp7aa_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}.pFp7aa_searchInput:focus{outline:1px solid var(--dsh-accent,#4c8bf5)}.pFp7aa_sessionGroup{flex-direction:column;gap:2px;display:flex}.pFp7aa_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}.pFp7aa_groupChevron{transition:transform .12s;display:inline-block}.pFp7aa_groupChevronClosed{transform:rotate(-90deg)}.pFp7aa_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}.pFp7aa_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}.pFp7aa_groupButton:hover{border-color:var(--dsh-border,#2a2a30);opacity:1}.pFp7aa_groupPicker{border:1px dashed var(--dsh-border,#2a2a30);border-radius:8px;flex-wrap:wrap;gap:6px;width:100%;padding:8px;display:flex;position:relative}.pFp7aa_groupOption{border:1px solid var(--dsh-border,#2a2a30);color:inherit;cursor:pointer;background:0 0;border-radius:999px;padding:2px 8px;font-size:11px}.pFp7aa_groupOptionActive{border-color:var(--dsh-accent,#4c8bf5);color:var(--dsh-accent,#4c8bf5)}.pFp7aa_groupNew{align-items:center;gap:6px;width:100%;margin-top:2px;display:flex}.pFp7aa_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}.pFp7aa_peerGrid{flex-wrap:wrap;gap:6px;display:flex}.pFp7aa_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}.pFp7aa_peerHost{text-overflow:ellipsis;white-space:nowrap;max-width:160px;overflow:hidden}.pFp7aa_peerScore{font-family:var(--dsh-mono,monospace);opacity:.6}.pFp7aa_activityList{flex-direction:column;gap:2px;display:flex}.pFp7aa_activityRow{border-radius:4px;align-items:center;gap:8px;padding:2px 4px;font-size:11px;display:flex}.pFp7aa_activityRow[data-ok=false]{opacity:.55}.pFp7aa_activityDir{flex:none;font-weight:600}.pFp7aa_activityDir.pFp7aa_in{color:var(--dsh-success,#3fb96f)}.pFp7aa_activityDir.pFp7aa_out{color:var(--dsh-accent,#4c8bf5)}.pFp7aa_activityTeam{text-overflow:ellipsis;white-space:nowrap;min-width:0;font-family:var(--dsh-mono,monospace);opacity:.8;flex:1;overflow:hidden}.pFp7aa_activityPeer{text-overflow:ellipsis;white-space:nowrap;opacity:.55;max-width:90px;overflow:hidden}.pFp7aa_activityTime{opacity:.5;font-family:var(--dsh-mono,monospace);flex:none}.pFp7aa_activityRow[data-jump=true]{cursor:pointer}.pFp7aa_activityRow[data-jump=true]:hover{background:var(--dsh-surface-hover,#ffffff0f)}.pFp7aa_fresh{animation:1.2s ease-out 3 pFp7aa_freshFlash}@keyframes pFp7aa_freshFlash{0%{background:#4c8bf538}to{background:0 0}}@media (prefers-reduced-motion:reduce){.pFp7aa_fresh{background:#4c8bf51f;animation:none}}.pFp7aa_inFlightList{flex-direction:column;gap:2px;margin-bottom:4px;display:flex}.pFp7aa_inFlightRow{border:1px dashed var(--dsh-border,#2a2a30);border-radius:4px;align-items:center;gap:8px;padding:3px 4px;font-size:11px;display:flex}.pFp7aa_inFlightPulse{background:var(--dsh-accent,#4c8bf5);border-radius:50%;flex:none;width:7px;height:7px;animation:1.1s ease-in-out infinite pFp7aa_flightPulse}@keyframes pFp7aa_flightPulse{0%,to{opacity:1;transform:scale(1)}50%{opacity:.5;transform:scale(.55)}}@media (prefers-reduced-motion:reduce){.pFp7aa_inFlightPulse{animation:none}}.pFp7aa_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}.pFp7aa_inFlightStale{opacity:.55}.pFp7aa_canvasCreate{align-items:center;gap:6px;margin-bottom:4px;display:flex}.pFp7aa_canvasCreate .pFp7aa_groupInput{flex:1;min-width:0}.pFp7aa_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}.pFp7aa_canvasChip>span:not([class]){white-space:nowrap;text-overflow:ellipsis;min-width:0;overflow:hidden}.pFp7aa_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}.pFp7aa_canvasChipRemove:hover{color:var(--dsh-color-text-primary,#eee)}.pFp7aa_canvasChipRemove{transition:color .14s ease-out}@media (prefers-reduced-motion:reduce){.pFp7aa_canvasChipRemove{transition:none}}.pFp7aa_canvasChipRemove:focus-visible{outline:2px solid var(--dsh-color-primary,#4f8cff);outline-offset:1px;border-radius:4px}.pFp7aa_canvasActions{gap:6px;margin-top:4px;display:flex}.pFp7aa_inFlightStale .pFp7aa_inFlightPulse{opacity:.4;animation:none}.pFp7aa_failMark{color:#f87171;flex-shrink:0;font-size:11px}.pFp7aa_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": "efpdCG_activityDir",
37
- "activityList": "efpdCG_activityList",
38
- "activityPeer": "efpdCG_activityPeer",
39
- "activityRow": "efpdCG_activityRow",
40
- "activityTeam": "efpdCG_activityTeam",
41
- "activityTime": "efpdCG_activityTime",
42
- "badgePulse": "efpdCG_badgePulse",
43
- "canvasActions": "efpdCG_canvasActions",
44
- "canvasChip": "efpdCG_canvasChip",
45
- "canvasChipRemove": "efpdCG_canvasChipRemove",
46
- "canvasCreate": "efpdCG_canvasCreate",
47
- "cold": "efpdCG_cold",
48
- "description": "efpdCG_description",
49
- "empty": "efpdCG_empty",
50
- "facts": "efpdCG_facts",
51
- "failMark": "efpdCG_failMark",
52
- "flightPulse": "efpdCG_flightPulse",
53
- "fresh": "efpdCG_fresh",
54
- "freshFlash": "efpdCG_freshFlash",
55
- "groupButton": "efpdCG_groupButton",
56
- "groupChevron": "efpdCG_groupChevron",
57
- "groupChevronClosed": "efpdCG_groupChevronClosed",
58
- "groupHead": "efpdCG_groupHead",
59
- "groupInput": "efpdCG_groupInput",
60
- "groupNew": "efpdCG_groupNew",
61
- "groupOption": "efpdCG_groupOption",
62
- "groupOptionActive": "efpdCG_groupOptionActive",
63
- "groupPicker": "efpdCG_groupPicker",
64
- "groupTag": "efpdCG_groupTag",
65
- "in": "efpdCG_in",
66
- "inFlightList": "efpdCG_inFlightList",
67
- "inFlightPulse": "efpdCG_inFlightPulse",
68
- "inFlightRow": "efpdCG_inFlightRow",
69
- "inFlightStale": "efpdCG_inFlightStale",
70
- "joined": "efpdCG_joined",
71
- "live": "efpdCG_live",
72
- "name": "efpdCG_name",
73
- "nameRow": "efpdCG_nameRow",
74
- "note": "efpdCG_note",
75
- "out": "efpdCG_out",
76
- "peerChip": "efpdCG_peerChip",
77
- "peerGrid": "efpdCG_peerGrid",
78
- "peerHost": "efpdCG_peerHost",
79
- "peerScore": "efpdCG_peerScore",
80
- "popover": "efpdCG_popover",
81
- "row": "efpdCG_row",
82
- "searchInput": "efpdCG_searchInput",
83
- "sectionTitle": "efpdCG_sectionTitle",
84
- "sessionGroup": "efpdCG_sessionGroup",
85
- "stageLink": "efpdCG_stageLink",
86
- "stageLinks": "efpdCG_stageLinks",
87
- "stageRow": "efpdCG_stageRow",
88
- "stageTitle": "efpdCG_stageTitle",
89
- "staleNote": "efpdCG_staleNote",
90
- "stateDot": "efpdCG_stateDot",
91
- "team": "efpdCG_team",
92
- "title": "efpdCG_title",
93
- "trigger": "efpdCG_trigger",
94
- "unreadBadge": "efpdCG_unreadBadge",
95
- "versionTag": "efpdCG_versionTag",
96
- "wrap": "efpdCG_wrap"
36
+ "activityDir": "pFp7aa_activityDir",
37
+ "activityList": "pFp7aa_activityList",
38
+ "activityPeer": "pFp7aa_activityPeer",
39
+ "activityRow": "pFp7aa_activityRow",
40
+ "activityTeam": "pFp7aa_activityTeam",
41
+ "activityTime": "pFp7aa_activityTime",
42
+ "badgePulse": "pFp7aa_badgePulse",
43
+ "canvasActions": "pFp7aa_canvasActions",
44
+ "canvasChip": "pFp7aa_canvasChip",
45
+ "canvasChipRemove": "pFp7aa_canvasChipRemove",
46
+ "canvasCreate": "pFp7aa_canvasCreate",
47
+ "cold": "pFp7aa_cold",
48
+ "description": "pFp7aa_description",
49
+ "empty": "pFp7aa_empty",
50
+ "facts": "pFp7aa_facts",
51
+ "failMark": "pFp7aa_failMark",
52
+ "flightPulse": "pFp7aa_flightPulse",
53
+ "fresh": "pFp7aa_fresh",
54
+ "freshFlash": "pFp7aa_freshFlash",
55
+ "groupButton": "pFp7aa_groupButton",
56
+ "groupChevron": "pFp7aa_groupChevron",
57
+ "groupChevronClosed": "pFp7aa_groupChevronClosed",
58
+ "groupHead": "pFp7aa_groupHead",
59
+ "groupInput": "pFp7aa_groupInput",
60
+ "groupNew": "pFp7aa_groupNew",
61
+ "groupOption": "pFp7aa_groupOption",
62
+ "groupOptionActive": "pFp7aa_groupOptionActive",
63
+ "groupPicker": "pFp7aa_groupPicker",
64
+ "groupTag": "pFp7aa_groupTag",
65
+ "in": "pFp7aa_in",
66
+ "inFlightList": "pFp7aa_inFlightList",
67
+ "inFlightPulse": "pFp7aa_inFlightPulse",
68
+ "inFlightRow": "pFp7aa_inFlightRow",
69
+ "inFlightStale": "pFp7aa_inFlightStale",
70
+ "joined": "pFp7aa_joined",
71
+ "live": "pFp7aa_live",
72
+ "name": "pFp7aa_name",
73
+ "nameRow": "pFp7aa_nameRow",
74
+ "note": "pFp7aa_note",
75
+ "out": "pFp7aa_out",
76
+ "peerChip": "pFp7aa_peerChip",
77
+ "peerGrid": "pFp7aa_peerGrid",
78
+ "peerHost": "pFp7aa_peerHost",
79
+ "peerScore": "pFp7aa_peerScore",
80
+ "popover": "pFp7aa_popover",
81
+ "row": "pFp7aa_row",
82
+ "searchInput": "pFp7aa_searchInput",
83
+ "sectionTitle": "pFp7aa_sectionTitle",
84
+ "sessionGroup": "pFp7aa_sessionGroup",
85
+ "stageLink": "pFp7aa_stageLink",
86
+ "stageLinks": "pFp7aa_stageLinks",
87
+ "stageRow": "pFp7aa_stageRow",
88
+ "stageTitle": "pFp7aa_stageTitle",
89
+ "staleNote": "pFp7aa_staleNote",
90
+ "stateDot": "pFp7aa_stateDot",
91
+ "team": "pFp7aa_team",
92
+ "title": "pFp7aa_title",
93
+ "trigger": "pFp7aa_trigger",
94
+ "unreadBadge": "pFp7aa_unreadBadge",
95
+ "versionTag": "pFp7aa_versionTag",
96
+ "wrap": "pFp7aa_wrap"
97
97
  };
98
98
  //#endregion
99
99
  //#region lib/client/A2aControl.js
@@ -2,6 +2,18 @@
2
2
  import { DEFAULT_HOP_BUDGET, guardHeader, mintGuard } from "./recursion-guard.js";
3
3
  import { briefOf } from "./task-ledger.js";
4
4
  import { parseReceipt } from "./receipt.js";
5
+ /** Birth barrier exists only for eligible, actively dispatched tasks; receipts never reserve a slot. */
6
+ export class ForwardBirths {
7
+ pending = new Map();
8
+ wait(taskId) { return this.pending.get(taskId); }
9
+ begin(taskId) {
10
+ if (this.pending.size >= 64 || this.pending.has(taskId))
11
+ return undefined;
12
+ let release;
13
+ this.pending.set(taskId, new Promise(resolve => { release = resolve; }));
14
+ return () => { this.pending.delete(taskId); release(); };
15
+ }
16
+ }
5
17
  export function createDirectForward(d) {
6
18
  return async (request) => {
7
19
  const refuse = (why) => ({ ok: false, code: -32000, task_id: request.taskId, error: 'not forwarded: ' + why });
@@ -22,39 +34,49 @@ export function createDirectForward(d) {
22
34
  for (const url of directory.urls) {
23
35
  if (!d.deposited(request.caller))
24
36
  return refuse('local CLI deposit expired before dispatch');
37
+ const peerCard = await directory.card(url);
38
+ const receivers = request.team.includes('/canvas/') ? peerCard?.canvasOrg?.find(row => row.team === request.team)?.members.filter(member => member !== request.caller) : undefined;
25
39
  if (request.team.includes('/canvas/')) {
26
- const card = await directory.card(url);
27
- const org = card?.canvasOrg?.find(row => row.team === request.team);
28
- const receivers = org?.members.filter(member => member !== request.caller) ?? [];
29
- if (receivers.length === 0 || receivers.some(member => !d.memberLinked(member))) {
40
+ if (receivers === undefined || receivers.length === 0 || receivers.some(member => !d.memberLinked(member))) {
30
41
  failures.push(url + ': forwarding a canvas requires a direct operator link to every receiving member for its receipt return path');
31
42
  continue;
32
43
  }
33
44
  }
34
- const answer = await d.dispatch(url, request, guard);
35
- const result = !answer.ok && answer.ownBudgetExhausted === true ? { ...answer, code: -32005 } : answer;
36
- d.activity(request.team, url, result.ok);
37
- if (result.ok || result.code === -32005) {
38
- // A receipt owes no receipt; native bridge rounds have no A2A receipt contract.
39
- // Match routeTrunk/trackOwedTask rather than manufacturing an unpayable debt.
40
- if (parseReceipt(request.message) !== null)
41
- return { ...result, receipt: 'none' };
42
- if (result.ok && result.bridge !== undefined)
43
- return { ...result, receipt: 'none' };
44
- const card = await directory.card(url);
45
- const birth = { forwarded: true, ...(card?.publicKey ? { peerKey: card.publicKey } : {}), ...(card?.session ? { peerHost: card.session } : {}), deliveredTo: result.ok ? result.deliveredTo ?? request.team : request.team };
46
- // Exactly one outbound row. A completed sync response is receiptless bookkeeping.
47
- if (result.ok && (result.task_status === 'TASK_STATE_COMPLETED' || result.receipt === 'none'))
48
- d.ledger.trackReceiptless(request.taskId, request.team, url, request.contextId, briefOf(request.message), { kind: 'cli', id: request.caller }, birth);
49
- else
50
- d.ledger.track(request.taskId, request.team, url, request.contextId, briefOf(request.message), { kind: 'cli', id: request.caller }, birth);
51
- return result;
45
+ // Reserve only after all eligibility/receiver-link gates. A receipt owes no receipt.
46
+ const receipt = parseReceipt(request.message) !== null;
47
+ const release = receipt ? () => { } : d.births.begin(request.taskId);
48
+ if (release === undefined)
49
+ return refuse('forward receipt correlation capacity is full');
50
+ try {
51
+ // The same selected-peer snapshot supplies both return-link gate and settlement birth.
52
+ const answer = await d.dispatch(url, request, guard);
53
+ const result = !answer.ok && answer.ownBudgetExhausted === true ? { ...answer, code: -32005 } : answer;
54
+ d.activity(request.team, url, result.ok);
55
+ if (result.ok || result.code === -32005) {
56
+ // A receipt owes no receipt; native bridge rounds have no A2A receipt contract.
57
+ // Match routeTrunk/trackOwedTask rather than manufacturing an unpayable debt.
58
+ if (parseReceipt(request.message) !== null)
59
+ return { ...result, receipt: 'none' };
60
+ if (result.ok && result.bridge !== undefined)
61
+ return { ...result, receipt: 'none' };
62
+ const deliveredTo = result.ok ? result.deliveredTo : undefined;
63
+ const birth = { forwarded: true, ...(request.auth !== undefined ? { auth: request.auth } : {}), ...(peerCard?.publicKey ? { peerKey: peerCard.publicKey } : {}), ...(peerCard?.session ? { peerHost: peerCard.session } : {}), ...(deliveredTo !== undefined ? { deliveredTo } : {}), ...(receivers !== undefined ? { receiverMembers: receivers } : {}) };
64
+ // Exactly one outbound row. A completed sync response is receiptless bookkeeping.
65
+ if (result.ok && (result.task_status === 'TASK_STATE_COMPLETED' || result.receipt === 'none'))
66
+ d.ledger.trackReceiptless(request.taskId, request.team, url, request.contextId, briefOf(request.message), { kind: 'cli', id: request.caller }, birth);
67
+ else
68
+ d.ledger.track(request.taskId, request.team, url, request.contextId, briefOf(request.message), { kind: 'cli', id: request.caller }, birth);
69
+ return result;
70
+ }
71
+ // The shared dispatcher's sync recovery query may prove a definitive failed
72
+ // outcome; like routeTrunk, that is an eligible miss, not an uncertain -32005.
73
+ failures.push(url + ': ' + result.error);
74
+ if (result.code === -32002 || result.code === -32003)
75
+ return { ...result, task_id: request.taskId };
76
+ }
77
+ finally {
78
+ release();
52
79
  }
53
- // The shared dispatcher's sync recovery query may prove a definitive failed
54
- // outcome; like routeTrunk, that is an eligible miss, not an uncertain -32005.
55
- failures.push(url + ': ' + result.error);
56
- if (result.code === -32002 || result.code === -32003)
57
- return { ...result, task_id: request.taskId };
58
80
  }
59
81
  return { ok: false, code: -32004, task_id: request.taskId, error: 'forwarding to "' + request.team + '" failed: ' + (failures.join('; ') || 'no eligible peer candidate') };
60
82
  };
@@ -125,6 +125,7 @@ export function registerAdminFaces(webServer, d) {
125
125
  const body = JSON.stringify({
126
126
  nodes: true,
127
127
  zone: config.team,
128
+ ownerHost: d.ownCardUrl,
128
129
  version: PLUGIN_VERSION,
129
130
  // gh#193: a dark host says so — an empty remote list is not an outage.
130
131
  ...(config.isolated ? { isolated: true } : {}),
@@ -204,6 +205,9 @@ export function registerAdminFaces(webServer, d) {
204
205
  taskId: task.taskId, team: task.team, peer: task.peer, startedAt: task.startedAt, status: task.status,
205
206
  direction: task.direction ?? 'outbound', receiptExpected: task.receiptExpected !== false,
206
207
  ...(task.answeredAt !== undefined ? { answeredAt: task.answeredAt } : {}),
208
+ ...(task.forwarded === true ? { forwarded: true } : {}),
209
+ ...(task.deliveredTo !== undefined ? { deliveredTo: task.deliveredTo } : {}),
210
+ ...(task.receiverMembers !== undefined ? { receiverMembers: task.receiverMembers } : {}),
207
211
  // The dispatch brief rides every row: a task id and a team
208
212
  // say nothing about WHAT is owed — supervision cannot tell
209
213
  // a nudge from an abandon without it.
@@ -256,6 +260,8 @@ export function registerAdminFaces(webServer, d) {
256
260
  ...(row.summary !== undefined && row.summary !== '' ? { summary: row.summary.slice(0, 160) } : {}),
257
261
  ...(row.sender !== undefined ? { sender: row.sender } : {}),
258
262
  ...(row.forwarded === true ? { forwarded: true } : {}),
263
+ ...(row.deliveredTo !== undefined ? { deliveredTo: row.deliveredTo } : {}),
264
+ ...(row.receiverMembers !== undefined ? { receiverMembers: row.receiverMembers } : {}),
259
265
  ...(row.auth !== undefined ? { auth: row.auth } : {}),
260
266
  // #114 S4: who settled it and how they stood on the chain.
261
267
  ...(row.settledBy !== undefined ? { settledBy: row.settledBy } : {}),
@@ -270,6 +276,8 @@ export function registerAdminFaces(webServer, d) {
270
276
  const roles = archive.flatMap(row => (row.settledBy !== undefined ? [row.settledBy.chain_role] : []));
271
277
  return {
272
278
  mode: config.settlementAuth,
279
+ ...(d.enforceRefused !== undefined ? { enforceRefused: d.enforceRefused } : {}),
280
+ enforceBlockers: taskLedger.settlementEnforceBlockers(),
273
281
  offChain: roles.filter(r => r === 'off-chain').length,
274
282
  legacy: roles.filter(r => r === 'legacy').length,
275
283
  compat: roles.filter(r => r === 'compat').length,
@@ -327,6 +335,8 @@ export function registerAdminFaces(webServer, d) {
327
335
  // Of those, envelopes whose bound form missed here: the sender
328
336
  // dials an origin missing from originClaimAudiences.
329
337
  boundMissInbound: d.boundMissInboundCount,
338
+ // Who missed, by verified caller and the origin it dialled (newest first, ≤16).
339
+ boundMissRecent: d.boundMissRecent,
330
340
  // gh#113: CLI mailbox loss, all handles — mail the 64-row cap
331
341
  // pushed out, and mail cut at 4000 chars. The team verifier
332
342
  // holds `dropped` to zero.
package/lib/faces/wire.js CHANGED
@@ -53,7 +53,7 @@ export function settlerClaimFields(claim, originOf) {
53
53
  * @param d - the captures listed on {@link WireDeps}.
54
54
  */
55
55
  export function registerWireFaces(webServer, d) {
56
- const { ctx, logger, config, cliDeposits, canvasStore, canvasNameReserved, sessionNodes, joinedSessions, idempotencyStore, verifyOriginClaim, remoteCliProof, callerLinkedToThisHost, recordActivity, secretEqual, a2aCallerRefusal, a2aCallerAdmission, allowlistVersionNow, observeAuthorization, scanSessionLogList, readSessionLogTail, taskLedger, refuseIfFlooded, refuseIfQueueFull, settleAndAnnounce, session, resolveAgentForTeam, parseCanvasTeamName, canvasLiveAgent, wakeColdTeam, probeConsumption, sessionTeamOf, localSessionOfHandle, steerRelay, fromClause, sanitizeFromClause, armReceiptAutosend, eventCountOf, receiptExpectationOf, armAsyncNudge, liveAgent, callerIsAgent, selfRouteText, noWakeFaceText, archivedColdMatchText, noColdMatchText, nativeTeamsPrepare, nativeTeamsRound, routeIntoAgent, } = d;
56
+ const { ctx, logger, config, cliDeposits, canvasStore, canvasNameReserved, sessionNodes, joinedSessions, idempotencyStore, verifyOriginClaim, remoteCliProof, callerLinkedToThisHost, recordActivity, secretEqual, a2aCallerRefusal, a2aCallerAdmission, allowlistVersionNow, observeAuthorization, scanSessionLogList, readSessionLogTail, taskLedger, refuseIfFlooded, refuseIfQueueFull, settleAndAnnounce, waitForForwardBirth, session, resolveAgentForTeam, parseCanvasTeamName, canvasLiveAgent, wakeColdTeam, probeConsumption, sessionTeamOf, localSessionOfHandle, steerRelay, fromClause, sanitizeFromClause, armReceiptAutosend, eventCountOf, receiptExpectationOf, armAsyncNudge, liveAgent, callerIsAgent, selfRouteText, noWakeFaceText, archivedColdMatchText, noColdMatchText, nativeTeamsPrepare, nativeTeamsRound, routeIntoAgent, } = d;
57
57
  // gh#45 CLI 户口卡面 (peer face, unsigned card serve): the deposited
58
58
  // CLI's SIGNED card is served verbatim under its own base URL — an
59
59
  // OriginClaim verification of a CLI envelope fetches exactly here
@@ -559,6 +559,8 @@ export function registerWireFaces(webServer, d) {
559
559
  // #114 S4: the settler is this delivery's caller, as proven here. A CLI identity's claim URL
560
560
  // is not a host's origin (its origin is unconstrained), so it never vouches for one (review of PR #140 P1-2).
561
561
  // #139: the claim's card key names the host whatever address spelling it signed with.
562
+ // The response can race a fast peer receipt. Wait for this exact forwarding birth before judging or acknowledging it.
563
+ await waitForForwardBirth(message);
562
564
  const refusedSettlement = settleAndAnnounce(message, { caller, proof: callerProof, ...settlerClaimFields(claim, originOf), ingress: 'wire' });
563
565
  // #139 (design §4.4): every answer this delivery gets carries the refusal.
564
566
  if (refusedSettlement !== undefined)
@@ -588,12 +590,14 @@ export function registerWireFaces(webServer, d) {
588
590
  d.touchWakeDemand();
589
591
  const forwardMiss = async (original) => {
590
592
  const result = await d.forwardDirect({ team, message, caller, taskId, contextId, noWait,
593
+ auth: buildAuthorization(admission, { caller, proof: callerProof, ...(claim.ok ? { claim: { caller: claim.caller, origin: originOf(claim.callerUrl) } } : {}), ingress: 'wire', allowlistVersion: admittedAt.version, target: team, receiptTarget: caller, receivedAt: admittedAt.at }),
591
594
  localCliProof: claim.ok && claim.cli === true && claim.bound && claim.caller === caller, guarded: req.headers['x-a2a-guard'] !== undefined }).catch((error) => ({ ok: false, error: `forwarding unavailable: ${String(error)}`, code: -32000, task_id: taskId }));
595
+ const failure = result.ok ? '' : await d.diagnoseCanvas(team, original + ' — ' + result.error, claim.ok && claim.cli === true && claim.bound && claim.caller === caller ? caller : undefined);
592
596
  if (!result.ok && result.code !== -32005)
593
- idempotencyStore.recordOutcome(taskId, { status: 'failed', error: original + ' — ' + result.error });
597
+ idempotencyStore.recordOutcome(taskId, { status: 'failed', error: failure });
594
598
  else if (result.ok && result.task_status === 'TASK_STATE_COMPLETED')
595
599
  idempotencyStore.recordOutcome(taskId, { status: 'completed', reply: result.reply });
596
- const payload = JSON.stringify(result.ok ? { routed: true, ...(result.task_status === 'TASK_STATE_DELIVERED' ? { delivered: true } : { result: { text: result.reply } }), team, task_id: taskId, context_id: contextId, task_status: result.task_status, consumed: result.consumed, bridge: result.bridge, receipt: result.receipt, forwarded: true } : { error: original + ' — ' + result.error, code: result.code ?? -32000, receipt: result.receipt, task_id: taskId, team, task_status: result.code === -32005 ? 'TASK_STATE_DELIVERED' : 'TASK_STATE_FAILED' });
600
+ const payload = JSON.stringify(result.ok ? { routed: true, ...(result.task_status === 'TASK_STATE_DELIVERED' ? { delivered: true } : { result: { text: result.reply } }), team, task_id: taskId, context_id: contextId, task_status: result.task_status, consumed: result.consumed, bridge: result.bridge, receipt: result.receipt, forwarded: true } : { error: failure, code: result.code ?? -32000, receipt: result.receipt, task_id: taskId, team, task_status: result.code === -32005 ? 'TASK_STATE_DELIVERED' : 'TASK_STATE_FAILED' });
597
601
  res.writeHead(200, { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(payload) });
598
602
  res.end(payload);
599
603
  };
@@ -809,7 +813,16 @@ export function registerWireFaces(webServer, d) {
809
813
  })();
810
814
  return;
811
815
  }
812
- void routeIntoAgent(team, message, caller, taskId, proofClause, directHeader).then(async (outcome) => {
816
+ let syncTarget;
817
+ // A forwarded synchronous delivery still owes its automatic receipt after the HTTP waiter expires.
818
+ const syncSteered = req.headers['x-a2a-guard'] === undefined || parseReceipt(message) !== null ? undefined : (target, floor) => {
819
+ syncTarget = sessionTeamOf(target);
820
+ const receiptTarget = callback !== '' ? callback : caller;
821
+ const auth = buildAuthorization(admission, { caller, proof: callerProof, ...(claim.ok ? { claim: { caller: claim.caller, origin: originOf(claim.callerUrl) } } : {}), ...(admission.rule === 'remote-ref-cli' && claim.ok ? { carrierUrl: originOf(claim.callerUrl) } : {}), ingress: 'wire', allowlistVersion: admittedAt.version, target: team, receiptTarget, receivedAt: admittedAt.at });
822
+ taskLedger.trackInbound(taskId, team, receiptTarget, contextId, receiptExpectationOf(receiptTarget) !== 'process-label', briefOf(message), auth, syncTarget === team ? undefined : syncTarget);
823
+ armReceiptAutosend(target, taskId, receiptTarget, message, floor);
824
+ };
825
+ void routeIntoAgent(team, message, caller, taskId, proofClause, directHeader, undefined, syncSteered).then(async (outcome) => {
813
826
  if (!outcome.ok && outcome.localMiss) {
814
827
  await forwardMiss(outcome.error);
815
828
  return;
@@ -840,7 +853,7 @@ export function registerWireFaces(webServer, d) {
840
853
  team,
841
854
  session,
842
855
  result: { text: outcome.reply },
843
- ...(req.headers['x-a2a-guard'] !== undefined ? ((target) => target === undefined ? {} : { delivered_to: sessionTeamOf(target) })(resolveAgentForTeam(team) ?? canvasLiveAgent(parseCanvasTeamName(team))) : {}),
856
+ ...(syncTarget !== undefined ? { delivered_to: syncTarget } : {}),
844
857
  task_id: taskId,
845
858
  context_id: contextId,
846
859
  task_status: outcome.status ?? 'TASK_STATE_COMPLETED',