rivet-design 0.26.2 → 0.27.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 (94) hide show
  1. package/dist/agent-variants/DirectionRefReservations.d.ts +11 -0
  2. package/dist/agent-variants/DirectionRefReservations.js +47 -0
  3. package/dist/agent-variants/PreviewRuntime.js +20 -7
  4. package/dist/agent-variants/SessionStore.d.ts +10 -1
  5. package/dist/agent-variants/SessionStore.js +25 -2
  6. package/dist/agent-variants/WorkItemRequestTimes.d.ts +6 -0
  7. package/dist/agent-variants/WorkItemRequestTimes.js +21 -0
  8. package/dist/agent-variants/WorktreeOrchestrator.d.ts +2 -1
  9. package/dist/agent-variants/WorktreeOrchestrator.js +92 -78
  10. package/dist/agent-variants/contracts.d.ts +120 -116
  11. package/dist/agent-variants/existingStaticEntry.d.ts +34 -0
  12. package/dist/agent-variants/existingStaticEntry.js +136 -0
  13. package/dist/agent-variants/staticPreviewFiles.js +1 -1
  14. package/dist/agent-variants/variant-project-runtime.d.ts +2 -1
  15. package/dist/agent-variants/variantsTelemetry.d.ts +8 -0
  16. package/dist/agent-variants/variantsTelemetry.js +5 -0
  17. package/dist/agent-variants/workItemInput.d.ts +168 -146
  18. package/dist/agent-variants/workItemInput.js +12 -1
  19. package/dist/cli/commands/directionTarget.d.ts +23 -0
  20. package/dist/cli/commands/directionTarget.js +87 -0
  21. package/dist/cli/commands/mcpServe.js +1 -0
  22. package/dist/cli/commands/open.js +28 -4
  23. package/dist/cli/commands/serve.js +2 -0
  24. package/dist/cli/commands/variants.js +36 -43
  25. package/dist/cli/envelope.d.ts +1 -1
  26. package/dist/cli/envelope.js +1 -0
  27. package/dist/cli/handoff.d.ts +1 -0
  28. package/dist/cli/handoff.js +20 -4
  29. package/dist/cli/projectRuntimeUpgrade.d.ts +11 -0
  30. package/dist/cli/projectRuntimeUpgrade.js +43 -11
  31. package/dist/cli/router.js +1 -0
  32. package/dist/cli/telemetry.d.ts +10 -0
  33. package/dist/cli/telemetry.js +37 -1
  34. package/dist/config/pluginMetrics.js +38 -0
  35. package/dist/index.d.ts +19 -0
  36. package/dist/index.js +14 -5
  37. package/dist/mcp/explorationSchema.js +1 -1
  38. package/dist/mcp/getExploration.js +11 -5
  39. package/dist/proxy-middleware/proxy-config.js +7 -0
  40. package/dist/routes/agentVariants.js +4 -0
  41. package/dist/routes/cli.js +10 -1
  42. package/dist/routes/library/middleware.js +11 -5
  43. package/dist/server.js +20 -6
  44. package/dist/services/DependencyPreparationService.js +57 -19
  45. package/dist/services/GuidanceVerifierRunner.js +4 -4
  46. package/dist/services/ProjectLibrary.d.ts +4 -1
  47. package/dist/services/ProjectLibrary.js +14 -9
  48. package/dist/services/ProjectPackageManager.d.ts +5 -0
  49. package/dist/services/ProjectPackageManager.js +33 -11
  50. package/dist/services/ProjectRuntimeService.d.ts +8 -0
  51. package/dist/services/ProjectRuntimeService.js +15 -2
  52. package/dist/services/TelemetryService.d.ts +13 -2
  53. package/dist/services/TelemetryService.js +29 -0
  54. package/dist/services/VariantHistoryService.d.ts +8 -0
  55. package/dist/services/VariantHistoryService.js +40 -0
  56. package/dist/services/WorktreeManager.d.ts +1 -0
  57. package/dist/services/WorktreeManager.js +17 -0
  58. package/dist/services/createAgentVariantsOrchestrator.js +9 -2
  59. package/dist/services/directionRefs.d.ts +21 -0
  60. package/dist/services/directionRefs.js +148 -0
  61. package/dist/services/profiles/DesignProfiles.js +16 -8
  62. package/dist/services/projectDirections.d.ts +22 -0
  63. package/dist/services/projectDirections.js +38 -0
  64. package/dist/types/direction-ref.d.ts +36 -0
  65. package/dist/types/direction-ref.js +3 -0
  66. package/dist/types/guidance-verifier.d.ts +7 -5
  67. package/dist/types/guidance-verifier.js +14 -1
  68. package/dist/types/variant-history.d.ts +3 -1
  69. package/dist/utils/devServerError.d.ts +5 -0
  70. package/dist/utils/devServerError.js +4 -0
  71. package/dist/utils/failureReason.d.ts +16 -0
  72. package/dist/utils/failureReason.js +106 -0
  73. package/dist/utils/hostedLogPrivacy.d.ts +1 -0
  74. package/dist/utils/hostedLogPrivacy.js +10 -4
  75. package/dist/utils/skills/claude-skill.d.ts +1 -1
  76. package/dist/utils/skills/claude-skill.js +1 -1
  77. package/dist/utils/skills/cli-guidance.d.ts +1 -1
  78. package/dist/utils/skills/cli-guidance.js +7 -6
  79. package/dist/utils/skills/cursor-rules.d.ts +1 -1
  80. package/dist/utils/skills/cursor-rules.js +1 -1
  81. package/dist/utils/subprocessEnvironment.d.ts +1 -1
  82. package/dist/utils/subprocessEnvironment.js +0 -1
  83. package/package.json +4 -4
  84. package/src/ui/dist/assets/LibraryEntry-BZ5EdFgA.js +37 -0
  85. package/src/ui/dist/assets/main-MepMmTwb.css +1 -0
  86. package/src/ui/dist/assets/main-RriU35pI.js +497 -0
  87. package/src/ui/dist/index.html +2 -2
  88. package/dist/services/StaticPreviewServer.d.ts +0 -8
  89. package/dist/services/StaticPreviewServer.js +0 -207
  90. package/src/ui/dist/assets/LibraryEntry-92R_T-Xa.js +0 -37
  91. package/src/ui/dist/assets/main-D0UmAaUe.css +0 -1
  92. package/src/ui/dist/assets/main-DgEfJvYu.js +0 -497
  93. package/src/ui/dist/fonts/Goldman-Bold.woff2 +0 -0
  94. package/src/ui/dist/fonts/Goldman-Regular.woff2 +0 -0
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.pluginMetricProperties = void 0;
4
4
  const zod_1 = require("zod");
