@nelsonlongxiang/dsh-open-a2a-net 0.6.41 → 0.6.42

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 (61) hide show
  1. package/README.md +1 -1
  2. package/README_zh.md +1 -1
  3. package/assets/stageDist/assets/index-Bj7S2pA1.css +1 -1
  4. package/assets/stageDist/assets/index-C-28k_o5.js +974 -974
  5. package/assets/stageDist/index.html +12 -12
  6. package/lib/client.js +64 -64
  7. package/lib/client.js.map +1 -1
  8. package/lib/index.js +57 -9
  9. package/lib/origin-claim.js +4 -0
  10. package/lib/skill/SKILL.md +117 -117
  11. package/lib/skill/references/collaboration.md +428 -428
  12. package/lib/skill/references/discovery.md +78 -78
  13. package/lib/skill/references/intake.md +107 -107
  14. package/lib/skill/references/membership.md +73 -73
  15. package/lib/skill/references/pitfalls.md +51 -51
  16. package/lib/skill/references/router.md +96 -96
  17. package/lib/skill/references/routing.md +65 -65
  18. package/lib/types/origin-claim.d.ts +2 -0
  19. package/nexus-stage/package.json +21 -21
  20. package/nexus-stage/scripts/mockhost.py +261 -261
  21. package/nexus-stage/src/canvas-ops.ts +191 -191
  22. package/nexus-stage/src/canvas-wire.ts +164 -164
  23. package/nexus-stage/src/cli-home-picker.ts +42 -42
  24. package/nexus-stage/src/fault.ts +56 -56
  25. package/nexus-stage/src/federation-overlay.ts +103 -103
  26. package/nexus-stage/src/federation.ts +241 -241
  27. package/nexus-stage/src/layout-doc.ts +145 -145
  28. package/nexus-stage/src/main.ts +513 -513
  29. package/nexus-stage/src/planning-card-text.ts +73 -73
  30. package/nexus-stage/src/planning-card.ts +63 -63
  31. package/nexus-stage/src/planning-edges.ts +26 -26
  32. package/nexus-stage/src/planning-fit.ts +45 -45
  33. package/nexus-stage/src/planning-frame-hue.ts +13 -13
  34. package/nexus-stage/src/planning-legend.ts +39 -39
  35. package/nexus-stage/src/planning-member-handles.ts +61 -61
  36. package/nexus-stage/src/planning-menus.ts +422 -422
  37. package/nexus-stage/src/planning-mirror.ts +75 -75
  38. package/nexus-stage/src/planning-nodelist.ts +294 -294
  39. package/nexus-stage/src/planning-overlap.ts +159 -159
  40. package/nexus-stage/src/planning-reachability.ts +28 -28
  41. package/nexus-stage/src/planning-relayout.ts +121 -121
  42. package/nexus-stage/src/planning-roster.ts +15 -15
  43. package/nexus-stage/src/planning-search.ts +168 -168
  44. package/nexus-stage/src/planning-types.ts +115 -115
  45. package/nexus-stage/src/planning-undo.ts +68 -68
  46. package/nexus-stage/src/planning-view.ts +1499 -1499
  47. package/nexus-stage/src/planning.css +340 -340
  48. package/nexus-stage/src/seat-name-editor.ts +31 -31
  49. package/nexus-stage/src/session-drag.ts +64 -64
  50. package/nexus-stage/src/viewport.ts +106 -106
  51. package/nexus-stage/src/wb-chat.ts +684 -684
  52. package/nexus-stage/src/wb-directory.ts +237 -237
  53. package/nexus-stage/src/wb-ledger.ts +279 -279
  54. package/nexus-stage/src/wb-names.ts +43 -43
  55. package/nexus-stage/src/wb-rail.ts +147 -147
  56. package/nexus-stage/src/workbench.css +388 -388
  57. package/nexus-stage/src/workbench.ts +1272 -1272
  58. package/nexus-stage/src/world.ts +509 -509
  59. package/nexus-stage/tsconfig.json +24 -24
  60. package/nexus-stage/vite.config.ts +15 -15
  61. package/package.json +1 -1
