@nelsonlongxiang/dsh-open-a2a-net 0.6.28 → 0.6.30

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 (69) hide show
  1. package/README.md +87 -14
  2. package/README_zh.md +83 -7
  3. package/assets/nexusDist/assets/index-C_x3iAth.js +9 -0
  4. package/assets/nexusDist/assets/index-Dz8N80tI.css +1 -0
  5. package/assets/nexusDist/index.html +2 -2
  6. package/cli/a2a.mjs +0 -0
  7. package/cordis.patch.yml +21 -21
  8. package/lib/canvas-store.js +21 -1
  9. package/lib/client.js +64 -64
  10. package/lib/client.js.map +1 -1
  11. package/lib/faces/admin.js +755 -0
  12. package/lib/faces/canvas-notify.js +138 -0
  13. package/lib/faces/wire.js +669 -0
  14. package/lib/flood-guard.js +106 -0
  15. package/lib/index.js +200 -1392
  16. package/lib/recursion-guard.js +78 -0
  17. package/lib/runtime.js +1 -0
  18. package/lib/skill/SKILL.md +35 -9
  19. package/lib/skill/references/cli.md +24 -7
  20. package/lib/skill/references/collaboration.md +355 -0
  21. package/lib/skill/references/discovery.md +5 -2
  22. package/lib/skill/references/pitfalls.md +2 -1
  23. package/lib/skill/references/routing.md +18 -8
  24. package/lib/skill/references/setup.md +152 -0
  25. package/lib/task-ledger.js +40 -4
  26. package/lib/types/canvas-store.d.ts +10 -1
  27. package/lib/types/faces/admin.d.ts +133 -0
  28. package/lib/types/faces/canvas-notify.d.ts +74 -0
  29. package/lib/types/faces/wire.d.ts +171 -0
  30. package/lib/types/flood-guard.d.ts +29 -0
  31. package/lib/types/index.d.ts +11 -3
  32. package/lib/types/recursion-guard.d.ts +56 -0
  33. package/lib/types/runtime.d.ts +35 -0
  34. package/lib/types/task-ledger.d.ts +15 -1
  35. package/nexus-stage/package-lock.json +2262 -2262
  36. package/nexus-stage/src/canvas-ops.ts +175 -175
  37. package/nexus-stage/src/canvas-wire.ts +156 -156
  38. package/nexus-stage/src/edges.ts +72 -72
  39. package/nexus-stage/src/federation-overlay.ts +103 -103
  40. package/nexus-stage/src/federation.ts +240 -240
  41. package/nexus-stage/src/layout-doc.ts +143 -143
  42. package/nexus-stage/src/layout-wire.ts +158 -158
  43. package/nexus-stage/src/main.ts +5 -1
  44. package/nexus-stage/src/planning-card-text.ts +73 -0
  45. package/nexus-stage/src/planning-edges.ts +26 -0
  46. package/nexus-stage/src/planning-fit.ts +45 -0
  47. package/nexus-stage/src/planning-legend.ts +38 -0
  48. package/nexus-stage/src/planning-mirror.ts +31 -0
  49. package/nexus-stage/src/planning-overlap.ts +159 -0
  50. package/nexus-stage/src/planning-relayout.ts +121 -0
  51. package/nexus-stage/src/planning-search.ts +168 -0
  52. package/nexus-stage/src/planning-types.ts +1 -0
  53. package/nexus-stage/src/planning-undo.ts +68 -0
  54. package/nexus-stage/src/planning-view.ts +120 -121
  55. package/nexus-stage/src/planning.css +41 -8
  56. package/nexus-stage/src/seat.ts +35 -35
  57. package/nexus-stage/src/stage-name.ts +13 -13
  58. package/nexus-stage/src/tokens.ts +31 -31
  59. package/nexus-stage/src/viewport.ts +24 -1
  60. package/nexus-stage/src/wb-chat.ts +20 -6
  61. package/nexus-stage/src/wb-ledger.ts +20 -7
  62. package/nexus-stage/src/wb-names.ts +43 -0
  63. package/nexus-stage/src/workbench.css +8 -2
  64. package/nexus-stage/src/workbench.ts +55 -17
  65. package/nexus-stage/src/world.ts +13 -21
  66. package/package.json +216 -214
  67. package/assets/nexusDist/assets/index-1niOBYxp.js +0 -5
  68. package/assets/nexusDist/assets/index-uQf7NwjF.css +0 -1
  69. package/nexus-stage/scripts/__pycache__/mockhost.cpython-312.pyc +0 -0