5
+ const failureReason_1 = require("../utils/failureReason");
5
6
  const count = zod_1.z.number().finite().nonnegative().max(Number.MAX_SAFE_INTEGER);
6
7
  const metricProperties = zod_1.z.object({
7
8
  command: zod_1.z
@@ -19,6 +20,17 @@ const metricProperties = zod_1.z.object({
19
20
  'variants.cancel',
20
21
  'reference.fetch',
21
22
  'evidence.capture',
23
+ 'context.add',
24
+ 'context.update',
25
+ 'context.list',
26
+ 'context.show',
27
+ 'context.enable',
28
+ 'context.disable',
29
+ 'context.remove',
30
+ 'context.resolve',
31
+ 'context.inspect',
32
+ 'context.verifier',
33
+ 'context.verify',
22
34
  ])
23
35
  .optional(),
24
36
  tool_name: zod_1.z
@@ -108,12 +120,17 @@ const metricProperties = zod_1.z.object({
108
120
  'WAIT_TIMEOUT',
109
121
  'SESSION_NOT_FOUND',
110
122
  'WORK_ITEM_NOT_FOUND',
123
+ 'DIRECTION_AMBIGUOUS',
111
124
  'MCP_REMOVED',
112
125
  'STATIC_PREVIEW_FAILED',
113
126
  'DEV_SERVER_FAILED',
114
127
  'PREVIEW_UNAVAILABLE',
128
+ 'DEPENDENCY_INSTALL_FAILED',
129
+ 'DEV_SERVER_START_FAILED',
130
+ 'DEV_SCRIPT_MISSING',
115
131
  ])
116
132
  .optional(),
133
+ reason_code: zod_1.z.enum(failureReason_1.FAILURE_REASON_CODES).optional(),
117
134
  failure_reason: zod_1.z
118
135
  .enum([
119
136
  'ack_failed',
@@ -148,6 +165,7 @@ const metricProperties = zod_1.z.object({
148
165
  'usage_error',
149
166
  'wait_timeout',
150
167
  'session_not_found',
168
+ 'direction_ambiguous',
151
169
  'mcp_removed',
152
170
  'server_start_timeout',
153
171
  'handshake_missing',
@@ -169,6 +187,26 @@ const metricProperties = zod_1.z.object({
169
187
  'editor_server_start_failed',
170
188
  ])
171
189
  .optional(),
190
+ verification_outcome: zod_1.z
191
+ .enum([
192
+ 'pass',
193
+ 'fail',
194
+ 'blocked',
195
+ 'package_required',
196
+ 'verifier_missing',
197
+ 'verifier_stale',
198
+ 'cancelled',
199
+ 'timeout',
200
+ 'output_limit',
201
+ 'process_failed',
202
+ 'invalid_result',
203
+ 'invalid_evidence',
204
+ 'cleanup_failed',
205
+ 'setup_failed',
206
+ ])
207
+ .optional(),
208
+ verification_check_count: count.int().optional(),
209
+ verification_duration_ms: count.optional(),
172
210
  ok: zod_1.z.boolean().optional(),
173
211
  is_recoverable: zod_1.z.boolean().optional(),
174
212
  is_fresh: zod_1.z.boolean().optional(),
package/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { ProjectDetectionService } from './index-core';
2
+ import { AuthService } from './services/AuthService';
2
3
  import { SessionBridgeService } from './services/SessionBridgeService';
3
4
  import type { Framework } from './services/ProjectDetectionService';
4
5
  import { DevServerRuntimeService, type StartDevServerResult } from './services/DevServerRuntimeService';
@@ -75,6 +76,24 @@ export declare const authCommandOptionsForArgs: (args: string[]) => {
75
76
  };
76
77
  export declare const rememberInstallRef: (args: string[], downloadType?: string) => string[];
77
78
  export declare const authFailureReasonForError: (error: string | undefined) => "oauth_failed" | "timeout";
79
+ export declare const signInWithoutBrowser: (authService?: AuthService) => Promise<{
80
+ success: boolean;
81
+ token?: string | undefined;
82
+ refreshToken?: string | undefined;
83
+ user?: {
84
+ id: string;
85
+ email: string;
86
+ displayName?: string | undefined;
87
+ } | undefined;
88
+ profile?: {
89
+ remaining_spend_usd?: number | null | undefined;
90
+ monthly_limit_usd?: number | null | undefined;
91
+ current_month_spend_usd?: number | null | undefined;
92
+ total_spend_usd?: number | null | undefined;
93
+ } | null | undefined;
94
+ error?: string | undefined;
95
+ message?: string | undefined;
96
+ }>;
78
97
  export declare const main: (args?: string[]) => Promise<void>;
79
98
  export declare const parseArgs: (args: string[]) => CLIOptions;
80
99
  export declare const handleLogin: (options?: {
package/dist/index.js CHANGED
@@ -36,7 +36,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
36
36
  return (mod && mod.__esModule) ? mod : { "default": mod };
37
37
  };
38
38
  Object.defineProperty(exports, "__esModule", { value: true });
39
- exports.handleLogin = exports.parseArgs = exports.main = exports.authFailureReasonForError = exports.rememberInstallRef = exports.authCommandOptionsForArgs = exports.registerOwnedDevServerSignalHandlers = exports.isAgentVariantsEnabled = exports.updateOwnedDevServerHealthOnExit = exports.buildAgentDevServerHealth = exports.resolveAgentDevServer = exports.buildAgentDevServerContext = exports.DEFAULT_USER_PORT = exports.DEFAULT_PORT = void 0;
39
+ exports.handleLogin = exports.parseArgs = exports.main = exports.signInWithoutBrowser = exports.authFailureReasonForError = exports.rememberInstallRef = exports.authCommandOptionsForArgs = exports.registerOwnedDevServerSignalHandlers = exports.isAgentVariantsEnabled = exports.updateOwnedDevServerHealthOnExit = exports.buildAgentDevServerHealth = exports.resolveAgentDevServer = exports.buildAgentDevServerContext = exports.DEFAULT_USER_PORT = exports.DEFAULT_PORT = void 0;
40
40
  /* eslint-disable no-console */
41
41
  const dotenv = __importStar(require("dotenv"));
42
42
  const crypto_1 = require("crypto");
@@ -260,6 +260,18 @@ const discardInstallRefForValidatedSession = () => {
260
260
  };
261
261
  const authFailureReasonForError = (error) => error?.trim().toLowerCase() === 'timeout' ? 'timeout' : 'oauth_failed';
262
262
  exports.authFailureReasonForError = authFailureReasonForError;
263
+ const signInWithoutBrowser = async (authService = new AuthService_1.AuthService()) => {
264
+ console.log('\n🔐 Authentication required');
265
+ console.log('Open this link to sign in:');
266
+ return authService.loginWithGoogle({
267
+ source: 'cli',
268
+ noBrowser: true,
269
+ onAuthUrl: (url) => {
270
+ console.log(`\n ${url}\n`);
271
+ },
272
+ });
273
+ };
274
+ exports.signInWithoutBrowser = signInWithoutBrowser;
263
275
  const main = async (args = process.argv.slice(2)) => {
264
276
  if (args.includes('--no-telemetry'))
265
277
  process.env.RIVET_TELEMETRY_DISABLED = '1';
@@ -452,10 +464,7 @@ const main = async (args = process.argv.slice(2)) => {
452
464
  process.exit(1);
453
465
  }
454
466
  if (options.noBrowser) {
455
- console.log('\n🔐 Authentication required');
456
- console.log('Opening browser for Google login...\n');
457
- const authService = new AuthService_1.AuthService();
458
- const loginResult = await authService.loginWithGoogle();
467
+ const loginResult = await (0, exports.signInWithoutBrowser)();
459
468
  if (!loginResult.success) {
460
469
  console.log(`\n❌ Authentication failed: ${loginResult.error || loginResult.message}`);
461
470
  process.exit(1);
@@ -117,7 +117,7 @@ exports.explorationShape = {
117
117
  baseDirectionId: v3_1.z
118
118
  .string()
119
119
  .optional()
120
- .describe('start: sessionId:variantId — refine an existing variant in place'),
120
+ .describe('start: direction ref (e.g. warm-editorial-green), sessionId:variantId, or projectId/sessionId:variantId — refine an existing variant in place'),
121
121
  project: v3_1.z.string().trim().min(1).describe('Absolute project root path'),
122
122
  };
123
123
  //# sourceMappingURL=explorationSchema.js.map
@@ -17,6 +17,8 @@ const ConfigManager_1 = require("../services/ConfigManager");
17
17
  const ProjectLibrary_1 = require("../services/ProjectLibrary");
18
18
  const VariantHistoryService_1 = require("../services/VariantHistoryService");
19
19
  const historyRoots_1 = require("../services/historyRoots");
20
+ const directionRefs_1 = require("../services/directionRefs");
21
+ const projectDirections_1 = require("../services/projectDirections");
20
22
  const profileAccess_1 = require("./profileAccess");
21
23
  const getExploration = async (input) => {
22
24
  const command = 'rivet_get_exploration';
@@ -68,12 +70,16 @@ const getExploration = async (input) => {
68
70
  ? registered.library.registry.historyRoots(registered.owner.id)
69
71
  : [project, ...(0, historyRoots_1.nestedFreshSessionWorkdirs)(project)];
70
72
  if (!hasLiveServer || input.includeHistory || input.sessionId) {
73
+ const directions = await (0, projectDirections_1.listProjectDirections)(registered
74
+ ? {
75
+ kind: 'library',
76
+ library: registered.library,
77
+ projectId: registered.owner.id,
78
+ }
79
+ : { kind: 'history', history, projectPath: project });
71
80
  const entries = registered
72
- ? await registered.library.listVariants(registered.owner.id)
73
- : (await Promise.all(roots.map(async (projectPath) => (await history.listVariants({ projectPath })).map((entry) => ({
74
- ...entry,
75
- projectPath,
76
- }))))).flat();
81
+ ? directions.filter((entry) => (0, directionRefs_1.isDirectionActive)(entry.manifest.status))
82
+ : directions;
77
83
  data.history = entries.filter((entry) => (!input.sessionId || entry.manifest.sessionId === input.sessionId) &&
78
84
  (!input.variantId || entry.manifest.variantId === input.variantId));
79
85
  if (input.variantId && input.sessionId) {
@@ -13,6 +13,10 @@ const TARGET_ADDRESSED_PREVIEW_PREFIX = '/__rivet/preview';
13
13
  const TARGET_ADDRESSED_PREVIEW_KIND_BASE = 'base';
14
14
  const TARGET_ADDRESSED_PREVIEW_KIND_HISTORY = 'history';
15
15
  const TARGET_ADDRESSED_PREVIEW_KIND_VARIANT = 'variant';
16
+ const CACHE_VALIDATOR_REQUEST_HEADERS = [
17
+ 'if-none-match',
18
+ 'if-modified-since',
19
+ ];
16
20
  const hasValidUserDevPort = (userPort) => Number.isInteger(userPort) && userPort > 0;
17
21
  const resolveTargetAddressedPreview = (args) => {
18
22
  const parsed = new URL(args.rawUrl ?? '/', 'http://rivet.local');
@@ -152,6 +156,9 @@ const createUserDevServerProxy = (userPort, userHost = 'localhost', options = {}
152
156
  requestTargets.set(req, target);
153
157
  if (target.isTargetAddressed) {
154
158
  req.url = target.path;
159
+ for (const header of CACHE_VALIDATOR_REQUEST_HEADERS) {
160
+ delete req.headers[header];
161
+ }
155
162
  }
156
163
  };
157
164
  const getRouterTargetForLibrary = (req) => {
@@ -543,6 +543,7 @@ const createAgentVariantsRouter = (orchestrator, bridge, telemetry, variantHisto
543
543
  filesDir,
544
544
  entryPath: preview.entryPath,
545
545
  label: detail.manifest.label,
546
+ directionRef: detail.manifest.directionRef,
546
547
  description: detail.manifest.brief,
547
548
  references: detail.manifest.references,
548
549
  previewRoutes: detail.manifest.previewRoutes,
@@ -1058,6 +1059,7 @@ const createAgentVariantsRouter = (orchestrator, bridge, telemetry, variantHisto
1058
1059
  sessionId: entry.manifest.sessionId,
1059
1060
  variantId: entry.manifest.variantId,
1060
1061
  requestId: entry.manifest.requestId,
1062
+ directionRef: entry.manifest.directionRef,
1061
1063
  label: entry.manifest.label,
1062
1064
  brief: entry.manifest.brief,
1063
1065
  references: entry.manifest.references,
@@ -1507,6 +1509,7 @@ const createAgentVariantsRouter = (orchestrator, bridge, telemetry, variantHisto
1507
1509
  filesDir,
1508
1510
  entryPath: preview.entryPath,
1509
1511
  label: detail.manifest.label,
1512
+ directionRef: detail.manifest.directionRef,
1510
1513
  description: detail.manifest.brief,
1511
1514
  references: detail.manifest.references,
1512
1515
  previewRoutes: detail.manifest.previewRoutes,
@@ -1580,6 +1583,7 @@ const createAgentVariantsRouter = (orchestrator, bridge, telemetry, variantHisto
1580
1583
  filesDir,
1581
1584
  entryPath: preview.entryPath,
1582
1585
  label: detail.manifest.label,
1586
+ directionRef: detail.manifest.directionRef,
1583
1587
  description: detail.manifest.brief,
1584
1588
  references: detail.manifest.references,
1585
1589
  previewRoutes: detail.manifest.previewRoutes,
@@ -102,8 +102,17 @@ const createCliRouter = (options) => {
102
102
  setImmediate(() => options.onShutdown?.());
103
103
  return;
104
104
  }
105
+ const isIdleRequired = req.body?.requireIdle === true;
105
106
  const activity = (0, runtimeActivity_1.classifyRuntimeActivity)(readStatus());
106
- if (!admission.beginRetirement(activity, true)) {
107
+ if (!admission.beginRetirement(activity, !isIdleRequired)) {
108
+ if (isIdleRequired) {
109
+ res.status(409).json({
110
+ reason: 'active_work',
111
+ error: 'Rivet left the server running because it has active work or is already restarting.',
112
+ upgrade: admission.status(),
113
+ });
114
+ return;
115
+ }
107
116
  res.status(409).json({
108
117
  reason: 'server_retiring',
109
118
  error: 'Rivet is already restarting. Retry after the runtime handoff completes.',
@@ -45,13 +45,18 @@ const authenticateLibrary = (token, sessions) => (req, res, next) => {
45
45
  next();
46
46
  };
47
47
  exports.authenticateLibrary = authenticateLibrary;
48
+ const HANDLED_ERROR_KEY = 'libraryError';
48
49
  const trackLibraryOperation = (telemetry) => (operation) => (_req, res, next) => {
49
50
  const startedAt = Date.now();
50
- res.once('finish', () => telemetry?.trackLibraryOperation({
51
- operation,
52
- outcome: res.statusCode < 400 ? 'completed' : 'failed',
53
- durationMs: Date.now() - startedAt,
54
- }));
51
+ res.once('finish', () => {
52
+ const isCompleted = res.statusCode < 400;
53
+ telemetry?.trackLibraryOperation({
54
+ operation,
55
+ outcome: isCompleted ? 'completed' : 'failed',
56
+ durationMs: Date.now() - startedAt,
57
+ error: isCompleted ? undefined : res.locals[HANDLED_ERROR_KEY],
58
+ });
59
+ });
55
60
  next();
56
61
  };
57
62
  exports.trackLibraryOperation = trackLibraryOperation;
@@ -73,6 +78,7 @@ const libraryErrorStatus = (code) => {
73
78
  }
74
79
  };
75
80
  const handleLibraryError = (error, _req, res, _next) => {
81
+ res.locals[HANDLED_ERROR_KEY] = error;
76
82
  const message = (0, errorMessage_1.errorMessage)(error);
77
83
  if (error instanceof libraryErrors_1.LibraryError) {
78
84
  res
package/dist/server.js CHANGED
@@ -212,6 +212,7 @@ const startServer = async (options) => {
212
212
  ? (0, crypto_1.randomBytes)(QUEUE_ACCESS_TOKEN_BYTES).toString('base64url')
213
213
  : null;
214
214
  const variantsOrchestrator = options.variantsRuntime?.orchestrator ?? options.agentVariantsOrchestrator;
215
+ await variantsOrchestrator?.loadDirectionRefs();
215
216
  const inlineStartTracker = (0, inlineStartTracker_1.createInlineStartTracker)();
216
217
  const runtimeAdmission = new RuntimeAdmission_1.RuntimeAdmission(telemetry_2.RIVET_VERSION);
217
218
  const variantsBridge = options.variantsRuntime?.bridge ?? sessionBridge;
@@ -251,11 +252,16 @@ const startServer = async (options) => {
251
252
  const editorHost = () => `${EMBEDDED_AGENT_LISTEN_HOST}:${listenPort}`;
252
253
  const isPreviewRequest = (req) => req.headers.host === new URL(previewOrigin).host &&
253
254
  (previewPort === undefined || req.socket.localPort === previewPort);
255
+ const rejectRequest = ({ req, res, reason, }) => {
256
+ log.warn(`Rejected ${req.method} ${req.path} with 403: ${reason}`);
257
+ telemetry?.trackRequestRejected(reason);
258
+ res.sendStatus(403);
259
+ };
254
260
  app.use((req, res, next) => {
255
261
  const requestHost = req.headers.host;
256
262
  const isEditorRequest = requestHost === editorHost() && req.socket.localPort === listenPort;
257
263
  if (!isEditorRequest && !isPreviewRequest(req)) {
258
- res.sendStatus(403);
264
+ rejectRequest({ req, res, reason: 'host_not_allowed' });
259
265
  return;
260
266
  }
261
267
  next();
@@ -293,7 +299,7 @@ const startServer = async (options) => {
293
299
  return;
294
300
  }
295
301
  if (req.headers[editorAccess_2.EDITOR_ACCESS_TOKEN_HEADER]) {
296
- res.sendStatus(403);
302
+ rejectRequest({ req, res, reason: 'editor_token_on_preview' });
297
303
  return;
298
304
  }
299
305
  const isAppApi = req.path.toLowerCase().startsWith('/api/') &&
@@ -370,6 +376,14 @@ const startServer = async (options) => {
370
376
  app.get('/rivet/favicon.ico', (_req, res) => {
371
377
  res.sendFile('favicon.ico', { root: exports.DIST_UI_PATH });
372
378
  });
379
+ app.get(/^\/rivet\/library(?:\/.*)?$/, (req, res) => {
380
+ const resolvedLibraryOrigin = options.resolveLibraryOrigin?.();
381
+ if (!resolvedLibraryOrigin) {
382
+ res.sendStatus(404);
383
+ return;
384
+ }
385
+ res.redirect(302, `${new URL(resolvedLibraryOrigin).origin}${req.path}`);
386
+ });
373
387
  app.get(/^\/rivet(?:\/.*)?$/, (req, res, next) => {
374
388
  if ((0, exports.isRivetUiStaticAssetPath)(req.path)) {
375
389
  next();
@@ -430,7 +444,7 @@ const startServer = async (options) => {
430
444
  PREVIEW_PORT_ALLOWED_API_RE.test(req.path.toLowerCase());
431
445
  if (!isPreviewRequest(req) && isPreviewPath) {
432
446
  if (req.headers[editorAccess_2.EDITOR_ACCESS_TOKEN_HEADER]) {
433
- res.sendStatus(403);
447
+ rejectRequest({ req, res, reason: 'editor_token_on_preview' });
434
448
  return;
435
449
  }
436
450
  res.redirect(302, `${previewOrigin}${req.originalUrl}`);
@@ -503,7 +517,7 @@ const startServer = async (options) => {
503
517
  });
504
518
  app.post('/api/library/browser-launches', express_1.default.urlencoded({ extended: false }), async (req, res) => {
505
519
  if (!(0, middleware_1.isLocalBrowserRequest)(req))
506
- return res.sendStatus(403);
520
+ return rejectRequest({ req, res, reason: 'origin_not_allowed' });
507
521
  const view = req.body?.view;
508
522
  if (view !== 'projects' && view !== 'references')
509
523
  return res.sendStatus(400);
@@ -583,14 +597,14 @@ const startServer = async (options) => {
583
597
  return;
584
598
  }
585
599
  if (req.headers[editorAccess_2.EDITOR_ACCESS_TOKEN_HEADER]) {
586
- res.sendStatus(403);
600
+ rejectRequest({ req, res, reason: 'editor_token_on_preview' });
587
601
  return;
588
602
  }
589
603
  res.redirect(302, `${previewOrigin}${req.originalUrl}`);
590
604
  return;
591
605
  }
592
606
  if (req.headers[editorAccess_2.EDITOR_ACCESS_TOKEN_HEADER]) {
593
- res.sendStatus(403);
607
+ rejectRequest({ req, res, reason: 'editor_token_on_preview' });
594
608
  return;
595
609
  }
596
610
  next();
@@ -16,6 +16,7 @@ const ProjectPackageManager_1 = require("./ProjectPackageManager");
16
16
  const errnoCode_1 = require("../utils/errnoCode");
17
17
  const pathContainment_1 = require("../utils/pathContainment");
18
18
  const errorMessage_1 = require("../utils/errorMessage");
19
+ const failureReason_1 = require("../utils/failureReason");
19
20
  const subprocessEnvironment_1 = require("../utils/subprocessEnvironment");
20
21
  const MAX_CONCURRENT_INSTALLS = 2;
21
22
  const INSTALL_TIMEOUT_MS = 5 * 60 * 1000;
@@ -43,11 +44,13 @@ const isInstallEnvironmentKey = (key) => {
43
44
  INSTALL_ENVIRONMENT_KEYS.has(lower));
44
45
  };
45
46
  const yarnAgentForVersion = (version) => Number.parseInt(version, 10) > 1 ? 'yarn@berry' : 'yarn';
46
- const preparationError = (message, detail = '') => new devServerError_1.DevServerStartError({
47
+ const preparationError = ({ reasonCode, message, detail = '', }) => new devServerError_1.DevServerStartError({
47
48
  message: detail ? `${message}\n${detail}` : message,
48
49
  reason: 'dependency_install_failed',
49
50
  portInUse: false,
50
51
  detail,
52
+ reasonCode,
53
+ summary: message,
51
54
  });
52
55
  class DependencyPreparationService {
53
56
  pending = new Map();
@@ -78,7 +81,10 @@ class DependencyPreparationService {
78
81
  catch (error) {
79
82
  if (error instanceof devServerError_1.DevServerStartError)
80
83
  throw error;
81
- throw preparationError(`Could not prepare preview dependencies: ${(0, errorMessage_1.errorMessage)(error)}`);
84
+ throw preparationError({
85
+ reasonCode: (0, failureReason_1.failureReasonCode)(error),
86
+ message: `Could not prepare preview dependencies: ${(0, errorMessage_1.errorMessage)(error)}`,
87
+ });
82
88
  }
83
89
  }
84
90
  forget(workspaceRoot) {
@@ -116,9 +122,16 @@ class DependencyPreparationService {
116
122
  .reverse()
117
123
  .find((line) => /^\d+\.\d+\.\d+(?:-.+)?$/.test(line));
118
124
  if (!version)
119
- throw preparationError(`Could not identify the ${project.name} version`, versionOutput);
125
+ throw preparationError({
126
+ reasonCode: 'version_unreadable',
127
+ message: `Could not identify the ${project.name} version`,
128
+ detail: versionOutput,
129
+ });
120
130
  if (project.version && version !== project.version) {
121
- throw preparationError(`This project requires ${project.name}@${project.version}, but the available command reports ${version}. Activate the project's package manager version, then retry the preview.`);
131
+ throw preparationError({
132
+ reasonCode: 'version_mismatch',
133
+ message: `This project requires ${project.name}@${project.version}, but the available command reports ${version}. Activate the project's package manager version, then retry the preview.`,
134
+ });
122
135
  }
123
136
  const agent = project.name === 'yarn' ? yarnAgentForVersion(version) : project.agent;
124
137
  const fingerprint = await this.fingerprint(project, version);
@@ -134,7 +147,10 @@ class DependencyPreparationService {
134
147
  : await this.fingerprint(project, version, true);
135
148
  const command = (0, package_manager_detector_1.resolveCommand)(agent, project.hasLockfile ? 'frozen' : 'install', []);
136
149
  if (!command)
137
- throw preparationError(`No dependency install command is available for ${project.name}`);
150
+ throw preparationError({
151
+ reasonCode: 'no_install_command',
152
+ message: `No dependency install command is available for ${project.name}`,
153
+ });
138
154
  if (project.name === 'npm')
139
155
  command.args.push('--no-audit', '--no-fund');
140
156
  if (this.activeInstalls >= MAX_CONCURRENT_INSTALLS) {
@@ -166,7 +182,10 @@ class DependencyPreparationService {
166
182
  ? completedFingerprint
167
183
  : await this.fingerprint(project, version, true);
168
184
  if (inputsAfterInstall !== inputsBeforeInstall) {
169
- throw preparationError('Dependency inputs changed during installation. Retry the preview to prepare the updated project.');
185
+ throw preparationError({
186
+ reasonCode: 'inputs_changed',
187
+ message: 'Dependency inputs changed during installation. Retry the preview to prepare the updated project.',
188
+ });
170
189
  }
171
190
  this.prepared.set(project.rootPath, {
172
191
  fingerprint: completedFingerprint,
@@ -194,14 +213,20 @@ class DependencyPreparationService {
194
213
  try {
195
214
  const resolved = await promises_1.default.realpath(candidate);
196
215
  if (!(0, pathContainment_1.isPathWithinRoot)(boundary, resolved))
197
- throw preparationError(`Dependency path resolves outside the preview workspace: ${candidate}`);
216
+ throw preparationError({
217
+ reasonCode: 'path_outside_workspace',
218
+ message: `Dependency path resolves outside the preview workspace: ${candidate}`,
219
+ });
198
220
  }
199
221
  catch (error) {
200
222
  if ((0, errnoCode_1.errnoCode)(error) !== 'ENOENT')
201
223
  throw error;
202
224
  const entry = await promises_1.default.lstat(candidate).catch(() => null);
203
225
  if (entry?.isSymbolicLink())
204
- throw preparationError(`Dependency path is a dangling symlink: ${candidate}`);
226
+ throw preparationError({
227
+ reasonCode: 'dangling_symlink',
228
+ message: `Dependency path is a dangling symlink: ${candidate}`,
229
+ });
205
230
  }
206
231
  }
207
232
  }
@@ -284,7 +309,7 @@ class DependencyPreparationService {
284
309
  stdio: 'pipe',
285
310
  });
286
311
  let output = '';
287
- let interrupted = false;
312
+ let interruption;
288
313
  let settleTimer;
289
314
  const drain = (chunk) => {
290
315
  output = (output + chunk.toString()).slice(-OUTPUT_TAIL_LIMIT);
@@ -292,35 +317,48 @@ class DependencyPreparationService {
292
317
  proc.stdout?.on('data', drain);
293
318
  proc.stderr?.on('data', drain);
294
319
  proc.stdin?.end();
295
- const stop = () => {
296
- interrupted = true;
320
+ const stop = (cause) => {
321
+ interruption ??= cause;
297
322
  void OperatingSystemServices_1.selectedOperatingSystemServices.projectProcesses
298
323
  .terminate(proc, { isForceful: true })
299
324
  .catch(() => undefined);
300
325
  settleTimer ??= setTimeout(() => {
301
326
  cleanup();
302
- reject(preparationError(`Dependency preparation could not be stopped: ${project.name} ${args.join(' ')} is still running. Stop the process, then retry the preview.`, output.trim()));
327
+ reject(preparationError({
328
+ reasonCode: 'installer_unstoppable',
329
+ message: `Dependency preparation could not be stopped: ${project.name} ${args.join(' ')} is still running. Stop the process, then retry the preview.`,
330
+ detail: output.trim(),
331
+ }));
303
332
  }, this.terminationSettleTimeoutMs);
304
333
  };
305
- const timer = setTimeout(stop, timeoutMs);
306
- signal?.addEventListener('abort', stop, { once: true });
334
+ const abort = () => stop('aborted');
335
+ const timer = setTimeout(() => stop('timeout'), timeoutMs);
336
+ signal?.addEventListener('abort', abort, { once: true });
307
337
  if (signal?.aborted)
308
- stop();
338
+ abort();
309
339
  const cleanup = () => {
310
340
  clearTimeout(timer);
311
341
  clearTimeout(settleTimer);
312
- signal?.removeEventListener('abort', stop);
342
+ signal?.removeEventListener('abort', abort);
313
343
  };
314
344
  proc.once('error', (error) => {
315
345
  cleanup();
316
- reject(preparationError(`Could not run ${project.name} ${args.join(' ')}: ${error.message}`, output));
346
+ reject(preparationError({
347
+ reasonCode: (0, failureReason_1.failureReasonCode)(error),
348
+ message: `Could not run ${project.name} ${args.join(' ')}: ${error.message}`,
349
+ detail: output,
350
+ }));
317
351
  });
318
352
  proc.once('close', (code) => {
319
353
  cleanup();
320
- if (code === 0 && !interrupted)
354
+ if (code === 0 && !interruption)
321
355
  resolve(output);
322
356
  else
323
- reject(preparationError(`Dependency preparation failed: ${project.name} ${args.join(' ')} (${interrupted ? 'cancelled or timed out' : `exit ${code}`}). Fix the installation error, then retry the preview.`, output.trim()));
357
+ reject(preparationError({
358
+ reasonCode: interruption ?? 'install_failed',
359
+ message: `Dependency preparation failed: ${project.name} ${args.join(' ')} (${interruption ? 'cancelled or timed out' : `exit ${code}`}). Fix the installation error, then retry the preview.`,
360
+ detail: output.trim(),
361
+ }));
324
362
  });
325
363
  });
326
364
  }
@@ -254,16 +254,16 @@ const executeGuidanceVerifier = async (args) => {
254
254
  durationMs: Date.now() - started,
255
255
  });
256
256
  try {
257
- await promises_1.default.mkdir(args.runsDirectory, { recursive: true, mode: 0o700 });
258
- runDirectory = await promises_1.default.mkdtemp(path_1.default.join(await promises_1.default.realpath(args.runsDirectory), 'run-'));
259
- if (args.signal?.aborted)
260
- return failed('cancelled');
261
257
  if (args.packet.verifierState === 'missing')
262
258
  return failed('verifier_missing');
263
259
  if (args.packet.verifierState === 'stale')
264
260
  return failed('verifier_stale');
265
261
  if (!args.packet.verifier.package)
266
262
  return failed('package_required');
263
+ await promises_1.default.mkdir(args.runsDirectory, { recursive: true, mode: 0o700 });
264
+ runDirectory = await promises_1.default.mkdtemp(path_1.default.join(await promises_1.default.realpath(args.runsDirectory), 'run-'));
265
+ if (args.signal?.aborted)
266
+ return failed('cancelled');
267
267
  const executable = guidance_verifier_1.GuidanceVerifierPackageSchema.parse(args.packet.verifier.package);
268
268
  const packageDirectory = await (0, retainedTextFiles_1.materializeTextFiles)({
269
269
  files: executable.files,
@@ -12,6 +12,9 @@ interface VariantReference {
12
12
  sessionId: string;
13
13
  variantId: string;
14
14
  }
15
+ interface LibraryListOptions {
16
+ isRemovedIncluded?: boolean;
17
+ }
15
18
  export declare const currentLibraryRoot: (env?: NodeJS.ProcessEnv) => string;
16
19
  export declare class ProjectLibrary {
17
20
  readonly registry: ProjectRegistry;
@@ -45,7 +48,7 @@ export declare class ProjectLibrary {
45
48
  projectPath: string;
46
49
  }): Promise<void>;
47
50
  private listProject;
48
- listVariants(projectId?: string): Promise<LibraryVariant[]>;
51
+ listVariants(projectId?: string, options?: LibraryListOptions): Promise<LibraryVariant[]>;
49
52
  private findVariant;
50
53
  variantSummary(reference: VariantReference): Promise<{
51
54
  summary: VariantHistorySummary;
@@ -23,6 +23,11 @@ const currentLibraryRoot = (env = process.env) => {
23
23
  : (0, ProjectRegistry_1.libraryRoot)({ channel });
24
24
  };
25
25
  exports.currentLibraryRoot = currentLibraryRoot;
26
+ const isSameDirection = (left, right) => (!left.directionRef ||
27
+ !right.directionRef ||
28
+ left.directionRef === right.directionRef) &&
29
+ JSON.stringify({ ...left, directionRef: undefined }) ===
30
+ JSON.stringify({ ...right, directionRef: undefined });
26
31
  class ProjectLibrary {
27
32
  registry;
28
33
  isReadOnly;
@@ -158,7 +163,7 @@ class ProjectLibrary {
158
163
  });
159
164
  }
160
165
  }
161
- async listProject(projectId) {
166
+ async listProject(projectId, options) {
162
167
  const project = this.registry.get(projectId);
163
168
  if (!project.isAvailable && project.storageKind === 'project')
164
169
  throw new Error('Working folder and project-local history are unavailable');
@@ -171,23 +176,23 @@ class ProjectLibrary {
171
176
  : history.listVariantsFromIndex({ projectPath }))).map((entry) => ({ ...entry, projectId, projectPath }))))).flat();
172
177
  const unique = new Map();
173
178
  for (const entry of entries) {
174
- if (entry.manifest.status === 'removed' ||
175
- entry.manifest.status === 'cancelled')
179
+ if (!options.isRemovedIncluded &&
180
+ (entry.manifest.status === 'removed' ||
181
+ entry.manifest.status === 'cancelled'))
176
182
  continue;
177
183
  const key = `${entry.manifest.sessionId}:${entry.manifest.variantId}`;
178
184
  const existing = unique.get(key);
179
- if (existing &&
180
- JSON.stringify(existing.manifest) !== JSON.stringify(entry.manifest))
185
+ if (existing && !isSameDirection(existing.manifest, entry.manifest))
181
186
  throw new Error('Conflicting history identities across attached roots');
182
- unique.set(key, entry);
187
+ unique.set(key, existing?.manifest.directionRef ? existing : entry);
183
188
  }
184
189
  return [...unique.values()];
185
190
  }
186
- async listVariants(projectId) {
191
+ async listVariants(projectId, options = {}) {
187
192
  if (projectId)
188
- return this.listProject(projectId);
193
+ return this.listProject(projectId, options);
189
194
  const projects = this.registry.list();
190
- const results = await Promise.allSettled(projects.map((project) => this.listProject(project.id)));
195
+ const results = await Promise.allSettled(projects.map((project) => this.listProject(project.id, options)));
191
196
  return results
192
197
  .flatMap((result, index) => {
193
198
  if (result.status === 'fulfilled') {
@@ -1,5 +1,10 @@
1
1
  import { type Agent } from 'package-manager-detector';
2
+ import type { FailureReasonCode } from '../utils/failureReason';
2
3
  export type PreviewPackageManager = 'npm' | 'yarn' | 'pnpm';
4
+ export declare class PackageManagerError extends Error {
5
+ readonly reasonCode: FailureReasonCode;
6
+ constructor(reasonCode: FailureReasonCode, message: string);
7
+ }
3
8
  export interface ProjectPackageManager {
4
9
  name: PreviewPackageManager;
5
10
  agent: Agent;