@ggui-ai/mcp-server 0.15.0 → 0.16.0
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/api-renders-routes.d.ts +0 -7
- package/dist/api-renders-routes.d.ts.map +1 -1
- package/dist/api-renders-routes.js +5 -5
- package/dist/api-renders-stream-route.js +4 -4
- package/dist/auth.d.ts +3 -3
- package/dist/auth.js +6 -6
- package/dist/build-mcp.d.ts +1 -1
- package/dist/console-blueprint-routes.js +2 -2
- package/dist/console-chat-routes.d.ts +1 -1
- package/dist/console-chat-routes.js +1 -1
- package/dist/console-keys-routes.js +2 -2
- package/dist/console-llm-keys-routes.d.ts +1 -1
- package/dist/console-llm-keys-routes.js +5 -5
- package/dist/console-registry-routes.d.ts +1 -1
- package/dist/console-registry-routes.js +1 -1
- package/dist/console-theme-routes.d.ts +1 -1
- package/dist/console-theme-routes.js +1 -1
- package/dist/control-service.d.ts +3 -3
- package/dist/control-service.js +3 -3
- package/dist/ggui-session-channel/socket-router.js +2 -2
- package/dist/ggui-session-channel/subscribe.d.ts +1 -1
- package/dist/ggui-session-channel/subscribe.d.ts.map +1 -1
- package/dist/ggui-session-channel/subscribe.js +2 -2
- package/dist/ggui-session-channel/subscriber-lifecycle.d.ts +1 -1
- package/dist/ggui-session-channel/subscriber-lifecycle.js +1 -1
- package/dist/ggui-session-channel.d.ts +4 -4
- package/dist/ggui-session-channel.js +4 -4
- package/dist/health-routes.d.ts +7 -6
- package/dist/health-routes.d.ts.map +1 -1
- package/dist/health-routes.js +8 -7
- package/dist/index.js +1 -1
- package/dist/instructions-presets.js +5 -5
- package/dist/mcp-apps-outbound.d.ts +17 -11
- package/dist/mcp-apps-outbound.d.ts.map +1 -1
- package/dist/mcp-apps-outbound.js +53 -25
- package/dist/oauth-as-routes.d.ts +1 -1
- package/dist/oauth.d.ts +3 -3
- package/dist/oauth.js +3 -3
- package/dist/render-read-gate.d.ts +3 -3
- package/dist/render-read-gate.js +2 -2
- package/dist/runtime-bundle-hash.d.ts +1 -1
- package/dist/runtime-bundle-hash.js +1 -1
- package/dist/schema-compat.d.ts +2 -1
- package/dist/schema-compat.d.ts.map +1 -1
- package/dist/schema-compat.js +5 -5
- package/dist/server.d.ts +48 -76
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +29 -41
- package/dist/thread-transport.js +1 -1
- package/package.json +14 -14
|
@@ -44,7 +44,6 @@
|
|
|
44
44
|
* or the render was reset). Clients re-mount from /state.
|
|
45
45
|
*/
|
|
46
46
|
import type { AppMetadataStore, CodeStore, GguiSessionStore } from "@ggui-ai/mcp-server-core";
|
|
47
|
-
import { type AppThemeBase } from "@ggui-ai/mcp-server-handlers/renders";
|
|
48
47
|
import { type McpAppAiGguiRenderMeta } from "@ggui-ai/protocol/integrations/mcp-apps";
|
|
49
48
|
import type { Express } from "express";
|
|
50
49
|
import type { Logger } from "./logger.js";
|
|
@@ -57,12 +56,6 @@ interface MountOptions {
|
|
|
57
56
|
readonly secret: string;
|
|
58
57
|
/** Per-app metadata store for the publicEnv projection (optional). */
|
|
59
58
|
readonly appMetadataStore?: AppMetadataStore;
|
|
60
|
-
/**
|
|
61
|
-
* Runtime theme-registration resolver (ggui#598-C) — same contract
|
|
62
|
-
* as `RenderSliceMetaDeps.themeBaseProvider`; bound by the server
|
|
63
|
-
* composer so /state cannot drift from the result-meta emitters.
|
|
64
|
-
*/
|
|
65
|
-
readonly themeBaseProvider?: (appId: string, themeName: string) => Promise<AppThemeBase | null> | AppThemeBase | null;
|
|
66
59
|
/** Operator-picked theme preset id stamped on /state reads. */
|
|
67
60
|
readonly themeId?: string;
|
|
68
61
|
/** Operator-picked theme mode stamped on /state reads. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api-renders-routes.d.ts","sourceRoot":"","sources":["../src/api-renders-routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;
|
|
1
|
+
{"version":3,"file":"api-renders-routes.d.ts","sourceRoot":"","sources":["../src/api-renders-routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAQ9F,OAAO,EAGL,KAAK,sBAAsB,EAC5B,MAAM,yCAAyC,CAAC;AACjD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAE1C,UAAU,YAAY;IACpB,iCAAiC;IACjC,QAAQ,CAAC,GAAG,EAAE,OAAO,CAAC;IACtB,+CAA+C;IAC/C,QAAQ,CAAC,WAAW,EAAE,gBAAgB,CAAC;IACvC,wEAAwE;IACxE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,sEAAsE;IACtE,QAAQ,CAAC,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;IAC7C,+DAA+D;IAC/D,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,0DAA0D;IAC1D,QAAQ,CAAC,SAAS,CAAC,EAAE,sBAAsB,CAAC,WAAW,CAAC,CAAC;IACzD,sEAAsE;IACtE,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,CAAC;IAC/B,sEAAsE;IACtE,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC;;;;;;;OAOG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B;;;;;;OAMG;IACH,QAAQ,CAAC,iBAAiB,CAAC,EAAE,CAAC,IAAI,EAAE;QAClC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;KACvB,KAAK,MAAM,GAAG,SAAS,CAAC;IACzB,qEAAqE;IACrE,QAAQ,CAAC,aAAa,CAAC,EAAE,CACvB,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,MAAM,KACV;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC;IACzD,oEAAoE;IACpE,QAAQ,CAAC,iBAAiB,EAAE,MAAM,MAAM,CAAC;IACzC,iDAAiD;IACjD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,YAAY,GAAG,IAAI,CAgX9D"}
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
* or the render was reset). Clients re-mount from /state.
|
|
45
45
|
*/
|
|
46
46
|
import { verifyToken } from "@ggui-ai/mcp-server-core";
|
|
47
|
-
import { deriveContractBundle, derivePublicEnvProjection, deriveRenderMeta,
|
|
47
|
+
import { deriveContractBundle, derivePublicEnvProjection, deriveRenderMeta, spreadRenderMetaViewOntoSlice, } from "@ggui-ai/mcp-server-handlers/renders";
|
|
48
48
|
import { composeSessionApiUrls, MCP_APP_AI_GGUI_RENDER_META_KEY, } from "@ggui-ai/protocol/integrations/mcp-apps";
|
|
49
49
|
/**
|
|
50
50
|
* Mount `GET /api/sessions/:sessionId/state` +
|
|
@@ -90,7 +90,7 @@ export function mountApiRendersRoutes(opts) {
|
|
|
90
90
|
res.status(401).type("text/plain").send("wsToken invalid");
|
|
91
91
|
return;
|
|
92
92
|
}
|
|
93
|
-
//
|
|
93
|
+
// Binding gate: the wsToken's claimed sessionId MUST match the
|
|
94
94
|
// URL's sessionId. A wsToken minted for render A MUST NOT read
|
|
95
95
|
// render B's state.
|
|
96
96
|
if (verify.claims.sessionId !== sessionId) {
|
|
@@ -116,7 +116,7 @@ export function mountApiRendersRoutes(opts) {
|
|
|
116
116
|
res.status(404).type("text/plain").send("render not found");
|
|
117
117
|
return;
|
|
118
118
|
}
|
|
119
|
-
//
|
|
119
|
+
// App-scope gate (round 2): the wsToken's appId MUST match the
|
|
120
120
|
// render's appId. Closes the case where a render is created
|
|
121
121
|
// under a different appId than the token was minted for.
|
|
122
122
|
if (verify.claims.appId !== stored.appId) {
|
|
@@ -137,7 +137,7 @@ export function mountApiRendersRoutes(opts) {
|
|
|
137
137
|
// R6 addition: polling clients use it to initialize the R7 /events
|
|
138
138
|
// cursor (`?sinceSequence=N`) aligned with the WS stream.
|
|
139
139
|
const view = !isMcpApps
|
|
140
|
-
?
|
|
140
|
+
? deriveRenderMeta(render)
|
|
141
141
|
: undefined;
|
|
142
142
|
let statePublicEnv;
|
|
143
143
|
if (appMetadataStore && !isMcpApps) {
|
|
@@ -361,7 +361,7 @@ export function mountApiRendersRoutes(opts) {
|
|
|
361
361
|
res.status(404).type("text/plain").send("render not found");
|
|
362
362
|
return;
|
|
363
363
|
}
|
|
364
|
-
//
|
|
364
|
+
// App-scope gate (round 2): the wsToken's appId MUST match the
|
|
365
365
|
// render's appId. We need the render record to check — fetch
|
|
366
366
|
// it. listEventsSince validated the render exists.
|
|
367
367
|
const stored = await renderStore.get(sessionId);
|
|
@@ -63,7 +63,7 @@ class SseSink {
|
|
|
63
63
|
/**
|
|
64
64
|
* Invoked exactly once, from `end()`, BEFORE the response closes —
|
|
65
65
|
* the route assigns its teardown (clear heartbeat + detach) here so
|
|
66
|
-
* a channel-initiated `end` (
|
|
66
|
+
* a channel-initiated `end` (server shutdown, expiry probe) tears the
|
|
67
67
|
* subscriber down through the same single path as a client close.
|
|
68
68
|
*/
|
|
69
69
|
onEnd;
|
|
@@ -141,7 +141,7 @@ export function mountApiRendersStreamRoute(opts) {
|
|
|
141
141
|
res.status(401).type("text/plain").send("wsToken invalid");
|
|
142
142
|
return;
|
|
143
143
|
}
|
|
144
|
-
//
|
|
144
|
+
// Binding gate: the wsToken's claimed sessionId MUST match the
|
|
145
145
|
// URL's sessionId.
|
|
146
146
|
if (verify.claims.sessionId !== sessionId) {
|
|
147
147
|
res.status(401).type("text/plain").send("wsToken scope mismatch");
|
|
@@ -203,7 +203,7 @@ export function mountApiRendersStreamRoute(opts) {
|
|
|
203
203
|
res.status(404).type("text/plain").send("render not found");
|
|
204
204
|
return;
|
|
205
205
|
}
|
|
206
|
-
//
|
|
206
|
+
// App-scope gate (round 2): the wsToken's appId MUST match the
|
|
207
207
|
// render's appId.
|
|
208
208
|
if (verify.claims.appId !== stored.appId) {
|
|
209
209
|
res.status(401).type("text/plain").send("wsToken scope mismatch");
|
|
@@ -229,7 +229,7 @@ export function mountApiRendersStreamRoute(opts) {
|
|
|
229
229
|
res.write("retry: 3000\n\n");
|
|
230
230
|
const sink = new SseSink(res, logger);
|
|
231
231
|
// Single idempotent teardown shared by every exit path: client
|
|
232
|
-
// close, channel-initiated sink.end (
|
|
232
|
+
// close, channel-initiated sink.end (server shutdown / expiry probe),
|
|
233
233
|
// attach failure.
|
|
234
234
|
let detachFn = null;
|
|
235
235
|
let torn = false;
|
package/dist/auth.d.ts
CHANGED
|
@@ -29,10 +29,10 @@ export declare function resolveIdentity(adapter: AuthAdapter, req: Request): Pro
|
|
|
29
29
|
* Derive a stable `appId` from an auth result.
|
|
30
30
|
*
|
|
31
31
|
* In OSS single-user mode every identity collapses to `{kind:'builder'}`
|
|
32
|
-
* which doesn't carry
|
|
32
|
+
* which doesn't carry an app id. We fold these into a single
|
|
33
33
|
* well-known value (`DEFAULT_BUILDER_APP_ID`) so blueprint / vector
|
|
34
|
-
* scoping still works.
|
|
35
|
-
*
|
|
34
|
+
* scoping still works. A multi-app production deployment overrides
|
|
35
|
+
* this by passing `appIdFromIdentity` on `createGguiServer`.
|
|
36
36
|
*/
|
|
37
37
|
export declare const DEFAULT_BUILDER_APP_ID = "builder";
|
|
38
38
|
export declare function defaultAppIdFromIdentity(result: AuthResult): string;
|
package/dist/auth.js
CHANGED
|
@@ -47,10 +47,10 @@ export async function resolveIdentity(adapter, req) {
|
|
|
47
47
|
* Derive a stable `appId` from an auth result.
|
|
48
48
|
*
|
|
49
49
|
* In OSS single-user mode every identity collapses to `{kind:'builder'}`
|
|
50
|
-
* which doesn't carry
|
|
50
|
+
* which doesn't carry an app id. We fold these into a single
|
|
51
51
|
* well-known value (`DEFAULT_BUILDER_APP_ID`) so blueprint / vector
|
|
52
|
-
* scoping still works.
|
|
53
|
-
*
|
|
52
|
+
* scoping still works. A multi-app production deployment overrides
|
|
53
|
+
* this by passing `appIdFromIdentity` on `createGguiServer`.
|
|
54
54
|
*/
|
|
55
55
|
export const DEFAULT_BUILDER_APP_ID = 'builder';
|
|
56
56
|
export function defaultAppIdFromIdentity(result) {
|
|
@@ -65,9 +65,9 @@ export function defaultAppIdFromIdentity(result) {
|
|
|
65
65
|
result.identity.userId);
|
|
66
66
|
}
|
|
67
67
|
// `kind: 'app'` carries the appId directly — surfaced by API-key /
|
|
68
|
-
// OAuth-bearer adapters (e.g. an
|
|
69
|
-
// multi-
|
|
70
|
-
// here would discard the very
|
|
68
|
+
// OAuth-bearer adapters (e.g. an API-key adapter proving an app-scoped
|
|
69
|
+
// bearer on a multi-app deployment). Falling through to DEFAULT_BUILDER_APP_ID
|
|
70
|
+
// here would discard the very app id the adapter just proved.
|
|
71
71
|
if (result.identity.kind === 'app') {
|
|
72
72
|
return result.identity.appId;
|
|
73
73
|
}
|
package/dist/build-mcp.d.ts
CHANGED
|
@@ -65,7 +65,7 @@ export interface BuildMcpServerOptions {
|
|
|
65
65
|
/**
|
|
66
66
|
* Live-channel origins for the static shell's CSP declaration —
|
|
67
67
|
* forwarded to `installMcpAppsOutbound`. Deployments that set no
|
|
68
|
-
* `publicBaseUrl` (
|
|
68
|
+
* `publicBaseUrl` (a hosted deployment) pass their `wsUrl` + its ws→http
|
|
69
69
|
* origin flip here so the mounted iframe's `connect-src` covers the
|
|
70
70
|
* SSE / HTTP-polling session API and the WebSocket; otherwise
|
|
71
71
|
* cross-origin hosts CSP-block every network rung of the failover
|
|
@@ -121,8 +121,8 @@ export function mountConsoleBlueprintRoutes(opts) {
|
|
|
121
121
|
code = out;
|
|
122
122
|
}
|
|
123
123
|
// Same default appId the CLI's pairing-authenticated /mcp
|
|
124
|
-
// ingress resolves for single-
|
|
125
|
-
// scoped to the same
|
|
124
|
+
// ingress resolves for single-app OSS — the render is
|
|
125
|
+
// scoped to the same app.
|
|
126
126
|
const appId = DEFAULT_BUILDER_APP_ID;
|
|
127
127
|
// Phase B: a render IS the addressable unit; the prior
|
|
128
128
|
// (sessionId, stackItemId) pair collapses to a single
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
* serve` instance; adding bearer auth here would block the
|
|
31
31
|
* dev-page-is-usable claim without meaningful security gain.
|
|
32
32
|
*
|
|
33
|
-
* The render invocation uses `DEFAULT_BUILDER_APP_ID` for
|
|
33
|
+
* The render invocation uses `DEFAULT_BUILDER_APP_ID` for app
|
|
34
34
|
* scope — same well-known value the `/mcp` endpoint collapses to
|
|
35
35
|
* in OSS single-user mode. Matches blueprint + vector scoping
|
|
36
36
|
* applied by the generator / cache seams.
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
* serve` instance; adding bearer auth here would block the
|
|
31
31
|
* dev-page-is-usable claim without meaningful security gain.
|
|
32
32
|
*
|
|
33
|
-
* The render invocation uses `DEFAULT_BUILDER_APP_ID` for
|
|
33
|
+
* The render invocation uses `DEFAULT_BUILDER_APP_ID` for app
|
|
34
34
|
* scope — same well-known value the `/mcp` endpoint collapses to
|
|
35
35
|
* in OSS single-user mode. Matches blueprint + vector scoping
|
|
36
36
|
* applied by the generator / cache seams.
|
|
@@ -38,7 +38,7 @@ export function mountConsoleKeysRoutes(opts) {
|
|
|
38
38
|
const { app, adminToken, pairing, logger } = opts;
|
|
39
39
|
const requestHasAdminAuth = (req) => {
|
|
40
40
|
// Header path — `Authorization: Bearer <token>`. Constant-time
|
|
41
|
-
// compare not needed: this is single-
|
|
41
|
+
// compare not needed: this is single-operator local-host with a
|
|
42
42
|
// local network attacker model; the token also has 72 bits of
|
|
43
43
|
// entropy, so a timing-side-channel attack would still need
|
|
44
44
|
// ~2^36 attempts on average to materialize. Skip the cost.
|
|
@@ -72,7 +72,7 @@ export function mountConsoleKeysRoutes(opts) {
|
|
|
72
72
|
// For zero-config local-host, trust-proxy is OFF and req.secure
|
|
73
73
|
// reflects the literal socket. Operators behind a tunnel with
|
|
74
74
|
// TLS termination at the edge get the cookie WITHOUT Secure
|
|
75
|
-
// (intended — the
|
|
75
|
+
// (intended — the request reaching the server is plaintext HTTP). Browsers
|
|
76
76
|
// still scope it to the origin via SameSite, which is the
|
|
77
77
|
// primary CSRF protection here; Secure is a defense-in-depth
|
|
78
78
|
// attribute, not load-bearing for this token.
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* - `gateMode: 'admin-token'` (default — OSS-personal):
|
|
5
5
|
* gate accepts the admin bearer. Single global keyset; the
|
|
6
6
|
* /settings UI lets the operator paste keys for everyone.
|
|
7
|
-
* - `gateMode: 'auth-adapter'` (multi-
|
|
7
|
+
* - `gateMode: 'auth-adapter'` (multi-app): gate
|
|
8
8
|
* calls the server's `AuthAdapter`. Each user's keys are
|
|
9
9
|
* scoped by `scopeFromRequest` (default: `userId`/`appId`).
|
|
10
10
|
*
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* - `gateMode: 'admin-token'` (default — OSS-personal):
|
|
5
5
|
* gate accepts the admin bearer. Single global keyset; the
|
|
6
6
|
* /settings UI lets the operator paste keys for everyone.
|
|
7
|
-
* - `gateMode: 'auth-adapter'` (multi-
|
|
7
|
+
* - `gateMode: 'auth-adapter'` (multi-app): gate
|
|
8
8
|
* calls the server's `AuthAdapter`. Each user's keys are
|
|
9
9
|
* scoped by `scopeFromRequest` (default: `userId`/`appId`).
|
|
10
10
|
*
|
|
@@ -103,7 +103,7 @@ export function mountConsoleLlmKeysRoutes(opts) {
|
|
|
103
103
|
// admin-token → same shape as /keys (Bearer admin OR
|
|
104
104
|
// ggui_console_admin cookie).
|
|
105
105
|
// auth-adapter → calls `resolveIdentity(auth, req)`. `kind:'builder'`
|
|
106
|
-
// is rejected with 401 — the multi-
|
|
106
|
+
// is rejected with 401 — the multi-app posture
|
|
107
107
|
// is meaningless without a real per-caller id.
|
|
108
108
|
app.use("/ggui/console/llm-keys", (req, res, next) => {
|
|
109
109
|
if (gateMode === "admin-token") {
|
|
@@ -113,14 +113,14 @@ export function mountConsoleLlmKeysRoutes(opts) {
|
|
|
113
113
|
res.status(401).json({ error: "admin_auth_required" });
|
|
114
114
|
return;
|
|
115
115
|
}
|
|
116
|
-
// 'auth-adapter' — the multi-
|
|
116
|
+
// 'auth-adapter' — the multi-app gate.
|
|
117
117
|
resolveIdentity(auth, req)
|
|
118
118
|
.then((identity) => {
|
|
119
119
|
if (identity.identity.kind === "builder") {
|
|
120
120
|
applyDevtoolSecurityHeaders(res);
|
|
121
121
|
res.status(401).json({
|
|
122
|
-
error: "
|
|
123
|
-
message: "Multi-
|
|
122
|
+
error: "user_or_app_identity_required",
|
|
123
|
+
message: "Multi-app /llm-keys requires an end-user or app identity. " +
|
|
124
124
|
'The configured AuthAdapter resolved kind:"builder" — pair a ' +
|
|
125
125
|
'real user/app bearer or use providerKeysGate:"admin-token".',
|
|
126
126
|
});
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
* Scope: read-only on `/registry` (authoring happens on disk and the
|
|
27
27
|
* server re-reads on boot; zero-config is an honest empty shape).
|
|
28
28
|
* The cache/registry surfaces are scoped to `DEFAULT_BUILDER_APP_ID`
|
|
29
|
-
* because the OSS server is single-
|
|
29
|
+
* because the OSS server is single-app by construction — same
|
|
30
30
|
* scope the render handler writes to, so list/invalidate/clear see
|
|
31
31
|
* what the real cache writes.
|
|
32
32
|
*
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
* Scope: read-only on `/registry` (authoring happens on disk and the
|
|
27
27
|
* server re-reads on boot; zero-config is an honest empty shape).
|
|
28
28
|
* The cache/registry surfaces are scoped to `DEFAULT_BUILDER_APP_ID`
|
|
29
|
-
* because the OSS server is single-
|
|
29
|
+
* because the OSS server is single-app by construction — same
|
|
30
30
|
* scope the render handler writes to, so list/invalidate/clear see
|
|
31
31
|
* what the real cache writes.
|
|
32
32
|
*
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
* routes use — operator-only since the value persists to ggui.json,
|
|
23
23
|
* which the OSS server treats as trusted manifest input. End-users
|
|
24
24
|
* who paired into a render don't get to mutate the project's
|
|
25
|
-
* theme. Multi-
|
|
25
|
+
* theme. Multi-app deployments may relax this in a follow-up by
|
|
26
26
|
* scoping overrides per user (overrides become server state, not
|
|
27
27
|
* file state).
|
|
28
28
|
*/
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
* routes use — operator-only since the value persists to ggui.json,
|
|
23
23
|
* which the OSS server treats as trusted manifest input. End-users
|
|
24
24
|
* who paired into a render don't get to mutate the project's
|
|
25
|
-
* theme. Multi-
|
|
25
|
+
* theme. Multi-app deployments may relax this in a follow-up by
|
|
26
26
|
* scoping overrides per user (overrides become server state, not
|
|
27
27
|
* file state).
|
|
28
28
|
*/
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* ggui serves exactly TWO MCP surfaces:
|
|
5
5
|
*
|
|
6
6
|
* - **Data plane** — the agent routes (`universalMcpPath`, plus the
|
|
7
|
-
* per-
|
|
7
|
+
* per-app `${pathPrefix}/:appId` variant when configured). Carries
|
|
8
8
|
* the `agent` + `runtime` audiences: the tools an agent calls while a
|
|
9
9
|
* session is running.
|
|
10
10
|
* - **Control plane** — this service. Carries the `protocol` + `ops`
|
|
@@ -93,7 +93,7 @@ export declare function filterHandlersByAudience(set: ReadonlyArray<AnyHandler>,
|
|
|
93
93
|
* ruling (ggui#525, 2026-08-16). The argument that it is safe on the
|
|
94
94
|
* first call: the save is idempotent under the name-collapse rule (same
|
|
95
95
|
* slug ⇒ one row, a repeat is a no-op), it writes only to the CALLER's
|
|
96
|
-
* own library (no cross-
|
|
96
|
+
* own library (no cross-app or account-wide effect), and its caller
|
|
97
97
|
* is the console helper agent auto-saving after a render — the
|
|
98
98
|
* "confirm" it performed was an agent self-confirm with no human in
|
|
99
99
|
* the loop, which bought no safety and cost one extra tool round trip
|
|
@@ -116,7 +116,7 @@ export declare function stripAudience<I extends ZodRawShape, O extends ZodRawSha
|
|
|
116
116
|
* credential — or presented one the adapter rejected. An un-gated ops
|
|
117
117
|
* handler would then run against that synthetic rather than refusing,
|
|
118
118
|
* which is how a caller with no account would read and write a phantom
|
|
119
|
-
* shared
|
|
119
|
+
* shared app.
|
|
120
120
|
*
|
|
121
121
|
* The gate is on `ctx.authSource`, not on which identity FIELDS are
|
|
122
122
|
* populated: every deployment tier proves identity differently
|
package/dist/control-service.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* ggui serves exactly TWO MCP surfaces:
|
|
5
5
|
*
|
|
6
6
|
* - **Data plane** — the agent routes (`universalMcpPath`, plus the
|
|
7
|
-
* per-
|
|
7
|
+
* per-app `${pathPrefix}/:appId` variant when configured). Carries
|
|
8
8
|
* the `agent` + `runtime` audiences: the tools an agent calls while a
|
|
9
9
|
* session is running.
|
|
10
10
|
* - **Control plane** — this service. Carries the `protocol` + `ops`
|
|
@@ -94,7 +94,7 @@ export function filterHandlersByAudience(set, allowed) {
|
|
|
94
94
|
* ruling (ggui#525, 2026-08-16). The argument that it is safe on the
|
|
95
95
|
* first call: the save is idempotent under the name-collapse rule (same
|
|
96
96
|
* slug ⇒ one row, a repeat is a no-op), it writes only to the CALLER's
|
|
97
|
-
* own library (no cross-
|
|
97
|
+
* own library (no cross-app or account-wide effect), and its caller
|
|
98
98
|
* is the console helper agent auto-saving after a render — the
|
|
99
99
|
* "confirm" it performed was an agent self-confirm with no human in
|
|
100
100
|
* the loop, which bought no safety and cost one extra tool round trip
|
|
@@ -137,7 +137,7 @@ export function stripAudience(h) {
|
|
|
137
137
|
* credential — or presented one the adapter rejected. An un-gated ops
|
|
138
138
|
* handler would then run against that synthetic rather than refusing,
|
|
139
139
|
* which is how a caller with no account would read and write a phantom
|
|
140
|
-
* shared
|
|
140
|
+
* shared app.
|
|
141
141
|
*
|
|
142
142
|
* The gate is on `ctx.authSource`, not on which identity FIELDS are
|
|
143
143
|
* populated: every deployment tier proves identity differently
|
|
@@ -72,7 +72,7 @@ export function attachSocketRouter(wss, deps) {
|
|
|
72
72
|
releasePending(ws);
|
|
73
73
|
}
|
|
74
74
|
/**
|
|
75
|
-
*
|
|
75
|
+
* Binding guard for client-emitted observation messages
|
|
76
76
|
* (`host_context_observed` today). Returns `false`
|
|
77
77
|
* AND emits the appropriate error frame when:
|
|
78
78
|
*
|
|
@@ -80,7 +80,7 @@ export function attachSocketRouter(wss, deps) {
|
|
|
80
80
|
* - payload.sessionId doesn't match the subscriber binding
|
|
81
81
|
* (SESSION_MISMATCH)
|
|
82
82
|
*
|
|
83
|
-
* Subscriber binding is the authoritative
|
|
83
|
+
* Subscriber binding is the authoritative scope. The wire
|
|
84
84
|
* payload's sessionId is belt-and-suspenders so the error message
|
|
85
85
|
* can be specific; appId narrows transparently via the binding.
|
|
86
86
|
*/
|
|
@@ -138,7 +138,7 @@ export interface SubscribeDeps {
|
|
|
138
138
|
readonly bootstrap?: GguiSessionChannelBootstrap;
|
|
139
139
|
/** Console cookie plumbing — see `GguiSessionChannelOptions.cookieAuth`. */
|
|
140
140
|
readonly cookieAuth?: GguiSessionChannelCookieAuth;
|
|
141
|
-
/** Identity →
|
|
141
|
+
/** Identity → appId mapping — see `GguiSessionChannelOptions.appIdFromIdentity`. */
|
|
142
142
|
readonly appIdFromIdentity?: (result: AuthResult) => string;
|
|
143
143
|
/** Version-handshake policy — see `GguiSessionChannelOptions.versionPolicy`. */
|
|
144
144
|
readonly versionPolicy?: "advisory" | "reject";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"subscribe.d.ts","sourceRoot":"","sources":["../../src/ggui-session-channel/subscribe.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EACV,WAAW,EACX,UAAU,EACV,gBAAgB,EAChB,uBAAuB,EACvB,iBAAiB,EACjB,YAAY,EACb,MAAM,0BAA0B,CAAC;AAGlC,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uCAAuC,CAAC;AAC9E,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AACjD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,IAAI,CAAC;AAMpC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,KAAK,EAGV,cAAc,EAEf,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAgB,KAAK,QAAQ,EAAE,MAAM,eAAe,CAAC;AAC5D,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AAErE;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH;;;;;;;;;;GAUG;AACH,MAAM,MAAM,uCAAuC,GAC/C;IACE,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;IAClB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB,GACD;IAAE,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,SAAS,GAAG,SAAS,CAAA;CAAE,CAAC;AAEnE;;;;;;GAMG;AACH,MAAM,MAAM,wCAAwC,GAChD;IACE,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;IAClB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B,GACD;IAAE,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,eAAe,GAAG,SAAS,CAAA;CAAE,CAAC;AAEzE,MAAM,WAAW,2BAA2B;IAC1C;;;;;;;;OAQG;IACH,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,uCAAuC,CAAC;IAC/D;;;;OAIG;IACH,iBAAiB,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC;IAC5D;;;;;;;;;;;;;OAaG;IACH,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,wCAAwC,CAAC;CAClE;AAED;;;;GAIG;AACH,MAAM,WAAW,4BAA4B;IAC3C;;;;OAIG;IACH,UAAU,CAAC,OAAO,EAAE,OAAO,WAAW,EAAE,mBAAmB,GAAG,MAAM,GAAG,IAAI,CAAC;IAC5E;;;;OAIG;IACH,MAAM,CAAC,WAAW,EAAE,MAAM,GAAG;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;CAC1E;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,kEAAkE;IAClE,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC;IAC3B,QAAQ,CAAC,WAAW,EAAE,gBAAgB,CAAC;IACvC,sEAAsE;IACtE,QAAQ,CAAC,YAAY,EAAE,uBAAuB,CAAC;IAC/C,mEAAmE;IACnE,QAAQ,CAAC,YAAY,EAAE,YAAY,CAAC;IACpC,iFAAiF;IACjF,QAAQ,CAAC,SAAS,CAAC,EAAE,2BAA2B,CAAC;IACjD,4EAA4E;IAC5E,QAAQ,CAAC,UAAU,CAAC,EAAE,4BAA4B,CAAC;IACnD,
|
|
1
|
+
{"version":3,"file":"subscribe.d.ts","sourceRoot":"","sources":["../../src/ggui-session-channel/subscribe.ts"],"names":[],"mappings":"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"}
|
|
@@ -238,8 +238,8 @@ export function createSubscribeHandlers(deps) {
|
|
|
238
238
|
// cookie binds the same pair), else the deployment's identity →
|
|
239
239
|
// appId mapping (`appIdFromIdentity`, defaulting to
|
|
240
240
|
// `defaultAppIdFromIdentity`). The resolved value then flows
|
|
241
|
-
// through the EXISTING
|
|
242
|
-
// an `undefined`
|
|
241
|
+
// through the EXISTING app-scope gate + provisioning below — never
|
|
242
|
+
// an `undefined` appId on a stored row.
|
|
243
243
|
const effectiveAppId = payload.appId ??
|
|
244
244
|
tokenBoundAppId ??
|
|
245
245
|
cookieBound?.appId ??
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* Owns the per-render subscriber counter that drives the
|
|
15
15
|
* `onFirstSubscriber` / `onLastSubscriberGone` 0↔1 transition hooks —
|
|
16
16
|
* no other module reads it. The hooks fire for SSE attaches too:
|
|
17
|
-
* hosted cross-
|
|
17
|
+
* hosted cross-replica pubsub scoping keys on "does this replica hold any
|
|
18
18
|
* subscriber for this render", regardless of transport.
|
|
19
19
|
*/
|
|
20
20
|
import type { WebSocket } from "ws";
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* Owns the per-render subscriber counter that drives the
|
|
15
15
|
* `onFirstSubscriber` / `onLastSubscriberGone` 0↔1 transition hooks —
|
|
16
16
|
* no other module reads it. The hooks fire for SSE attaches too:
|
|
17
|
-
* hosted cross-
|
|
17
|
+
* hosted cross-replica pubsub scoping keys on "does this replica hold any
|
|
18
18
|
* subscriber for this render", regardless of transport.
|
|
19
19
|
*/
|
|
20
20
|
export function createSubscriberLifecycle(deps) {
|
|
@@ -159,7 +159,7 @@ export interface GguiSessionChannelOptions {
|
|
|
159
159
|
*/
|
|
160
160
|
readonly auth: AuthAdapter;
|
|
161
161
|
/**
|
|
162
|
-
* Maps resolved identity →
|
|
162
|
+
* Maps resolved identity → the appId for subscribes that omit
|
|
163
163
|
* `payload.appId` (SPEC §12.2 identity-default resolution). Defaults
|
|
164
164
|
* to `defaultAppIdFromIdentity` — same mapping the `/mcp` endpoint
|
|
165
165
|
* uses. Token-bound subscribes (wsToken / console cookie) never
|
|
@@ -348,12 +348,12 @@ export interface GguiSessionChannelOptions {
|
|
|
348
348
|
* render connects to this server instance).
|
|
349
349
|
*
|
|
350
350
|
* Hosted deployments use this to lazily SUBSCRIBE to the per-render
|
|
351
|
-
* cross-
|
|
351
|
+
* cross-replica broadcast channel (e.g. Redis pub/sub); OSS has no use
|
|
352
352
|
* for it (in-process broadcasts already route via
|
|
353
353
|
* {@link GguiSessionChannelServer.sendPropsUpdate}). Bounding pubsub
|
|
354
|
-
* fan-in to only renders a
|
|
354
|
+
* fan-in to only renders a replica actually holds connections for is a
|
|
355
355
|
* correctness requirement, not an optimization — without it every
|
|
356
|
-
*
|
|
356
|
+
* replica receives every other replica's broadcast for every active render.
|
|
357
357
|
*
|
|
358
358
|
* Best-effort: a thrown callback is logged and swallowed.
|
|
359
359
|
* `register()` MUST NOT fail because of a hook error or the
|
|
@@ -339,18 +339,18 @@ export function createGguiSessionChannelServer(opts) {
|
|
|
339
339
|
// Close code 1012 ("Service Restart", RFC 6455 + IANA registry)
|
|
340
340
|
// signals to clients that the server is restarting and they
|
|
341
341
|
// should reconnect immediately rather than treat the close as
|
|
342
|
-
// permanent.
|
|
343
|
-
// a new
|
|
342
|
+
// permanent. A rolling update of the deployment fits this exactly:
|
|
343
|
+
// a new replica is already accepting connections behind the same
|
|
344
344
|
// load balancer; iframe-runtime + console viewer should
|
|
345
345
|
// reconnect on next message instead of blinking "disconnected".
|
|
346
346
|
// Code 1001 (used previously) means "endpoint going away" with
|
|
347
|
-
// no reconnect hint — semantically inaccurate for the
|
|
347
|
+
// no reconnect hint — semantically inaccurate for the rolling-update
|
|
348
348
|
// case and the wrong signal for client reconnect logic.
|
|
349
349
|
const sessionIds = new Set();
|
|
350
350
|
for (const sub of wsSubscribers) {
|
|
351
351
|
sessionIds.add(sub.sessionId);
|
|
352
352
|
// Transport-neutral shutdown: WS maps to close(1012), SSE ends
|
|
353
|
-
// the response so EventSource auto-reconnects to the new
|
|
353
|
+
// the response so EventSource auto-reconnects to the new replica —
|
|
354
354
|
// exactly the 1012 semantics. `sink.end` is best-effort by
|
|
355
355
|
// contract (never throws on an already-closing transport).
|
|
356
356
|
sub.sink.end("service_restart");
|
package/dist/health-routes.d.ts
CHANGED
|
@@ -14,16 +14,17 @@
|
|
|
14
14
|
* 401 otherwise (with `WWW-Authenticate` when
|
|
15
15
|
* OAuth is enabled).
|
|
16
16
|
*
|
|
17
|
-
* Why live and health are separate:
|
|
18
|
-
* have distinct semantics. Liveness asks "is the process alive?" and
|
|
19
|
-
* a failure tells the
|
|
20
|
-
* the
|
|
17
|
+
* Why live and health are separate: an orchestrator's liveness and
|
|
18
|
+
* readiness probes have distinct semantics. Liveness asks "is the process alive?" and
|
|
19
|
+
* a failure tells the orchestrator to RESTART the container. Readiness
|
|
20
|
+
* asks "is the replica ready to receive traffic?" and a failure tells the service
|
|
21
21
|
* to STOP ROUTING. Tying both probes to a single endpoint that gates
|
|
22
22
|
* on dependency health (Redis, DDB, RAG) means a transient upstream
|
|
23
|
-
* blip kills the
|
|
23
|
+
* blip kills the replica entirely instead of just removing it from
|
|
24
24
|
* rotation.
|
|
25
25
|
*
|
|
26
|
-
* Self-hoster wiring (
|
|
26
|
+
* Self-hoster wiring (probe-spec shape; any orchestrator with separate
|
|
27
|
+
* liveness and readiness probes works the same way):
|
|
27
28
|
*
|
|
28
29
|
* livenessProbe: { httpGet: { path: '/ggui/live', port } }
|
|
29
30
|
* readinessProbe: { httpGet: { path: '/ggui/health', port } }
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"health-routes.d.ts","sourceRoot":"","sources":["../src/health-routes.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"health-routes.d.ts","sourceRoot":"","sources":["../src/health-routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAC5D,OAAO,KAAK,EAAE,OAAO,EAAqB,MAAM,SAAS,CAAC;AAE1D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AACjD,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,2BAA2B,CAAC;AAC1E,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAW1C,UAAU,YAAY;IACpB,iCAAiC;IACjC,QAAQ,CAAC,GAAG,EAAE,OAAO,CAAC;IACtB,+DAA+D;IAC/D,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAC1B,wDAAwD;IACxD,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,iEAAiE;IACjE,QAAQ,CAAC,eAAe,EAAE,aAAa,CAAC;QACtC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QACtB,QAAQ,CAAC,KAAK,EAAE,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;KAClD,CAAC,CAAC;IACH;;;;;;;;;;OAUG;IACH,QAAQ,CAAC,cAAc,CAAC,EAAE,aAAa,CAAC;QACtC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QACtB,QAAQ,CAAC,KAAK,EAAE,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;KAClD,CAAC,CAAC;IACH;;;;OAIG;IACH,QAAQ,CAAC,UAAU,EAAE,MAAM,wBAAwB,GAAG,IAAI,CAAC;IAC3D;;;;OAIG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE;QAAE,QAAQ,CAAC,UAAU,CAAC,EAAE,SAAS,GAAG,WAAW,CAAA;KAAE,CAAC;IACrE,gEAAgE;IAChE,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC;IAC3B,iEAAiE;IACjE,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAC;IAC/B,uDAAuD;IACvD,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IACjC,4DAA4D;IAC5D,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB;gEAC4D;IAC5D,QAAQ,CAAC,WAAW,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;CAC9C;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,YAAY,GAAG,IAAI,CAmJ1D"}
|
package/dist/health-routes.js
CHANGED
|
@@ -14,16 +14,17 @@
|
|
|
14
14
|
* 401 otherwise (with `WWW-Authenticate` when
|
|
15
15
|
* OAuth is enabled).
|
|
16
16
|
*
|
|
17
|
-
* Why live and health are separate:
|
|
18
|
-
* have distinct semantics. Liveness asks "is the process alive?" and
|
|
19
|
-
* a failure tells the
|
|
20
|
-
* the
|
|
17
|
+
* Why live and health are separate: an orchestrator's liveness and
|
|
18
|
+
* readiness probes have distinct semantics. Liveness asks "is the process alive?" and
|
|
19
|
+
* a failure tells the orchestrator to RESTART the container. Readiness
|
|
20
|
+
* asks "is the replica ready to receive traffic?" and a failure tells the service
|
|
21
21
|
* to STOP ROUTING. Tying both probes to a single endpoint that gates
|
|
22
22
|
* on dependency health (Redis, DDB, RAG) means a transient upstream
|
|
23
|
-
* blip kills the
|
|
23
|
+
* blip kills the replica entirely instead of just removing it from
|
|
24
24
|
* rotation.
|
|
25
25
|
*
|
|
26
|
-
* Self-hoster wiring (
|
|
26
|
+
* Self-hoster wiring (probe-spec shape; any orchestrator with separate
|
|
27
|
+
* liveness and readiness probes works the same way):
|
|
27
28
|
*
|
|
28
29
|
* livenessProbe: { httpGet: { path: '/ggui/live', port } }
|
|
29
30
|
* readinessProbe: { httpGet: { path: '/ggui/health', port } }
|
|
@@ -31,7 +32,7 @@
|
|
|
31
32
|
import { resolveIdentity, UnauthenticatedError } from "./auth.js";
|
|
32
33
|
import { buildWwwAuthenticate, resolveIssuerUrl } from "./oauth.js";
|
|
33
34
|
/**
|
|
34
|
-
* 1s per-check timeout — a hung dependency must not block the
|
|
35
|
+
* 1s per-check timeout — a hung dependency must not block the orchestrator's
|
|
35
36
|
* readiness probe (/ggui/health), which itself runs on a short period.
|
|
36
37
|
* A timeout is treated as a failed check; the dependency is degraded
|
|
37
38
|
* either way.
|
package/dist/index.js
CHANGED
|
@@ -44,7 +44,7 @@ export { buildControlService, CONTROL_PATH, filterHandlersByAudience, SINGLE_CAL
|
|
|
44
44
|
// one derivation, no drift against the route the factory mounts.
|
|
45
45
|
export { computeRuntimeBundleHash, insertRuntimeBundleHash, resolveHashedRuntimeBundleUrl, resolveRuntimeBundleHash, } from './runtime-bundle-hash.js';
|
|
46
46
|
// Strict-CSP module-variant delivery (ggui#522 slice 2). The minter is
|
|
47
|
-
// exported for the same composed-handler deployments: a
|
|
47
|
+
// exported for the same composed-handler deployments: a deployment that binds
|
|
48
48
|
// `codeStore`/`codeBaseUrl` on its own render tool builds the minter
|
|
49
49
|
// from `resolveRuntimeBundleHash()` so its `codeModuleUrl`s name the
|
|
50
50
|
// exact variant family the co-resident factory routes serve.
|