@nelsonlongxiang/dsh-open-a2a-net 0.6.37 → 0.6.39

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 (52) hide show
  1. package/README.md +340 -335
  2. package/README_zh.md +241 -236
  3. package/assets/nexusDist/assets/index-C7HagaGy.css +1 -1
  4. package/assets/nexusDist/assets/index-NkNSeIx6.js +9 -9
  5. package/cli/a2a.mjs +44 -6
  6. package/cli/serve.mjs +132 -0
  7. package/cordis.patch.yml +21 -21
  8. package/lib/canvas-discovery.js +29 -0
  9. package/lib/canvas-id.js +27 -0
  10. package/lib/canvas-store.js +7 -1
  11. package/lib/client.js +63 -63
  12. package/lib/config.js +125 -0
  13. package/lib/direct-forward.js +105 -37
  14. package/lib/faces/admin.js +14 -1
  15. package/lib/faces/wire.js +41 -10
  16. package/lib/flood-guard.js +3 -1
  17. package/lib/index.js +116 -155
  18. package/lib/live-roots.js +39 -0
  19. package/lib/origin-claim.js +4 -3
  20. package/lib/peer-directory.js +1 -1
  21. package/lib/queue-guard.js +3 -1
  22. package/lib/skill/references/cli.md +214 -199
  23. package/lib/skill/references/pitfalls.md +1 -1
  24. package/lib/skill/references/routing.md +4 -0
  25. package/lib/skill/references/setup.md +5 -9
  26. package/lib/task-auth.js +15 -11
  27. package/lib/task-ledger-rows.js +32 -4
  28. package/lib/task-ledger.js +11 -5
  29. package/lib/tools/info.js +12 -5
  30. package/lib/types/canvas-discovery.d.ts +20 -0
  31. package/lib/types/canvas-id.d.ts +9 -0
  32. package/lib/types/config.d.ts +278 -0
  33. package/lib/types/direct-forward.d.ts +33 -2
  34. package/lib/types/faces/admin.d.ts +12 -0
  35. package/lib/types/faces/wire.d.ts +10 -1
  36. package/lib/types/index.d.ts +3 -277
  37. package/lib/types/live-roots.d.ts +35 -0
  38. package/lib/types/origin-claim.d.ts +5 -1
  39. package/lib/types/queue-guard.d.ts +2 -0
  40. package/lib/types/task-auth.d.ts +5 -0
  41. package/lib/types/task-ledger-rows.d.ts +16 -5
  42. package/lib/types/task-ledger.d.ts +3 -0
  43. package/lib/types/tools/info.d.ts +3 -0
  44. package/lib/types/types.d.ts +2 -0
  45. package/nexus-stage/package-lock.json +2262 -2262
  46. package/nexus-stage/scripts/__pycache__/mockhost.cpython-312.pyc +0 -0
  47. package/nexus-stage/src/edges.ts +72 -72
  48. package/nexus-stage/src/layout-wire.ts +158 -158
  49. package/nexus-stage/src/seat.ts +35 -35
  50. package/nexus-stage/src/stage-name.ts +13 -13
  51. package/nexus-stage/src/tokens.ts +31 -31
  52. package/package.json +218 -218
package/cli/a2a.mjs CHANGED
@@ -46,6 +46,7 @@ usage: a2a <command> [args] [--home URL] [--key <apiKey>] [--json]
46
46
  户口制: init <name> [--uuid 16hex] [--force] · publish · inbox [--new|--wait|--hook] · revoke
47
47
  对齐面: whoami · teams [query] · route <team> <msg…> [--sync] [--context ID] [--task ID] · status · tasks · probe · report <msg…> [--task ID]
48
48
  CLI 专有: skills [name] [--json] [--root DIR] · tools (this alignment table) · help
