@ggui-ai/mcp-server 0.2.0-alpha.3 → 0.3.0-rc.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.
Files changed (152) hide show
  1. package/dist/admin-blueprints-transport.d.ts.map +1 -1
  2. package/dist/admin-blueprints-transport.js +2 -1
  3. package/dist/admin-oauth-providers-transport.d.ts.map +1 -1
  4. package/dist/admin-oauth-providers-transport.js +7 -5
  5. package/dist/api-renders-routes.d.ts +85 -0
  6. package/dist/api-renders-routes.d.ts.map +1 -0
  7. package/dist/api-renders-routes.js +372 -0
  8. package/dist/build-mcp.d.ts +1 -1
  9. package/dist/build-mcp.d.ts.map +1 -1
  10. package/dist/build-mcp.js +39 -5
  11. package/dist/code-routes.d.ts +47 -0
  12. package/dist/code-routes.d.ts.map +1 -0
  13. package/dist/code-routes.js +81 -0
  14. package/dist/code-store-fs.js +2 -2
  15. package/dist/console-auth.d.ts +10 -10
  16. package/dist/console-auth.d.ts.map +1 -1
  17. package/dist/console-auth.js +5 -5
  18. package/dist/console-blueprint-routes.d.ts +71 -0
  19. package/dist/console-blueprint-routes.d.ts.map +1 -0
  20. package/dist/console-blueprint-routes.js +348 -0
  21. package/dist/console-chat-routes.d.ts +80 -0
  22. package/dist/console-chat-routes.d.ts.map +1 -0
  23. package/dist/console-chat-routes.js +182 -0
  24. package/dist/console-config-routes.d.ts +37 -0
  25. package/dist/console-config-routes.d.ts.map +1 -0
  26. package/dist/console-config-routes.js +91 -0
  27. package/dist/console-headers.d.ts +1 -1
  28. package/dist/console-info-routes.d.ts +84 -0
  29. package/dist/console-info-routes.d.ts.map +1 -0
  30. package/dist/console-info-routes.js +135 -0
  31. package/dist/console-keys-routes.d.ts +50 -0
  32. package/dist/console-keys-routes.d.ts.map +1 -0
  33. package/dist/console-keys-routes.js +222 -0
  34. package/dist/console-llm-keys-routes.d.ts +47 -0
  35. package/dist/console-llm-keys-routes.d.ts.map +1 -0
  36. package/dist/console-llm-keys-routes.js +443 -0
  37. package/dist/console-mcp-tools-routes.d.ts +41 -0
  38. package/dist/console-mcp-tools-routes.d.ts.map +1 -0
  39. package/dist/console-mcp-tools-routes.js +60 -0
  40. package/dist/console-registry-routes.d.ts +66 -0
  41. package/dist/console-registry-routes.d.ts.map +1 -0
  42. package/dist/console-registry-routes.js +276 -0
  43. package/dist/console-session-routes.d.ts +89 -0
  44. package/dist/console-session-routes.d.ts.map +1 -0
  45. package/dist/console-session-routes.js +385 -0
  46. package/dist/console-sessions-routes.d.ts +52 -0
  47. package/dist/console-sessions-routes.d.ts.map +1 -0
  48. package/dist/console-sessions-routes.js +106 -0
  49. package/dist/console-static-routes.d.ts +54 -0
  50. package/dist/console-static-routes.d.ts.map +1 -0
  51. package/dist/console-static-routes.js +190 -0
  52. package/dist/console-theme-routes.d.ts +3 -3
  53. package/dist/console-theme-routes.js +1 -1
  54. package/dist/console-timeline.d.ts +5 -5
  55. package/dist/console-timeline.d.ts.map +1 -1
  56. package/dist/console-timeline.js +27 -26
  57. package/dist/console-welcome.js +2 -2
  58. package/dist/email-login.d.ts.map +1 -1
  59. package/dist/email-login.js +2 -3
  60. package/dist/ggui-session-channel/action-ingress.d.ts +54 -0
  61. package/dist/ggui-session-channel/action-ingress.d.ts.map +1 -0
  62. package/dist/ggui-session-channel/action-ingress.js +228 -0
  63. package/dist/ggui-session-channel/channel-subscriptions.d.ts +97 -0
  64. package/dist/ggui-session-channel/channel-subscriptions.d.ts.map +1 -0
  65. package/dist/ggui-session-channel/channel-subscriptions.js +224 -0
  66. package/dist/ggui-session-channel/internal-types.d.ts +102 -0
  67. package/dist/ggui-session-channel/internal-types.d.ts.map +1 -0
  68. package/dist/ggui-session-channel/internal-types.js +6 -0
  69. package/dist/ggui-session-channel/outbound.d.ts +81 -0
  70. package/dist/ggui-session-channel/outbound.d.ts.map +1 -0
  71. package/dist/ggui-session-channel/outbound.js +174 -0
  72. package/dist/ggui-session-channel/socket-router.d.ts +38 -0
  73. package/dist/ggui-session-channel/socket-router.d.ts.map +1 -0
  74. package/dist/ggui-session-channel/socket-router.js +213 -0
  75. package/dist/ggui-session-channel/subscribe.d.ts +165 -0
  76. package/dist/ggui-session-channel/subscribe.d.ts.map +1 -0
  77. package/dist/ggui-session-channel/subscribe.js +370 -0
  78. package/dist/ggui-session-channel/subscriber-lifecycle.d.ts +40 -0
  79. package/dist/ggui-session-channel/subscriber-lifecycle.d.ts.map +1 -0
  80. package/dist/ggui-session-channel/subscriber-lifecycle.js +123 -0
  81. package/dist/ggui-session-channel.d.ts +425 -0
  82. package/dist/ggui-session-channel.d.ts.map +1 -0
  83. package/dist/ggui-session-channel.js +262 -0
  84. package/dist/health-routes.d.ts +76 -0
  85. package/dist/health-routes.d.ts.map +1 -0
  86. package/dist/health-routes.js +145 -0
  87. package/dist/index.d.ts +10 -11
  88. package/dist/index.d.ts.map +1 -1
  89. package/dist/index.js +8 -9
  90. package/dist/instructions-presets.d.ts +3 -3
  91. package/dist/instructions-presets.js +25 -25
  92. package/dist/llm-backed-negotiator.d.ts +68 -67
  93. package/dist/llm-backed-negotiator.d.ts.map +1 -1
  94. package/dist/llm-backed-negotiator.js +82 -248
  95. package/dist/mcp-apps-outbound.d.ts +47 -48
  96. package/dist/mcp-apps-outbound.d.ts.map +1 -1
  97. package/dist/mcp-apps-outbound.js +204 -183
  98. package/dist/mcp-endpoint-routes.d.ts +88 -0
  99. package/dist/mcp-endpoint-routes.d.ts.map +1 -0
  100. package/dist/mcp-endpoint-routes.js +359 -0
  101. package/dist/mcp-mounts.d.ts +2 -76
  102. package/dist/mcp-mounts.d.ts.map +1 -1
  103. package/dist/mcp-mounts.js +0 -76
  104. package/dist/oauth-as-routes.d.ts +60 -0
  105. package/dist/oauth-as-routes.d.ts.map +1 -0
  106. package/dist/oauth-as-routes.js +82 -0
  107. package/dist/oauth-clients-routes.d.ts +39 -0
  108. package/dist/oauth-clients-routes.d.ts.map +1 -0
  109. package/dist/oauth-clients-routes.js +87 -0
  110. package/dist/oauth-login-types.d.ts +1 -20
  111. package/dist/oauth-login-types.d.ts.map +1 -1
  112. package/dist/oauth-login-types.js +30 -7
  113. package/dist/oauth-login.d.ts.map +1 -1
  114. package/dist/oauth-login.js +3 -2
  115. package/dist/oauth-providers-store.d.ts.map +1 -1
  116. package/dist/oauth-providers-store.js +5 -5
  117. package/dist/oauth.d.ts +9 -8
  118. package/dist/oauth.d.ts.map +1 -1
  119. package/dist/oauth.js +41 -19
  120. package/dist/pairing-transport.d.ts.map +1 -1
  121. package/dist/pairing-transport.js +2 -1
  122. package/dist/request-context.d.ts +2 -2
  123. package/dist/request-context.js +2 -2
  124. package/dist/reserved-validators.d.ts.map +1 -1
  125. package/dist/reserved-validators.js +9 -1
  126. package/dist/route-param.d.ts +9 -0
  127. package/dist/route-param.d.ts.map +1 -0
  128. package/dist/route-param.js +10 -0
  129. package/dist/runtime-bundle-route.d.ts +43 -0
  130. package/dist/runtime-bundle-route.d.ts.map +1 -0
  131. package/dist/runtime-bundle-route.js +80 -0
  132. package/dist/schema-compat.d.ts +64 -62
  133. package/dist/schema-compat.d.ts.map +1 -1
  134. package/dist/schema-compat.js +23 -51
  135. package/dist/server.d.ts +179 -193
  136. package/dist/server.d.ts.map +1 -1
  137. package/dist/server.js +644 -3759
  138. package/dist/storage.d.ts +5 -5
  139. package/dist/storage.d.ts.map +1 -1
  140. package/dist/storage.js +5 -5
  141. package/dist/thread-transport.d.ts.map +1 -1
  142. package/dist/thread-transport.js +4 -3
  143. package/dist/user-session-auth.d.ts +7 -21
  144. package/dist/user-session-auth.d.ts.map +1 -1
  145. package/dist/user-session-auth.js +7 -28
  146. package/package.json +16 -15
  147. package/dist/mcp-apps-inbound.d.ts +0 -86
  148. package/dist/mcp-apps-inbound.d.ts.map +0 -1
  149. package/dist/mcp-apps-inbound.js +0 -283
  150. package/dist/render-channel.d.ts +0 -694
  151. package/dist/render-channel.d.ts.map +0 -1
  152. package/dist/render-channel.js +0 -1775
