@nelsonlongxiang/dsh-open-a2a-net 0.6.30 → 0.6.32

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.
Files changed (89) hide show
  1. package/README.md +4 -1
  2. package/README_zh.md +3 -0
  3. package/assets/nexusDist/assets/index-D5H8R8de.js +9 -0
  4. package/assets/nexusDist/assets/index-D9GYZyGZ.css +1 -0
  5. package/assets/nexusDist/index.html +2 -2
  6. package/cli/a2a.mjs +445 -57
  7. package/lib/a2a-client.js +9 -4
  8. package/lib/auth-observer.js +254 -0
  9. package/lib/blob-store.js +210 -0
  10. package/lib/card.js +4 -2
  11. package/lib/cli-deposit.js +122 -7
  12. package/lib/client.js +64 -64
  13. package/lib/client.js.map +1 -1
  14. package/lib/faces/admin.js +133 -43
  15. package/lib/faces/canvas-notify.js +12 -3
  16. package/lib/faces/files.js +155 -0
  17. package/lib/faces/nodes.js +171 -0
  18. package/lib/faces/session-log.js +162 -0
  19. package/lib/faces/wire.js +206 -39
  20. package/lib/index.js +809 -1720
  21. package/lib/joined-store.js +23 -0
  22. package/lib/ledger-conservation.js +97 -0
  23. package/lib/legacy/teams-bridge-inbound.js +156 -0
  24. package/lib/node-bind.js +534 -0
  25. package/lib/node-github.js +72 -0
  26. package/lib/node-self.js +453 -0
  27. package/lib/node-store.js +484 -0
  28. package/lib/node-tasks.js +342 -0
  29. package/lib/origin-claim.js +246 -0
  30. package/lib/peer-directory.js +134 -0
  31. package/lib/receipt.js +26 -1
  32. package/lib/skill/SKILL.md +3 -1
  33. package/lib/skill/references/cli.md +82 -3
  34. package/lib/skill/references/collaboration.md +146 -73
  35. package/lib/skill/references/discovery.md +29 -0
  36. package/lib/skill/references/intake.md +107 -0
  37. package/lib/skill/references/router.md +96 -0
  38. package/lib/skill/references/setup.md +2 -1
  39. package/lib/task-auth.js +483 -0
  40. package/lib/task-ledger.js +324 -12
  41. package/lib/team-store.js +24 -1
  42. package/lib/tools/act.js +383 -0
  43. package/lib/tools/files.js +201 -0
  44. package/lib/tools/info.js +684 -0
  45. package/lib/types/a2a-client.d.ts +10 -4
  46. package/lib/types/auth-observer.d.ts +110 -0
  47. package/lib/types/blob-store.d.ts +83 -0
  48. package/lib/types/cli-deposit.d.ts +38 -0
  49. package/lib/types/faces/admin.d.ts +14 -2
  50. package/lib/types/faces/canvas-notify.d.ts +4 -1
  51. package/lib/types/faces/files.d.ts +53 -0
  52. package/lib/types/faces/nodes.d.ts +69 -0
  53. package/lib/types/faces/session-log.d.ts +44 -0
  54. package/lib/types/faces/wire.d.ts +50 -5
  55. package/lib/types/index.d.ts +27 -0
  56. package/lib/types/joined-store.d.ts +9 -0
  57. package/lib/types/ledger-conservation.d.ts +77 -0
  58. package/lib/types/legacy/teams-bridge-inbound.d.ts +94 -0
  59. package/lib/types/node-bind.d.ts +220 -0
  60. package/lib/types/node-github.d.ts +35 -0
  61. package/lib/types/node-self.d.ts +178 -0
  62. package/lib/types/node-store.d.ts +180 -0
  63. package/lib/types/node-tasks.d.ts +121 -0
  64. package/lib/types/origin-claim.d.ts +119 -0
  65. package/lib/types/peer-directory.d.ts +39 -0
  66. package/lib/types/receipt.d.ts +12 -0
  67. package/lib/types/task-auth.d.ts +271 -0
  68. package/lib/types/task-ledger.d.ts +143 -6
  69. package/lib/types/team-store.d.ts +9 -0
  70. package/lib/types/tools/act.d.ts +52 -0
  71. package/lib/types/tools/files.d.ts +38 -0
  72. package/lib/types/tools/info.d.ts +102 -0
  73. package/lib/types/types.d.ts +6 -0
  74. package/nexus-stage/src/canvas-ops.ts +17 -2
  75. package/nexus-stage/src/canvas-wire.ts +27 -19
  76. package/nexus-stage/src/federation.ts +1 -0
  77. package/nexus-stage/src/main.ts +41 -18
  78. package/nexus-stage/src/planning-member-handles.ts +34 -0
  79. package/nexus-stage/src/planning-mirror.ts +47 -3
  80. package/nexus-stage/src/planning-reachability.ts +28 -0
  81. package/nexus-stage/src/planning-types.ts +1 -1
  82. package/nexus-stage/src/planning-view.ts +30 -27
  83. package/nexus-stage/src/planning.css +4 -0
  84. package/nexus-stage/src/wb-directory.ts +13 -0
  85. package/nexus-stage/src/workbench.css +3 -0
  86. package/nexus-stage/src/workbench.ts +5 -1
  87. package/package.json +1 -1
  88. package/assets/nexusDist/assets/index-C_x3iAth.js +0 -9
  89. package/assets/nexusDist/assets/index-Dz8N80tI.css +0 -1