@@ -1,156 +1,156 @@
1
- /**
2
- * The canvas write face's transport layer (POST /__dsh_a2a/canvas): action
3
- * sequencing, per-team serialization, and the notice classification that
4
- * design.md §3.4 mandates (host error text surfaces VERBATIM - the sidebar
5
- * reference's swallowed `.catch(() => {})` is the anti-pattern here).
6
- *
7
- * Classification per request:
8
- * - `ok:true` → success (silent)
9
- * - `ok:false` WITH an `error` string → error notice, verbatim
10
- * - `ok:false` without `error` (absent target, → idempotent no-op,
11
- * member cap hit) silent success
12
- * - HTTP >= 400 → body error verbatim, else `HTTP <status>`
13
- * - send() throws → `画布不可达:<message>`
14
- * - 200 + ok:false WITHOUT an error string → idempotent no-op
15
- * (absent target, member/team cap hit) — silent success
16
- * - 200 + non-JSON body (proxy artifact) → classified as success
17
- * (known limitation: nothing parseable to classify)
18
- *
19
- * Ops on the same team run through a serial queue (a later gesture waits
20
- * instead of racing; cross-team ops run in parallel) - mirrors the
21
- * sidebar's per-team busy keys, minus the rejection hostility. Pure
22
- * control flow: the transport is injected.
23
- * @module nexus-stage/canvas-wire
24
- */
25
-
26
- /** One send outcome: HTTP status plus the parsed JSON body (whatever it is). */
27
- export interface CanvasWireResult {
28
- status: number
29
- body: { ok?: boolean; error?: string; teams?: readonly string[]; members?: readonly string[]; name?: string }
30
- }
31
-
32
- /** Transport injection: POST the body, resolve with parsed outcome, throw on network failure. */
33
- export type CanvasSend = (body: unknown) => Promise<CanvasWireResult>
34
-
35
- export interface CanvasWireDeps {
36
- send: CanvasSend
37
- onNotice(kind: 'error' | 'info', text: string): void
38
- }
39
-
40
- export interface CanvasWire {
41
- /** create → add-member per id, serial, first failure stops. `created`:
42
- * the team did not exist model-side before this action — a first-add
43
- * failure would then leave a ghost EMPTY team on the host, compensated
44
- * with a best-effort remove. */
45
- createTeam(name: string, ids: readonly string[], created?: boolean): Promise<boolean>
46
- addMembers(team: string, ids: readonly string[]): Promise<boolean>
47
- removeMembers(team: string, ids: readonly string[]): Promise<boolean>
48
- /** Pre-computed remove/add roster ops (from canvas-ops.reorderOps). */
49
- runRosterOps(team: string, ops: ReadonlyArray<{ op: 'remove' | 'add'; id: string }>): Promise<boolean>
50
- removeTeam(name: string): Promise<boolean>
51
- hasPending(): boolean
52
- }
53
-
54
- type RosterOp = { op: 'remove' | 'add'; id: string }
55
-
56
- function errorText(body: CanvasWireResult['body']): string | undefined {
57
- return typeof body.error === 'string' ? body.error : undefined
58
- }
59
-
60
- export function createCanvasWire(deps: CanvasWireDeps): CanvasWire {
61
- const queues = new Map<string, Promise<unknown>>()
62
- let pending = 0
63
-
64
- /** Serial per-team queue: same key waits, other keys run in parallel. */
65
- function enqueue<T>(key: string, task: () => Promise<T>): Promise<T> {
66
- pending += 1
67
- const prev = queues.get(key) ?? Promise.resolve()
68
- const run = async (): Promise<T> => {
69
- try {
70
- return await task()
71
- } finally {
72
- pending -= 1 // settles BEFORE the returned promise resolves
73
- }
74
- }
75
- const next = prev.then(run, run) // run regardless of how the predecessor settled
76
- queues.set(
77
- key,
78
- next.then(
79
- () => undefined,
80
- () => undefined,
81
- ),
82
- )
83
- return next
84
- }
85
-
86
- /** One classified request — NEVER enqueues (public ops own the queue). */
87
- async function raw(body: Record<string, unknown>): Promise<boolean> {
88
- let res: CanvasWireResult
89
- try {
90
- res = await deps.send(body)
91
- } catch (error) {
92
- const message = String((error as Error | undefined)?.message ?? error).slice(0, 80)
93
- deps.onNotice('error', `画布不可达:${message}`)
94
- return false
95
- }
96
- if (res.status >= 400) {
97
- deps.onNotice('error', errorText(res.body) ?? `HTTP ${res.status}`)
98
- return false
99
- }
100
- if (res.body.ok === true) return true
101
- const text = errorText(res.body)
102
- if (text !== undefined) {
103
- deps.onNotice('error', text)
104
- return false
105
- }
106
- return true // ok:false without error: idempotent no-op (absent target / cap)
107
- }
108
-
109
- async function serial(body: Record<string, unknown>, ids: readonly string[]): Promise<boolean> {
110
- for (const id of ids) {
111
- if (!(await raw({ ...body, id }))) return false
112
- }
113
- return true
114
- }
115
-
116
- return {
117
- createTeam(name, ids, created = false) {
118
- return enqueue(name, async () => {
119
- if (!(await raw({ action: 'create', name }))) return false
120
- for (let i = 0; i < ids.length; i++) {
121
- if (!(await raw({ action: 'add-member', name, id: ids[i]! }))) {
122
- // A failure on the FIRST add of a team this action created leaves
123
- // a ghost EMPTY team on the host — compensate with a remove.
124
- // Later failures keep whatever members genuinely joined.
125
- if (created && i === 0) await raw({ action: 'remove', name })
126
- return false
127
- }
128
- }
129
- return true
130
- })
131
- },
132
- addMembers(team, ids) {
133
- return enqueue(team, () => serial({ action: 'add-member', name: team }, ids))
134
- },
135
- removeMembers(team, ids) {
136
- return enqueue(team, () => serial({ action: 'remove-member', name: team }, ids))
137
- },
138
- runRosterOps(team, ops) {
139
- return enqueue(team, async () => {
140
- for (const op of ops) {
141
- const body = op.op === 'remove'
142
- ? { action: 'remove-member', name: team, id: op.id }
143
- : { action: 'add-member', name: team, id: op.id }
144
- if (!(await raw(body))) return false
145
- }
146
- return true
147
- })
148
- },
149
- removeTeam(name) {
150
- return enqueue(name, () => raw({ action: 'remove', name }))
151
- },
152
- hasPending() {
153
- return pending > 0
154
- },
155
- }
156
- }
1
+ /**
2
+ * The canvas write face's transport layer (POST /__dsh_a2a/canvas): action
3
+ * sequencing, per-team serialization, and the notice classification that
4
+ * design.md §3.4 mandates (host error text surfaces VERBATIM - the sidebar
5
+ * reference's swallowed `.catch(() => {})` is the anti-pattern here).
6
+ *
7
+ * Classification per request:
8
+ * - `ok:true` → success (silent)
9
+ * - `ok:false` WITH an `error` string → error notice, verbatim
10
+ * - `ok:false` without `error` (absent target, → idempotent no-op,
11
+ * member cap hit) silent success
12
+ * - HTTP >= 400 → body error verbatim, else `HTTP <status>`
13
+ * - send() throws → `画布不可达:<message>`
14
+ * - 200 + ok:false WITHOUT an error string → idempotent no-op
15
+ * (absent target, member/team cap hit) — silent success
16
+ * - 200 + non-JSON body (proxy artifact) → classified as success
17
+ * (known limitation: nothing parseable to classify)
18
+ *
19
+ * Ops on the same team run through a serial queue (a later gesture waits
20
+ * instead of racing; cross-team ops run in parallel) - mirrors the
21
+ * sidebar's per-team busy keys, minus the rejection hostility. Pure
22
+ * control flow: the transport is injected.
23
+ * @module nexus-stage/canvas-wire
24
+ */
25
+
26
+ /** One send outcome: HTTP status plus the parsed JSON body (whatever it is). */
27
+ export interface CanvasWireResult {
28
+ status: number
29
+ body: { ok?: boolean; error?: string; teams?: readonly string[]; members?: readonly string[]; name?: string }
30
+ }
31
+
32
+ /** Transport injection: POST the body, resolve with parsed outcome, throw on network failure. */
33
+ export type CanvasSend = (body: unknown) => Promise<CanvasWireResult>
34
+
35
+ export interface CanvasWireDeps {
36
+ send: CanvasSend
37
+ onNotice(kind: 'error' | 'info', text: string): void
38
+ }
39
+
40
+ export interface CanvasWire {
41
+ /** create → add-member per id, serial, first failure stops. `created`:
42
+ * the team did not exist model-side before this action — a first-add
43
+ * failure would then leave a ghost EMPTY team on the host, compensated
44
+ * with a best-effort remove. */
45
+ createTeam(name: string, ids: readonly string[], created?: boolean): Promise<boolean>
46
+ addMembers(team: string, ids: readonly string[]): Promise<boolean>
47
+ removeMembers(team: string, ids: readonly string[]): Promise<boolean>
48
+ /** Pre-computed remove/add roster ops (from canvas-ops.reorderOps). */
49
+ runRosterOps(team: string, ops: ReadonlyArray<{ op: 'remove' | 'add'; id: string }>): Promise<boolean>
50
+ removeTeam(name: string): Promise<boolean>
51
+ hasPending(): boolean
52
+ }
53
+
54
+ type RosterOp = { op: 'remove' | 'add'; id: string }
55
+
56
+ function errorText(body: CanvasWireResult['body']): string | undefined {
57
+ return typeof body.error === 'string' ? body.error : undefined
58
+ }
59
+
60
+ export function createCanvasWire(deps: CanvasWireDeps): CanvasWire {
61
+ const queues = new Map<string, Promise<unknown>>()
62
+ let pending = 0
63
+
64
+ /** Serial per-team queue: same key waits, other keys run in parallel. */
65
+ function enqueue<T>(key: string, task: () => Promise<T>): Promise<T> {
66
+ pending += 1
67
+ const prev = queues.get(key) ?? Promise.resolve()
68
+ const run = async (): Promise<T> => {
69
+ try {
70
+ return await task()
71
+ } finally {
72
+ pending -= 1 // settles BEFORE the returned promise resolves
73
+ }
74
+ }
75
+ const next = prev.then(run, run) // run regardless of how the predecessor settled
76
+ queues.set(
77
+ key,
78
+ next.then(
79
+ () => undefined,
80
+ () => undefined,
81
+ ),
82
+ )
83
+ return next
84
+ }
85
+
86
+ /** One classified request — NEVER enqueues (public ops own the queue). */
87
+ async function raw(body: Record<string, unknown>): Promise<boolean> {
88
+ let res: CanvasWireResult
89
+ try {
90
+ res = await deps.send(body)
91
+ } catch (error) {
92
+ const message = String((error as Error | undefined)?.message ?? error).slice(0, 80)
93
+ deps.onNotice('error', `画布不可达:${message}`)
94
+ return false
95
+ }
96
+ if (res.status >= 400) {
97
+ deps.onNotice('error', errorText(res.body) ?? `HTTP ${res.status}`)
98
+ return false
99
+ }
100
+ if (res.body.ok === true) return true
101
+ const text = errorText(res.body)
102
+ if (text !== undefined) {
103
+ deps.onNotice('error', text)
104
+ return false
105
+ }
106
+ return true // ok:false without error: idempotent no-op (absent target / cap)
107
+ }
108
+
109
+ async function serial(body: Record<string, unknown>, ids: readonly string[]): Promise<boolean> {
110
+ for (const id of ids) {
111
+ if (!(await raw({ ...body, id }))) return false
112
+ }
113
+ return true
114
+ }
115
+
116
+ return {
117
+ createTeam(name, ids, created = false) {
118
+ return enqueue(name, async () => {
119
+ if (!(await raw({ action: 'create', name }))) return false
120
+ for (let i = 0; i < ids.length; i++) {
121
+ if (!(await raw({ action: 'add-member', name, id: ids[i]! }))) {
122
+ // A failure on the FIRST add of a team this action created leaves
123
+ // a ghost EMPTY team on the host — compensate with a remove.
124
+ // Later failures keep whatever members genuinely joined.
125
+ if (created && i === 0) await raw({ action: 'remove', name })
126
+ return false
127
+ }
128
+ }
129
+ return true
130
+ })
131
+ },
132
+ addMembers(team, ids) {
133
+ return enqueue(team, () => serial({ action: 'add-member', name: team }, ids))
134
+ },
135
+ removeMembers(team, ids) {
136
+ return enqueue(team, () => serial({ action: 'remove-member', name: team }, ids))
137
+ },
138
+ runRosterOps(team, ops) {
139
+ return enqueue(team, async () => {
140
+ for (const op of ops) {
141
+ const body = op.op === 'remove'
142
+ ? { action: 'remove-member', name: team, id: op.id }
143
+ : { action: 'add-member', name: team, id: op.id }
144
+ if (!(await raw(body))) return false
145
+ }
146
+ return true
147
+ })
148
+ },
149
+ removeTeam(name) {
150
+ return enqueue(name, () => raw({ action: 'remove', name }))
151
+ },
152
+ hasPending() {
153
+ return pending > 0
154
+ },
155
+ }
156
+ }
@@ -1,72 +1,72 @@
1
- /**
2
- * Star-shaped membership edges for the 2D planning canvas: one spoke per
3
- * (team frame -> member card), replacing the O(n^2) pairwise mesh. All
4
- * geometry is world-space so the SVG layer can emit raw coordinates - the
5
- * transformed #world container carries them to the screen for free.
6
- *
7
- * Anchor contract (must stay in lockstep with planning.css):
8
- * - origin = the frame titlebar's bottom-edge midpoint. The head is a
9
- * 24px border-box straddling the frame's top border at top:-13px, so
10
- * its bottom edge sits at fy + 11, horizontally centered at fx + fw/2.
11
- * - target = the member card's top-edge midpoint {nx, ny - NODE_H/2}.
12
- *
13
- * Cross-team members draw solid from their first membership (state order)
14
- * and dashed from every later one - the many-to-many invariant made
15
- * visible. Edges are derived only; there is no freehand edge in this
16
- * canvas. Pure: no DOM, no three.
17
- * @module nexus-stage/edges
18
- */
19
-
20
- import { NODE_H } from './world'
21
- import type { FrameGeom } from './world'
22
-
23
- /** Titlebar border-box height (planning.css .frame-head must match). */
24
- export const FRAME_HEAD_H = 24
25
- /** Titlebar CSS top offset, negative - straddles the frame's top border. */
26
- export const FRAME_HEAD_TOP = -13
27
- /** Origin anchor: frame top + the head's bottom edge offset. */
28
- export const FRAME_HEAD_LIFT = FRAME_HEAD_TOP + FRAME_HEAD_H
29
-
30
- /** Minimal team shape (topology.CanvasTeam satisfies it). */
31
- export interface EdgeTeam { name: string; members: ReadonlyArray<{ id: string }> }
32
-
33
- /** One derived spoke, ready for the SVG layer. */
34
- export interface StarEdge {
35
- x1: number
36
- y1: number
37
- x2: number
38
- y2: number
39
- /** True from a member's second membership on (cross-team many-to-many). */
40
- dashed: boolean
41
- /** Team name and member id, for keyed diffing. */
42
- team: string
43
- id: string
44
- }
45
-
46
- /**
47
- * Compute every membership spoke for the current world. Frames without a
48
- * rect and member ids without a position are skipped - dangling rows are
49
- * a polling transient, not an error.
50
- */
51
- export function starEdges(
52
- teams: ReadonlyArray<EdgeTeam>,
53
- frames: ReadonlyMap<string, FrameGeom>,
54
- positions: ReadonlyMap<string, { readonly x: number; readonly y: number }>,
55
- ): StarEdge[] {
56
- const out: StarEdge[] = []
57
- const seen = new Set<string>()
58
- for (const team of teams) {
59
- const rect = frames.get(team.name)
60
- if (rect === undefined) continue
61
- const x1 = rect.x + rect.w / 2
62
- const y1 = rect.y + FRAME_HEAD_LIFT
63
- for (const member of team.members) {
64
- const pos = positions.get(member.id)
65
- if (pos === undefined) continue
66
- const dashed = seen.has(member.id)
67
- seen.add(member.id)
68
- out.push({ x1, y1, x2: pos.x, y2: pos.y - NODE_H / 2, dashed, team: team.name, id: member.id })
69
- }
70
- }
71
- return out
72
- }
1
+ /**
2
+ * Star-shaped membership edges for the 2D planning canvas: one spoke per
3
+ * (team frame -> member card), replacing the O(n^2) pairwise mesh. All
4
+ * geometry is world-space so the SVG layer can emit raw coordinates - the
5
+ * transformed #world container carries them to the screen for free.
6
+ *
7
+ * Anchor contract (must stay in lockstep with planning.css):
8
+ * - origin = the frame titlebar's bottom-edge midpoint. The head is a
9
+ * 24px border-box straddling the frame's top border at top:-13px, so
10
+ * its bottom edge sits at fy + 11, horizontally centered at fx + fw/2.
11
+ * - target = the member card's top-edge midpoint {nx, ny - NODE_H/2}.
12
+ *
13
+ * Cross-team members draw solid from their first membership (state order)
14
+ * and dashed from every later one - the many-to-many invariant made
15
+ * visible. Edges are derived only; there is no freehand edge in this
16
+ * canvas. Pure: no DOM, no three.
17
+ * @module nexus-stage/edges
18
+ */
19
+
20
+ import { NODE_H } from './world'
21
+ import type { FrameGeom } from './world'
22
+
23
+ /** Titlebar border-box height (planning.css .frame-head must match). */
24
+ export const FRAME_HEAD_H = 24
25
+ /** Titlebar CSS top offset, negative - straddles the frame's top border. */
26
+ export const FRAME_HEAD_TOP = -13
27
+ /** Origin anchor: frame top + the head's bottom edge offset. */
28
+ export const FRAME_HEAD_LIFT = FRAME_HEAD_TOP + FRAME_HEAD_H
29
+
30
+ /** Minimal team shape (topology.CanvasTeam satisfies it). */
31
+ export interface EdgeTeam { name: string; members: ReadonlyArray<{ id: string }> }
32
+
33
+ /** One derived spoke, ready for the SVG layer. */
34
+ export interface StarEdge {
35
+ x1: number
36
+ y1: number
37
+ x2: number
38
+ y2: number
39
+ /** True from a member's second membership on (cross-team many-to-many). */
40
+ dashed: boolean
41
+ /** Team name and member id, for keyed diffing. */
42
+ team: string
43
+ id: string
44
+ }
45
+
46
+ /**
47
+ * Compute every membership spoke for the current world. Frames without a
48
+ * rect and member ids without a position are skipped - dangling rows are
49
+ * a polling transient, not an error.
50
+ */
51
+ export function starEdges(
52
+ teams: ReadonlyArray<EdgeTeam>,
53
+ frames: ReadonlyMap<string, FrameGeom>,
54
+ positions: ReadonlyMap<string, { readonly x: number; readonly y: number }>,
55
+ ): StarEdge[] {
56
+ const out: StarEdge[] = []
57
+ const seen = new Set<string>()
58
+ for (const team of teams) {
59
+ const rect = frames.get(team.name)
60
+ if (rect === undefined) continue
61
+ const x1 = rect.x + rect.w / 2
62
+ const y1 = rect.y + FRAME_HEAD_LIFT
63
+ for (const member of team.members) {
64
+ const pos = positions.get(member.id)
65
+ if (pos === undefined) continue
66
+ const dashed = seen.has(member.id)
67
+ seen.add(member.id)
68
+ out.push({ x1, y1, x2: pos.x, y2: pos.y - NODE_H / 2, dashed, team: team.name, id: member.id })
69
+ }
70
+ }
71
+ return out
72
+ }