@nelsonlongxiang/dsh-open-a2a-net 0.5.14 → 0.6.24
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 +243 -121
- package/README_zh.md +144 -84
- package/assets/nexusDist/assets/index-034h1cNc.js +3 -0
- package/assets/nexusDist/assets/index-PG9EieGX.css +1 -0
- package/assets/nexusDist/index.html +26 -0
- package/assets/stageDist/assets/index-Bj7S2pA1.css +1 -0
- package/assets/stageDist/assets/index-C-28k_o5.js +974 -0
- package/assets/stageDist/assets/interiors-CQGBjffT.png +0 -0
- package/assets/stageDist/assets/office-tileset-CHQT-uYL.png +0 -0
- package/assets/stageDist/index.html +13 -0
- package/lib/a2a-client.js +190 -13
- package/lib/canvas-store.js +218 -0
- package/lib/card.js +25 -0
- package/lib/client/A2aControl.js +45 -137
- package/lib/client/locales.js +16 -40
- package/lib/client.js +164 -467
- package/lib/client.js.map +1 -1
- package/lib/group-store.js +6 -0
- package/lib/idempotency-store.js +291 -0
- package/lib/index.js +4217 -259
- package/lib/joined-store.js +11 -0
- package/lib/layout-store.js +155 -0
- package/lib/peer-store.js +98 -13
- package/lib/receipt-ladder.js +35 -0
- package/lib/receipt.js +102 -0
- package/lib/self-suppress.js +62 -0
- package/lib/session-transcript.js +150 -0
- package/lib/skill/SKILL.md +77 -0
- package/lib/skill/references/discovery.md +38 -0
- package/lib/skill/references/membership.md +50 -0
- package/lib/skill/references/pitfalls.md +42 -0
- package/lib/skill/references/routing.md +43 -0
- package/lib/skill-registration.js +52 -0
- package/lib/stage-mount.js +32 -0
- package/lib/task-ledger.js +540 -0
- package/lib/team-store.js +122 -0
- package/lib/teams-bridge.js +18 -0
- package/lib/transport-caps.js +31 -0
- package/lib/types/a2a-client.d.ts +72 -1
- package/lib/types/canvas-store.d.ts +102 -0
- package/lib/types/client/A2aControl.d.ts +18 -28
- package/lib/types/client/locales.d.ts +1 -1
- package/lib/types/group-store.d.ts +6 -0
- package/lib/types/idempotency-store.d.ts +164 -0
- package/lib/types/index.d.ts +210 -6
- package/lib/types/joined-store.d.ts +9 -0
- package/lib/types/layout-store.d.ts +63 -0
- package/lib/types/peer-store.d.ts +45 -2
- package/lib/types/receipt-ladder.d.ts +25 -0
- package/lib/types/receipt.d.ts +51 -0
- package/lib/types/self-suppress.d.ts +41 -0
- package/lib/types/session-transcript.d.ts +37 -0
- package/lib/types/skill-registration.d.ts +3 -0
- package/lib/types/stage-mount.d.ts +29 -0
- package/lib/types/task-ledger.d.ts +308 -0
- package/lib/types/team-store.d.ts +55 -0
- package/lib/types/teams-bridge.d.ts +112 -0
- package/lib/types/transport-caps.d.ts +29 -0
- package/lib/types/types.d.ts +43 -0
- package/nexus-stage/package-lock.json +2262 -0
- package/nexus-stage/package.json +21 -0
- package/nexus-stage/scripts/__pycache__/mockhost.cpython-312.pyc +0 -0
- package/nexus-stage/scripts/mockhost.py +253 -0
- package/nexus-stage/src/canvas-ops.ts +175 -0
- package/nexus-stage/src/canvas-wire.ts +156 -0
- package/nexus-stage/src/edges.ts +72 -0
- package/nexus-stage/src/fault.ts +56 -0
- package/nexus-stage/src/federation.ts +234 -0
- package/nexus-stage/src/layout-doc.ts +143 -0
- package/nexus-stage/src/layout-wire.ts +158 -0
- package/nexus-stage/src/main.ts +361 -0
- package/nexus-stage/src/planning-menus.ts +352 -0
- package/nexus-stage/src/planning-nodelist.ts +294 -0
- package/nexus-stage/src/planning-types.ts +111 -0
- package/nexus-stage/src/planning-view.ts +1313 -0
- package/nexus-stage/src/planning.css +280 -0
- package/nexus-stage/src/seat.ts +35 -0
- package/nexus-stage/src/stage-name.ts +13 -0
- package/nexus-stage/src/tokens.ts +32 -0
- package/nexus-stage/src/viewport.ts +83 -0
- package/nexus-stage/src/workbench.css +267 -0
- package/nexus-stage/src/workbench.ts +1530 -0
- package/nexus-stage/src/world.ts +509 -0
- package/nexus-stage/tsconfig.json +25 -0
- package/nexus-stage/vite.config.ts +16 -0
- package/package.json +83 -22
package/lib/client/A2aControl.js
CHANGED
|
@@ -1,18 +1,17 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
/**
|
|
3
|
-
* A2A network control: a footer action opening this host's network
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* visibly follow each turn.
|
|
3
|
+
* A2A network control: a footer action opening this host's recent network
|
|
4
|
+
* activity feed. The feed is the panel's whole content — in-flight routes,
|
|
5
|
+
* owed receipts, and the routing activity ring. Session/team management
|
|
6
|
+
* lives in the planning view (design: docs/design/panel-activity-feed.md):
|
|
7
|
+
* the stage links row above the feed is the explicit navigation to it.
|
|
8
|
+
* Probes the host's state route at registration time (the seat renders only
|
|
9
|
+
* on a host that serves it). While open, the feed polls so it visibly
|
|
10
|
+
* follows each turn; the closed trigger's unread badge rides a slow poll.
|
|
12
11
|
*/
|
|
13
12
|
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
14
13
|
import clsx from 'clsx';
|
|
15
|
-
import { Button,
|
|
14
|
+
import { Button, IconGlobeOutlineRegular } from '@deepseek-ai/dsh-client-ui-primitives';
|
|
16
15
|
import css from './A2aControl.module.css';
|
|
17
16
|
/** Fetch the host's network state; undefined means the route is absent. */
|
|
18
17
|
async function fetchState() {
|
|
@@ -24,11 +23,10 @@ async function fetchState() {
|
|
|
24
23
|
return undefined;
|
|
25
24
|
return {
|
|
26
25
|
sessions: body.sessions,
|
|
27
|
-
groups: Array.isArray(body.groups) ? body.groups : [],
|
|
28
|
-
peers: Array.isArray(body.peers) ? body.peers : [],
|
|
29
26
|
activity: Array.isArray(body.activity) ? body.activity : [],
|
|
30
27
|
inFlight: Array.isArray(body.inFlight) ? body.inFlight : [],
|
|
31
|
-
|
|
28
|
+
tasks: Array.isArray(body.tasks) ? body.tasks : [],
|
|
29
|
+
...(typeof body.tasksStaleCount === 'number' ? { tasksStaleCount: body.tasksStaleCount } : {}),
|
|
32
30
|
...(typeof body.version === 'string' ? { version: body.version } : {}),
|
|
33
31
|
};
|
|
34
32
|
}
|
|
@@ -42,46 +40,24 @@ function relativeTime(ts) {
|
|
|
42
40
|
return `${String(minutes)}m`;
|
|
43
41
|
return `${String(Math.round(minutes / 60))}h`;
|
|
44
42
|
}
|
|
45
|
-
/**
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
function SessionRow({ row, t, listById, openSession, busy, onToggle, pickerFor, setPickerFor, groups, newGroupName, setNewGroupName, onAssign }) {
|
|
51
|
-
const cold = row.live === false;
|
|
52
|
-
const name = cold ? (listById[row.id]?.displayTitle ?? row.label) : (row.name ?? row.label);
|
|
53
|
-
const description = cold ? t('a2a.cold') : (row.description ?? '');
|
|
54
|
-
const pickerOpen = pickerFor === row.id;
|
|
55
|
-
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
|
|
56
|
-
? (_jsx(Button, { variant: "outline", size: "sm", disabled: busy === row.id, onClick: () => { onToggle(row); }, children: t('a2a.leave') }))
|
|
57
|
-
: (_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) => {
|
|
58
|
-
if (event.key === 'Enter' && newGroupName.trim() !== '')
|
|
59
|
-
onAssign(row.id, newGroupName.trim());
|
|
60
|
-
} }), _jsx(Button, { variant: "primary", size: "sm", disabled: newGroupName.trim() === '', onClick: () => { if (newGroupName.trim() !== '')
|
|
61
|
-
onAssign(row.id, newGroupName.trim()); }, children: t('a2a.groupNewGo') })] })] }))] }));
|
|
43
|
+
/** One feed row: direction glyph (aria-labelled), team, peer host, age, fail mark. */
|
|
44
|
+
function FeedRow({ dir, team, peer, ts, ok, target, openSession, t }) {
|
|
45
|
+
const fresh = Date.now() - ts < 5_000;
|
|
46
|
+
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)
|
|
47
|
+
openSession(target.id); }, role: target !== undefined ? 'button' : undefined, children: [_jsx("span", { className: clsx(css.activityDir, dir), "aria-label": dir === 'in' ? 'inbound' : 'outbound', children: 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) })] }));
|
|
62
48
|
}
|
|
63
49
|
/**
|
|
64
|
-
* Render the network toggle and its
|
|
50
|
+
* Render the network toggle and its activity feed.
|
|
65
51
|
* @param props - composed slot props.
|
|
66
52
|
* @returns the control element tree.
|
|
67
53
|
*/
|
|
68
|
-
export function A2aControl({ wide, t,
|
|
54
|
+
export function A2aControl({ wide, t, openSession }) {
|
|
69
55
|
const [open, setOpen] = useState(false);
|
|
70
|
-
const [state, setState] = useState({ sessions: [],
|
|
71
|
-
const [busy, setBusy] = useState(null);
|
|
56
|
+
const [state, setState] = useState({ sessions: [], activity: [], inFlight: [], tasks: [] });
|
|
72
57
|
const [seenActivity, setSeenActivity] = useState(0);
|
|
73
|
-
// Panel-local search: filters session rows by name/team/description.
|
|
74
|
-
const [search, setSearch] = useState('');
|
|
75
|
-
// Group UI: the row whose group picker is open, and collapsed groups.
|
|
76
|
-
const [pickerFor, setPickerFor] = useState(null);
|
|
77
|
-
const [newGroupName, setNewGroupName] = useState('');
|
|
78
|
-
const [collapsed, setCollapsed] = useState(new Set());
|
|
79
58
|
const stopped = useRef(false);
|
|
80
59
|
useEffect(() => () => { stopped.current = true; }, []);
|
|
81
60
|
const wrap = useRef(null);
|
|
82
|
-
// Cold rows carry no host-side facts; the client's own session list holds
|
|
83
|
-
// the durable title for exactly those sessions.
|
|
84
|
-
const listById = useSessions(state2 => state2.byId);
|
|
85
61
|
// The popover anchors to the trigger's viewport rect: the sidebar column
|
|
86
62
|
// clips its descendants (the collapse slide), so an in-column absolute
|
|
87
63
|
// popover is cut at the column edge whenever the column is narrower than
|
|
@@ -128,8 +104,7 @@ export function A2aControl({ wide, t, useSessions, openSession }) {
|
|
|
128
104
|
useEffect(() => {
|
|
129
105
|
if (!open)
|
|
130
106
|
return;
|
|
131
|
-
// While the
|
|
132
|
-
// ring keep moving; poll so the listing visibly follows.
|
|
107
|
+
// While the feed is open, it polls so the ring visibly follows turns.
|
|
133
108
|
const poll = setInterval(refresh, 2_000);
|
|
134
109
|
return () => { clearInterval(poll); };
|
|
135
110
|
}, [open, refresh]);
|
|
@@ -143,97 +118,30 @@ export function A2aControl({ wide, t, useSessions, openSession }) {
|
|
|
143
118
|
document.addEventListener('pointerdown', onPointerDown);
|
|
144
119
|
return () => { document.removeEventListener('pointerdown', onPointerDown); };
|
|
145
120
|
}, [open]);
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
const needle = search.trim().toLowerCase();
|
|
172
|
-
const matches = (row) => {
|
|
173
|
-
if (needle === '')
|
|
174
|
-
return true;
|
|
175
|
-
const name = row.live === false ? (listById[row.id]?.displayTitle ?? row.label) : (row.name ?? row.label);
|
|
176
|
-
return name.toLowerCase().includes(needle)
|
|
177
|
-
|| row.team.toLowerCase().includes(needle)
|
|
178
|
-
|| (row.description ?? '').toLowerCase().includes(needle);
|
|
179
|
-
};
|
|
180
|
-
const visible = sessions.filter(matches);
|
|
181
|
-
const ungrouped = visible.filter(row => row.group === undefined);
|
|
182
|
-
const grouped = groups
|
|
183
|
-
.map(name => ({ name, rows: visible.filter(row => row.group === name) }))
|
|
184
|
-
.filter(group => group.rows.length > 0);
|
|
185
|
-
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
|
|
186
|
-
? (_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
|
|
187
|
-
? _jsx("div", { className: css.empty, children: t('a2a.empty') })
|
|
188
|
-
: visible.length === 0
|
|
189
|
-
? _jsx("div", { className: css.empty, children: t('a2a.searchEmpty') })
|
|
190
|
-
: (_jsxs(_Fragment, { children: [ungrouped.length > 0 && (_jsxs("div", { className: css.sessionGroup, children: [_jsxs("button", { type: "button", className: css.groupHead, onClick: () => {
|
|
191
|
-
setCollapsed((current) => {
|
|
192
|
-
const next = new Set(current);
|
|
193
|
-
if (next.has(''))
|
|
194
|
-
next.delete('');
|
|
195
|
-
else
|
|
196
|
-
next.add('');
|
|
197
|
-
return next;
|
|
198
|
-
});
|
|
199
|
-
}, 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 }) => {
|
|
200
|
-
const isCollapsed = collapsed.has(name);
|
|
201
|
-
return (_jsxs("div", { className: css.sessionGroup, children: [_jsxs("button", { type: "button", className: css.groupHead, onClick: () => {
|
|
202
|
-
setCollapsed((current) => {
|
|
203
|
-
const next = new Set(current);
|
|
204
|
-
if (next.has(name))
|
|
205
|
-
next.delete(name);
|
|
206
|
-
else
|
|
207
|
-
next.add(name);
|
|
208
|
-
return next;
|
|
209
|
-
});
|
|
210
|
-
}, 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));
|
|
211
|
-
})] })), _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)))
|
|
212
|
-
.reduce((byOrigin, row) => {
|
|
213
|
-
const key = row.origin ?? '';
|
|
214
|
-
byOrigin[key] = [...(byOrigin[key] ?? []), row];
|
|
215
|
-
return byOrigin;
|
|
216
|
-
}, {})).map(([origin, rows]) => (_jsxs("div", { className: css.sessionGroup, children: [_jsxs("button", { type: "button", className: css.groupHead, onClick: () => {
|
|
217
|
-
setCollapsed((current) => {
|
|
218
|
-
const next = new Set(current);
|
|
219
|
-
if (next.has('remote:' + origin))
|
|
220
|
-
next.delete('remote:' + origin);
|
|
221
|
-
else
|
|
222
|
-
next.add('remote:' + origin);
|
|
223
|
-
return next;
|
|
224
|
-
});
|
|
225
|
-
}, 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
|
|
226
|
-
? _jsx("div", { className: css.empty, children: t('a2a.peersEmpty') })
|
|
227
|
-
: (_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
|
|
228
|
-
? _jsx("div", { className: css.empty, children: t('a2a.activityEmpty') })
|
|
229
|
-
: (_jsx("div", { className: css.activityList, children: [...activity].reverse().slice(0, 10).map((entry, index) => {
|
|
230
|
-
// A same-host session team resolves to its session row;
|
|
231
|
-
// clicking the activity line jumps there (the linkage
|
|
232
|
-
// into an ongoing collaboration).
|
|
233
|
-
const target = sessions.find(row => row.team === entry.team);
|
|
234
|
-
const fresh = Date.now() - entry.ts < 5_000;
|
|
235
|
-
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)
|
|
236
|
-
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)}`));
|
|
237
|
-
}) }))] }))
|
|
121
|
+
// 0.6.10: the receive face of click-to-reach (0.6.7 BroadcastChannel,
|
|
122
|
+
// 0.6.8 ?dsh-a2a-open deep link) is removed by ruling — 禁止任何跳转,
|
|
123
|
+
// 全部使用响应式布局. The workbench's 快派 form is the only cross-surface
|
|
124
|
+
// reach primitive and it stays on the workbench page; this panel's
|
|
125
|
+
// FeedRow openSession remains (it switches a conversation in place inside
|
|
126
|
+
// the GUI — no navigation).
|
|
127
|
+
const { sessions, activity, inFlight, tasks } = state;
|
|
128
|
+
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
|
|
129
|
+
? (_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)}`))) })), (() => {
|
|
130
|
+
// C-ruling: render only settleable debt; the stale tail is a
|
|
131
|
+
// count line, never a row-per-noise listing.
|
|
132
|
+
const settleableTasks = tasks.filter(task => task.settleable !== false);
|
|
133
|
+
const staleCount = state.tasksStaleCount ?? (tasks.length - settleableTasks.length);
|
|
134
|
+
if (settleableTasks.length === 0 && staleCount === 0)
|
|
135
|
+
return null;
|
|
136
|
+
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)] }))] }));
|
|
137
|
+
})(), _jsx("div", { className: css.sectionTitle, children: t('a2a.activity') }), activity.length === 0
|
|
138
|
+
? _jsx("div", { className: css.empty, children: t('a2a.activityEmpty') })
|
|
139
|
+
: (_jsx("div", { className: css.activityList, children: [...activity].reverse().slice(0, 10).map((entry, index) => {
|
|
140
|
+
// A same-host session team resolves to its session row;
|
|
141
|
+
// clicking the activity line jumps there (the linkage
|
|
142
|
+
// into an ongoing collaboration).
|
|
143
|
+
const target = sessions.find(row => row.team === entry.team);
|
|
144
|
+
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)}`));
|
|
145
|
+
}) }))] })] }))
|
|
238
146
|
: null] }));
|
|
239
147
|
}
|
package/lib/client/locales.js
CHANGED
|
@@ -2,54 +2,30 @@
|
|
|
2
2
|
/** Simplified Chinese dictionary. */
|
|
3
3
|
export const zh = {
|
|
4
4
|
'a2a.label': 'A2A 网络',
|
|
5
|
-
'a2a.title': 'A2A
|
|
6
|
-
'a2a.
|
|
7
|
-
'a2a.
|
|
8
|
-
'a2a.leave': '退出',
|
|
9
|
-
'a2a.cold': '未加载(重启后待唤醒)',
|
|
10
|
-
'a2a.wake': '唤醒',
|
|
11
|
-
'a2a.note': '只有加入的会话才会被网络中的其他节点发现;点击会话行可跳转。',
|
|
12
|
-
'a2a.remote': '远程节点(按 host 分组)',
|
|
13
|
-
'a2a.remoteUnknownOrigin': '未知来源',
|
|
14
|
-
'a2a.peers': '对等节点',
|
|
15
|
-
'a2a.peersEmpty': '尚无已知对等节点(配 peers 种子入网)',
|
|
16
|
-
'a2a.activity': '路由活动',
|
|
17
|
-
'a2a.activityEmpty': '暂无路由记录',
|
|
5
|
+
'a2a.title': 'A2A 网络动态',
|
|
6
|
+
'a2a.activity': '最近动态',
|
|
7
|
+
'a2a.activityEmpty': '暂无网络动态——加入网络即可开始协作',
|
|
18
8
|
'a2a.inFlight': '进行中的路由',
|
|
19
9
|
'a2a.inFlightStale': '等待回执超时(对端处理慢或回执丢失);180 秒后自动解除并按已送达处理',
|
|
10
|
+
'a2a.tasks': '欠回执的异步任务',
|
|
11
|
+
'a2a.tasksNote': '异步欠账分三段:未决(等回执)→ 死信(超期自动标注,目标恢复后仍可补回执结算)→ 归档(结算留痕不蒸发)',
|
|
12
|
+
'a2a.tasksStale': '对话式已送达未答(非欠账,24h 自动死信)',
|
|
20
13
|
'a2a.jump': '点击跳转到该会话',
|
|
21
|
-
'a2a.
|
|
22
|
-
'a2a.
|
|
23
|
-
'a2a.group': '设置分组',
|
|
24
|
-
'a2a.groupDefault': '未分组',
|
|
25
|
-
'a2a.groupClear': '清除分组',
|
|
26
|
-
'a2a.groupNew': '新建分组…',
|
|
27
|
-
'a2a.groupNewGo': '创建并归入',
|
|
14
|
+
'a2a.stageTitle': 'A2A 工作台(目录 · 台账 · 活动 · 规划)',
|
|
15
|
+
'a2a.stageWorkbench': '打开工作台',
|
|
28
16
|
};
|
|
29
17
|
/** English dictionary. */
|
|
30
18
|
export const en = {
|
|
31
19
|
'a2a.label': 'A2A network',
|
|
32
|
-
'a2a.title': 'A2A network
|
|
33
|
-
'a2a.
|
|
34
|
-
'a2a.
|
|
35
|
-
'a2a.leave': 'Leave',
|
|
36
|
-
'a2a.cold': 'not loaded (waiting to wake after a restart)',
|
|
37
|
-
'a2a.wake': 'Wake',
|
|
38
|
-
'a2a.note': 'Only joined sessions are discoverable by other nodes; click a row to open the session.',
|
|
39
|
-
'a2a.remote': 'Remote teams (by host)',
|
|
40
|
-
'a2a.remoteUnknownOrigin': 'Unknown origin',
|
|
41
|
-
'a2a.peers': 'Peers',
|
|
42
|
-
'a2a.peersEmpty': 'No known peers yet (set peers seeds to join the mesh)',
|
|
43
|
-
'a2a.activity': 'Routing activity',
|
|
44
|
-
'a2a.activityEmpty': 'No routes yet',
|
|
20
|
+
'a2a.title': 'A2A network activity',
|
|
21
|
+
'a2a.activity': 'Recent activity',
|
|
22
|
+
'a2a.activityEmpty': 'No network activity yet — join sessions to the network to start collaborating',
|
|
45
23
|
'a2a.inFlight': 'Routes in flight',
|
|
46
24
|
'a2a.inFlightStale': 'Reply wait past 120s (slow or lost receipt); auto-releases as delivered at 180s',
|
|
25
|
+
'a2a.tasks': 'Owed receipts',
|
|
26
|
+
'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)',
|
|
27
|
+
'a2a.tasksStale': 'Delivered conversationally, no formal receipt (not debt; auto-dead-letters at the TTL)',
|
|
47
28
|
'a2a.jump': 'Click to open this session',
|
|
48
|
-
'a2a.
|
|
49
|
-
'a2a.
|
|
50
|
-
'a2a.group': 'Set group',
|
|
51
|
-
'a2a.groupDefault': 'Ungrouped',
|
|
52
|
-
'a2a.groupClear': 'Clear group',
|
|
53
|
-
'a2a.groupNew': 'New group…',
|
|
54
|
-
'a2a.groupNewGo': 'Create & assign',
|
|
29
|
+
'a2a.stageTitle': 'A2A workbench (directory · ledger · activity · plan)',
|
|
30
|
+
'a2a.stageWorkbench': 'Open workbench',
|
|
55
31
|
};
|