rivet-design 0.7.1 → 0.8.1

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 (100) hide show
  1. package/dist/config/evaluateFlags.d.ts +7 -0
  2. package/dist/config/evaluateFlags.d.ts.map +1 -0
  3. package/dist/config/evaluateFlags.js +27 -0
  4. package/dist/config/evaluateFlags.js.map +1 -0
  5. package/dist/config/flags.d.ts +25 -5
  6. package/dist/config/flags.d.ts.map +1 -1
  7. package/dist/config/flags.js +22 -18
  8. package/dist/config/flags.js.map +1 -1
  9. package/dist/demo/sessionRuntime.d.ts +2 -0
  10. package/dist/demo/sessionRuntime.d.ts.map +1 -0
  11. package/dist/demo/sessionRuntime.js +48 -0
  12. package/dist/demo/sessionRuntime.js.map +1 -0
  13. package/dist/hosted-demo.d.ts +2 -0
  14. package/dist/hosted-demo.d.ts.map +1 -0
  15. package/dist/hosted-demo.js +80 -0
  16. package/dist/hosted-demo.js.map +1 -0
  17. package/dist/index.d.ts +10 -0
  18. package/dist/index.d.ts.map +1 -1
  19. package/dist/index.js +110 -3
  20. package/dist/index.js.map +1 -1
  21. package/dist/proxy-middleware/proxy-config.js +1 -1
  22. package/dist/routes/demo.d.ts +33 -0
  23. package/dist/routes/demo.d.ts.map +1 -0
  24. package/dist/routes/demo.js +180 -0
  25. package/dist/routes/demo.js.map +1 -0
  26. package/dist/routes/git.d.ts +3 -1
  27. package/dist/routes/git.d.ts.map +1 -1
  28. package/dist/routes/git.js +15 -1
  29. package/dist/routes/git.js.map +1 -1
  30. package/dist/routes/modifications.d.ts +7 -0
  31. package/dist/routes/modifications.d.ts.map +1 -1
  32. package/dist/routes/modifications.js +81 -4
  33. package/dist/routes/modifications.js.map +1 -1
  34. package/dist/routes/static.d.ts.map +1 -1
  35. package/dist/routes/static.js +17 -2
  36. package/dist/routes/static.js.map +1 -1
  37. package/dist/server.d.ts +20 -1
  38. package/dist/server.d.ts.map +1 -1
  39. package/dist/server.js +530 -35
  40. package/dist/server.js.map +1 -1
  41. package/dist/services/AuthService.d.ts +1 -1
  42. package/dist/services/AuthService.js +1 -1
  43. package/dist/services/ConfigManager.d.ts.map +1 -1
  44. package/dist/services/ConfigManager.js +13 -0
  45. package/dist/services/ConfigManager.js.map +1 -1
  46. package/dist/services/FeatureFlagService.d.ts +18 -0
  47. package/dist/services/FeatureFlagService.d.ts.map +1 -0
  48. package/dist/services/FeatureFlagService.js +62 -0
  49. package/dist/services/FeatureFlagService.js.map +1 -0
  50. package/dist/services/HostedDemoAuthSessionService.d.ts +42 -0
  51. package/dist/services/HostedDemoAuthSessionService.d.ts.map +1 -0
  52. package/dist/services/HostedDemoAuthSessionService.js +179 -0
  53. package/dist/services/HostedDemoAuthSessionService.js.map +1 -0
  54. package/dist/services/HostedDemoAuthSessionStore.d.ts +43 -0
  55. package/dist/services/HostedDemoAuthSessionStore.d.ts.map +1 -0
  56. package/dist/services/HostedDemoAuthSessionStore.js +90 -0
  57. package/dist/services/HostedDemoAuthSessionStore.js.map +1 -0
  58. package/dist/services/HostedDemoSessionService.d.ts +91 -0
  59. package/dist/services/HostedDemoSessionService.d.ts.map +1 -0
  60. package/dist/services/HostedDemoSessionService.js +568 -0
  61. package/dist/services/HostedDemoSessionService.js.map +1 -0
  62. package/dist/services/HostedDemoSessionStore.d.ts +49 -0
  63. package/dist/services/HostedDemoSessionStore.d.ts.map +1 -0
  64. package/dist/services/HostedDemoSessionStore.js +90 -0
  65. package/dist/services/HostedDemoSessionStore.js.map +1 -0
  66. package/dist/services/ProjectDetectionService.d.ts +2 -2
  67. package/dist/services/ProjectDetectionService.d.ts.map +1 -1
  68. package/dist/services/ProjectDetectionService.js +26 -9
  69. package/dist/services/ProjectDetectionService.js.map +1 -1
  70. package/dist/services/RequestAuthContext.d.ts +8 -0
  71. package/dist/services/RequestAuthContext.d.ts.map +1 -0
  72. package/dist/services/RequestAuthContext.js +14 -0
  73. package/dist/services/RequestAuthContext.js.map +1 -0
  74. package/dist/services/TelemetryService.d.ts +95 -0
  75. package/dist/services/TelemetryService.d.ts.map +1 -1
  76. package/dist/services/TelemetryService.js +198 -0
  77. package/dist/services/TelemetryService.js.map +1 -1
  78. package/dist/services/accessTokenRefresh.d.ts +36 -0
  79. package/dist/services/accessTokenRefresh.d.ts.map +1 -0
  80. package/dist/services/accessTokenRefresh.js +102 -0
  81. package/dist/services/accessTokenRefresh.js.map +1 -0
  82. package/dist/services/agent/AgentCore.js +1 -1
  83. package/dist/services/agent/AgentCore.js.map +1 -1
  84. package/dist/services/hostedDemoSessionAuthRefresh.d.ts +18 -0
  85. package/dist/services/hostedDemoSessionAuthRefresh.d.ts.map +1 -0
  86. package/dist/services/hostedDemoSessionAuthRefresh.js +39 -0
  87. package/dist/services/hostedDemoSessionAuthRefresh.js.map +1 -0
  88. package/dist/utils/shouldRecordHostedDemoSessionAction.d.ts +8 -0
  89. package/dist/utils/shouldRecordHostedDemoSessionAction.d.ts.map +1 -0
  90. package/dist/utils/shouldRecordHostedDemoSessionAction.js +27 -0
  91. package/dist/utils/shouldRecordHostedDemoSessionAction.js.map +1 -0
  92. package/package.json +4 -2
  93. package/src/ui/dist/assets/logo.png +0 -0
  94. package/src/ui/dist/assets/main-BHd_bEP8.css +1 -0
  95. package/src/ui/dist/assets/{main-C1vRMtjM.js → main-C7zScMx3.js} +138 -134
  96. package/src/ui/dist/assets/rivet.svg +3 -0
  97. package/src/ui/dist/fonts/Goldman-Bold.woff2 +0 -0
  98. package/src/ui/dist/fonts/Goldman-Regular.woff2 +0 -0
  99. package/src/ui/dist/index.html +3 -3
  100. package/src/ui/dist/assets/main-BsJYpJMo.css +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"HostedDemoAuthSessionStore.js","sourceRoot":"","sources":["../../src/services/HostedDemoAuthSessionStore.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,91 @@
1
+ import { type ChildProcess } from 'child_process';
2
+ import { TelemetryService } from './TelemetryService';
3
+ import { type HostedDemoSessionPersistedRuntimeState, type HostedDemoSessionStore } from './HostedDemoSessionStore';
4
+ export type HostedDemoSession = {
5
+ sessionId: string;
6
+ userId: string;
7
+ createdAt: number;
8
+ expiresAt: number;
9
+ lastActionAt: number;
10
+ worktreePath: string;
11
+ projectPath: string;
12
+ rivetPort: number;
13
+ runtimeState: HostedDemoSessionPersistedRuntimeState;
14
+ };
15
+ export type HostedDemoSessionWithUrl = HostedDemoSession & {
16
+ sessionUrl: string;
17
+ };
18
+ type RuntimeStartParams = {
19
+ projectPath: string;
20
+ rivetPort: number;
21
+ sessionId: string;
22
+ };
23
+ type HostedDemoRuntimeDeps = {
24
+ now?: () => number;
25
+ runGitCommand?: (repoPath: string, args: string[]) => Promise<void>;
26
+ startRuntime?: (params: RuntimeStartParams) => Promise<ChildProcess>;
27
+ stopRuntime?: (runtimeProcess: ChildProcess) => Promise<void>;
28
+ waitForRuntimeHealth?: (rivetPort: number) => Promise<void>;
29
+ };
30
+ export type HostedDemoSessionConfig = {
31
+ repoPath: string;
32
+ templateProjectPath: string;
33
+ telemetry?: TelemetryService;
34
+ worktreesRootPath?: string;
35
+ sessionTtlMs?: number;
36
+ sessionIdleTtlMs?: number;
37
+ maxActiveSessions?: number;
38
+ baseRivetPort?: number;
39
+ publicBaseUrl?: string;
40
+ sessionMetadataStore?: HostedDemoSessionStore;
41
+ };
42
+ export declare class HostedDemoSessionService {
43
+ private readonly config;
44
+ private readonly deps;
45
+ private readonly telemetry;
46
+ private readonly sessionStore;
47
+ private readonly sessions;
48
+ private pendingSessionCreations;
49
+ private readonly cleanupTimer;
50
+ private readonly rehydrationLocks;
51
+ constructor(config: HostedDemoSessionConfig, deps?: HostedDemoRuntimeDeps);
52
+ /**
53
+ * Loads durable session rows into memory so stopped sessions can be rehydrated after deploy.
54
+ */
55
+ initialize(): Promise<void>;
56
+ createSession(userId: string): Promise<HostedDemoSessionWithUrl>;
57
+ getActiveSessionCount(): number;
58
+ getTemplateProjectPath(): string;
59
+ getSession(sessionId: string): HostedDemoSession | null;
60
+ getSessionOwnerUserId(sessionId: string): string | null;
61
+ /**
62
+ * Records a Rivet user action for idle TTL (does not extend idle on passive polling).
63
+ */
64
+ recordSessionAction(sessionId: string): Promise<void>;
65
+ getSessionProxyTarget(sessionId: string): string | null;
66
+ /**
67
+ * Starts the per-session runtime when it was stopped for idle or after process restart.
68
+ */
69
+ ensureRuntimeReady(sessionId: string): Promise<void>;
70
+ closeSession(sessionId: string, options?: {
71
+ removeWorktree?: boolean;
72
+ }): Promise<void>;
73
+ shutdown(): Promise<void>;
74
+ private readonly countRunningOrStartingSessions;
75
+ private readonly persistSession;
76
+ private readonly toPersisted;
77
+ private readonly internalFromPersisted;
78
+ private readonly runRehydration;
79
+ private readonly stopRuntimeForIdle;
80
+ private readonly cleanupExpiredSessions;
81
+ private readonly safeRemoveWorktree;
82
+ private readonly toErrorType;
83
+ getSessionUrl(sessionId: string): string;
84
+ private readonly toPublicSession;
85
+ private readonly defaultRunGitCommand;
86
+ private readonly defaultStartRuntime;
87
+ private readonly defaultStopRuntime;
88
+ private readonly defaultWaitForRuntimeHealth;
89
+ }
90
+ export {};
91
+ //# sourceMappingURL=HostedDemoSessionService.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"HostedDemoSessionService.d.ts","sourceRoot":"","sources":["../../src/services/HostedDemoSessionService.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,568 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.HostedDemoSessionService = void 0;
7
+ const child_process_1 = require("child_process");
8
+ const crypto_1 = require("crypto");
9
+ const fs_1 = __importDefault(require("fs"));
10
+ const path_1 = __importDefault(require("path"));
11
+ const util_1 = require("util");
12
+ const logger_1 = require("../utils/logger");
13
+ const portUtils_1 = require("../utils/portUtils");
14
+ const HostedDemoSessionStore_1 = require("./HostedDemoSessionStore");
15
+ const execFileAsync = (0, util_1.promisify)(child_process_1.execFile);
16
+ const log = (0, logger_1.createLogger)('HostedDemoSessionService');
17
+ const CLEANUP_INTERVAL_MS = 30_000;
18
+ const MIN_PERSIST_TTL_MS = 1_000;
19
+ const DEFAULT_SESSION_TTL_MS = 30 * 24 * 60 * 60 * 1000;
20
+ const DEFAULT_SESSION_IDLE_TTL_MS = 15 * 60 * 1000;
21
+ const DEFAULT_MAX_ACTIVE_SESSIONS = 25;
22
+ const DEFAULT_BASE_RIVET_PORT = 5000;
23
+ class HostedDemoSessionService {
24
+ config;
25
+ deps;
26
+ telemetry;
27
+ sessionStore;
28
+ sessions = new Map();
29
+ pendingSessionCreations = 0;
30
+ cleanupTimer;
31
+ rehydrationLocks = new Map();
32
+ constructor(config, deps) {
33
+ const { telemetry, sessionMetadataStore, ...runtimeConfig } = config;
34
+ const worktreesRootPath = runtimeConfig.worktreesRootPath ??
35
+ path_1.default.join(runtimeConfig.repoPath, '.rivet-demo-worktrees');
36
+ this.telemetry = telemetry;
37
+ this.sessionStore = sessionMetadataStore ?? new HostedDemoSessionStore_1.InMemoryHostedDemoSessionStore();
38
+ this.config = {
39
+ ...runtimeConfig,
40
+ worktreesRootPath,
41
+ sessionTtlMs: runtimeConfig.sessionTtlMs ?? DEFAULT_SESSION_TTL_MS,
42
+ sessionIdleTtlMs: runtimeConfig.sessionIdleTtlMs ?? DEFAULT_SESSION_IDLE_TTL_MS,
43
+ maxActiveSessions: runtimeConfig.maxActiveSessions ?? DEFAULT_MAX_ACTIVE_SESSIONS,
44
+ baseRivetPort: runtimeConfig.baseRivetPort ?? DEFAULT_BASE_RIVET_PORT,
45
+ publicBaseUrl: runtimeConfig.publicBaseUrl ?? '',
46
+ };
47
+ this.deps = {
48
+ now: deps?.now ?? (() => Date.now()),
49
+ runGitCommand: deps?.runGitCommand ?? this.defaultRunGitCommand,
50
+ startRuntime: deps?.startRuntime ?? this.defaultStartRuntime,
51
+ stopRuntime: deps?.stopRuntime ?? this.defaultStopRuntime,
52
+ waitForRuntimeHealth: deps?.waitForRuntimeHealth ?? this.defaultWaitForRuntimeHealth,
53
+ };
54
+ fs_1.default.mkdirSync(this.config.worktreesRootPath, { recursive: true });
55
+ this.cleanupTimer = setInterval(() => {
56
+ void this.cleanupExpiredSessions();
57
+ }, CLEANUP_INTERVAL_MS);
58
+ this.cleanupTimer.unref();
59
+ }
60
+ /**
61
+ * Loads durable session rows into memory so stopped sessions can be rehydrated after deploy.
62
+ */
63
+ async initialize() {
64
+ await this.sessionStore.initialize();
65
+ const records = await this.sessionStore.loadAll();
66
+ for (const { sessionId, value } of records) {
67
+ if (value.expiresAt <= this.deps.now()) {
68
+ await this.sessionStore.delete(sessionId);
69
+ continue;
70
+ }
71
+ if (!fs_1.default.existsSync(value.worktreePath)) {
72
+ await this.sessionStore.delete(sessionId);
73
+ continue;
74
+ }
75
+ const internal = this.internalFromPersisted(value);
76
+ this.sessions.set(sessionId, internal);
77
+ log.info(`Hosted demo session restored from store ${sessionId}`, {
78
+ activeSessions: this.sessions.size,
79
+ });
80
+ }
81
+ }
82
+ async createSession(userId) {
83
+ await this.cleanupExpiredSessions();
84
+ const runningOrStartingCount = this.countRunningOrStartingSessions();
85
+ const activeAndPendingRuntimeCount = runningOrStartingCount + this.pendingSessionCreations;
86
+ if (activeAndPendingRuntimeCount >= this.config.maxActiveSessions) {
87
+ this.telemetry?.trackTryoutSessionCapacityRejected({
88
+ activeSessions: activeAndPendingRuntimeCount,
89
+ maxActiveSessions: this.config.maxActiveSessions,
90
+ deploymentEnv: process.env.NODE_ENV ?? 'development',
91
+ templateProject: this.config.templateProjectPath,
92
+ });
93
+ throw new Error('Maximum active demo sessions reached');
94
+ }
95
+ this.pendingSessionCreations += 1;
96
+ const sessionId = (0, crypto_1.randomUUID)();
97
+ const worktreePath = path_1.default.join(this.config.worktreesRootPath, sessionId);
98
+ const projectPath = path_1.default.join(worktreePath, this.config.templateProjectPath);
99
+ const rivetPort = await (0, portUtils_1.findAvailablePort)(this.config.baseRivetPort);
100
+ const createdAt = this.deps.now();
101
+ const expiresAt = createdAt + this.config.sessionTtlMs;
102
+ await this.deps.runGitCommand(this.config.repoPath, [
103
+ 'worktree',
104
+ 'add',
105
+ '--detach',
106
+ worktreePath,
107
+ 'HEAD',
108
+ ]);
109
+ try {
110
+ if (!fs_1.default.existsSync(projectPath)) {
111
+ throw new Error(`Template project path not found: ${this.config.templateProjectPath}`);
112
+ }
113
+ const runtimeProcess = await this.deps.startRuntime({
114
+ sessionId,
115
+ projectPath,
116
+ rivetPort,
117
+ });
118
+ try {
119
+ await this.deps.waitForRuntimeHealth(rivetPort);
120
+ }
121
+ catch (error) {
122
+ try {
123
+ await this.deps.stopRuntime(runtimeProcess);
124
+ }
125
+ catch (runtimeStopError) {
126
+ this.telemetry?.trackTryoutSessionCleanupFailed({
127
+ demoSessionId: sessionId,
128
+ errorType: this.toErrorType(runtimeStopError),
129
+ stage: 'runtime_stop',
130
+ deploymentEnv: process.env.NODE_ENV ?? 'development',
131
+ templateProject: this.config.templateProjectPath,
132
+ });
133
+ log.warn(`Failed to stop runtime for hosted demo session ${sessionId} after health check failure`, runtimeStopError);
134
+ }
135
+ throw error;
136
+ }
137
+ const session = {
138
+ sessionId,
139
+ userId,
140
+ createdAt,
141
+ expiresAt,
142
+ lastActionAt: createdAt,
143
+ worktreePath,
144
+ projectPath,
145
+ rivetPort,
146
+ runtimeState: 'running',
147
+ runtimeProcess,
148
+ lastRuntimeStartAt: createdAt,
149
+ };
150
+ this.sessions.set(sessionId, session);
151
+ await this.persistSession(session);
152
+ log.info(`Hosted demo session created ${sessionId}`, {
153
+ activeSessions: this.sessions.size,
154
+ });
155
+ this.telemetry?.trackTryoutRuntimeStarted({
156
+ demoSessionId: sessionId,
157
+ rivetPort,
158
+ deploymentEnv: process.env.NODE_ENV ?? 'development',
159
+ templateProject: this.config.templateProjectPath,
160
+ });
161
+ return {
162
+ ...this.toPublicSession(session),
163
+ sessionUrl: this.getSessionUrl(sessionId),
164
+ };
165
+ }
166
+ catch (error) {
167
+ await this.safeRemoveWorktree(worktreePath);
168
+ this.telemetry?.trackTryoutSessionProvisionFailed({
169
+ errorType: this.toErrorType(error),
170
+ deploymentEnv: process.env.NODE_ENV ?? 'development',
171
+ templateProject: this.config.templateProjectPath,
172
+ });
173
+ throw error;
174
+ }
175
+ finally {
176
+ this.pendingSessionCreations -= 1;
177
+ }
178
+ }
179
+ getActiveSessionCount() {
180
+ return this.sessions.size;
181
+ }
182
+ getTemplateProjectPath() {
183
+ return this.config.templateProjectPath;
184
+ }
185
+ getSession(sessionId) {
186
+ const session = this.sessions.get(sessionId);
187
+ if (!session) {
188
+ return null;
189
+ }
190
+ return this.toPublicSession(session);
191
+ }
192
+ getSessionOwnerUserId(sessionId) {
193
+ const session = this.sessions.get(sessionId);
194
+ if (!session) {
195
+ return null;
196
+ }
197
+ return session.userId;
198
+ }
199
+ /**
200
+ * Records a Rivet user action for idle TTL (does not extend idle on passive polling).
201
+ */
202
+ async recordSessionAction(sessionId) {
203
+ const session = this.sessions.get(sessionId);
204
+ if (!session) {
205
+ return;
206
+ }
207
+ session.lastActionAt = this.deps.now();
208
+ await this.persistSession(session);
209
+ }
210
+ getSessionProxyTarget(sessionId) {
211
+ const session = this.sessions.get(sessionId);
212
+ if (!session) {
213
+ return null;
214
+ }
215
+ if (session.runtimeState !== 'running' || !session.runtimeProcess) {
216
+ return null;
217
+ }
218
+ return `http://127.0.0.1:${session.rivetPort}`;
219
+ }
220
+ /**
221
+ * Starts the per-session runtime when it was stopped for idle or after process restart.
222
+ */
223
+ async ensureRuntimeReady(sessionId) {
224
+ const session = this.sessions.get(sessionId);
225
+ if (!session) {
226
+ throw new Error('Session not found');
227
+ }
228
+ while (true) {
229
+ const current = this.sessions.get(sessionId);
230
+ if (!current) {
231
+ throw new Error('Session not found');
232
+ }
233
+ if (current.runtimeState === 'running' &&
234
+ current.runtimeProcess &&
235
+ !current.runtimeProcess.killed) {
236
+ return;
237
+ }
238
+ const inFlight = this.rehydrationLocks.get(sessionId);
239
+ if (inFlight) {
240
+ await inFlight;
241
+ continue;
242
+ }
243
+ const work = this.runRehydration(sessionId);
244
+ this.rehydrationLocks.set(sessionId, work);
245
+ try {
246
+ await work;
247
+ return;
248
+ }
249
+ finally {
250
+ this.rehydrationLocks.delete(sessionId);
251
+ }
252
+ }
253
+ }
254
+ async closeSession(sessionId, options) {
255
+ const removeWorktree = options?.removeWorktree ?? true;
256
+ const session = this.sessions.get(sessionId);
257
+ if (!session) {
258
+ return;
259
+ }
260
+ this.sessions.delete(sessionId);
261
+ await this.sessionStore.delete(sessionId);
262
+ log.info(`Hosted demo session closed ${sessionId}`, {
263
+ activeSessions: this.sessions.size,
264
+ });
265
+ if (session.runtimeProcess && !session.runtimeProcess.killed) {
266
+ try {
267
+ await this.deps.stopRuntime(session.runtimeProcess);
268
+ }
269
+ catch (error) {
270
+ this.telemetry?.trackTryoutSessionCleanupFailed({
271
+ demoSessionId: sessionId,
272
+ errorType: this.toErrorType(error),
273
+ stage: 'runtime_stop',
274
+ deploymentEnv: process.env.NODE_ENV ?? 'development',
275
+ templateProject: this.config.templateProjectPath,
276
+ });
277
+ log.warn(`Failed to stop runtime for hosted demo session ${sessionId}`, error);
278
+ }
279
+ }
280
+ if (removeWorktree) {
281
+ await this.safeRemoveWorktree(session.worktreePath, sessionId);
282
+ }
283
+ }
284
+ async shutdown() {
285
+ clearInterval(this.cleanupTimer);
286
+ const sessionIds = [...this.sessions.keys()];
287
+ for (const sessionId of sessionIds) {
288
+ await this.closeSession(sessionId);
289
+ }
290
+ await this.sessionStore.shutdown();
291
+ }
292
+ countRunningOrStartingSessions = () => {
293
+ let count = 0;
294
+ for (const session of this.sessions.values()) {
295
+ if (session.runtimeState === 'running' || session.runtimeState === 'starting') {
296
+ count += 1;
297
+ }
298
+ }
299
+ return count;
300
+ };
301
+ persistSession = async (session) => {
302
+ const ttlMs = Math.max(MIN_PERSIST_TTL_MS, session.expiresAt - this.deps.now());
303
+ await this.sessionStore.set(session.sessionId, this.toPersisted(session), ttlMs);
304
+ };
305
+ toPersisted = (session) => {
306
+ return {
307
+ sessionId: session.sessionId,
308
+ userId: session.userId,
309
+ createdAt: session.createdAt,
310
+ expiresAt: session.expiresAt,
311
+ lastActionAt: session.lastActionAt,
312
+ lastRuntimeStartAt: session.lastRuntimeStartAt,
313
+ worktreePath: session.worktreePath,
314
+ projectPath: session.projectPath,
315
+ rivetPort: session.rivetPort,
316
+ runtimeState: session.runtimeState,
317
+ };
318
+ };
319
+ internalFromPersisted = (value) => {
320
+ return {
321
+ sessionId: value.sessionId,
322
+ userId: value.userId,
323
+ createdAt: value.createdAt,
324
+ expiresAt: value.expiresAt,
325
+ lastActionAt: value.lastActionAt,
326
+ worktreePath: value.worktreePath,
327
+ projectPath: value.projectPath,
328
+ rivetPort: value.rivetPort,
329
+ runtimeState: 'stopped',
330
+ runtimeProcess: null,
331
+ lastRuntimeStartAt: value.lastRuntimeStartAt,
332
+ };
333
+ };
334
+ runRehydration = async (sessionId) => {
335
+ const session = this.sessions.get(sessionId);
336
+ if (!session) {
337
+ throw new Error('Session not found');
338
+ }
339
+ if (session.runtimeState === 'running' &&
340
+ session.runtimeProcess &&
341
+ !session.runtimeProcess.killed) {
342
+ return;
343
+ }
344
+ const startedAt = this.deps.now();
345
+ this.telemetry?.trackTryoutRuntimeRehydrationStarted({
346
+ demoSessionId: sessionId,
347
+ deploymentEnv: process.env.NODE_ENV ?? 'development',
348
+ templateProject: this.config.templateProjectPath,
349
+ });
350
+ session.runtimeState = 'starting';
351
+ await this.persistSession(session);
352
+ try {
353
+ if (session.runtimeProcess && !session.runtimeProcess.killed) {
354
+ await this.deps.stopRuntime(session.runtimeProcess);
355
+ }
356
+ session.runtimeProcess = null;
357
+ const rivetPort = await (0, portUtils_1.findAvailablePort)(this.config.baseRivetPort);
358
+ session.rivetPort = rivetPort;
359
+ const runtimeProcess = await this.deps.startRuntime({
360
+ sessionId: session.sessionId,
361
+ projectPath: session.projectPath,
362
+ rivetPort,
363
+ });
364
+ session.runtimeProcess = runtimeProcess;
365
+ await this.deps.waitForRuntimeHealth(rivetPort);
366
+ session.runtimeState = 'running';
367
+ session.lastRuntimeStartAt = this.deps.now();
368
+ await this.persistSession(session);
369
+ this.telemetry?.trackTryoutRuntimeRehydrationCompleted({
370
+ demoSessionId: sessionId,
371
+ timeToReadyMs: this.deps.now() - startedAt,
372
+ rivetPort,
373
+ deploymentEnv: process.env.NODE_ENV ?? 'development',
374
+ templateProject: this.config.templateProjectPath,
375
+ });
376
+ this.telemetry?.trackTryoutRuntimeStarted({
377
+ demoSessionId: sessionId,
378
+ rivetPort,
379
+ deploymentEnv: process.env.NODE_ENV ?? 'development',
380
+ templateProject: this.config.templateProjectPath,
381
+ });
382
+ }
383
+ catch (error) {
384
+ session.runtimeState = 'failed';
385
+ session.runtimeProcess = null;
386
+ await this.persistSession(session);
387
+ this.telemetry?.trackTryoutRuntimeRehydrationFailed({
388
+ demoSessionId: sessionId,
389
+ errorType: this.toErrorType(error),
390
+ timeToFailureMs: this.deps.now() - startedAt,
391
+ deploymentEnv: process.env.NODE_ENV ?? 'development',
392
+ templateProject: this.config.templateProjectPath,
393
+ });
394
+ throw error;
395
+ }
396
+ };
397
+ stopRuntimeForIdle = async (sessionId) => {
398
+ const session = this.sessions.get(sessionId);
399
+ if (!session || session.runtimeState !== 'running') {
400
+ return;
401
+ }
402
+ if (!session.runtimeProcess || session.runtimeProcess.killed) {
403
+ session.runtimeState = 'stopped';
404
+ session.runtimeProcess = null;
405
+ await this.persistSession(session);
406
+ return;
407
+ }
408
+ try {
409
+ await this.deps.stopRuntime(session.runtimeProcess);
410
+ }
411
+ catch (error) {
412
+ this.telemetry?.trackTryoutSessionCleanupFailed({
413
+ demoSessionId: sessionId,
414
+ errorType: this.toErrorType(error),
415
+ stage: 'runtime_stop',
416
+ deploymentEnv: process.env.NODE_ENV ?? 'development',
417
+ templateProject: this.config.templateProjectPath,
418
+ });
419
+ log.warn(`Failed to stop idle runtime for hosted demo session ${sessionId}`, error);
420
+ }
421
+ session.runtimeProcess = null;
422
+ session.runtimeState = 'stopped';
423
+ await this.persistSession(session);
424
+ this.telemetry?.trackTryoutRuntimeStoppedIdle({
425
+ demoSessionId: sessionId,
426
+ deploymentEnv: process.env.NODE_ENV ?? 'development',
427
+ templateProject: this.config.templateProjectPath,
428
+ });
429
+ };
430
+ cleanupExpiredSessions = async () => {
431
+ const now = this.deps.now();
432
+ const sessionEntries = [...this.sessions.entries()];
433
+ for (const [sessionId, session] of sessionEntries) {
434
+ if (session.expiresAt <= now) {
435
+ const startedAt = this.deps.now();
436
+ log.info(`Cleaning up expired hosted demo session ${sessionId}`);
437
+ await this.closeSession(sessionId);
438
+ this.telemetry?.trackTryoutSessionExpiredCleaned({
439
+ demoSessionId: sessionId,
440
+ cleanupDurationMs: this.deps.now() - startedAt,
441
+ deploymentEnv: process.env.NODE_ENV ?? 'development',
442
+ templateProject: this.config.templateProjectPath,
443
+ });
444
+ }
445
+ }
446
+ const idleEntries = [...this.sessions.entries()];
447
+ for (const [sessionId, session] of idleEntries) {
448
+ if (session.expiresAt <= now) {
449
+ continue;
450
+ }
451
+ if (session.runtimeState === 'running' &&
452
+ now - session.lastActionAt >= this.config.sessionIdleTtlMs) {
453
+ log.info(`Stopping idle runtime for hosted demo session ${sessionId}`);
454
+ await this.stopRuntimeForIdle(sessionId);
455
+ }
456
+ }
457
+ };
458
+ safeRemoveWorktree = async (worktreePath, sessionId) => {
459
+ try {
460
+ await this.deps.runGitCommand(this.config.repoPath, [
461
+ 'worktree',
462
+ 'remove',
463
+ '--force',
464
+ worktreePath,
465
+ ]);
466
+ }
467
+ catch (error) {
468
+ this.telemetry?.trackTryoutSessionCleanupFailed({
469
+ demoSessionId: sessionId,
470
+ errorType: this.toErrorType(error),
471
+ stage: 'worktree_remove',
472
+ deploymentEnv: process.env.NODE_ENV ?? 'development',
473
+ templateProject: this.config.templateProjectPath,
474
+ });
475
+ log.warn(`Failed to remove worktree at ${worktreePath}`, error);
476
+ }
477
+ };
478
+ toErrorType = (error) => {
479
+ if (error instanceof Error) {
480
+ return error.message;
481
+ }
482
+ return 'unknown_error';
483
+ };
484
+ getSessionUrl(sessionId) {
485
+ const baseUrl = this.config.publicBaseUrl.replace(/\/$/, '');
486
+ if (!baseUrl) {
487
+ return `/try/${sessionId}`;
488
+ }
489
+ return `${baseUrl}/try/${sessionId}`;
490
+ }
491
+ toPublicSession = (session) => {
492
+ return {
493
+ sessionId: session.sessionId,
494
+ userId: session.userId,
495
+ createdAt: session.createdAt,
496
+ expiresAt: session.expiresAt,
497
+ lastActionAt: session.lastActionAt,
498
+ worktreePath: session.worktreePath,
499
+ projectPath: session.projectPath,
500
+ rivetPort: session.rivetPort,
501
+ runtimeState: session.runtimeState,
502
+ };
503
+ };
504
+ defaultRunGitCommand = async (repoPath, args) => {
505
+ await execFileAsync('git', args, { cwd: repoPath });
506
+ };
507
+ defaultStartRuntime = async (params) => {
508
+ const runtimeScriptPath = path_1.default.join(this.config.repoPath, 'dist', 'demo', 'sessionRuntime.js');
509
+ const runtimeProcess = (0, child_process_1.spawn)(process.execPath, [runtimeScriptPath], {
510
+ env: {
511
+ ...process.env,
512
+ RIVET_SESSION_ID: params.sessionId,
513
+ RIVET_SESSION_PROJECT_PATH: params.projectPath,
514
+ RIVET_SESSION_RIVET_PORT: String(params.rivetPort),
515
+ },
516
+ stdio: 'pipe',
517
+ });
518
+ runtimeProcess.stderr?.on('data', (chunk) => {
519
+ log.warn(`[${params.sessionId}] ${chunk.toString().trim()}`);
520
+ });
521
+ runtimeProcess.stdout?.on('data', (chunk) => {
522
+ log.debug(`[${params.sessionId}] ${chunk.toString().trim()}`);
523
+ });
524
+ return runtimeProcess;
525
+ };
526
+ defaultStopRuntime = async (runtimeProcess) => {
527
+ if (runtimeProcess.killed || runtimeProcess.exitCode !== null) {
528
+ return;
529
+ }
530
+ await new Promise((resolve) => {
531
+ const timeout = setTimeout(() => {
532
+ runtimeProcess.kill('SIGKILL');
533
+ }, 5_000);
534
+ runtimeProcess.once('exit', () => {
535
+ clearTimeout(timeout);
536
+ resolve();
537
+ });
538
+ runtimeProcess.kill('SIGTERM');
539
+ });
540
+ };
541
+ defaultWaitForRuntimeHealth = async (rivetPort) => {
542
+ const deadline = this.deps.now() + 20_000;
543
+ while (this.deps.now() < deadline) {
544
+ try {
545
+ const healthResponse = await fetch(`http://127.0.0.1:${rivetPort}/api/health`, {
546
+ signal: AbortSignal.timeout(1_000),
547
+ });
548
+ if (!healthResponse.ok) {
549
+ await new Promise((resolve) => setTimeout(resolve, 500));
550
+ continue;
551
+ }
552
+ const staticResponse = await fetch(`http://127.0.0.1:${rivetPort}/static/`, {
553
+ signal: AbortSignal.timeout(1_000),
554
+ });
555
+ if (staticResponse.ok) {
556
+ return;
557
+ }
558
+ }
559
+ catch (error) {
560
+ log.debug(`Hosted demo runtime health poll failed for port ${rivetPort}`, error);
561
+ }
562
+ await new Promise((resolve) => setTimeout(resolve, 500));
563
+ }
564
+ throw new Error(`Timed out waiting for runtime health on port ${rivetPort}`);
565
+ };
566
+ }
567
+ exports.HostedDemoSessionService = HostedDemoSessionService;
568
+ //# sourceMappingURL=HostedDemoSessionService.js.map