@@ -0,0 +1,190 @@
1
+ /**
2
+ * Console static-bundle + SPA serving routes.
3
+ *
4
+ * GET <consolePath> — mode-meta-injected `index.html`,
5
+ * welcome page, or onboarding redirect.
6
+ * <consolePath>/* (static) — `express.static` over the package's
7
+ * built `dist/` (HTML + JS + CSS).
8
+ * GET /admin/* + /devtools/* — admin-HTML gate (302 to
9
+ * `/admin-login?next=…` on miss).
10
+ * GET <consolePath>/* (SPA) — fallback to the rewritten
11
+ * `index.html` so the React router
12
+ * takes over for client-side routes.
13
+ *
14
+ * When the `distDir` doesn't exist on disk (e.g. operator forgot
15
+ * to run `pnpm --filter @ggui-ai/console build`), the static
16
+ * route is replaced with a 503 that points at the missing build —
17
+ * silent 404 would be mistaken for "console is broken" rather
18
+ * than "console wasn't built yet," which is a real debugging
19
+ * trap for self-hosted operators.
20
+ */
21
+ import express from "express";
22
+ import { existsSync, readFileSync } from "node:fs";
23
+ import path from "node:path";
24
+ import { applyDevtoolSecurityHeaders } from "./console-headers.js";
25
+ import { renderWelcomeHtml } from "./console-welcome.js";
26
+ /**
27
+ * Mount the console static + SPA routes onto the express app.
28
+ * Returns nothing — the routes self-register.
29
+ */
30
+ export function mountConsoleStaticRoutes(opts) {
31
+ const { app, consolePath, consoleDistDir, mode, serverName, welcomePage, landingRedirect, requestHasAdminAuth, logger, } = opts;
32
+ if (existsSync(consoleDistDir)) {
33
+ // Read + stamp `<meta name="ggui-mode" content="dev|prod">` into
34
+ // the SPA's `<head>` once at boot. The SPA's `mode.ts` reads the
35
+ // meta synchronously on first paint so `TopNav` renders the
36
+ // `/devtools` link without a `/info` round-trip flicker. Mode
37
+ // changes require a server restart — same shape as every other
38
+ // `CreateGguiServerOptions` field, no live-toggle ceremony.
39
+ const indexPath = path.join(consoleDistDir, "index.html");
40
+ const META_TAG = `<meta name="ggui-mode" content="${mode}">`;
41
+ let indexHtml;
42
+ try {
43
+ const raw = readFileSync(indexPath, "utf-8");
44
+ // Inject right after `<head>` so the meta is available before
45
+ // any subsequent `<script>` tag executes. Idempotent: if a
46
+ // previous build somehow already inlined the meta, the
47
+ // injection still produces a valid (duplicate but harmless)
48
+ // tag — `mode.ts` reads the first hit.
49
+ indexHtml = raw.includes("<head>")
50
+ ? raw.replace("<head>", `<head>${META_TAG}`)
51
+ : raw.replace(/^/, `${META_TAG}\n`);
52
+ }
53
+ catch (err) {
54
+ logger.warn("console_index_read_failed", {
55
+ path: indexPath,
56
+ error: String(err),
57
+ });
58
+ indexHtml = `<!doctype html><html><head>${META_TAG}</head><body></body></html>`;
59
+ }
60
+ const sendConsoleHtml = (res) => {
61
+ applyDevtoolSecurityHeaders(res);
62
+ res.type("text/html").send(indexHtml);
63
+ };
64
+ // Welcome HTML — server-rendered landing for `consolePath === '/'`
65
+ // when the operator wires `welcomePage`. Identifies who runs
66
+ // the server (operator block; entirely hidden when unset),
67
+ // describes the public deep-link surfaces, and offers the
68
+ // operator-login affordance. No JS, no SPA mount, same security
69
+ // headers as `sendConsoleHtml`.
70
+ const welcomeEnabled = welcomePage !== undefined && consolePath === "/";
71
+ const sendWelcomeHtml = welcomeEnabled && welcomePage !== undefined
72
+ ? (res) => {
73
+ applyDevtoolSecurityHeaders(res);
74
+ res.type("text/html").send(renderWelcomeHtml(welcomePage, serverName));
75
+ }
76
+ : null;
77
+ // Onboarding redirect — must run BEFORE express.static, which
78
+ // would otherwise serve `index.html` for `GET /` directly and
79
+ // never give the SPA fallback (or this redirect) a chance.
80
+ app.get(consolePath, (req, res, next) => {
81
+ // Static middleware fires on the trailing-slash variant when
82
+ // consolePath !== '/'. Both shapes route here.
83
+ if (req.path !== consolePath && req.path !== `${consolePath}/`) {
84
+ return next();
85
+ }
86
+ if (landingRedirect) {
87
+ const target = landingRedirect();
88
+ if (target && target !== req.path) {
89
+ res.redirect(302, target);
90
+ return;
91
+ }
92
+ }
93
+ if (sendWelcomeHtml) {
94
+ sendWelcomeHtml(res);
95
+ return;
96
+ }
97
+ sendConsoleHtml(res);
98
+ });
99
+ app.use(consolePath, express.static(consoleDistDir, {
100
+ // index:false — explicit handler above owns `/` so the meta
101
+ // tag gets injected. Without this, express.static would
102
+ // race the handler and sometimes serve the raw file.
103
+ index: false,
104
+ // Short cache — operators iterating on their server want
105
+ // fresh copies after a rebuild; production-hardening
106
+ // (etag, long-term caching for /assets/*) is a slice-3 polish
107
+ // concern.
108
+ maxAge: 0,
109
+ fallthrough: true,
110
+ // Attach the console security header set to every
111
+ // static response. `setHeaders` runs for successful hits
112
+ // (HTML + JS + CSS + asset 200s); misses that fall through
113
+ // to the SPA fallback below are covered by the fallback's
114
+ // explicit `applyDevtoolSecurityHeaders` call.
115
+ setHeaders: applyDevtoolSecurityHeaders,
116
+ }));
117
+ // Admin-HTML gate. The SPA's `/admin/*` and `/devtools/*` zones
118
+ // are operator-only — without this gate, every admin page is a
119
+ // GET away from anyone who guesses the path. The corresponding
120
+ // JSON APIs already gate at `/ggui/console/keys`, but the SPA
121
+ // shell itself was unauthenticated. Mounted only when the gate
122
+ // shape is available (admin token resolved + closure built).
123
+ //
124
+ // 302 to `/admin-login?next=<encoded-path>` on miss — the login
125
+ // page reads `next` from the query string and bounces back after
126
+ // a successful token paste. No client-side cookie set here; the
127
+ // existing `POST /ggui/console/admin-login` route owns minting.
128
+ //
129
+ // Scope:
130
+ // - GATED: `/admin/*`, `/devtools/*`
131
+ // - UNGATED: `/admin-login`, `/s/*`, `/preview/*`, `/` (welcome
132
+ // when wired, SPA index otherwise)
133
+ //
134
+ // The welcome page (`/`) is intentionally public — it's the
135
+ // operator-identification surface, not an admin tool. Public
136
+ // deep-link surfaces stay reachable: a render viewer URL
137
+ // `/s/<shortCode>` and a blueprint preview URL `/preview/<id>`
138
+ // are how end-users / blueprint authors land on the server.
139
+ if (requestHasAdminAuth !== null) {
140
+ const adminAuth = requestHasAdminAuth;
141
+ app.get(/^\/(admin|devtools)(\/.*)?$/, (req, res, next) => {
142
+ if (req.path === "/admin-login")
143
+ return next();
144
+ if (adminAuth(req))
145
+ return next();
146
+ applyDevtoolSecurityHeaders(res);
147
+ const next_ = encodeURIComponent(req.originalUrl || req.path);
148
+ res.redirect(302, `/admin-login?next=${next_}`);
149
+ });
150
+ }
151
+ // SPA fallback: the console client owns client-side routes
152
+ // (`/`, `/s/<shortCode>`, `/admin/*`, `/devtools/*`). An unknown
153
+ // sub-path under the mount must serve the rewritten
154
+ // `index.html` so the React router takes over.
155
+ //
156
+ // Express 5 / path-to-regexp v8 rejects the bare `'*'` and
157
+ // `'foo/*'` wildcard strings that worked in v6; named splats
158
+ // (`{*splat}`) or RegExp patterns are required. Using RegExp
159
+ // here matches the admin-gate pattern above and avoids the
160
+ // parser-version churn.
161
+ const spaFallbackPattern = consolePath === "/"
162
+ ? /^\/.*$/
163
+ : new RegExp(`^${consolePath.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}/.*$`);
164
+ app.get(spaFallbackPattern, (req, res, next) => {
165
+ // Only fallback for GET of non-asset, non-API paths.
166
+ // `express.static` already served any asset that exists;
167
+ // an `/assets/foo.js` that doesn't exist SHOULD 404 rather
168
+ // than returning HTML.
169
+ if (req.path.startsWith("/ggui/"))
170
+ return next();
171
+ if (req.path.startsWith(`${consolePath === "/" ? "" : consolePath}/assets/`)) {
172
+ return next();
173
+ }
174
+ sendConsoleHtml(res);
175
+ });
176
+ }
177
+ else {
178
+ logger.warn("console_dist_missing", {
179
+ distDir: consoleDistDir,
180
+ hint: "Run `pnpm --filter @ggui-ai/console build` to produce the static bundle. Serving 503 from the mount point until the bundle exists.",
181
+ });
182
+ app.use(consolePath, (_req, res) => {
183
+ applyDevtoolSecurityHeaders(res);
184
+ res
185
+ .status(503)
186
+ .type("text/plain")
187
+ .send("console bundle not built. Run:\n pnpm --filter @ggui-ai/console build\n");
188
+ });
189
+ }
190
+ }
@@ -21,7 +21,7 @@
21
21
  * Authentication: piggy-backs on the same admin gate the LLM-keys
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
- * who paired into a session don't get to mutate the project's
24
+ * who paired into a render don't get to mutate the project's
25
25
  * theme. Multi-tenant deployments may relax this in a follow-up by
