@nelsonlongxiang/dsh-open-a2a-net 0.6.42 → 0.6.43
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 +1 -0
- package/README_zh.md +1 -0
- package/assets/nexusDist/assets/index-CaA76j0F.js +9 -0
- package/assets/nexusDist/index.html +1 -1
- package/cli/a2a.mjs +24 -4
- package/lib/canvas-store.js +249 -17
- package/lib/client.js +62 -72
- package/lib/client.js.map +1 -1
- package/lib/config.js +1 -0
- package/lib/faces/admin.js +103 -63
- package/lib/faces/wire.js +3 -1
- package/lib/index.js +59 -13
- package/lib/types/canvas-store.d.ts +48 -1
- package/lib/types/config.d.ts +2 -0
- package/lib/types/faces/admin.d.ts +2 -0
- package/nexus-stage/src/main.ts +7 -0
- package/nexus-stage/src/workbench.ts +24 -2
- package/package.json +1 -1
- package/assets/nexusDist/assets/index-NkNSeIx6.js +0 -9
package/lib/client.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.js","names":["css","Button","IconGlobeOutlineRegular","_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 recent network\n * activity feed. The feed is the panel's whole content — in-flight routes,\n * owed receipts, and the routing activity ring. Session/team management\n * lives in the planning view (design: docs/design/panel-activity-feed.md):\n * the stage links row above the feed is the explicit navigation to it.\n * Probes the host's state route at registration time (the seat renders only\n * on a host that serves it). While open, the feed polls so it visibly\n * follows each turn; the closed trigger's unread badge rides a slow poll.\n */\nimport { useCallback, useEffect, useRef, useState } from 'react';\nimport clsx from 'clsx';\nimport { Button, IconGlobeOutlineRegular } 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 activity: Array.isArray(body.activity) ? body.activity : [],\n inFlight: Array.isArray(body.inFlight) ? body.inFlight : [],\n tasks: Array.isArray(body.tasks) ? body.tasks : [],\n ...(typeof body.tasksStaleCount === 'number' ? { tasksStaleCount: body.tasksStaleCount } : {}),\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/** One feed row: direction glyph (aria-labelled), team, peer host, age, fail mark. */\nfunction FeedRow({ dir, team, peer, ts, ok, target, openSession, t }) {\n const fresh = Date.now() - ts < 5_000;\n return (_jsxs(\"div\", { className: clsx(css.activityRow, fresh && css.fresh), \"data-ok\": ok === undefined ? undefined : String(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, dir), \"aria-label\": dir === 'fwd' ? 'forwarded' : dir === 'in' ? 'inbound' : 'outbound', children: dir === 'fwd' ? '⇢' : dir === 'in' ? '←' : '→' }), _jsx(\"span\", { className: css.activityTeam, title: team, children: team }), _jsx(\"span\", { className: css.activityPeer, title: peer, children: peer === '' || peer === 'local' ? '' : peer.replace(/^https?:\\/\\//, '').replace(/\\/.*$/, '') }), ok === false ? _jsx(\"span\", { className: css.failMark, \"aria-label\": \"failed\", children: \"\\u2715\" }) : null, _jsx(\"span\", { className: css.activityTime, children: relativeTime(ts) })] }));\n}\n/**\n * Render the network toggle and its activity feed.\n * @param props - composed slot props.\n * @returns the control element tree.\n */\nexport function A2aControl({ wide, t, openSession }) {\n const [open, setOpen] = useState(false);\n const [state, setState] = useState({ sessions: [], activity: [], inFlight: [], tasks: [] });\n const [seenActivity, setSeenActivity] = useState(0);\n const stopped = useRef(false);\n useEffect(() => () => { stopped.current = true; }, []);\n const wrap = useRef(null);\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 feed is open, it polls so the ring visibly follows turns.\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 // 0.6.10: the receive face of click-to-reach (0.6.7 BroadcastChannel,\n // 0.6.8 ?dsh-a2a-open deep link) is removed by ruling — 禁止任何跳转,\n // 全部使用响应式布局. The workbench's 快派 form is the only cross-surface\n // reach primitive and it stays on the workbench page; this panel's\n // FeedRow openSession remains (it switches a conversation in place inside\n // the GUI — no navigation).\n const { sessions, activity, inFlight, tasks } = state;\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(IconGlobeOutlineRegular, {}), 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] }), _jsxs(\"div\", { className: css.stageRow, children: [_jsx(\"span\", { className: css.stageTitle, children: t('a2a.stageTitle') }), _jsx(\"span\", { className: css.stageLinks, children: _jsx(\"a\", { className: css.stageLink, href: \"/__dsh_a2a_nexus/\", target: \"_blank\", rel: \"noreferrer\", title: t('a2a.stageWorkbench'), children: t('a2a.stageWorkbench') }) })] }), _jsxs(\"div\", { \"aria-live\": \"polite\", children: [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(FeedRow, { dir: \"out\", team: route.team, peer: route.peer, ts: route.startedAt, openSession: openSession, t: t })] }, `${route.team}-${String(route.startedAt)}`))) })), (() => {\n // C-ruling: render only settleable debt; the stale tail is a\n // count line, never a row-per-noise listing.\n const settleableTasks = tasks.filter(task => task.settleable !== false);\n const staleCount = state.tasksStaleCount ?? (tasks.length - settleableTasks.length);\n if (settleableTasks.length === 0 && staleCount === 0)\n return null;\n return (_jsxs(_Fragment, { children: [settleableTasks.length > 0 && (_jsxs(_Fragment, { children: [_jsx(\"div\", { className: css.sectionTitle, children: t('a2a.tasks') }), _jsx(\"div\", { className: css.inFlightList, \"aria-label\": t('a2a.tasks'), children: settleableTasks.map((task) => (_jsx(\"div\", { className: css.inFlightRow, title: t('a2a.tasksNote'), children: _jsx(FeedRow, { dir: \"out\", team: task.team, peer: task.peer, ts: task.startedAt, openSession: openSession, t: t }) }, task.taskId))) })] })), staleCount > 0 && (_jsxs(\"div\", { className: css.staleNote, title: t('a2a.tasksNote'), children: [t('a2a.tasksStale'), \" \\u00B7 \", String(staleCount)] }))] }));\n })(), _jsx(\"div\", { className: css.sectionTitle, children: t('a2a.activity') }), 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 return (_jsx(FeedRow, { dir: entry.dir, team: entry.team, peer: entry.peer, ts: entry.ts, ok: entry.ok, ...(target !== undefined ? { target } : {}), openSession: openSession, t: t }, `${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.activity': '最近动态',\n 'a2a.activityEmpty': '暂无网络动态——加入网络即可开始协作',\n 'a2a.inFlight': '进行中的路由',\n 'a2a.inFlightStale': '等待回执超时(对端处理慢或回执丢失);180 秒后自动解除并按已送达处理',\n 'a2a.tasks': '欠回执的异步任务',\n 'a2a.tasksNote': '异步欠账分三段:未决(等回执)→ 死信(超期自动标注,目标恢复后仍可补回执结算)→ 归档(结算留痕不蒸发)',\n 'a2a.tasksStale': '对话式已送达未答(非欠账,24h 自动死信)',\n 'a2a.jump': '点击跳转到该会话',\n 'a2a.stageTitle': 'A2A 工作台(目录 · 台账 · 活动 · 规划)',\n 'a2a.stageWorkbench': '打开工作台',\n};\n/** English dictionary. */\nexport const en = {\n 'a2a.label': 'A2A network',\n 'a2a.title': 'A2A network activity',\n 'a2a.activity': 'Recent activity',\n 'a2a.activityEmpty': 'No network activity yet — join sessions to the network to start collaborating',\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.tasks': 'Owed receipts',\n 'a2a.tasksNote': 'Async debts in three tiers: pending (awaiting receipt) → dead-lettered (auto-flagged past the stale TTL; a revived target can still settle late) → archived (settled records kept for audit)',\n 'a2a.tasksStale': 'Delivered conversationally, no formal receipt (not debt; auto-dead-letters at the TTL)',\n 'a2a.jump': 'Click to open this session',\n 'a2a.stageTitle': 'A2A workbench (directory · ledger · activity · plan)',\n 'a2a.stageWorkbench': 'Open workbench',\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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"}
|
|
1
|
+
{"version":3,"file":"client.js","names":["css","Button","IconGlobeOutlineRegular","_Fragment"],"sources":["../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 recent network\n * activity feed. The feed is the panel's whole content — in-flight routes,\n * owed receipts, and the routing activity ring. Session/team management\n * lives in the planning view (design: docs/design/panel-activity-feed.md):\n * the stage links row above the feed is the explicit navigation to it.\n * Probes the host's state route at registration time (the seat renders only\n * on a host that serves it). While open, the feed polls so it visibly\n * follows each turn; the closed trigger's unread badge rides a slow poll.\n */\nimport { useCallback, useEffect, useRef, useState } from 'react';\nimport clsx from 'clsx';\nimport { Button, IconGlobeOutlineRegular } 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 activity: Array.isArray(body.activity) ? body.activity : [],\n inFlight: Array.isArray(body.inFlight) ? body.inFlight : [],\n tasks: Array.isArray(body.tasks) ? body.tasks : [],\n ...(typeof body.tasksStaleCount === 'number' ? { tasksStaleCount: body.tasksStaleCount } : {}),\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/** One feed row: direction glyph (aria-labelled), team, peer host, age, fail mark. */\nfunction FeedRow({ dir, team, peer, ts, ok, target, openSession, t }) {\n const fresh = Date.now() - ts < 5_000;\n return (_jsxs(\"div\", { className: clsx(css.activityRow, fresh && css.fresh), \"data-ok\": ok === undefined ? undefined : String(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, dir), \"aria-label\": dir === 'fwd' ? 'forwarded' : dir === 'in' ? 'inbound' : 'outbound', children: dir === 'fwd' ? '⇢' : dir === 'in' ? '←' : '→' }), _jsx(\"span\", { className: css.activityTeam, title: team, children: team }), _jsx(\"span\", { className: css.activityPeer, title: peer, children: peer === '' || peer === 'local' ? '' : peer.replace(/^https?:\\/\\//, '').replace(/\\/.*$/, '') }), ok === false ? _jsx(\"span\", { className: css.failMark, \"aria-label\": \"failed\", children: \"\\u2715\" }) : null, _jsx(\"span\", { className: css.activityTime, children: relativeTime(ts) })] }));\n}\n/**\n * Render the network toggle and its activity feed.\n * @param props - composed slot props.\n * @returns the control element tree.\n */\nexport function A2aControl({ wide, t, openSession }) {\n const [open, setOpen] = useState(false);\n const [state, setState] = useState({ sessions: [], activity: [], inFlight: [], tasks: [] });\n const [seenActivity, setSeenActivity] = useState(0);\n const stopped = useRef(false);\n useEffect(() => () => { stopped.current = true; }, []);\n const wrap = useRef(null);\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 feed is open, it polls so the ring visibly follows turns.\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 // 0.6.10: the receive face of click-to-reach (0.6.7 BroadcastChannel,\n // 0.6.8 ?dsh-a2a-open deep link) is removed by ruling — 禁止任何跳转,\n // 全部使用响应式布局. The workbench's 快派 form is the only cross-surface\n // reach primitive and it stays on the workbench page; this panel's\n // FeedRow openSession remains (it switches a conversation in place inside\n // the GUI — no navigation).\n const { sessions, activity, inFlight, tasks } = state;\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(IconGlobeOutlineRegular, {}), 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] }), _jsxs(\"div\", { className: css.stageRow, children: [_jsx(\"span\", { className: css.stageTitle, children: t('a2a.stageTitle') }), _jsx(\"span\", { className: css.stageLinks, children: _jsx(\"a\", { className: css.stageLink, href: \"/__dsh_a2a_nexus/\", target: \"_blank\", rel: \"noreferrer\", title: t('a2a.stageWorkbench'), children: t('a2a.stageWorkbench') }) })] }), _jsxs(\"div\", { \"aria-live\": \"polite\", children: [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(FeedRow, { dir: \"out\", team: route.team, peer: route.peer, ts: route.startedAt, openSession: openSession, t: t })] }, `${route.team}-${String(route.startedAt)}`))) })), (() => {\n // C-ruling: render only settleable debt; the stale tail is a\n // count line, never a row-per-noise listing.\n const settleableTasks = tasks.filter(task => task.settleable !== false);\n const staleCount = state.tasksStaleCount ?? (tasks.length - settleableTasks.length);\n if (settleableTasks.length === 0 && staleCount === 0)\n return null;\n return (_jsxs(_Fragment, { children: [settleableTasks.length > 0 && (_jsxs(_Fragment, { children: [_jsx(\"div\", { className: css.sectionTitle, children: t('a2a.tasks') }), _jsx(\"div\", { className: css.inFlightList, \"aria-label\": t('a2a.tasks'), children: settleableTasks.map((task) => (_jsx(\"div\", { className: css.inFlightRow, title: t('a2a.tasksNote'), children: _jsx(FeedRow, { dir: \"out\", team: task.team, peer: task.peer, ts: task.startedAt, openSession: openSession, t: t }) }, task.taskId))) })] })), staleCount > 0 && (_jsxs(\"div\", { className: css.staleNote, title: t('a2a.tasksNote'), children: [t('a2a.tasksStale'), \" \\u00B7 \", String(staleCount)] }))] }));\n })(), _jsx(\"div\", { className: css.sectionTitle, children: t('a2a.activity') }), 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 return (_jsx(FeedRow, { dir: entry.dir, team: entry.team, peer: entry.peer, ts: entry.ts, ok: entry.ok, ...(target !== undefined ? { target } : {}), openSession: openSession, t: t }, `${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.activity': '最近动态',\n 'a2a.activityEmpty': '暂无网络动态——加入网络即可开始协作',\n 'a2a.inFlight': '进行中的路由',\n 'a2a.inFlightStale': '等待回执超时(对端处理慢或回执丢失);180 秒后自动解除并按已送达处理',\n 'a2a.tasks': '欠回执的异步任务',\n 'a2a.tasksNote': '异步欠账分三段:未决(等回执)→ 死信(超期自动标注,目标恢复后仍可补回执结算)→ 归档(结算留痕不蒸发)',\n 'a2a.tasksStale': '对话式已送达未答(非欠账,24h 自动死信)',\n 'a2a.jump': '点击跳转到该会话',\n 'a2a.stageTitle': 'A2A 工作台(目录 · 台账 · 活动 · 规划)',\n 'a2a.stageWorkbench': '打开工作台',\n};\n/** English dictionary. */\nexport const en = {\n 'a2a.label': 'A2A network',\n 'a2a.title': 'A2A network activity',\n 'a2a.activity': 'Recent activity',\n 'a2a.activityEmpty': 'No network activity yet — join sessions to the network to start collaborating',\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.tasks': 'Owed receipts',\n 'a2a.tasksNote': 'Async debts in three tiers: pending (awaiting receipt) → dead-lettered (auto-flagged past the stale TTL; a revived target can still settle late) → archived (settled records kept for audit)',\n 'a2a.tasksStale': 'Delivered conversationally, no formal receipt (not debt; auto-dead-letters at the TTL)',\n 'a2a.jump': 'Click to open this session',\n 'a2a.stageTitle': 'A2A workbench (directory · ledger · activity · plan)',\n 'a2a.stageWorkbench': 'Open workbench',\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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"}
|
package/lib/config.js
CHANGED
|
@@ -100,6 +100,7 @@ export const Config = s.object({
|
|
|
100
100
|
* audience-bound form only. See docs/protocol/delivery-origin-auth.md §2.1.
|
|
101
101
|
*/
|
|
102
102
|
originClaimUnboundWindow: s.boolean().default(true),
|
|
103
|
+
canvasLegacyBareMembers: s.boolean().default(true),
|
|
103
104
|
/** Extra OriginClaim audience origins (port-mapped/proxied hosts). */
|
|
104
105
|
originClaimAudiences: s.array(s.string()).default([]),
|
|
105
106
|
linkKeys: s.dict(s.string()).default({}),
|
package/lib/faces/admin.js
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
19
19
|
import { join } from 'node:path';
|
|
20
20
|
import { fileURLToPath } from 'node:url';
|
|
21
|
-
import { canvasNameReserved as reservedCanvasName, isRemoteMemberRef, validRemoteMemberRef } from "../canvas-store.js";
|
|
21
|
+
import { canvasNameReserved as reservedCanvasName, isRemoteMemberRef, validRemoteMemberRef, legacyBareMember } from "../canvas-store.js";
|
|
22
22
|
import { qualifiedCanvasId } from "../canvas-id.js";
|
|
23
23
|
import { refreshPeerCard, registerCanvasNotify } from "./canvas-notify.js";
|
|
24
24
|
import { CLI_HANDLE_RE } from "../cli-deposit.js";
|
|
@@ -133,6 +133,9 @@ export function registerAdminFaces(webServer, d) {
|
|
|
133
133
|
sessions,
|
|
134
134
|
groups: groupStore.list(),
|
|
135
135
|
canvas: {
|
|
136
|
+
...d.canvasMigrationState(),
|
|
137
|
+
// Raw retained slots are an inspection view, never the effective team member list.
|
|
138
|
+
retainedMembers: canvasStore.list().flatMap(name => canvasStore.rawMembersOf(name).filter(id => !canvasStore.membersOf(name).includes(id)).map(id => ({ team: name, member: id, suspended: canvasStore.isSuspended(name, id), records: canvasStore.suspensionOf(name, id) }))),
|
|
136
139
|
teams: canvasStore.list().map(name => ({
|
|
137
140
|
name,
|
|
138
141
|
team: canvasTeamOf(name),
|
|
@@ -148,16 +151,17 @@ export function registerAdminFaces(webServer, d) {
|
|
|
148
151
|
const rows = [];
|
|
149
152
|
const seenLocal = new Set();
|
|
150
153
|
for (const id of canvasStore.membersOf(name)) {
|
|
154
|
+
const legacy = legacyBareMember(id);
|
|
151
155
|
if (isRemoteMemberRef(id)) {
|
|
152
156
|
const localId = localSessionOfHandle(id);
|
|
153
157
|
if (localId !== undefined) {
|
|
154
158
|
if (seenLocal.has(localId))
|
|
155
159
|
continue;
|
|
156
160
|
seenLocal.add(localId);
|
|
157
|
-
rows.push({ id: localId, team: `${config.team}/${id8(localId)}`, joined: sessionNodes.has(localId) || joinedSessions.has(localId), live: sessionNodes.has(localId) });
|
|
161
|
+
rows.push({ ...(legacy ? { provenance: 'legacy-inferred' } : {}), id: localId, team: `${config.team}/${id8(localId)}`, joined: sessionNodes.has(localId) || joinedSessions.has(localId), live: sessionNodes.has(localId) });
|
|
158
162
|
continue;
|
|
159
163
|
}
|
|
160
|
-
rows.push({ id, team: id, remote: true, ...(CLI_HANDLE_RE.test(id) ? { cliHome: canvasStore.cliHomeOf(id) ?? null, cliHeld: cliDeposits.directoryRows().some(row => row.team === id), ...(canvasStore.wasCliHomeCleared(id) ? { cliHomeCleared: true } : {}) } : {}) });
|
|
164
|
+
rows.push({ ...(legacy ? { provenance: 'unknown' } : {}), id, team: id, remote: true, ...(CLI_HANDLE_RE.test(id) ? { cliHome: canvasStore.cliHomeOf(id) ?? null, cliHeld: cliDeposits.directoryRows().some(row => row.team === id), ...(canvasStore.wasCliHomeCleared(id) ? { cliHomeCleared: true } : {}) } : {}) });
|
|
161
165
|
continue;
|
|
162
166
|
}
|
|
163
167
|
if (seenLocal.has(id))
|
|
@@ -169,7 +173,8 @@ export function registerAdminFaces(webServer, d) {
|
|
|
169
173
|
})(),
|
|
170
174
|
})),
|
|
171
175
|
},
|
|
172
|
-
|
|
176
|
+
// #332: home-controlled aliases, never inferred from peer cards or a file reference.
|
|
177
|
+
host: { ...(lanIp !== '' ? { lanIp } : {}), originClaimAudiences: [...config.originClaimAudiences ?? []] },
|
|
173
178
|
// gh#20 W-C (host rail): each peer row also carries its display
|
|
174
179
|
// name and version from the warm card (absent until a fetch),
|
|
175
180
|
// and the last-contact timestamp for the health lamp. W-A's
|
|
@@ -445,7 +450,16 @@ export function registerAdminFaces(webServer, d) {
|
|
|
445
450
|
readJsonBody(req, res, (body) => {
|
|
446
451
|
const id = typeof body.id === 'string' ? body.id : '';
|
|
447
452
|
// gh#131: read while the session is still joined — its own handle reads as it only then.
|
|
448
|
-
|
|
453
|
+
try {
|
|
454
|
+
if (id !== '')
|
|
455
|
+
d.revokeCanvasMember(id, 'leave');
|
|
456
|
+
}
|
|
457
|
+
catch (error) {
|
|
458
|
+
const payload = JSON.stringify({ ok: false, error: String(error) });
|
|
459
|
+
res.writeHead(503, { 'Content-Type': 'application/json' });
|
|
460
|
+
res.end(payload);
|
|
461
|
+
return;
|
|
462
|
+
}
|
|
449
463
|
if (id !== '')
|
|
450
464
|
sessionNodes.delete(id);
|
|
451
465
|
joinedSessions.remove(id);
|
|
@@ -453,9 +467,6 @@ export function registerAdminFaces(webServer, d) {
|
|
|
453
467
|
// without join consent would be a routing backdoor. Team
|
|
454
468
|
// roster declarations die the same way (S2 consistency) — and so
|
|
455
469
|
// does a membership a fleet edit stored by the session's handle.
|
|
456
|
-
canvasStore.dropMember(id);
|
|
457
|
-
if (ownHandle !== undefined)
|
|
458
|
-
canvasStore.dropMember(ownHandle);
|
|
459
470
|
teamMemberships.dropSession(id);
|
|
460
471
|
const payload = JSON.stringify({ id });
|
|
461
472
|
res.writeHead(200, { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(payload) });
|
|
@@ -545,6 +556,12 @@ export function registerAdminFaces(webServer, d) {
|
|
|
545
556
|
body,
|
|
546
557
|
signal: AbortSignal.timeout(15_000),
|
|
547
558
|
});
|
|
559
|
+
// #320: a JSON body cannot turn a failed/redirected HTTP operation into success.
|
|
560
|
+
if (!reply.ok || reply.status < 200 || reply.status >= 300) {
|
|
561
|
+
void reply.body?.cancel().catch(() => { });
|
|
562
|
+
send(JSON.stringify({ ok: false, status: reply.status, error: `peer ${peerUrl} answered HTTP ${String(reply.status)} — fleet canvas operation failed` }));
|
|
563
|
+
return;
|
|
564
|
+
}
|
|
548
565
|
const raw = await reply.text();
|
|
549
566
|
try {
|
|
550
567
|
const out = JSON.parse(raw);
|
|
@@ -738,72 +755,95 @@ export function registerAdminFaces(webServer, d) {
|
|
|
738
755
|
readJsonBody(req, res, async (body) => {
|
|
739
756
|
const action = body.action;
|
|
740
757
|
let payload;
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
758
|
+
try {
|
|
759
|
+
if (action === 'restore-suspended' || action === 'delete-suspended') {
|
|
760
|
+
const request = body;
|
|
761
|
+
const name = typeof request.name === 'string' ? request.name : '', id = typeof request.id === 'string' ? request.id : '';
|
|
762
|
+
const out = canvasStore.regulateSuspended(action === 'restore-suspended' ? 'restore' : 'delete', name, id, request.revision, request.expected);
|
|
763
|
+
logger.info('canvas suspended owner operation %s %s %s: %s', action, name, id, out.ok);
|
|
764
|
+
payload = JSON.stringify({ ...out, ...canvasStore.state() });
|
|
765
|
+
}
|
|
766
|
+
else if (action === 'cli-home-options') {
|
|
767
|
+
const name = typeof body.name === 'string' ? body.name : '';
|
|
768
|
+
const id = typeof body.id === 'string' ? body.id : '';
|
|
769
|
+
if (!CLI_HANDLE_RE.test(id) || !canvasStore.membersOf(name).includes(id))
|
|
770
|
+
payload = JSON.stringify({ ok: false, error: 'an existing canvas CLI member is required' });
|
|
771
|
+
else {
|
|
772
|
+
try {
|
|
773
|
+
payload = JSON.stringify({ ok: true, ...await d.cliHomeOptions(id) });
|
|
774
|
+
}
|
|
775
|
+
catch {
|
|
776
|
+
payload = JSON.stringify({ ok: false, error: 'fresh home card lookup failed' });
|
|
777
|
+
}
|
|
752
778
|
}
|
|
753
779
|
}
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
payload = JSON.stringify(stored === undefined ? { ok: false, error: 'invalid, roster-reserved name or team cap reached' } : { ok: true, name: stored, teams: canvasStore.list() });
|
|
759
|
-
}
|
|
760
|
-
else if (action === 'remove') {
|
|
761
|
-
const name = typeof body.name === 'string' ? body.name : '';
|
|
762
|
-
payload = JSON.stringify({ ok: canvasStore.remove(name), teams: canvasStore.list() });
|
|
763
|
-
}
|
|
764
|
-
else if (action === 'add-member') {
|
|
765
|
-
const name = typeof body.name === 'string' ? body.name : '';
|
|
766
|
-
const id = typeof body.id === 'string' ? body.id : '';
|
|
767
|
-
// Dual member shape: a local session id must be a joined node;
|
|
768
|
-
// a remote ref is a zone-qualified team handle whose shape this
|
|
769
|
-
// gate checks and whose existence the UI picker (directory
|
|
770
|
-
// rows) vouches for. Humans compose both via the workbench.
|
|
771
|
-
const memberValid = isRemoteMemberRef(id)
|
|
772
|
-
? validRemoteMemberRef(id)
|
|
773
|
-
: id !== '' && (sessionNodes.has(id) || joinedSessions.has(id));
|
|
774
|
-
if (!canvasStore.hasTeam(name) && canvasNameReserved(name)) {
|
|
775
|
-
payload = JSON.stringify({ ok: false, error: 'roster reserves this canvas name' });
|
|
780
|
+
else if (action === 'create') {
|
|
781
|
+
const name = typeof body.name === 'string' ? body.name : '';
|
|
782
|
+
const stored = canvasNameReserved(name) ? undefined : canvasStore.create(name);
|
|
783
|
+
payload = JSON.stringify(stored === undefined ? { ok: false, error: 'invalid, roster-reserved name or team cap reached' } : { ok: true, name: stored, teams: canvasStore.list() });
|
|
776
784
|
}
|
|
777
|
-
else if (
|
|
778
|
-
|
|
785
|
+
else if (action === 'remove') {
|
|
786
|
+
const name = typeof body.name === 'string' ? body.name : '';
|
|
787
|
+
payload = JSON.stringify({ ok: canvasStore.remove(name), teams: canvasStore.list() });
|
|
779
788
|
}
|
|
780
|
-
else {
|
|
781
|
-
const
|
|
782
|
-
|
|
783
|
-
|
|
789
|
+
else if (action === 'add-member') {
|
|
790
|
+
const name = typeof body.name === 'string' ? body.name : '';
|
|
791
|
+
const id = typeof body.id === 'string' ? body.id : '';
|
|
792
|
+
// Dual member shape: a local session id must be a joined node;
|
|
793
|
+
// a remote ref is a zone-qualified team handle whose shape this
|
|
794
|
+
// gate checks and whose existence the UI picker (directory
|
|
795
|
+
// rows) vouches for. Humans compose both via the workbench.
|
|
796
|
+
const memberValid = isRemoteMemberRef(id)
|
|
797
|
+
? validRemoteMemberRef(id)
|
|
798
|
+
: id !== '' && (sessionNodes.has(id) || joinedSessions.has(id));
|
|
799
|
+
if (!canvasStore.hasTeam(name) && canvasNameReserved(name)) {
|
|
800
|
+
payload = JSON.stringify({ ok: false, error: 'roster reserves this canvas name' });
|
|
801
|
+
}
|
|
802
|
+
else if (name === '' || !memberValid) {
|
|
803
|
+
payload = JSON.stringify({ ok: false, error: 'name and a member (joined session id, or a zone-qualified remote team handle like zone/a1b2c3d4) are required' });
|
|
804
|
+
}
|
|
784
805
|
else {
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
806
|
+
const pin = await d.cliHomeForAdd(id, body.home).catch(() => ({ ok: false, error: 'linked home card lookup failed' }));
|
|
807
|
+
if (!pin.ok)
|
|
808
|
+
payload = JSON.stringify({ ok: false, error: pin.error });
|
|
809
|
+
else {
|
|
810
|
+
canvasStore.create(name);
|
|
811
|
+
const explicitHome = body.home !== undefined;
|
|
812
|
+
const home = explicitHome ? pin.home : canvasStore.cliHomeOf(id) ?? pin.home;
|
|
813
|
+
payload = JSON.stringify({ ok: canvasStore.addMember(name, id, home, { replaceHome: explicitHome }), teams: canvasStore.list(), members: canvasStore.membersOf(name) });
|
|
814
|
+
}
|
|
789
815
|
}
|
|
790
816
|
}
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
817
|
+
else if (action === 'remove-member') {
|
|
818
|
+
const name = typeof body.name === 'string' ? body.name : '';
|
|
819
|
+
const id = typeof body.id === 'string' ? body.id : '';
|
|
820
|
+
if (name === '' || id === '') {
|
|
821
|
+
payload = JSON.stringify({ ok: false, error: 'name and id are required' });
|
|
822
|
+
}
|
|
823
|
+
else {
|
|
824
|
+
// gh#131: removing a local session removes its handle-stored slot too.
|
|
825
|
+
let removed = false;
|
|
826
|
+
try {
|
|
827
|
+
removed = isRemoteMemberRef(id) ? (!canvasStore.isSuspended(name, id) && canvasStore.removeMember(name, id)) : d.revokeCanvasMember(id, 'remove', name);
|
|
828
|
+
}
|
|
829
|
+
catch (error) {
|
|
830
|
+
payload = JSON.stringify({ ok: false, error: String(error) });
|
|
831
|
+
res.writeHead(503, { 'Content-Type': 'application/json' });
|
|
832
|
+
res.end(payload);
|
|
833
|
+
return;
|
|
834
|
+
}
|
|
835
|
+
payload = JSON.stringify({ ok: removed, teams: canvasStore.list(), members: canvasStore.membersOf(name) });
|
|
836
|
+
}
|
|
797
837
|
}
|
|
798
838
|
else {
|
|
799
|
-
|
|
800
|
-
const ownHandle = isRemoteMemberRef(id) ? undefined : ownCanvasHandleOf(id);
|
|
801
|
-
const removed = [id, ...(ownHandle !== undefined ? [ownHandle] : [])].map(each => canvasStore.removeMember(name, each)).some(Boolean);
|
|
802
|
-
payload = JSON.stringify({ ok: removed, teams: canvasStore.list(), members: canvasStore.membersOf(name) });
|
|
839
|
+
payload = JSON.stringify({ ok: false, error: 'unknown action' });
|
|
803
840
|
}
|
|
804
841
|
}
|
|
805
|
-
|
|
806
|
-
|
|
842
|
+
catch (error) {
|
|
843
|
+
const failure = JSON.stringify({ ok: false, error: String(error) });
|
|
844
|
+
res.writeHead(503, { 'Content-Type': 'application/json' });
|
|
845
|
+
res.end(failure);
|
|
846
|
+
return;
|
|
807
847
|
}
|
|
808
848
|
res.writeHead(200, { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(payload) });
|
|
809
849
|
res.end(payload);
|
package/lib/faces/wire.js
CHANGED
|
@@ -182,7 +182,9 @@ export function registerWireFaces(webServer, d) {
|
|
|
182
182
|
const name = typeof body.name === 'string' ? body.name : '';
|
|
183
183
|
const rawId = typeof body.id === 'string' ? body.id : '';
|
|
184
184
|
// gh#131: a peer names our sessions by handle — the session it names, for removal.
|
|
185
|
-
|
|
185
|
+
// A bare handle may still infer for routing, never for deleting another stored slot.
|
|
186
|
+
const provenOwn = rawId.endsWith('@' + d.ownCanvasFingerprint);
|
|
187
|
+
const id = (provenOwn ? localSessionOfHandle(rawId) : undefined) ?? rawId;
|
|
186
188
|
let payload;
|
|
187
189
|
if (op === 'create') {
|
|
188
190
|
const stored = canvasNameReserved(name) ? undefined : canvasStore.create(name);
|
package/lib/index.js
CHANGED
|
@@ -30,7 +30,7 @@ export function resolveCallerLabel(input) {
|
|
|
30
30
|
}
|
|
31
31
|
import { encodePublicKey, signCard, verifyCard } from "./card.js";
|
|
32
32
|
import { GroupStore } from "./group-store.js";
|
|
33
|
-
import { CanvasStore, canvasNameReserved, isRemoteMemberRef } from "./canvas-store.js";
|
|
33
|
+
import { CanvasStore, canvasNameReserved, isRemoteMemberRef, legacyBareMember } from "./canvas-store.js";
|
|
34
34
|
import { decodeSessionLog, extractTranscript, TRANSCRIPT_MAX_FILE_BYTES } from "./session-transcript.js";
|
|
35
35
|
import { LayoutStore } from "./layout-store.js";
|
|
36
36
|
import { peerLinkDisplay } from "./peer-link-display.js";
|
|
@@ -597,7 +597,7 @@ export function apply(ctx, config) {
|
|
|
597
597
|
// Canvas teams: user-composed multi-member routing groups. A session node
|
|
598
598
|
// may sit in many teams; routing to <team>/canvas/<name> resolves the
|
|
599
599
|
// first live member or wakes the first cold one (member order = priority).
|
|
600
|
-
const canvasStore = new CanvasStore(join(home, 'a2a', 'canvas.json'));
|
|
600
|
+
const canvasStore = new CanvasStore(join(home, 'a2a', 'canvas.json'), { legacyBareMembers: config.canvasLegacyBareMembers !== false });
|
|
601
601
|
// #91 file channel: the sender host's blob store (docs/design/file-channel.md).
|
|
602
602
|
const blobStore = new BlobStore(join(home, 'a2a'));
|
|
603
603
|
// Spatial presentation state for the full-page stage (contract v2).
|
|
@@ -918,7 +918,13 @@ export function apply(ctx, config) {
|
|
|
918
918
|
for (const id of joinedSessions.list()) {
|
|
919
919
|
if (!isArchived(id))
|
|
920
920
|
continue;
|
|
921
|
-
|
|
921
|
+
try {
|
|
922
|
+
revokeCanvasMember(id, 'archive');
|
|
923
|
+
}
|
|
924
|
+
catch (error) {
|
|
925
|
+
logger.error('canvas archive suspension failed: %s', String(error));
|
|
926
|
+
continue;
|
|
927
|
+
}
|
|
922
928
|
sessionNodes.delete(id);
|
|
923
929
|
// Sections come back at the next mount (re-publish) if this archived reading was transient; the row stays.
|
|
924
930
|
nodeBinder.suspend(id);
|
|
@@ -928,9 +934,6 @@ export function apply(ctx, config) {
|
|
|
928
934
|
// joined session matches"). Safety without the deletion: wake refuses
|
|
929
935
|
// archived intents (:804), card assembly and the state cold rows filter
|
|
930
936
|
// them, the reconciler skips them, and JOIN_CAP bounds the file.
|
|
931
|
-
canvasStore.dropMember(id);
|
|
932
|
-
if (ownHandle !== undefined)
|
|
933
|
-
canvasStore.dropMember(ownHandle);
|
|
934
937
|
// S2 consistency: an archived node's roster declarations die with it
|
|
935
938
|
// — a left node keeps no team membership.
|
|
936
939
|
teamMemberships.dropSession(id);
|
|
@@ -961,7 +964,13 @@ export function apply(ctx, config) {
|
|
|
961
964
|
continue;
|
|
962
965
|
if (sessionNodes.has(id) || joinedSessions.has(id))
|
|
963
966
|
continue;
|
|
964
|
-
|
|
967
|
+
try {
|
|
968
|
+
canvasStore.removeMember(name, id);
|
|
969
|
+
}
|
|
970
|
+
catch (error) {
|
|
971
|
+
logger.error('canvas stale-member prune failed (%s/%s): %s', name, id, String(error));
|
|
972
|
+
continue;
|
|
973
|
+
}
|
|
965
974
|
logger.info(`a2a: canvas team "${name}" dropped stale member ${id8(id)} (no live node, no join intent)`);
|
|
966
975
|
}
|
|
967
976
|
}
|
|
@@ -1004,7 +1013,7 @@ export function apply(ctx, config) {
|
|
|
1004
1013
|
* @param includeArchived - count archived sessions too (the archive prune
|
|
1005
1014
|
* drops an archived session's own handle).
|
|
1006
1015
|
*/
|
|
1007
|
-
function localHandleResolver(includeArchived = false) {
|
|
1016
|
+
function localHandleResolver(includeArchived = false, count = false, legacyOverride = false) {
|
|
1008
1017
|
const isArchived = includeArchived ? undefined : archivedSessionFilter();
|
|
1009
1018
|
const byHandle = new Map();
|
|
1010
1019
|
for (const id of new Set([...joinedSessions.list(), ...sessionNodes.keys()])) {
|
|
@@ -1021,7 +1030,20 @@ export function apply(ctx, config) {
|
|
|
1021
1030
|
published.add(row.team);
|
|
1022
1031
|
}
|
|
1023
1032
|
return (ref) => {
|
|
1033
|
+
// #334 qualified member routing is deliberately deferred; this window is only legacy bare inference.
|
|
1034
|
+
if (!legacyOverride && config.canvasLegacyBareMembers === false)
|
|
1035
|
+
return undefined;
|
|
1024
1036
|
const hits = byHandle.get(ref);
|
|
1037
|
+
const collision = (hits?.length ?? 0) > 1 || (hits?.length === 1 && published.has(ref));
|
|
1038
|
+
if (count && legacyBareMember(ref)) {
|
|
1039
|
+
legacyInferenceResolutions++;
|
|
1040
|
+
if (collision) {
|
|
1041
|
+
legacyCollisionWarnings++;
|
|
1042
|
+
if (legacyCollisionRecent.length >= 16)
|
|
1043
|
+
legacyCollisionRecent.shift();
|
|
1044
|
+
legacyCollisionRecent.push({ member: ref, at: Date.now() });
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1025
1047
|
return hits?.length === 1 && !published.has(ref) ? hits[0] : undefined;
|
|
1026
1048
|
};
|
|
1027
1049
|
}
|
|
@@ -1032,6 +1054,30 @@ export function apply(ctx, config) {
|
|
|
1032
1054
|
* handle-stored membership must not outlive the session's consent).
|
|
1033
1055
|
* @param id - the local session id (still joined when this is read).
|
|
1034
1056
|
*/
|
|
1057
|
+
const canvasMigrationEpoch = new Date().toISOString();
|
|
1058
|
+
let legacyInferenceResolutions = 0, legacyCollisionWarnings = 0;
|
|
1059
|
+
const legacyCollisionRecent = [];
|
|
1060
|
+
function revokeCanvasMember(id, reason, name) {
|
|
1061
|
+
if (!joinedSessions.has(id) && !sessionNodes.has(id))
|
|
1062
|
+
return false;
|
|
1063
|
+
const bare = config.team + '/' + id8(id);
|
|
1064
|
+
const inferred = localHandleResolver(true, false, true)(bare) === id ? bare : undefined;
|
|
1065
|
+
const qualified = bare + '@' + ownCanvasFingerprint;
|
|
1066
|
+
const sameId8 = new Set([...joinedSessions.list(), ...sessionNodes.keys()].filter(local => id8(local) === id8(id)));
|
|
1067
|
+
const pinned = canvasStore.ownerKeyOf(qualified);
|
|
1068
|
+
const proof = sameId8.size === 1 && (pinned === undefined || pinned === encodePublicKey(createPublicKey(nodePrivateKey))) ? qualified : undefined;
|
|
1069
|
+
return canvasStore.revokeLocal(id, proof, inferred, reason, name);
|
|
1070
|
+
}
|
|
1071
|
+
function canvasMigrationState() {
|
|
1072
|
+
const raw = canvasStore.list().flatMap(name => canvasStore.rawMembersOf(name).filter(legacyBareMember).map(member => ({ name, member })));
|
|
1073
|
+
const resolve = localHandleResolver(false, false);
|
|
1074
|
+
return { canvasLegacyBareMembers: config.canvasLegacyBareMembers !== false, epoch: canvasMigrationEpoch,
|
|
1075
|
+
legacyBareMembers: raw.length,
|
|
1076
|
+
legacyInferredMembers: raw.filter(r => canvasStore.membersOf(r.name).includes(r.member) && resolve(r.member) !== undefined).length,
|
|
1077
|
+
legacyInferenceResolutions, legacyCollisionWarnings, legacyCollisionRecent: [...legacyCollisionRecent],
|
|
1078
|
+
suspendedMembersCount: new Set(canvasStore.state().suspendedMembers.map(r => JSON.stringify([r.team, r.member]))).size,
|
|
1079
|
+
...canvasStore.state() };
|
|
1080
|
+
}
|
|
1035
1081
|
function ownCanvasHandleOf(id, includeArchived = false) {
|
|
1036
1082
|
const handle = `${config.team}/${id8(id)}`;
|
|
1037
1083
|
return localHandleResolver(includeArchived)(handle) === id ? handle : undefined;
|
|
@@ -1059,8 +1105,8 @@ export function apply(ctx, config) {
|
|
|
1059
1105
|
* reading that depends on which peer cards are cached must not rewrite
|
|
1060
1106
|
* the store (review of PR #151 P2-2).
|
|
1061
1107
|
*/
|
|
1062
|
-
function canvasLocalMembersOf(name) {
|
|
1063
|
-
const resolve = localHandleResolver();
|
|
1108
|
+
function canvasLocalMembersOf(name, count = false) {
|
|
1109
|
+
const resolve = localHandleResolver(false, count);
|
|
1064
1110
|
return canvasStore.membersOf(name).map(id => (isRemoteMemberRef(id) ? resolve(id) ?? id : id));
|
|
1065
1111
|
}
|
|
1066
1112
|
/** The 8-char suffix of one agent's id. */
|
|
@@ -1111,7 +1157,7 @@ export function apply(ctx, config) {
|
|
|
1111
1157
|
function canvasLiveAgent(name, skipId) {
|
|
1112
1158
|
if (name === undefined)
|
|
1113
1159
|
return undefined;
|
|
1114
|
-
for (const id of canvasLocalMembersOf(name)) {
|
|
1160
|
+
for (const id of canvasLocalMembersOf(name, true)) {
|
|
1115
1161
|
if (id === skipId)
|
|
1116
1162
|
continue;
|
|
1117
1163
|
const agent = sessionNodes.get(id);
|
|
@@ -1131,7 +1177,7 @@ export function apply(ctx, config) {
|
|
|
1131
1177
|
if (name === undefined)
|
|
1132
1178
|
return undefined;
|
|
1133
1179
|
const isArchived = archivedSessionFilter();
|
|
1134
|
-
return canvasLocalMembersOf(name).find(id => id !== skipId && joinedSessions.has(id) && !liveRoots.has(id) && isArchived?.(id) !== true);
|
|
1180
|
+
return canvasLocalMembersOf(name, true).find(id => id !== skipId && joinedSessions.has(id) && !liveRoots.has(id) && isArchived?.(id) !== true);
|
|
1135
1181
|
}
|
|
1136
1182
|
/**
|
|
1137
1183
|
* Human-facing node facts for one session: the session title (fallback:
|
|
@@ -1596,7 +1642,7 @@ export function apply(ctx, config) {
|
|
|
1596
1642
|
whenWebServerSettled((webServer) => {
|
|
1597
1643
|
// gh#77 B1: the admin faces live in src/faces/admin.ts.
|
|
1598
1644
|
registerAdminFaces(webServer, {
|
|
1599
|
-
ctx, config, logger, home, session, lanIp, PLUGIN_VERSION, controlRoute, controlTrustedWithoutKey, readJsonBody, peerFetch, peerStore, peerLinkState, originClaimHeaders, refreshRemoteRows, joinedSessions, exposureStore, teamMemberships, cliDeposits, groupStore, canvasStore, cliHomeForAdd, cliHomeOptions, layoutStore, taskLedger, idempotencyStore, inFlightRoutes, recentActivity, teamAuditRing, reportRing, sessionNodes, liveRoots, mountSessionNode, coldJoinedIds, prewarmStatus, reconcileStatus, id8, sessionLabelOf, sessionTeamOf, ownCanvasHandleOf, localSessionOfHandle, canvasTeamsOfSession, nodeMetadataOf, canvasTeamOf, archivedSessionFilter, pruneArchivedJoins, pruneCanvasMemberships, verifyOriginClaim, callerLinkedToThisHost, cachedCardFetch, authorizationView,
|
|
1645
|
+
ctx, config, logger, home, session, lanIp, PLUGIN_VERSION, controlRoute, controlTrustedWithoutKey, readJsonBody, peerFetch, peerStore, peerLinkState, originClaimHeaders, refreshRemoteRows, joinedSessions, exposureStore, teamMemberships, cliDeposits, groupStore, canvasStore, revokeCanvasMember, canvasMigrationState, cliHomeForAdd, cliHomeOptions, layoutStore, taskLedger, idempotencyStore, inFlightRoutes, recentActivity, teamAuditRing, reportRing, sessionNodes, liveRoots, mountSessionNode, coldJoinedIds, prewarmStatus, reconcileStatus, id8, sessionLabelOf, sessionTeamOf, ownCanvasHandleOf, localSessionOfHandle, canvasTeamsOfSession, nodeMetadataOf, canvasTeamOf, archivedSessionFilter, pruneArchivedJoins, pruneCanvasMemberships, verifyOriginClaim, callerLinkedToThisHost, cachedCardFetch, authorizationView,
|
|
1600
1646
|
indexUrl: import.meta.url, enforceRefused,
|
|
1601
1647
|
get cardCache() { return cardCache; },
|
|
1602
1648
|
get remoteRowsCache() { return remoteRowsCache; },
|