pilotswarm 0.5.57 → 0.5.59

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 (93) hide show
  1. package/mcp/dist/src/context.d.ts +2 -0
  2. package/mcp/dist/src/context.d.ts.map +1 -1
  3. package/mcp/dist/src/context.js +3 -1
  4. package/mcp/dist/src/context.js.map +1 -1
  5. package/mcp/dist/src/tools/agent-packages.js +1 -1
  6. package/mcp/dist/src/tools/agent-packages.js.map +1 -1
  7. package/mcp/dist/src/tools/capabilities.d.ts +4 -0
  8. package/mcp/dist/src/tools/capabilities.d.ts.map +1 -1
  9. package/mcp/dist/src/tools/capabilities.js +4 -0
  10. package/mcp/dist/src/tools/capabilities.js.map +1 -1
  11. package/package.json +2 -2
  12. package/tui/src/node-sdk-transport.js +5 -0
  13. package/ui/core/src/controller.js +136 -11
  14. package/ui/core/src/formatting.js +18 -9
  15. package/ui/core/src/history.js +162 -9
  16. package/ui/core/src/reducer.js +32 -5
  17. package/ui/core/src/selectors.js +160 -16
  18. package/ui/core/src/session-errors.js +16 -11
  19. package/ui/react/src/components.js +1 -0
  20. package/ui/react/src/web-app.js +230 -80
  21. package/web/api/live-plane.js +193 -0
  22. package/web/api/router.js +1 -1
  23. package/web/api/ws.js +160 -4
  24. package/web/authz.js +6 -1
  25. package/web/dist/assets/{abnfDiagram-VRR7QNED-CJOR2hPi.js → abnfDiagram-VRR7QNED-D9Rv2VpV.js} +1 -1
  26. package/web/dist/assets/{arc-Yh3kc_KB.js → arc-BATiucnq.js} +1 -1
  27. package/web/dist/assets/{architectureDiagram-ZJ3FMSHR-DLRlUeyk.js → architectureDiagram-ZJ3FMSHR-CSTMt0si.js} +1 -1
  28. package/web/dist/assets/{blockDiagram-677ZJIJ3-x_TGH_fp.js → blockDiagram-677ZJIJ3-C_RGqfeY.js} +1 -1
  29. package/web/dist/assets/{c4Diagram-LMCZKHZV-C-j_bxeF.js → c4Diagram-LMCZKHZV-DH__s__0.js} +1 -1
  30. package/web/dist/assets/channel-CqgY9oZh.js +1 -0
  31. package/web/dist/assets/{chunk-2Q5K7J3B-CaI5Xqwo.js → chunk-2Q5K7J3B-C197fD3B.js} +1 -1
  32. package/web/dist/assets/{chunk-32BRIVSS-Bh_Crac6.js → chunk-32BRIVSS-BjpuBroU.js} +1 -1
  33. package/web/dist/assets/{chunk-5VM5RSS4-XM-3ajKG.js → chunk-5VM5RSS4-W_jL8euj.js} +1 -1
  34. package/web/dist/assets/{chunk-EX3LRPZG-ZwkulfFp.js → chunk-EX3LRPZG-a9h0dtlp.js} +1 -1
  35. package/web/dist/assets/{chunk-JWPE2WC7-CT3Q7T2A.js → chunk-JWPE2WC7-CQN0qE7F.js} +1 -1
  36. package/web/dist/assets/{chunk-MOJQB5TN-_Uc0ksg3.js → chunk-MOJQB5TN-D-Y6KQ7t.js} +1 -1
  37. package/web/dist/assets/{chunk-RYQCIY6F-DosoId7u.js → chunk-RYQCIY6F-jPwseC5f.js} +1 -1
  38. package/web/dist/assets/{chunk-V7JOEXUC-KRjq1cwM.js → chunk-V7JOEXUC-DRNCEcQh.js} +1 -1
  39. package/web/dist/assets/{chunk-VR4S4FIN-B6Ul1z3S.js → chunk-VR4S4FIN-DzhUcoZ7.js} +1 -1
  40. package/web/dist/assets/{chunk-XXDRQBXY-AikdVXm4.js → chunk-XXDRQBXY-ldJfyPXK.js} +1 -1
  41. package/web/dist/assets/classDiagram-OUVF2IWQ-Uz4DYZs2.js +1 -0
  42. package/web/dist/assets/classDiagram-v2-EOCWNBFH-Uz4DYZs2.js +1 -0
  43. package/web/dist/assets/{cose-bilkent-JH36ORCC-DTYwgEAa.js → cose-bilkent-JH36ORCC-BBVo21wR.js} +1 -1
  44. package/web/dist/assets/{cynefin-VYW2F7L2-C_8v2wIz.js → cynefin-VYW2F7L2-CgRQfdW1.js} +1 -1
  45. package/web/dist/assets/{cynefinDiagram-TSTJHNR4-1-nw1zbQ.js → cynefinDiagram-TSTJHNR4-Ckz2X0t3.js} +1 -1
  46. package/web/dist/assets/{dagre-VKFMJZFB-CAjwRpvT.js → dagre-VKFMJZFB-C1SanJiY.js} +1 -1
  47. package/web/dist/assets/{diagram-FQU43EPY-Bg41nQw_.js → diagram-FQU43EPY-dBdEifVy.js} +1 -1
  48. package/web/dist/assets/{diagram-G47NLZAW-Cp6P-qMn.js → diagram-G47NLZAW-Byqc1VCB.js} +1 -1
  49. package/web/dist/assets/{diagram-NH7WQ7WH-DNdFuedg.js → diagram-NH7WQ7WH-9aeTaCsX.js} +1 -1
  50. package/web/dist/assets/{diagram-OA4YK3LP-DcHpELG2.js → diagram-OA4YK3LP-CL0a0o0k.js} +1 -1
  51. package/web/dist/assets/{diagram-WEI45ONY-B2_kyMw3.js → diagram-WEI45ONY-YlMQOJfY.js} +1 -1
  52. package/web/dist/assets/{ebnfDiagram-CCIWWBDH-DztuAo3o.js → ebnfDiagram-CCIWWBDH-qMZQEnws.js} +1 -1
  53. package/web/dist/assets/{erDiagram-Q63AITRT-DNjKTOzp.js → erDiagram-Q63AITRT-C3CWBCtq.js} +1 -1
  54. package/web/dist/assets/{flowDiagram-23GEKE2U-DWjfeUM1.js → flowDiagram-23GEKE2U-BN79sybL.js} +1 -1
  55. package/web/dist/assets/{ganttDiagram-NO4QXBWP-Y0MgGVmy.js → ganttDiagram-NO4QXBWP-BOWhiAgB.js} +1 -1
  56. package/web/dist/assets/{gitGraphDiagram-IHSO6WYX--lDKAKVz.js → gitGraphDiagram-IHSO6WYX-CKX9OACp.js} +1 -1
  57. package/web/dist/assets/index-B4s2lTsK.js +24 -0
  58. package/web/dist/assets/{index-BxPfp0AO.css → index-DhGdE2Nd.css} +1 -1
  59. package/web/dist/assets/{infoDiagram-FWYZ7A6U-CKmH8_YO.js → infoDiagram-FWYZ7A6U-QQEunPNs.js} +1 -1
  60. package/web/dist/assets/{ishikawaDiagram-FXEZZL3T-D88R7zUc.js → ishikawaDiagram-FXEZZL3T-CvMwrnLQ.js} +1 -1
  61. package/web/dist/assets/{journeyDiagram-5HDEW3XC-Ck7EMrsY.js → journeyDiagram-5HDEW3XC-giNpEVpj.js} +1 -1
  62. package/web/dist/assets/{kanban-definition-HUTT4EX6-hc-mOfSG.js → kanban-definition-HUTT4EX6-CUPPpo27.js} +1 -1
  63. package/web/dist/assets/{linear-BVhU4csP.js → linear-AXqadfOR.js} +1 -1
  64. package/web/dist/assets/{mermaid.core-C9u4FOCX.js → mermaid.core-C78wdk8Y.js} +4 -4
  65. package/web/dist/assets/{mindmap-definition-LN4V7U3C-DatQu9Xp.js → mindmap-definition-LN4V7U3C-BGUmh9bK.js} +1 -1
  66. package/web/dist/assets/{papaparse.min-BdnoXc8M.js → papaparse.min-Bh8RLCk_.js} +1 -1
  67. package/web/dist/assets/{pegDiagram-2B236MQR-Cr_nZ_jR.js → pegDiagram-2B236MQR-BHsKXKhK.js} +1 -1
  68. package/web/dist/assets/{pieDiagram-ENE6RG2P-CcAbSIcY.js → pieDiagram-ENE6RG2P-D3EvWTdH.js} +1 -1
  69. package/web/dist/assets/pilotswarm-UV6T1_QI.js +98 -0
  70. package/web/dist/assets/{quadrantDiagram-ABIIQ3AL-CgJw5laM.js → quadrantDiagram-ABIIQ3AL-CbkxfLkV.js} +1 -1
  71. package/web/dist/assets/{railroadDiagram-RFXS5EU6-CHeun9SN.js → railroadDiagram-RFXS5EU6-H4M6JSDW.js} +1 -1
  72. package/web/dist/assets/{requirementDiagram-TGXJPOKE-D1kpDj8W.js → requirementDiagram-TGXJPOKE-BJo7JW1v.js} +1 -1
  73. package/web/dist/assets/{sankeyDiagram-HTMAVEWB-C5vUSUaX.js → sankeyDiagram-HTMAVEWB-DL19MeOP.js} +1 -1
  74. package/web/dist/assets/{sequenceDiagram-DBY2YBRQ-T1aPSxk1.js → sequenceDiagram-DBY2YBRQ-DyVFc5pQ.js} +1 -1
  75. package/web/dist/assets/{sizeCapture-X5ZJPWSS-j-wh2LVX.js → sizeCapture-X5ZJPWSS-CKbcKIEQ.js} +1 -1
  76. package/web/dist/assets/{stateDiagram-2N3HPSRC-DfUZZR8N.js → stateDiagram-2N3HPSRC-BSukSXzS.js} +1 -1
  77. package/web/dist/assets/stateDiagram-v2-6OUMAXLB-BhKG3pJB.js +1 -0
  78. package/web/dist/assets/{swimlanes-5IMT3BWC-OpASJiRw.js → swimlanes-5IMT3BWC-BUd7UhsF.js} +2 -2
  79. package/web/dist/assets/swimlanesDiagram-G3AALYLV-CgoFkXJE.js +8 -0
  80. package/web/dist/assets/{timeline-definition-FHXFAJF6-BBnc_z1U.js → timeline-definition-FHXFAJF6-Dt1gludn.js} +1 -1
  81. package/web/dist/assets/{vennDiagram-L72KCM5P-BlPQe9p4.js → vennDiagram-L72KCM5P-BHxt00GV.js} +1 -1
  82. package/web/dist/assets/{wardleyDiagram-EHGQE667-DBcBFUx2.js → wardleyDiagram-EHGQE667-BT3fIDbe.js} +1 -1
  83. package/web/dist/assets/{xychartDiagram-FW5EYKEG-BQUSGL6g.js → xychartDiagram-FW5EYKEG-ChoYDS2b.js} +1 -1
  84. package/web/dist/index.html +3 -3
  85. package/web/runtime.js +89 -44
  86. package/web/server.js +9 -0
  87. package/web/dist/assets/channel-BIn92AzB.js +0 -1
  88. package/web/dist/assets/classDiagram-OUVF2IWQ-B7ktW6XM.js +0 -1
  89. package/web/dist/assets/classDiagram-v2-EOCWNBFH-B7ktW6XM.js +0 -1
  90. package/web/dist/assets/index-rPji8Uud.js +0 -24
  91. package/web/dist/assets/pilotswarm--kvfB_6q.js +0 -99
  92. package/web/dist/assets/stateDiagram-v2-6OUMAXLB-ilKtTdHG.js +0 -1
  93. package/web/dist/assets/swimlanesDiagram-G3AALYLV-C1PthnB4.js +0 -8
