rivet-design 0.27.0 → 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 (65) 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/SessionStore.d.ts +10 -1
  4. package/dist/agent-variants/SessionStore.js +25 -2
  5. package/dist/agent-variants/WorkItemRequestTimes.d.ts +6 -0
  6. package/dist/agent-variants/WorkItemRequestTimes.js +21 -0
  7. package/dist/agent-variants/WorktreeOrchestrator.d.ts +2 -1
  8. package/dist/agent-variants/WorktreeOrchestrator.js +92 -78
  9. package/dist/agent-variants/contracts.d.ts +4 -0
  10. package/dist/agent-variants/existingStaticEntry.d.ts +34 -0
  11. package/dist/agent-variants/existingStaticEntry.js +136 -0
  12. package/dist/agent-variants/staticPreviewFiles.js +1 -1
  13. package/dist/agent-variants/variant-project-runtime.d.ts +2 -1
  14. package/dist/agent-variants/variantsTelemetry.d.ts +4 -0
  15. package/dist/agent-variants/variantsTelemetry.js +2 -0
  16. package/dist/agent-variants/workItemInput.d.ts +22 -0
  17. package/dist/agent-variants/workItemInput.js +12 -1
  18. package/dist/cli/commands/directionTarget.d.ts +23 -0
  19. package/dist/cli/commands/directionTarget.js +87 -0
  20. package/dist/cli/commands/open.js +28 -4
  21. package/dist/cli/commands/variants.js +36 -43
  22. package/dist/cli/envelope.d.ts +1 -1
  23. package/dist/cli/envelope.js +1 -0
  24. package/dist/cli/projectRuntimeUpgrade.d.ts +11 -0
  25. package/dist/cli/projectRuntimeUpgrade.js +43 -11
  26. package/dist/config/pluginMetrics.js +2 -0
  27. package/dist/mcp/explorationSchema.js +1 -1
  28. package/dist/mcp/getExploration.js +11 -5
  29. package/dist/proxy-middleware/proxy-config.js +7 -0
  30. package/dist/routes/agentVariants.js +4 -0
  31. package/dist/routes/cli.js +10 -1
  32. package/dist/server.js +12 -6
  33. package/dist/services/ProjectLibrary.d.ts +4 -1
  34. package/dist/services/ProjectLibrary.js +14 -9
  35. package/dist/services/ProjectRuntimeService.d.ts +8 -0
  36. package/dist/services/ProjectRuntimeService.js +15 -2
  37. package/dist/services/TelemetryService.d.ts +5 -0
  38. package/dist/services/TelemetryService.js +19 -0
  39. package/dist/services/VariantHistoryService.d.ts +8 -0
  40. package/dist/services/VariantHistoryService.js +40 -0
  41. package/dist/services/WorktreeManager.d.ts +1 -0
  42. package/dist/services/WorktreeManager.js +12 -0
  43. package/dist/services/createAgentVariantsOrchestrator.js +9 -2
  44. package/dist/services/directionRefs.d.ts +21 -0
  45. package/dist/services/directionRefs.js +148 -0
  46. package/dist/services/projectDirections.d.ts +22 -0
  47. package/dist/services/projectDirections.js +38 -0
  48. package/dist/types/direction-ref.d.ts +36 -0
  49. package/dist/types/direction-ref.js +3 -0
  50. package/dist/types/variant-history.d.ts +3 -1
  51. package/dist/utils/skills/cli-guidance.js +4 -3
  52. package/dist/utils/subprocessEnvironment.d.ts +1 -1
  53. package/dist/utils/subprocessEnvironment.js +0 -1
  54. package/package.json +3 -3
  55. package/src/ui/dist/assets/LibraryEntry-BZ5EdFgA.js +37 -0
  56. package/src/ui/dist/assets/main-MepMmTwb.css +1 -0
  57. package/src/ui/dist/assets/main-RriU35pI.js +497 -0
  58. package/src/ui/dist/index.html +2 -2
  59. package/dist/services/StaticPreviewServer.d.ts +0 -8
  60. package/dist/services/StaticPreviewServer.js +0 -207
  61. package/src/ui/dist/assets/LibraryEntry-RI9Lc7P3.js +0 -37
  62. package/src/ui/dist/assets/main-D0UmAaUe.css +0 -1
  63. package/src/ui/dist/assets/main-DgrLzS5j.js +0 -497
  64. package/src/ui/dist/fonts/Goldman-Bold.woff2 +0 -0
  65. package/src/ui/dist/fonts/Goldman-Regular.woff2 +0 -0
