@rynx-ai/server 0.1.11-beta.30 → 0.1.11-beta.32
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.
- package/dist/control-api.js +37 -0
- package/dist/control-web/assets/{abnfDiagram-VRR7QNED-COu02MlJ.js → abnfDiagram-VRR7QNED-8c99_3I5.js} +1 -1
- package/dist/control-web/assets/{arc-DqiFeYAM.js → arc-FWW96Tzo.js} +1 -1
- package/dist/control-web/assets/{architectureDiagram-ZJ3FMSHR-BWFGgUV4.js → architectureDiagram-ZJ3FMSHR-B5FrqoRB.js} +1 -1
- package/dist/control-web/assets/{blockDiagram-677ZJIJ3-ZC0bXDzB.js → blockDiagram-677ZJIJ3-DJRwOGMa.js} +1 -1
- package/dist/control-web/assets/{c4Diagram-LMCZKHZV-Uupq5I-s.js → c4Diagram-LMCZKHZV-B8FCUurl.js} +1 -1
- package/dist/control-web/assets/channel-Bnyhn1Qs.js +1 -0
- package/dist/control-web/assets/{chunk-2Q5K7J3B-C77vwuDM.js → chunk-2Q5K7J3B-DC5xexY2.js} +1 -1
- package/dist/control-web/assets/{chunk-32BRIVSS-BkfLE_TX.js → chunk-32BRIVSS-DAzT69zb.js} +1 -1
- package/dist/control-web/assets/{chunk-5VM5RSS4-By6zfDWW.js → chunk-5VM5RSS4-CXKpL77L.js} +1 -1
- package/dist/control-web/assets/{chunk-EX3LRPZG-CO439p_3.js → chunk-EX3LRPZG-DQKQvoPv.js} +1 -1
- package/dist/control-web/assets/{chunk-JWPE2WC7-B9BPEwug.js → chunk-JWPE2WC7-CWMqpJoT.js} +1 -1
- package/dist/control-web/assets/{chunk-MOJQB5TN-CYKygWZz.js → chunk-MOJQB5TN-Bd8jn7RZ.js} +1 -1
- package/dist/control-web/assets/{chunk-RYQCIY6F-ug-An4HG.js → chunk-RYQCIY6F-DKXEMFO6.js} +1 -1
- package/dist/control-web/assets/{chunk-V7JOEXUC-3UFQGnKX.js → chunk-V7JOEXUC-BUDLq5nU.js} +1 -1
- package/dist/control-web/assets/{chunk-VR4S4FIN-DjspjYUb.js → chunk-VR4S4FIN-C65l7VIz.js} +1 -1
- package/dist/control-web/assets/{chunk-XXDRQBXY-3znnutie.js → chunk-XXDRQBXY-Y2498HkC.js} +1 -1
- package/dist/control-web/assets/classDiagram-OUVF2IWQ-CBtXSdwW.js +1 -0
- package/dist/control-web/assets/classDiagram-v2-EOCWNBFH-CBtXSdwW.js +1 -0
- package/dist/control-web/assets/{cose-bilkent-JH36ORCC-Dr28goCd.js → cose-bilkent-JH36ORCC-C9aVXb8E.js} +1 -1
- package/dist/control-web/assets/{cynefin-VYW2F7L2-Be5uJhnw.js → cynefin-VYW2F7L2-BsyMhvG0.js} +1 -1
- package/dist/control-web/assets/{cynefinDiagram-TSTJHNR4-BLxtSvL6.js → cynefinDiagram-TSTJHNR4-DwY43f3-.js} +1 -1
- package/dist/control-web/assets/{dagre-VKFMJZFB-DPRuDV5p.js → dagre-VKFMJZFB-CP1xk7Ox.js} +1 -1
- package/dist/control-web/assets/{diagram-FQU43EPY-Cybg22U0.js → diagram-FQU43EPY-D_yRnH1b.js} +1 -1
- package/dist/control-web/assets/{diagram-G47NLZAW-CGeIKUfW.js → diagram-G47NLZAW-DpTmLmbm.js} +1 -1
- package/dist/control-web/assets/{diagram-NH7WQ7WH-BJq5MBRm.js → diagram-NH7WQ7WH-Wh_P26Dr.js} +1 -1
- package/dist/control-web/assets/{diagram-OA4YK3LP-DquOQGwg.js → diagram-OA4YK3LP-BMMaO78f.js} +1 -1
- package/dist/control-web/assets/{diagram-WEI45ONY-Cblb4xP2.js → diagram-WEI45ONY-D8smty9F.js} +1 -1
- package/dist/control-web/assets/{ebnfDiagram-CCIWWBDH-CQjHiOqj.js → ebnfDiagram-CCIWWBDH-BqVsmEYh.js} +1 -1
- package/dist/control-web/assets/{erDiagram-Q63AITRT-Bspr3uKJ.js → erDiagram-Q63AITRT-CxaumIzW.js} +1 -1
- package/dist/control-web/assets/{flowDiagram-23GEKE2U-CxcZyBA-.js → flowDiagram-23GEKE2U-DZhUBne-.js} +1 -1
- package/dist/control-web/assets/{ganttDiagram-NO4QXBWP-qBdh7A-P.js → ganttDiagram-NO4QXBWP-B20dY8SF.js} +1 -1
- package/dist/control-web/assets/{gitGraphDiagram-IHSO6WYX-BTYub2KT.js → gitGraphDiagram-IHSO6WYX-CAlnqFfW.js} +1 -1
- package/dist/control-web/assets/{highlighted-body-OFNGDK62-DyksUrwu.js → highlighted-body-OFNGDK62-vBlGjC8F.js} +1 -1
- package/dist/control-web/assets/{index-BDaSctIT.js → index-B3g4Kfko.js} +1 -1
- package/dist/control-web/assets/index-BKqZNg1a.js +718 -0
- package/dist/control-web/assets/index-CZrE_F_i.css +32 -0
- package/dist/control-web/assets/{infoDiagram-FWYZ7A6U-ChbQR3Zo.js → infoDiagram-FWYZ7A6U-PaEyeSke.js} +1 -1
- package/dist/control-web/assets/{ishikawaDiagram-FXEZZL3T-C-4_qGzn.js → ishikawaDiagram-FXEZZL3T-BwsgPXGC.js} +1 -1
- package/dist/control-web/assets/{journeyDiagram-5HDEW3XC-B090zN18.js → journeyDiagram-5HDEW3XC-BUEf1NEq.js} +1 -1
- package/dist/control-web/assets/{kanban-definition-HUTT4EX6-RlTcRbPs.js → kanban-definition-HUTT4EX6-CiTCwnJ7.js} +1 -1
- package/dist/control-web/assets/{linear-DTuNxDqu.js → linear-Cs4GSPvX.js} +1 -1
- package/dist/control-web/assets/{mermaid-GHXKKRXX-BkcV3dgg.js → mermaid-GHXKKRXX-C_lUqoOB.js} +6 -6
- package/dist/control-web/assets/{mindmap-definition-LN4V7U3C-DYby41E9.js → mindmap-definition-LN4V7U3C-B8TkylOR.js} +1 -1
- package/dist/control-web/assets/{pegDiagram-2B236MQR-CPK6GaEv.js → pegDiagram-2B236MQR-Bnge_jzz.js} +1 -1
- package/dist/control-web/assets/{pieDiagram-ENE6RG2P-DLCmXuji.js → pieDiagram-ENE6RG2P-DOP7FAKG.js} +1 -1
- package/dist/control-web/assets/{quadrantDiagram-ABIIQ3AL-RIS8r7CA.js → quadrantDiagram-ABIIQ3AL-Cqqu5uu-.js} +1 -1
- package/dist/control-web/assets/{railroadDiagram-RFXS5EU6-B1HB7xsg.js → railroadDiagram-RFXS5EU6-1bk56BMW.js} +1 -1
- package/dist/control-web/assets/{requirementDiagram-TGXJPOKE-Inh_4hU0.js → requirementDiagram-TGXJPOKE-BHVwG79A.js} +1 -1
- package/dist/control-web/assets/{sankeyDiagram-HTMAVEWB-DPF33kPB.js → sankeyDiagram-HTMAVEWB-ghjBFqfs.js} +1 -1
- package/dist/control-web/assets/{sequenceDiagram-DBY2YBRQ-iywKWoYc.js → sequenceDiagram-DBY2YBRQ-DChtyqq5.js} +1 -1
- package/dist/control-web/assets/{sizeCapture-X5ZJPWSS-DlnHubz9.js → sizeCapture-X5ZJPWSS-BNp94nCS.js} +1 -1
- package/dist/control-web/assets/{stateDiagram-2N3HPSRC-CShzll2N.js → stateDiagram-2N3HPSRC-CaFYkZZ2.js} +1 -1
- package/dist/control-web/assets/stateDiagram-v2-6OUMAXLB-CCvEZFYI.js +1 -0
- package/dist/control-web/assets/{swimlanes-5IMT3BWC-CxlQpFBv.js → swimlanes-5IMT3BWC-CV2YWeYv.js} +2 -2
- package/dist/control-web/assets/swimlanesDiagram-G3AALYLV-BFAdJHEB.js +8 -0
- package/dist/control-web/assets/{timeline-definition-FHXFAJF6-Dlx59Xc3.js → timeline-definition-FHXFAJF6-Cq32UWLH.js} +1 -1
- package/dist/control-web/assets/{vennDiagram-L72KCM5P-DLsv4i_t.js → vennDiagram-L72KCM5P-BgBoqPth.js} +1 -1
- package/dist/control-web/assets/{wardleyDiagram-EHGQE667-BazV8fpH.js → wardleyDiagram-EHGQE667-VsC2Gfpu.js} +1 -1
- package/dist/control-web/assets/{xychartDiagram-FW5EYKEG-Bx9uDlYu.js → xychartDiagram-FW5EYKEG-CyFiAunt.js} +1 -1
- package/dist/control-web/index.html +2 -2
- package/dist/desktop-browser-host.d.ts +3 -1
- package/dist/desktop-browser-host.js +39 -4
- package/dist/machine-session-service.d.ts +12 -0
- package/dist/machine-session-service.js +31 -7
- package/dist/remote-runtime-dispatcher.d.ts +1 -1
- package/dist/remote-runtime-dispatcher.js +26 -1
- package/dist/remote-runtime-session-projection.d.ts +1 -0
- package/dist/remote-runtime-session-projection.js +1 -0
- package/dist/server.d.ts +23 -1
- package/dist/server.js +94 -11
- package/dist/session-browser-service.d.ts +24 -4
- package/dist/session-browser-service.js +127 -1
- package/dist/session-pending-inputs.d.ts +40 -0
- package/dist/session-pending-inputs.js +84 -0
- package/dist/session-portal.d.ts +14 -2
- package/dist/session-portal.js +268 -31
- package/package.json +7 -7
- package/dist/control-web/assets/channel-3HG15wfa.js +0 -1
- package/dist/control-web/assets/classDiagram-OUVF2IWQ-WmU4L9YT.js +0 -1
- package/dist/control-web/assets/classDiagram-v2-EOCWNBFH-WmU4L9YT.js +0 -1
- package/dist/control-web/assets/index-B9SAq_63.js +0 -718
- package/dist/control-web/assets/index-CtaYRNgJ.css +0 -32
- package/dist/control-web/assets/stateDiagram-v2-6OUMAXLB-Cdx0fWna.js +0 -1
- package/dist/control-web/assets/swimlanesDiagram-G3AALYLV-CoX0jnUu.js +0 -8
package/dist/session-portal.js
CHANGED
|
@@ -6,6 +6,9 @@ import { WebSocket, WebSocketServer } from "ws";
|
|
|
6
6
|
import { sendSpaFile } from "./control-web-dist.js";
|
|
7
7
|
export const SESSION_PORTAL_HEADER = "x-rynx-session-portal";
|
|
8
8
|
export const SESSION_PORTAL_QUERY = "portal";
|
|
9
|
+
export const SESSION_PORTAL_SECRET_HEADER = "x-rynx-session-portal-secret";
|
|
10
|
+
export const SESSION_PORTAL_SECRET_QUERY = "portal_secret";
|
|
11
|
+
export const SESSION_PORTAL_EMBED_ORIGIN_QUERY = "embed_origin";
|
|
9
12
|
const MAX_TICKET_BYTES = 256;
|
|
10
13
|
const MAX_FILTERED_RESPONSE_BYTES = 16 * 1024 * 1024;
|
|
11
14
|
const MAX_PENDING_SOCKET_FRAMES = 128;
|
|
@@ -29,20 +32,63 @@ const HOP_BY_HOP_HEADERS = new Set([
|
|
|
29
32
|
"transfer-encoding",
|
|
30
33
|
"upgrade",
|
|
31
34
|
]);
|
|
35
|
+
class SessionPortalTransportRegistry {
|
|
36
|
+
transports = new Map();
|
|
37
|
+
track(portalId, close) {
|
|
38
|
+
let finish;
|
|
39
|
+
const done = new Promise((resolve) => { finish = resolve; });
|
|
40
|
+
const entry = { close, done, finish };
|
|
41
|
+
const entries = this.transports.get(portalId) ?? new Set();
|
|
42
|
+
entries.add(entry);
|
|
43
|
+
this.transports.set(portalId, entries);
|
|
44
|
+
let finished = false;
|
|
45
|
+
return () => {
|
|
46
|
+
if (finished)
|
|
47
|
+
return;
|
|
48
|
+
finished = true;
|
|
49
|
+
entries.delete(entry);
|
|
50
|
+
if (entries.size === 0)
|
|
51
|
+
this.transports.delete(portalId);
|
|
52
|
+
finish();
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
async revoke(portalIds) {
|
|
56
|
+
const draining = [];
|
|
57
|
+
for (const portalId of portalIds) {
|
|
58
|
+
const entries = [...(this.transports.get(portalId) ?? [])];
|
|
59
|
+
this.transports.delete(portalId);
|
|
60
|
+
for (const entry of entries) {
|
|
61
|
+
draining.push(entry.done);
|
|
62
|
+
entry.close();
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
await Promise.all(draining);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
32
68
|
/** Session Portal is a narrow adapter in front of the one Control service. It
|
|
33
69
|
* serves the native SPA, validates Portal grants, and forwards only authorized
|
|
34
70
|
* Session HTTP calls to Control; it owns no Session or Runtime handlers. */
|
|
35
|
-
export function createSessionPortalApp(options) {
|
|
71
|
+
export function createSessionPortalApp(options, transports = new SessionPortalTransportRegistry()) {
|
|
36
72
|
const app = new Koa();
|
|
37
73
|
const bootstrapRouter = new Router();
|
|
38
74
|
app.use(async (ctx, next) => {
|
|
39
75
|
ctx.set("Cache-Control", "no-store");
|
|
40
76
|
ctx.set("Referrer-Policy", "no-referrer");
|
|
41
77
|
ctx.set("X-Content-Type-Options", "nosniff");
|
|
42
|
-
ctx
|
|
78
|
+
const embedded = embeddedPageBootstrap(ctx, options.authorization);
|
|
79
|
+
if (embedded)
|
|
80
|
+
ctx.state.sessionPortalEmbed = embedded;
|
|
81
|
+
// The redeem shell contains no Session data and must be frameable before
|
|
82
|
+
// its fragment ticket can be posted. A real Session page is frameable only
|
|
83
|
+
// after its portal id resolves to the exact still-active embedded grant.
|
|
84
|
+
const frameOrigin = ctx.path === "/session-portal/redeem"
|
|
85
|
+
? sessionPortalEmbedOrigin(ctx)
|
|
86
|
+
: embedded?.frameOrigin;
|
|
87
|
+
if (!frameOrigin)
|
|
88
|
+
ctx.set("X-Frame-Options", "DENY");
|
|
43
89
|
ctx.set("Content-Security-Policy", "default-src 'self'; style-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-inline'; " +
|
|
44
90
|
"connect-src 'self' ws: wss:; img-src 'self' data: blob:; font-src 'self' data:; " +
|
|
45
|
-
|
|
91
|
+
`base-uri 'none'; form-action 'self'; frame-ancestors ${frameOrigin ?? "'none'"}`);
|
|
46
92
|
if (options.shutdownSignal?.aborted) {
|
|
47
93
|
serviceUnavailable(ctx);
|
|
48
94
|
return;
|
|
@@ -64,7 +110,8 @@ export function createSessionPortalApp(options) {
|
|
|
64
110
|
ctx.body = { error: "invalid_ticket" };
|
|
65
111
|
return;
|
|
66
112
|
}
|
|
67
|
-
const
|
|
113
|
+
const requestedEmbedOrigin = sessionPortalEmbedOrigin(ctx);
|
|
114
|
+
const grant = options.authorization.redeem(ticket, requestedEmbedOrigin);
|
|
68
115
|
if (!grant) {
|
|
69
116
|
ctx.status = 401;
|
|
70
117
|
ctx.body = { error: "ticket_expired_or_used" };
|
|
@@ -81,9 +128,22 @@ export function createSessionPortalApp(options) {
|
|
|
81
128
|
const path = grant.scope === "single_session"
|
|
82
129
|
? `/runtimes/local/sessions/${encodeURIComponent(grant.sessionId)}`
|
|
83
130
|
: "/runtimes/local/sessions";
|
|
131
|
+
const location = new URL(path, "http://session-portal.invalid");
|
|
132
|
+
location.searchParams.set(SESSION_PORTAL_QUERY, grant.portalId);
|
|
133
|
+
if (grant.embedOrigin) {
|
|
134
|
+
location.searchParams.set(SESSION_PORTAL_EMBED_ORIGIN_QUERY, grant.embedOrigin);
|
|
135
|
+
}
|
|
84
136
|
ctx.body = {
|
|
85
|
-
location: `${
|
|
137
|
+
location: `${location.pathname}${location.search}`,
|
|
86
138
|
expiresAt: grant.expiresAt,
|
|
139
|
+
...(grant.embedOrigin
|
|
140
|
+
? {
|
|
141
|
+
authorization: {
|
|
142
|
+
...publicAuthorization(grant),
|
|
143
|
+
secret: grant.secret,
|
|
144
|
+
},
|
|
145
|
+
}
|
|
146
|
+
: {}),
|
|
87
147
|
};
|
|
88
148
|
});
|
|
89
149
|
app.use(bootstrapRouter.routes());
|
|
@@ -91,24 +151,51 @@ export function createSessionPortalApp(options) {
|
|
|
91
151
|
app.use(async (ctx, next) => {
|
|
92
152
|
if (isStaticAssetRequest(ctx))
|
|
93
153
|
return next();
|
|
154
|
+
const embedded = ctx.state.sessionPortalEmbed;
|
|
155
|
+
if (embedded) {
|
|
156
|
+
ctx.state.sessionPortalEmbed = embedded;
|
|
157
|
+
return next();
|
|
158
|
+
}
|
|
94
159
|
const grant = authorizeHttp(ctx, options.authorization);
|
|
95
160
|
if (!grant)
|
|
96
161
|
return;
|
|
97
162
|
ctx.state.sessionPortal = grant;
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
ctx.
|
|
103
|
-
|
|
163
|
+
const revocation = new AbortController();
|
|
164
|
+
ctx.state.sessionPortalRevocationSignal = revocation.signal;
|
|
165
|
+
const untrack = transports.track(grant.portalId, () => {
|
|
166
|
+
revocation.abort(new Error("Session Portal grant revoked"));
|
|
167
|
+
if (!ctx.res.writableEnded)
|
|
168
|
+
ctx.res.destroy(new Error("Session Portal grant revoked"));
|
|
169
|
+
});
|
|
170
|
+
try {
|
|
171
|
+
// Close the authorize -> registry race: if revoke committed between
|
|
172
|
+
// those steps, its notification may have had no transport to close yet.
|
|
173
|
+
if (!options.authorization.inspect(grant.portalId)) {
|
|
174
|
+
ctx.status = 401;
|
|
175
|
+
ctx.body = { error: "session_portal_grant_expired_or_invalid" };
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
expireHttpResponseAtGrantDeadline(ctx, grant.expiresAt);
|
|
179
|
+
if (ctx.path === "/api/session-portal/access") {
|
|
180
|
+
if (ctx.method !== "GET")
|
|
181
|
+
return methodNotAllowed(ctx);
|
|
182
|
+
ctx.body = publicAuthorization(grant);
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
if (!authorizePortalRoute(ctx, grant, options.authorization))
|
|
186
|
+
return;
|
|
187
|
+
if (ctx.path.startsWith("/api/")) {
|
|
188
|
+
await proxyControlHttp(ctx, grant, options);
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
await next();
|
|
104
192
|
}
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
193
|
+
finally {
|
|
194
|
+
// This is operation completion, not merely client disconnect. Revoke
|
|
195
|
+
// waits for it, so an already-admitted Control call cannot outlive the
|
|
196
|
+
// Host RPC that revoked its grant.
|
|
197
|
+
untrack();
|
|
110
198
|
}
|
|
111
|
-
await next();
|
|
112
199
|
});
|
|
113
200
|
app.use(async (ctx, next) => {
|
|
114
201
|
if (ctx.method !== "GET" && ctx.method !== "HEAD")
|
|
@@ -116,9 +203,12 @@ export function createSessionPortalApp(options) {
|
|
|
116
203
|
if (ctx.path.startsWith("/api/") || ctx.path.startsWith("/session-portal/"))
|
|
117
204
|
return next();
|
|
118
205
|
const grant = ctx.state.sessionPortal;
|
|
206
|
+
const embedded = ctx.state.sessionPortalEmbed;
|
|
119
207
|
await sendSpaFile(ctx, options.controlWebDist, "/", grant
|
|
120
208
|
? (html) => injectPortalBootstrap(html, grant)
|
|
121
|
-
:
|
|
209
|
+
: embedded
|
|
210
|
+
? (html) => injectEmbeddedPortalBootstrap(html, embedded.portalId)
|
|
211
|
+
: undefined);
|
|
122
212
|
});
|
|
123
213
|
return app;
|
|
124
214
|
}
|
|
@@ -132,7 +222,9 @@ export async function startSessionPortal(options) {
|
|
|
132
222
|
void shutdownPortal?.().catch(() => undefined);
|
|
133
223
|
};
|
|
134
224
|
options.shutdownSignal?.addEventListener("abort", stopForControlShutdown, { once: true });
|
|
135
|
-
const
|
|
225
|
+
const transports = new SessionPortalTransportRegistry();
|
|
226
|
+
const unsubscribeRevocations = options.authorization.subscribeRevocations?.((portalIds) => transports.revoke(portalIds));
|
|
227
|
+
const app = createSessionPortalApp({ ...options, shutdownSignal: lifecycle.signal }, transports);
|
|
136
228
|
let server;
|
|
137
229
|
try {
|
|
138
230
|
server = await new Promise((resolve, reject) => {
|
|
@@ -144,6 +236,7 @@ export async function startSessionPortal(options) {
|
|
|
144
236
|
});
|
|
145
237
|
}
|
|
146
238
|
catch (error) {
|
|
239
|
+
unsubscribeRevocations?.();
|
|
147
240
|
options.shutdownSignal?.removeEventListener("abort", stopForControlShutdown);
|
|
148
241
|
throw error;
|
|
149
242
|
}
|
|
@@ -153,9 +246,11 @@ export async function startSessionPortal(options) {
|
|
|
153
246
|
authorization: options.authorization,
|
|
154
247
|
controlOrigin: options.controlOrigin,
|
|
155
248
|
shutdownSignal: lifecycle.signal,
|
|
249
|
+
transports,
|
|
156
250
|
});
|
|
157
251
|
}
|
|
158
252
|
catch (error) {
|
|
253
|
+
unsubscribeRevocations?.();
|
|
159
254
|
options.shutdownSignal?.removeEventListener("abort", stopForControlShutdown);
|
|
160
255
|
await closeHttpServerImmediately(server);
|
|
161
256
|
throw error;
|
|
@@ -173,6 +268,7 @@ export async function startSessionPortal(options) {
|
|
|
173
268
|
closeWebSocketServer(sockets.external),
|
|
174
269
|
closeHttpServerImmediately(server),
|
|
175
270
|
]).then(() => {
|
|
271
|
+
unsubscribeRevocations?.();
|
|
176
272
|
options.shutdownSignal?.removeEventListener("abort", stopForControlShutdown);
|
|
177
273
|
});
|
|
178
274
|
return shutdownPromise;
|
|
@@ -199,7 +295,8 @@ export function authorizeSessionPortalWebSocket(request, ports, runtimeSelector,
|
|
|
199
295
|
const portalId = url.searchParams.get(SESSION_PORTAL_QUERY) ?? "";
|
|
200
296
|
if (!PORTAL_ID_PATTERN.test(portalId))
|
|
201
297
|
return undefined;
|
|
202
|
-
const secret = cookieValue(request.headers.cookie, cookieName(portalId))
|
|
298
|
+
const secret = cookieValue(request.headers.cookie, cookieName(portalId)) ||
|
|
299
|
+
url.searchParams.get(SESSION_PORTAL_SECRET_QUERY) || undefined;
|
|
203
300
|
if (!secret)
|
|
204
301
|
return undefined;
|
|
205
302
|
const grant = ports.authorize(portalId, secret);
|
|
@@ -225,7 +322,8 @@ function authorizeHttp(ctx, ports) {
|
|
|
225
322
|
: queryPortal;
|
|
226
323
|
const portalId = candidate.trim();
|
|
227
324
|
const secret = PORTAL_ID_PATTERN.test(portalId)
|
|
228
|
-
? ctx.cookies.get(cookieName(portalId))
|
|
325
|
+
? ctx.cookies.get(cookieName(portalId)) ||
|
|
326
|
+
ctx.get(SESSION_PORTAL_SECRET_HEADER).trim() || undefined
|
|
229
327
|
: undefined;
|
|
230
328
|
const grant = secret ? ports.authorize(portalId, secret) : undefined;
|
|
231
329
|
if (!grant) {
|
|
@@ -268,6 +366,11 @@ function authorizePortalRoute(ctx, grant, ports) {
|
|
|
268
366
|
return notFound(ctx);
|
|
269
367
|
if (!ports.ownsSession(grant.pluginId, sessionId))
|
|
270
368
|
return notFound(ctx);
|
|
369
|
+
if (isBrowserRequestHeadersRoute(ctx, apiMatch[1])) {
|
|
370
|
+
if (ctx.method !== "GET" && ctx.method !== "PUT")
|
|
371
|
+
return methodNotAllowed(ctx);
|
|
372
|
+
return denyMutation(ctx, grant);
|
|
373
|
+
}
|
|
271
374
|
if (ctx.method === "GET" || ctx.method === "HEAD")
|
|
272
375
|
return true;
|
|
273
376
|
if (!isAllowedSessionMutation(ctx, apiMatch[1]))
|
|
@@ -277,20 +380,30 @@ function authorizePortalRoute(ctx, grant, ports) {
|
|
|
277
380
|
return notFound(ctx);
|
|
278
381
|
}
|
|
279
382
|
async function proxyControlHttp(ctx, grant, options) {
|
|
383
|
+
const revocationSignal = ctx.state.sessionPortalRevocationSignal;
|
|
384
|
+
// Reads/streams can be aborted immediately. An already-admitted mutation is
|
|
385
|
+
// different: keep its Control request alive and let the registry wait for
|
|
386
|
+
// the response, so revoke cannot return while a write is still executing.
|
|
387
|
+
const mutating = ctx.method !== "GET" && ctx.method !== "HEAD";
|
|
388
|
+
const signal = mutating
|
|
389
|
+
? options.shutdownSignal
|
|
390
|
+
: options.shutdownSignal && revocationSignal
|
|
391
|
+
? AbortSignal.any([options.shutdownSignal, revocationSignal])
|
|
392
|
+
: options.shutdownSignal ?? revocationSignal;
|
|
280
393
|
let response;
|
|
281
394
|
try {
|
|
282
|
-
response = await openControlRequest(ctx, options.controlOrigin,
|
|
395
|
+
response = await openControlRequest(ctx, options.controlOrigin, signal);
|
|
283
396
|
}
|
|
284
397
|
catch (error) {
|
|
285
|
-
if (
|
|
286
|
-
return
|
|
398
|
+
if (signal?.aborted)
|
|
399
|
+
return;
|
|
287
400
|
ctx.status = 502;
|
|
288
401
|
ctx.body = { error: "control_service_unavailable" };
|
|
289
402
|
return;
|
|
290
403
|
}
|
|
291
404
|
if (shouldFilterControlResponse(ctx)) {
|
|
292
405
|
try {
|
|
293
|
-
const data = await readBoundedResponse(response, MAX_FILTERED_RESPONSE_BYTES,
|
|
406
|
+
const data = await readBoundedResponse(response, MAX_FILTERED_RESPONSE_BYTES, signal);
|
|
294
407
|
applyControlResponseHeaders(ctx, response.headers, true);
|
|
295
408
|
ctx.status = response.statusCode ?? 502;
|
|
296
409
|
const body = JSON.parse(data.toString("utf8"));
|
|
@@ -298,8 +411,8 @@ async function proxyControlHttp(ctx, grant, options) {
|
|
|
298
411
|
ctx.body = body;
|
|
299
412
|
}
|
|
300
413
|
catch {
|
|
301
|
-
if (
|
|
302
|
-
return
|
|
414
|
+
if (signal?.aborted)
|
|
415
|
+
return;
|
|
303
416
|
ctx.status = 502;
|
|
304
417
|
ctx.body = { error: "invalid_control_response" };
|
|
305
418
|
}
|
|
@@ -310,8 +423,8 @@ async function proxyControlHttp(ctx, grant, options) {
|
|
|
310
423
|
ctx.respond = false;
|
|
311
424
|
ctx.res.statusCode = ctx.status;
|
|
312
425
|
try {
|
|
313
|
-
await pipeline(response, ctx.res, ...(
|
|
314
|
-
? [{ signal
|
|
426
|
+
await pipeline(response, ctx.res, ...(signal
|
|
427
|
+
? [{ signal }]
|
|
315
428
|
: []));
|
|
316
429
|
}
|
|
317
430
|
catch {
|
|
@@ -322,6 +435,8 @@ async function proxyControlHttp(ctx, grant, options) {
|
|
|
322
435
|
function openControlRequest(ctx, controlOrigin, signal) {
|
|
323
436
|
const target = new URL(`${ctx.path}${ctx.search}`, controlOrigin);
|
|
324
437
|
target.searchParams.delete(SESSION_PORTAL_QUERY);
|
|
438
|
+
target.searchParams.delete(SESSION_PORTAL_SECRET_QUERY);
|
|
439
|
+
target.searchParams.delete(SESSION_PORTAL_EMBED_ORIGIN_QUERY);
|
|
325
440
|
return new Promise((resolve, reject) => {
|
|
326
441
|
const request = httpRequest(target, {
|
|
327
442
|
method: ctx.method,
|
|
@@ -345,6 +460,7 @@ function controlRequestHeaders(headers, target) {
|
|
|
345
460
|
lower === "referer" ||
|
|
346
461
|
lower === "accept-encoding" ||
|
|
347
462
|
lower === SESSION_PORTAL_HEADER ||
|
|
463
|
+
lower === SESSION_PORTAL_SECRET_HEADER ||
|
|
348
464
|
lower.startsWith("forwarded") ||
|
|
349
465
|
lower.startsWith("x-forwarded-"))
|
|
350
466
|
continue;
|
|
@@ -397,6 +513,9 @@ function attachPortalWebSocketProxy(server, options) {
|
|
|
397
513
|
const route = portalSocketRoute(request.url);
|
|
398
514
|
if (!route)
|
|
399
515
|
return rejectUpgrade(socket, 404, "Session Portal socket not found");
|
|
516
|
+
const credential = sessionPortalSocketCredential(request);
|
|
517
|
+
if (!credential)
|
|
518
|
+
return rejectSessionPortalUpgrade(socket);
|
|
400
519
|
const grant = authorizeSessionPortalWebSocket(request, options.authorization, route.runtimeSelector, route.sessionId);
|
|
401
520
|
if (!grant || !readOnlySocketRoleAllowed(route.kind, request.url, grant)) {
|
|
402
521
|
rejectSessionPortalUpgrade(socket);
|
|
@@ -408,6 +527,9 @@ function attachPortalWebSocketProxy(server, options) {
|
|
|
408
527
|
controlOrigin: options.controlOrigin,
|
|
409
528
|
shutdownSignal: options.shutdownSignal,
|
|
410
529
|
upstreamSockets,
|
|
530
|
+
transports: options.transports,
|
|
531
|
+
portalId: grant.portalId,
|
|
532
|
+
reauthorize: () => options.authorization.authorize(credential.portalId, credential.secret),
|
|
411
533
|
});
|
|
412
534
|
});
|
|
413
535
|
}
|
|
@@ -422,6 +544,8 @@ function bridgePortalWebSocket(client, kind, requestUrl, grant, options) {
|
|
|
422
544
|
const target = new URL(`${externalTarget.pathname}${externalTarget.search}`, options.controlOrigin);
|
|
423
545
|
target.protocol = target.protocol === "https:" ? "wss:" : "ws:";
|
|
424
546
|
target.searchParams.delete(SESSION_PORTAL_QUERY);
|
|
547
|
+
target.searchParams.delete(SESSION_PORTAL_SECRET_QUERY);
|
|
548
|
+
target.searchParams.delete(SESSION_PORTAL_EMBED_ORIGIN_QUERY);
|
|
425
549
|
const upstream = new WebSocket(target, {
|
|
426
550
|
origin: options.controlOrigin,
|
|
427
551
|
headers: { "sec-fetch-site": "same-origin" },
|
|
@@ -429,6 +553,27 @@ function bridgePortalWebSocket(client, kind, requestUrl, grant, options) {
|
|
|
429
553
|
maxPayload: MAX_SOCKET_BUFFERED_BYTES,
|
|
430
554
|
});
|
|
431
555
|
options.upstreamSockets.add(upstream);
|
|
556
|
+
const untrack = options.transports.track(options.portalId, () => {
|
|
557
|
+
closeSocket(client, 4403, "Session Portal grant revoked");
|
|
558
|
+
upstream.terminate();
|
|
559
|
+
const forceClose = setTimeout(() => client.terminate(), 100);
|
|
560
|
+
forceClose.unref?.();
|
|
561
|
+
client.once("close", () => clearTimeout(forceClose));
|
|
562
|
+
});
|
|
563
|
+
// Close the authorize -> transport-registry race just like the HTTP path.
|
|
564
|
+
// Revoke may have committed and returned while handleUpgrade was accepting
|
|
565
|
+
// this socket but before it became visible to the registry; in that case the
|
|
566
|
+
// fresh lookup must fail before the upstream can receive any frame.
|
|
567
|
+
if (!options.reauthorize()) {
|
|
568
|
+
closeSocket(client, 4403, "Session Portal grant revoked");
|
|
569
|
+
// `ws` emits an error when a CONNECTING client is terminated; this early
|
|
570
|
+
// branch returns before the normal upstream error handler is installed.
|
|
571
|
+
upstream.on("error", () => undefined);
|
|
572
|
+
upstream.terminate();
|
|
573
|
+
options.upstreamSockets.delete(upstream);
|
|
574
|
+
untrack();
|
|
575
|
+
return;
|
|
576
|
+
}
|
|
432
577
|
const pending = [];
|
|
433
578
|
let pendingBytes = 0;
|
|
434
579
|
let closed = false;
|
|
@@ -449,10 +594,17 @@ function bridgePortalWebSocket(client, kind, requestUrl, grant, options) {
|
|
|
449
594
|
pending.length = 0;
|
|
450
595
|
pendingBytes = 0;
|
|
451
596
|
options.upstreamSockets.delete(upstream);
|
|
597
|
+
untrack();
|
|
452
598
|
options.shutdownSignal.removeEventListener("abort", stopForShutdown);
|
|
453
599
|
};
|
|
454
600
|
client.on("message", (data, isBinary) => {
|
|
455
|
-
|
|
601
|
+
const current = options.reauthorize();
|
|
602
|
+
if (!current) {
|
|
603
|
+
closeSocket(client, 4403, "Session Portal grant revoked");
|
|
604
|
+
closeSocket(upstream, 4403, "Session Portal grant revoked");
|
|
605
|
+
return;
|
|
606
|
+
}
|
|
607
|
+
if (current.access === "read_only") {
|
|
456
608
|
if (kind === "terminal")
|
|
457
609
|
return;
|
|
458
610
|
if (!allowedReadOnlySocketFrame(kind, data, isBinary)) {
|
|
@@ -476,6 +628,11 @@ function bridgePortalWebSocket(client, kind, requestUrl, grant, options) {
|
|
|
476
628
|
pendingBytes += frame.byteLength;
|
|
477
629
|
});
|
|
478
630
|
upstream.on("open", () => {
|
|
631
|
+
if (!options.reauthorize()) {
|
|
632
|
+
closeSocket(client, 4403, "Session Portal grant revoked");
|
|
633
|
+
closeSocket(upstream, 4403, "Session Portal grant revoked");
|
|
634
|
+
return;
|
|
635
|
+
}
|
|
479
636
|
for (const frame of pending.splice(0)) {
|
|
480
637
|
pendingBytes -= frame.data.byteLength;
|
|
481
638
|
if (!forwardSocketFrame(client, upstream, frame.data, frame.isBinary))
|
|
@@ -483,6 +640,11 @@ function bridgePortalWebSocket(client, kind, requestUrl, grant, options) {
|
|
|
483
640
|
}
|
|
484
641
|
});
|
|
485
642
|
upstream.on("message", (data, isBinary) => {
|
|
643
|
+
if (!options.reauthorize()) {
|
|
644
|
+
closeSocket(client, 4403, "Session Portal grant revoked");
|
|
645
|
+
closeSocket(upstream, 4403, "Session Portal grant revoked");
|
|
646
|
+
return;
|
|
647
|
+
}
|
|
486
648
|
forwardSocketFrame(upstream, client, data, isBinary);
|
|
487
649
|
});
|
|
488
650
|
client.on("close", (code, reason) => {
|
|
@@ -501,6 +663,21 @@ function bridgePortalWebSocket(client, kind, requestUrl, grant, options) {
|
|
|
501
663
|
closeSocket(client, 1011, "Control service unavailable");
|
|
502
664
|
});
|
|
503
665
|
}
|
|
666
|
+
function sessionPortalSocketCredential(request) {
|
|
667
|
+
let url;
|
|
668
|
+
try {
|
|
669
|
+
url = new URL(request.url ?? "", "http://localhost");
|
|
670
|
+
}
|
|
671
|
+
catch {
|
|
672
|
+
return undefined;
|
|
673
|
+
}
|
|
674
|
+
const portalId = url.searchParams.get(SESSION_PORTAL_QUERY) ?? "";
|
|
675
|
+
if (!PORTAL_ID_PATTERN.test(portalId))
|
|
676
|
+
return undefined;
|
|
677
|
+
const secret = cookieValue(request.headers.cookie, cookieName(portalId)) ||
|
|
678
|
+
url.searchParams.get(SESSION_PORTAL_SECRET_QUERY) || undefined;
|
|
679
|
+
return secret ? { portalId, secret } : undefined;
|
|
680
|
+
}
|
|
504
681
|
function portalSocketRoute(rawUrl) {
|
|
505
682
|
let pathname;
|
|
506
683
|
try {
|
|
@@ -607,8 +784,15 @@ function isAllowedSessionMutation(ctx, encodedSessionId) {
|
|
|
607
784
|
if (ctx.method === "POST") {
|
|
608
785
|
return /^(?:\/interrupt|\/messages(?:\/queue)?|\/terminal\/start|\/resources|\/interactions\/[^/]+\/resolve|\/browser\/(?:open|close|pages)|\/browser\/pages\/[^/]+\/(?:close|activate|back|forward|reload|navigate)|\/emulator\/(?:attach|detach|tap|button|type|rotate|launch|gesture))$/.test(suffix);
|
|
609
786
|
}
|
|
787
|
+
if (ctx.method === "PUT") {
|
|
788
|
+
return suffix === "/execution" || suffix === "/browser/request-headers";
|
|
789
|
+
}
|
|
610
790
|
return ctx.method === "DELETE" && /^\/resources\/[^/]+$/.test(suffix);
|
|
611
791
|
}
|
|
792
|
+
function isBrowserRequestHeadersRoute(ctx, encodedSessionId) {
|
|
793
|
+
const prefix = `${SESSION_COLLECTION_API_PATH}/${encodedSessionId}`;
|
|
794
|
+
return ctx.path.slice(prefix.length) === "/browser/request-headers";
|
|
795
|
+
}
|
|
612
796
|
function denyMutation(ctx, grant) {
|
|
613
797
|
if (grant.access === "read_write")
|
|
614
798
|
return true;
|
|
@@ -635,10 +819,63 @@ function publicAuthorization(grant) {
|
|
|
635
819
|
portalId: grant.portalId,
|
|
636
820
|
scope: grant.scope,
|
|
637
821
|
access: grant.access,
|
|
822
|
+
...(grant.embedOrigin ? { embedOrigin: grant.embedOrigin } : {}),
|
|
638
823
|
...(grant.sessionId ? { sessionId: grant.sessionId } : {}),
|
|
639
824
|
expiresAt: grant.expiresAt,
|
|
640
825
|
};
|
|
641
826
|
}
|
|
827
|
+
function injectEmbeddedPortalBootstrap(html, portalId) {
|
|
828
|
+
const id = JSON.stringify(portalId).replace(/</g, "\\u003c");
|
|
829
|
+
const script = `<script>(()=>{try{const raw=sessionStorage.getItem('rynx_session_portal_'+${id});` +
|
|
830
|
+
`const value=raw?JSON.parse(raw):null;if(value&&value.portalId===${id})window.rynxSessionPortal=value}` +
|
|
831
|
+
`catch{}})()</script>`;
|
|
832
|
+
return html.replace("</head>", `${script}</head>`);
|
|
833
|
+
}
|
|
834
|
+
function embeddedPageBootstrap(ctx, ports) {
|
|
835
|
+
if (ctx.method !== "GET" && ctx.method !== "HEAD")
|
|
836
|
+
return undefined;
|
|
837
|
+
const match = RUNTIME_SESSION_PATH.exec(ctx.path);
|
|
838
|
+
if (!match)
|
|
839
|
+
return undefined;
|
|
840
|
+
const portalId = typeof ctx.query[SESSION_PORTAL_QUERY] === "string"
|
|
841
|
+
? ctx.query[SESSION_PORTAL_QUERY].trim()
|
|
842
|
+
: "";
|
|
843
|
+
const frameOrigin = sessionPortalEmbedOrigin(ctx);
|
|
844
|
+
if (!PORTAL_ID_PATTERN.test(portalId) || !frameOrigin)
|
|
845
|
+
return undefined;
|
|
846
|
+
const grant = ports.inspect(portalId);
|
|
847
|
+
if (!grant || grant.embedOrigin !== frameOrigin)
|
|
848
|
+
return undefined;
|
|
849
|
+
const requestedSessionId = decoded(match[1]);
|
|
850
|
+
if (grant.scope === "single_session" &&
|
|
851
|
+
requestedSessionId !== grant.sessionId)
|
|
852
|
+
return undefined;
|
|
853
|
+
if (requestedSessionId && !ports.ownsSession(grant.pluginId, requestedSessionId)) {
|
|
854
|
+
return undefined;
|
|
855
|
+
}
|
|
856
|
+
return { portalId, frameOrigin };
|
|
857
|
+
}
|
|
858
|
+
function sessionPortalEmbedOrigin(ctx) {
|
|
859
|
+
const value = typeof ctx.query[SESSION_PORTAL_EMBED_ORIGIN_QUERY] === "string"
|
|
860
|
+
? ctx.query[SESSION_PORTAL_EMBED_ORIGIN_QUERY]
|
|
861
|
+
: "";
|
|
862
|
+
return normalizedFrameOrigin(value);
|
|
863
|
+
}
|
|
864
|
+
function normalizedFrameOrigin(value) {
|
|
865
|
+
const candidate = value.trim();
|
|
866
|
+
if (!candidate || candidate.length > 512)
|
|
867
|
+
return undefined;
|
|
868
|
+
try {
|
|
869
|
+
const parsed = new URL(candidate);
|
|
870
|
+
if ((parsed.protocol !== "http:" && parsed.protocol !== "https:") ||
|
|
871
|
+
parsed.origin !== candidate)
|
|
872
|
+
return undefined;
|
|
873
|
+
return parsed.origin;
|
|
874
|
+
}
|
|
875
|
+
catch {
|
|
876
|
+
return undefined;
|
|
877
|
+
}
|
|
878
|
+
}
|
|
642
879
|
function injectPortalBootstrap(html, grant) {
|
|
643
880
|
const value = JSON.stringify(publicAuthorization(grant)).replace(/</g, "\\u003c");
|
|
644
881
|
return html.replace("</head>", `<script>window.rynxSessionPortal=${value}</script></head>`);
|
|
@@ -742,5 +979,5 @@ function closeHttpServerImmediately(server) {
|
|
|
742
979
|
});
|
|
743
980
|
}
|
|
744
981
|
function redeemPage() {
|
|
745
|
-
return `<!doctype html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Rynx Sessions</title><style>body{font:14px Inter,system-ui;margin:0;display:grid;min-height:100vh;place-items:center;background:#f6f6f8;color:#17171b}.box{text-align:center}.spin{width:22px;height:22px;margin:0 auto 14px;border:2px solid #d8d8df;border-top-color:#6d5ce7;border-radius:50%;animation:s .8s linear infinite}@keyframes s{to{transform:rotate(360deg)}}</style></head><body><main class="box"><div class="spin"></div><p id="status">正在打开 Rynx Sessions…</p></main><script>(()=>{const el=document.getElementById('status');const p=new URLSearchParams(location.hash.slice(1));const ticket=p.get('ticket');history.replaceState(null,'',location.pathname);if(!ticket){el.textContent='访问链接缺少票据。';return}fetch(location.pathname,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({ticket})}).then(async r=>{const b=await r.json();if(!r.ok)throw new Error(b.error||'票据兑换失败');location.replace(b.location)}).catch(e=>{el.textContent='无法打开:'+e.message})})();</script></body></html>`;
|
|
982
|
+
return `<!doctype html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Rynx Sessions</title><style>body{font:14px Inter,system-ui;margin:0;display:grid;min-height:100vh;place-items:center;background:#f6f6f8;color:#17171b}.box{text-align:center}.spin{width:22px;height:22px;margin:0 auto 14px;border:2px solid #d8d8df;border-top-color:#6d5ce7;border-radius:50%;animation:s .8s linear infinite}@keyframes s{to{transform:rotate(360deg)}}</style></head><body><main class="box"><div class="spin"></div><p id="status">正在打开 Rynx Sessions…</p></main><script>(()=>{const el=document.getElementById('status');const p=new URLSearchParams(location.hash.slice(1));const ticket=p.get('ticket');history.replaceState(null,'',location.pathname+location.search);if(!ticket){el.textContent='访问链接缺少票据。';return}fetch(location.pathname+location.search,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({ticket})}).then(async r=>{const b=await r.json();if(!r.ok)throw new Error(b.error||'票据兑换失败');if(b.authorization){sessionStorage.setItem('rynx_session_portal_'+b.authorization.portalId,JSON.stringify(b.authorization))}location.replace(b.location)}).catch(e=>{el.textContent='无法打开:'+e.message})})();</script></body></html>`;
|
|
746
983
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rynx-ai/server",
|
|
3
|
-
"version": "0.1.11-beta.
|
|
3
|
+
"version": "0.1.11-beta.32",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/rynx-ai/rynx.git",
|
|
@@ -27,15 +27,15 @@
|
|
|
27
27
|
"@koa/router": "^15.4.0",
|
|
28
28
|
"koa": "^3.2.0",
|
|
29
29
|
"ws": "^8.21.0",
|
|
30
|
-
"@rynx-ai/
|
|
31
|
-
"@rynx-ai/
|
|
32
|
-
"@rynx-ai/
|
|
33
|
-
"@rynx-ai/remote-runtime-e2ee": "0.1.11-beta.
|
|
34
|
-
"@rynx-ai/runtime": "0.1.11-beta.
|
|
30
|
+
"@rynx-ai/plugin-sdk": "0.1.11-beta.32",
|
|
31
|
+
"@rynx-ai/protocol": "0.1.11-beta.32",
|
|
32
|
+
"@rynx-ai/core": "0.1.11-beta.32",
|
|
33
|
+
"@rynx-ai/remote-runtime-e2ee": "0.1.11-beta.32",
|
|
34
|
+
"@rynx-ai/runtime": "0.1.11-beta.32"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
37
|
"@types/ws": "^8.18.1",
|
|
38
|
-
"@rynx-ai/control-web": "0.1.11-beta.
|
|
38
|
+
"@rynx-ai/control-web": "0.1.11-beta.32"
|
|
39
39
|
},
|
|
40
40
|
"scripts": {
|
|
41
41
|
"build": "rm -rf dist && tsc -p tsconfig.json && mkdir -p dist/control-web && cp -R ../control-web/dist/. dist/control-web/",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{aj as o,ak as n}from"./mermaid-GHXKKRXX-BkcV3dgg.js";const t=(a,r)=>o.lang.round(n.parse(a)[r]);export{t as c};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{s as a,c as s,a as e,C as t}from"./chunk-V7JOEXUC-3UFQGnKX.js";import{_ as i}from"./mermaid-GHXKKRXX-BkcV3dgg.js";import"./chunk-5VM5RSS4-By6zfDWW.js";import"./chunk-XXDRQBXY-3znnutie.js";import"./chunk-VR4S4FIN-DjspjYUb.js";import"./chunk-32BRIVSS-BkfLE_TX.js";import"./index-B9SAq_63.js";var n={parser:e,get db(){return new t},renderer:s,styles:a,init:i(r=>{r.class||(r.class={}),r.class.arrowMarkerAbsolute=r.arrowMarkerAbsolute},"init")};export{n as diagram};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{s as a,c as s,a as e,C as t}from"./chunk-V7JOEXUC-3UFQGnKX.js";import{_ as i}from"./mermaid-GHXKKRXX-BkcV3dgg.js";import"./chunk-5VM5RSS4-By6zfDWW.js";import"./chunk-XXDRQBXY-3znnutie.js";import"./chunk-VR4S4FIN-DjspjYUb.js";import"./chunk-32BRIVSS-BkfLE_TX.js";import"./index-B9SAq_63.js";var n={parser:e,get db(){return new t},renderer:s,styles:a,init:i(r=>{r.class||(r.class={}),r.class.arrowMarkerAbsolute=r.arrowMarkerAbsolute},"init")};export{n as diagram};
|