@nelsonlongxiang/dsh-open-a2a-net 0.6.41 → 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.
Files changed (72) hide show
  1. package/README.md +2 -1
  2. package/README_zh.md +2 -1
  3. package/assets/nexusDist/assets/index-CaA76j0F.js +9 -0
  4. package/assets/nexusDist/index.html +1 -1
  5. package/assets/stageDist/assets/index-Bj7S2pA1.css +1 -1
  6. package/assets/stageDist/assets/index-C-28k_o5.js +974 -974
  7. package/assets/stageDist/index.html +12 -12
  8. package/cli/a2a.mjs +24 -4
  9. package/lib/canvas-store.js +249 -17
  10. package/lib/client.js +62 -72
  11. package/lib/client.js.map +1 -1
  12. package/lib/config.js +1 -0
  13. package/lib/faces/admin.js +103 -63
  14. package/lib/faces/wire.js +3 -1
  15. package/lib/index.js +116 -22
  16. package/lib/origin-claim.js +4 -0
  17. package/lib/skill/SKILL.md +117 -117
  18. package/lib/skill/references/collaboration.md +428 -428
  19. package/lib/skill/references/discovery.md +78 -78
  20. package/lib/skill/references/intake.md +107 -107
  21. package/lib/skill/references/membership.md +73 -73
  22. package/lib/skill/references/pitfalls.md +51 -51
  23. package/lib/skill/references/router.md +96 -96
  24. package/lib/skill/references/routing.md +65 -65
  25. package/lib/types/canvas-store.d.ts +48 -1
  26. package/lib/types/config.d.ts +2 -0
  27. package/lib/types/faces/admin.d.ts +2 -0
  28. package/lib/types/origin-claim.d.ts +2 -0
  29. package/nexus-stage/package.json +21 -21
  30. package/nexus-stage/scripts/mockhost.py +261 -261
  31. package/nexus-stage/src/canvas-ops.ts +191 -191
  32. package/nexus-stage/src/canvas-wire.ts +164 -164
  33. package/nexus-stage/src/cli-home-picker.ts +42 -42
  34. package/nexus-stage/src/fault.ts +56 -56
  35. package/nexus-stage/src/federation-overlay.ts +103 -103
  36. package/nexus-stage/src/federation.ts +241 -241
  37. package/nexus-stage/src/layout-doc.ts +145 -145
  38. package/nexus-stage/src/main.ts +520 -513
  39. package/nexus-stage/src/planning-card-text.ts +73 -73
  40. package/nexus-stage/src/planning-card.ts +63 -63
  41. package/nexus-stage/src/planning-edges.ts +26 -26
  42. package/nexus-stage/src/planning-fit.ts +45 -45
  43. package/nexus-stage/src/planning-frame-hue.ts +13 -13
  44. package/nexus-stage/src/planning-legend.ts +39 -39
  45. package/nexus-stage/src/planning-member-handles.ts +61 -61
  46. package/nexus-stage/src/planning-menus.ts +422 -422
  47. package/nexus-stage/src/planning-mirror.ts +75 -75
  48. package/nexus-stage/src/planning-nodelist.ts +294 -294
  49. package/nexus-stage/src/planning-overlap.ts +159 -159
  50. package/nexus-stage/src/planning-reachability.ts +28 -28
  51. package/nexus-stage/src/planning-relayout.ts +121 -121
  52. package/nexus-stage/src/planning-roster.ts +15 -15
  53. package/nexus-stage/src/planning-search.ts +168 -168
  54. package/nexus-stage/src/planning-types.ts +115 -115
  55. package/nexus-stage/src/planning-undo.ts +68 -68
  56. package/nexus-stage/src/planning-view.ts +1499 -1499
  57. package/nexus-stage/src/planning.css +340 -340
  58. package/nexus-stage/src/seat-name-editor.ts +31 -31
  59. package/nexus-stage/src/session-drag.ts +64 -64
  60. package/nexus-stage/src/viewport.ts +106 -106
  61. package/nexus-stage/src/wb-chat.ts +684 -684
  62. package/nexus-stage/src/wb-directory.ts +237 -237
  63. package/nexus-stage/src/wb-ledger.ts +279 -279
  64. package/nexus-stage/src/wb-names.ts +43 -43
  65. package/nexus-stage/src/wb-rail.ts +147 -147
  66. package/nexus-stage/src/workbench.css +388 -388
  67. package/nexus-stage/src/workbench.ts +1294 -1272
  68. package/nexus-stage/src/world.ts +509 -509
  69. package/nexus-stage/tsconfig.json +24 -24
  70. package/nexus-stage/vite.config.ts +15 -15
  71. package/package.json +1 -1
  72. package/assets/nexusDist/assets/index-NkNSeIx6.js +0 -9
@@ -1,13 +1,13 @@
1
- <!doctype html>
2
- <html lang="zh-CN">
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width, initial-scale=1" />
6
- <title>dsh-a2a-munder-difflin · A2A Floor</title>
7
- <script type="module" crossorigin src="./assets/index-C-28k_o5.js"></script>
8
- <link rel="stylesheet" crossorigin href="./assets/index-Bj7S2pA1.css">
9
- </head>
10
- <body>
11
- <div id="root"></div>
12
- </body>
1
+ <!doctype html>
2
+ <html lang="zh-CN">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <title>dsh-a2a-munder-difflin · A2A Floor</title>
7
+ <script type="module" crossorigin src="./assets/index-C-28k_o5.js"></script>
8
+ <link rel="stylesheet" crossorigin href="./assets/index-Bj7S2pA1.css">
9
+ </head>
10
+ <body>
11
+ <div id="root"></div>
12
+ </body>
13
13
  </html>