49
+ 独立节点: serve [--config node.json] [--host 127.0.0.1|0.0.0.0] [--port N] [--home DIR] (the network layer without a DSH instance)
49
50
  文件通道: file send <path…> <team> [--note TEXT] · file fetch <ref> [--out DIR] (#91: bytes never ride a message)
50
51
 
51
52
  host: --home > $A2A_HOME > the home saved by 'a2a init --home URL' > ${DEFAULT_HOME}
@@ -108,7 +109,8 @@ function parseArgs(argv) {
108
109
  return { flags, positional }
109
110
  }
110
111
 
111
- const parsed = parseArgs(process.argv.slice(2))
112
+ // `serve` owns its flags (cli/serve.mjs) and loads the node runtime lazily: the other commands never need it.
113
+ const parsed = process.argv[2] === 'serve' ? { flags: new Map(), positional: ['serve'] } : parseArgs(process.argv.slice(2))
112
114
  const cmd = parsed.positional[0] ?? ''
113
115
  const flag = (name) => {
114
116
  const value = parsed.flags.get(name)
@@ -251,10 +253,13 @@ function keyHint(status) {
251
253
  return `host ${home()} answered ${String(status)} — ${apiKey() === undefined ? 'it is keyed: set $A2A_KEY (or pass --key)' : 'the key was refused: check $A2A_KEY / --key'}${pocket}`
252
254
  }
253
255
 
254
- async function state() {
256
+ async function state(optional = false) {
255
257
  const res = await homeFetch(`${home()}/__dsh_a2a/state`, { headers: keyHeaders() })
256
258
  if (res.status === 401 || res.status === 403) die(keyHint(res.status))
257
- if (res.status !== 200) die(`state face ${String(res.status)} on ${home()}`)
259
+ if (res.status !== 200) {
260
+ if (optional) return { error: `state face ${String(res.status)} on ${home()}` }
261
+ die(`state face ${String(res.status)} on ${home()}`)
262
+ }
258
263
  return await replyOf(res)
259
264
  }
260
265
 
@@ -865,12 +870,33 @@ async function printDelivery(verb, res) {
865
870
  if (out.task_status !== undefined && out.task_status !== 'TASK_STATE_COMPLETED') console.error(`[${verb}] status ${String(out.task_status)} — the round has not settled; reconcile by task id before re-sending`)
866
871
  return
867
872
  }
868
- if (out.delivered !== true) die(`[${verb}] not delivered (HTTP ${String(res.status)}): ${String(out.error ?? out.task_status ?? 'no reason given')}`)
873
+ // #282 S1: one actionable next step under the reason, when the host knows one.
874
+ if (out.delivered !== true) die(`[${verb}] not delivered (HTTP ${String(res.status)}): ${String(out.error ?? out.task_status ?? 'no reason given')}${typeof out.next === 'string' && out.next !== '' ? `\n[${verb}] next: ${out.next}` : ''}`)
869
875
  const receipt = out.receipt === 'none' ? 'no receipt will come' : 'receipt arrives in: a2a inbox'
870
876
  console.log(`[${verb}] delivered → ${String(out.team)} · task ${String(out.task_id)} · ${receipt}`)
871
877
  }
872
878
 
879
+ /** Canvas rows come from the owner state face, not a new discovery or admission path. */
880
+ function stateCanvasTeams(s) {
881
+ const rows = []
882
+ const owner = String(s.ownerHost ?? home())
883
+ for (const team of s.canvas?.teams ?? []) rows.push({ team: String(team.id ?? team.team ?? String(s.zone ?? 'dsh') + '/canvas/' + team.name), name: String(team.name), kind: 'canvas', owner, memberCount: (team.members ?? []).length, where: 'local canvas', members: (team.members ?? []).map(m => typeof m === 'string' ? m : m.team ?? m.id) })
884
+ const seen = new Set()
885
+ for (const peer of s.peerOrg ?? []) for (const team of peer.org ?? []) {
886
+ const key = String(peer.url) + ':' + String(team.team)
887
+ if (seen.has(key)) continue
888
+ seen.add(key)
889
+ rows.push({ team: String(team.id || team.team), name: String(team.name), kind: 'canvas', owner: String(peer.url), memberCount: (team.members ?? []).length, where: 'remote canvas', members: team.members ?? [] })
890
+ }
891
+ return rows
892
+ }
893
+
873
894
  async function main() {
895
+ if (cmd === 'serve') {
896
+ const { serve } = await import('./serve.mjs')
897
+ await serve(process.argv.slice(3))
898
+ return
899
+ }
874
900
  if (cmd === 'help' || has('--help') || has('-h')) {
875
901
  console.log(USAGE)
876
902
  return
@@ -925,25 +951,31 @@ async function main() {
925
951
  if (cmd === 'whoami') {
926
952
  const id = identity()
927
953
  const list = await control({ action: 'list' })
954
+ const s = await state(true)
955
+ const canvas = stateCanvasTeams(s).filter(team => team.members.includes(id.handle)).map(({ members, ...row }) => row)
956
+ if (has('--json')) { console.log(JSON.stringify({ handle: id.handle, host: home(), deposit: depositLine(list, id.handle, 'OFFLINE'), canvas, ...(s.error ? { canvasError: s.error } : {}) }, null, 2)); return }
928
957
  console.log(`handle ${id.handle}`)
929
958
  console.log(`identity ~/.a2a/cli.json (own Ed25519 key — the card's team = "${id.name}" is its own zone)`)
930
959
  console.log(`deposit ${depositLine(list, id.handle, 'OFFLINE (expired or never published — run: a2a publish)')}`)
931
960
  console.log(`host ${home()}`)
932
961
  console.log(`card url ${home()}/a2a/cli/${encodeURIComponent(id.handle)}`)
933
962
  console.log(`capability route via signed envelope; receive via mailbox (poll: a2a inbox)`)
963
+ if (s.error) console.log(`canvas UNKNOWN — ${String(s.error)}`)
964
+ for (const team of canvas) console.log(`canvas ${team.team} owner=${team.owner} members=${String(team.memberCount)}`)
934
965
  return
935
966
  }
936
967
  if (cmd === 'teams') {
937
968
  const s = await state()
938
969
  const q = (positionals()[0] ?? '').toLowerCase()
939
970
  const rows = [
940
- ...['dsh'].map(t => ({ team: String(t), name: String(s.session ?? 'host'), where: 'host' })),
971
+ ...[s.zone ?? 'dsh'].map(t => ({ team: String(t), name: String(s.session ?? 'host'), where: 'host' })),
941
972
  ...(s.sessions ?? []).map(x => ({ team: String(x.team ?? ''), name: String(x.seatName ?? x.name ?? x.label ?? ''), where: 'local' })),
942
973
  ...(s.remote ?? []).map(x => ({ team: String(x.team ?? ''), name: String(x.name ?? ''), where: `remote · ${String(x.via ?? '')}` })),
974
+ ...stateCanvasTeams(s).map(({ members, ...row }) => row),
943
975
  ]
944
976
  const hits = rows.filter(r => q === '' || r.team.toLowerCase().includes(q) || r.name.toLowerCase().includes(q))
945
977
  if (has('--json')) { console.log(JSON.stringify(hits, null, 2)); return }
946
- for (const r of hits) console.log(`${r.team.padEnd(28)} ${r.name.slice(0, 24).padEnd(26)} [${r.where}]`)
978
+ for (const r of hits) console.log(`${r.team.padEnd(28)} ${r.name.slice(0, 24).padEnd(26)} [${r.where}${r.kind === 'canvas' ? ` owner=${r.owner} members=${String(r.memberCount)}` : ''}]`)
947
979
  if (hits.length === 0) console.log(q === '' ? '(no teams)' : `(no team matches "${q}")`)
948
980
  return
949
981
  }
@@ -968,12 +1000,14 @@ async function main() {
968
1000
  listCapped(archived, 20, t => ` ${String(t.taskId)} ${String(t.outcome ?? '?')}${t.endedBy !== undefined && t.endedBy !== 'receipt' ? ` (${String(t.endedBy)})` : ''}${senderTail(t)}`)
969
1001
  if (s.conservation != null) console.log(`conservation: conserved=${String(s.conservation.conserved)} delta=${String(s.conservation.delta)}`)
970
1002
  if (s.settlement != null) console.log(`settlement: mode=${String(s.settlement.mode)} refusalsTotal=${String(s.settlement.refusalsTotal)} offChain=${String(s.settlement.offChain)}`)
1003
+ if (s.settlement?.enforceRefused) console.log(`enforce refused: ${String(s.settlement.enforceRefused.reason)}; blockers=${JSON.stringify(s.settlement.enforceRefused.blockers)}`)
971
1004
  return
972
1005
  }
973
1006
  if (cmd === 'status') {
974
1007
  const s = await state()
975
1008
  const list = await control({ action: 'list' })
976
1009
  console.log(`host ${home()} version=${String(s.version ?? '?')} peers=${String((s.peers ?? []).length)}`)
1010
+ if (s.settlement?.enforceRefused) console.log(`settlement mode=${String(s.settlement.mode)}; enforce refused: ${String(s.settlement.enforceRefused.reason)}; blockers=${JSON.stringify(s.settlement.enforceRefused.blockers)}`)
977
1011
  for (const p of s.peers ?? []) console.log(` peer ${String(p.url).padEnd(34)} link=${String(p.link ?? 'stranger')}`)
978
1012
  const inflight = s.inFlight ?? []
979
1013
  if (inflight.length > 0) {
@@ -984,6 +1018,10 @@ async function main() {
984
1018
  // the address it dials is missing from the host's originClaimAudiences.
985
1019
  if (typeof s.unboundInbound === 'number') {
986
1020
  console.log(`claims unboundInbound=${String(s.unboundInbound)} boundMissInbound=${String(s.boundMissInbound ?? 0)} (dialled as ${claimTarget(home())?.audience ?? home()})`)
1021
+ // Who missed and by which origin: add that origin to the host's originClaimAudiences.
1022
+ for (const m of (Array.isArray(s.boundMissRecent) ? s.boundMissRecent : []).slice(0, 5)) {
1023
+ console.log(` miss ${String(m.caller)} dialled ${String(m.audience) || '(no origin)'} ×${String(m.count)}`)
1024
+ }
987
1025
  }
988
1026
  console.log(`deposit ${depositLine(list, identity().handle, 'OFFLINE')}`)
989
1027
  return
package/cli/serve.mjs ADDED
@@ -0,0 +1,132 @@
1
+ /**
2
+ * `a2a-net serve` — the A2A network layer as a standalone node, without a DSH instance
3
+ * (owner request 2026-11-04: the network must not go down when a DSH host restarts).
4
+ *
5
+ * It boots the same plugin a DSH host runs, in a bare cordis context with only the four
6
+ * services the network layer needs (system prompt, tool runtime, timer, web server) and no
7
+ * sessions: cards and OriginClaim, the peer directory, CLI deposits and mailboxes, one-hop
8
+ * forwarding, canvas ownership, the task ledger, the state face and the workbench. Delivering
9
+ * to an AI session still needs a DSH host — this node forwards there like any home host.
10
+ *
11
+ * a2a-net serve [--config node.json] [--host 127.0.0.1|0.0.0.0] [--port 13090] [--home DIR]
12
+ *
13
+ * node.json holds plugin Config keys (team, session, agentName, apiKey, peers,
14
+ * originClaimUrl, originClaimAudiences, forwardDirect, …); flags win over the file.
15
+ */
16
+ import { readFileSync, mkdirSync } from 'node:fs'
17
+ import { homedir, hostname } from 'node:os'
18
+ import { join, resolve } from 'node:path'
19
+
20
+ /** The tested runtime set (the repo lockfile's versions); npm's `latest` tags are older and do not work. */
21
+ export const SERVE_DEPS = {
22
+ '@deepseek-ai/cordis': '4.0.5',
23
+ '@deepseek-ai/cordis-plugin-timer': '1.1.7',
24
+ '@deepseek-ai/dsh-host-webserver': '0.1.7-rc.2',
25
+ '@deepseek-ai/dsh-tools': '0.1.7-rc.2',
26
+ '@deepseek-ai/dsh-system-prompt': '0.1.7-rc.2',
27
+ '@deepseek-ai/dsh-home-paths': '0.1.7-rc.2',
28
+ '@deepseek-ai/dsh-llm': '0.1.7-rc.2',
29
+ '@deepseek-ai/dsh-session': '0.1.7-rc.2',
30
+ '@deepseek-ai/schemastery': '3.18.5',
31
+ }
32
+
33
+ export const SERVE_USAGE = 'usage: a2a-net serve [--config node.json] [--host 127.0.0.1|0.0.0.0] [--port 13090] [--home DIR]'
34
+
35
+ /** One `npm i` line that installs this package and the tested runtime set into a folder. */
36
+ export function installLine(version) {
37
+ return 'npm i @nelsonlongxiang/dsh-open-a2a-net@' + version + ' ' + Object.entries(SERVE_DEPS).map(([name, v]) => name + '@' + v).join(' ')
38
+ }
39
+
40
+ /** Merge file config and flags into the plugin config; pure. */
41
+ export function nodeConfig(file, flags) {
42
+ const raw = flags.port ?? file.port ?? 13090
43
+ const port = typeof raw === 'number' ? raw : /^\d+$/.test(String(raw)) ? Number(raw) : NaN
44
+ if (!Number.isInteger(port) || port < 0 || port > 65535) throw new Error('--port must be an integer 0-65535')
45
+ const home = resolve(flags.home ?? file.dshHome ?? join(homedir(), '.a2a-net', 'node'))
46
+ const { port: _port, host: _host, ...rest } = file
47
+ void _port; void _host
48
+ return {
49
+ host: flags.host ?? file.host ?? '127.0.0.1',
50
+ port,
51
+ config: {
52
+ team: 'dsh',
53
+ agentName: 'a2a-net node',
54
+ session: 'a2a-node-' + hostname().toLowerCase().replace(/[^a-z0-9-]/g, '-').slice(0, 24),
55
+ apiKey: '',
56
+ peers: [],
57
+ announce: true,
58
+ ...rest,
59
+ // The control faces (state, CLI deposits and mailboxes, canvas) are mounted with session nodes; this node
60
+ // composes no agents service, so there is no session to track, expose or wake.
61
+ sessionNodes: true,
62
+ wakeJoinedOnBoot: false,
63
+ dshHome: home,
64
+ },
65
+ }
66
+ }
67
+
68
+ /** Boot the node; resolves to { url, close }. `load` is injectable for tests. */
69
+ export async function startNode({ host, port, config }, load = specifier => import(specifier)) {
70
+ const [{ Context }, timer, webServer, tools, systemPrompt, plugin] = await Promise.all([
71
+ load('@deepseek-ai/cordis'),
72
+ load('@deepseek-ai/cordis-plugin-timer'),
73
+ load('@deepseek-ai/dsh-host-webserver'),
74
+ load('@deepseek-ai/dsh-tools'),
75
+ load('@deepseek-ai/dsh-system-prompt'),
76
+ load(new URL('../lib/index.js', import.meta.url).href),
77
+ ])
78
+ mkdirSync(config.dshHome, { recursive: true })
79
+ const ctx = new Context()
80
+ await ctx.plugin(systemPrompt.default)
81
+ await ctx.plugin(tools.default)
82
+ await ctx.plugin(timer.default)
83
+ await ctx.plugin(webServer.default, { host, port })
84
+ try {
85
+ plugin.apply(ctx, plugin.Config(config))
86
+ } catch (error) {
87
+ await ctx.fiber.dispose() // the web server is already listening
88
+ throw error
89
+ }
90
+ const bound = ctx.webServer.port
91
+ const url = 'http://' + (host === '0.0.0.0' ? '127.0.0.1' : host) + ':' + String(bound)
92
+ return { url, port: bound, close: async () => { await ctx.fiber.dispose() } }
93
+ }
94
+
95
+ /** CLI entry: parse argv, boot, log one line, stay up until SIGINT/SIGTERM. */
96
+ export async function serve(argv) {
97
+ const version = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8')).version
98
+ const flags = {}
99
+ if (argv.includes('--help') || argv.includes('-h')) { console.log(SERVE_USAGE); process.exit(0) }
100
+ for (let i = 0; i < argv.length; i++) {
101
+ const eq = argv[i].indexOf('=')
102
+ const name = eq === -1 ? argv[i] : argv[i].slice(0, eq)
103
+ const inline = eq === -1 ? undefined : argv[i].slice(eq + 1)
104
+ if (!['--config', '--host', '--port', '--home'].includes(name)) { console.error(SERVE_USAGE); process.exit(2) }
105
+ const value = inline ?? argv[++i]
106
+ if (value === undefined || value === '' || value.startsWith('--')) { console.error(SERVE_USAGE); process.exit(2) }
107
+ flags[name.slice(2)] = value
108
+ }
109
+ let file = {}
110
+ if (flags.config !== undefined) {
111
+ try { file = JSON.parse(readFileSync(flags.config, 'utf8')) } catch (error) { console.error('[serve] cannot read --config ' + flags.config + ': ' + error.message); process.exit(2) }
112
+ if (file === null || typeof file !== 'object' || Array.isArray(file)) { console.error('[serve] --config must hold a JSON object of plugin config keys'); process.exit(2) }
113
+ }
114
+ let options
115
+ try { options = nodeConfig(file, flags) } catch (error) { console.error('[serve] ' + error.message); process.exit(2) }
116
+ let node
117
+ try {
118
+ node = await startNode(options)
119
+ } catch (error) {
120
+ if (error?.code === 'ERR_MODULE_NOT_FOUND') {
121
+ console.error('[serve] a runtime dependency is missing: ' + error.message.split('\n')[0])
122
+ console.error('[serve] install the tested set into a folder and run from there:\n ' + installLine(version) + '\n npx a2a-net serve --config node.json')
123
+ process.exit(1)
124
+ }
125
+ console.error('[serve] failed to start: ' + (error?.stack ?? error))
126
+ process.exit(1)
127
+ }
128
+ console.log('[serve] a2a-net node on ' + node.url + ' · zone ' + options.config.team + ' · session ' + options.config.session + ' · home ' + options.config.dshHome + ' (no DSH sessions; Ctrl+C to stop)')
129
+ const stop = async () => { await node.close().catch(() => {}); process.exit(0) }
130
+ process.once('SIGINT', stop)
131
+ process.once('SIGTERM', stop)
132
+ }
package/cordis.patch.yml CHANGED
@@ -1,21 +1,21 @@
1
- # Bundle patch for @nelsonlongxiang/dsh-open-a2a-net: mounts the a2a row into
2
- # the host composition of a dsh profile. The plugin registers its
3
- # /.well-known/agent-card.json announce route, its /__dsh_a2a/* state and
4
- # control routes, and the a2a_teams / a2a_route model tools on the shared web
5
- # server and tool runtime — it owns no listener of its own, so compose it
6
- # beside a webserver row (the stock web profile already has one).
7
- #
8
- # Install: `dsh plugin --profile <name> add @nelsonlongxiang/dsh-open-a2a-net`
9
- # (npm, a local path, or Git). The `dsh plugin` command pnpm-installs the
10
- # package into the profile and reconciles it into the profile's
11
- # `dsh.profile.bundles` layer list.
12
- #
13
- # All config keys carry schema defaults: session nodes are on (the sidebar
14
- # join control works out of the box), announce is opt-in. Peers, zone
15
- # delegates, and an API key are set per deployment through row config
16
- # overlays, never here.
17
- - insert:
18
- - id: a2a
19
- # Node-resolvable package name — must stay in sync with package.json
20
- # `name`. Quoted because `@` is a reserved YAML indicator.
21
- name: '@nelsonlongxiang/dsh-open-a2a-net'
1
+ # Bundle patch for @nelsonlongxiang/dsh-open-a2a-net: mounts the a2a row into
2
+ # the host composition of a dsh profile. The plugin registers its
3
+ # /.well-known/agent-card.json announce route, its /__dsh_a2a/* state and
4
+ # control routes, and the a2a_teams / a2a_route model tools on the shared web
5
+ # server and tool runtime — it owns no listener of its own, so compose it
6
+ # beside a webserver row (the stock web profile already has one).
7
+ #
8
+ # Install: `dsh plugin --profile <name> add @nelsonlongxiang/dsh-open-a2a-net`
9
+ # (npm, a local path, or Git). The `dsh plugin` command pnpm-installs the
10
+ # package into the profile and reconciles it into the profile's
11
+ # `dsh.profile.bundles` layer list.
12
+ #
13
+ # All config keys carry schema defaults: session nodes are on (the sidebar
14
+ # join control works out of the box), announce is opt-in. Peers, zone
15
+ # delegates, and an API key are set per deployment through row config
16
+ # overlays, never here.
17
+ - insert:
18
+ - id: a2a
19
+ # Node-resolvable package name — must stay in sync with package.json
20
+ # `name`. Quoted because `@` is a reserved YAML indicator.
21
+ name: '@nelsonlongxiang/dsh-open-a2a-net'
@@ -0,0 +1,29 @@
1
+ /** Read-only canvas ownership projection; never authorizes or selects a dispatch candidate. */
2
+ import { advertisedCanvasTeams } from "./peer-directory.js";
3
+ export function canvasOwners(local, ownUrl, peers) {
4
+ const rows = local.map(row => ({ ...row, owner: ownUrl, memberCount: row.members.length, local: true }));
5
+ const seen = new Set();
6
+ for (const { url, card } of peers)
7
+ for (const entry of card.expiresAt > Date.now() ? advertisedCanvasTeams(card) : []) {
8
+ const key = JSON.stringify([card.publicKey, card.session, entry.team]);
9
+ if (seen.has(key))
10
+ continue;
11
+ seen.add(key);
12
+ rows.push({ team: entry.team, name: entry.name, owner: url, memberCount: entry.members.length, local: false, members: entry.members, scopeMembers: entry.members.filter(ref => !(card.sessionTeams ?? []).some(session => session.team === ref)) });
13
+ }
14
+ return rows;
15
+ }
16
+ export function canvasMemberships(owners, handle, localId, resolveLocal) {
17
+ return owners.filter(row => ((row.scopeMembers ?? row.members).includes(handle) && (row.local || resolveLocal === undefined || resolveLocal(handle) === localId)) || (row.local && localId !== undefined && row.members.includes(localId))).map(({ members: _members, scopeMembers: _scopeMembers, ...row }) => row);
18
+ }
19
+ export function canvasFailure(team, owners, reason) {
20
+ if (!new RegExp('^.+/canvas/[^/]+$').test(team))
21
+ return reason;
22
+ const matches = owners.filter(row => row.team === team);
23
+ if (matches.length === 0)
24
+ return reason + ' — canvas-not-found: no matching team in the currently visible verified canvas directory (may be absent or outside your information scope; not proof of fleet-wide absence)';
25
+ const list = matches.map(row => row.owner + ' (' + row.team + ', members=' + row.memberCount + ')').join('; ');
26
+ if (matches.length > 1)
27
+ return reason + ' — canvas-multiple-owners: ' + list + ' — use an unambiguous operator-established team; do not guess a host';
28
+ return reason + ' — canvas-owner: ' + list + ' — existing refusal/prerequisite above still applies; an owner URL is discovery, not a direct-connect requirement or permission to bypass home';
29
+ }
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Owner-qualified canvas ids (#282 S2, design docs/design/simplify-2026-11.md D1).
3
+ * `<zone>/canvas/<name>@<fp8>`: the suffix is the first 8 hex of sha256 over the
4
+ * owner's signed card public key (SPKI DER), so a host that changes address keeps
5
+ * its id. The suffix selects a signing key, not a URL: as on master, a seeded peer
6
+ * serving that key's signed card core counts as an alias of the same owner, and the
7
+ * card's canvasOrg itself is not signed. A bare `<zone>/canvas/<name>` keeps routing
8
+ * exactly as before; the suffix only selects among owners.
9
+ */
10
+ import { createHash } from 'node:crypto';
11
+ const QUALIFIED = /^(.+\/canvas\/[^/]+)@([0-9a-f]{8})$/;
12
+ /** First 8 hex of sha256 over the base64 SPKI DER public key; '' when there is no key. */
13
+ export function canvasOwnerFingerprint(publicKey) {
14
+ if (publicKey === undefined || publicKey === '')
15
+ return '';
16
+ return createHash('sha256').update(Buffer.from(publicKey, 'base64')).digest('hex').slice(0, 8);
17
+ }
18
+ /** Split `<zone>/canvas/<name>@<fp8>` into the plain team and the owner fingerprint. */
19
+ export function splitCanvasOwner(team) {
20
+ const m = QUALIFIED.exec(team);
21
+ return m === null ? { team } : { team: m[1], owner: m[2] };
22
+ }
23
+ /** The display/routing id of a canvas team owned by the card holding `publicKey`. */
24
+ export function qualifiedCanvasId(team, publicKey) {
25
+ const fp = canvasOwnerFingerprint(publicKey);
26
+ return fp === '' ? team : `${team}@${fp}`;
27
+ }
@@ -56,6 +56,12 @@ function validName(raw) {
56
56
  return undefined;
57
57
  return clean;
58
58
  }
59
+ /** A NEW canvas name: `@<8 lowercase hex>` at the end is the owner-fingerprint suffix of a routing id (#282 S2), never part of a name.
60
+ * An existing canvas with such a name still loads; it is reached by its full id `<zone>/canvas/<name>@<own fp8>`. */
61
+ function validNewName(raw) {
62
+ const clean = validName(raw);
63
+ return clean === undefined || /@[0-9a-f]{8}$/.test(clean) ? undefined : clean;
64
+ }
59
65
  /**
60
66
  * Bounded canvas-team store with last-write-wins persistence. Pure of any
61
67
  * registry knowledge: callers validate join state and resolve members.
@@ -191,7 +197,7 @@ export class CanvasStore {
191
197
  * @returns the stored name, or undefined when rejected.
192
198
  */
193
199
  create(name) {
194
- const clean = validName(name);
200
+ const clean = validNewName(name);
195
201
  if (clean === undefined)
196
202
  return undefined;
197
203
  if (this.hasTeam(clean))
package/lib/client.js CHANGED
@@ -22,8 +22,8 @@ window.__ModuleLoader__.load({
22
22
  return n;
23
23
  }
24
24
  //#endregion
25
- //#region \0dsh-css:D:\workspace\dsh-open-a2a-net\tmp\release-0.6.37-d5df179\src\src\client\A2aControl.module.css.mjs
26
- const css = ".efpdCG_wrap{max-width:100%;display:inline-flex;position:relative}.efpdCG_trigger{align-items:center;gap:4px;display:inline-flex;position:relative}.efpdCG_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 efpdCG_badgePulse;position:absolute;top:-6px;right:-8px}@keyframes efpdCG_badgePulse{0%,to{box-shadow:0 0 #4c8bf573}50%{box-shadow:0 0 0 4px #4c8bf500}}@media (prefers-reduced-motion:reduce){.efpdCG_unreadBadge{animation:none}}.efpdCG_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}.efpdCG_title{font-size:13px;font-weight:600}.efpdCG_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}.efpdCG_stageRow{border-top:1px solid var(--dsh-border,#2a2a30);align-items:center;gap:8px;margin-top:4px;padding-top:8px;display:flex}.efpdCG_stageTitle{text-transform:uppercase;letter-spacing:.04em;opacity:.65;font-size:11px;font-weight:600}.efpdCG_stageLinks{gap:6px;display:flex}.efpdCG_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}.efpdCG_stageLink:hover,.efpdCG_stageLink:focus-visible{border-color:var(--dsh-accent,#4c8bf5)}.efpdCG_empty{opacity:.7;font-size:12px}.efpdCG_row{border:1px solid #0000;border-radius:8px;align-items:center;gap:10px;padding:8px;display:flex}.efpdCG_joined{border-color:var(--dsh-border,#2a2a30)}.efpdCG_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}.efpdCG_facts:hover{background:var(--dsh-surface-hover,#ffffff0f)}.efpdCG_nameRow{align-items:center;gap:6px;display:flex}.efpdCG_stateDot{border-radius:50%;flex:none;width:7px;height:7px}.efpdCG_stateDot.efpdCG_live{background:var(--dsh-accent,#4c8bf5)}.efpdCG_stateDot.efpdCG_live.efpdCG_joined{background:var(--dsh-success,#3fb96f)}.efpdCG_stateDot.efpdCG_cold{background:var(--dsh-warning,#d9a13c)}.efpdCG_name{text-overflow:ellipsis;white-space:nowrap;font-size:13px;font-weight:500;overflow:hidden}.efpdCG_description{opacity:.7;text-overflow:ellipsis;white-space:nowrap;font-size:12px;overflow:hidden}.efpdCG_team{opacity:.55;font-size:11px;font-family:var(--dsh-mono,monospace)}.efpdCG_note{opacity:.6;font-size:11px}.efpdCG_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}.efpdCG_searchInput:focus{outline:1px solid var(--dsh-accent,#4c8bf5)}.efpdCG_sessionGroup{flex-direction:column;gap:2px;display:flex}.efpdCG_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}.efpdCG_groupChevron{transition:transform .12s;display:inline-block}.efpdCG_groupChevronClosed{transform:rotate(-90deg)}.efpdCG_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}.efpdCG_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}.efpdCG_groupButton:hover{border-color:var(--dsh-border,#2a2a30);opacity:1}.efpdCG_groupPicker{border:1px dashed var(--dsh-border,#2a2a30);border-radius:8px;flex-wrap:wrap;gap:6px;width:100%;padding:8px;display:flex;position:relative}.efpdCG_groupOption{border:1px solid var(--dsh-border,#2a2a30);color:inherit;cursor:pointer;background:0 0;border-radius:999px;padding:2px 8px;font-size:11px}.efpdCG_groupOptionActive{border-color:var(--dsh-accent,#4c8bf5);color:var(--dsh-accent,#4c8bf5)}.efpdCG_groupNew{align-items:center;gap:6px;width:100%;margin-top:2px;display:flex}.efpdCG_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}.efpdCG_peerGrid{flex-wrap:wrap;gap:6px;display:flex}.efpdCG_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}.efpdCG_peerHost{text-overflow:ellipsis;white-space:nowrap;max-width:160px;overflow:hidden}.efpdCG_peerScore{font-family:var(--dsh-mono,monospace);opacity:.6}.efpdCG_activityList{flex-direction:column;gap:2px;display:flex}.efpdCG_activityRow{border-radius:4px;align-items:center;gap:8px;padding:2px 4px;font-size:11px;display:flex}.efpdCG_activityRow[data-ok=false]{opacity:.55}.efpdCG_activityDir{flex:none;font-weight:600}.efpdCG_activityDir.efpdCG_in{color:var(--dsh-success,#3fb96f)}.efpdCG_activityDir.efpdCG_out{color:var(--dsh-accent,#4c8bf5)}.efpdCG_activityTeam{text-overflow:ellipsis;white-space:nowrap;min-width:0;font-family:var(--dsh-mono,monospace);opacity:.8;flex:1;overflow:hidden}.efpdCG_activityPeer{text-overflow:ellipsis;white-space:nowrap;opacity:.55;max-width:90px;overflow:hidden}.efpdCG_activityTime{opacity:.5;font-family:var(--dsh-mono,monospace);flex:none}.efpdCG_activityRow[data-jump=true]{cursor:pointer}.efpdCG_activityRow[data-jump=true]:hover{background:var(--dsh-surface-hover,#ffffff0f)}.efpdCG_fresh{animation:1.2s ease-out 3 efpdCG_freshFlash}@keyframes efpdCG_freshFlash{0%{background:#4c8bf538}to{background:0 0}}@media (prefers-reduced-motion:reduce){.efpdCG_fresh{background:#4c8bf51f;animation:none}}.efpdCG_inFlightList{flex-direction:column;gap:2px;margin-bottom:4px;display:flex}.efpdCG_inFlightRow{border:1px dashed var(--dsh-border,#2a2a30);border-radius:4px;align-items:center;gap:8px;padding:3px 4px;font-size:11px;display:flex}.efpdCG_inFlightPulse{background:var(--dsh-accent,#4c8bf5);border-radius:50%;flex:none;width:7px;height:7px;animation:1.1s ease-in-out infinite efpdCG_flightPulse}@keyframes efpdCG_flightPulse{0%,to{opacity:1;transform:scale(1)}50%{opacity:.5;transform:scale(.55)}}@media (prefers-reduced-motion:reduce){.efpdCG_inFlightPulse{animation:none}}.efpdCG_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}.efpdCG_inFlightStale{opacity:.55}.efpdCG_canvasCreate{align-items:center;gap:6px;margin-bottom:4px;display:flex}.efpdCG_canvasCreate .efpdCG_groupInput{flex:1;min-width:0}.efpdCG_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}.efpdCG_canvasChip>span:not([class]){white-space:nowrap;text-overflow:ellipsis;min-width:0;overflow:hidden}.efpdCG_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}.efpdCG_canvasChipRemove:hover{color:var(--dsh-color-text-primary,#eee)}.efpdCG_canvasChipRemove{transition:color .14s ease-out}@media (prefers-reduced-motion:reduce){.efpdCG_canvasChipRemove{transition:none}}.efpdCG_canvasChipRemove:focus-visible{outline:2px solid var(--dsh-color-primary,#4f8cff);outline-offset:1px;border-radius:4px}.efpdCG_canvasActions{gap:6px;margin-top:4px;display:flex}.efpdCG_inFlightStale .efpdCG_inFlightPulse{opacity:.4;animation:none}.efpdCG_failMark{color:#f87171;flex-shrink:0;font-size:11px}.efpdCG_staleNote{color:var(--dsh-text-muted,#94a3b8b3);padding:4px 8px;font-size:11px}";
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}";
27
27
  const tagId = "@nelsonlongxiang/dsh-open-a2a-net/A2aControl.module.css";
28
28
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
29
29
  const tag = document.createElement("style");
@@ -33,67 +33,67 @@ window.__ModuleLoader__.load({
33
33
  document.head.appendChild(tag);
34
34
  }
35
35
  var A2aControl_module_css_default = {
36
- "activityDir": "efpdCG_activityDir",
37
- "activityList": "efpdCG_activityList",
38
- "activityPeer": "efpdCG_activityPeer",
39
- "activityRow": "efpdCG_activityRow",
40
- "activityTeam": "efpdCG_activityTeam",
41
- "activityTime": "efpdCG_activityTime",
42
- "badgePulse": "efpdCG_badgePulse",
43
- "canvasActions": "efpdCG_canvasActions",
44
- "canvasChip": "efpdCG_canvasChip",
45
- "canvasChipRemove": "efpdCG_canvasChipRemove",
46
- "canvasCreate": "efpdCG_canvasCreate",
47
- "cold": "efpdCG_cold",
48
- "description": "efpdCG_description",
49
- "empty": "efpdCG_empty",
50
- "facts": "efpdCG_facts",
51
- "failMark": "efpdCG_failMark",
52
- "flightPulse": "efpdCG_flightPulse",
53
- "fresh": "efpdCG_fresh",
54
- "freshFlash": "efpdCG_freshFlash",
55
- "groupButton": "efpdCG_groupButton",
56
- "groupChevron": "efpdCG_groupChevron",
57
- "groupChevronClosed": "efpdCG_groupChevronClosed",
58
- "groupHead": "efpdCG_groupHead",
59
- "groupInput": "efpdCG_groupInput",
60
- "groupNew": "efpdCG_groupNew",
61
- "groupOption": "efpdCG_groupOption",
62
- "groupOptionActive": "efpdCG_groupOptionActive",
63
- "groupPicker": "efpdCG_groupPicker",
64
- "groupTag": "efpdCG_groupTag",
65
- "in": "efpdCG_in",
66
- "inFlightList": "efpdCG_inFlightList",
67
- "inFlightPulse": "efpdCG_inFlightPulse",
68
- "inFlightRow": "efpdCG_inFlightRow",
69
- "inFlightStale": "efpdCG_inFlightStale",
70
- "joined": "efpdCG_joined",
71
- "live": "efpdCG_live",
72
- "name": "efpdCG_name",
73
- "nameRow": "efpdCG_nameRow",
74
- "note": "efpdCG_note",
75
- "out": "efpdCG_out",
76
- "peerChip": "efpdCG_peerChip",
77
- "peerGrid": "efpdCG_peerGrid",
78
- "peerHost": "efpdCG_peerHost",
79
- "peerScore": "efpdCG_peerScore",
80
- "popover": "efpdCG_popover",
81
- "row": "efpdCG_row",
82
- "searchInput": "efpdCG_searchInput",
83
- "sectionTitle": "efpdCG_sectionTitle",
84
- "sessionGroup": "efpdCG_sessionGroup",
85
- "stageLink": "efpdCG_stageLink",
86
- "stageLinks": "efpdCG_stageLinks",
87
- "stageRow": "efpdCG_stageRow",
88
- "stageTitle": "efpdCG_stageTitle",
89
- "staleNote": "efpdCG_staleNote",
90
- "stateDot": "efpdCG_stateDot",
91
- "team": "efpdCG_team",
92
- "title": "efpdCG_title",
93
- "trigger": "efpdCG_trigger",
94
- "unreadBadge": "efpdCG_unreadBadge",
95
- "versionTag": "efpdCG_versionTag",
96
- "wrap": "efpdCG_wrap"
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"
97
97
  };
98
98
  //#endregion
99
99
  //#region lib/client/A2aControl.js