@@ -0,0 +1,193 @@
1
+ import pg from "pg";
2
+
3
+ /**
4
+ * Generic live-plane relay: one LISTEN connection per portal process, fanned
5
+ * out by (sessionId, topic). Large notifications are pointers; getLive reads
6
+ * the retained row before delivery.
7
+ */
8
+ export function createLivePlane({
9
+ connectionString = process.env.DATABASE_URL,
10
+ schema = process.env.PILOTSWARM_CMS_SCHEMA || "copilot_sessions",
11
+ channel = "pilotswarm_live",
12
+ getLive = null,
13
+ createClient = (options) => new pg.Client(options),
14
+ } = {}) {
15
+ if (!/^[a-z_][a-z0-9_]{0,62}$/i.test(channel)) {
16
+ throw new Error("Invalid live-plane channel");
17
+ }
18
+ const subscribers = new Map();
19
+ let client = null;
20
+ let started = false;
21
+ let stopped = false;
22
+ let probeTimer = null;
23
+ let reconnectTimer = null;
24
+ let reconnectDelay = 1_000;
25
+ let connecting = false;
26
+ let epoch = 0;
27
+ const deliveredSeq = new Map();
28
+ const pointerReads = new Map();
29
+ const available = Boolean(connectionString);
30
+
31
+ const keyFor = (sessionId, topic) => `${sessionId}\u0000${topic}`;
32
+
33
+ async function deliver(payload) {
34
+ if (!payload?.sessionId || !payload?.topic) return;
35
+ if (payload.schema !== schema || stopped) return;
36
+ const key = keyFor(payload.sessionId, payload.topic);
37
+ const handlers = subscribers.get(key);
38
+ if (!handlers || handlers.size === 0) return;
39
+
40
+ let update = {
41
+ sessionId: String(payload.sessionId),
42
+ topic: String(payload.topic),
43
+ seq: payload.seq == null ? null : Number(payload.seq),
44
+ kind: payload.kind === "signal" ? "signal" : payload.kind === "patch" ? "patch" : "snapshot",
45
+ ...(payload.data && typeof payload.data === "object" ? { data: payload.data } : {}),
46
+ ...(payload.updatedAt ? { updatedAt: payload.updatedAt } : {}),
47
+ };
48
+ if (update.kind !== "signal" && update.data === undefined) {
49
+ if (typeof getLive !== "function") return;
50
+ if (pointerReads.has(key)) {
51
+ pointerReads.get(key).latest = payload;
52
+ return;
53
+ }
54
+ const read = { latest: payload };
55
+ pointerReads.set(key, read);
56
+ const readEpoch = epoch;
57
+ try {
58
+ let requested;
59
+ do {
60
+ requested = read.latest;
61
+ const rows = await getLive(update.sessionId, [update.topic]);
62
+ if (stopped || epoch !== readEpoch || subscribers.get(key) !== handlers) return;
63
+ const row = (rows || []).find((candidate) => candidate?.topic === update.topic);
64
+ if (row) await deliver({ ...payload, seq: Number(row.seq), kind: "snapshot", data: row.payload || {}, updatedAt: row.updatedAt });
65
+ } while (requested !== read.latest);
66
+ } catch {
67
+ // Ephemeral reads are best effort; durable replay is unchanged.
68
+ } finally {
69
+ if (pointerReads.get(key) === read) pointerReads.delete(key);
70
+ }
71
+ return;
72
+ }
73
+ if (update.kind !== "signal") {
74
+ if (!Number.isSafeInteger(update.seq) || update.seq <= (deliveredSeq.get(key) || 0)) return;
75
+ deliveredSeq.set(key, update.seq);
76
+ }
77
+ for (const callback of [...handlers]) {
78
+ try { callback(update); } catch { /* isolate sockets */ }
79
+ }
80
+ }
81
+
82
+ async function connect() {
83
+ if (stopped || !available || client || connecting) return;
84
+ connecting = true;
85
+ reconnectTimer = null;
86
+ const next = createClient({ connectionString, keepAlive: true, connectionTimeoutMillis: 5_000, query_timeout: 5_000 });
87
+ // Error listeners must exist while connect/LISTEN are still pending.
88
+ const onGone = () => {
89
+ if (client === next) {
90
+ client = null;
91
+ void next.end().catch(() => {});
92
+ }
93
+ scheduleReconnect();
94
+ };
95
+ next.on("error", onGone);
96
+ next.on("end", onGone);
97
+ try {
98
+ await next.connect();
99
+ await next.query(`LISTEN ${channel}`);
100
+ if (stopped) {
101
+ await next.end();
102
+ return;
103
+ }
104
+ } catch {
105
+ try { await next.end(); } catch {}
106
+ scheduleReconnect();
107
+ return;
108
+ } finally {
109
+ connecting = false;
110
+ }
111
+ client = next;
112
+ const isReconnect = epoch++ > 0;
113
+ deliveredSeq.clear();
114
+ pointerReads.clear();
115
+ reconnectDelay = 1_000;
116
+ next.on("notification", (message) => {
117
+ let payload;
118
+ try { payload = JSON.parse(message.payload || ""); } catch { return; }
119
+ void deliver(payload);
120
+ });
121
+ if (isReconnect) {
122
+ for (const [key, handlers] of subscribers) {
123
+ const [sessionId, topic] = key.split("\u0000");
124
+ // Release stale previews even when an UNLOGGED table was
125
+ // emptied by restart, then refresh events missed by LISTEN.
126
+ for (const callback of handlers) {
127
+ try { callback({ sessionId, topic, kind: "unavailable" }); } catch {}
128
+ }
129
+ void deliver({ schema, sessionId, topic, kind: "snapshot" });
130
+ }
131
+ }
132
+ }
133
+
134
+ function scheduleReconnect() {
135
+ if (stopped || reconnectTimer) return;
136
+ const delay = reconnectDelay;
137
+ reconnectDelay = Math.min(reconnectDelay * 2, 15_000);
138
+ reconnectTimer = setTimeout(() => { void connect(); }, delay);
139
+ reconnectTimer.unref?.();
140
+ }
141
+
142
+ return {
143
+ available,
144
+ async start() {
145
+ if (!available || stopped || started) return;
146
+ started = true;
147
+ await connect();
148
+ probeTimer = setInterval(() => {
149
+ const current = client;
150
+ if (current) current.query("SELECT 1").catch(() => {
151
+ if (client !== current) return;
152
+ client = null;
153
+ void current.end().catch(() => {});
154
+ scheduleReconnect();
155
+ });
156
+ }, 30_000);
157
+ probeTimer.unref?.();
158
+ },
159
+ subscribe(sessionId, topics, callback) {
160
+ const cleanSessionId = String(sessionId || "").trim();
161
+ const cleanTopics = [...new Set((topics || []).map((topic) => String(topic || "").trim()))];
162
+ if (!cleanSessionId || cleanTopics.length === 0) return () => {};
163
+ for (const topic of cleanTopics) {
164
+ const key = keyFor(cleanSessionId, topic);
165
+ if (!subscribers.has(key)) subscribers.set(key, new Set());
166
+ subscribers.get(key).add(callback);
167
+ }
168
+ return () => {
169
+ for (const topic of cleanTopics) {
170
+ const key = keyFor(cleanSessionId, topic);
171
+ const handlers = subscribers.get(key);
172
+ if (!handlers) continue;
173
+ handlers.delete(callback);
174
+ if (handlers.size === 0) {
175
+ subscribers.delete(key);
176
+ deliveredSeq.delete(key);
177
+ pointerReads.delete(key);
178
+ }
179
+ }
180
+ };
181
+ },
182
+ async stop() {
183
+ stopped = true;
184
+ if (probeTimer) clearInterval(probeTimer);
185
+ if (reconnectTimer) clearTimeout(reconnectTimer);
186
+ subscribers.clear();
187
+ if (client) {
188
+ try { await client.end(); } catch {}
189
+ client = null;
190
+ }
191
+ },
192
+ };
193
+ }
package/web/api/router.js CHANGED
@@ -136,7 +136,7 @@ export function createApiRouter({ runtime, requireAuth }) {
136
136
 
137
137
  // ── Public routes ───────────────────────────────────────────────────
138
138
  router.get("/health", (_req, res) => {
139
- res.json({ ok: true, started: runtime.started, mode: runtime.mode, apiVersion: API_VERSION });
139
+ res.json({ ok: true, started: runtime.started, mode: runtime.mode, apiVersion: API_VERSION, authz: runtime.getAuthorizationPolicy?.() });
140
140
  });
141
141
 
142
142
  router.get("/auth/config", async (req, res) => {
package/web/api/ws.js CHANGED
@@ -7,8 +7,11 @@ import { authenticateToken, extractToken } from "../auth.js";
7
7
  * One connection handler serves both `/api/v1/ws` (the product API) and the
8
8
  * legacy `/portal-ws` (which additionally answers the portal-only `theme`
9
9
  * message). Vocabulary:
10
- * client -> server: subscribeSession | unsubscribeSession | subscribeLogs | unsubscribeLogs
11
- * server -> client: ready | subscribedSession | sessionEvent | subscribedLogs | logEntry | error
10
+ * client -> server: subscribeSession | unsubscribeSession |
11
+ * subscribeLive | unsubscribeLive |
12
+ * subscribeLogs | unsubscribeLogs
13
+ * server -> client: ready | subscribedSession | sessionEvent |
14
+ * subscribedLive | live | subscribedLogs | logEntry | error
12
15
  *
13
16
  * Delivery here is an acceleration path — correctness comes from event
14
17
  * replay via GET /api/v1/management/sessions/:id/events?afterSeq=… after a
@@ -83,13 +86,48 @@ export function createConnectionHandler(runtime, { allowThemeMessages = false }
83
86
 
84
87
  const sessionSubscriptions = new Map();
85
88
  const canvasSubscriptions = new Map();
89
+ const liveSubscriptions = new Map();
90
+ const liveSubscriptionGeneration = new Map();
86
91
  let logUnsubscribe = null;
92
+ let accessCheckPending = false;
93
+ // An open socket must not preserve a revoked grant indefinitely.
94
+ // One bounded check per subscribed session per interval, not per delta;
95
+ // session/live/canvas subscriptions share the same check.
96
+ const accessRevalidateTimer = !shareScope && runtime.authz?.enforce
97
+ ? setInterval(async () => {
98
+ if (accessCheckPending || ws.readyState !== ws.OPEN) return;
99
+ accessCheckPending = true;
100
+ try {
101
+ const ids = new Set([...sessionSubscriptions.keys(), ...canvasSubscriptions.keys(), ...liveSubscriptions.keys()]);
102
+ for (const sessionId of ids) {
103
+ try {
104
+ await runtime.authorizeSessionSubscribe(sessionId, auth);
105
+ } catch {
106
+ sessionSubscriptions.get(sessionId)?.();
107
+ sessionSubscriptions.delete(sessionId);
108
+ canvasSubscriptions.get(sessionId)?.();
109
+ canvasSubscriptions.delete(sessionId);
110
+ liveSubscriptions.get(sessionId)?.unsubscribe();
111
+ liveSubscriptions.delete(sessionId);
112
+ liveSubscriptionGeneration.set(sessionId, (liveSubscriptionGeneration.get(sessionId) || 0) + 1);
113
+ send({ type: "error", scope: "session", sessionId, code: "ACCESS_REVOKED", error: "Session not found." });
114
+ }
115
+ }
116
+ } finally { accessCheckPending = false; }
117
+ }, Math.max(1, Number(process.env.PILOTSWARM_SESSION_REVALIDATE_MS) || 5_000))
118
+ : null;
119
+ accessRevalidateTimer?.unref?.();
87
120
 
88
121
  const send = (message) => {
89
122
  if (ws.readyState === ws.OPEN) {
90
123
  ws.send(JSON.stringify(message));
91
124
  }
92
125
  };
126
+ const sendSubscriptionError = (scope, sessionId, error) => {
127
+ const denied = [403, 404].includes(error?.status) || ["FORBIDDEN", "NOT_FOUND"].includes(error?.code);
128
+ send({ type: "error", scope, sessionId, ...(denied ? { code: "ACCESS_REVOKED" } : {}),
129
+ error: denied ? "Session not found." : error?.message || String(error) });
130
+ };
93
131
 
94
132
  send({ type: "ready" });
95
133
 
@@ -111,6 +149,12 @@ export function createConnectionHandler(runtime, { allowThemeMessages = false }
111
149
  }
112
150
 
113
151
  const type = String(message?.type || "");
152
+ if (!shareScope && type.startsWith("subscribe")
153
+ && new Set([...sessionSubscriptions.keys(), ...canvasSubscriptions.keys(), ...liveSubscriptions.keys()]).size >= 64
154
+ && !sessionSubscriptions.has(message.sessionId) && !canvasSubscriptions.has(message.sessionId) && !liveSubscriptions.has(message.sessionId)) {
155
+ send({ type: "error", scope: "session", error: "Too many subscriptions." });
156
+ return;
157
+ }
114
158
  if (shareScope) {
115
159
  // The whole share-scope protocol: subscribe to THE canvas the
116
160
  // token names. No session events, no logs, no other sessions.
@@ -162,7 +206,7 @@ export function createConnectionHandler(runtime, { allowThemeMessages = false }
162
206
  sessionSubscriptions.set(sessionId, unsubscribe);
163
207
  send({ type: "subscribedSession", sessionId });
164
208
  } catch (error) {
165
- send({ type: "error", scope: "session", sessionId, error: error?.message || String(error) });
209
+ sendSubscriptionError("session", sessionId, error);
166
210
  }
167
211
  return;
168
212
  }
@@ -198,7 +242,7 @@ export function createConnectionHandler(runtime, { allowThemeMessages = false }
198
242
  canvasSubscriptions.set(sessionId, unsubscribe);
199
243
  send({ type: "subscribedCanvas", sessionId });
200
244
  } catch (error) {
201
- send({ type: "error", scope: "canvas", sessionId, error: error?.message || String(error) });
245
+ sendSubscriptionError("canvas", sessionId, error);
202
246
  }
203
247
  return;
204
248
  }
@@ -213,6 +257,112 @@ export function createConnectionHandler(runtime, { allowThemeMessages = false }
213
257
  return;
214
258
  }
215
259
 
260
+ if (type === "subscribeLive") {
261
+ const sessionId = String(message?.sessionId || "").trim();
262
+ const topics = [...new Set(Array.isArray(message?.topics)
263
+ ? message.topics.map((topic) => String(topic || "").trim())
264
+ : [])];
265
+ if (!sessionId || topics.length === 0 || topics.length > 16
266
+ || topics.some((topic) => !/^[a-z][a-z0-9_.:-]{0,63}$/.test(topic))) {
267
+ send({ type: "error", scope: "live", sessionId, error: "invalid live subscription" });
268
+ return;
269
+ }
270
+ const generation = (liveSubscriptionGeneration.get(sessionId) || 0) + 1;
271
+ liveSubscriptionGeneration.set(sessionId, generation);
272
+ const previous = liveSubscriptions.get(sessionId);
273
+ if (previous) {
274
+ previous.unsubscribe();
275
+ liveSubscriptions.delete(sessionId);
276
+ }
277
+ const plane = runtime.livePlane;
278
+ if (!plane?.available) {
279
+ send({ type: "error", scope: "live", sessionId, error: "live plane unavailable" });
280
+ return;
281
+ }
282
+ try {
283
+ await runtime.start();
284
+ if (typeof runtime.authorizeSessionSubscribe === "function") {
285
+ await runtime.authorizeSessionSubscribe(sessionId, auth);
286
+ }
287
+ if (liveSubscriptionGeneration.get(sessionId) !== generation) return;
288
+
289
+ // Subscribe before reading the burst so no notification is
290
+ // lost in between. Buffer until the snapshot is sent to
291
+ // preserve burst-before-live ordering for the browser.
292
+ let bursting = true;
293
+ const queued = [];
294
+ const unsubscribe = plane.subscribe(sessionId, topics, (update) => {
295
+ if (liveSubscriptionGeneration.get(sessionId) !== generation) return;
296
+ // Bound both the initial read queue and a slow socket.
297
+ // Reconnect performs an authoritative snapshot burst.
298
+ if (queued.length >= 128 || ws.bufferedAmount > 1_048_576) {
299
+ ws.close(1013, "Live consumer is too slow; reconnect for a snapshot");
300
+ return;
301
+ }
302
+ if (bursting) queued.push(update);
303
+ else send({ type: "live", ...update });
304
+ });
305
+ liveSubscriptions.set(sessionId, { generation, unsubscribe });
306
+ const rows = typeof runtime.getLive === "function"
307
+ ? await runtime.getLive(sessionId, topics)
308
+ : [];
309
+ if (liveSubscriptionGeneration.get(sessionId) !== generation) {
310
+ unsubscribe();
311
+ const current = liveSubscriptions.get(sessionId);
312
+ if (current?.generation === generation) liveSubscriptions.delete(sessionId);
313
+ return;
314
+ }
315
+ const burstSeq = new Map();
316
+ for (const row of rows || []) {
317
+ const seq = Number(row.seq) || 0;
318
+ burstSeq.set(row.topic, seq);
319
+ send({
320
+ type: "live",
321
+ sessionId,
322
+ topic: row.topic,
323
+ seq,
324
+ kind: "snapshot",
325
+ data: row.payload || {},
326
+ updatedAt: row.updatedAt,
327
+ });
328
+ }
329
+ bursting = false;
330
+ for (const update of queued) {
331
+ const seen = burstSeq.get(update.topic) || 0;
332
+ if (update.seq != null && Number(update.seq) <= seen) continue;
333
+ if (update.seq != null) burstSeq.set(update.topic, Number(update.seq));
334
+ send({ type: "live", ...update });
335
+ }
336
+ queued.length = 0;
337
+ send({ type: "subscribedLive", sessionId, topics });
338
+ } catch (error) {
339
+ const current = liveSubscriptions.get(sessionId);
340
+ if (current?.generation === generation) {
341
+ current.unsubscribe();
342
+ liveSubscriptions.delete(sessionId);
343
+ }
344
+ if (liveSubscriptionGeneration.get(sessionId) !== generation) return;
345
+ sendSubscriptionError("live", sessionId, error);
346
+ }
347
+ return;
348
+ }
349
+
350
+ if (type === "unsubscribeLive") {
351
+ const sessionId = String(message?.sessionId || "").trim();
352
+ liveSubscriptionGeneration.set(sessionId, (liveSubscriptionGeneration.get(sessionId) || 0) + 1);
353
+ const subscription = liveSubscriptions.get(sessionId);
354
+ if (subscription) {
355
+ subscription.unsubscribe();
356
+ liveSubscriptions.delete(sessionId);
357
+ }
358
+ return;
359
+ }
360
+
361
+ if (type === "publishLive") {
362
+ send({ type: "error", scope: "live", error: "live publishing is server-only" });
363
+ return;
364
+ }
365
+
216
366
  if (type === "subscribeLogs") {
217
367
  if (logUnsubscribe) return;
218
368
  try {
@@ -245,6 +395,7 @@ export function createConnectionHandler(runtime, { allowThemeMessages = false }
245
395
  });
246
396
 
247
397
  ws.on("close", () => {
398
+ if (accessRevalidateTimer) clearInterval(accessRevalidateTimer);
248
399
  if (shareRevalidateTimer) clearInterval(shareRevalidateTimer);
249
400
  for (const unsubscribe of sessionSubscriptions.values()) {
250
401
  try {
@@ -258,6 +409,11 @@ export function createConnectionHandler(runtime, { allowThemeMessages = false }
258
409
  } catch {}
259
410
  }
260
411
  canvasSubscriptions.clear();
412
+ for (const subscription of liveSubscriptions.values()) {
413
+ try { subscription.unsubscribe(); } catch {}
414
+ }
415
+ liveSubscriptions.clear();
416
+ liveSubscriptionGeneration.clear();
261
417
  if (logUnsubscribe) {
262
418
  try {
263
419
  logUnsubscribe();
package/web/authz.js CHANGED
@@ -4,7 +4,9 @@ import {
4
4
  normalizeVisibility,
5
5
  relationFor,
6
6
  SESSION_VISIBILITY_VALUES,
7
+ validateAdminScope,
7
8
  } from "pilotswarm-sdk/api";
9
+ import { loadAuthorizationPolicy, resolveAuthProviderId } from "./auth/config.js";
8
10
 
9
11
  // The session-tree predicate now lives in the SDK so the WORKER evaluates the
10
12
  // same rules for agent tools (Agent Manager proposal §4). Re-exported here so
@@ -35,6 +37,10 @@ export function loadAuthzConfig(env = process.env) {
35
37
  const rawDefault = String(env.SESSIONS_DEFAULT_VISIBILITY || "").trim().toLowerCase();
36
38
  const visibilityValues = new Set(SESSION_VISIBILITY_VALUES);
37
39
  return {
40
+ adminScope: validateAdminScope(env, {
41
+ authenticationEnabled: resolveAuthProviderId({ env }) !== "none"
42
+ && !loadAuthorizationPolicy({ env }).allowUnauthenticated,
43
+ }),
38
44
  enforce: parseBooleanEnv(env.AUTHZ_ENFORCE_OWNERSHIP, false),
39
45
  defaultVisibility: visibilityValues.has(rawDefault) ? rawDefault : "private",
40
46
  // "read" (default): system sessions are metadata/content-visible to
@@ -66,4 +72,3 @@ export function notFoundError() {
66
72
  function ownerLabel(snapshot) {
67
73
  return snapshot?.owner?.displayName || snapshot?.owner?.email || snapshot?.owner?.subject || "another user";
68
74
  }
69
-
@@ -1 +1 @@
1
- import{g as p,r as u,d as a}from"./chunk-MOJQB5TN-_Uc0ksg3.js";import{p as f}from"./chunk-JWPE2WC7-CT3Q7T2A.js";import{_ as n,l as o}from"./mermaid.core-C9u4FOCX.js";import{M as c,b as d}from"./cynefin-VYW2F7L2-C_8v2wIz.js";import"./pilotswarm--kvfB_6q.js";var v=d().RailroadAbnf.parser.LangiumParser,i=n(e=>{const r=e.alternatives.map(g);return r.length===1?r[0]:{type:"choice",alternatives:r}},"transformAlternation"),g=n(e=>{const r=e.elements.map(y);return r.length===1?r[0]:{type:"sequence",elements:r}},"transformConcatenation"),b=n(e=>{if(e.includes("*")){const[t,s]=e.split("*"),l=t?parseInt(t,10):0,m=s?parseInt(s,10):1/0;return{min:l,max:m}}const r=parseInt(e,10);return{min:r,max:r}},"parseRepeat"),y=n(e=>{const r=A(e.primary);if(!e.repeat)return r;const{min:t,max:s}=b(e.repeat);return t===0&&s===1?{type:"optional",element:r}:{type:"repetition",element:r,min:t,max:s}},"transformElement"),A=n(e=>{switch(e.$type){case"AbnfStringLiteral":return{type:"terminal",value:e.value};case"AbnfNumVal":return{type:"terminal",value:e.value};case"AbnfRuleName":return{type:"nonterminal",name:e.name};case"AbnfGroup":return i(e.element);case"AbnfOptionalGroup":return{type:"optional",element:i(e.element)};default:throw new Error(`Unsupported ABNF primary node: ${e.$type}`)}},"transformPrimary"),P=n(e=>({name:e.name,definition:i(e.definition)}),"transformRule"),h=n(e=>{f(e,a),e.title&&a.setTitle(e.title),e.rules.map(r=>a.addRule(P(r)))},"populateDb"),R={parse:n(e=>{a.clear(),o.debug("[ABNF Parser] Starting Langium parse");const r=v.parse(e);if(r.lexerErrors.length>0||r.parserErrors.length>0)throw new c(r);const t=r.value;o.debug("[ABNF Parser] Parsed rules:",t.rules.length),h(t),o.debug("[ABNF Parser] Parse complete")},"parse"),parser:{yy:a}},B={parser:R,db:a,renderer:u,styles:p};export{B as diagram};
1
+ import{g as p,r as u,d as a}from"./chunk-MOJQB5TN-D-Y6KQ7t.js";import{p as f}from"./chunk-JWPE2WC7-CQN0qE7F.js";import{_ as n,l as o}from"./mermaid.core-C78wdk8Y.js";import{M as c,b as d}from"./cynefin-VYW2F7L2-CgRQfdW1.js";import"./pilotswarm-UV6T1_QI.js";var v=d().RailroadAbnf.parser.LangiumParser,i=n(e=>{const r=e.alternatives.map(g);return r.length===1?r[0]:{type:"choice",alternatives:r}},"transformAlternation"),g=n(e=>{const r=e.elements.map(y);return r.length===1?r[0]:{type:"sequence",elements:r}},"transformConcatenation"),b=n(e=>{if(e.includes("*")){const[t,s]=e.split("*"),l=t?parseInt(t,10):0,m=s?parseInt(s,10):1/0;return{min:l,max:m}}const r=parseInt(e,10);return{min:r,max:r}},"parseRepeat"),y=n(e=>{const r=A(e.primary);if(!e.repeat)return r;const{min:t,max:s}=b(e.repeat);return t===0&&s===1?{type:"optional",element:r}:{type:"repetition",element:r,min:t,max:s}},"transformElement"),A=n(e=>{switch(e.$type){case"AbnfStringLiteral":return{type:"terminal",value:e.value};case"AbnfNumVal":return{type:"terminal",value:e.value};case"AbnfRuleName":return{type:"nonterminal",name:e.name};case"AbnfGroup":return i(e.element);case"AbnfOptionalGroup":return{type:"optional",element:i(e.element)};default:throw new Error(`Unsupported ABNF primary node: ${e.$type}`)}},"transformPrimary"),P=n(e=>({name:e.name,definition:i(e.definition)}),"transformRule"),h=n(e=>{f(e,a),e.title&&a.setTitle(e.title),e.rules.map(r=>a.addRule(P(r)))},"populateDb"),R={parse:n(e=>{a.clear(),o.debug("[ABNF Parser] Starting Langium parse");const r=v.parse(e);if(r.lexerErrors.length>0||r.parserErrors.length>0)throw new c(r);const t=r.value;o.debug("[ABNF Parser] Parsed rules:",t.rules.length),h(t),o.debug("[ABNF Parser] Parse complete")},"parse"),parser:{yy:a}},B={parser:R,db:a,renderer:u,styles:p};export{B as diagram};
@@ -1 +1 @@
1
- import{Y as ln,$ as an,a0 as F,a1 as q,a2 as j,a3 as un,a4 as y,a5 as tn,a6 as J,a7 as _,a8 as rn,a9 as o,aa as on,ab as sn,ac as fn}from"./mermaid.core-C9u4FOCX.js";function cn(l){return l.innerRadius}function yn(l){return l.outerRadius}function gn(l){return l.startAngle}function dn(l){return l.endAngle}function mn(l){return l&&l.padAngle}function pn(l,h,I,D,v,A,z,a){var O=I-l,i=D-h,n=z-v,d=a-A,u=d*O-n*i;if(!(u*u<y))return u=(n*(h-A)-d*(l-v))/u,[l+u*O,h+u*i]}function U(l,h,I,D,v,A,z){var a=l-I,O=h-D,i=(z?A:-A)/J(a*a+O*O),n=i*O,d=-i*a,u=l+n,s=h+d,f=I+n,c=D+d,B=(u+f)/2,t=(s+c)/2,m=f-u,g=c-s,R=m*m+g*g,T=v-A,P=u*c-f*s,S=(g<0?-1:1)*J(on(0,T*T*R-P*P)),Y=(P*g-m*S)/R,$=(-P*m-g*S)/R,w=(P*g+m*S)/R,p=(-P*m+g*S)/R,x=Y-B,e=$-t,r=w-B,C=p-t;return x*x+e*e>r*r+C*C&&(Y=w,$=p),{cx:Y,cy:$,x01:-n,y01:-d,x11:Y*(v/T-1),y11:$*(v/T-1)}}function hn(){var l=cn,h=yn,I=j(0),D=null,v=gn,A=dn,z=mn,a=null,O=ln(i);function i(){var n,d,u=+l.apply(this,arguments),s=+h.apply(this,arguments),f=v.apply(this,arguments)-un,c=A.apply(this,arguments)-un,B=rn(c-f),t=c>f;if(a||(a=n=O()),s<u&&(d=s,s=u,u=d),!(s>y))a.moveTo(0,0);else if(B>tn-y)a.moveTo(s*F(f),s*q(f)),a.arc(0,0,s,f,c,!t),u>y&&(a.moveTo(u*F(c),u*q(c)),a.arc(0,0,u,c,f,t));else{var m=f,g=c,R=f,T=c,P=B,S=B,Y=z.apply(this,arguments)/2,$=Y>y&&(D?+D.apply(this,arguments):J(u*u+s*s)),w=_(rn(s-u)/2,+I.apply(this,arguments)),p=w,x=w,e,r;if($>y){var C=sn($/u*q(Y)),K=sn($/s*q(Y));(P-=C*2)>y?(C*=t?1:-1,R+=C,T-=C):(P=0,R=T=(f+c)/2),(S-=K*2)>y?(K*=t?1:-1,m+=K,g-=K):(S=0,m=g=(f+c)/2)}var G=s*F(m),H=s*q(m),L=u*F(T),M=u*q(T);if(w>y){var N=s*F(g),Q=s*q(g),V=u*F(R),W=u*q(R),E;if(B<an)if(E=pn(G,H,V,W,N,Q,L,M)){var X=G-E[0],Z=H-E[1],b=N-E[0],k=Q-E[1],nn=1/q(fn((X*b+Z*k)/(J(X*X+Z*Z)*J(b*b+k*k)))/2),en=J(E[0]*E[0]+E[1]*E[1]);p=_(w,(u-en)/(nn-1)),x=_(w,(s-en)/(nn+1))}else p=x=0}S>y?x>y?(e=U(V,W,G,H,s,x,t),r=U(N,Q,L,M,s,x,t),a.moveTo(e.cx+e.x01,e.cy+e.y01),x<w?a.arc(e.cx,e.cy,x,o(e.y01,e.x01),o(r.y01,r.x01),!t):(a.arc(e.cx,e.cy,x,o(e.y01,e.x01),o(e.y11,e.x11),!t),a.arc(0,0,s,o(e.cy+e.y11,e.cx+e.x11),o(r.cy+r.y11,r.cx+r.x11),!t),a.arc(r.cx,r.cy,x,o(r.y11,r.x11),o(r.y01,r.x01),!t))):(a.moveTo(G,H),a.arc(0,0,s,m,g,!t)):a.moveTo(G,H),!(u>y)||!(P>y)?a.lineTo(L,M):p>y?(e=U(L,M,N,Q,u,-p,t),r=U(G,H,V,W,u,-p,t),a.lineTo(e.cx+e.x01,e.cy+e.y01),p<w?a.arc(e.cx,e.cy,p,o(e.y01,e.x01),o(r.y01,r.x01),!t):(a.arc(e.cx,e.cy,p,o(e.y01,e.x01),o(e.y11,e.x11),!t),a.arc(0,0,u,o(e.cy+e.y11,e.cx+e.x11),o(r.cy+r.y11,r.cx+r.x11),t),a.arc(r.cx,r.cy,p,o(r.y11,r.x11),o(r.y01,r.x01),!t))):a.arc(0,0,u,T,R,t)}if(a.closePath(),n)return a=null,n+""||null}return i.centroid=function(){var n=(+l.apply(this,arguments)+ +h.apply(this,arguments))/2,d=(+v.apply(this,arguments)+ +A.apply(this,arguments))/2-an/2;return[F(d)*n,q(d)*n]},i.innerRadius=function(n){return arguments.length?(l=typeof n=="function"?n:j(+n),i):l},i.outerRadius=function(n){return arguments.length?(h=typeof n=="function"?n:j(+n),i):h},i.cornerRadius=function(n){return arguments.length?(I=typeof n=="function"?n:j(+n),i):I},i.padRadius=function(n){return arguments.length?(D=n==null?null:typeof n=="function"?n:j(+n),i):D},i.startAngle=function(n){return arguments.length?(v=typeof n=="function"?n:j(+n),i):v},i.endAngle=function(n){return arguments.length?(A=typeof n=="function"?n:j(+n),i):A},i.padAngle=function(n){return arguments.length?(z=typeof n=="function"?n:j(+n),i):z},i.context=function(n){return arguments.length?(a=n??null,i):a},i}export{hn as d};
1
+ import{Y as ln,$ as an,a0 as F,a1 as q,a2 as j,a3 as un,a4 as y,a5 as tn,a6 as J,a7 as _,a8 as rn,a9 as o,aa as on,ab as sn,ac as fn}from"./mermaid.core-C78wdk8Y.js";function cn(l){return l.innerRadius}function yn(l){return l.outerRadius}function gn(l){return l.startAngle}function dn(l){return l.endAngle}function mn(l){return l&&l.padAngle}function pn(l,h,I,D,v,A,z,a){var O=I-l,i=D-h,n=z-v,d=a-A,u=d*O-n*i;if(!(u*u<y))return u=(n*(h-A)-d*(l-v))/u,[l+u*O,h+u*i]}function U(l,h,I,D,v,A,z){var a=l-I,O=h-D,i=(z?A:-A)/J(a*a+O*O),n=i*O,d=-i*a,u=l+n,s=h+d,f=I+n,c=D+d,B=(u+f)/2,t=(s+c)/2,m=f-u,g=c-s,R=m*m+g*g,T=v-A,P=u*c-f*s,S=(g<0?-1:1)*J(on(0,T*T*R-P*P)),Y=(P*g-m*S)/R,$=(-P*m-g*S)/R,w=(P*g+m*S)/R,p=(-P*m+g*S)/R,x=Y-B,e=$-t,r=w-B,C=p-t;return x*x+e*e>r*r+C*C&&(Y=w,$=p),{cx:Y,cy:$,x01:-n,y01:-d,x11:Y*(v/T-1),y11:$*(v/T-1)}}function hn(){var l=cn,h=yn,I=j(0),D=null,v=gn,A=dn,z=mn,a=null,O=ln(i);function i(){var n,d,u=+l.apply(this,arguments),s=+h.apply(this,arguments),f=v.apply(this,arguments)-un,c=A.apply(this,arguments)-un,B=rn(c-f),t=c>f;if(a||(a=n=O()),s<u&&(d=s,s=u,u=d),!(s>y))a.moveTo(0,0);else if(B>tn-y)a.moveTo(s*F(f),s*q(f)),a.arc(0,0,s,f,c,!t),u>y&&(a.moveTo(u*F(c),u*q(c)),a.arc(0,0,u,c,f,t));else{var m=f,g=c,R=f,T=c,P=B,S=B,Y=z.apply(this,arguments)/2,$=Y>y&&(D?+D.apply(this,arguments):J(u*u+s*s)),w=_(rn(s-u)/2,+I.apply(this,arguments)),p=w,x=w,e,r;if($>y){var C=sn($/u*q(Y)),K=sn($/s*q(Y));(P-=C*2)>y?(C*=t?1:-1,R+=C,T-=C):(P=0,R=T=(f+c)/2),(S-=K*2)>y?(K*=t?1:-1,m+=K,g-=K):(S=0,m=g=(f+c)/2)}var G=s*F(m),H=s*q(m),L=u*F(T),M=u*q(T);if(w>y){var N=s*F(g),Q=s*q(g),V=u*F(R),W=u*q(R),E;if(B<an)if(E=pn(G,H,V,W,N,Q,L,M)){var X=G-E[0],Z=H-E[1],b=N-E[0],k=Q-E[1],nn=1/q(fn((X*b+Z*k)/(J(X*X+Z*Z)*J(b*b+k*k)))/2),en=J(E[0]*E[0]+E[1]*E[1]);p=_(w,(u-en)/(nn-1)),x=_(w,(s-en)/(nn+1))}else p=x=0}S>y?x>y?(e=U(V,W,G,H,s,x,t),r=U(N,Q,L,M,s,x,t),a.moveTo(e.cx+e.x01,e.cy+e.y01),x<w?a.arc(e.cx,e.cy,x,o(e.y01,e.x01),o(r.y01,r.x01),!t):(a.arc(e.cx,e.cy,x,o(e.y01,e.x01),o(e.y11,e.x11),!t),a.arc(0,0,s,o(e.cy+e.y11,e.cx+e.x11),o(r.cy+r.y11,r.cx+r.x11),!t),a.arc(r.cx,r.cy,x,o(r.y11,r.x11),o(r.y01,r.x01),!t))):(a.moveTo(G,H),a.arc(0,0,s,m,g,!t)):a.moveTo(G,H),!(u>y)||!(P>y)?a.lineTo(L,M):p>y?(e=U(L,M,N,Q,u,-p,t),r=U(G,H,V,W,u,-p,t),a.lineTo(e.cx+e.x01,e.cy+e.y01),p<w?a.arc(e.cx,e.cy,p,o(e.y01,e.x01),o(r.y01,r.x01),!t):(a.arc(e.cx,e.cy,p,o(e.y01,e.x01),o(e.y11,e.x11),!t),a.arc(0,0,u,o(e.cy+e.y11,e.cx+e.x11),o(r.cy+r.y11,r.cx+r.x11),t),a.arc(r.cx,r.cy,p,o(r.y11,r.x11),o(r.y01,r.x01),!t))):a.arc(0,0,u,T,R,t)}if(a.closePath(),n)return a=null,n+""||null}return i.centroid=function(){var n=(+l.apply(this,arguments)+ +h.apply(this,arguments))/2,d=(+v.apply(this,arguments)+ +A.apply(this,arguments))/2-an/2;return[F(d)*n,q(d)*n]},i.innerRadius=function(n){return arguments.length?(l=typeof n=="function"?n:j(+n),i):l},i.outerRadius=function(n){return arguments.length?(h=typeof n=="function"?n:j(+n),i):h},i.cornerRadius=function(n){return arguments.length?(I=typeof n=="function"?n:j(+n),i):I},i.padRadius=function(n){return arguments.length?(D=n==null?null:typeof n=="function"?n:j(+n),i):D},i.startAngle=function(n){return arguments.length?(v=typeof n=="function"?n:j(+n),i):v},i.endAngle=function(n){return arguments.length?(A=typeof n=="function"?n:j(+n),i):A},i.padAngle=function(n){return arguments.length?(z=typeof n=="function"?n:j(+n),i):z},i.context=function(n){return arguments.length?(a=n??null,i):a},i}export{hn as d};