package/cli/a2a.mjs CHANGED
@@ -801,11 +801,31 @@ async function cmdFileFetch() {
801
801
  const text = positionals().slice(1).join(' ')
802
802
  const ref = parseRef(text)
803
803
  if (ref === undefined) usage('usage: a2a file fetch "<[A2A file] v1 … reference line>" [--out DIR]')
804
- const homeOrigin = new URL(home()).origin
805
- if (ref.from !== homeOrigin) die(`[file fetch] the file is on ${ref.from}, not your home host ${homeOrigin}: a CLI identity proves itself only on its home host — ask the sender to send it through your home host, or fetch it from a session there`)
806
- const target = `${ref.from}/a2a/blob`
804
+ const homeBase = home()
805
+ const homeOrigin = new URL(homeBase).origin
806
+ let localSource = ref.from === homeOrigin
807
+ if (!localSource) {
808
+ // Ask only the configured home: neither a reference nor a remote card can
809
+ // authorize sending CLI credentials to another origin (#332). Old/denied
810
+ // metadata fails closed; same-origin fetches retain their no-lookup path.
811
+ const metadata = await homeFetch(`${homeBase}/__dsh_a2a/state`, { headers: keyHeaders(), signal: AbortSignal.timeout(5_000) })
812
+ if (metadata.status === 401 || metadata.status === 403) die(keyHint(metadata.status))
813
+ if (metadata.status === 200) {
814
+ const info = await replyOf(metadata)
815
+ const aliases = [info.ownerHost, ...(Array.isArray(info.host?.originClaimAudiences) ? info.host.originClaimAudiences : [])]
816
+ localSource = info.error === undefined && aliases.some(value => {
817
+ if (typeof value !== 'string') return false
818
+ try {
819
+ const url = new URL(value)
820
+ return ['http:', 'https:'].includes(url.protocol) && url.username === '' && url.password === '' && url.search === '' && url.hash === '' && url.origin === ref.from
821
+ } catch { return false }
822
+ })
823
+ }
824
+ }
825
+ if (!localSource) die(`[file fetch] the file is on ${ref.from}, not your home host ${homeOrigin}: a CLI identity proves itself only on its home host — ask the sender to send it through your home host, or fetch it from a session there`)
826
+ const target = `${homeBase}/a2a/blob`
807
827
  const res = await homeFetch(`${target}?sha256=${ref.sha256}&caller_session=${encodeURIComponent(identity().handle)}`, {
808
- headers: { ...originClaimFor(ref.from)(target, 'GET'), ...keyHeaders() },
828
+ headers: { ...originClaimFor(homeBase)(target, 'GET'), ...keyHeaders() },
809
829
  })
810
830
  if (res.status !== 200) {
811
831
  const out = await replyOf(res)
@@ -16,9 +16,15 @@
16
16
  * read fresh by the state route.
17
17
  * @module @nelsonlongxiang/dsh-open-a2a-net/canvas-store
18
18
  */
19
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
19
+ import { existsSync, mkdirSync, readFileSync, writeFileSync, renameSync, rmSync, openSync, closeSync, fsyncSync } from 'node:fs';
20
+ import { randomUUID } from 'node:crypto';
21
+ import { hostname } from 'node:os';
20
22
  import { dirname } from 'node:path';
21
23
  import { CLI_HANDLE_RE } from "./cli-deposit.js";
24
+ /** A bare session handle is not a CLI identity or a qualified owner selector. */
25
+ export function legacyBareMember(id) {
26
+ return isRemoteMemberRef(id) && !CLI_HANDLE_RE.test(id) && !/@[0-9a-f]{8}$/.test(id);
27
+ }
22
28
  /** Upper bounds: a canvas taxonomy, not a database. */
23
29
  export const CANVAS_TEAM_CAP = 64;
24
30
  export const CANVAS_MEMBER_CAP = 32;
@@ -67,18 +73,29 @@ function validNewName(raw) {
67
73
  * registry knowledge: callers validate join state and resolve members.
68
74
  */
69
75
  export class CanvasStore {
76
+ options;
70
77
  /** Team entries in creation order; members in add order (routing priority). */
71
78
  teams = [];
72
79
  cliHome = {};
73
80
  cliHomeCleared = new Set();
81
+ suspended = [];
82
+ revision = 0;
83
+ committed = '';
84
+ memberOwners = {};
85
+ disk;
86
+ readOnlyReason = '';
87
+ lastWriteError = '';
88
+ failedSuspension = false;
74
89
  path;
75
90
  listeners = new Set();
76
91
  /**
77
92
  * @param path - the snapshot file; an empty path keeps the store in memory.
78
93
  */
79
- constructor(path) {
94
+ constructor(path, options = {}) {
95
+ this.options = options;
80
96
  this.path = path;
81
97
  this.restore();
98
+ this.committed = this.serialize();
82
99
  }
83
100
  /**
84
101
  * Observe changes: the listener runs after every write that changed the
@@ -95,7 +112,53 @@ export class CanvasStore {
95
112
  if (this.path === '' || !existsSync(this.path))
96
113
  return;
97
114
  try {
98
- const snapshot = JSON.parse(readFileSync(this.path, 'utf8'));
115
+ this.disk = readFileSync(this.path, 'utf8');
116
+ const snapshot = JSON.parse(this.disk);
117
+ if (snapshot?.formatVersion !== undefined && snapshot.formatVersion !== 2) {
118
+ this.readOnlyReason = 'unsupported canvas formatVersion';
119
+ return;
120
+ }
121
+ if (snapshot?.memberOwners && typeof snapshot.memberOwners === 'object' && !Array.isArray(snapshot.memberOwners))
122
+ this.memberOwners = { ...snapshot.memberOwners };
123
+ if (snapshot?.formatVersion === 2) {
124
+ if (!Number.isSafeInteger(snapshot.revision) || snapshot.revision < 0 || !Array.isArray(snapshot.suspendedMembers) || !Array.isArray(snapshot.teams) || snapshot.teams.length > CANVAS_TEAM_CAP)
125
+ throw Error('incomplete or oversized canvas v2 snapshot');
126
+ const names = new Set();
127
+ for (const entry of snapshot.teams) {
128
+ if (!entry || typeof entry !== 'object' || typeof entry.name !== 'string' || validName(entry.name) !== entry.name || names.has(entry.name) || !Array.isArray(entry.members) || entry.members.length > CANVAS_MEMBER_CAP)
129
+ throw Error('invalid canvas v2 team');
130
+ names.add(entry.name);
131
+ const members = new Set();
132
+ for (const member of entry.members) {
133
+ if (typeof member !== 'string' || member === '' || /[\x00-\x1f\x7f]/.test(member) || members.has(member))
134
+ throw Error('invalid canvas v2 member');
135
+ members.add(member);
136
+ }
137
+ }
138
+ if (snapshot.memberOwners !== undefined && (snapshot.memberOwners === null || typeof snapshot.memberOwners !== 'object' || Array.isArray(snapshot.memberOwners) || Object.values(snapshot.memberOwners).some(key => typeof key !== 'string')))
139
+ throw Error('invalid canvas v2 owners');
140
+ if (snapshot.cliHome !== undefined && (snapshot.cliHome === null || typeof snapshot.cliHome !== 'object' || Array.isArray(snapshot.cliHome) || Object.entries(snapshot.cliHome).some(([handle, home]) => !CLI_HANDLE_RE.test(handle) || typeof home !== 'string' || !validHomeOrigin(home) || !snapshot.teams.some(t => t.members.includes(handle)))))
141
+ throw Error('invalid canvas v2 CLI homes');
142
+ if (snapshot.cliHomeCleared !== undefined && (!Array.isArray(snapshot.cliHomeCleared) || new Set(snapshot.cliHomeCleared).size !== snapshot.cliHomeCleared.length || snapshot.cliHomeCleared.some(handle => typeof handle !== 'string' || !CLI_HANDLE_RE.test(handle) || !snapshot.teams.some(t => t.members.includes(handle)))))
143
+ throw Error('invalid canvas v2 cleared homes');
144
+ }
145
+ this.revision = Number.isSafeInteger(snapshot?.revision) && snapshot.revision >= 0 ? snapshot.revision : 0;
146
+ if (snapshot?.suspendedMembers !== undefined) {
147
+ if (!Array.isArray(snapshot.suspendedMembers))
148
+ throw Error('invalid suspension records');
149
+ const seen = new Set();
150
+ for (const row of snapshot.suspendedMembers) {
151
+ if (!row || typeof row.team !== 'string' || typeof row.member !== 'string' || typeof row.localSessionId !== 'string' || !['leave', 'archive', 'remove'].includes(row.reason) || typeof row.at !== 'string' || !Number.isFinite(Date.parse(row.at)) || typeof row.token !== 'string' || !row.token)
152
+ throw Error('invalid suspension record');
153
+ if (snapshot.formatVersion === 2 && !snapshot.teams?.some(t => t.name === row.team && t.members.includes(row.member)))
154
+ throw Error('orphan suspension record');
155
+ const key = JSON.stringify([row.team, row.member, row.reason, row.localSessionId]);
156
+ if (seen.has(key))
157
+ throw Error('duplicate suspension record');
158
+ seen.add(key);
159
+ this.suspended.push({ ...row });
160
+ }
161
+ }
99
162
  if (!Array.isArray(snapshot?.teams))
100
163
  return;
101
164
  const kept = [];
@@ -125,13 +188,30 @@ export class CanvasStore {
125
188
  }
126
189
  }
127
190
  catch {
128
- // A corrupt store is not fatal: the user re-creates teams; routing
129
- // names simply stop resolving until then.
191
+ this.teams = [];
192
+ this.suspended = [];
193
+ // A malformed v2 suspension must never silently reactivate members.
194
+ if (this.disk?.includes('suspendedMembers') || this.disk?.includes('formatVersion'))
195
+ this.readOnlyReason = 'invalid canvas v2 snapshot';
130
196
  }
131
197
  }
132
198
  /** Persist the current snapshot (no-op without a path), then tell the listeners. */
133
199
  persist() {
134
- this.write();
200
+ try {
201
+ this.write();
202
+ this.committed = this.serialize();
203
+ this.lastWriteError = '';
204
+ }
205
+ catch (error) {
206
+ this.lastWriteError = String(error);
207
+ const previous = JSON.parse(this.committed);
208
+ this.teams = previous.teams.map(t => ({ name: t.name, members: [...t.members] }));
209
+ this.suspended = [...previous.suspendedMembers ?? []];
210
+ this.cliHome = { ...previous.cliHome };
211
+ this.cliHomeCleared = new Set(previous.cliHomeCleared);
212
+ this.revision = previous.revision ?? 0;
213
+ throw error;
214
+ }
135
215
  for (const listener of this.listeners) {
136
216
  try {
137
217
  listener();
@@ -139,17 +219,100 @@ export class CanvasStore {
139
219
  catch { /* a listener's failure is its own */ }
140
220
  }
141
221
  }
222
+ serialize() {
223
+ return JSON.stringify({ teams: this.teams, suspendedMembers: this.suspended, cliHome: this.cliHome, cliHomeCleared: [...this.cliHomeCleared], revision: this.revision });
224
+ }
142
225
  write() {
143
- if (this.path === '')
144
- return;
145
- try {
226
+ if (this.readOnlyReason)
227
+ throw Error(this.readOnlyReason);
228
+ const revision = this.revision + 1;
229
+ const snapshot = { formatVersion: 2, revision,
230
+ teams: this.teams.map(t => ({ name: t.name, members: [...t.members] })),
231
+ suspendedMembers: this.suspended.map(r => ({ ...r })),
232
+ ...(Object.keys(this.memberOwners).length ? { memberOwners: this.memberOwners } : {}),
233
+ ...(Object.keys(this.cliHome).length ? { cliHome: { ...this.cliHome } } : {}),
234
+ ...(this.cliHomeCleared.size ? { cliHomeCleared: [...this.cliHomeCleared] } : {}) };
235
+ const bytes = JSON.stringify(snapshot);
236
+ if (this.path !== '') {
146
237
  mkdirSync(dirname(this.path), { recursive: true });
147
- const snapshot = { teams: this.teams.map(t => ({ name: t.name, members: [...t.members] })), ...(Object.keys(this.cliHome).length > 0 ? { cliHome: { ...this.cliHome } } : {}), ...(this.cliHomeCleared.size > 0 ? { cliHomeCleared: [...this.cliHomeCleared] } : {}) };
148
- writeFileSync(this.path, JSON.stringify(snapshot), { mode: 0o600 });
238
+ const lock = this.path + '.lock';
239
+ // Never steal another writer's lock. Diagnostics do not authorize removal.
240
+ try {
241
+ mkdirSync(lock);
242
+ }
243
+ catch (error) {
244
+ throw Error('canvas lock held at ' + lock + ': ' + JSON.stringify(this.lockInfo()) + ' (' + String(error) + ')');
245
+ }
246
+ try {
247
+ writeFileSync(lock + '/owner.json', JSON.stringify({ pid: process.pid, host: hostname(), at: new Date().toISOString() }), { mode: 0o600, flag: 'wx' });
248
+ const current = existsSync(this.path) ? readFileSync(this.path, 'utf8') : undefined;
249
+ if (current !== this.disk)
250
+ throw Error('canvas snapshot changed externally; reload before writing');
251
+ let oldVersion;
252
+ try {
253
+ oldVersion = current === undefined ? undefined : JSON.parse(current)?.formatVersion;
254
+ }
255
+ catch { /* back up malformed legacy data too */ }
256
+ if (current !== undefined && oldVersion !== 2) {
257
+ const backup = this.path + '.pre-v2';
258
+ if (existsSync(backup)) {
259
+ if (readFileSync(backup, 'utf8') !== current)
260
+ throw Error('canvas pre-v2 backup differs; inspect before migration: ' + backup);
261
+ }
262
+ else
263
+ this.replaceFile(backup, current);
264
+ }
265
+ this.replaceFile(this.path, bytes);
266
+ this.disk = bytes;
267
+ }
268
+ finally {
269
+ rmSync(lock, { recursive: true });
270
+ }
149
271
  }
150
- catch {
151
- // An unwritable home must not break the canvas; the store degrades to
152
- // memory-only.
272
+ this.revision = revision;
273
+ }
274
+ replaceFile(target, bytes) {
275
+ const temporary = target + '.tmp-' + randomUUID();
276
+ try {
277
+ writeFileSync(temporary, bytes, { mode: 0o600, flag: 'wx' });
278
+ const fd = openSync(temporary, 'r+');
279
+ try {
280
+ fsyncSync(fd);
281
+ }
282
+ finally {
283
+ closeSync(fd);
284
+ }
285
+ renameSync(temporary, target);
286
+ if (process.platform !== 'win32') {
287
+ try {
288
+ const dir = openSync(dirname(target), 'r');
289
+ try {
290
+ fsyncSync(dir);
291
+ }
292
+ finally {
293
+ closeSync(dir);
294
+ }
295
+ }
296
+ catch (error) {
297
+ // Rename already committed: rolled-back memory must not be used as active state.
298
+ this.readOnlyReason = 'canvas directory fsync failed after rename: ' + String(error);
299
+ throw error;
300
+ }
301
+ }
302
+ }
303
+ finally {
304
+ rmSync(temporary, { force: true });
305
+ }
306
+ }
307
+ lockInfo() {
308
+ const path = this.path + '.lock';
309
+ if (this.path === '' || !existsSync(path))
310
+ return undefined;
311
+ try {
312
+ return { path, owner: JSON.parse(readFileSync(path + '/owner.json', 'utf8')) };
313
+ }
314
+ catch (error) {
315
+ return { path, error: 'owner metadata unavailable: ' + String(error) };
153
316
  }
154
317
  }
155
318
  /** Every team name, in creation order. */
@@ -163,7 +326,71 @@ export class CanvasStore {
163
326
  /** The named team's member ids in routing-priority order (a copy). */
164
327
  membersOf(name) {
165
328
  const entry = this.teams.find(t => t.name === name);
166
- return entry === undefined ? [] : [...entry.members];
329
+ if (this.readOnlyReason || this.failedSuspension)
330
+ return [];
331
+ return entry === undefined ? [] : entry.members.filter(id => !this.isSuspended(name, id) && (this.options.legacyBareMembers !== false || !legacyBareMember(id)));
332
+ }
333
+ /** Inspection only: never use raw rows for routing, authorization or counts. */
334
+ rawMembersOf(name) { return [...(this.teams.find(t => t.name === name)?.members ?? [])]; }
335
+ isSuspended(name, member) { return this.suspended.some(r => r.team === name && r.member === member); }
336
+ suspensionOf(name, member) { return this.suspended.filter(r => r.team === name && r.member === member).map(r => ({ ...r })); }
337
+ state() {
338
+ return { revision: this.revision, suspendedMembers: this.suspended.map(r => ({ ...r })),
339
+ directoryFsync: process.platform === 'win32' ? 'unavailable on Windows; file fsync and atomic rename only' : 'required after rename',
340
+ ...(this.lockInfo() ? { lock: this.lockInfo() } : {}),
341
+ ...(this.readOnlyReason || this.failedSuspension || this.lastWriteError ? { error: this.readOnlyReason || (this.failedSuspension ? 'canvas suspension persistence failed; restart after repair' : this.lastWriteError) } : {}) };
342
+ }
343
+ ownerKeyOf(member) { return this.memberOwners[member]; }
344
+ /** Durably revoke canvas consent BEFORE join/agent/roster state is removed. */
345
+ revokeLocal(id, ownQualified, inferredBare, reason, team) {
346
+ if (this.readOnlyReason || this.failedSuspension)
347
+ throw Error(this.state().error);
348
+ let changed = false;
349
+ for (const entry of this.teams) {
350
+ if (team !== undefined && entry.name !== team)
351
+ continue;
352
+ if (inferredBare !== undefined && entry.members.includes(inferredBare) && !this.suspended.some(r => r.team === entry.name && r.member === inferredBare && r.localSessionId === id && r.reason === reason)) {
353
+ this.suspended.push({ team: entry.name, member: inferredBare, localSessionId: id, reason, at: new Date().toISOString(), token: randomUUID() });
354
+ changed = true;
355
+ }
356
+ const kept = entry.members.filter(m => m !== id && m !== ownQualified);
357
+ if (kept.length !== entry.members.length) {
358
+ entry.members = kept;
359
+ changed = true;
360
+ }
361
+ }
362
+ if (changed) {
363
+ try {
364
+ this.persist();
365
+ }
366
+ catch (error) {
367
+ this.failedSuspension = true;
368
+ throw error;
369
+ }
370
+ }
371
+ return changed;
372
+ }
373
+ /** Owner-only CAS. The caller supplies the exact slot and the complete current suspension records. */
374
+ regulateSuspended(action, team, member, revision, expected) {
375
+ if (this.readOnlyReason || this.failedSuspension)
376
+ return { ok: false, error: this.state().error };
377
+ const records = this.suspensionOf(team, member);
378
+ if (revision !== this.revision || !records.length || !this.rawMembersOf(team).includes(member) || JSON.stringify(expected) !== JSON.stringify(records))
379
+ return { ok: false, error: 'canvas version, member slot or suspension records changed; refresh first' };
380
+ const old = this.suspended;
381
+ const entry = this.teams.find(t => t.name === team), members = entry.members;
382
+ this.suspended = this.suspended.filter(r => r.team !== team || r.member !== member);
383
+ if (action === 'delete')
384
+ entry.members = entry.members.filter(m => m !== member);
385
+ try {
386
+ this.persist();
387
+ return { ok: true };
388
+ }
389
+ catch (error) {
390
+ this.suspended = old;
391
+ entry.members = members;
392
+ return { ok: false, error: String(error) };
393
+ }
167
394
  }
168
395
  /** Frozen, operator-linked CLI home; undefined means unknown or ambiguous. */
169
396
  cliHomeOf(handle) { return this.cliHome[handle]; }
@@ -188,7 +415,7 @@ export class CanvasStore {
188
415
  }
189
416
  /** Every team name the id is a member of, in team creation order. */
190
417
  teamsOf(id) {
191
- return this.teams.filter(t => t.members.includes(id)).map(t => t.name);
418
+ return this.teams.filter(t => this.membersOf(t.name).includes(id)).map(t => t.name);
192
419
  }
193
420
  /**
194
421
  * Create a named team; whitespace trims, duplicates are idempotent, and
@@ -216,6 +443,8 @@ export class CanvasStore {
216
443
  const index = this.teams.findIndex(t => t.name === name);
217
444
  if (index < 0)
218
445
  return false;
446
+ if (this.suspended.some(r => r.team === name))
447
+ return false;
219
448
  const [removed] = this.teams.splice(index, 1);
220
449
  for (const id of removed.members)
221
450
  this.forgetUnusedHome(id);
@@ -261,6 +490,9 @@ export class CanvasStore {
261
490
  const entry = this.teams.find(t => t.name === name);
262
491
  if (entry === undefined)
263
492
  return false;
493
+ // Suspended slots require owner CAS, never a peer remove/readd round trip.
494
+ if (this.isSuspended(name, id))
495
+ return false;
264
496
  const index = entry.members.indexOf(id);
265
497
  if (index < 0)
266
498
  return false;
@@ -284,7 +516,7 @@ export class CanvasStore {
284
516
  let dirty = false;
285
517
  for (const entry of this.teams) {
286
518
  const index = entry.members.indexOf(id);
287
- if (index >= 0) {
519
+ if (index >= 0 && !this.isSuspended(entry.name, id)) {
288
520
  entry.members.splice(index, 1);
289
521
  dirty = true;
290
522
  }
package/lib/client.js CHANGED
@@ -7,7 +7,6 @@ window.__ModuleLoader__.load({
7
7
  let react_jsx_runtime = require("react/jsx-runtime");
8
8
  let react = require("react");
9
9
  let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
10
- //#region node_modules/.pnpm/clsx@2.1.1/node_modules/clsx/dist/clsx.mjs
11
10
  function r(e) {
12
11
  var t, f, n = "";
13
12
  if ("string" == typeof e || "number" == typeof e) n += e;
@@ -21,9 +20,7 @@ window.__ModuleLoader__.load({
21
20
  for (var e, t, f = 0, n = "", o = arguments.length; f < o; f++) (e = arguments[f]) && (t = r(e)) && (n && (n += " "), n += t);
22
21
  return n;
23
22
  }
24
- //#endregion
25
- //#region \0dsh-css:D:\workspace\dsh-open-a2a-net\src\client\A2aControl.module.css.mjs
26
- const css = ".Q9Ky_q_wrap{max-width:100%;display:inline-flex;position:relative}.Q9Ky_q_trigger{align-items:center;gap:4px;display:inline-flex;position:relative}.Q9Ky_q_unreadBadge{text-align:center;color:#fff;background:var(--dsh-accent,#4c8bf5);border-radius:7px;min-width:14px;height:14px;padding:0 3px;font-size:9px;line-height:14px;animation:2s ease-in-out infinite Q9Ky_q_badgePulse;position:absolute;top:-6px;right:-8px}@keyframes Q9Ky_q_badgePulse{0%,to{box-shadow:0 0 #4c8bf573}50%{box-shadow:0 0 0 4px #4c8bf500}}@media (prefers-reduced-motion:reduce){.Q9Ky_q_unreadBadge{animation:none}}.Q9Ky_q_popover{z-index:1100;border:1px solid var(--dsh-border,#2a2a30);background:var(--dsh-surface-raised,#1c1c22);border-radius:10px;flex-direction:column;gap:8px;width:320px;max-width:calc(100vw - 16px);max-height:60vh;padding:12px;display:flex;position:fixed;overflow-y:auto;box-shadow:0 8px 24px #00000059}.Q9Ky_q_title{font-size:13px;font-weight:600}.Q9Ky_q_sectionTitle{border-top:1px solid var(--dsh-border,#2a2a30);text-transform:uppercase;letter-spacing:.04em;opacity:.65;margin-top:4px;padding-top:8px;font-size:11px;font-weight:600}.Q9Ky_q_stageRow{border-top:1px solid var(--dsh-border,#2a2a30);align-items:center;gap:8px;margin-top:4px;padding-top:8px;display:flex}.Q9Ky_q_stageTitle{text-transform:uppercase;letter-spacing:.04em;opacity:.65;font-size:11px;font-weight:600}.Q9Ky_q_stageLinks{gap:6px;display:flex}.Q9Ky_q_stageLink{border:1px solid var(--dsh-border,#2a2a30);color:inherit;border-radius:999px;align-items:center;padding:3px 8px;font-size:11px;text-decoration:none;display:inline-flex}.Q9Ky_q_stageLink:hover,.Q9Ky_q_stageLink:focus-visible{border-color:var(--dsh-accent,#4c8bf5)}.Q9Ky_q_empty{opacity:.7;font-size:12px}.Q9Ky_q_row{border:1px solid #0000;border-radius:8px;align-items:center;gap:10px;padding:8px;display:flex}.Q9Ky_q_joined{border-color:var(--dsh-border,#2a2a30)}.Q9Ky_q_facts{text-align:left;min-width:0;color:inherit;font:inherit;cursor:pointer;background:0 0;border:none;border-radius:6px;flex-direction:column;flex:1;gap:2px;padding:2px 4px;display:flex}.Q9Ky_q_facts:hover{background:var(--dsh-surface-hover,#ffffff0f)}.Q9Ky_q_nameRow{align-items:center;gap:6px;display:flex}.Q9Ky_q_stateDot{border-radius:50%;flex:none;width:7px;height:7px}.Q9Ky_q_stateDot.Q9Ky_q_live{background:var(--dsh-accent,#4c8bf5)}.Q9Ky_q_stateDot.Q9Ky_q_live.Q9Ky_q_joined{background:var(--dsh-success,#3fb96f)}.Q9Ky_q_stateDot.Q9Ky_q_cold{background:var(--dsh-warning,#d9a13c)}.Q9Ky_q_name{text-overflow:ellipsis;white-space:nowrap;font-size:13px;font-weight:500;overflow:hidden}.Q9Ky_q_description{opacity:.7;text-overflow:ellipsis;white-space:nowrap;font-size:12px;overflow:hidden}.Q9Ky_q_team{opacity:.55;font-size:11px;font-family:var(--dsh-mono,monospace)}.Q9Ky_q_note{opacity:.6;font-size:11px}.Q9Ky_q_searchInput{box-sizing:border-box;border:1px solid var(--dsh-border,#2a2a30);background:var(--dsh-surface,#141419);width:100%;color:inherit;border-radius:6px;padding:5px 8px;font-size:12px}.Q9Ky_q_searchInput:focus{outline:1px solid var(--dsh-accent,#4c8bf5)}.Q9Ky_q_sessionGroup{flex-direction:column;gap:2px;display:flex}.Q9Ky_q_groupHead{color:inherit;font:inherit;opacity:.75;cursor:pointer;text-align:left;background:0 0;border:none;align-items:center;gap:6px;padding:4px 2px;font-size:11px;font-weight:600;display:flex}.Q9Ky_q_groupChevron{transition:transform .12s;display:inline-block}.Q9Ky_q_groupChevronClosed{transform:rotate(-90deg)}.Q9Ky_q_groupTag{text-overflow:ellipsis;white-space:nowrap;border:1px solid var(--dsh-border,#2a2a30);opacity:.8;border-radius:999px;flex:none;max-width:90px;padding:0 6px;font-size:10px;line-height:16px;overflow:hidden}.Q9Ky_q_groupButton{width:24px;height:24px;color:inherit;opacity:.55;cursor:pointer;background:0 0;border:1px solid #0000;border-radius:6px;flex:none;font-size:12px;line-height:1}.Q9Ky_q_groupButton:hover{border-color:var(--dsh-border,#2a2a30);opacity:1}.Q9Ky_q_groupPicker{border:1px dashed var(--dsh-border,#2a2a30);border-radius:8px;flex-wrap:wrap;gap:6px;width:100%;padding:8px;display:flex;position:relative}.Q9Ky_q_groupOption{border:1px solid var(--dsh-border,#2a2a30);color:inherit;cursor:pointer;background:0 0;border-radius:999px;padding:2px 8px;font-size:11px}.Q9Ky_q_groupOptionActive{border-color:var(--dsh-accent,#4c8bf5);color:var(--dsh-accent,#4c8bf5)}.Q9Ky_q_groupNew{align-items:center;gap:6px;width:100%;margin-top:2px;display:flex}.Q9Ky_q_groupInput{border:1px solid var(--dsh-border,#2a2a30);background:var(--dsh-surface,#141419);min-width:0;color:inherit;border-radius:6px;flex:1;padding:4px 8px;font-size:12px}.Q9Ky_q_peerGrid{flex-wrap:wrap;gap:6px;display:flex}.Q9Ky_q_peerChip{border:1px solid var(--dsh-border,#2a2a30);border-radius:999px;align-items:center;gap:6px;padding:3px 8px;font-size:11px;display:inline-flex}.Q9Ky_q_peerHost{text-overflow:ellipsis;white-space:nowrap;max-width:160px;overflow:hidden}.Q9Ky_q_peerScore{font-family:var(--dsh-mono,monospace);opacity:.6}.Q9Ky_q_activityList{flex-direction:column;gap:2px;display:flex}.Q9Ky_q_activityRow{border-radius:4px;align-items:center;gap:8px;padding:2px 4px;font-size:11px;display:flex}.Q9Ky_q_activityRow[data-ok=false]{opacity:.55}.Q9Ky_q_activityDir{flex:none;font-weight:600}.Q9Ky_q_activityDir.Q9Ky_q_in{color:var(--dsh-success,#3fb96f)}.Q9Ky_q_activityDir.Q9Ky_q_out{color:var(--dsh-accent,#4c8bf5)}.Q9Ky_q_activityTeam{text-overflow:ellipsis;white-space:nowrap;min-width:0;font-family:var(--dsh-mono,monospace);opacity:.8;flex:1;overflow:hidden}.Q9Ky_q_activityPeer{text-overflow:ellipsis;white-space:nowrap;opacity:.55;max-width:90px;overflow:hidden}.Q9Ky_q_activityTime{opacity:.5;font-family:var(--dsh-mono,monospace);flex:none}.Q9Ky_q_activityRow[data-jump=true]{cursor:pointer}.Q9Ky_q_activityRow[data-jump=true]:hover{background:var(--dsh-surface-hover,#ffffff0f)}.Q9Ky_q_fresh{animation:1.2s ease-out 3 Q9Ky_q_freshFlash}@keyframes Q9Ky_q_freshFlash{0%{background:#4c8bf538}to{background:0 0}}@media (prefers-reduced-motion:reduce){.Q9Ky_q_fresh{background:#4c8bf51f;animation:none}}.Q9Ky_q_inFlightList{flex-direction:column;gap:2px;margin-bottom:4px;display:flex}.Q9Ky_q_inFlightRow{border:1px dashed var(--dsh-border,#2a2a30);border-radius:4px;align-items:center;gap:8px;padding:3px 4px;font-size:11px;display:flex}.Q9Ky_q_inFlightPulse{background:var(--dsh-accent,#4c8bf5);border-radius:50%;flex:none;width:7px;height:7px;animation:1.1s ease-in-out infinite Q9Ky_q_flightPulse}@keyframes Q9Ky_q_flightPulse{0%,to{opacity:1;transform:scale(1)}50%{opacity:.5;transform:scale(.55)}}@media (prefers-reduced-motion:reduce){.Q9Ky_q_inFlightPulse{animation:none}}.Q9Ky_q_versionTag{color:var(--dsh-color-text-tertiary,#888);background:var(--dsh-color-bg-secondary,#8080801f);border-radius:999px;margin-left:auto;padding:1px 6px;font-size:10px;font-weight:500}.Q9Ky_q_inFlightStale{opacity:.55}.Q9Ky_q_canvasCreate{align-items:center;gap:6px;margin-bottom:4px;display:flex}.Q9Ky_q_canvasCreate .Q9Ky_q_groupInput{flex:1;min-width:0}.Q9Ky_q_canvasChip{max-width:100%;color:var(--dsh-color-text-secondary,#aaa);background:var(--dsh-color-bg-secondary,#8080801f);border-radius:999px;align-items:center;gap:4px;margin:2px 4px 2px 0;padding:1px 6px;font-size:11px;display:inline-flex}.Q9Ky_q_canvasChip>span:not([class]){white-space:nowrap;text-overflow:ellipsis;min-width:0;overflow:hidden}.Q9Ky_q_canvasChipRemove{cursor:pointer;min-width:24px;min-height:24px;color:var(--dsh-color-text-tertiary,#888);background:0 0;border:none;padding:2px 5px;font-size:11px;line-height:1}.Q9Ky_q_canvasChipRemove:hover{color:var(--dsh-color-text-primary,#eee)}.Q9Ky_q_canvasChipRemove{transition:color .14s ease-out}@media (prefers-reduced-motion:reduce){.Q9Ky_q_canvasChipRemove{transition:none}}.Q9Ky_q_canvasChipRemove:focus-visible{outline:2px solid var(--dsh-color-primary,#4f8cff);outline-offset:1px;border-radius:4px}.Q9Ky_q_canvasActions{gap:6px;margin-top:4px;display:flex}.Q9Ky_q_inFlightStale .Q9Ky_q_inFlightPulse{opacity:.4;animation:none}.Q9Ky_q_failMark{color:#f87171;flex-shrink:0;font-size:11px}.Q9Ky_q_staleNote{color:var(--dsh-text-muted,#94a3b8b3);padding:4px 8px;font-size:11px}";
23
+ const css = ".EbJb-G_wrap{max-width:100%;display:inline-flex;position:relative}.EbJb-G_trigger{align-items:center;gap:4px;display:inline-flex;position:relative}.EbJb-G_unreadBadge{text-align:center;color:#fff;background:var(--dsh-accent,#4c8bf5);border-radius:7px;min-width:14px;height:14px;padding:0 3px;font-size:9px;line-height:14px;animation:2s ease-in-out infinite EbJb-G_badgePulse;position:absolute;top:-6px;right:-8px}@keyframes EbJb-G_badgePulse{0%,to{box-shadow:0 0 #4c8bf573}50%{box-shadow:0 0 0 4px #4c8bf500}}@media (prefers-reduced-motion:reduce){.EbJb-G_unreadBadge{animation:none}}.EbJb-G_popover{z-index:1100;border:1px solid var(--dsh-border,#2a2a30);background:var(--dsh-surface-raised,#1c1c22);border-radius:10px;flex-direction:column;gap:8px;width:320px;max-width:calc(100vw - 16px);max-height:60vh;padding:12px;display:flex;position:fixed;overflow-y:auto;box-shadow:0 8px 24px #00000059}.EbJb-G_title{font-size:13px;font-weight:600}.EbJb-G_sectionTitle{border-top:1px solid var(--dsh-border,#2a2a30);text-transform:uppercase;letter-spacing:.04em;opacity:.65;margin-top:4px;padding-top:8px;font-size:11px;font-weight:600}.EbJb-G_stageRow{border-top:1px solid var(--dsh-border,#2a2a30);align-items:center;gap:8px;margin-top:4px;padding-top:8px;display:flex}.EbJb-G_stageTitle{text-transform:uppercase;letter-spacing:.04em;opacity:.65;font-size:11px;font-weight:600}.EbJb-G_stageLinks{gap:6px;display:flex}.EbJb-G_stageLink{border:1px solid var(--dsh-border,#2a2a30);color:inherit;border-radius:999px;align-items:center;padding:3px 8px;font-size:11px;text-decoration:none;display:inline-flex}.EbJb-G_stageLink:hover,.EbJb-G_stageLink:focus-visible{border-color:var(--dsh-accent,#4c8bf5)}.EbJb-G_empty{opacity:.7;font-size:12px}.EbJb-G_row{border:1px solid #0000;border-radius:8px;align-items:center;gap:10px;padding:8px;display:flex}.EbJb-G_joined{border-color:var(--dsh-border,#2a2a30)}.EbJb-G_facts{text-align:left;min-width:0;color:inherit;font:inherit;cursor:pointer;background:0 0;border:none;border-radius:6px;flex-direction:column;flex:1;gap:2px;padding:2px 4px;display:flex}.EbJb-G_facts:hover{background:var(--dsh-surface-hover,#ffffff0f)}.EbJb-G_nameRow{align-items:center;gap:6px;display:flex}.EbJb-G_stateDot{border-radius:50%;flex:none;width:7px;height:7px}.EbJb-G_stateDot.EbJb-G_live{background:var(--dsh-accent,#4c8bf5)}.EbJb-G_stateDot.EbJb-G_live.EbJb-G_joined{background:var(--dsh-success,#3fb96f)}.EbJb-G_stateDot.EbJb-G_cold{background:var(--dsh-warning,#d9a13c)}.EbJb-G_name{text-overflow:ellipsis;white-space:nowrap;font-size:13px;font-weight:500;overflow:hidden}.EbJb-G_description{opacity:.7;text-overflow:ellipsis;white-space:nowrap;font-size:12px;overflow:hidden}.EbJb-G_team{opacity:.55;font-size:11px;font-family:var(--dsh-mono,monospace)}.EbJb-G_note{opacity:.6;font-size:11px}.EbJb-G_searchInput{box-sizing:border-box;border:1px solid var(--dsh-border,#2a2a30);background:var(--dsh-surface,#141419);width:100%;color:inherit;border-radius:6px;padding:5px 8px;font-size:12px}.EbJb-G_searchInput:focus{outline:1px solid var(--dsh-accent,#4c8bf5)}.EbJb-G_sessionGroup{flex-direction:column;gap:2px;display:flex}.EbJb-G_groupHead{color:inherit;font:inherit;opacity:.75;cursor:pointer;text-align:left;background:0 0;border:none;align-items:center;gap:6px;padding:4px 2px;font-size:11px;font-weight:600;display:flex}.EbJb-G_groupChevron{transition:transform .12s;display:inline-block}.EbJb-G_groupChevronClosed{transform:rotate(-90deg)}.EbJb-G_groupTag{text-overflow:ellipsis;white-space:nowrap;border:1px solid var(--dsh-border,#2a2a30);opacity:.8;border-radius:999px;flex:none;max-width:90px;padding:0 6px;font-size:10px;line-height:16px;overflow:hidden}.EbJb-G_groupButton{width:24px;height:24px;color:inherit;opacity:.55;cursor:pointer;background:0 0;border:1px solid #0000;border-radius:6px;flex:none;font-size:12px;line-height:1}.EbJb-G_groupButton:hover{border-color:var(--dsh-border,#2a2a30);opacity:1}.EbJb-G_groupPicker{border:1px dashed var(--dsh-border,#2a2a30);border-radius:8px;flex-wrap:wrap;gap:6px;width:100%;padding:8px;display:flex;position:relative}.EbJb-G_groupOption{border:1px solid var(--dsh-border,#2a2a30);color:inherit;cursor:pointer;background:0 0;border-radius:999px;padding:2px 8px;font-size:11px}.EbJb-G_groupOptionActive{border-color:var(--dsh-accent,#4c8bf5);color:var(--dsh-accent,#4c8bf5)}.EbJb-G_groupNew{align-items:center;gap:6px;width:100%;margin-top:2px;display:flex}.EbJb-G_groupInput{border:1px solid var(--dsh-border,#2a2a30);background:var(--dsh-surface,#141419);min-width:0;color:inherit;border-radius:6px;flex:1;padding:4px 8px;font-size:12px}.EbJb-G_peerGrid{flex-wrap:wrap;gap:6px;display:flex}.EbJb-G_peerChip{border:1px solid var(--dsh-border,#2a2a30);border-radius:999px;align-items:center;gap:6px;padding:3px 8px;font-size:11px;display:inline-flex}.EbJb-G_peerHost{text-overflow:ellipsis;white-space:nowrap;max-width:160px;overflow:hidden}.EbJb-G_peerScore{font-family:var(--dsh-mono,monospace);opacity:.6}.EbJb-G_activityList{flex-direction:column;gap:2px;display:flex}.EbJb-G_activityRow{border-radius:4px;align-items:center;gap:8px;padding:2px 4px;font-size:11px;display:flex}.EbJb-G_activityRow[data-ok=false]{opacity:.55}.EbJb-G_activityDir{flex:none;font-weight:600}.EbJb-G_activityDir.EbJb-G_in{color:var(--dsh-success,#3fb96f)}.EbJb-G_activityDir.EbJb-G_out{color:var(--dsh-accent,#4c8bf5)}.EbJb-G_activityTeam{text-overflow:ellipsis;white-space:nowrap;min-width:0;font-family:var(--dsh-mono,monospace);opacity:.8;flex:1;overflow:hidden}.EbJb-G_activityPeer{text-overflow:ellipsis;white-space:nowrap;opacity:.55;max-width:90px;overflow:hidden}.EbJb-G_activityTime{opacity:.5;font-family:var(--dsh-mono,monospace);flex:none}.EbJb-G_activityRow[data-jump=true]{cursor:pointer}.EbJb-G_activityRow[data-jump=true]:hover{background:var(--dsh-surface-hover,#ffffff0f)}.EbJb-G_fresh{animation:1.2s ease-out 3 EbJb-G_freshFlash}@keyframes EbJb-G_freshFlash{0%{background:#4c8bf538}to{background:0 0}}@media (prefers-reduced-motion:reduce){.EbJb-G_fresh{background:#4c8bf51f;animation:none}}.EbJb-G_inFlightList{flex-direction:column;gap:2px;margin-bottom:4px;display:flex}.EbJb-G_inFlightRow{border:1px dashed var(--dsh-border,#2a2a30);border-radius:4px;align-items:center;gap:8px;padding:3px 4px;font-size:11px;display:flex}.EbJb-G_inFlightPulse{background:var(--dsh-accent,#4c8bf5);border-radius:50%;flex:none;width:7px;height:7px;animation:1.1s ease-in-out infinite EbJb-G_flightPulse}@keyframes EbJb-G_flightPulse{0%,to{opacity:1;transform:scale(1)}50%{opacity:.5;transform:scale(.55)}}@media (prefers-reduced-motion:reduce){.EbJb-G_inFlightPulse{animation:none}}.EbJb-G_versionTag{color:var(--dsh-color-text-tertiary,#888);background:var(--dsh-color-bg-secondary,#8080801f);border-radius:999px;margin-left:auto;padding:1px 6px;font-size:10px;font-weight:500}.EbJb-G_inFlightStale{opacity:.55}.EbJb-G_canvasCreate{align-items:center;gap:6px;margin-bottom:4px;display:flex}.EbJb-G_canvasCreate .EbJb-G_groupInput{flex:1;min-width:0}.EbJb-G_canvasChip{max-width:100%;color:var(--dsh-color-text-secondary,#aaa);background:var(--dsh-color-bg-secondary,#8080801f);border-radius:999px;align-items:center;gap:4px;margin:2px 4px 2px 0;padding:1px 6px;font-size:11px;display:inline-flex}.EbJb-G_canvasChip>span:not([class]){white-space:nowrap;text-overflow:ellipsis;min-width:0;overflow:hidden}.EbJb-G_canvasChipRemove{cursor:pointer;min-width:24px;min-height:24px;color:var(--dsh-color-text-tertiary,#888);background:0 0;border:none;padding:2px 5px;font-size:11px;line-height:1}.EbJb-G_canvasChipRemove:hover{color:var(--dsh-color-text-primary,#eee)}.EbJb-G_canvasChipRemove{transition:color .14s ease-out}@media (prefers-reduced-motion:reduce){.EbJb-G_canvasChipRemove{transition:none}}.EbJb-G_canvasChipRemove:focus-visible{outline:2px solid var(--dsh-color-primary,#4f8cff);outline-offset:1px;border-radius:4px}.EbJb-G_canvasActions{gap:6px;margin-top:4px;display:flex}.EbJb-G_inFlightStale .EbJb-G_inFlightPulse{opacity:.4;animation:none}.EbJb-G_failMark{color:#f87171;flex-shrink:0;font-size:11px}.EbJb-G_staleNote{color:var(--dsh-text-muted,#94a3b8b3);padding:4px 8px;font-size:11px}";
27
24
  const tagId = "@nelsonlongxiang/dsh-open-a2a-net/A2aControl.module.css";
28
25
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
29
26
  const tag = document.createElement("style");
@@ -33,70 +30,68 @@ window.__ModuleLoader__.load({
33
30
  document.head.appendChild(tag);
34
31
  }
35
32
  var A2aControl_module_css_default = {
36
- "activityDir": "Q9Ky_q_activityDir",
37
- "activityList": "Q9Ky_q_activityList",
38
- "activityPeer": "Q9Ky_q_activityPeer",
39
- "activityRow": "Q9Ky_q_activityRow",
40
- "activityTeam": "Q9Ky_q_activityTeam",
41
- "activityTime": "Q9Ky_q_activityTime",
42
- "badgePulse": "Q9Ky_q_badgePulse",
43
- "canvasActions": "Q9Ky_q_canvasActions",
44
- "canvasChip": "Q9Ky_q_canvasChip",
45
- "canvasChipRemove": "Q9Ky_q_canvasChipRemove",
46
- "canvasCreate": "Q9Ky_q_canvasCreate",
47
- "cold": "Q9Ky_q_cold",
48
- "description": "Q9Ky_q_description",
49
- "empty": "Q9Ky_q_empty",
50
- "facts": "Q9Ky_q_facts",
51
- "failMark": "Q9Ky_q_failMark",
52
- "flightPulse": "Q9Ky_q_flightPulse",
53
- "fresh": "Q9Ky_q_fresh",
54
- "freshFlash": "Q9Ky_q_freshFlash",
55
- "groupButton": "Q9Ky_q_groupButton",
56
- "groupChevron": "Q9Ky_q_groupChevron",
57
- "groupChevronClosed": "Q9Ky_q_groupChevronClosed",
58
- "groupHead": "Q9Ky_q_groupHead",
59
- "groupInput": "Q9Ky_q_groupInput",
60
- "groupNew": "Q9Ky_q_groupNew",
61
- "groupOption": "Q9Ky_q_groupOption",
62
- "groupOptionActive": "Q9Ky_q_groupOptionActive",
63
- "groupPicker": "Q9Ky_q_groupPicker",
64
- "groupTag": "Q9Ky_q_groupTag",
65
- "in": "Q9Ky_q_in",
66
- "inFlightList": "Q9Ky_q_inFlightList",
67
- "inFlightPulse": "Q9Ky_q_inFlightPulse",
68
- "inFlightRow": "Q9Ky_q_inFlightRow",
69
- "inFlightStale": "Q9Ky_q_inFlightStale",
70
- "joined": "Q9Ky_q_joined",
71
- "live": "Q9Ky_q_live",
72
- "name": "Q9Ky_q_name",
73
- "nameRow": "Q9Ky_q_nameRow",
74
- "note": "Q9Ky_q_note",
75
- "out": "Q9Ky_q_out",
76
- "peerChip": "Q9Ky_q_peerChip",
77
- "peerGrid": "Q9Ky_q_peerGrid",
78
- "peerHost": "Q9Ky_q_peerHost",
79
- "peerScore": "Q9Ky_q_peerScore",
80
- "popover": "Q9Ky_q_popover",
81
- "row": "Q9Ky_q_row",
82
- "searchInput": "Q9Ky_q_searchInput",
83
- "sectionTitle": "Q9Ky_q_sectionTitle",
84
- "sessionGroup": "Q9Ky_q_sessionGroup",
85
- "stageLink": "Q9Ky_q_stageLink",
86
- "stageLinks": "Q9Ky_q_stageLinks",
87
- "stageRow": "Q9Ky_q_stageRow",
88
- "stageTitle": "Q9Ky_q_stageTitle",
89
- "staleNote": "Q9Ky_q_staleNote",
90
- "stateDot": "Q9Ky_q_stateDot",
91
- "team": "Q9Ky_q_team",
92
- "title": "Q9Ky_q_title",
93
- "trigger": "Q9Ky_q_trigger",
94
- "unreadBadge": "Q9Ky_q_unreadBadge",
95
- "versionTag": "Q9Ky_q_versionTag",
96
- "wrap": "Q9Ky_q_wrap"
33
+ "activityDir": "EbJb-G_activityDir",
34
+ "activityList": "EbJb-G_activityList",
35
+ "activityPeer": "EbJb-G_activityPeer",
36
+ "activityRow": "EbJb-G_activityRow",
37
+ "activityTeam": "EbJb-G_activityTeam",
38
+ "activityTime": "EbJb-G_activityTime",
39
+ "badgePulse": "EbJb-G_badgePulse",
40
+ "canvasActions": "EbJb-G_canvasActions",
41
+ "canvasChip": "EbJb-G_canvasChip",
42
+ "canvasChipRemove": "EbJb-G_canvasChipRemove",
43
+ "canvasCreate": "EbJb-G_canvasCreate",
44
+ "cold": "EbJb-G_cold",
45
+ "description": "EbJb-G_description",
46
+ "empty": "EbJb-G_empty",
47
+ "facts": "EbJb-G_facts",
48
+ "failMark": "EbJb-G_failMark",
49
+ "flightPulse": "EbJb-G_flightPulse",
50
+ "fresh": "EbJb-G_fresh",
51
+ "freshFlash": "EbJb-G_freshFlash",
52
+ "groupButton": "EbJb-G_groupButton",
53
+ "groupChevron": "EbJb-G_groupChevron",
54
+ "groupChevronClosed": "EbJb-G_groupChevronClosed",
55
+ "groupHead": "EbJb-G_groupHead",
56
+ "groupInput": "EbJb-G_groupInput",
57
+ "groupNew": "EbJb-G_groupNew",
58
+ "groupOption": "EbJb-G_groupOption",
59
+ "groupOptionActive": "EbJb-G_groupOptionActive",
60
+ "groupPicker": "EbJb-G_groupPicker",
61
+ "groupTag": "EbJb-G_groupTag",
62
+ "in": "EbJb-G_in",
63
+ "inFlightList": "EbJb-G_inFlightList",
64
+ "inFlightPulse": "EbJb-G_inFlightPulse",
65
+ "inFlightRow": "EbJb-G_inFlightRow",
66
+ "inFlightStale": "EbJb-G_inFlightStale",
67
+ "joined": "EbJb-G_joined",
68
+ "live": "EbJb-G_live",
69
+ "name": "EbJb-G_name",
70
+ "nameRow": "EbJb-G_nameRow",
71
+ "note": "EbJb-G_note",
72
+ "out": "EbJb-G_out",
73
+ "peerChip": "EbJb-G_peerChip",
74
+ "peerGrid": "EbJb-G_peerGrid",
75
+ "peerHost": "EbJb-G_peerHost",
76
+ "peerScore": "EbJb-G_peerScore",
77
+ "popover": "EbJb-G_popover",
78
+ "row": "EbJb-G_row",
79
+ "searchInput": "EbJb-G_searchInput",
80
+ "sectionTitle": "EbJb-G_sectionTitle",
81
+ "sessionGroup": "EbJb-G_sessionGroup",
82
+ "stageLink": "EbJb-G_stageLink",
83
+ "stageLinks": "EbJb-G_stageLinks",
84
+ "stageRow": "EbJb-G_stageRow",
85
+ "stageTitle": "EbJb-G_stageTitle",
86
+ "staleNote": "EbJb-G_staleNote",
87
+ "stateDot": "EbJb-G_stateDot",
88
+ "team": "EbJb-G_team",
89
+ "title": "EbJb-G_title",
90
+ "trigger": "EbJb-G_trigger",
91
+ "unreadBadge": "EbJb-G_unreadBadge",
92
+ "versionTag": "EbJb-G_versionTag",
93
+ "wrap": "EbJb-G_wrap"
97
94
  };
98
- //#endregion
99
- //#region lib/client/A2aControl.js
100
95
  /**
101
96
  * A2A network control: a footer action opening this host's recent network
102
97
  * activity feed. The feed is the panel's whole content — in-flight routes,
@@ -378,8 +373,6 @@ window.__ModuleLoader__.load({
378
373
  }) : null]
379
374
  });
380
375
  }
381
- //#endregion
382
- //#region lib/client/locales.js
383
376
  /** Locale dictionaries for the A2A network sidebar control (namespace `a2aNet`). */
384
377
  /** Simplified Chinese dictionary. */
385
378
  const zh = {
@@ -411,8 +404,6 @@ window.__ModuleLoader__.load({
411
404
  "a2a.stageTitle": "A2A workbench (directory · ledger · activity · plan)",
412
405
  "a2a.stageWorkbench": "Open workbench"
413
406
  };
414
- //#endregion
415
- //#region lib/client/index.js
416
407
  /** Dictionary namespace owned by this plugin (network control copy). */
417
408
  const NS = "a2aNet";
418
409
  /** Services required by the browser half: the slot system, the sessions
@@ -443,7 +434,6 @@ window.__ModuleLoader__.load({
443
434
  }, A2aControl));
444
435
  }).catch(() => {});
445
436
  }
446
- //#endregion
447
437
  exports.apply = apply;
448
438
  exports.inject = inject;
449
439
  return module.exports;