@@ -30,6 +30,7 @@ import { buildLayoutDoc, clampDoc, type LayoutDoc, type LayoutRect } from './lay
30
30
  import { FRAME_HEAD_LIFT } from './edges'
31
31
  import { drawMemberEdges } from './planning-edges'
32
32
  import { createFederationOverlay } from './federation-overlay'
33
+ import { joinedLocalIdsByHandle, remoteMemberBoardId } from './planning-member-handles'
33
34
  import { displayName } from './stage-name'
34
35
  import {
35
36
  activityEdges, federalEdges, flyBounds, groupRemoteTeams, peerNodeId, peerHostOf, placePeers,
@@ -39,17 +40,18 @@ import {
39
40
  import { clampViewport, fitView, panBy, screenToWorld, worldTransformCss, zoomAt, type LayoutViewport } from './viewport'
40
41
  import { boardBounds, fitBelowHud, hudBottomOf } from './planning-fit'
41
42
  import { WorldModel, nodeRect, deriveInitialFrame, homeTeams, planSeatClear, planTeamClusters, NODE_W, NODE_H } from './world'
42
- import { applyAction, actionTeam, innermostFrameAt, reorderOps, yOrderedMembers, type CanvasAction } from './canvas-ops'
43
+ import { applyAction, settleAction, actionTeam, innermostFrameAt, reorderOps, yOrderedMembers, type CanvasAction } from './canvas-ops'
43
44
  import { FRAME_HUES } from './tokens'
44
45
  import { createPlanningMenus } from './planning-menus'
45
46
  import { createNodeFlyer, mountBoardSearch } from './planning-search'
46
47
  import { applyBoardRelayout } from './planning-relayout'
47
- import { mirrorRoute } from './planning-mirror'
48
+ import { createDissolveGuard, mirrorRoute } from './planning-mirror'
48
49
  import { settleBoard, tidyMembers } from './planning-overlap'
49
50
  import { createLayoutUndo, type LayoutSnap } from './planning-undo'
50
51
  import { createNodeList } from './planning-nodelist'
51
52
  import { cardText } from './planning-card-text'
52
53
  import { createLegend } from './planning-legend'
54
+ import { UNREACHABLE_HINT, showFrameReachability, unreachableRefs } from './planning-reachability'
53
55
  import { viewportBucket, type PlanningDeps, type PlanningInput, type PlanningSession, type PlanningTeam, type PlanningView, type SeamKey, type SeamPointer, type ViewportBucket } from './planning-types'
54
56
  import './planning.css'
55
57
 
@@ -382,12 +384,14 @@ export function createPlanningView(deps: PlanningDeps): PlanningView {
382
384
  const cntText = `${members} 成员` + (cross > 0 ? ` +${cross} 跨队` : '')
383
385
  if (cnt.textContent !== cntText) cnt.textContent = cntText
384
386
  const mirror = mirrorRoute(remoteFramesRef.get(name), pendingTeams.has(name)) // PR#96 single owner + mirror
387
+ const unavailable = mirror !== undefined && reachability.hosts.has(peerHostOf(remoteFramesRef.get(name)!))
385
388
  const routeText0 = mirror?.text ?? routeText(name)
386
389
  if (route.textContent !== routeText0) route.textContent = routeText0
387
- route.title = mirror?.title ?? ''
390
+ route.title = `${mirror?.title ?? ''}${unavailable ? ` · ${UNREACHABLE_HINT}` : ''}`
391
+ showFrameReachability(el, unavailable)
388
392
  el.classList.toggle('is-mirror', mirror !== undefined)
389
393
  el.classList.toggle('is-syncing', mirror?.syncing === true)
390
- const label = `团队框 ${name}${mirror !== undefined ? `(${mirror.text})` : ''}(拖动整组,回车菜单)`
394
+ const label = `团队框 ${name}${mirror !== undefined ? `(${mirror.text})` : ''}${unavailable ? `(${UNREACHABLE_HINT})` : ''}(拖动整组,回车菜单)`
391
395
  const headEl = el.querySelector('.p-frame-head')!
392
396
  if (headEl.getAttribute('aria-label') !== label) headEl.setAttribute('aria-label', label) // a rewrite re-announces
393
397
  }
@@ -438,11 +442,15 @@ export function createPlanningView(deps: PlanningDeps): PlanningView {
438
442
  const host = n.id.slice('peer-'.length)
439
443
  // Title / second line / tooltip rules: planning-card-text.
440
444
  const txt = cardText(n, n.remote === true ? lastRemoteTeams.get(host) ?? [] : [], peerScoreByText.get(host))
445
+ const unavailable = n.remote === true && (n.id.startsWith('member-') ? reachability.members.has(n.id) : reachability.hosts.has(host))
446
+ el.classList.toggle('is-unreachable', unavailable)
441
447
  const nmEl = el.querySelector<HTMLElement>('.nm-text')!
442
448
  if (nmEl.textContent !== txt.title) nmEl.textContent = txt.title
443
449
  const subEl = el.querySelector<HTMLElement>('.sub')!
444
- if (subEl.textContent !== txt.sub) subEl.textContent = txt.sub
445
- if (el.title !== txt.tip) el.title = txt.tip
450
+ const subText = unavailable ? `未应答 · ${txt.sub}` : txt.sub
451
+ if (subEl.textContent !== subText) subEl.textContent = subText
452
+ const tip = unavailable ? `${txt.tip} · ${UNREACHABLE_HINT}` : txt.tip
453
+ if (el.title !== tip) el.title = tip
446
454
  const prioEl = el.querySelector<HTMLElement>('.prio')!
447
455
  if (n.remote === true) {
448
456
  // gh#20 W-A: the peer badge — link class colors the chip (linked
@@ -526,6 +534,8 @@ export function createPlanningView(deps: PlanningDeps): PlanningView {
526
534
  // gh#41: remote-frame registry (frame name → peer URL), refreshed by
527
535
  // reconcile and read by emitAction's serverAction to fork remote drops.
528
536
  let remoteFramesRef = new Map<string, string>()
537
+ let reachability = unreachableRefs({})
538
+ const dissolving = createDissolveGuard(deps.now)
529
539
 
530
540
  function emitAction(a: CanvasAction): void {
531
541
  // gh#20 W-B: the SERVER sees zone/id8 remote handles where the model
@@ -555,7 +565,8 @@ export function createPlanningView(deps: PlanningDeps): PlanningView {
555
565
  ? { ...act, ids, ...(fleetPeer !== undefined ? { fleetPeer } : {}) }
556
566
  : act
557
567
  }
558
- return act
568
+ // Dissolving a mirror frame is the owner's write as well (relay op 'remove').
569
+ return act.type === 'remove-team' && remoteFramesRef.has(act.name) ? dissolving.fork(act, remoteFramesRef.get(act.name)!) : act
559
570
  }
560
571
 
561
572
  // Optimistic seats: a write may reference a session the teamed gate
@@ -583,10 +594,11 @@ export function createPlanningView(deps: PlanningDeps): PlanningView {
583
594
  const team = actionTeam(a)
584
595
  pendingAdd(team)
585
596
  render()
586
- void deps.onCanvasAction(serverAction(a)).then(
597
+ const sent = serverAction(a)
598
+ void deps.onCanvasAction(sent).then(
587
599
  ok => {
588
600
  pendingRelease(team)
589
- if (!ok) undo() // host refused: roll this action's optimistic delta back
601
+ layoutDoc = settleAction(model, a, ok, undo, layoutDoc); markDirty(); dissolving.settled(sent, ok)
590
602
  render()
591
603
  },
592
604
  () => {
@@ -594,7 +606,7 @@ export function createPlanningView(deps: PlanningDeps): PlanningView {
594
606
  // team's guard (and its optimistic state) forever — and per §3.4
595
607
  // 不吞错, the rollback surfaces to the user.
596
608
  pendingRelease(team)
597
- undo()
609
+ layoutDoc = settleAction(model, a, false, undo, layoutDoc); markDirty(); dissolving.settled(sent, false)
598
610
  notice('error', '操作通道异常,已回滚')
599
611
  render()
600
612
  },
@@ -1207,6 +1219,7 @@ export function createPlanningView(deps: PlanningDeps): PlanningView {
1207
1219
  // excluded from bounds, so the column cannot feed back into itself).
1208
1220
  const peerSeen = new Set<string>()
1209
1221
  lastRemoteTeams = groupRemoteTeams(input.remoteTeams ?? [])
1222
+ reachability = unreachableRefs(input)
1210
1223
  peerScoreByText.clear()
1211
1224
  for (const p of input.peers ?? []) {
1212
1225
  peerScoreByText.set(peerHostOf(p.url), p.score)
@@ -1247,6 +1260,7 @@ export function createPlanningView(deps: PlanningDeps): PlanningView {
1247
1260
  // peer card — pulling a specific remote member into a local team is a
1248
1261
  // plain drag (no picker, no hand-typed handle). Host cards stay for
1249
1262
  // federation context; the member cards carry the actual join gesture.
1263
+
1250
1264
  const memberSeen = new Set<string>()
1251
1265
  for (const [host, rows] of lastRemoteTeams) {
1252
1266
  const hostNode = model.allNodes().find(n => n.remote === true && peerHostOf(n.peerUrl ?? '') === host && !n.id.startsWith('member-'))
@@ -1306,25 +1320,14 @@ export function createPlanningView(deps: PlanningDeps): PlanningView {
1306
1320
  if (input.peerOrg !== undefined) {
1307
1321
  let slot = 0
1308
1322
  const localNames = new Set(input.teams.map(t => t.name))
1323
+ dissolving.sweep(input.peerOrg)
1309
1324
  for (const row of input.peerOrg) {
1310
- // The peer's zone comes from its directory rows ('dsh/xxxx' →
1311
- // 'dsh'); the org's members are stored verbatim — LOCAL members as
1312
- // session ids, remote members as zone/id8 handles. Both must
1313
- // resolve onto that host's session cards: id-form members become
1314
- // `member-<zone>-<id8>` (id8 = strip 'session-' prefix, first 8
1315
- // chars — the repo's id8 derivation), handle members sanitize
1316
- // directly. The old sanitize-only pass misresolved every local
1317
- // member (owner: 数据对不上).
1318
- const peerZone = input.remoteTeams?.find(r => r.via === row.url)?.team.split('/')[0] ?? 'dsh'
1319
- const id8of = (id: string): string => id.replace(/^session-/, '').slice(0, 8)
1320
- // The server now canonicalizes org members to handles (gh#41 wire
1321
- // form); the id-form branch stays as the defensive path for peers
1322
- // on an older build that still publish raw session ids.
1323
- const memberBoardId = (m: string): string =>
1324
- m.includes('/') ? `member-${m.replace(/[^a-z0-9-]/gi, '-')}` : `member-${peerZone}-${id8of(m)}`
1325
+ // Resolve joined local handles and id-form legacy members through the pure projection helper.
1326
+ const publishedHandles = new Set((input.remoteTeams ?? []).map(session => session.team))
1327
+ const localIdsByHandle = joinedLocalIdsByHandle(input.sessions, publishedHandles)
1325
1328
  for (const team of row.org) {
1326
- if (team.name === '' || localNames.has(team.name)) continue
1327
- const memberIds = team.members.map(memberBoardId).filter(id => model.getNode(id) !== undefined)
1329
+ if (team.name === '' || localNames.has(team.name) || dissolving.hides(team.name, row.url)) continue
1330
+ const memberIds = team.members.map(member => remoteMemberBoardId(member, localIdsByHandle, input.remoteTeams, row.url)).filter(id => model.getNode(id) !== undefined)
1328
1331
  const saved = layoutDoc?.frames?.[team.name]
1329
1332
  // gh#44 (owner: frame position/size never sticks): the LOCAL-team
1330
1333
  // path keeps an existing frame's live rect ("live rect wins" — a
@@ -84,6 +84,10 @@
84
84
  #dsh-plan .p-frame-head .route{font-size:10.5px; color:var(--ink-muted)}
85
85
  #dsh-plan .p-frame.is-mirror{border-style:dashed}
86
86
  #dsh-plan .p-frame.is-syncing .p-frame-head .route{color:var(--accent)}
87
+ #dsh-plan .p-frame.is-unreachable{opacity:.76;border-style:dashed}
88
+ #dsh-plan .p-frame-unreachable{position:absolute;left:8px;top:20px;max-width:calc(100% - 16px);color:var(--uupm-error);font-size:11px;pointer-events:none}
89
+ #dsh-plan .p-node.remote.is-unreachable{opacity:.72;border-left-color:var(--uupm-error)}
90
+ #dsh-plan .p-node.remote.is-unreachable .sub{color:var(--uupm-error)}
87
91
 
88
92
  /* ── node card (fixed 56px border-box height = world.NODE_H) ── */
89
93
  #dsh-plan .p-node{position:absolute; width:172px; height:56px; padding:8px 26px 8px 14px;
@@ -130,8 +130,15 @@ export function renderDirectory(box: HTMLElement, ctx: DirectoryContext): string
130
130
  const g = group('本机会话', sessions.length, cold.length > 0 ? `${live.length} 在线 · ${cold.length} 冷` : undefined)
131
131
  for (const s of [...live, ...cold]) {
132
132
  const isLive = s.live === true
133
+ const missing = s.nodePromptMissing === true
133
134
  const item = row(g, isLive ? 'wb-dot-live' : 'wb-dot-cold', isLive ? '●' : '○', displayName(s), isLive ? s.team : `冷 · ${s.team}`,
134
135
  () => ctx.openChat({ kind: 'session', id: s.id, title: displayName(s), sub: s.team, team: s.team, live: s.live }), s.id)
136
+ if (missing) {
137
+ item.classList.add('is-node-prompt-missing')
138
+ const note = el('span', 'wb-node-warning')
139
+ note.textContent = '本会话的组装不含节点段'
140
+ item.querySelector('.wb-item-text')?.appendChild(note)
141
+ }
135
142
  badges(item, s.canvasTeams)
136
143
  }
137
144
  }
@@ -153,6 +160,12 @@ export function renderDirectory(box: HTMLElement, ctx: DirectoryContext): string
153
160
  for (const r of remote) {
154
161
  const item = row(g, 'wb-dot-remote', '◇', displayName(r), r.origin !== undefined && r.origin !== '' ? r.origin : r.team,
155
162
  () => ctx.openChat({ kind: 'remote', team: r.team, title: displayName(r), sub: r.team, row: r }), r.team)
163
+ if (r.unreachable === true) {
164
+ item.classList.add('is-unreachable')
165
+ const note = el('span', 'wb-unreachable-note')
166
+ note.textContent = '对端未应答(显示的是最后已知状态)'
167
+ item.querySelector('.wb-item-text')?.appendChild(note)
168
+ }
156
169
  badges(item, r.canvasTeams)
157
170
  }
158
171
  }
@@ -321,6 +321,9 @@ body{background:var(--n-bg);color:var(--n-ink);font:13px/1.45 system-ui,"Segoe U
321
321
  .wb-item-sub{font:11px/1.35 ui-monospace,Consolas,monospace;color:var(--n-ink-muted);
322
322
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
323
323
  .wb-item.is-untitled .wb-item-label{color:var(--n-ink-muted);font-family:ui-monospace,Consolas,monospace;font-size:12px}
324
+ .wb-directory .wb-item.is-unreachable{opacity:.78}
325
+ .wb-unreachable-note{font-size:11px;line-height:1.35;color:var(--n-danger);white-space:normal;overflow-wrap:anywhere}
326
+ .wb-node-warning{font-size:11px;line-height:1.35;color:var(--n-danger);white-space:normal;overflow-wrap:anywhere}
324
327
  .wb-dot-log{color:var(--n-ink-muted)}
325
328
 
326
329
  /* ── 台账 (wb-ledger) ── */
@@ -84,6 +84,7 @@ export interface ArchivedRow {
84
84
  * canvasOrg) — the board renders REMOTE team frames from these rows. */
85
85
  export interface PeerOrgRow {
86
86
  readonly url: string
87
+ readonly unreachable?: boolean
87
88
  readonly org: ReadonlyArray<{ name: string; team: string; members: readonly string[] }>
88
89
  }
89
90
 
@@ -97,6 +98,8 @@ export interface SessionRow {
97
98
  readonly workspace?: string
98
99
  readonly joined?: boolean
99
100
  readonly live?: boolean
101
+ /** Final scoped assembly lacks at least one registered node section (#174). */
102
+ readonly nodePromptMissing?: boolean
100
103
  readonly teams?: readonly string[]
101
104
  /** Canvas teams this session is a member of (0.6.6 roster annotation). */
102
105
  readonly canvasTeams?: readonly string[]
@@ -107,6 +110,7 @@ export interface SessionRow {
107
110
  /** One remote team row from /state `remote`. */
108
111
  export interface RemoteRow {
109
112
  readonly team: string
113
+ readonly unreachable?: boolean
110
114
  readonly name?: string
111
115
  readonly origin?: string
112
116
  readonly via?: string
@@ -201,7 +205,7 @@ export interface WorkbenchDeps {
201
205
  ownerSettingsWrite?(changes: { teamJoinAllowlist?: string[]; teamScopeInfo?: boolean; announce?: boolean }): Promise<{ ok: boolean; restartNeeded?: boolean; error?: string }>
202
206
  /** gh#41 fleet planning: regulate a REMOTE peer's canvas team (this host
203
207
  * signs the envelope and forwards; the peer's link gate authorizes). */
204
- fleetCanvasOp?(peer: string, op: 'create' | 'add-member' | 'remove-member', name: string, id?: string): Promise<{ ok: boolean; members?: string[]; error?: string }>
208
+ fleetCanvasOp?(peer: string, op: 'create' | 'add-member' | 'remove-member' | 'remove', name: string, id?: string): Promise<{ ok: boolean; members?: string[]; error?: string }>
205
209
  /** Abandon one dead-lettered task via the control route. True on success. */
206
210
  abandon(taskId: string, reason: string): Promise<boolean>
207
211
  /** Canvas team member change via the control route (0.6.6): `id` is a
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nelsonlongxiang/dsh-open-a2a-net",
3
- "version": "0.6.30",
3
+ "version": "0.6.32",
4
4
  "description": "Open A2A network plugin for DeepSeek Harness: signed agent cards, decentralized peer/zone discovery, direct routing model tools, and joinable session nodes with a sidebar control",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",