@nelsonlongxiang/dsh-open-a2a-net 0.6.36 → 0.6.37
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 +4 -1
- package/README_zh.md +4 -1
- package/assets/nexusDist/assets/index-C7HagaGy.css +1 -0
- package/assets/nexusDist/assets/index-NkNSeIx6.js +9 -0
- package/assets/nexusDist/index.html +2 -2
- package/cli/a2a.mjs +1 -1
- package/lib/a2a-client.js +3 -3
- package/lib/client.js +63 -63
- package/lib/faces/admin.js +20 -4
- package/lib/faces/canvas-notify.js +8 -6
- package/lib/faces/wire.js +11 -5
- package/lib/index.js +30 -10
- package/lib/origin-claim.js +30 -7
- package/lib/owner-settings.js +38 -1
- package/lib/peer-link-display.js +15 -0
- package/lib/skill/references/cli.md +4 -0
- package/lib/skill/references/setup.md +4 -0
- package/lib/task-ledger-rows.js +192 -0
- package/lib/task-ledger.js +2 -191
- package/lib/tools/info.js +3 -1
- package/lib/types/a2a-client.d.ts +1 -0
- package/lib/types/faces/admin.d.ts +1 -0
- package/lib/types/faces/canvas-notify.d.ts +3 -1
- package/lib/types/faces/wire.d.ts +1 -0
- package/lib/types/index.d.ts +2 -0
- package/lib/types/origin-claim.d.ts +12 -3
- package/lib/types/owner-settings.d.ts +5 -0
- package/lib/types/peer-link-display.d.ts +13 -0
- package/lib/types/task-ledger-rows.d.ts +341 -0
- package/lib/types/task-ledger.d.ts +4 -288
- package/lib/types/tools/info.d.ts +2 -0
- package/lib/types/types.d.ts +2 -1
- package/nexus-stage/src/planning-member-handles.ts +28 -1
- package/nexus-stage/src/planning-menus.ts +12 -5
- package/nexus-stage/src/planning-view.ts +18 -14
- package/nexus-stage/src/seat-name-editor.ts +31 -0
- package/nexus-stage/src/session-drag.ts +64 -0
- package/nexus-stage/src/wb-directory.ts +2 -0
- package/nexus-stage/src/workbench.css +2 -0
- package/nexus-stage/src/workbench.ts +16 -5
- package/package.json +1 -1
- package/assets/nexusDist/assets/index-DIQNDBy6.js +0 -9
- package/assets/nexusDist/assets/index-z33lHtLw.css +0 -1
package/lib/types/types.d.ts
CHANGED
|
@@ -72,8 +72,9 @@ export interface A2aPeerCard {
|
|
|
72
72
|
export interface A2aSessionTeamInfo {
|
|
73
73
|
/** Session team name (`<team>/<agentId8>`); the route target. */
|
|
74
74
|
readonly team: string;
|
|
75
|
-
/**
|
|
75
|
+
/** Human seatName when configured, otherwise the session title at read time. */
|
|
76
76
|
readonly name: string;
|
|
77
|
+
readonly seatName?: string;
|
|
77
78
|
/** One-line excerpt of the session's newest user or assistant text. */
|
|
78
79
|
readonly description: string;
|
|
79
80
|
/** CLI deposit status, explicitly online/offline on full host cards; absent on ordinary sessions. */
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/** Pure joined-local
|
|
1
|
+
/** Pure membership projections: joined-local handles for remote organization members, canvas team indexes, mirror-frame members. */
|
|
2
2
|
import type { RemoteTeamRow } from './federation'
|
|
3
3
|
import type { SessionLite } from './world'
|
|
4
4
|
|
|
@@ -32,3 +32,30 @@ export function remoteMemberBoardId(member: string, localIdsByHandle: ReadonlyMa
|
|
|
32
32
|
const peerZone = remoteTeams?.find(row => row.via === peerUrl)?.team.split('/')[0] ?? 'dsh'
|
|
33
33
|
return `member-${peerZone}-${member.replace(/^session-/, '').slice(0, 8)}`
|
|
34
34
|
}
|
|
35
|
+
|
|
36
|
+
/** Each member id → the canvas teams it sits in, with its position in that team's member order. */
|
|
37
|
+
export function membershipIndex(teams: ReadonlyArray<{ name: string; members: ReadonlyArray<{ id: string }> }>): Map<string, Array<{ team: string; index: number }>> {
|
|
38
|
+
const memberships = new Map<string, Array<{ team: string; index: number }>>()
|
|
39
|
+
for (const team of teams) {
|
|
40
|
+
for (let i = 0; i < team.members.length; i++) {
|
|
41
|
+
const id = team.members[i]!.id
|
|
42
|
+
const list = memberships.get(id) ?? []
|
|
43
|
+
list.push({ team: team.name, index: i })
|
|
44
|
+
memberships.set(id, list)
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return memberships
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Local joined sessions that sit in a peer's org team (a mirror frame): they are teamed even with no local team. */
|
|
51
|
+
export function mirrorMemberIds(peerOrg: ReadonlyArray<{ org: ReadonlyArray<{ members: readonly string[] }> }>, sessions: ReadonlyArray<HandleSession>, remoteTeams: ReadonlyArray<RemoteTeamRow> | undefined): Set<string> {
|
|
52
|
+
const byHandle = joinedLocalIdsByHandle(sessions, new Set((remoteTeams ?? []).map(session => session.team)))
|
|
53
|
+
const ids = new Set<string>()
|
|
54
|
+
for (const row of peerOrg) {
|
|
55
|
+
for (const team of row.org) for (const member of team.members) {
|
|
56
|
+
const id = byHandle.get(member)
|
|
57
|
+
if (id !== undefined) ids.add(id)
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return ids
|
|
61
|
+
}
|
|
@@ -306,14 +306,13 @@ export function createPlanningMenus(d: MenusDeps): PlanningMenus {
|
|
|
306
306
|
const create = menuItem('组成团队…', model.selectedIds().length >= 2, () => openNameDialog(selectedByY()))
|
|
307
307
|
create.title = '先框选或 Shift 加选至少 2 个节点'
|
|
308
308
|
items.push(create)
|
|
309
|
+
const teamsOf = model.getNode(id)?.memberships.map(m => m.team) ?? []
|
|
309
310
|
if (d.teams().length > 0) {
|
|
310
311
|
items.push(menuItem('加入团队 ▸', true, () => openMenu(x, y, target, 'join')))
|
|
311
|
-
|
|
312
|
-
if (teamsOf.length > 0) {
|
|
313
|
-
items.push(menuItem('置顶路由 ▸', true, () => openMenu(x, y, target, 'promote')))
|
|
314
|
-
items.push(menuItem('离队 ▸', true, () => openMenu(x, y, target, 'leave')))
|
|
315
|
-
}
|
|
312
|
+
if (teamsOf.length > 0) items.push(menuItem('置顶路由 ▸', true, () => openMenu(x, y, target, 'promote')))
|
|
316
313
|
}
|
|
314
|
+
// A card that is only in mirror frames (a peer's team) still has a team to leave.
|
|
315
|
+
if (teamsOf.length > 0) items.push(menuItem('离队 ▸', true, () => openMenu(x, y, target, 'leave')))
|
|
317
316
|
// Network-level leave (distinct from canvas-team membership): the
|
|
318
317
|
// panel-slim migration moved the join/leave toggle here. Remote
|
|
319
318
|
// nodes are read-only entries — never a leave target.
|
|
@@ -342,6 +341,14 @@ export function createPlanningMenus(d: MenusDeps): PlanningMenus {
|
|
|
342
341
|
}))
|
|
343
342
|
}
|
|
344
343
|
}
|
|
344
|
+
// d.teams() is this host's own teams; a mirror frame's membership leaves through
|
|
345
|
+
// the owner's relay (the view adds fleetPeer), so list those memberships too.
|
|
346
|
+
if (level === 'leave') {
|
|
347
|
+
const local = new Set(d.teams().map(t => t.name))
|
|
348
|
+
for (const m of model.getNode(id)?.memberships ?? []) {
|
|
349
|
+
if (!local.has(m.team)) items.push(menuItem(titleOf(m.team), true, () => d.emit({ type: 'remove-member', team: m.team, ids: [id] })))
|
|
350
|
+
}
|
|
351
|
+
}
|
|
345
352
|
}
|
|
346
353
|
}
|
|
347
354
|
el.append(...items)
|
|
@@ -30,7 +30,8 @@ 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
|
+
import { joinedLocalIdsByHandle, membershipIndex, mirrorMemberIds, remoteMemberBoardId } from './planning-member-handles'
|
|
34
|
+
import { attachSessionDrop } from './session-drag'
|
|
34
35
|
import { displayName } from './stage-name'
|
|
35
36
|
import {
|
|
36
37
|
activityEdges, federalEdges, flyBounds, groupRemoteTeams, peerNodeId, peerHostOf, placePeers,
|
|
@@ -625,6 +626,12 @@ export function createPlanningView(deps: PlanningDeps): PlanningView {
|
|
|
625
626
|
openNameDialog: menus.openNameDialog,
|
|
626
627
|
locate: locateNode,
|
|
627
628
|
})
|
|
629
|
+
// A rail 本机会话 row dropped on a frame joins that team, like a card drop (session-drag).
|
|
630
|
+
attachSessionDrop(root, {
|
|
631
|
+
signal, frames: () => frameEls,
|
|
632
|
+
frameAt: (clientX, clientY) => { const p = localXY({ clientX, clientY }); return innermostFrameAt(new Map(model.allFrames()), screenToWorld(vp, p.x, p.y)) },
|
|
633
|
+
drop: (team, id) => model.getNode(id)?.memberships.some(m => m.team === team) === true ? notice('info', '该会话已在这个队里') : emitAction({ type: 'add-member', team, ids: [id] }),
|
|
634
|
+
})
|
|
628
635
|
|
|
629
636
|
/** Center the viewport on one canvas node (the 已组队 row's 定位). */
|
|
630
637
|
function locateNode(id: string): void {
|
|
@@ -1119,15 +1126,7 @@ export function createPlanningView(deps: PlanningDeps): PlanningView {
|
|
|
1119
1126
|
// toolbar chip instead of vanishing entirely.
|
|
1120
1127
|
lastSessions = input.sessions
|
|
1121
1128
|
menus.setUnjoined(input.sessions.filter(s => s.joined !== true && !s.id.startsWith('peer-')))
|
|
1122
|
-
const memberships =
|
|
1123
|
-
for (const team of input.teams) {
|
|
1124
|
-
for (let i = 0; i < team.members.length; i++) {
|
|
1125
|
-
const id = team.members[i]!.id
|
|
1126
|
-
const list = memberships.get(id) ?? []
|
|
1127
|
-
list.push({ team: team.name, index: i })
|
|
1128
|
-
memberships.set(id, list)
|
|
1129
|
-
}
|
|
1130
|
-
}
|
|
1129
|
+
const memberships = membershipIndex(input.teams)
|
|
1131
1130
|
lastCanvasMemberIds = new Set(memberships.keys())
|
|
1132
1131
|
const seen = new Set<string>()
|
|
1133
1132
|
// In-flight write guard: membership entries of teams with unsettled
|
|
@@ -1161,15 +1160,20 @@ export function createPlanningView(deps: PlanningDeps): PlanningView {
|
|
|
1161
1160
|
syncViewSize()
|
|
1162
1161
|
clusterSeats = planTeamClusters(input.teams, seatIds, viewW > 0 ? viewW : undefined)
|
|
1163
1162
|
}
|
|
1163
|
+
// A joined session whose only team is a peer's (a mirror frame) is teamed too: without its
|
|
1164
|
+
// card the mirror frame cannot show it, and 离队 has nothing to reach (owner report 2026-11-04).
|
|
1165
|
+
const mirrorMembers = input.peerOrg !== undefined ? mirrorMemberIds(input.peerOrg, input.sessions, input.remoteTeams) : new Set<string>()
|
|
1166
|
+
// The node list reads the same fact: such a session is teamed, not 未组队.
|
|
1167
|
+
if (input.peerOrg !== undefined) lastCanvasMemberIds = new Set([...lastCanvasMemberIds, ...mirrorMembers])
|
|
1164
1168
|
for (const s of input.sessions) {
|
|
1165
1169
|
if (s.joined !== true) continue
|
|
1166
1170
|
// Teamed-only canvas (network-membership-display ruling): a joined
|
|
1167
1171
|
// session earns a canvas node only with a team — canvas membership
|
|
1168
|
-
// (optimistic-aware)
|
|
1169
|
-
// sessions ride the node list's 未组队
|
|
1170
|
-
// from `seen` are swept from the model below.
|
|
1172
|
+
// (optimistic-aware), a declared roster team, or a mirror frame's
|
|
1173
|
+
// membership. Teamless joined sessions ride the node list's 未组队
|
|
1174
|
+
// group instead; sessions absent from `seen` are swept from the model below.
|
|
1171
1175
|
const effective = resolveMemberships(s.id)
|
|
1172
|
-
if (effective.length === 0 && (s.teams === undefined || s.teams.length === 0)) continue
|
|
1176
|
+
if (effective.length === 0 && (s.teams === undefined || s.teams.length === 0) && !mirrorMembers.has(s.id)) continue
|
|
1173
1177
|
seen.add(s.id)
|
|
1174
1178
|
if (model.getNode(s.id) !== undefined) {
|
|
1175
1179
|
model.upsertNode({
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/** Human owner form only: labels never grant membership or become routing ids. */
|
|
2
|
+
export function createSeatNameEditor(sessions: readonly { id: string; name?: string; label?: string }[], names: Record<string, string>, save: (names: Record<string, string>) => Promise<{ ok: boolean; error?: string }>): HTMLElement {
|
|
3
|
+
const box = document.createElement('section')
|
|
4
|
+
const heading = document.createElement('h4'); heading.textContent = '会话席位名(仅人工设定)'; box.appendChild(heading)
|
|
5
|
+
const note = document.createElement('p'); note.className = 'wb-hint'; note.textContent = '稳定展示名,不改变会话标题、路由句柄或准入。留空并保存可清除。'; box.appendChild(note)
|
|
6
|
+
const select = document.createElement('select'); select.setAttribute('aria-label', '选择命名的本机会话')
|
|
7
|
+
for (const id of new Set([...sessions.map(row => row.id), ...Object.keys(names)])) {
|
|
8
|
+
const row = sessions.find(row => row.id === id), option = document.createElement('option')
|
|
9
|
+
option.value = id; option.textContent = (names[id] ?? row?.name ?? row?.label ?? id) + ' · ' + id; select.appendChild(option)
|
|
10
|
+
}
|
|
11
|
+
const input = document.createElement('input'); input.type = 'text'; input.maxLength = 64; input.setAttribute('aria-label', '人工席位名'); input.placeholder = '例如:开发席'
|
|
12
|
+
const button = document.createElement('button'); button.type = 'submit'; button.textContent = '保存席位名'; button.className = 'wb-member-submit'
|
|
13
|
+
const form = document.createElement('form'); form.className = 'wb-member-add'; form.append(select, input, button)
|
|
14
|
+
const status = document.createElement('p'); status.className = 'wb-hint'; status.setAttribute('role', 'status'); status.setAttribute('aria-live', 'polite')
|
|
15
|
+
let pending = false
|
|
16
|
+
const load = () => { input.value = names[select.value] ?? ''; button.disabled = pending || select.value === '' }
|
|
17
|
+
select.addEventListener('change', load); load()
|
|
18
|
+
form.addEventListener('submit', async event => {
|
|
19
|
+
event.preventDefault(); if (pending || select.value === '') return
|
|
20
|
+
const id = select.value, value = input.value.trim(), next = { ...names }
|
|
21
|
+
if (value === '') delete next[id]; else next[id] = value
|
|
22
|
+
pending = true; button.disabled = true; select.disabled = true; input.disabled = true
|
|
23
|
+
try {
|
|
24
|
+
const result = await save(next)
|
|
25
|
+
if (result.ok) { delete names[id]; if (value !== '') names[id] = value; status.textContent = '席位名已保存;目录将随状态刷新。' }
|
|
26
|
+
else status.textContent = result.error ?? '保存失败'
|
|
27
|
+
} catch (error) { status.textContent = '保存失败:' + String(error) }
|
|
28
|
+
finally { pending = false; select.disabled = false; input.disabled = false; load() }
|
|
29
|
+
})
|
|
30
|
+
box.append(form, status); return box
|
|
31
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Drag a local session from the workbench rail (目录 · 本机会话) straight into
|
|
3
|
+
* a canvas team frame on the board (owner request 2026-11-04). The rail row
|
|
4
|
+
* carries the session id in a private drag type; the board accepts it only
|
|
5
|
+
* over a frame and turns the drop into the same add-member action a card drop
|
|
6
|
+
* makes, so roster frames, mirror frames (fleet relay) and the host's own
|
|
7
|
+
* refusals all behave exactly as for an on-board drag.
|
|
8
|
+
* @module nexus-stage/session-drag
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/** Private drag type: an arbitrary page or file dropped on the board is ignored. */
|
|
12
|
+
export const SESSION_DRAG_TYPE = 'application/x-dsh-session'
|
|
13
|
+
|
|
14
|
+
/** Make one rail row draggable as a local session. */
|
|
15
|
+
export function makeSessionDraggable(item: HTMLElement, sessionId: string): void {
|
|
16
|
+
item.draggable = true
|
|
17
|
+
item.classList.add('is-draggable')
|
|
18
|
+
item.title = `${item.title === '' ? sessionId : item.title} · 可拖入画布队`
|
|
19
|
+
item.addEventListener('dragstart', ev => {
|
|
20
|
+
if (ev.dataTransfer === null) return
|
|
21
|
+
ev.dataTransfer.setData(SESSION_DRAG_TYPE, sessionId)
|
|
22
|
+
ev.dataTransfer.effectAllowed = 'copy'
|
|
23
|
+
})
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface SessionDropDeps {
|
|
27
|
+
/** The innermost team frame under a client point, if any. */
|
|
28
|
+
frameAt(clientX: number, clientY: number): string | undefined
|
|
29
|
+
/** Frame elements by team name, for the drop-target highlight. */
|
|
30
|
+
frames(): ReadonlyMap<string, HTMLElement>
|
|
31
|
+
/** A session dropped on a frame. */
|
|
32
|
+
drop(team: string, sessionId: string): void
|
|
33
|
+
readonly signal: AddEventListenerOptions
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Accept rail session drags on the board root. */
|
|
37
|
+
export function attachSessionDrop(root: HTMLElement, d: SessionDropDeps): void {
|
|
38
|
+
const carries = (ev: DragEvent): boolean => ev.dataTransfer !== null && [...ev.dataTransfer.types].includes(SESSION_DRAG_TYPE)
|
|
39
|
+
const highlight = (team: string | undefined): void => {
|
|
40
|
+
for (const [name, el] of d.frames()) el.classList.toggle('drop-target', name === team)
|
|
41
|
+
}
|
|
42
|
+
root.addEventListener('dragover', ev => {
|
|
43
|
+
if (!carries(ev)) return
|
|
44
|
+
const team = d.frameAt(ev.clientX, ev.clientY)
|
|
45
|
+
highlight(team)
|
|
46
|
+
if (team === undefined) return
|
|
47
|
+
ev.preventDefault() // only a frame is a drop target
|
|
48
|
+
ev.dataTransfer!.dropEffect = 'copy'
|
|
49
|
+
}, d.signal)
|
|
50
|
+
root.addEventListener('dragleave', ev => {
|
|
51
|
+
if (carries(ev) && (ev.relatedTarget === null || !root.contains(ev.relatedTarget as Node))) highlight(undefined)
|
|
52
|
+
}, d.signal)
|
|
53
|
+
// A drag cancelled with Esc over a frame fires no dragleave on the board: the source's dragend clears it.
|
|
54
|
+
root.ownerDocument.addEventListener('dragend', () => highlight(undefined), d.signal)
|
|
55
|
+
root.addEventListener('drop', ev => {
|
|
56
|
+
if (!carries(ev)) return
|
|
57
|
+
highlight(undefined)
|
|
58
|
+
const team = d.frameAt(ev.clientX, ev.clientY)
|
|
59
|
+
const id = ev.dataTransfer!.getData(SESSION_DRAG_TYPE)
|
|
60
|
+
if (team === undefined || id === '') return
|
|
61
|
+
ev.preventDefault()
|
|
62
|
+
d.drop(team, id)
|
|
63
|
+
}, d.signal)
|
|
64
|
+
}
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
import type { CanvasTeamRow, StateBody } from './workbench'
|
|
16
16
|
import type { ChatTarget } from './wb-chat'
|
|
17
17
|
import { displayName } from './stage-name'
|
|
18
|
+
import { makeSessionDraggable } from './session-drag'
|
|
18
19
|
|
|
19
20
|
export type DirFilter = 'all' | 'local' | 'canvas' | 'remote' | 'logs'
|
|
20
21
|
|
|
@@ -140,6 +141,7 @@ export function renderDirectory(box: HTMLElement, ctx: DirectoryContext): string
|
|
|
140
141
|
item.querySelector('.wb-item-text')?.appendChild(note)
|
|
141
142
|
}
|
|
142
143
|
badges(item, s.canvasTeams)
|
|
144
|
+
makeSessionDraggable(item, s.id)
|
|
143
145
|
}
|
|
144
146
|
}
|
|
145
147
|
|
|
@@ -49,6 +49,7 @@ body{background:var(--n-bg);color:var(--n-ink);font:13px/1.45 system-ui,"Segoe U
|
|
|
49
49
|
width:30px;height:30px;display:none;align-items:center;justify-content:center;font:inherit;font-size:14px;
|
|
50
50
|
cursor:pointer;flex:none}
|
|
51
51
|
.wb-icon-btn:hover{border-color:var(--n-accent)}
|
|
52
|
+
.wb-owner-btn{display:inline-flex}
|
|
52
53
|
.wb-menu{margin-left:0}
|
|
53
54
|
.wb-rail-toggle{margin-left:auto}
|
|
54
55
|
.wb.is-rail-collapsed .wb-rail{display:none}
|
|
@@ -320,6 +321,7 @@ body{background:var(--n-bg);color:var(--n-ink);font:13px/1.45 system-ui,"Segoe U
|
|
|
320
321
|
.wb-dir-day{padding:6px 6px 2px;font-size:10.5px;color:var(--n-ink-muted)}
|
|
321
322
|
.wb-dir-empty{padding:24px 8px;text-align:center;font-size:12px;color:var(--n-ink-muted)}
|
|
322
323
|
.wb-directory .wb-item{gap:9px;padding:5px 6px;border-radius:8px}
|
|
324
|
+
.wb-directory .wb-item.is-draggable{cursor:grab}
|
|
323
325
|
.wb-directory .wb-item:focus-visible{outline:2px solid var(--n-accent);outline-offset:-2px}
|
|
324
326
|
.wb-item-text{display:flex;flex-direction:column;flex:1 1 auto;min-width:0;line-height:1.35}
|
|
325
327
|
.wb-item-sub{font:11px/1.35 ui-monospace,Consolas,monospace;color:var(--n-ink-muted);
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
* @module nexus-stage/workbench
|
|
20
20
|
*/
|
|
21
21
|
import { createChatDrawer, type ChatTarget } from './wb-chat'
|
|
22
|
+
import { createSeatNameEditor } from './seat-name-editor'
|
|
22
23
|
import { createCliHomePicker, type CliHomeOptions } from './cli-home-picker'
|
|
23
24
|
import { createRailTabs } from './wb-rail'
|
|
24
25
|
import { DIRECTORY_FILTERS, learnLogTitle, loadLogTitles, renderDirectory, type DirFilter } from './wb-directory'
|
|
@@ -95,6 +96,7 @@ export interface SessionRow {
|
|
|
95
96
|
readonly label: string
|
|
96
97
|
readonly team: string
|
|
97
98
|
readonly name?: string
|
|
99
|
+
readonly seatName?: string
|
|
98
100
|
readonly description?: string
|
|
99
101
|
readonly workspace?: string
|
|
100
102
|
readonly joined?: boolean
|
|
@@ -207,9 +209,9 @@ export interface WorkbenchDeps {
|
|
|
207
209
|
/** gh#41 fleet planning: each tracked peer's published canvas org. */
|
|
208
210
|
peerOrg?: readonly PeerOrgRow[]
|
|
209
211
|
/** gh#34: read the owner three-key panel state (GET /__dsh_a2a/owner-settings). */
|
|
210
|
-
ownerSettingsRead?(): Promise<{ ok: boolean; teamJoinAllowlist?: string[]; teamScopeInfo?: boolean; announce?: boolean; announceRestartPending?: boolean; error?: string }>
|
|
212
|
+
ownerSettingsRead?(): Promise<{ ok: boolean; seatNames?: Record<string, string>; teamJoinAllowlist?: string[]; teamScopeInfo?: boolean; announce?: boolean; announceRestartPending?: boolean; error?: string }>
|
|
211
213
|
/** gh#34: write owner panel changes; true on success (POST the same face). */
|
|
212
|
-
ownerSettingsWrite?(changes: { teamJoinAllowlist?: string[]; teamScopeInfo?: boolean; announce?: boolean }): Promise<{ ok: boolean; restartNeeded?: boolean; error?: string }>
|
|
214
|
+
ownerSettingsWrite?(changes: { seatNames?: Record<string, string>; teamJoinAllowlist?: string[]; teamScopeInfo?: boolean; announce?: boolean }): Promise<{ ok: boolean; restartNeeded?: boolean; error?: string }>
|
|
213
215
|
/** gh#41 fleet planning: regulate a REMOTE peer's canvas team (this host
|
|
214
216
|
* signs the envelope and forwards; the peer's link gate authorizes). */
|
|
215
217
|
fleetCanvasOp?(peer: string, op: 'create' | 'add-member' | 'remove-member' | 'remove', name: string, id?: string): Promise<{ ok: boolean; members?: string[]; error?: string }>
|
|
@@ -375,8 +377,8 @@ export function createWorkbench(deps: WorkbenchDeps): Workbench {
|
|
|
375
377
|
const ownerBtn = el('button', 'wb-icon-btn wb-owner-btn')
|
|
376
378
|
ownerBtn.type = 'button'
|
|
377
379
|
ownerBtn.dataset.testid = 'wb-owner-btn'
|
|
378
|
-
ownerBtn.setAttribute('aria-label', '
|
|
379
|
-
ownerBtn.title = '
|
|
380
|
+
ownerBtn.setAttribute('aria-label', '主人设置')
|
|
381
|
+
ownerBtn.title = '主人设置:席位名 / 入网白名单 / 可见性 / 发布'
|
|
380
382
|
ownerBtn.textContent = '⚙'
|
|
381
383
|
ownerBtn.addEventListener('click', () => { ownerPanelOpen = !ownerPanelOpen; renderOwnerPanel() })
|
|
382
384
|
header.appendChild(ownerBtn)
|
|
@@ -827,15 +829,18 @@ export function createWorkbench(deps: WorkbenchDeps): Workbench {
|
|
|
827
829
|
|
|
828
830
|
// ── Inspector (overlay card over the stage; bottom sheet on narrow) ──
|
|
829
831
|
// gh#34 owner panel state + render (the gear toggles; the inspector hosts).
|
|
832
|
+
let seatEditor: HTMLElement | undefined
|
|
830
833
|
let ownerPanelOpen = false
|
|
831
|
-
let ownerState: { teamJoinAllowlist: string[]; teamScopeInfo: boolean; announce: boolean; announceRestartPending: boolean } | null = null
|
|
834
|
+
let ownerState: { seatNames: Record<string, string>; teamJoinAllowlist: string[]; teamScopeInfo: boolean; announce: boolean; announceRestartPending: boolean } | null = null
|
|
832
835
|
function renderOwnerPanel(): void {
|
|
833
836
|
renderInspector()
|
|
834
837
|
if (!ownerPanelOpen) return
|
|
835
838
|
void deps.ownerSettingsRead?.().then(result => {
|
|
836
839
|
if (!ownerPanelOpen) return
|
|
837
840
|
if (result.ok !== true) { ownerState = null; renderInspector(); return }
|
|
841
|
+
seatEditor = undefined
|
|
838
842
|
ownerState = {
|
|
843
|
+
seatNames: result.seatNames ?? {},
|
|
839
844
|
teamJoinAllowlist: result.teamJoinAllowlist ?? [],
|
|
840
845
|
teamScopeInfo: result.teamScopeInfo !== false,
|
|
841
846
|
announce: result.announce === true,
|
|
@@ -870,6 +875,12 @@ export function createWorkbench(deps: WorkbenchDeps): Workbench {
|
|
|
870
875
|
return
|
|
871
876
|
}
|
|
872
877
|
const st = ownerState
|
|
878
|
+
seatEditor ??= createSeatNameEditor(last?.sessions ?? [], st.seatNames, async seatNames => {
|
|
879
|
+
const result = await deps.ownerSettingsWrite?.({ seatNames })
|
|
880
|
+
if (result?.ok) { st.seatNames = seatNames; return { ok: true } }
|
|
881
|
+
return { ok: false, error: result?.error ?? '保存失败' }
|
|
882
|
+
})
|
|
883
|
+
card.appendChild(seatEditor)
|
|
873
884
|
// ① 入网白名单(精确团队名增删)
|
|
874
885
|
const alHead = el('div', 'wb-side-head')
|
|
875
886
|
alHead.textContent = '入网白名单(精确团队名)'
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nelsonlongxiang/dsh-open-a2a-net",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.37",
|
|
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",
|