@nelsonlongxiang/dsh-open-a2a-net 0.5.13
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 +115 -0
- package/README_zh.md +80 -0
- package/cordis.patch.yml +21 -0
- package/images/image-01.png +0 -0
- package/images/image-02.png +0 -0
- package/lib/a2a-client.js +166 -0
- package/lib/card.js +159 -0
- package/lib/client/A2aControl.js +239 -0
- package/lib/client/index.js +35 -0
- package/lib/client/locales.js +55 -0
- package/lib/client.js +756 -0
- package/lib/client.js.map +1 -0
- package/lib/group-store.js +131 -0
- package/lib/index.js +1485 -0
- package/lib/invariant.js +16 -0
- package/lib/joined-store.js +98 -0
- package/lib/peer-store.js +169 -0
- package/lib/types/a2a-client.d.ts +73 -0
- package/lib/types/card.d.ts +54 -0
- package/lib/types/client/A2aControl.d.ts +64 -0
- package/lib/types/client/index.d.ts +27 -0
- package/lib/types/client/locales.d.ts +7 -0
- package/lib/types/client.d.ts +73 -0
- package/lib/types/group-store.d.ts +58 -0
- package/lib/types/index.d.ts +113 -0
- package/lib/types/invariant.d.ts +8 -0
- package/lib/types/joined-store.d.ts +61 -0
- package/lib/types/peer-store.d.ts +91 -0
- package/lib/types/types.d.ts +97 -0
- package/lib/types/zone.d.ts +37 -0
- package/lib/types.js +5 -0
- package/lib/zone.js +55 -0
- package/package.json +157 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","names":["css","Button","IconGlobeOutline14","_Fragment"],"sources":["../node_modules/.pnpm/clsx@2.1.1/node_modules/clsx/dist/clsx.mjs","client/A2aControl.js","client/locales.js","client/index.js"],"sourcesContent":["function r(e){var t,f,n=\"\";if(\"string\"==typeof e||\"number\"==typeof e)n+=e;else if(\"object\"==typeof e)if(Array.isArray(e)){var o=e.length;for(t=0;t<o;t++)e[t]&&(f=r(e[t]))&&(n&&(n+=\" \"),n+=f)}else for(f in e)e[f]&&(n&&(n+=\" \"),n+=f);return n}export function clsx(){for(var e,t,f=0,n=\"\",o=arguments.length;f<o;f++)(e=arguments[f])&&(t=r(e))&&(n&&(n+=\" \"),n+=t);return n}export default clsx;","import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from \"react/jsx-runtime\";\n/**\n * A2A network control: a footer action opening this host's network panel.\n * Probes the host's session-node state route at registration time (the seat\n * renders only on a host that serves it). The panel has three sections:\n * session nodes (live rows show title, recent-activity excerpt, and team with\n * a join/leave toggle and open the session on row click — the wake action on\n * cold rows opens the session through the standard flow), the peer fleet\n * (tracked URLs with quality scores), and the recent routing activity ring\n * (inbound and outbound outcomes). While open, the panel polls so listings\n * visibly follow each turn.\n */\nimport { useCallback, useEffect, useRef, useState } from 'react';\nimport clsx from 'clsx';\nimport { Button, IconGlobeOutline14 } from '@deepseek-ai/dsh-client-ui-primitives';\nimport css from './A2aControl.module.css';\n/** Fetch the host's network state; undefined means the route is absent. */\nasync function fetchState() {\n const response = await fetch('/__dsh_a2a/state', { cache: 'no-store' });\n if (!response.ok)\n return undefined;\n const body = await response.json();\n if (body.nodes !== true || !Array.isArray(body.sessions))\n return undefined;\n return {\n sessions: body.sessions,\n groups: Array.isArray(body.groups) ? body.groups : [],\n peers: Array.isArray(body.peers) ? body.peers : [],\n activity: Array.isArray(body.activity) ? body.activity : [],\n inFlight: Array.isArray(body.inFlight) ? body.inFlight : [],\n remote: Array.isArray(body.remote) ? body.remote : [],\n ...(typeof body.version === 'string' ? { version: body.version } : {}),\n };\n}\n/** Compact relative time for an activity row. */\nfunction relativeTime(ts) {\n const seconds = Math.max(0, Math.round((Date.now() - ts) / 1000));\n if (seconds < 60)\n return `${String(seconds)}s`;\n const minutes = Math.round(seconds / 60);\n if (minutes < 60)\n return `${String(minutes)}m`;\n return `${String(Math.round(minutes / 60))}h`;\n}\n/**\n * One session row: the facts block opens the session, the toggle joins or\n * leaves, and the tag button opens the group picker (assign / create /\n * clear).\n */\nfunction SessionRow({ row, t, listById, openSession, busy, onToggle, pickerFor, setPickerFor, groups, newGroupName, setNewGroupName, onAssign }) {\n const cold = row.live === false;\n const name = cold ? (listById[row.id]?.displayTitle ?? row.label) : (row.name ?? row.label);\n const description = cold ? t('a2a.cold') : (row.description ?? '');\n const pickerOpen = pickerFor === row.id;\n return (_jsxs(\"div\", { className: clsx(css.row, row.joined && css.joined), children: [_jsxs(\"button\", { type: \"button\", className: css.facts, title: cold ? t('a2a.wake') : name, onClick: () => { openSession(row.id); }, children: [_jsxs(\"span\", { className: css.nameRow, children: [_jsx(\"span\", { className: clsx(css.stateDot, cold ? 'cold' : 'live', row.joined && 'joined'), \"aria-hidden\": true }), _jsx(\"span\", { className: css.name, children: name }), row.group !== undefined ? _jsx(\"span\", { className: css.groupTag, children: row.group }) : null] }), _jsx(\"span\", { className: css.description, children: description }), _jsx(\"span\", { className: css.team, children: row.team })] }), _jsx(\"button\", { type: \"button\", className: css.groupButton, title: t('a2a.group'), \"aria-label\": t('a2a.group'), onClick: () => { setPickerFor(pickerOpen ? null : row.id); }, children: \"#\" }), cold\n ? (_jsx(Button, { variant: \"outline\", size: \"sm\", disabled: busy === row.id, onClick: () => { onToggle(row); }, children: t('a2a.leave') }))\n : (_jsx(Button, { variant: row.joined ? 'outline' : 'primary', size: \"sm\", disabled: busy === row.id, onClick: () => { onToggle(row); }, children: row.joined ? t('a2a.leave') : t('a2a.join') })), pickerOpen && (_jsxs(\"div\", { className: css.groupPicker, children: [_jsx(\"button\", { type: \"button\", className: css.groupOption, onClick: () => { onAssign(row.id, ''); }, children: t('a2a.groupClear') }), groups.map((group) => (_jsx(\"button\", { type: \"button\", className: clsx(css.groupOption, row.group === group && css.groupOptionActive), onClick: () => { onAssign(row.id, group); }, children: group }, group))), _jsxs(\"div\", { className: css.groupNew, children: [_jsx(\"input\", { className: css.groupInput, value: newGroupName, placeholder: t('a2a.groupNew'), maxLength: 40, onChange: (event) => { setNewGroupName(event.target.value); }, onKeyDown: (event) => {\n if (event.key === 'Enter' && newGroupName.trim() !== '')\n onAssign(row.id, newGroupName.trim());\n } }), _jsx(Button, { variant: \"primary\", size: \"sm\", disabled: newGroupName.trim() === '', onClick: () => { if (newGroupName.trim() !== '')\n onAssign(row.id, newGroupName.trim()); }, children: t('a2a.groupNewGo') })] })] }))] }));\n}\n/**\n * Render the network toggle and its network panel.\n * @param props - composed slot props.\n * @returns the control element tree.\n */\nexport function A2aControl({ wide, t, useSessions, openSession }) {\n const [open, setOpen] = useState(false);\n const [state, setState] = useState({ sessions: [], groups: [], peers: [], activity: [], inFlight: [], remote: [] });\n const [busy, setBusy] = useState(null);\n const [seenActivity, setSeenActivity] = useState(0);\n // Panel-local search: filters session rows by name/team/description.\n const [search, setSearch] = useState('');\n // Group UI: the row whose group picker is open, and collapsed groups.\n const [pickerFor, setPickerFor] = useState(null);\n const [newGroupName, setNewGroupName] = useState('');\n const [collapsed, setCollapsed] = useState(new Set());\n const stopped = useRef(false);\n useEffect(() => () => { stopped.current = true; }, []);\n const wrap = useRef(null);\n // Cold rows carry no host-side facts; the client's own session list holds\n // the durable title for exactly those sessions.\n const listById = useSessions(state2 => state2.byId);\n // The popover anchors to the trigger's viewport rect: the sidebar column\n // clips its descendants (the collapse slide), so an in-column absolute\n // popover is cut at the column edge whenever the column is narrower than\n // the popover; fixed positioning escapes the clip at rest (the ui-settings\n // panel precedent). The left edge clamps so a narrow viewport never hides\n // the popover's right side.\n const [anchor, setAnchor] = useState(null);\n // The unread badge counts inbound activity entries that arrived while the\n // panel was closed; opening the panel clears it.\n const unread = Math.max(0, state.activity.filter(entry => entry.dir === 'in').length - seenActivity);\n const toggleOpen = () => {\n if (open) {\n setOpen(false);\n return;\n }\n const rect = wrap.current?.getBoundingClientRect();\n if (rect !== undefined) {\n setAnchor({\n left: Math.max(8, Math.min(rect.left, window.innerWidth - 336)),\n bottom: window.innerHeight - rect.top + 8,\n });\n }\n setSeenActivity(state.activity.filter(entry => entry.dir === 'in').length);\n setOpen(true);\n };\n const refresh = useCallback(() => {\n void fetchState().then((next) => {\n if (!stopped.current && next !== undefined)\n setState(next);\n }).catch(() => { });\n }, []);\n useEffect(() => {\n if (open)\n refresh();\n }, [open, refresh]);\n useEffect(() => {\n // The unread badge lives on the closed trigger, so the state must move\n // even while the panel is closed — a slow poll keeps it fresh; the open\n // panel's faster poll below takes over while visible.\n const slow = setInterval(refresh, 10_000);\n refresh();\n return () => { clearInterval(slow); };\n }, [refresh]);\n useEffect(() => {\n if (!open)\n return;\n // While the panel is open, titles, activity excerpts, and the routing\n // ring keep moving; poll so the listing visibly follows.\n const poll = setInterval(refresh, 2_000);\n return () => { clearInterval(poll); };\n }, [open, refresh]);\n useEffect(() => {\n if (!open)\n return;\n const onPointerDown = (event) => {\n if (event.target.closest(`.${css.wrap}`) === null)\n setOpen(false);\n };\n document.addEventListener('pointerdown', onPointerDown);\n return () => { document.removeEventListener('pointerdown', onPointerDown); };\n }, [open]);\n /** Join or leave one session, then refresh the listing. */\n const toggle = (row) => {\n setBusy(row.id);\n void fetch(`/__dsh_a2a/${row.joined ? 'leave' : 'join'}`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ id: row.id }),\n })\n .then(() => { refresh(); })\n .catch(() => { })\n .finally(() => { if (!stopped.current)\n setBusy(null); });\n };\n /** Assign one session to a group ('' clears), then refresh. */\n const assign = (id, name) => {\n void fetch('/__dsh_a2a/groups', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ action: 'assign', id, name }),\n })\n .then(() => { setPickerFor(null); setNewGroupName(''); refresh(); })\n .catch(() => { });\n };\n const { sessions, groups, peers, activity, inFlight, remote } = state;\n // Search filters by name, team, or description (case-insensitive).\n const needle = search.trim().toLowerCase();\n const matches = (row) => {\n if (needle === '')\n return true;\n const name = row.live === false ? (listById[row.id]?.displayTitle ?? row.label) : (row.name ?? row.label);\n return name.toLowerCase().includes(needle)\n || row.team.toLowerCase().includes(needle)\n || (row.description ?? '').toLowerCase().includes(needle);\n };\n const visible = sessions.filter(matches);\n const ungrouped = visible.filter(row => row.group === undefined);\n const grouped = groups\n .map(name => ({ name, rows: visible.filter(row => row.group === name) }))\n .filter(group => group.rows.length > 0);\n return (_jsxs(\"div\", { ref: wrap, className: clsx(css.wrap, wide ? css.wide : css.rail), children: [_jsx(Button, { variant: \"ghost\", size: \"sm\", title: t('a2a.label'), \"aria-label\": t('a2a.label'), \"aria-expanded\": open, onClick: () => { toggleOpen(); }, children: _jsxs(\"span\", { className: css.trigger, children: [_jsx(IconGlobeOutline14, {}), wide ? _jsx(\"span\", { children: t('a2a.label') }) : null, unread > 0 ? _jsx(\"span\", { className: css.unreadBadge, \"aria-label\": String(unread), children: unread > 9 ? '9+' : String(unread) }) : null] }) }), open\n ? (_jsxs(\"div\", { className: css.popover, role: \"dialog\", \"aria-label\": t('a2a.title'), style: anchor === null ? undefined : { left: `${String(anchor.left)}px`, bottom: `${String(anchor.bottom)}px` }, children: [_jsxs(\"div\", { className: css.title, children: [t('a2a.title'), state.version !== undefined ? _jsxs(\"span\", { className: css.versionTag, title: state.version, children: [\"v\", state.version] }) : null] }), _jsx(\"input\", { className: css.searchInput, type: \"search\", value: search, placeholder: t('a2a.search'), \"aria-label\": t('a2a.search'), onChange: (event) => { setSearch(event.target.value); } }), sessions.length === 0\n ? _jsx(\"div\", { className: css.empty, children: t('a2a.empty') })\n : visible.length === 0\n ? _jsx(\"div\", { className: css.empty, children: t('a2a.searchEmpty') })\n : (_jsxs(_Fragment, { children: [ungrouped.length > 0 && (_jsxs(\"div\", { className: css.sessionGroup, children: [_jsxs(\"button\", { type: \"button\", className: css.groupHead, onClick: () => {\n setCollapsed((current) => {\n const next = new Set(current);\n if (next.has(''))\n next.delete('');\n else\n next.add('');\n return next;\n });\n }, children: [_jsx(\"span\", { className: clsx(css.groupChevron, collapsed.has('') && css.groupChevronClosed), \"aria-hidden\": true, children: \"\\u25BE\" }), t('a2a.groupDefault'), \" \\u00B7 \", String(ungrouped.length)] }), !collapsed.has('') && ungrouped.map((row) => (_jsx(SessionRow, { row: row, t: t, listById: listById, openSession: openSession, busy: busy, onToggle: toggle, pickerFor: pickerFor, setPickerFor: setPickerFor, groups: groups, newGroupName: newGroupName, setNewGroupName: setNewGroupName, onAssign: assign }, row.id)))] })), grouped.map(({ name, rows }) => {\n const isCollapsed = collapsed.has(name);\n return (_jsxs(\"div\", { className: css.sessionGroup, children: [_jsxs(\"button\", { type: \"button\", className: css.groupHead, onClick: () => {\n setCollapsed((current) => {\n const next = new Set(current);\n if (next.has(name))\n next.delete(name);\n else\n next.add(name);\n return next;\n });\n }, children: [_jsx(\"span\", { className: clsx(css.groupChevron, isCollapsed && css.groupChevronClosed), \"aria-hidden\": true, children: \"\\u25BE\" }), name, \" \\u00B7 \", String(rows.length)] }), !isCollapsed && rows.map((row) => (_jsx(SessionRow, { row: row, t: t, listById: listById, openSession: openSession, busy: busy, onToggle: toggle, pickerFor: pickerFor, setPickerFor: setPickerFor, groups: groups, newGroupName: newGroupName, setNewGroupName: setNewGroupName, onAssign: assign }, row.id)))] }, name));\n })] })), _jsx(\"div\", { className: css.note, children: t('a2a.note') }), (needle === '' ? remote : remote.filter(row => row.team.toLowerCase().includes(needle) || row.name.toLowerCase().includes(needle) || (row.origin ?? '').toLowerCase().includes(needle) || (row.workspace ?? '').toLowerCase().includes(needle))).length > 0 && (_jsxs(_Fragment, { children: [_jsx(\"div\", { className: css.sectionTitle, children: t('a2a.remote') }), Object.entries((needle === '' ? remote : remote.filter(row => row.team.toLowerCase().includes(needle) || row.name.toLowerCase().includes(needle) || (row.origin ?? '').toLowerCase().includes(needle) || (row.workspace ?? '').toLowerCase().includes(needle)))\n .reduce((byOrigin, row) => {\n const key = row.origin ?? '';\n byOrigin[key] = [...(byOrigin[key] ?? []), row];\n return byOrigin;\n }, {})).map(([origin, rows]) => (_jsxs(\"div\", { className: css.sessionGroup, children: [_jsxs(\"button\", { type: \"button\", className: css.groupHead, onClick: () => {\n setCollapsed((current) => {\n const next = new Set(current);\n if (next.has('remote:' + origin))\n next.delete('remote:' + origin);\n else\n next.add('remote:' + origin);\n return next;\n });\n }, children: [_jsx(\"span\", { className: clsx(css.groupChevron, collapsed.has('remote:' + origin) && css.groupChevronClosed), \"aria-hidden\": true, children: \"\\u25BE\" }), origin === '' ? t('a2a.remoteUnknownOrigin') : origin, \" \\u00B7 \", String(rows.length)] }), !collapsed.has('remote:' + origin) && rows.map((row) => (_jsx(\"div\", { className: css.row, title: row.workspace ?? undefined, children: _jsxs(\"div\", { className: css.facts, children: [_jsxs(\"span\", { className: css.nameRow, children: [_jsx(\"span\", { className: css.stateDot, \"data-peer\": true, \"aria-hidden\": true }), _jsx(\"span\", { className: css.name, children: row.name === '' ? row.team : row.name }), row.workspace !== undefined ? _jsx(\"span\", { className: css.groupTag, children: row.workspace }) : null] }), _jsx(\"span\", { className: css.team, children: row.team })] }) }, row.team)))] }, origin)))] })), _jsx(\"div\", { className: css.sectionTitle, children: t('a2a.peers') }), peers.length === 0\n ? _jsx(\"div\", { className: css.empty, children: t('a2a.peersEmpty') })\n : (_jsx(\"div\", { className: css.peerGrid, children: peers.map((peer) => (_jsxs(\"span\", { className: css.peerChip, title: peer.url, children: [_jsx(\"span\", { className: css.peerHost, children: peer.url.replace(/^https?:\\/\\//, '').replace(/\\/.*$/, '') }), typeof peer.score === 'number' ? _jsx(\"span\", { className: css.peerScore, children: String(Math.round(peer.score)) }) : null] }, peer.url))) })), _jsx(\"div\", { className: css.sectionTitle, children: t('a2a.activity') }), inFlight.length > 0 && (_jsx(\"div\", { className: css.inFlightList, \"aria-label\": t('a2a.inFlight'), children: inFlight.map((route) => (_jsxs(\"div\", { className: clsx(css.inFlightRow, Date.now() - route.startedAt > 120_000 && css.inFlightStale), title: Date.now() - route.startedAt > 120_000 ? t('a2a.inFlightStale') : undefined, children: [_jsx(\"span\", { className: css.inFlightPulse, \"aria-hidden\": true }), _jsx(\"span\", { className: clsx(css.activityDir, 'out'), children: '→' }), _jsx(\"span\", { className: css.activityTeam, title: route.team, children: route.team }), _jsx(\"span\", { className: css.activityPeer, children: route.peer === 'local' ? '' : route.peer.replace(/^https?:\\/\\//, '').replace(/\\/.*$/, '') }), _jsx(\"span\", { className: css.activityTime, children: relativeTime(route.startedAt) })] }, `${route.team}-${String(route.startedAt)}`))) })), activity.length === 0\n ? _jsx(\"div\", { className: css.empty, children: t('a2a.activityEmpty') })\n : (_jsx(\"div\", { className: css.activityList, children: [...activity].reverse().slice(0, 10).map((entry, index) => {\n // A same-host session team resolves to its session row;\n // clicking the activity line jumps there (the linkage\n // into an ongoing collaboration).\n const target = sessions.find(row => row.team === entry.team);\n const fresh = Date.now() - entry.ts < 5_000;\n return (_jsxs(\"div\", { className: clsx(css.activityRow, fresh && css.fresh), \"data-ok\": entry.ok, \"data-jump\": target !== undefined, title: target !== undefined ? t('a2a.jump') : undefined, onClick: () => { if (target !== undefined)\n openSession(target.id); }, role: target !== undefined ? 'button' : undefined, children: [_jsx(\"span\", { className: clsx(css.activityDir, entry.dir), children: entry.dir === 'in' ? '←' : '→' }), _jsx(\"span\", { className: css.activityTeam, title: entry.team, children: entry.team }), _jsx(\"span\", { className: css.activityPeer, title: entry.peer, children: entry.peer === '' ? '' : entry.peer.replace(/^https?:\\/\\//, '').replace(/\\/.*$/, '') }), _jsx(\"span\", { className: css.activityTime, children: relativeTime(entry.ts) })] }, `${String(entry.ts)}-${String(index)}`));\n }) }))] }))\n : null] }));\n}\n","/** Locale dictionaries for the A2A network sidebar control (namespace `a2aNet`). */\n/** Simplified Chinese dictionary. */\nexport const zh = {\n 'a2a.label': 'A2A 网络',\n 'a2a.title': 'A2A 网络面板',\n 'a2a.empty': '没有可加入的会话',\n 'a2a.join': '加入',\n 'a2a.leave': '退出',\n 'a2a.cold': '未加载(重启后待唤醒)',\n 'a2a.wake': '唤醒',\n 'a2a.note': '只有加入的会话才会被网络中的其他节点发现;点击会话行可跳转。',\n 'a2a.remote': '远程节点(按 host 分组)',\n 'a2a.remoteUnknownOrigin': '未知来源',\n 'a2a.peers': '对等节点',\n 'a2a.peersEmpty': '尚无已知对等节点(配 peers 种子入网)',\n 'a2a.activity': '路由活动',\n 'a2a.activityEmpty': '暂无路由记录',\n 'a2a.inFlight': '进行中的路由',\n 'a2a.inFlightStale': '等待回执超时(对端处理慢或回执丢失);180 秒后自动解除并按已送达处理',\n 'a2a.jump': '点击跳转到该会话',\n 'a2a.search': '搜索会话(名称 / team / 摘要)',\n 'a2a.searchEmpty': '没有匹配的会话',\n 'a2a.group': '设置分组',\n 'a2a.groupDefault': '未分组',\n 'a2a.groupClear': '清除分组',\n 'a2a.groupNew': '新建分组…',\n 'a2a.groupNewGo': '创建并归入',\n};\n/** English dictionary. */\nexport const en = {\n 'a2a.label': 'A2A network',\n 'a2a.title': 'A2A network panel',\n 'a2a.empty': 'No joinable sessions',\n 'a2a.join': 'Join',\n 'a2a.leave': 'Leave',\n 'a2a.cold': 'not loaded (waiting to wake after a restart)',\n 'a2a.wake': 'Wake',\n 'a2a.note': 'Only joined sessions are discoverable by other nodes; click a row to open the session.',\n 'a2a.remote': 'Remote teams (by host)',\n 'a2a.remoteUnknownOrigin': 'Unknown origin',\n 'a2a.peers': 'Peers',\n 'a2a.peersEmpty': 'No known peers yet (set peers seeds to join the mesh)',\n 'a2a.activity': 'Routing activity',\n 'a2a.activityEmpty': 'No routes yet',\n 'a2a.inFlight': 'Routes in flight',\n 'a2a.inFlightStale': 'Reply wait past 120s (slow or lost receipt); auto-releases as delivered at 180s',\n 'a2a.jump': 'Click to open this session',\n 'a2a.search': 'Search sessions (name / team / excerpt)',\n 'a2a.searchEmpty': 'No sessions match',\n 'a2a.group': 'Set group',\n 'a2a.groupDefault': 'Ungrouped',\n 'a2a.groupClear': 'Clear group',\n 'a2a.groupNew': 'New group…',\n 'a2a.groupNewGo': 'Create & assign',\n};\n","import { A2aControl } from \"./A2aControl.js\";\nimport { en, zh } from \"./locales.js\";\n/** Dictionary namespace owned by this plugin (network control copy). */\nconst NS = 'a2aNet';\n/** Services required by the browser half: the slot system, the sessions\n * flow used to wake cold rows, and the locale dictionaries. */\nexport const inject = ['slots', 'sessions', 'locale'];\n/** Registers the network control's dictionaries and its sidebar seat.\n * @param ctx - Client root context.\n */\nexport function apply(ctx) {\n ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'a2a-net: dictionaries');\n // The network control renders only on a host that serves the session-node\n // state route (a2a host half + session nodes composed); the probe keeps\n // the seat quiet everywhere else.\n void fetch('/__dsh_a2a/state', { cache: 'no-store' })\n .then(response => (response.ok ? response.json() : undefined))\n .then((info) => {\n if (info?.nodes !== true)\n return;\n ctx.slots.inject('sidebar.footer.action', () => ctx.slots.register({\n name: 'sidebar.footer.action',\n id: 'a2a',\n order: 10,\n locale: NS,\n // Wake = the standard session-open flow: the host materializes the\n // session's agent and the a2a host half's persisted intent remounts\n // the node, so the cold row flips live on the next poll.\n inject: () => ({\n openSession: (id) => { ctx.sessions.open(id); },\n }),\n }, A2aControl));\n })\n .catch(() => { });\n}\n"],"x_google_ignoreList":[0],"mappings":"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"}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Persisted session groups: user-named buckets for the network panel's
|
|
3
|
+
* session listing, stored under the a2a home so they survive restarts and
|
|
4
|
+
* follow the node (not the browser). Plain JSON, one writer (the control
|
|
5
|
+
* routes), read fresh by the state route.
|
|
6
|
+
* @module @nelsonlongxiang/dsh-open-a2a-net/group-store
|
|
7
|
+
*/
|
|
8
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
9
|
+
import { dirname } from 'node:path';
|
|
10
|
+
/** Upper bound on named groups (a panel taxonomy, not a database). */
|
|
11
|
+
export const GROUP_CAP = 20;
|
|
12
|
+
/**
|
|
13
|
+
* Bounded group store with last-write-wins persistence.
|
|
14
|
+
*/
|
|
15
|
+
export class GroupStore {
|
|
16
|
+
groups = [];
|
|
17
|
+
assignments = {};
|
|
18
|
+
path;
|
|
19
|
+
/**
|
|
20
|
+
* @param path - the snapshot file; an empty path keeps the store in memory.
|
|
21
|
+
*/
|
|
22
|
+
constructor(path) {
|
|
23
|
+
this.path = path;
|
|
24
|
+
this.restore();
|
|
25
|
+
}
|
|
26
|
+
/** Load a persisted snapshot, if present and well-formed. */
|
|
27
|
+
restore() {
|
|
28
|
+
if (this.path === '' || !existsSync(this.path))
|
|
29
|
+
return;
|
|
30
|
+
try {
|
|
31
|
+
const snapshot = JSON.parse(readFileSync(this.path, 'utf8'));
|
|
32
|
+
if (Array.isArray(snapshot?.groups)) {
|
|
33
|
+
this.groups = snapshot.groups.filter((name) => typeof name === 'string' && name.trim() !== '').slice(0, GROUP_CAP);
|
|
34
|
+
}
|
|
35
|
+
if (snapshot?.assignments !== null && typeof snapshot?.assignments === 'object') {
|
|
36
|
+
const kept = {};
|
|
37
|
+
for (const [id, name] of Object.entries(snapshot.assignments)) {
|
|
38
|
+
if (typeof name === 'string' && this.groups.includes(name))
|
|
39
|
+
kept[id] = name;
|
|
40
|
+
}
|
|
41
|
+
this.assignments = kept;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
// A corrupt store is not fatal: fall back to empty groups.
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
/** Persist the current snapshot (no-op without a path). */
|
|
49
|
+
persist() {
|
|
50
|
+
if (this.path === '')
|
|
51
|
+
return;
|
|
52
|
+
try {
|
|
53
|
+
mkdirSync(dirname(this.path), { recursive: true });
|
|
54
|
+
const snapshot = { groups: [...this.groups], assignments: { ...this.assignments } };
|
|
55
|
+
writeFileSync(this.path, JSON.stringify(snapshot), { mode: 0o600 });
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
// An unwritable home must not break the panel; the store degrades to memory-only.
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/** Every named group, in creation order. */
|
|
62
|
+
list() {
|
|
63
|
+
return [...this.groups];
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* The group a session belongs to, if any.
|
|
67
|
+
* @param id - the session id.
|
|
68
|
+
*/
|
|
69
|
+
groupOf(id) {
|
|
70
|
+
return this.assignments[id];
|
|
71
|
+
}
|
|
72
|
+
/** Every assignment, for the state route's session rows. */
|
|
73
|
+
all() {
|
|
74
|
+
return { ...this.assignments };
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Create a named group; whitespace trims, duplicates are idempotent, and
|
|
78
|
+
* the cap rejects a group beyond {@link GROUP_CAP}.
|
|
79
|
+
* @param name - the raw user-supplied name.
|
|
80
|
+
* @returns the stored name, or undefined when rejected.
|
|
81
|
+
*/
|
|
82
|
+
create(name) {
|
|
83
|
+
const clean = name.trim();
|
|
84
|
+
if (clean === '' || clean.length > 40)
|
|
85
|
+
return undefined;
|
|
86
|
+
if (this.groups.includes(clean))
|
|
87
|
+
return clean;
|
|
88
|
+
if (this.groups.length >= GROUP_CAP)
|
|
89
|
+
return undefined;
|
|
90
|
+
this.groups.push(clean);
|
|
91
|
+
this.persist();
|
|
92
|
+
return clean;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Assign one session to an existing group (creating it when new and
|
|
96
|
+
* under the cap); an empty group name unassigns.
|
|
97
|
+
* @param id - the session id.
|
|
98
|
+
* @param name - the group name, or '' to clear.
|
|
99
|
+
*/
|
|
100
|
+
assign(id, name) {
|
|
101
|
+
if (name === '') {
|
|
102
|
+
if (this.assignments[id] === undefined)
|
|
103
|
+
return true;
|
|
104
|
+
delete this.assignments[id];
|
|
105
|
+
this.persist();
|
|
106
|
+
return true;
|
|
107
|
+
}
|
|
108
|
+
const stored = this.create(name);
|
|
109
|
+
if (stored === undefined)
|
|
110
|
+
return false;
|
|
111
|
+
this.assignments[id] = stored;
|
|
112
|
+
this.persist();
|
|
113
|
+
return true;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Delete an empty-or-not group; its assignments clear with it.
|
|
117
|
+
* @param name - the group name.
|
|
118
|
+
*/
|
|
119
|
+
remove(name) {
|
|
120
|
+
const index = this.groups.indexOf(name);
|
|
121
|
+
if (index < 0)
|
|
122
|
+
return false;
|
|
123
|
+
this.groups.splice(index, 1);
|
|
124
|
+
for (const id of Object.keys(this.assignments)) {
|
|
125
|
+
if (this.assignments[id] === name)
|
|
126
|
+
delete this.assignments[id];
|
|
127
|
+
}
|
|
128
|
+
this.persist();
|
|
129
|
+
return true;
|
|
130
|
+
}
|
|
131
|
+
}
|