@@ -50,6 +50,7 @@ const handoff_1 = require("../handoff");
50
50
  const callerModel_1 = require("../callerModel");
51
51
  const shared_1 = require("./shared");
52
52
  const elementSelector_1 = require("./elementSelector");
53
+ const directionTarget_1 = require("./directionTarget");
53
54
  const hostWorkNextAction_1 = require("../hostWorkNextAction");
54
55
  const runLabel_1 = require("../../agent-variants/runLabel");
55
56
  const contracts_1 = require("../../agent-variants/contracts");
@@ -116,44 +117,22 @@ const LIST_FLAG_SPEC = {
116
117
  const GET_FLAG_SPEC = {
117
118
  detail: { takesValue: false },
118
119
  };
119
- const parseVariantTarget = (raw) => {
120
- if (raw === undefined) {
120
+ const START_TARGET_USAGE = 'Usage: --target <directionRef>|<sessionId>:<variantId> (example: --target warm-editorial-green)';
121
+ const GET_USAGE = 'Usage: rivet variants get <directionRef>|last|[<projectId>/]<sessionId>:<variantId> [--detail] [--project <path>]';
122
+ const resolveStartTarget = async (args) => {
123
+ if (args.raw === undefined)
121
124
  return { ok: true };
122
- }
123
- const separatorIndex = raw.indexOf(':');
124
- if (separatorIndex <= 0 || separatorIndex === raw.length - 1) {
125
- return {
126
- ok: false,
127
- message: 'Usage: --target <sessionId>:<variantId> (example: --target abc:def)',
128
- };
129
- }
130
- const sessionId = raw.slice(0, separatorIndex).trim();
131
- const variantId = raw.slice(separatorIndex + 1).trim();
132
- if (!sessionId || !variantId) {
133
- return {
134
- ok: false,
135
- message: 'Usage: --target <sessionId>:<variantId> (example: --target abc:def)',
136
- };
137
- }
125
+ const resolved = await (0, directionTarget_1.resolveDirectionTarget)({
126
+ command: START_COMMAND,
127
+ projectPath: args.projectPath,
128
+ input: args.raw,
129
+ usage: START_TARGET_USAGE,
130
+ });
131
+ if (!resolved.ok)
132
+ return resolved;
133
+ const { sessionId, variantId } = resolved.target;
138
134
  return { ok: true, target: { kind: 'variant', sessionId, variantId } };
139
135
  };
140
- const variantReferenceFromString = (raw) => {
141
- if (!raw)
142
- return null;
143
- const projectSeparator = raw.indexOf('/');
144
- const projectId = projectSeparator === -1 ? undefined : raw.slice(0, projectSeparator);
145
- const direction = projectSeparator === -1 ? raw : raw.slice(projectSeparator + 1);
146
- if (projectSeparator !== -1 && (!projectId || direction.includes('/')))
147
- return null;
148
- const variantSeparator = direction.indexOf(':');
149
- if (variantSeparator <= 0 || variantSeparator === direction.length - 1)
150
- return null;
151
- if (direction.indexOf(':', variantSeparator + 1) !== -1)
152
- return null;
153
- const sessionId = direction.slice(0, variantSeparator).trim();
154
- const variantId = direction.slice(variantSeparator + 1).trim();
155
- return sessionId && variantId ? { projectId, sessionId, variantId } : null;
156
- };
157
136
  const parseJsonFlag = (command, name, raw) => {
158
137
  if (raw === undefined) {
159
138
  return { ok: true };
@@ -437,7 +416,7 @@ const handleVariantsStart = async (args, options) => {
437
416
  }
438
417
  const instruction = (0, flags_1.stringFlag)(context.flags, 'instruction');
439
418
  if (!instruction?.trim()) {
440
- return (0, envelope_1.errorResult)(START_COMMAND, 'USAGE_ERROR', 'Usage: rivet variants start --instruction <text> [--run-label <text>] [--briefs <json>] [--count N] [--fidelity low|medium|high] [--element <selector>] [--files <json>] [--fresh] [--context-images <json>] [--context-assets <json>] [--reference-pages <json>] [--reference-videos <json>] [--target <sessionId>:<variantId>]');
419
+ return (0, envelope_1.errorResult)(START_COMMAND, 'USAGE_ERROR', 'Usage: rivet variants start --instruction <text> [--run-label <text>] [--briefs <json>] [--count N] [--fidelity low|medium|high] [--element <selector>] [--files <json>] [--fresh] [--context-images <json>] [--context-assets <json>] [--reference-pages <json>] [--reference-videos <json>] [--target <directionRef>|<sessionId>:<variantId>]');
441
420
  }
442
421
  if (context.flags.errors.length > 0) {
443
422
  return (0, envelope_1.errorResult)(START_COMMAND, 'USAGE_ERROR', context.flags.errors.join('; '));
@@ -508,10 +487,12 @@ const handleVariantsStart = async (args, options) => {
508
487
  return (0, envelope_1.errorResult)(START_COMMAND, 'USAGE_ERROR', '--count must match --briefs length when both are provided.');
509
488
  }
510
489
  const isFresh = isFreshStart;
511
- const parsedTarget = parseVariantTarget((0, flags_1.stringFlag)(context.flags, 'target'));
512
- if (!parsedTarget.ok) {
513
- return (0, envelope_1.errorResult)(START_COMMAND, 'USAGE_ERROR', parsedTarget.message);
514
- }
490
+ const parsedTarget = await resolveStartTarget({
491
+ projectPath: context.projectPath,
492
+ raw: (0, flags_1.stringFlag)(context.flags, 'target'),
493
+ });
494
+ if (!parsedTarget.ok)
495
+ return parsedTarget.result;
515
496
  if (isFresh && parsedTarget.target) {
516
497
  return (0, envelope_1.errorResult)(START_COMMAND, 'USAGE_ERROR', '--fresh cannot be combined with --target; start fresh generation from the base project.');
517
498
  }
@@ -782,6 +763,7 @@ const handleSavedVariantGet = async (args) => {
782
763
  return (0, envelope_1.errorResult)(GET_COMMAND, 'WORK_ITEM_NOT_FOUND', 'No saved variant exists for this project and reference.');
783
764
  const saved = 'detail' in found ? found.detail : found.summary;
784
765
  const { manifest, variantDir } = saved;
766
+ const directionRef = args.directionRef ?? manifest.directionRef;
785
767
  const kind = manifest.artifact.kind === 'diff' ? 'diff' : 'project-created';
786
768
  const artifactPath = path.join(variantDir, manifest.artifact.kind === 'diff'
787
769
  ? manifest.artifact.diffPath
@@ -789,6 +771,7 @@ const handleSavedVariantGet = async (args) => {
789
771
  const variant = args.isDetailed && 'detail' in found
790
772
  ? {
791
773
  ...manifest,
774
+ directionRef,
792
775
  kind,
793
776
  artifactPath,
794
777
  diff: found.detail.diff,
@@ -796,6 +779,7 @@ const handleSavedVariantGet = async (args) => {
796
779
  designMarkdown: found.detail.designMarkdown,
797
780
  }
798
781
  : {
782
+ directionRef,
799
783
  sessionId: manifest.sessionId,
800
784
  variantId: manifest.variantId,
801
785
  status: manifest.status,
@@ -825,12 +809,21 @@ const handleVariantsGet = async (args) => {
825
809
  if ((0, shared_1.isCommandResult)(context)) {
826
810
  return context;
827
811
  }
828
- const reference = variantReferenceFromString(context.flags.positionals[0]);
829
- if (!reference ||
812
+ const input = context.flags.positionals[0];
813
+ if (input === undefined ||
830
814
  context.flags.positionals.length !== 1 ||
831
815
  context.flags.errors.length > 0) {
832
- return (0, envelope_1.errorResult)(GET_COMMAND, 'USAGE_ERROR', 'Usage: rivet variants get [<projectId>/]<sessionId>:<variantId> [--detail] [--project <path>]');
816
+ return (0, envelope_1.errorResult)(GET_COMMAND, 'USAGE_ERROR', GET_USAGE);
833
817
  }
818
+ const resolved = await (0, directionTarget_1.resolveDirectionTarget)({
819
+ command: GET_COMMAND,
820
+ projectPath: context.projectPath,
821
+ input,
822
+ usage: GET_USAGE,
823
+ });
824
+ if (!resolved.ok)
825
+ return resolved.result;
826
+ const reference = resolved.target;
834
827
  if (reference.projectId)
835
828
  return handleSavedVariantGet({
836
829
  ...reference,
@@ -8,7 +8,7 @@ export declare const CLI_EXIT_CODES: {
8
8
  readonly waitTimeout: 5;
9
9
  };
10
10
  export type CliExitCode = (typeof CLI_EXIT_CODES)[keyof typeof CLI_EXIT_CODES];
11
- export type CliErrorCode = 'OPERATION_FAILED' | 'USAGE_ERROR' | 'AUTH_REQUIRED' | 'SERVER_UNAVAILABLE' | 'WAIT_TIMEOUT' | 'SESSION_NOT_FOUND' | 'WORK_ITEM_NOT_FOUND' | 'MCP_REMOVED';
11
+ export type CliErrorCode = 'OPERATION_FAILED' | 'USAGE_ERROR' | 'AUTH_REQUIRED' | 'SERVER_UNAVAILABLE' | 'WAIT_TIMEOUT' | 'SESSION_NOT_FOUND' | 'WORK_ITEM_NOT_FOUND' | 'DIRECTION_AMBIGUOUS' | 'MCP_REMOVED';
12
12
  export type CliLoginAction = {
13
13
  action: 'login';
14
14
  command: 'rivet login';
@@ -24,6 +24,7 @@ const ERROR_EXIT_CODES = {
24
24
  WAIT_TIMEOUT: exports.CLI_EXIT_CODES.waitTimeout,
25
25
  SESSION_NOT_FOUND: exports.CLI_EXIT_CODES.operationFailed,
26
26
  WORK_ITEM_NOT_FOUND: exports.CLI_EXIT_CODES.operationFailed,
27
+ DIRECTION_AMBIGUOUS: exports.CLI_EXIT_CODES.usageError,
27
28
  MCP_REMOVED: exports.CLI_EXIT_CODES.usageError,
28
29
  };
29
30
  const loginRecovery = (authUrl) => ({
@@ -12,6 +12,17 @@ type RuntimeUpgradeOutcome = {
12
12
  restartArgs?: string[];
13
13
  interruptedWork?: RuntimeInterruption;
14
14
  };
15
+ type RuntimeRetirement = {
16
+ outcome: 'retired';
17
+ restartArgs?: string[];
18
+ } | {
19
+ outcome: 'kept';
20
+ };
21
+ export declare const retireUnavailablePreview: ({ attached, clientVersion, shutdownTimeoutMs, }: {
22
+ attached: AttachedServer;
23
+ clientVersion: string;
24
+ shutdownTimeoutMs: number;
25
+ }) => Promise<RuntimeRetirement>;
15
26
  export declare const prepareRuntimeUpgrade: ({ attached, io, shutdownTimeoutMs, }: {
16
27
  attached: AttachedServer;
17
28
  io: CliIO;
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.prepareRuntimeUpgrade = void 0;
6
+ exports.prepareRuntimeUpgrade = exports.retireUnavailablePreview = void 0;
7
7
  const zod_1 = require("zod");
8
8
  const fs_1 = __importDefault(require("fs"));
9
9
  const client_1 = require("./client");
@@ -34,6 +34,43 @@ const INTERRUPTIBLE_REASONS = new Set([
34
34
  'active_work',
35
35
  'unknown_execution_state',
36
36
  ]);
37
+ const awaitOwnedRuntimeShutdown = async ({ attached, processIdentity, shutdownTimeoutMs, }) => {
38
+ if (await (0, serverShutdown_1.waitForServerShutdown)(attached, shutdownTimeoutMs))
39
+ return;
40
+ await (0, stopRuntimeProcess_1.stopRuntimeProcess)({
41
+ pid: attached.handshake.pid,
42
+ timeoutMs: shutdownTimeoutMs,
43
+ isOwned: async () => {
44
+ return (processIdentity !== null &&
45
+ (0, stopRuntimeProcess_1.runtimeProcessIdentity)(attached.handshake.pid) === processIdentity);
46
+ },
47
+ });
48
+ };
49
+ const retireUnavailablePreview = async ({ attached, clientVersion, shutdownTimeoutMs, }) => {
50
+ const serverVersion = attached.handshake.rivetVersion;
51
+ if (!serverVersion || (0, serverVersion_1.isNewerVersion)(serverVersion, clientVersion))
52
+ return { outcome: 'kept' };
53
+ try {
54
+ const processIdentity = (0, stopRuntimeProcess_1.runtimeProcessIdentity)(attached.handshake.pid);
55
+ const upgrade = await (0, client_1.requestServer)(attached, '/api/cli/upgrade', {
56
+ method: 'POST',
57
+ body: { clientVersion, requireIdle: true },
58
+ });
59
+ const parsed = upgradeSchema.parse(upgrade.body);
60
+ if (upgrade.status !== 200 || parsed.stopping !== true)
61
+ return { outcome: 'kept' };
62
+ await awaitOwnedRuntimeShutdown({
63
+ attached,
64
+ processIdentity,
65
+ shutdownTimeoutMs,
66
+ });
67
+ return { outcome: 'retired', restartArgs: parsed.restartArgs };
68
+ }
69
+ catch {
70
+ return { outcome: 'kept' };
71
+ }
72
+ };
73
+ exports.retireUnavailablePreview = retireUnavailablePreview;
37
74
  const prepareRuntimeUpgrade = async ({ attached, io, shutdownTimeoutMs, }) => {
38
75
  const info = (0, serverVersion_1.serverVersionInfo)(attached.handshake);
39
76
  if (!info.staleServer)
@@ -92,16 +129,11 @@ const prepareRuntimeUpgrade = async ({ attached, io, shutdownTimeoutMs, }) => {
92
129
  : undefined;
93
130
  if (interruptedWork)
94
131
  io.writeProgress(`${interruptedWork.message} Recovery details: ${interruptedWork.receiptPath}`);
95
- if (!(await (0, serverShutdown_1.waitForServerShutdown)(attached, shutdownTimeoutMs))) {
96
- await (0, stopRuntimeProcess_1.stopRuntimeProcess)({
97
- pid: attached.handshake.pid,
98
- timeoutMs: shutdownTimeoutMs,
99
- isOwned: async () => {
100
- return (processIdentity !== null &&
101
- (0, stopRuntimeProcess_1.runtimeProcessIdentity)(attached.handshake.pid) === processIdentity);
102
- },
103
- });
104
- }
132
+ await awaitOwnedRuntimeShutdown({
133
+ attached,
134
+ processIdentity,
135
+ shutdownTimeoutMs,
136
+ });
105
137
  io.writeProgress(`Upgrading project server to rivet v${info.cliVersion}…`);
106
138
  return {
107
139
  outcome: 'spawn',
@@ -120,6 +120,7 @@ const metricProperties = zod_1.z.object({
120
120
  'WAIT_TIMEOUT',
121
121
  'SESSION_NOT_FOUND',
122
122
  'WORK_ITEM_NOT_FOUND',
123
+ 'DIRECTION_AMBIGUOUS',
123
124
  'MCP_REMOVED',
124
125
  'STATIC_PREVIEW_FAILED',
125
126
  'DEV_SERVER_FAILED',
@@ -164,6 +165,7 @@ const metricProperties = zod_1.z.object({
164
165
  'usage_error',
165
166
  'wait_timeout',
166
167
  'session_not_found',
168
+ 'direction_ambiguous',
167
169
  'mcp_removed',
168
170
  'server_start_timeout',
169
171
  'handshake_missing',
@@ -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.',
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/') &&
@@ -438,7 +444,7 @@ const startServer = async (options) => {
438
444
  PREVIEW_PORT_ALLOWED_API_RE.test(req.path.toLowerCase());
439
445
  if (!isPreviewRequest(req) && isPreviewPath) {
440
446
  if (req.headers[editorAccess_2.EDITOR_ACCESS_TOKEN_HEADER]) {
441
- res.sendStatus(403);
447
+ rejectRequest({ req, res, reason: 'editor_token_on_preview' });
442
448
  return;
443
449
  }
444
450
  res.redirect(302, `${previewOrigin}${req.originalUrl}`);
@@ -511,7 +517,7 @@ const startServer = async (options) => {
511
517
  });
512
518
  app.post('/api/library/browser-launches', express_1.default.urlencoded({ extended: false }), async (req, res) => {
513
519
  if (!(0, middleware_1.isLocalBrowserRequest)(req))
514
- return res.sendStatus(403);
520
+ return rejectRequest({ req, res, reason: 'origin_not_allowed' });
515
521
  const view = req.body?.view;
516
522
  if (view !== 'projects' && view !== 'references')
517
523
  return res.sendStatus(400);
@@ -591,14 +597,14 @@ const startServer = async (options) => {
591
597
  return;
592
598
  }
593
599
  if (req.headers[editorAccess_2.EDITOR_ACCESS_TOKEN_HEADER]) {
594
- res.sendStatus(403);
600
+ rejectRequest({ req, res, reason: 'editor_token_on_preview' });
595
601
  return;
596
602
  }
597
603
  res.redirect(302, `${previewOrigin}${req.originalUrl}`);
598
604
  return;
599
605
  }
600
606
  if (req.headers[editorAccess_2.EDITOR_ACCESS_TOKEN_HEADER]) {
601
- res.sendStatus(403);
607
+ rejectRequest({ req, res, reason: 'editor_token_on_preview' });
602
608
  return;
603
609
  }
604
610
  next();
@@ -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,7 +1,15 @@
1
1
  import type { ProjectRegistry } from './ProjectRegistry';
2
+ import { type ServerUnavailableReason } from '../cli/telemetry';
2
3
  interface ProjectRuntime {
3
4
  editorUrl: string;
4
5
  }
6
+ export declare class ProjectRuntimeUnavailable extends Error {
7
+ readonly serverUnavailableReason?: ServerUnavailableReason;
8
+ constructor({ message, serverUnavailableReason, }: {
9
+ message: string;
10
+ serverUnavailableReason?: ServerUnavailableReason;
11
+ });
12
+ }
5
13
  export declare class ProjectRuntimeService {
6
14
  private readonly registry;
7
15
  private readonly activations;
@@ -36,14 +36,24 @@ 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.ProjectRuntimeService = void 0;
39
+ exports.ProjectRuntimeService = exports.ProjectRuntimeUnavailable = void 0;
40
40
  const fs_1 = __importDefault(require("fs"));
41
41
  const zod_1 = require("zod");
42
42
  const installationChannel_1 = require("../install/installationChannel");
43
43
  const libraryErrors_1 = require("./libraryErrors");
44
44
  const serverHandshake_1 = require("../cli/serverHandshake");
45
45
  const shared_1 = require("../cli/commands/shared");
46
+ const telemetry_1 = require("../cli/telemetry");
46
47
  const HEALTH_TIMEOUT_MS = 2_000;
48
+ class ProjectRuntimeUnavailable extends Error {
49
+ serverUnavailableReason;
50
+ constructor({ message, serverUnavailableReason, }) {
51
+ super(message);
52
+ this.name = 'ProjectRuntimeUnavailable';
53
+ this.serverUnavailableReason = serverUnavailableReason;
54
+ }
55
+ }
56
+ exports.ProjectRuntimeUnavailable = ProjectRuntimeUnavailable;
47
57
  class ProjectRuntimeService {
48
58
  registry;
49
59
  activations = new Map();
@@ -118,7 +128,10 @@ class ProjectRuntimeService {
118
128
  writeEnvelope: () => { },
119
129
  });
120
130
  if (!result.envelope.ok)
121
- throw new Error(result.envelope.error.message);
131
+ throw new ProjectRuntimeUnavailable({
132
+ message: result.envelope.error.message,
133
+ serverUnavailableReason: (0, telemetry_1.serverUnavailableReasonForEnvelope)(result.envelope),
134
+ });
122
135
  const data = zod_1.z
123
136
  .object({ editorUrl: zod_1.z.string().url() })
124
137
  .parse(result.envelope.data);
@@ -3,6 +3,7 @@ import type { BrowserMetadata, DesignContextKind, ServerUnavailableReason, Varia
3
3
  import type { GuidanceErrorCode, GuidanceSelection } from '../types/guidance';
4
4
  import type { MCPAuthMethod, MCPAuthState, MCPOnboardingCorrelation, MCPOAuthCallbackResult } from './MCPOnboardingTelemetry';
5
5
  import { type MCPVariantsTelemetryMetadata } from './MCPVariantsTelemetry';
6
+ export type RequestRejectionReason = 'host_not_allowed' | 'origin_not_allowed' | 'editor_token_on_preview';
6
7
  export type TelemetryOptions = {
7
8
  enabled?: boolean;
8
9
  source?: 'cli' | 'mcp' | 'core';
@@ -41,12 +42,14 @@ export declare class TelemetryService {
41
42
  trackGuidanceOperation(data: GuidanceOperation): void;
42
43
  trackProfileOperation(data: ProfileOperation): void;
43
44
  trackLibraryOperation(data: LibraryOperation): void;
45
+ trackRequestRejected(reason: RequestRejectionReason): void;
44
46
  private hashTelemetryValue;
45
47
  private posthog;
46
48
  private isEnabled;
47
49
  private readonly defaultSource;
48
50
  private readonly editor?;
49
51
  private readonly identifiedAccounts;
52
+ private readonly reportedRequestRejections;
50
53
  constructor(options?: boolean | TelemetryOptions);
51
54
  private get distinctId();
52
55
  identifyUser(identity?: AccountIdentity): void;
@@ -285,6 +288,7 @@ export declare class TelemetryService {
285
288
  hasPort: boolean;
286
289
  port?: number;
287
290
  durationMs?: number | null;
291
+ staticEntrySource?: string;
288
292
  }): void;
289
293
  trackAgentVariantsPreviewFailed(data: {
290
294
  sessionId: string;
@@ -298,6 +302,7 @@ export declare class TelemetryService {
298
302
  exitCode?: number | null;
299
303
  signal?: string | null;
300
304
  missingDeps?: boolean;
305
+ staticEntrySource?: string;
301
306
  error?: unknown;
302
307
  }): void;
303
308
  trackAgentVariantsPreviewSwitchStarted(data: {