@@ -1,191 +1,191 @@
1
- /**
2
- * Canvas action algebra for PR C (design.md §3.3/§3.4): the five user-facing
3
- * write actions as optimistic model mutations that each return a TEAM-SCOPED
4
- * undo. Scoped undos are the feedback loop's core - a whole-state revert
5
- * would clobber other queued actions' optimistic state; a per-team undo only
6
- * ever touches the team whose write failed, so queued actions on other teams
7
- * survive, and undo state (captured at apply time) stays consistent with the
8
- * per-team serial settle order.
9
- *
10
- * Pure: no DOM, no three, no fetch. The wire (canvas-wire.ts) owns transport;
11
- * the view owns calling applyAction before emitting and undo() on failure.
12
- * @module nexus-stage/canvas-ops
13
- */
14
-
15
- import { nodeRect, deriveInitialFrame, type WorldModel } from './world'
16
- import type { LayoutDoc } from './layout-doc'
17
-
18
- /** One pre-computed host op (remove = remove-member, add = add-member). */
19
- export type RosterOp = { op: 'remove' | 'add'; id: string }
20
-
21
- /** The five user-facing write actions (one per design.md §3.3 row), plus the two network join/leave actions the panel-slim migration moved into the planning view. */
22
- export type CanvasAction = (
23
- | { type: 'create-team'; name: string; ids: readonly string[]; /** true when the model had no such team before this action; NOT server creation provenance and never authority to delete on failure. */ created?: boolean }
24
- | { type: 'add-member'; team: string; ids: readonly string[]; /** gh#41: set when the target frame is a REMOTE peer's team — the wire routes the op through the host fleet relay. */ fleetPeer?: string }
25
- | { type: 'remove-member'; team: string; ids: readonly string[]; /** gh#41: see add-member. */ fleetPeer?: string }
26
- | { type: 'remove-team'; name: string; /** set when the frame is a REMOTE peer's team: the owner dissolves it, through the relay. */ fleetPeer?: string }
27
- /** `ops` comes from reorderOps() at emit time; the wire stays model-free. */
28
- | { type: 'reorder'; team: string; ops: ReadonlyArray<RosterOp> }
29
- /** Network-level join/leave (the session joins the A2A mesh) — distinct from canvas-team membership. */
30
- | { type: 'join-network'; id: string }
31
- | { type: 'leave-network'; id: string }
32
- ) & { roster?: boolean }
33
-
34
- /** Reverts exactly what one applyAction did (team-scoped). */
35
- export type Undo = () => void
36
-
37
- /** The team whose writes this action touches (queue key for the wire). */
38
- export function actionTeam(a: CanvasAction): string {
39
- // Network join/leave are session-scoped, not team-scoped: one shared
40
- // queue key keeps them serial against each other.
41
- if (a.type === 'join-network' || a.type === 'leave-network') return '$network'
42
- return a.type === 'create-team' || a.type === 'remove-team' ? a.name : a.team
43
- }
44
-
45
- /**
46
- * Apply one action's optimistic mutation and return its undo. The undo is
47
- * expressed as the INVERSE DELTA against the roster at undo time, never as
48
- * a wholesale restore of a captured prefix - a later queued action on the
49
- * same team may have applied optimistically before this action's write
50
- * fails, and rolling back must remove only this action's effect.
51
- */
52
- export function applyAction(model: WorldModel, a: CanvasAction): Undo {
53
- if (a.type === 'create-team') {
54
- const rects = a.ids
55
- .map(id => model.getNode(id))
56
- .filter(n => n !== undefined)
57
- .map(n => nodeRect(n))
58
- // Ids not on the canvas yet (a create emitted from the node list for an
59
- // unteamed session) resolve no rect: skip the optimistic frame so the
60
- // next poll derives it from the member's real seat instead of pinning a
61
- // 260x160 placeholder at the origin.
62
- if (rects.length > 0) model.setFrame(a.name, deriveInitialFrame(rects))
63
- model.setTeamMembers(a.name, a.ids)
64
- return () => {
65
- // Only the created ids (a later add to this new team must survive).
66
- model.setTeamMembers(a.name, model.teamMemberIds(a.name).filter(id => !a.ids.includes(id)))
67
- model.removeFrame(a.name)
68
- }
69
- }
70
- if (a.type === 'add-member') {
71
- const pre = model.teamMemberIds(a.team)
72
- const fresh = a.ids.filter(id => !pre.includes(id))
73
- model.setTeamMembers(a.team, [...pre, ...fresh])
74
- return () => {
75
- model.setTeamMembers(a.team, model.teamMemberIds(a.team).filter(id => !fresh.includes(id)))
76
- }
77
- }
78
- if (a.type === 'remove-member') {
79
- const pre = model.teamMemberIds(a.team)
80
- model.setTeamMembers(a.team, pre.filter(id => !a.ids.includes(id)))
81
- return () => {
82
- // Re-append the removed ids that nothing else has re-added since.
83
- const current = model.teamMemberIds(a.team)
84
- model.setTeamMembers(a.team, [...current, ...a.ids.filter(id => !current.includes(id))])
85
- }
86
- }
87
- if (a.type === 'remove-team') {
88
- const preIds = model.teamMemberIds(a.name)
89
- const frame = model.getFrame(a.name)
90
- const preRect = frame !== undefined ? { ...frame } : undefined
91
- model.setTeamMembers(a.name, [])
92
- model.removeFrame(a.name)
93
- return () => {
94
- if (preRect !== undefined) model.setFrame(a.name, preRect)
95
- model.setTeamMembers(a.name, preIds)
96
- }
97
- }
98
- // Network join/leave: no optimistic canvas mutation — the next inventory
99
- // poll absorbs the host's truth (the node appears/disappears on the
100
- // reconcile after the host accepts), so the undo is a no-op.
101
- if (a.type === 'join-network' || a.type === 'leave-network') {
102
- return () => {}
103
- }
104
- // reorder: apply the pre-computed ops to the current roster (remove
105
- // anywhere, append). The undo WALKS the current roster back to the
106
- // captured pre-order (same append-host discipline), so members a later
107
- // queued action added stay put.
108
- const pre = model.teamMemberIds(a.team)
109
- let roster = [...pre]
110
- for (const op of a.ops) {
111
- if (op.op === 'remove') roster = roster.filter(x => x !== op.id)
112
- else roster.push(op.id)
113
- }
114
- model.setTeamMembers(a.team, roster)
115
- return () => {
116
- let back = model.teamMemberIds(a.team)
117
- for (let i = 0; i < pre.length; i++) {
118
- const id = pre[i]!
119
- if (back[i] === id) continue
120
- if (!back.includes(id)) continue
121
- back = back.filter(x => x !== id)
122
- back.push(id)
123
- }
124
- model.setTeamMembers(a.team, back)
125
- }
126
- }
127
-
128
- /** Settle the optimistic delta and forget geometry only for a removed frame.
129
- * Never prune all saved frames: remote/offline teams may be absent this poll.
130
- * The caller schedules a correcting save even on rollback, so an earlier
131
- * in-flight layout POST cannot leave optimistic geometry persisted forever.
132
- */
133
- export function settleAction(model: WorldModel, a: CanvasAction, ok: boolean, undo: Undo, layout: LayoutDoc | undefined): LayoutDoc | undefined {
134
- if (!ok) undo()
135
- const removed = (a.type === 'remove-team' && ok) || (a.type === 'create-team' && !ok && a.created === true)
136
- if (!removed || layout === undefined || model.getFrame(actionTeam(a)) !== undefined) return layout
137
- const frames = { ...layout.frames }
138
- delete frames[actionTeam(a)]
139
- return { ...layout, frames }
140
- }
141
-
142
- /**
143
- * The minimal remove+add walk that turns `current` into `desired` on a
144
- * host whose add-member always APPENDS: walk desired positions in order;
145
- * whenever the working copy disagrees, remove+re-add the desired member
146
- * (host-side remove is positional, add appends, so the working copy mirrors
147
- * the host roster exactly). Bounded at 2*|desired| writes and converges;
148
- * an already-equal roster yields zero ops; desired ids absent from current
149
- * are skipped (adding members is never reorder's business).
150
- */
151
- export function reorderOps(
152
- current: ReadonlyArray<string>,
153
- desired: ReadonlyArray<string>,
154
- ): Array<{ op: 'remove' | 'add'; id: string }> {
155
- const working = [...current]
156
- const ops: Array<{ op: 'remove' | 'add'; id: string }> = []
157
- for (let i = 0; i < desired.length; i++) {
158
- const id = desired[i]!
159
- if (working[i] === id) continue
160
- if (!working.includes(id)) continue // not a member: not reorder's business
161
- ops.push({ op: 'remove', id })
162
- ops.push({ op: 'add', id })
163
- working.splice(working.indexOf(id), 1)
164
- working.push(id)
165
- }
166
- return ops
167
- }
168
-
169
- /** That team's members ordered by card-center y (ties: current priority). */
170
- export function yOrderedMembers(model: WorldModel, team: string): string[] {
171
- return model
172
- .teamMemberIds(team)
173
- .map(id => ({ id, y: model.getNode(id)?.y ?? 0 }))
174
- .sort((a, b) => a.y - b.y)
175
- .map(r => r.id)
176
- }
177
-
178
- /** Innermost (smallest-area) frame containing the world point, if any. */
179
- export function innermostFrameAt(
180
- frames: ReadonlyMap<string, { x: number; y: number; w: number; h: number }>,
181
- p: { x: number; y: number },
182
- ): string | undefined {
183
- let best: string | undefined
184
- let bestArea = Number.POSITIVE_INFINITY
185
- for (const [name, r] of frames) {
186
- if (p.x < r.x || p.x > r.x + r.w || p.y < r.y || p.y > r.y + r.h) continue
187
- const area = r.w * r.h
188
- if (area < bestArea) { best = name; bestArea = area }
189
- }
190
- return best
191
- }
1
+ /**
2
+ * Canvas action algebra for PR C (design.md §3.3/§3.4): the five user-facing
3
+ * write actions as optimistic model mutations that each return a TEAM-SCOPED
4
+ * undo. Scoped undos are the feedback loop's core - a whole-state revert
5
+ * would clobber other queued actions' optimistic state; a per-team undo only
6
+ * ever touches the team whose write failed, so queued actions on other teams
7
+ * survive, and undo state (captured at apply time) stays consistent with the
8
+ * per-team serial settle order.
9
+ *
10
+ * Pure: no DOM, no three, no fetch. The wire (canvas-wire.ts) owns transport;
11
+ * the view owns calling applyAction before emitting and undo() on failure.
12
+ * @module nexus-stage/canvas-ops
13
+ */
14
+
15
+ import { nodeRect, deriveInitialFrame, type WorldModel } from './world'
16
+ import type { LayoutDoc } from './layout-doc'
17
+
18
+ /** One pre-computed host op (remove = remove-member, add = add-member). */
19
+ export type RosterOp = { op: 'remove' | 'add'; id: string }
20
+
21
+ /** The five user-facing write actions (one per design.md §3.3 row), plus the two network join/leave actions the panel-slim migration moved into the planning view. */
22
+ export type CanvasAction = (
23
+ | { type: 'create-team'; name: string; ids: readonly string[]; /** true when the model had no such team before this action; NOT server creation provenance and never authority to delete on failure. */ created?: boolean }
24
+ | { type: 'add-member'; team: string; ids: readonly string[]; /** gh#41: set when the target frame is a REMOTE peer's team — the wire routes the op through the host fleet relay. */ fleetPeer?: string }
25
+ | { type: 'remove-member'; team: string; ids: readonly string[]; /** gh#41: see add-member. */ fleetPeer?: string }
26
+ | { type: 'remove-team'; name: string; /** set when the frame is a REMOTE peer's team: the owner dissolves it, through the relay. */ fleetPeer?: string }
27
+ /** `ops` comes from reorderOps() at emit time; the wire stays model-free. */
28
+ | { type: 'reorder'; team: string; ops: ReadonlyArray<RosterOp> }
29
+ /** Network-level join/leave (the session joins the A2A mesh) — distinct from canvas-team membership. */
30
+ | { type: 'join-network'; id: string }
31
+ | { type: 'leave-network'; id: string }
32
+ ) & { roster?: boolean }
33
+
34
+ /** Reverts exactly what one applyAction did (team-scoped). */
35
+ export type Undo = () => void
36
+
37
+ /** The team whose writes this action touches (queue key for the wire). */
38
+ export function actionTeam(a: CanvasAction): string {
39
+ // Network join/leave are session-scoped, not team-scoped: one shared
40
+ // queue key keeps them serial against each other.
41
+ if (a.type === 'join-network' || a.type === 'leave-network') return '$network'
42
+ return a.type === 'create-team' || a.type === 'remove-team' ? a.name : a.team
43
+ }
44
+
45
+ /**
46
+ * Apply one action's optimistic mutation and return its undo. The undo is
47
+ * expressed as the INVERSE DELTA against the roster at undo time, never as
48
+ * a wholesale restore of a captured prefix - a later queued action on the
49
+ * same team may have applied optimistically before this action's write
50
+ * fails, and rolling back must remove only this action's effect.
51
+ */
52
+ export function applyAction(model: WorldModel, a: CanvasAction): Undo {
53
+ if (a.type === 'create-team') {
54
+ const rects = a.ids
55
+ .map(id => model.getNode(id))
56
+ .filter(n => n !== undefined)
57
+ .map(n => nodeRect(n))
58
+ // Ids not on the canvas yet (a create emitted from the node list for an
59
+ // unteamed session) resolve no rect: skip the optimistic frame so the
60
+ // next poll derives it from the member's real seat instead of pinning a
61
+ // 260x160 placeholder at the origin.
62
+ if (rects.length > 0) model.setFrame(a.name, deriveInitialFrame(rects))
63
+ model.setTeamMembers(a.name, a.ids)
64
+ return () => {
65
+ // Only the created ids (a later add to this new team must survive).
66
+ model.setTeamMembers(a.name, model.teamMemberIds(a.name).filter(id => !a.ids.includes(id)))
67
+ model.removeFrame(a.name)
68
+ }
69
+ }
70
+ if (a.type === 'add-member') {
71
+ const pre = model.teamMemberIds(a.team)
72
+ const fresh = a.ids.filter(id => !pre.includes(id))
73
+ model.setTeamMembers(a.team, [...pre, ...fresh])
74
+ return () => {
75
+ model.setTeamMembers(a.team, model.teamMemberIds(a.team).filter(id => !fresh.includes(id)))
76
+ }
77
+ }
78
+ if (a.type === 'remove-member') {
79
+ const pre = model.teamMemberIds(a.team)
80
+ model.setTeamMembers(a.team, pre.filter(id => !a.ids.includes(id)))
81
+ return () => {
82
+ // Re-append the removed ids that nothing else has re-added since.
83
+ const current = model.teamMemberIds(a.team)
84
+ model.setTeamMembers(a.team, [...current, ...a.ids.filter(id => !current.includes(id))])
85
+ }
86
+ }
87
+ if (a.type === 'remove-team') {
88
+ const preIds = model.teamMemberIds(a.name)
89
+ const frame = model.getFrame(a.name)
90
+ const preRect = frame !== undefined ? { ...frame } : undefined
91
+ model.setTeamMembers(a.name, [])
92
+ model.removeFrame(a.name)
93
+ return () => {
94
+ if (preRect !== undefined) model.setFrame(a.name, preRect)
95
+ model.setTeamMembers(a.name, preIds)
96
+ }
97
+ }
98
+ // Network join/leave: no optimistic canvas mutation — the next inventory
99
+ // poll absorbs the host's truth (the node appears/disappears on the
100
+ // reconcile after the host accepts), so the undo is a no-op.
101
+ if (a.type === 'join-network' || a.type === 'leave-network') {
102
+ return () => {}
103
+ }
104
+ // reorder: apply the pre-computed ops to the current roster (remove
105
+ // anywhere, append). The undo WALKS the current roster back to the
106
+ // captured pre-order (same append-host discipline), so members a later
107
+ // queued action added stay put.
108
+ const pre = model.teamMemberIds(a.team)
109
+ let roster = [...pre]
110
+ for (const op of a.ops) {
111
+ if (op.op === 'remove') roster = roster.filter(x => x !== op.id)
112
+ else roster.push(op.id)
113
+ }
114
+ model.setTeamMembers(a.team, roster)
115
+ return () => {
116
+ let back = model.teamMemberIds(a.team)
117
+ for (let i = 0; i < pre.length; i++) {
118
+ const id = pre[i]!
119
+ if (back[i] === id) continue
120
+ if (!back.includes(id)) continue
121
+ back = back.filter(x => x !== id)
122
+ back.push(id)
123
+ }
124
+ model.setTeamMembers(a.team, back)
125
+ }
126
+ }
127
+
128
+ /** Settle the optimistic delta and forget geometry only for a removed frame.
129
+ * Never prune all saved frames: remote/offline teams may be absent this poll.
130
+ * The caller schedules a correcting save even on rollback, so an earlier
131
+ * in-flight layout POST cannot leave optimistic geometry persisted forever.
132
+ */
133
+ export function settleAction(model: WorldModel, a: CanvasAction, ok: boolean, undo: Undo, layout: LayoutDoc | undefined): LayoutDoc | undefined {
134
+ if (!ok) undo()
135
+ const removed = (a.type === 'remove-team' && ok) || (a.type === 'create-team' && !ok && a.created === true)
136
+ if (!removed || layout === undefined || model.getFrame(actionTeam(a)) !== undefined) return layout
137
+ const frames = { ...layout.frames }
138
+ delete frames[actionTeam(a)]
139
+ return { ...layout, frames }
140
+ }
141
+
142
+ /**
143
+ * The minimal remove+add walk that turns `current` into `desired` on a
144
+ * host whose add-member always APPENDS: walk desired positions in order;
145
+ * whenever the working copy disagrees, remove+re-add the desired member
146
+ * (host-side remove is positional, add appends, so the working copy mirrors
147
+ * the host roster exactly). Bounded at 2*|desired| writes and converges;
148
+ * an already-equal roster yields zero ops; desired ids absent from current
149
+ * are skipped (adding members is never reorder's business).
150
+ */
151
+ export function reorderOps(
152
+ current: ReadonlyArray<string>,
153
+ desired: ReadonlyArray<string>,
154
+ ): Array<{ op: 'remove' | 'add'; id: string }> {
155
+ const working = [...current]
156
+ const ops: Array<{ op: 'remove' | 'add'; id: string }> = []
157
+ for (let i = 0; i < desired.length; i++) {
158
+ const id = desired[i]!
159
+ if (working[i] === id) continue
160
+ if (!working.includes(id)) continue // not a member: not reorder's business
161
+ ops.push({ op: 'remove', id })
162
+ ops.push({ op: 'add', id })
163
+ working.splice(working.indexOf(id), 1)
164
+ working.push(id)
165
+ }
166
+ return ops
167
+ }
168
+
169
+ /** That team's members ordered by card-center y (ties: current priority). */
170
+ export function yOrderedMembers(model: WorldModel, team: string): string[] {
171
+ return model
172
+ .teamMemberIds(team)
173
+ .map(id => ({ id, y: model.getNode(id)?.y ?? 0 }))
174
+ .sort((a, b) => a.y - b.y)
175
+ .map(r => r.id)
176
+ }
177
+
178
+ /** Innermost (smallest-area) frame containing the world point, if any. */
179
+ export function innermostFrameAt(
180
+ frames: ReadonlyMap<string, { x: number; y: number; w: number; h: number }>,
181
+ p: { x: number; y: number },
182
+ ): string | undefined {
183
+ let best: string | undefined
184
+ let bestArea = Number.POSITIVE_INFINITY
185
+ for (const [name, r] of frames) {
186
+ if (p.x < r.x || p.x > r.x + r.w || p.y < r.y || p.y > r.y + r.h) continue
187
+ const area = r.w * r.h
188
+ if (area < bestArea) { best = name; bestArea = area }
189
+ }
190
+ return best
191
+ }