26
26
  * scoping overrides per user (overrides become server state, not
27
27
  * file state).
@@ -81,8 +81,8 @@ interface MountOptions {
81
81
  * Optional change notifier — fires every time the operator's
82
82
  * theme selection changes through `POST /ggui/console/theme` (or
83
83
  * the `/upload` variant). The CLI uses this to mirror writes into
84
- * a shared mutable state cell that the push handler's
85
- * `themeProvider` reads, so a save reaches the next push without
84
+ * a shared mutable state cell that the render handler's
85
+ * `themeProvider` reads, so a save reaches the next render without
86
86
  * a server restart.
87
87
  *
88
88
  * Fires AFTER the on-disk write succeeds and AFTER the route's
@@ -21,7 +21,7 @@
21
21
  * Authentication: piggy-backs on the same admin gate the LLM-keys
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
- * who paired into a session don't get to mutate the project's
24
+ * who paired into a render don't get to mutate the project's
25
25
  * theme. Multi-tenant deployments may relax this in a follow-up by
26
26
  * scoping overrides per user (overrides become server state, not
27
27
  * file state).
@@ -8,20 +8,20 @@
8
8
  *
9
9
  * Post-Phase-B (flatten-render-identity): renamed from the prior
10
10
  * "sessions" terminology; the canonical addressable unit is now the
11
- * render. The on-wire `renderId` path param replaces `sessionId`.
11
+ * render. The on-wire `sessionId` path param replaces the prior `renderId`.
12
12
  *
13
- * Memory: bounded by whatever the underlying RenderStore retains.
14
- * `InMemoryRenderStore` keeps everything for the process lifetime —
13
+ * Memory: bounded by whatever the underlying GguiSessionStore retains.
14
+ * `InMemoryGguiSessionStore` keeps everything for the process lifetime —
15
15
  * fine for OSS dev. A hosted closed runtime's persistent store is the
16
16
  * durability surface.
17
17
  */
18
18
  import type { Express } from 'express';
19
- import type { RenderStore, SessionStreamBuffer } from '@ggui-ai/mcp-server-core';
19
+ import type { GguiSessionStore, GguiSessionStreamBuffer } from '@ggui-ai/mcp-server-core';
20
20
  /**
21
21
  * Mount `/ggui/console/timeline/*` routes on `app`. Caller is
22
22
  * responsible for admin-gating the path prefix beforehand — this
23
23
  * function does not re-implement auth (matches the
24
24
  * `mountConsoleLlmTraceRoutes` shape in console-llm-trace.ts).
25
25
  */
26
- export declare function mountConsoleTimelineRoutes(app: Express, renderStore: RenderStore | undefined, streamBuffer: SessionStreamBuffer | undefined): void;
26
+ export declare function mountConsoleTimelineRoutes(app: Express, renderStore: GguiSessionStore | undefined, streamBuffer: GguiSessionStreamBuffer | undefined): void;
27
27
  //# sourceMappingURL=console-timeline.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"console-timeline.d.ts","sourceRoot":"","sources":["../src/console-timeline.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,OAAO,KAAK,EAAqB,OAAO,EAAE,MAAM,SAAS,CAAC;AAC1D,OAAO,KAAK,EAEV,WAAW,EACX,mBAAmB,EACpB,MAAM,0BAA0B,CAAC;AAiElC;;;;;GAKG;AACH,wBAAgB,0BAA0B,CACxC,GAAG,EAAE,OAAO,EACZ,WAAW,EAAE,WAAW,GAAG,SAAS,EACpC,YAAY,EAAE,mBAAmB,GAAG,SAAS,GAC5C,IAAI,CA6JN"}
1
+ {"version":3,"file":"console-timeline.d.ts","sourceRoot":"","sources":["../src/console-timeline.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,OAAO,KAAK,EAAqB,OAAO,EAAE,MAAM,SAAS,CAAC;AAC1D,OAAO,KAAK,EAEV,gBAAgB,EAChB,uBAAuB,EACxB,MAAM,0BAA0B,CAAC;AAkElC;;;;;GAKG;AACH,wBAAgB,0BAA0B,CACxC,GAAG,EAAE,OAAO,EACZ,WAAW,EAAE,gBAAgB,GAAG,SAAS,EACzC,YAAY,EAAE,uBAAuB,GAAG,SAAS,GAChD,IAAI,CA6JN"}
@@ -1,13 +1,14 @@
1
1
  import { applyDevtoolSecurityHeaders } from './console-headers.js';
2
+ import { singleParam } from './route-param.js';
2
3
  /**
3
- * Drain {@link RenderStore.observe} into an array. The observe iterator
4
+ * Drain {@link GguiSessionStore.observe} into an array. The observe iterator
4
5
  * with `{ tail: false }` resolves cleanly after replaying every stored
5
6
  * event, so this is a plain `for await`. Bound by the store's per-
6
7
  * render retention (in-memory: full history; hosted: store-defined).
7
8
  */
8
- async function drainRenderEvents(renderStore, renderId) {
9
+ async function drainGguiSessionEvents(renderStore, sessionId) {
9
10
  const out = [];
10
- for await (const event of renderStore.observe(renderId, {
11
+ for await (const event of renderStore.observe(sessionId, {
11
12
  fromSeq: 1,
12
13
  tail: false,
13
14
  })) {
@@ -35,7 +36,7 @@ export function mountConsoleTimelineRoutes(app, renderStore, streamBuffer) {
35
36
  }
36
37
  // Zero-config shape: no store wired (pure-MCP boot) → empty list.
37
38
  if (!renderStore) {
38
- const body = { renders: [], total: 0 };
39
+ const body = { sessions: [], total: 0 };
39
40
  res.json(body);
40
41
  return;
41
42
  }
@@ -59,14 +60,14 @@ export function mountConsoleTimelineRoutes(app, renderStore, streamBuffer) {
59
60
  }
60
61
  // Compute status from render state (the store doesn't
61
62
  // surface its private `closed` flag uniformly across impls).
62
- // We mirror the InMemoryRenderStore's rule:
63
+ // We mirror the InMemoryGguiSessionStore's rule:
63
64
  // expired = expiresAt <= now, otherwise active. The
64
65
  // 'completed' state needs the closed flag to disambiguate;
65
66
  // we report 'active' until eviction. Operators reading the
66
67
  // detail pane get the authoritative status from the store.
67
68
  const status = row.expiresAt <= now ? 'expired' : 'active';
68
69
  summaries.push({
69
- renderId: row.id,
70
+ sessionId: row.id,
70
71
  appId: row.appId,
71
72
  createdAt: row.createdAt,
72
73
  lastActivityAt: row.lastActivityAt,
@@ -74,45 +75,45 @@ export function mountConsoleTimelineRoutes(app, renderStore, streamBuffer) {
74
75
  streamSeq,
75
76
  });
76
77
  }
77
- // Most-recent activity first. Tiebreak on renderId for
78
+ // Most-recent activity first. Tiebreak on sessionId for
78
79
  // stable ordering across reloads.
79
80
  summaries.sort((a, b) => {
80
81
  const byRecency = b.lastActivityAt - a.lastActivityAt;
81
82
  if (byRecency !== 0)
82
83
  return byRecency;
83
- return a.renderId.localeCompare(b.renderId);
84
+ return a.sessionId.localeCompare(b.sessionId);
84
85
  });
85
86
  const trimmed = summaries.slice(0, limit);
86
87
  const body = {
87
- renders: trimmed,
88
+ sessions: trimmed,
88
89
  total: summaries.length,
89
90
  };
90
91
  res.json(body);
91
92
  }
92
93
  catch (err) {
93
94
  res.status(500).json({
94
- error: 'timeline_renders_list_failed',
95
+ error: 'timeline_sessions_list_failed',
95
96
  message: err instanceof Error
96
- ? `Render store failed to list — ${err.message}`
97
- : `Render store failed to list — ${String(err)}`,
97
+ ? `GguiSession store failed to list — ${err.message}`
98
+ : `GguiSession store failed to list — ${String(err)}`,
98
99
  });
99
100
  }
100
101
  });
101
- // GET /ggui/console/timeline/:renderId/events
102
- app.get('/ggui/console/timeline/:renderId/events', async (req, res) => {
102
+ // GET /ggui/console/timeline/:sessionId/events
103
+ app.get('/ggui/console/timeline/:sessionId/events', async (req, res) => {
103
104
  applyDevtoolSecurityHeaders(res);
104
- const renderId = req.params['renderId'];
105
- if (!renderId || renderId.length === 0) {
105
+ const sessionId = singleParam(req.params['sessionId']);
106
+ if (!sessionId || sessionId.length === 0) {
106
107
  res.status(400).json({
107
- error: 'invalid_render_id',
108
- message: 'renderId path parameter is required',
108
+ error: 'invalid_session_id',
109
+ message: 'sessionId path parameter is required',
109
110
  });
110
111
  return;
111
112
  }
112
113
  // Zero-config shape: no store wired → empty events.
113
114
  if (!renderStore) {
114
115
  const body = {
115
- renderId,
116
+ sessionId,
116
117
  events: [],
117
118
  streamSeq: 0,
118
119
  status: 'unknown',
@@ -121,14 +122,14 @@ export function mountConsoleTimelineRoutes(app, renderStore, streamBuffer) {
121
122
  return;
122
123
  }
123
124
  try {
124
- const stored = await renderStore.get(renderId);
125
+ const stored = await renderStore.get(sessionId);
125
126
  if (!stored) {
126
127
  // 404 is the right status — but we still return a well-
127
128
  // formed body so the SPA can render an "expired/dropped"
128
129
  // notice without a special-case branch. Body matches the
129
130
  // schema; status code disambiguates.
130
131
  const body = {
131
- renderId,
132
+ sessionId,
132
133
  events: [],
133
134
  streamSeq: 0,
134
135
  status: 'unknown',
@@ -136,11 +137,11 @@ export function mountConsoleTimelineRoutes(app, renderStore, streamBuffer) {
136
137
  res.status(404).json(body);
137
138
  return;
138
139
  }
139
- const events = await drainRenderEvents(renderStore, renderId);
140
+ const events = await drainGguiSessionEvents(renderStore, sessionId);
140
141
  let streamSeq = 0;
141
142
  if (streamBuffer) {
142
143
  try {
143
- streamSeq = await streamBuffer.currentSeq(renderId);
144
+ streamSeq = await streamBuffer.currentSeq(sessionId);
144
145
  }
145
146
  catch {
146
147
  // Best-effort. Inbound events stand on their own.
@@ -149,7 +150,7 @@ export function mountConsoleTimelineRoutes(app, renderStore, streamBuffer) {
149
150
  const now = Date.now();
150
151
  const status = stored.expiresAt <= now ? 'expired' : 'active';
151
152
  const body = {
152
- renderId,
153
+ sessionId,
153
154
  events,
154
155
  streamSeq,
155
156
  status,
@@ -160,8 +161,8 @@ export function mountConsoleTimelineRoutes(app, renderStore, streamBuffer) {
160
161
  res.status(500).json({
161
162
  error: 'timeline_events_drain_failed',
162
163
  message: err instanceof Error
163
- ? `Render store failed to drain — ${err.message}`
164
- : `Render store failed to drain — ${String(err)}`,
164
+ ? `GguiSession store failed to drain — ${err.message}`
165
+ : `GguiSession store failed to drain — ${String(err)}`,
165
166
  });
166
167
  }
167
168
  });
@@ -202,9 +202,9 @@ export const renderWelcomeHtml = (inputs, serverName) => {
202
202
  <p class="eyebrow">Public surfaces</p>
203
203
  <div class="surfaces">
204
204
  <div class="surface">
205
- <span class="surface-label">Session viewer</span>
205
+ <span class="surface-label">GguiSession viewer</span>
206
206
  <code>/s/&lt;short-code&gt;</code>
207
- <span class="desc">Open the live session your agent linked you to.</span>
207
+ <span class="desc">Open the live render your agent linked you to.</span>
208
208
  </div>
209
209
  <div class="surface">
210
210
  <span class="surface-label">Blueprint preview</span>
@@ -1 +1 @@
1
- {"version":3,"file":"email-login.d.ts","sourceRoot":"","sources":["../src/email-login.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,OAAO,KAAK,EAAE,OAAO,EAAqB,MAAM,SAAS,CAAC;AAE1D,OAAO,KAAK,EAAc,SAAS,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAGnF,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAE1C;;;;GAIG;AACH,eAAO,MAAM,8BAA8B,4BAA4B,CAAC;AACxE,eAAO,MAAM,+BAA+B,6BAA6B,CAAC;AAC1E,eAAO,MAAM,+BAA+B,6BAA6B,CAAC;AAK1E;;;;;;;;GAQG;AACH,MAAM,WAAW,WAAW;IAC1B,IAAI,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC5C;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;;;;;;;;GASG;AACH,qBAAa,kBAAmB,YAAW,WAAW;IACpD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;gBACpB,MAAM,CAAC,EAAE,MAAM;IAGrB,IAAI,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;CAQjD;AAED;;;;;;;;;;GAUG;AACH,MAAM,WAAW,cAAc;IAC7B,SAAS,CAAC,KAAK,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAClD;;;;OAIG;IACH,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CAAC;CAC9D;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC3B;AAED;;;;;;GAMG;AACH,qBAAa,sBAAuB,YAAW,cAAc;IAC3D,OAAO,CAAC,QAAQ,CAAC,OAAO,CAGpB;IAEE,SAAS,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC;IAatE,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC;CASnE;AAED;;;;GAIG;AACH,MAAM,WAAW,uBAAuB;IACtC,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC;IAC7B,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC;IAC3B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,mEAAmE;IACnE,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B;;;;;OAKG;IACH,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,iEAAiE;IACjE,QAAQ,CAAC,KAAK,CAAC,EAAE,cAAc,CAAC;IAChC,wEAAwE;IACxE,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,CAAC;IAC/B,2CAA2C;IAC3C,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC;IAC1B,yCAAyC;IACzC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,yCAAyC;IACzC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B;;;OAGG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;CAC5B;AAoBD;;;;;GAKG;AACH,wBAAgB,qBAAqB,CACnC,GAAG,EAAE,OAAO,EACZ,IAAI,EAAE,uBAAuB,GAC5B,IAAI,CA8KN"}
1
+ {"version":3,"file":"email-login.d.ts","sourceRoot":"","sources":["../src/email-login.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,5 @@
1
1
  import { randomBytes } from 'node:crypto';
2
+ import { isRecord } from '@ggui-ai/protocol';
2
3
  import { formatUserSessionCookieHeader } from './user-session-auth.js';
3
4
  import { createConsoleLogger } from './logger.js';
4
5
  /**
@@ -121,9 +122,7 @@ export function mountEmailLoginRoutes(app, opts) {
121
122
  // email enumeration — see route docstring for why).
122
123
  app.post(startPath, async (req, res) => {
123
124
  const reqLogger = opts.logger.child({ route: 'POST ' + startPath });
124
- const body = typeof req.body === 'object' && req.body !== null
125
- ? req.body
126
- : {};
125
+ const body = isRecord(req.body) ? req.body : {};
127
126
  const rawEmail = typeof body['email'] === 'string' ? body['email'] : '';
128
127
  const email = rawEmail.trim().toLowerCase();
129
128
  if (!EMAIL_RE.test(email) || email.length > 254) {
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Inbound `action` ingress + consume bridge for the live channel —
3
+ * contract enforcement on user gestures arriving over WS, the
4
+ * dual-write onto the retained event ledger + the pending-events pipe
5
+ * (`ggui_consume`'s queue), and the ack carrying the ledger seq back
6
+ * to the client.
7
+ */
8
+ import type { GguiSessionStore, PendingEventConsumer } from "@ggui-ai/mcp-server-core";
9
+ import type { WebSocketMessage } from "@ggui-ai/protocol/transport/websocket";
10
+ import type { WebSocket } from "ws";
11
+ import type { Logger } from "../logger.js";
12
+ import type { Subscriber } from "./internal-types.js";
13
+ import type { Outbound } from "./outbound.js";
14
+ export interface ActionIngressDeps {
15
+ readonly logger: Logger;
16
+ readonly renderStore: GguiSessionStore;
17
+ /**
18
+ * Pending-events pipe — see
19
+ * `GguiSessionChannelOptions.pendingEventConsumer` for the dual-write
20
+ * contract. Absent → ledger-only ingress.
21
+ */
22
+ readonly pendingEventConsumer?: PendingEventConsumer;
23
+ readonly send: Outbound["send"];
24
+ readonly sendError: Outbound["sendError"];
25
+ }
26
+ export interface ActionIngress {
27
+ /**
28
+ * Handle an inbound `action` message — the canonical flat
29
+ * {@link ActionEnvelope} shape.
30
+ *
31
+ * Inbound actions are gated by {@link assertActionContract} only —
32
+ * the actionSpec payload check for `data:submit` types. (The
33
+ * pre-Phase-B `subscription.events` allowlist gate was deleted with
34
+ * the session-stack collapse; per-render event policy needs a new
35
+ * wire shape before any second gate can exist.)
36
+ *
37
+ * Accepted envelopes dual-write, mirroring the
38
+ * `ggui_runtime_submit_action` relay's posture:
39
+ *
40
+ * 1. The retained event ledger (`renderStore.appendEvent`) — the
41
+ * ack's `seq` source; failure is the load-bearing
42
+ * `APPEND_FAILED` path.
43
+ * 2. For `data:submit` only, the pending-events pipe
44
+ * ({@link ActionIngressDeps.pendingEventConsumer}) — the
45
+ * queue `ggui_consume` drains, so the agent receives the gesture
46
+ * mid-turn. Pipe failure degrades to ledger-only with a warn;
47
+ * it never changes the ack.
48
+ */
49
+ handleInboundAction(ws: WebSocket, sub: Subscriber, message: WebSocketMessage & {
50
+ type: "action";
51
+ }): Promise<void>;
52
+ }
53
+ export declare function createActionIngress(deps: ActionIngressDeps): ActionIngress;
54
+ //# sourceMappingURL=action-ingress.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"action-ingress.d.ts","sourceRoot":"","sources":["../../src/ggui-session-channel/action-ingress.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AAIvF,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uCAAuC,CAAC;AAE9E,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,IAAI,CAAC;AACpC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AACtD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AA+G9C,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,WAAW,EAAE,gBAAgB,CAAC;IACvC;;;;OAIG;IACH,QAAQ,CAAC,oBAAoB,CAAC,EAAE,oBAAoB,CAAC;IACrD,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;IAChC,QAAQ,CAAC,SAAS,EAAE,QAAQ,CAAC,WAAW,CAAC,CAAC;CAC3C;AAED,MAAM,WAAW,aAAa;IAC5B;;;;;;;;;;;;;;;;;;;;;OAqBG;IACH,mBAAmB,CACjB,EAAE,EAAE,SAAS,EACb,GAAG,EAAE,UAAU,EACf,OAAO,EAAE,gBAAgB,GAAG;QAAE,IAAI,EAAE,QAAQ,CAAA;KAAE,GAC7C,OAAO,CAAC,IAAI,CAAC,CAAC;CAClB;AAED,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,iBAAiB,GAAG,aAAa,CAiJ1E"}