rivet-design 0.18.4 → 0.19.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 (92) hide show
  1. package/README.md +1 -1
  2. package/README.npm.md +1 -1
  3. package/dist/agent-variants/PreviewRuntime.d.ts +100 -0
  4. package/dist/agent-variants/PreviewRuntime.d.ts.map +1 -0
  5. package/dist/agent-variants/PreviewRuntime.js +577 -0
  6. package/dist/agent-variants/PreviewRuntime.js.map +1 -0
  7. package/dist/agent-variants/WorkspaceInventory.d.ts +81 -0
  8. package/dist/agent-variants/WorkspaceInventory.d.ts.map +1 -0
  9. package/dist/agent-variants/WorkspaceInventory.js +172 -0
  10. package/dist/agent-variants/WorkspaceInventory.js.map +1 -0
  11. package/dist/agent-variants/WorkspaceProvisioning.d.ts +40 -0
  12. package/dist/agent-variants/WorkspaceProvisioning.d.ts.map +1 -0
  13. package/dist/agent-variants/WorkspaceProvisioning.js +419 -0
  14. package/dist/agent-variants/WorkspaceProvisioning.js.map +1 -0
  15. package/dist/agent-variants/WorktreeOrchestrator.d.ts +9 -178
  16. package/dist/agent-variants/WorktreeOrchestrator.d.ts.map +1 -1
  17. package/dist/agent-variants/WorktreeOrchestrator.js +190 -2213
  18. package/dist/agent-variants/WorktreeOrchestrator.js.map +1 -1
  19. package/dist/agent-variants/designContextArtifacts.d.ts +25 -0
  20. package/dist/agent-variants/designContextArtifacts.d.ts.map +1 -0
  21. package/dist/agent-variants/designContextArtifacts.js +471 -0
  22. package/dist/agent-variants/designContextArtifacts.js.map +1 -0
  23. package/dist/agent-variants/variant-project-runtime.d.ts +40 -0
  24. package/dist/agent-variants/variant-project-runtime.d.ts.map +1 -0
  25. package/dist/agent-variants/variant-project-runtime.js +3 -0
  26. package/dist/agent-variants/variant-project-runtime.js.map +1 -0
  27. package/dist/agent-variants/variantWorkspaceFiles.d.ts +44 -0
  28. package/dist/agent-variants/variantWorkspaceFiles.d.ts.map +1 -0
  29. package/dist/agent-variants/variantWorkspaceFiles.js +426 -0
  30. package/dist/agent-variants/variantWorkspaceFiles.js.map +1 -0
  31. package/dist/agent-variants/variantsTelemetry.d.ts +189 -0
  32. package/dist/agent-variants/variantsTelemetry.d.ts.map +1 -0
  33. package/dist/agent-variants/variantsTelemetry.js +160 -0
  34. package/dist/agent-variants/variantsTelemetry.js.map +1 -0
  35. package/dist/cli/commands/mcpServe.d.ts.map +1 -1
  36. package/dist/cli/commands/mcpServe.js +27 -2
  37. package/dist/cli/commands/mcpServe.js.map +1 -1
  38. package/dist/cli/commands/profile.d.ts.map +1 -1
  39. package/dist/cli/commands/profile.js +15 -1
  40. package/dist/cli/commands/profile.js.map +1 -1
  41. package/dist/cli/router.d.ts.map +1 -1
  42. package/dist/cli/router.js +2 -1
  43. package/dist/cli/router.js.map +1 -1
  44. package/dist/routes/library/profiles.d.ts.map +1 -1
  45. package/dist/routes/library/profiles.js +46 -0
  46. package/dist/routes/library/profiles.js.map +1 -1
  47. package/dist/routes/referenceProfiles.d.ts +2 -0
  48. package/dist/routes/referenceProfiles.d.ts.map +1 -0
  49. package/dist/routes/referenceProfiles.js +79 -0
  50. package/dist/routes/referenceProfiles.js.map +1 -0
  51. package/dist/server.d.ts.map +1 -1
  52. package/dist/server.js +2 -0
  53. package/dist/server.js.map +1 -1
  54. package/dist/services/TelemetryService.d.ts +1 -1
  55. package/dist/services/TelemetryService.d.ts.map +1 -1
  56. package/dist/services/TelemetryService.js.map +1 -1
  57. package/dist/services/profiles/DesignProfiles.d.ts +18 -0
  58. package/dist/services/profiles/DesignProfiles.d.ts.map +1 -1
  59. package/dist/services/profiles/DesignProfiles.js +129 -0
  60. package/dist/services/profiles/DesignProfiles.js.map +1 -1
  61. package/dist/services/profiles/ProfileMedia.d.ts +1 -0
  62. package/dist/services/profiles/ProfileMedia.d.ts.map +1 -1
  63. package/dist/services/profiles/ProfileMedia.js +5 -1
  64. package/dist/services/profiles/ProfileMedia.js.map +1 -1
  65. package/dist/storage/profiles/ProfileRecords.d.ts +2 -0
  66. package/dist/storage/profiles/ProfileRecords.d.ts.map +1 -1
  67. package/dist/storage/profiles/ProfileRecords.js +34 -0
  68. package/dist/storage/profiles/ProfileRecords.js.map +1 -1
  69. package/dist/types/design-library.d.ts +50 -0
  70. package/dist/types/design-library.d.ts.map +1 -0
  71. package/dist/types/design-library.js +32 -0
  72. package/dist/types/design-library.js.map +1 -0
  73. package/dist/types/reference-onboarding.d.ts +2 -2
  74. package/dist/utils/skills/claude-skill.d.ts +1 -1
  75. package/dist/utils/skills/claude-skill.d.ts.map +1 -1
  76. package/dist/utils/skills/claude-skill.js +1 -1
  77. package/dist/utils/skills/claude-skill.js.map +1 -1
  78. package/dist/utils/skills/cli-guidance.d.ts +1 -1
  79. package/dist/utils/skills/cli-guidance.d.ts.map +1 -1
  80. package/dist/utils/skills/cli-guidance.js +6 -4
  81. package/dist/utils/skills/cli-guidance.js.map +1 -1
  82. package/dist/utils/skills/codex-skill.d.ts +1 -1
  83. package/dist/utils/skills/codex-skill.d.ts.map +1 -1
  84. package/dist/utils/skills/codex-skill.js +2 -2
  85. package/package.json +1 -1
  86. package/src/ui/dist/assets/LibraryApp-Sm0rsDtm.js +36 -0
  87. package/src/ui/dist/assets/main-5v9Ic8BN.js +438 -0
  88. package/src/ui/dist/assets/main-Ky91HHF_.css +1 -0
  89. package/src/ui/dist/index.html +2 -2
  90. package/src/ui/dist/assets/LibraryApp-Cxdyy4oV.js +0 -21
  91. package/src/ui/dist/assets/main-B-LY2LQB.css +0 -1
  92. package/src/ui/dist/assets/main-CPHvvBUN.js +0 -438
@@ -3,19 +3,15 @@ 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.AgentVariantsOrchestrator = void 0;
7
- exports.buildStaticPreviewDocument = buildStaticPreviewDocument;
6
+ exports.AgentVariantsOrchestrator = exports.buildStaticPreviewDocument = void 0;
8
7
  const crypto_1 = require("crypto");
9
8
  const events_1 = require("events");
10
9
  const fs_1 = __importDefault(require("fs"));
11
10
  const os_1 = __importDefault(require("os"));
12
11
  const path_1 = __importDefault(require("path"));
13
12
  const DependencyPreparationService_1 = require("../services/DependencyPreparationService");
14
- const ProjectPackageManager_1 = require("../services/ProjectPackageManager");
15
- const devServerCommand_1 = require("../utils/devServerCommand");
16
13
  const simple_git_1 = require("simple-git");
17
14
  const logger_1 = require("../utils/logger");
18
- const devServerError_1 = require("../utils/devServerError");
19
15
  const errors_1 = require("./errors");
20
16
  const createProjectArtifacts_1 = require("./createProjectArtifacts");
21
17
  const contracts_1 = require("./contracts");
@@ -23,15 +19,18 @@ const workItemBriefing_1 = require("./workItemBriefing");
23
19
  const briefContent_1 = require("./briefContent");
24
20
  const runLabel_1 = require("./runLabel");
25
21
  const runPlan_1 = require("./runPlan");
26
- const viteReactTs_1 = require("../services/templates/viteReactTs");
27
- const StaticPreviewServer_1 = require("../services/StaticPreviewServer");
28
- const designCatalog_1 = require("../services/templates/designCatalog");
29
22
  const previewQa_1 = require("./previewQa");
30
- const VariantWorkspaceManager_1 = require("./VariantWorkspaceManager");
23
+ const WorkspaceInventory_1 = require("./WorkspaceInventory");
24
+ const WorkspaceProvisioning_1 = require("./WorkspaceProvisioning");
25
+ const PreviewRuntime_1 = require("./PreviewRuntime");
31
26
  const diffQa_1 = require("./diffQa");
32
27
  const VariantHistoryService_1 = require("../services/VariantHistoryService");
33
28
  const variantContext_1 = require("./variantContext");
34
- const contextAssets_1 = require("./contextAssets");
29
+ const designContextArtifacts_1 = require("./designContextArtifacts");
30
+ const variantsTelemetry_1 = require("./variantsTelemetry");
31
+ const variantWorkspaceFiles_1 = require("./variantWorkspaceFiles");
32
+ var variantWorkspaceFiles_2 = require("./variantWorkspaceFiles");
33
+ Object.defineProperty(exports, "buildStaticPreviewDocument", { enumerable: true, get: function () { return variantWorkspaceFiles_2.buildStaticPreviewDocument; } });
35
34
  const log = (0, logger_1.createLogger)('AgentVariantsOrchestrator');
36
35
  const FRESH_DEV_SERVER_HOST = '127.0.0.1';
37
36
  const BRIEF_LABEL_MAX_LENGTH = 48;
@@ -65,24 +64,9 @@ const BRIEF_LABEL_ALTERNATIVES = [
65
64
  'Density',
66
65
  'Color',
67
66
  ];
68
- const FALLBACK_DIRECTION_FOCI = [
69
- 'Emphasize hierarchy and one unmistakable primary action.',
70
- 'Use stronger contrast, scale shifts, and a more expressive visual system.',
71
- 'Favor a quieter, spacious composition with restrained supporting detail.',
72
- 'Make information density and fast scanning the organizing principle.',
73
- 'Lead with imagery and let content frame the visual story.',
74
- 'Use modular cards and clear grouping to expose the product structure.',
75
- 'Explore tactile depth, layering, and purposeful interaction cues.',
76
- 'Reduce the concept to its most essential typography and geometry.',
77
- ];
78
67
  const SETTLE_PERSISTS_TEARDOWN_TIMEOUT_MS = 20_000;
79
68
  const RECIPE_PORT_SENTINEL = 65535;
80
- const FRESH_DEV_SERVER_DEFAULT_PORT = 5173;
81
- const DEV_SERVER_START_MAX_ATTEMPTS = 3;
82
- const DEV_SERVER_MAX_AUTO_RESTARTS = 2;
83
69
  const DEV_SERVER_RESTART_DELAY_MS = 1_000;
84
- const DESIGN_CONTEXT_ROUTE_SEGMENT = 'design-md';
85
- const DESIGN_CONTEXT_VIEW_SEGMENT = 'view';
86
70
  const DEFAULT_PROVISION_TIMEOUT_MS = 540_000;
87
71
  const PROVISION_TIMEOUT_MS = (() => {
88
72
  const raw = process.env.RIVET_VARIANTS_PROVISION_TIMEOUT_MS;
@@ -99,142 +83,6 @@ const ACTIVE_VARIANT_SERVER_IDLE_TTL_MS = (() => {
99
83
  ? parsed
100
84
  : DEFAULT_ACTIVE_VARIANT_SERVER_IDLE_TTL_MS;
101
85
  })();
102
- class ProvisionTimeoutError extends Error {
103
- timeoutMs;
104
- constructor(timeoutMs) {
105
- super(`Worktree provisioning timed out after ${timeoutMs}ms`);
106
- this.timeoutMs = timeoutMs;
107
- this.name = 'ProvisionTimeoutError';
108
- }
109
- }
110
- function withProvisionTimeout(work, timeoutMs) {
111
- let timer;
112
- const timeout = new Promise((_, reject) => {
113
- timer = setTimeout(() => reject(new ProvisionTimeoutError(timeoutMs)), timeoutMs);
114
- if (typeof timer.unref === 'function')
115
- timer.unref();
116
- });
117
- return Promise.race([work, timeout]).finally(() => clearTimeout(timer));
118
- }
119
- const ALLOWED_INERT_ASSET_EXTENSIONS = new Set([
120
- '.glb',
121
- '.gltf',
122
- '.obj',
123
- '.fbx',
124
- '.usdz',
125
- '.png',
126
- '.jpg',
127
- '.jpeg',
128
- '.gif',
129
- '.webp',
130
- '.svg',
131
- '.avif',
132
- '.bmp',
133
- '.ico',
134
- '.mp4',
135
- '.webm',
136
- '.mov',
137
- '.mp3',
138
- '.wav',
139
- '.ogg',
140
- '.m4a',
141
- '.woff',
142
- '.woff2',
143
- '.ttf',
144
- '.otf',
145
- '.eot',
146
- '.pdf',
147
- ]);
148
- const SENSITIVE_PATH_SEGMENTS = new Set([
149
- '.ssh',
150
- '.aws',
151
- '.gnupg',
152
- '.kube',
153
- '.docker',
154
- '.config',
155
- '.npm',
156
- '.cargo',
157
- '.gem',
158
- '.netrc',
159
- 'private',
160
- 'secrets',
161
- 'credentials',
162
- ]);
163
- function hasSensitivePathSegment(absolutePath) {
164
- const segments = absolutePath.split(/[/\\]/).filter(Boolean);
165
- return segments.some((segment) => SENSITIVE_PATH_SEGMENTS.has(segment.toLowerCase()));
166
- }
167
- function isPathWithinRoot(absolutePath, root) {
168
- const relative = path_1.default.relative(root, absolutePath);
169
- return (relative === '' ||
170
- (relative.length > 0 &&
171
- !relative.startsWith('..') &&
172
- !path_1.default.isAbsolute(relative)));
173
- }
174
- function copyAssetIntoWorktree(worktreePath, entry, assetSourceRoot) {
175
- if (!assetSourceRoot) {
176
- throw new errors_1.AgentVariantsError('RUNTIME_VALIDATION_FAILED', `assetPlan.source requires an approved asset root before copying '${entry.source}'`);
177
- }
178
- if (!path_1.default.isAbsolute(assetSourceRoot)) {
179
- throw new errors_1.AgentVariantsError('RUNTIME_VALIDATION_FAILED', `assetSourceRoot must be an absolute path, got '${assetSourceRoot}'`);
180
- }
181
- const absoluteSource = path_1.default.isAbsolute(entry.source)
182
- ? entry.source
183
- : path_1.default.resolve(assetSourceRoot, entry.source);
184
- const ext = path_1.default.extname(absoluteSource).toLowerCase();
185
- if (!ALLOWED_INERT_ASSET_EXTENSIONS.has(ext)) {
186
- throw new errors_1.AgentVariantsError('RUNTIME_VALIDATION_FAILED', `assetPlan.source extension '${ext || '(none)'}' is not on the allowlist (got '${entry.source}'). Only inert media/font/document assets may be copied.`);
187
- }
188
- let lstat;
189
- try {
190
- lstat = fs_1.default.lstatSync(absoluteSource);
191
- }
192
- catch {
193
- throw new errors_1.AgentVariantsError('RUNTIME_VALIDATION_FAILED', `assetPlan.source not found on disk: ${entry.source}`);
194
- }
195
- if (lstat.isSymbolicLink()) {
196
- throw new errors_1.AgentVariantsError('RUNTIME_VALIDATION_FAILED', `assetPlan.source must not be a symlink: ${entry.source}`);
197
- }
198
- if (!lstat.isFile()) {
199
- throw new errors_1.AgentVariantsError('RUNTIME_VALIDATION_FAILED', `assetPlan.source must be a regular file: ${entry.source}`);
200
- }
201
- let resolvedSource;
202
- try {
203
- resolvedSource = fs_1.default.realpathSync(absoluteSource);
204
- }
205
- catch {
206
- throw new errors_1.AgentVariantsError('RUNTIME_VALIDATION_FAILED', `assetPlan.source could not be resolved: ${entry.source}`);
207
- }
208
- let resolvedAssetSourceRoot;
209
- try {
210
- resolvedAssetSourceRoot = fs_1.default.realpathSync(assetSourceRoot);
211
- }
212
- catch {
213
- throw new errors_1.AgentVariantsError('RUNTIME_VALIDATION_FAILED', `assetSourceRoot could not be resolved: ${assetSourceRoot}`);
214
- }
215
- const resolvedStat = fs_1.default.statSync(resolvedSource);
216
- if (!resolvedStat.isFile()) {
217
- throw new errors_1.AgentVariantsError('RUNTIME_VALIDATION_FAILED', `assetPlan.source resolved target must be a regular file: ${entry.source}`);
218
- }
219
- if (!isPathWithinRoot(resolvedSource, resolvedAssetSourceRoot)) {
220
- throw new errors_1.AgentVariantsError('RUNTIME_VALIDATION_FAILED', `assetPlan.source must stay inside the approved asset root: ${entry.source}`);
221
- }
222
- const relativeResolvedSource = path_1.default.relative(resolvedAssetSourceRoot, resolvedSource);
223
- if (hasSensitivePathSegment(relativeResolvedSource)) {
224
- throw new errors_1.AgentVariantsError('RUNTIME_VALIDATION_FAILED', `assetPlan.source traverses a sensitive directory and is refused: ${entry.source}`);
225
- }
226
- const resolvedExt = path_1.default.extname(resolvedSource).toLowerCase();
227
- if (!ALLOWED_INERT_ASSET_EXTENSIONS.has(resolvedExt)) {
228
- throw new errors_1.AgentVariantsError('RUNTIME_VALIDATION_FAILED', `assetPlan.source resolved extension '${resolvedExt || '(none)'}' is not on the allowlist (resolved from '${entry.source}').`);
229
- }
230
- const normalizedDest = path_1.default.normalize(entry.destination);
231
- if (normalizedDest.startsWith('..') || path_1.default.isAbsolute(normalizedDest)) {
232
- throw new errors_1.AgentVariantsError('RUNTIME_VALIDATION_FAILED', `assetPlan.destination must stay inside the worktree, got '${entry.destination}'`);
233
- }
234
- const absDest = path_1.default.join(worktreePath, normalizedDest);
235
- fs_1.default.mkdirSync(path_1.default.dirname(absDest), { recursive: true });
236
- fs_1.default.copyFileSync(resolvedSource, absDest);
237
- }
238
86
  const defaultPreviewQaRunner = ({ html }) => (0, previewQa_1.runPreviewQa)({ html });
239
87
  const buildViewDisabledReason = (args) => {
240
88
  if (args.status === 'failed') {
@@ -248,7 +96,6 @@ const buildViewDisabledReason = (args) => {
248
96
  }
249
97
  return 'Variant is still running';
250
98
  };
251
- const NOOP_TELEMETRY = { track: () => undefined };
252
99
  const isImplementationWorkItemKind = (kind) => kind === 'static_preview' || kind === 'refine_variant' || kind === 'code_gen';
253
100
  const DEFAULT_EXECUTION_MODE_BY_KIND = {
254
101
  static_preview: 'create',
@@ -350,41 +197,68 @@ class AgentVariantsOrchestrator {
350
197
  installDependencies;
351
198
  materializeProject;
352
199
  previewQaRunner;
353
- switchPreviewPort;
354
- setCommittedDevServerHealth;
355
200
  variantHistory;
356
- startStaticPreviewServerImpl;
357
201
  provisionTimeoutMs;
358
202
  activeVariantServerIdleTtlMs;
359
203
  devServerRestartDelayMs;
360
204
  resources = new Map();
361
- lingeringCommittedDevServers = new Map();
205
+ workspaceInventory;
206
+ workspaceProvisioning;
207
+ previewRuntime;
362
208
  inflightCommits = new Map();
363
209
  activeSessionId = null;
364
210
  events = new events_1.EventEmitter();
365
211
  constructor(deps) {
366
212
  this.store = deps.store;
367
213
  this.worktrees = deps.worktreeManager;
214
+ this.workspaceInventory = new WorkspaceInventory_1.WorkspaceInventory({
215
+ worktreeManager: deps.worktreeManager,
216
+ });
368
217
  this.resolveEnv = deps.resolveProjectEnvironment;
369
- this.telemetry = deps.telemetry ?? NOOP_TELEMETRY;
218
+ this.telemetry = deps.telemetry ?? variantsTelemetry_1.NOOP_TELEMETRY;
370
219
  this.installDependencies =
371
- deps.installDependencies ?? defaultInstallDependencies;
220
+ deps.installDependencies ?? variantWorkspaceFiles_1.defaultInstallDependencies;
372
221
  this.materializeProject =
373
- deps.materializeProject ?? defaultMaterializeProject;
222
+ deps.materializeProject ?? variantWorkspaceFiles_1.defaultMaterializeProject;
374
223
  this.previewQaRunner = deps.previewQaRunner ?? defaultPreviewQaRunner;
375
- this.switchPreviewPort = deps.switchPreviewPort;
376
- this.setCommittedDevServerHealth = deps.setCommittedDevServerHealth;
377
224
  this.variantHistory = deps.variantHistory ?? new VariantHistoryService_1.VariantHistoryService();
378
- this.startStaticPreviewServerImpl =
379
- deps.startStaticPreviewServer ?? StaticPreviewServer_1.startStaticPreviewServer;
380
225
  this.provisionTimeoutMs = deps.provisionTimeoutMs ?? PROVISION_TIMEOUT_MS;
226
+ this.workspaceProvisioning = new WorkspaceProvisioning_1.WorkspaceProvisioning({
227
+ store: deps.store,
228
+ inventory: this.workspaceInventory,
229
+ worktreeManager: deps.worktreeManager,
230
+ telemetry: deps.telemetry ?? variantsTelemetry_1.NOOP_TELEMETRY,
231
+ resolveProjectEnvironment: deps.resolveProjectEnvironment,
232
+ installDependencies: deps.installDependencies ?? variantWorkspaceFiles_1.defaultInstallDependencies,
233
+ provisionTimeoutMs: this.provisionTimeoutMs,
234
+ cancelSession: (sessionId) => this.cancel({ sessionId, reason: 'provisioning_failed' }),
235
+ changed: () => this.emitChange(),
236
+ isSessionLive: (sessionId) => this.resources.has(sessionId),
237
+ });
381
238
  this.activeVariantServerIdleTtlMs =
382
239
  deps.activeVariantServerIdleTtlMs ?? ACTIVE_VARIANT_SERVER_IDLE_TTL_MS;
383
240
  this.devServerRestartDelayMs =
384
241
  deps.devServerRestartDelayMs ?? DEV_SERVER_RESTART_DELAY_MS;
242
+ this.previewRuntime = new PreviewRuntime_1.PreviewRuntime({
243
+ worktreeManager: deps.worktreeManager,
244
+ resolveProjectEnvironment: deps.resolveProjectEnvironment,
245
+ getProjectContext: (sessionId) => this.store.getProjectContext(sessionId),
246
+ getDependencySignal: (sessionId) => this.workspaceInventory.getDependencySignal(sessionId),
247
+ installDependencies: this.installDependencies,
248
+ telemetry: this.telemetry,
249
+ isSessionLive: (sessionId) => this.resources.has(sessionId),
250
+ isCleanupStarted: (sessionId) => this.workspaceInventory.isCleanupStarted(sessionId),
251
+ hasUiClients: () => this.uiClientCount > 0,
252
+ changed: () => this.emitChange(),
253
+ switchPreviewPort: deps.switchPreviewPort,
254
+ setCommittedDevServerHealth: deps.setCommittedDevServerHealth,
255
+ startStaticPreviewServer: deps.startStaticPreviewServer,
256
+ activeVariantServerIdleTtlMs: this.activeVariantServerIdleTtlMs,
257
+ devServerRestartDelayMs: this.devServerRestartDelayMs,
258
+ });
385
259
  }
386
260
  propose(args) {
387
- void this.stopLingeringCommittedDevServers().catch((err) => {
261
+ void this.previewRuntime.stopLingeringCommitted().catch((err) => {
388
262
  log.warn('Stopping lingering committed dev servers failed', err);
389
263
  });
390
264
  const result = this.store.propose(args);
@@ -470,9 +344,9 @@ class AgentVariantsOrchestrator {
470
344
  }
471
345
  touchSession(sessionId) {
472
346
  const resources = this.resources.get(sessionId);
473
- if (!resources || resources.cleanupStarted)
347
+ if (!resources || this.workspaceInventory.isCleanupStarted(sessionId))
474
348
  return;
475
- this.scheduleActivePreviewIdleTimer(resources);
349
+ this.previewRuntime.touch(sessionId);
476
350
  }
477
351
  buildActiveSnapshot() {
478
352
  const sessionId = this.activeSessionId;
@@ -490,11 +364,11 @@ class AgentVariantsOrchestrator {
490
364
  : null;
491
365
  const variants = this.getVariants(sessionId);
492
366
  const sessionProjectContext = this.store.getProjectContext(sessionId);
493
- const projectContext = toActiveProjectContext(sessionProjectContext);
367
+ const projectContext = (0, designContextArtifacts_1.toActiveProjectContext)(sessionProjectContext);
494
368
  const destinationPath = projectContext.kind === 'fresh'
495
369
  ? projectContext.workspacePath
496
370
  : undefined;
497
- const artifacts = buildSessionArtifacts(sessionId, sessionProjectContext);
371
+ const artifacts = (0, designContextArtifacts_1.buildSessionArtifacts)(sessionId, sessionProjectContext);
498
372
  const hostWork = this.getHostWork(sessionId);
499
373
  const workerRuntime = this.store.getWorkerRuntime(sessionId);
500
374
  return {
@@ -547,14 +421,8 @@ class AgentVariantsOrchestrator {
547
421
  return planningItem;
548
422
  }
549
423
  const input = (item.input ?? {});
550
- const workspace = this.getAssignedWorkspace({
551
- sessionId,
552
- workItemId: item.workId,
553
- kind,
554
- });
555
- const contextAssets = this.resources
556
- .get(sessionId)
557
- ?.contextAssets.get(item.workId);
424
+ const workspace = this.workspaceInventory.requireAssignedWorkspace(sessionId, item.workId, kind);
425
+ const contextAssets = this.workspaceInventory.getContextAssets(sessionId, item.workId);
558
426
  const mode = input.sourceVariantId
559
427
  ? 'vary'
560
428
  : DEFAULT_EXECUTION_MODE_BY_KIND[kind];
@@ -601,16 +469,6 @@ class AgentVariantsOrchestrator {
601
469
  return implementationItem;
602
470
  });
603
471
  }
604
- getAssignedWorkspace(args) {
605
- const workspace = this.resources
606
- .get(args.sessionId)
607
- ?.workspaceManager.getAssignedWorkspace(args.workItemId);
608
- const expectedKind = args.kind === 'code_gen' ? 'git_worktree' : 'artifact_directory';
609
- if (!workspace || workspace.kind !== expectedKind) {
610
- throw new errors_1.AgentVariantsError('RUNTIME_VALIDATION_FAILED', `Variant ${args.workItemId} has no assigned ${expectedKind} workspace`);
611
- }
612
- return workspace;
613
- }
614
472
  getVariantsTelemetryMode(projectContext, _target) {
615
473
  if (projectContext.kind === 'fresh' && projectContext.variantContext) {
616
474
  return 'source_grounded';
@@ -908,9 +766,9 @@ class AgentVariantsOrchestrator {
908
766
  }
909
767
  requestWork(args) {
910
768
  const result = this.store.requestWork(args);
769
+ const resources = this.ensureResources(args.sessionId);
911
770
  try {
912
- this.ensureStaticWorkspaces(args.sessionId, result.leasedWorkItems);
913
- this.materializeContextAssets(args.sessionId, result.leasedWorkItems);
771
+ this.workspaceProvisioning.assignImplementationWorkspaces(args.sessionId, result.leasedWorkItems);
914
772
  }
915
773
  catch (error) {
916
774
  for (const item of result.leasedWorkItems) {
@@ -937,12 +795,9 @@ class AgentVariantsOrchestrator {
937
795
  }
938
796
  throw error;
939
797
  }
940
- const resources = this.resources.get(args.sessionId);
941
- if (resources) {
942
- const now = Date.now();
943
- for (const item of result.leasedWorkItems) {
944
- resources.leasedAt.set(item.id, now);
945
- }
798
+ const now = Date.now();
799
+ for (const item of result.leasedWorkItems) {
800
+ resources.leasedAt.set(item.id, now);
946
801
  }
947
802
  this.writeLeasedWorkItemBriefings(args.sessionId, result.leasedWorkItems.map((item) => item.id));
948
803
  this.trackRefinementLeased({ sessionId: args.sessionId, lease: result });
@@ -962,38 +817,6 @@ class AgentVariantsOrchestrator {
962
817
  this.emitChange();
963
818
  return decorated;
964
819
  }
965
- materializeContextAssets(sessionId, items) {
966
- const resources = this.ensureResources(sessionId);
967
- for (const item of items) {
968
- if (!isImplementationWorkItemKind(item.kind))
969
- continue;
970
- const input = (item.input ?? {});
971
- const assets = input.context?.contextAssets ?? [];
972
- if (assets.length === 0)
973
- continue;
974
- const workspace = this.getAssignedWorkspace({
975
- sessionId,
976
- workItemId: item.id,
977
- kind: item.kind,
978
- });
979
- const previewKind = workspace.kind === 'artifact_directory'
980
- ? 'static_artifact'
981
- : (resources.contextAssetPreviewKind ?? 'dev_server');
982
- resources.contextAssets.set(item.id, (0, contextAssets_1.materializeContextAssets)({ assets, workspace, previewKind }));
983
- }
984
- }
985
- async resolveContextAssetPreviewKind(sessionId) {
986
- const resources = this.ensureResources(sessionId);
987
- try {
988
- const environment = await this.resolveEnv(sessionId);
989
- resources.contextAssetPreviewKind =
990
- environment.framework === 'static' ? 'static_artifact' : 'dev_server';
991
- }
992
- catch (error) {
993
- resources.contextAssetPreviewKind = 'dev_server';
994
- log.warn(`Failed to resolve context asset preview kind for ${sessionId}; using dev-server references`, error);
995
- }
996
- }
997
820
  writeLeasedWorkItemBriefings(sessionId, leasedWorkItemIds) {
998
821
  if (leasedWorkItemIds.length === 0)
999
822
  return;
@@ -1023,62 +846,6 @@ class AgentVariantsOrchestrator {
1023
846
  }
1024
847
  }
1025
848
  }
1026
- ensureStaticWorkspaces(sessionId, items) {
1027
- const resources = this.ensureResources(sessionId);
1028
- const projectContext = this.store.getProjectContext(sessionId);
1029
- const provisionStartedAt = Date.now();
1030
- let assignedCount = 0;
1031
- for (const item of items) {
1032
- if (item.kind !== 'static_preview' && item.kind !== 'refine_variant') {
1033
- continue;
1034
- }
1035
- if (resources.workspaceManager.getAssignedWorkspace(item.id))
1036
- continue;
1037
- assignedCount += 1;
1038
- const input = (item.input ?? {});
1039
- const existingProjectPath = projectContext.kind === 'existing'
1040
- ? projectContext.projectPath
1041
- : undefined;
1042
- const existingParent = existingProjectPath && fs_1.default.existsSync(existingProjectPath)
1043
- ? path_1.default.join(existingProjectPath, '.rivet', 'variant-work', sessionId)
1044
- : path_1.default.join(os_1.default.tmpdir(), 'rivet-variant-work', sessionId);
1045
- const parent = projectContext.kind === 'fresh'
1046
- ? projectContext.workspacePath
1047
- : existingParent;
1048
- fs_1.default.mkdirSync(parent, { recursive: true });
1049
- const rootPath = projectContext.kind === 'fresh'
1050
- ? path_1.default.join(parent, `.rivet-preview-${item.id}`)
1051
- : path_1.default.join(parent, item.id);
1052
- const sourceVariantId = input.sourceVariantId ?? input.variantId;
1053
- const sourceDirectory = sourceVariantId
1054
- ? resources.staticPreviews.get(sourceVariantId)?.assetBase
1055
- : undefined;
1056
- resources.workspaceManager.assignArtifactWorkspace({
1057
- workItemId: item.id,
1058
- rootPath,
1059
- allowedRoot: parent,
1060
- entryPath: 'index.html',
1061
- sourceDirectory,
1062
- seedHtml: input.currentHtml,
1063
- });
1064
- }
1065
- if (assignedCount > 0) {
1066
- this.telemetry.track('agent_variants.provisioning_completed', {
1067
- source: 'mcp',
1068
- sessionId,
1069
- kind: 'artifact_directory',
1070
- variantCount: assignedCount,
1071
- durationMs: Date.now() - provisionStartedAt,
1072
- });
1073
- }
1074
- }
1075
- readAssignedStaticArtifact(sessionId, workItemId) {
1076
- const workspaceManager = this.resources.get(sessionId)?.workspaceManager;
1077
- if (!workspaceManager) {
1078
- throw new errors_1.AgentVariantsError('SESSION_NOT_FOUND', `No live resources for session ${sessionId}`);
1079
- }
1080
- return workspaceManager.readArtifact(workItemId);
1081
- }
1082
849
  beginStaticPreviewRefinements(args) {
1083
850
  const seenVariantIds = new Set();
1084
851
  const droppedRefinements = [];
@@ -1215,8 +982,8 @@ class AgentVariantsOrchestrator {
1215
982
  requestedCount: 1,
1216
983
  sourceVariantId: args.source.variantId,
1217
984
  });
1218
- const resources = this.ensureResources(args.sessionId);
1219
- resources.staticPreviews.set(append.workItemId, {
985
+ this.ensureResources(args.sessionId);
986
+ this.workspaceInventory.setStaticPreview(args.sessionId, {
1220
987
  workItemId: append.workItemId,
1221
988
  briefId: append.briefId,
1222
989
  entryPath: args.entryPath,
@@ -1263,7 +1030,7 @@ class AgentVariantsOrchestrator {
1263
1030
  };
1264
1031
  }
1265
1032
  const { assetBase, hasAssets } = await this.resolveAdoptedStaticPreviewFiles(args.filesDir, args.entryPath);
1266
- void this.stopLingeringCommittedDevServers().catch((err) => {
1033
+ void this.previewRuntime.stopLingeringCommitted().catch((err) => {
1267
1034
  log.warn('Stopping lingering committed dev servers failed', err);
1268
1035
  });
1269
1036
  const created = this.store.createSessionFromStaticHistoryVariant({
@@ -1276,8 +1043,8 @@ class AgentVariantsOrchestrator {
1276
1043
  projectContext: args.projectContext,
1277
1044
  });
1278
1045
  this.activeSessionId = created.sessionId;
1279
- const resources = this.ensureResources(created.sessionId);
1280
- resources.staticPreviews.set(created.workItemId, {
1046
+ this.ensureResources(created.sessionId);
1047
+ this.workspaceInventory.setStaticPreview(created.sessionId, {
1281
1048
  workItemId: created.workItemId,
1282
1049
  briefId: created.briefId,
1283
1050
  entryPath: args.entryPath,
@@ -1429,12 +1196,11 @@ class AgentVariantsOrchestrator {
1429
1196
  const resources = this.resources.get(sessionId);
1430
1197
  return this.store.getVariants(sessionId).map((variant) => {
1431
1198
  const port = this.getDevServerPort(sessionId, variant.workItemId);
1432
- const capturedDiff = resources?.worktrees.get(variant.workItemId)?.diff;
1199
+ const capturedDiff = this.workspaceInventory.getWorktree(sessionId, variant.workItemId)?.diff;
1433
1200
  const changedFilesCount = typeof capturedDiff === 'string' && capturedDiff.length > 0
1434
- ? countDiffFiles(capturedDiff)
1201
+ ? (0, variantWorkspaceFiles_1.countDiffFiles)(capturedDiff)
1435
1202
  : undefined;
1436
- const staticPreview = resources?.staticPreviews.get(variant.workItemId) ??
1437
- this.getStaticPreviewByBriefId(sessionId, variant.briefId);
1203
+ const staticPreview = this.workspaceInventory.getStaticPreview(sessionId, variant.workItemId) ?? this.getStaticPreviewByBriefId(sessionId, variant.briefId);
1438
1204
  let preview;
1439
1205
  if (staticPreview) {
1440
1206
  preview = {
@@ -1461,7 +1227,7 @@ class AgentVariantsOrchestrator {
1461
1227
  const isSucceeded = variant.status === 'succeeded';
1462
1228
  const qaFailed = qa?.status === 'failed';
1463
1229
  const canView = Boolean(preview) || (isSucceeded && Boolean(port));
1464
- const previewFailure = resources?.previewFailures.get(variant.workItemId);
1230
+ const previewFailure = this.previewRuntime.getFailure(sessionId, variant.workItemId);
1465
1231
  const previewUnavailable = !canView && isSucceeded && previewFailure
1466
1232
  ? {
1467
1233
  reason: previewFailure.reason,
@@ -1474,7 +1240,7 @@ class AgentVariantsOrchestrator {
1474
1240
  : 'Wait for a successful variant';
1475
1241
  return {
1476
1242
  ...variant,
1477
- design: enrichDesignSource(variant.design),
1243
+ design: (0, designContextArtifacts_1.enrichDesignSource)(variant.design),
1478
1244
  ...(preview ? { preview } : {}),
1479
1245
  port,
1480
1246
  ...(changedFilesCount !== undefined ? { changedFilesCount } : {}),
@@ -1508,55 +1274,35 @@ class AgentVariantsOrchestrator {
1508
1274
  return this.store.getProjectContext(sessionId);
1509
1275
  }
1510
1276
  getWorktreePath(sessionId, workItemId) {
1511
- return this.resources.get(sessionId)?.worktrees.get(workItemId)
1277
+ return this.workspaceInventory.getWorktree(sessionId, workItemId)
1512
1278
  ?.worktreePath;
1513
1279
  }
1514
1280
  getProjectCwdInWorktree(worktreePath) {
1515
1281
  return this.worktrees.getProjectCwdInWorktree(worktreePath);
1516
1282
  }
1517
1283
  getDevServerPort(sessionId, workItemId) {
1518
- return this.resources.get(sessionId)?.worktrees.get(workItemId)?.port;
1284
+ return this.previewRuntime.getPort(sessionId, workItemId);
1519
1285
  }
1520
1286
  async restoreVariantPreview(sessionId, workItemId, options = {}) {
1521
1287
  const resources = this.resources.get(sessionId);
1522
- const record = resources?.worktrees.get(workItemId);
1523
- if (!resources || !record || resources.cleanupStarted)
1288
+ const record = this.workspaceInventory.getWorktree(sessionId, workItemId);
1289
+ if (!resources ||
1290
+ !record ||
1291
+ this.workspaceInventory.isCleanupStarted(sessionId)) {
1524
1292
  return undefined;
1525
- if (record.previewRestoration)
1526
- return record.previewRestoration;
1527
- if (record.port !== undefined && record.devServerProcess) {
1528
- this.touchSession(sessionId);
1529
- return record.port;
1530
1293
  }
1531
- const failure = resources.previewFailures.get(workItemId);
1532
1294
  const isSucceeded = this.store
1533
1295
  .getVariants(sessionId)
1534
1296
  .some((variant) => variant.workItemId === workItemId && variant.status === 'succeeded');
1535
- const isExplicitRetry = options.retryFailed === true &&
1536
- failure?.reason !== 'missing_static_entry';
1537
- if (!failure ||
1538
- !isSucceeded ||
1539
- (failure.reason !== 'idle_timeout' && !isExplicitRetry)) {
1540
- return undefined;
1541
- }
1542
- resources.previewFailures.delete(workItemId);
1543
- record.devServerAutoRestarts = 0;
1544
- this.emitChange();
1545
1297
  const projectContext = this.store.getProjectContext(sessionId);
1546
- const restoration = this.startVariantDevServer({
1298
+ return this.previewRuntime.restoreVariant({
1547
1299
  sessionId,
1548
1300
  workItemId,
1549
- record,
1301
+ worktreePath: record.worktreePath,
1550
1302
  isFresh: projectContext.kind === 'fresh',
1303
+ isSucceeded,
1304
+ retryFailed: options.retryFailed,
1551
1305
  });
1552
- record.previewRestoration = restoration;
1553
- try {
1554
- return await restoration;
1555
- }
1556
- finally {
1557
- if (record.previewRestoration === restoration)
1558
- record.previewRestoration = undefined;
1559
- }
1560
1306
  }
1561
1307
  resolveDevServerPortForPreview(sessionId, workItemId) {
1562
1308
  const port = this.getDevServerPort(sessionId, workItemId);
@@ -1574,9 +1320,7 @@ class AgentVariantsOrchestrator {
1574
1320
  }
1575
1321
  }
1576
1322
  getStaticPreviewHtml(sessionId, workItemId) {
1577
- const preview = this.resources
1578
- .get(sessionId)
1579
- ?.staticPreviews.get(workItemId);
1323
+ const preview = this.workspaceInventory.getStaticPreview(sessionId, workItemId);
1580
1324
  if (preview) {
1581
1325
  try {
1582
1326
  const fromDisk = fs_1.default.readFileSync(path_1.default.join(preview.assetBase, preview.entryPath), 'utf8');
@@ -1588,7 +1332,7 @@ class AgentVariantsOrchestrator {
1588
1332
  try {
1589
1333
  const output = this.store.getWorkItemOutput(sessionId, workItemId);
1590
1334
  return typeof output?.html === 'string' && output.html.length > 0
1591
- ? buildStaticPreviewDocument({ html: output.html })
1335
+ ? (0, variantWorkspaceFiles_1.buildStaticPreviewDocument)({ html: output.html })
1592
1336
  : undefined;
1593
1337
  }
1594
1338
  catch {
@@ -1596,37 +1340,34 @@ class AgentVariantsOrchestrator {
1596
1340
  }
1597
1341
  }
1598
1342
  getDesignContextMarkdown(sessionId, artifactId) {
1599
- const artifact = findDesignContextArtifact(this.store.getProjectContext(sessionId), artifactId);
1343
+ const artifact = (0, designContextArtifacts_1.findDesignContextArtifact)(this.store.getProjectContext(sessionId), artifactId);
1600
1344
  return artifact?.content;
1601
1345
  }
1602
1346
  getDesignContextViewerHtml(sessionId, artifactId) {
1603
- const artifact = findDesignContextArtifact(this.store.getProjectContext(sessionId), artifactId);
1604
- return artifact ? buildDesignContextViewerDocument(artifact) : undefined;
1347
+ const artifact = (0, designContextArtifacts_1.findDesignContextArtifact)(this.store.getProjectContext(sessionId), artifactId);
1348
+ return artifact ? (0, designContextArtifacts_1.buildDesignContextViewerDocument)(artifact) : undefined;
1605
1349
  }
1606
1350
  hasStaticPreviewAssets(sessionId, workItemId) {
1607
- const record = this.resources
1608
- .get(sessionId)
1609
- ?.staticPreviews.get(workItemId);
1351
+ const record = this.workspaceInventory.getStaticPreview(sessionId, workItemId);
1610
1352
  return Boolean(record?.hasAssets);
1611
1353
  }
1612
1354
  resolveStaticPreviewAssetPath(sessionId, workItemId, requestedPath) {
1613
- const record = this.resources
1614
- .get(sessionId)
1615
- ?.staticPreviews.get(workItemId);
1355
+ const record = this.workspaceInventory.getStaticPreview(sessionId, workItemId);
1616
1356
  if (!record?.assetBase)
1617
1357
  return undefined;
1618
1358
  if (!requestedPath || requestedPath.length === 0)
1619
1359
  return undefined;
1620
- return (resolveAssetWithinBase(record.assetBase, requestedPath) ??
1360
+ return ((0, variantWorkspaceFiles_1.resolveAssetWithinBase)(record.assetBase, requestedPath) ??
1621
1361
  (record.assetFallbackBase
1622
- ? resolveAssetWithinBase(record.assetFallbackBase, requestedPath)
1362
+ ? (0, variantWorkspaceFiles_1.resolveAssetWithinBase)(record.assetFallbackBase, requestedPath)
1623
1363
  : undefined));
1624
1364
  }
1625
1365
  getStaticPreviewByBriefId(sessionId, briefId) {
1626
- const resources = this.resources.get(sessionId);
1627
- if (!resources)
1366
+ if (!this.workspaceInventory.hasSession(sessionId))
1628
1367
  return undefined;
1629
- return [...resources.staticPreviews.values()].find((preview) => preview.briefId === briefId);
1368
+ return this.workspaceInventory
1369
+ .getStaticPreviews(sessionId)
1370
+ .find((preview) => preview.briefId === briefId);
1630
1371
  }
1631
1372
  buildStaticPreviewUrl(sessionId, workItemId) {
1632
1373
  const safeSessionId = encodeURIComponent(sessionId);
@@ -1655,9 +1396,9 @@ class AgentVariantsOrchestrator {
1655
1396
  this.emitChange();
1656
1397
  try {
1657
1398
  if (projectContext.kind === 'existing') {
1658
- await this.resolveContextAssetPreviewKind(args.sessionId);
1399
+ await this.workspaceProvisioning.resolveContextAssetPreviewKind(args.sessionId);
1659
1400
  }
1660
- await this.provisionWorktrees(args.sessionId, result);
1401
+ await this.workspaceProvisioning.provision(args.sessionId, result);
1661
1402
  this.emitChange();
1662
1403
  }
1663
1404
  catch (err) {
@@ -1730,10 +1471,10 @@ class AgentVariantsOrchestrator {
1730
1471
  workItemIds: createdWorkItemIds,
1731
1472
  });
1732
1473
  if (args.sourceVariantId) {
1733
- await this.provisionForkedFromVariantWorktrees(sessionId, args.sourceVariantId, createdWorkItemIds);
1474
+ await this.workspaceProvisioning.provisionForked(sessionId, args.sourceVariantId, createdWorkItemIds);
1734
1475
  }
1735
1476
  else {
1736
- await this.provisionAppendedExistingWorktrees(sessionId, createdWorkItemIds);
1477
+ await this.workspaceProvisioning.provisionAppended(sessionId, createdWorkItemIds);
1737
1478
  }
1738
1479
  this.telemetry.track('agent_variants.variants_appended', {
1739
1480
  source: 'mcp',
@@ -1870,7 +1611,7 @@ class AgentVariantsOrchestrator {
1870
1611
  return Array.from({ length: args.count }, (_, i) => withUniqueLabel({
1871
1612
  briefId: briefId(i),
1872
1613
  label: args.count > 1 ? `${runLabel} ${i + 1}` : runLabel,
1873
- body: synthesizeUnifiedBriefBody(args.prompt, i + 1, args.count),
1614
+ body: (0, variantWorkspaceFiles_1.synthesizeUnifiedBriefBody)(args.prompt, i + 1, args.count),
1874
1615
  }, i));
1875
1616
  }
1876
1617
  getAppendableExistingSessionId(projectPath) {
@@ -1887,96 +1628,6 @@ class AgentVariantsOrchestrator {
1887
1628
  return null;
1888
1629
  return sessionId;
1889
1630
  }
1890
- async registerWorktreeWorkspace(resources, record) {
1891
- let projectPath = record.worktreePath;
1892
- try {
1893
- projectPath = await this.worktrees.getProjectCwdInWorktree(record.worktreePath);
1894
- }
1895
- catch (err) {
1896
- log.warn(`Failed to resolve project path in worktree ${record.worktreePath}; using its root`, err);
1897
- }
1898
- resources.worktrees.set(record.workItemId, record);
1899
- resources.workspaceManager.registerGitWorkspace({
1900
- workItemId: record.workItemId,
1901
- rootPath: record.worktreePath,
1902
- projectPath,
1903
- });
1904
- }
1905
- async withProvisioningTelemetry(args, run) {
1906
- const startedAt = Date.now();
1907
- this.telemetry.track('agent_variants.provisioning_started', {
1908
- source: 'mcp',
1909
- sessionId: args.sessionId,
1910
- kind: args.kind,
1911
- variantCount: args.variantCount,
1912
- });
1913
- try {
1914
- const result = await run();
1915
- this.telemetry.track('agent_variants.provisioning_completed', {
1916
- source: 'mcp',
1917
- sessionId: args.sessionId,
1918
- kind: args.kind,
1919
- variantCount: args.variantCount,
1920
- durationMs: Date.now() - startedAt,
1921
- });
1922
- return result;
1923
- }
1924
- catch (err) {
1925
- const timedOut = err instanceof ProvisionTimeoutError;
1926
- this.telemetry.track('agent_variants.provisioning_failed', {
1927
- source: 'mcp',
1928
- sessionId: args.sessionId,
1929
- kind: args.kind,
1930
- variantCount: args.variantCount,
1931
- durationMs: Date.now() - startedAt,
1932
- timedOut,
1933
- errorCode: timedOut
1934
- ? 'PROVISION_TIMEOUT'
1935
- : err instanceof errors_1.AgentVariantsError
1936
- ? err.code
1937
- : 'PROVISION_FAILED',
1938
- });
1939
- throw err;
1940
- }
1941
- }
1942
- async provisionAppendedExistingWorktrees(sessionId, workItemIds) {
1943
- if (workItemIds.length === 0)
1944
- return;
1945
- await this.withProvisioningTelemetry({
1946
- sessionId,
1947
- kind: 'appended_existing',
1948
- variantCount: workItemIds.length,
1949
- }, async () => {
1950
- const resources = this.ensureResources(sessionId);
1951
- const instanceKeyPrefix = `a${(0, crypto_1.randomUUID)().slice(0, 8)}-`;
1952
- const paths = await withProvisionTimeout(this.worktrees.createWorktrees(sessionId, workItemIds.length, instanceKeyPrefix), this.provisionTimeoutMs);
1953
- await Promise.all(workItemIds.map((id, index) => this.registerWorktreeWorkspace(resources, {
1954
- workItemId: id,
1955
- worktreePath: paths[index],
1956
- })));
1957
- });
1958
- }
1959
- async provisionForkedFromVariantWorktrees(sessionId, sourceVariantId, workItemIds) {
1960
- if (workItemIds.length === 0)
1961
- return;
1962
- const sourcePath = this.getWorktreePath(sessionId, sourceVariantId);
1963
- if (!sourcePath) {
1964
- throw new errors_1.AgentVariantsError('VARIANT_NOT_EDITABLE', `Cannot fork from variant ${sourceVariantId}: no worktree path`);
1965
- }
1966
- const forkWorktreesFrom = this.worktrees.forkWorktreesFrom?.bind(this.worktrees);
1967
- if (!forkWorktreesFrom) {
1968
- throw new errors_1.AgentVariantsError('RUNTIME_VALIDATION_FAILED', 'worktreeManager does not implement forkWorktreesFrom');
1969
- }
1970
- await this.withProvisioningTelemetry({ sessionId, kind: 'forked_variant', variantCount: workItemIds.length }, async () => {
1971
- const resources = this.ensureResources(sessionId);
1972
- const instanceKeyPrefix = `f${(0, crypto_1.randomUUID)().slice(0, 8)}-`;
1973
- const paths = await withProvisionTimeout(forkWorktreesFrom(sourcePath, sessionId, workItemIds.length, instanceKeyPrefix), this.provisionTimeoutMs);
1974
- await Promise.all(workItemIds.map((id, index) => this.registerWorktreeWorkspace(resources, {
1975
- workItemId: id,
1976
- worktreePath: paths[index],
1977
- })));
1978
- });
1979
- }
1980
1631
  async reportComplete(args) {
1981
1632
  const reportReceivedAt = Date.now();
1982
1633
  if (this.store.hasSession(args.sessionId) &&
@@ -1999,14 +1650,14 @@ class AgentVariantsOrchestrator {
1999
1650
  args.output === undefined
2000
1651
  ? {
2001
1652
  ...args,
2002
- output: this.readAssignedStaticArtifact(args.sessionId, args.workItemId),
1653
+ output: this.workspaceProvisioning.readArtifact(args.sessionId, args.workItemId),
2003
1654
  }
2004
1655
  : args;
2005
1656
  if (resolvedArgs.status === 'succeeded' &&
2006
1657
  this.store.hasSession(args.sessionId) &&
2007
1658
  (workItemKind === 'static_preview' ||
2008
1659
  workItemKind === 'refine_variant') &&
2009
- !parseStaticPreviewOutput(normalizeOutput(resolvedArgs.output))) {
1660
+ !(0, variantWorkspaceFiles_1.parseStaticPreviewOutput)((0, variantWorkspaceFiles_1.normalizeOutput)(resolvedArgs.output))) {
2010
1661
  const telemetry = this.refinementTelemetryForWorkItem({
2011
1662
  sessionId: args.sessionId,
2012
1663
  workItemId: args.workItemId,
@@ -2082,7 +1733,7 @@ class AgentVariantsOrchestrator {
2082
1733
  const durationMs = leasedAt !== undefined ? Date.now() - leasedAt : null;
2083
1734
  const approveStartedAt = resources?.approveStartedAt;
2084
1735
  const approveToCompleteMs = approveStartedAt !== undefined ? Date.now() - approveStartedAt : null;
2085
- const outputBytes = estimateOutputBytes(effectiveArgs.output);
1736
+ const outputBytes = (0, variantWorkspaceFiles_1.estimateOutputBytes)(effectiveArgs.output);
2086
1737
  const projectContextKind = this.store.getProjectContext(args.sessionId).kind;
2087
1738
  const mode = workItemKind !== undefined && isImplementationWorkItemKind(workItemKind)
2088
1739
  ? this.store.isVaryForkWorkItem(args.sessionId, args.workItemId)
@@ -2106,7 +1757,7 @@ class AgentVariantsOrchestrator {
2106
1757
  errorCode: effectiveArgs.error?.code ?? null,
2107
1758
  errorMessage: effectiveArgs.error?.message ?? null,
2108
1759
  errorApiStatus: effectiveArgs.error?.apiErrorStatus ?? null,
2109
- isScaffold: resources?.scaffoldBaseWorkItemId === args.workItemId,
1760
+ isScaffold: this.workspaceInventory.isScaffoldBaseWorkItem(args.sessionId, args.workItemId),
2110
1761
  projectContextKind,
2111
1762
  tokensIn: args.tokensIn ?? null,
2112
1763
  tokensOut: args.tokensOut ?? null,
@@ -2383,9 +2034,7 @@ class AgentVariantsOrchestrator {
2383
2034
  log.warn(`persistVariantHistoryAtCommit (duplicate retry) failed for session ${args.sessionId}`, err);
2384
2035
  });
2385
2036
  }
2386
- const lingering = this.lingeringCommittedDevServers.get(args.sessionId);
2387
- const live = this.resources.get(args.sessionId)?.committedDevServer;
2388
- const replayPort = lingering?.port ?? live?.port;
2037
+ const replayPort = this.previewRuntime.getCommittedPort(args.sessionId);
2389
2038
  return {
2390
2039
  enqueued: false,
2391
2040
  duplicate: true,
@@ -2436,12 +2085,12 @@ class AgentVariantsOrchestrator {
2436
2085
  else {
2437
2086
  const compareBase = realDestination ?? destinationPath;
2438
2087
  const anyAssetBaseIsDestination = [
2439
- ...resources.staticPreviews.values(),
2088
+ ...this.workspaceInventory.getStaticPreviews(args.sessionId),
2440
2089
  ].some((sp) => sp.assetBase === compareBase);
2441
2090
  if (!anyAssetBaseIsDestination) {
2442
2091
  const allowed = new Set();
2443
- for (const sp of resources.staticPreviews.values()) {
2444
- if (sp.assetBase && isStrictlyInside(sp.assetBase, compareBase)) {
2092
+ for (const sp of this.workspaceInventory.getStaticPreviews(args.sessionId)) {
2093
+ if (sp.assetBase && (0, variantWorkspaceFiles_1.isStrictlyInside)(sp.assetBase, compareBase)) {
2445
2094
  allowed.add(path_1.default.basename(sp.assetBase));
2446
2095
  }
2447
2096
  }
@@ -2449,16 +2098,16 @@ class AgentVariantsOrchestrator {
2449
2098
  }
2450
2099
  }
2451
2100
  if (isViteApp) {
2452
- const record = resources.worktrees.get(args.variantId);
2101
+ const record = this.workspaceInventory.getWorktree(args.sessionId, args.variantId);
2453
2102
  if (!record) {
2454
2103
  throw new errors_1.AgentVariantsError('WORK_ITEM_NOT_FOUND', `Unknown variantId ${args.variantId} for session ${args.sessionId}`);
2455
2104
  }
2456
2105
  let usedRename = false;
2457
2106
  let isDependencyInstallRequired = false;
2458
2107
  try {
2459
- if (areSameVolume(record.worktreePath, destinationPath)) {
2460
- await this.stopAllDevServersForSession(resources);
2461
- isDependencyInstallRequired = await resolveNodeModulesSymlink(record.worktreePath);
2108
+ if ((0, variantWorkspaceFiles_1.areSameVolume)(record.worktreePath, destinationPath)) {
2109
+ await this.previewRuntime.stopVariantServers(args.sessionId);
2110
+ isDependencyInstallRequired = await (0, variantWorkspaceFiles_1.resolveNodeModulesSymlink)(record.worktreePath);
2462
2111
  await this.renameWorktreeIntoDestination(record.worktreePath, destinationPath);
2463
2112
  usedRename = true;
2464
2113
  }
@@ -2470,11 +2119,11 @@ class AgentVariantsOrchestrator {
2470
2119
  const message = err instanceof Error ? err.message : String(err);
2471
2120
  throw new errors_1.AgentVariantsError('RUNTIME_VALIDATION_FAILED', `Failed to materialize vite_app variant to ${destinationPath}: ${message}`);
2472
2121
  }
2473
- changedFilesCount = countWorktreeFiles(destinationPath);
2122
+ changedFilesCount = (0, variantWorkspaceFiles_1.countWorktreeFiles)(destinationPath);
2474
2123
  if (!usedRename) {
2475
2124
  void this.installDependencies(destinationPath)
2476
- .then(() => this.startCommittedDevServer({
2477
- resources,
2125
+ .then(() => this.previewRuntime.startCommitted({
2126
+ sessionId: args.sessionId,
2478
2127
  destinationPath,
2479
2128
  }))
2480
2129
  .catch((err) => {
@@ -2495,8 +2144,8 @@ class AgentVariantsOrchestrator {
2495
2144
  catch (err) {
2496
2145
  log.warn(`preserveUnchosenVariants failed for session ${args.sessionId}; continuing without history`, err);
2497
2146
  }
2498
- committedPreviewPort = await this.startCommittedDevServer({
2499
- resources,
2147
+ committedPreviewPort = await this.previewRuntime.startCommitted({
2148
+ sessionId: args.sessionId,
2500
2149
  destinationPath,
2501
2150
  mode: 'vite_app',
2502
2151
  });
@@ -2512,7 +2161,7 @@ class AgentVariantsOrchestrator {
2512
2161
  envelopeDestination = destinationPath;
2513
2162
  }
2514
2163
  else {
2515
- const staticPreview = resources.staticPreviews.get(args.variantId);
2164
+ const staticPreview = this.workspaceInventory.getStaticPreview(args.sessionId, args.variantId);
2516
2165
  const htmlFromDir = staticPreview
2517
2166
  ? this.getStaticPreviewHtml(args.sessionId, args.variantId)
2518
2167
  : undefined;
@@ -2537,7 +2186,7 @@ class AgentVariantsOrchestrator {
2537
2186
  }
2538
2187
  const chosenAssetBase = staticPreview?.assetBase;
2539
2188
  if (chosenAssetBase &&
2540
- isStrictlyInside(chosenAssetBase, flattenBase)) {
2189
+ (0, variantWorkspaceFiles_1.isStrictlyInside)(chosenAssetBase, flattenBase)) {
2541
2190
  for (const entry of fs_1.default.readdirSync(chosenAssetBase)) {
2542
2191
  const src = path_1.default.join(chosenAssetBase, entry);
2543
2192
  const dst = path_1.default.join(flattenBase, entry);
@@ -2560,11 +2209,11 @@ class AgentVariantsOrchestrator {
2560
2209
  }
2561
2210
  fs_1.default.rmSync(chosenAssetBase, { recursive: true, force: true });
2562
2211
  }
2563
- for (const other of resources.staticPreviews.values()) {
2212
+ for (const other of this.workspaceInventory.getStaticPreviews(args.sessionId)) {
2564
2213
  if (other.workItemId === args.variantId)
2565
2214
  continue;
2566
2215
  if (other.assetBase &&
2567
- isStrictlyInside(other.assetBase, flattenBase)) {
2216
+ (0, variantWorkspaceFiles_1.isStrictlyInside)(other.assetBase, flattenBase)) {
2568
2217
  fs_1.default.rmSync(other.assetBase, { recursive: true, force: true });
2569
2218
  }
2570
2219
  }
@@ -2574,9 +2223,9 @@ class AgentVariantsOrchestrator {
2574
2223
  const message = err instanceof Error ? err.message : String(err);
2575
2224
  throw new errors_1.AgentVariantsError('RUNTIME_VALIDATION_FAILED', `Failed to write static preview to ${destinationPath}: ${message}`);
2576
2225
  }
2577
- changedFilesCount = countWorktreeFiles(destinationPath);
2578
- committedPreviewPort = await this.startCommittedDevServer({
2579
- resources,
2226
+ changedFilesCount = (0, variantWorkspaceFiles_1.countWorktreeFiles)(destinationPath);
2227
+ committedPreviewPort = await this.previewRuntime.startCommitted({
2228
+ sessionId: args.sessionId,
2580
2229
  destinationPath,
2581
2230
  mode: 'static_preview',
2582
2231
  });
@@ -2591,7 +2240,7 @@ class AgentVariantsOrchestrator {
2591
2240
  }
2592
2241
  else {
2593
2242
  const env = await this.resolveEnv(args.sessionId);
2594
- const record = resources.worktrees.get(args.variantId);
2243
+ const record = this.workspaceInventory.getWorktree(args.sessionId, args.variantId);
2595
2244
  const saved = record
2596
2245
  ? null
2597
2246
  : await this.getSavedVariant({
@@ -2606,7 +2255,7 @@ class AgentVariantsOrchestrator {
2606
2255
  if (diff === undefined) {
2607
2256
  throw new errors_1.AgentVariantsError('INVALID_STAGE_ACTION', `Variant ${args.variantId} has no captured diff yet — wait for report_variant_complete(succeeded) first`);
2608
2257
  }
2609
- changedFilesCount = countDiffFiles(diff);
2258
+ changedFilesCount = (0, variantWorkspaceFiles_1.countDiffFiles)(diff);
2610
2259
  if (!this.worktrees.applyDiffToProject) {
2611
2260
  throw new errors_1.AgentVariantsError('RUNTIME_VALIDATION_FAILED', 'WorktreeManager does not support applying diffs to the project tree');
2612
2261
  }
@@ -2706,212 +2355,7 @@ class AgentVariantsOrchestrator {
2706
2355
  throw new errors_1.AgentVariantsError('DESTINATION_NOT_EMPTY', `Destination ${destinationPath} is not empty (${userVisibleEntries.length} entries) — refuse to materialize.`);
2707
2356
  }
2708
2357
  getVariantDiff(sessionId, variantId) {
2709
- return this.resources.get(sessionId)?.worktrees.get(variantId)?.diff;
2710
- }
2711
- async provisionWorktrees(sessionId, approveResult) {
2712
- const projectContext = this.store.getProjectContext(sessionId);
2713
- const variantCount = approveResult.codeGenWorkItemIds.length +
2714
- (approveResult.scaffoldBaseWorkItemId ? 1 : 0);
2715
- if ((0, runPlan_1.resolveRunPlanPolicy)(projectContext).provision === 'none') {
2716
- return;
2717
- }
2718
- const startedAt = Date.now();
2719
- this.telemetry.track('agent_variants.provisioning_started', {
2720
- source: 'mcp',
2721
- sessionId,
2722
- kind: projectContext.kind,
2723
- variantCount,
2724
- });
2725
- const run = async () => {
2726
- if (projectContext.kind === 'fresh') {
2727
- await this.provisionFreshWorktrees(sessionId, approveResult, projectContext);
2728
- return;
2729
- }
2730
- await this.provisionExistingWorktrees(sessionId, approveResult);
2731
- };
2732
- try {
2733
- await withProvisionTimeout(run(), this.provisionTimeoutMs);
2734
- this.telemetry.track('agent_variants.provisioning_completed', {
2735
- source: 'mcp',
2736
- sessionId,
2737
- kind: projectContext.kind,
2738
- variantCount,
2739
- durationMs: Date.now() - startedAt,
2740
- });
2741
- }
2742
- catch (err) {
2743
- const timedOut = err instanceof ProvisionTimeoutError;
2744
- this.telemetry.track('agent_variants.provisioning_failed', {
2745
- source: 'mcp',
2746
- sessionId,
2747
- kind: projectContext.kind,
2748
- variantCount,
2749
- durationMs: Date.now() - startedAt,
2750
- timedOut,
2751
- errorCode: timedOut
2752
- ? 'PROVISION_TIMEOUT'
2753
- : err instanceof errors_1.AgentVariantsError
2754
- ? err.code
2755
- : 'PROVISION_FAILED',
2756
- });
2757
- void this.cancel({ sessionId, reason: 'provisioning_failed' }).catch((teardownErr) => {
2758
- log.warn(`cleanup after provisioning failure for ${sessionId} failed`, teardownErr);
2759
- });
2760
- if (timedOut) {
2761
- throw new errors_1.AgentVariantsError('RUNTIME_VALIDATION_FAILED', `Provisioning ${variantCount} variant worktree(s) timed out after ` +
2762
- `${Math.round(this.provisionTimeoutMs / 1000)}s. This usually means the ` +
2763
- `project is very large or slow to clone (e.g. a big monorepo, or a ` +
2764
- `React Native tree with native build dirs that aren't git-ignored). ` +
2765
- `Try git-ignoring build artifacts, or generate standalone variants ` +
2766
- `with start_variants(mode='zero_to_one') instead of cloning this project.`);
2767
- }
2768
- throw err;
2769
- }
2770
- }
2771
- async provisionExistingWorktrees(sessionId, approveResult) {
2772
- const resources = this.ensureResources(sessionId);
2773
- const totalCount = approveResult.codeGenWorkItemIds.length +
2774
- (approveResult.scaffoldBaseWorkItemId ? 1 : 0);
2775
- if (totalCount === 0)
2776
- return;
2777
- log.info(`Provisioning ${totalCount} worktree(s) for session ${sessionId}`);
2778
- const paths = await this.worktrees.createWorktrees(sessionId, totalCount);
2779
- let cursor = 0;
2780
- if (approveResult.scaffoldBaseWorkItemId) {
2781
- resources.scaffoldBaseWorkItemId = approveResult.scaffoldBaseWorkItemId;
2782
- await this.registerWorktreeWorkspace(resources, {
2783
- workItemId: approveResult.scaffoldBaseWorkItemId,
2784
- worktreePath: paths[cursor],
2785
- });
2786
- cursor += 1;
2787
- }
2788
- for (const id of approveResult.codeGenWorkItemIds) {
2789
- const p = paths[cursor];
2790
- cursor += 1;
2791
- await this.registerWorktreeWorkspace(resources, {
2792
- workItemId: id,
2793
- worktreePath: p,
2794
- });
2795
- }
2796
- }
2797
- async provisionFreshWorktrees(sessionId, approveResult, projectContext) {
2798
- const resources = this.ensureResources(sessionId);
2799
- const scaffoldId = approveResult.scaffoldBaseWorkItemId;
2800
- if (!scaffoldId) {
2801
- throw new errors_1.AgentVariantsError('INVALID_STAGE_ACTION', `Fresh session ${sessionId} approved without a scaffold_base item`);
2802
- }
2803
- const codeGenIds = approveResult.codeGenWorkItemIds;
2804
- if (codeGenIds.length === 0)
2805
- return;
2806
- const designContext = projectContext.designContext
2807
- ? codeGenIds.map((codeGenId) => {
2808
- const input = this.store.getWorkItemInput(sessionId, codeGenId);
2809
- return input.designContextEntry;
2810
- })
2811
- : undefined;
2812
- const createFresh = this.worktrees.createFreshWorktrees;
2813
- if (!createFresh) {
2814
- throw new errors_1.AgentVariantsError('RUNTIME_VALIDATION_FAILED', 'worktreeManager does not implement createFreshWorktrees');
2815
- }
2816
- const scaffoldStartedAt = Date.now();
2817
- trackScaffoldStarted(this.telemetry, {
2818
- sessionId,
2819
- variantCount: codeGenIds.length,
2820
- designContext: summarizeDesignContext(designContext),
2821
- });
2822
- log.info(`Provisioning ${codeGenIds.length} fresh worktree(s) for session ${sessionId}`);
2823
- const destinationParent = projectContext.workspaceRoot;
2824
- const paths = await createFresh.call(this.worktrees, sessionId, codeGenIds.length, viteReactTs_1.VITE_REACT_TS_TEMPLATE, designContext, destinationParent);
2825
- resources.scaffoldBaseWorkItemId = scaffoldId;
2826
- resources.freshDestinationParent = destinationParent;
2827
- await Promise.all(codeGenIds.map((workItemId, index) => this.registerWorktreeWorkspace(resources, {
2828
- workItemId,
2829
- worktreePath: paths[index],
2830
- })));
2831
- const assetPlan = projectContext.executionPlan?.assetPlan ?? [];
2832
- if (assetPlan.length > 0) {
2833
- try {
2834
- for (const worktreePath of paths) {
2835
- for (const entry of assetPlan) {
2836
- copyAssetIntoWorktree(worktreePath, entry, projectContext.assetSourceRoot);
2837
- }
2838
- }
2839
- }
2840
- catch (err) {
2841
- const message = err instanceof Error ? err.message : String(err);
2842
- log.warn(`asset copy failed for session ${sessionId}; marking scaffold_base failed`, err);
2843
- trackScaffoldFailed(this.telemetry, {
2844
- sessionId,
2845
- errorCode: 'SCAFFOLD_FAILED',
2846
- durationMs: Date.now() - scaffoldStartedAt,
2847
- });
2848
- try {
2849
- this.store.failInternal({
2850
- sessionId,
2851
- workItemId: scaffoldId,
2852
- error: { code: 'SCAFFOLD_FAILED', message },
2853
- });
2854
- this.emitChange();
2855
- }
2856
- catch (storeErr) {
2857
- log.error(`failInternal failed for ${sessionId}/${scaffoldId}`, storeErr);
2858
- }
2859
- return;
2860
- }
2861
- }
2862
- void this.runBackgroundInstall(sessionId, scaffoldId, paths, scaffoldStartedAt).catch((err) => {
2863
- log.error(`runBackgroundInstall failed for session ${sessionId}`, err);
2864
- });
2865
- }
2866
- async runBackgroundInstall(sessionId, scaffoldWorkItemId, worktreePaths, scaffoldStartedAt) {
2867
- const installStartedAt = Date.now();
2868
- try {
2869
- await Promise.all(worktreePaths.map((worktreePath) => this.installDependencies(worktreePath, {
2870
- workspaceRoot: worktreePath,
2871
- signal: this.resources.get(sessionId)?.dependencyAbortController.signal,
2872
- })));
2873
- }
2874
- catch (err) {
2875
- const resources = this.resources.get(sessionId);
2876
- if (!resources || resources.cleanupStarted)
2877
- return;
2878
- const message = err instanceof Error ? err.message : String(err);
2879
- log.warn(`npm install failed for session ${sessionId}; marking scaffold_base failed`, err);
2880
- trackScaffoldFailed(this.telemetry, {
2881
- sessionId,
2882
- errorCode: 'SCAFFOLD_FAILED',
2883
- durationMs: Date.now() - scaffoldStartedAt,
2884
- });
2885
- try {
2886
- this.store.failInternal({
2887
- sessionId,
2888
- workItemId: scaffoldWorkItemId,
2889
- error: { code: 'SCAFFOLD_FAILED', message },
2890
- });
2891
- this.emitChange();
2892
- }
2893
- catch (storeErr) {
2894
- log.error(`failInternal failed for ${sessionId}/${scaffoldWorkItemId}`, storeErr);
2895
- }
2896
- return;
2897
- }
2898
- try {
2899
- this.store.completeInternal({
2900
- sessionId,
2901
- workItemId: scaffoldWorkItemId,
2902
- output: { worktreePaths },
2903
- });
2904
- this.emitChange();
2905
- }
2906
- catch (err) {
2907
- log.warn(`completeInternal failed for ${sessionId}/${scaffoldWorkItemId}`, err);
2908
- }
2909
- trackScaffoldCompleted(this.telemetry, {
2910
- sessionId,
2911
- durationMs: Date.now() - scaffoldStartedAt,
2912
- installDurationMs: Date.now() - installStartedAt,
2913
- variantCount: worktreePaths.length,
2914
- });
2358
+ return this.workspaceInventory.getWorktree(sessionId, variantId)?.diff;
2915
2359
  }
2916
2360
  async evaluateQaForReport(args) {
2917
2361
  if (args.status !== 'succeeded')
@@ -2929,7 +2373,7 @@ class AgentVariantsOrchestrator {
2929
2373
  let baseQa = null;
2930
2374
  const checkedSource = undefined;
2931
2375
  if (kind === 'static_preview') {
2932
- const parsed = parseStaticPreviewOutput(normalizeOutput(args.output));
2376
+ const parsed = (0, variantWorkspaceFiles_1.parseStaticPreviewOutput)((0, variantWorkspaceFiles_1.normalizeOutput)(args.output));
2933
2377
  if (!parsed)
2934
2378
  return null;
2935
2379
  baseQa = await this.runPreviewQaSafely(args, parsed.html);
@@ -2966,8 +2410,7 @@ class AgentVariantsOrchestrator {
2966
2410
  if (!isDesignCreationCodeGenInput(input, this.store.getPrompt(args.sessionId))) {
2967
2411
  return null;
2968
2412
  }
2969
- const resources = this.resources.get(args.sessionId);
2970
- const record = resources?.worktrees.get(args.workItemId);
2413
+ const record = this.workspaceInventory.getWorktree(args.sessionId, args.workItemId);
2971
2414
  if (!record) {
2972
2415
  const qa = buildDiffCaptureFailureQa('diff:worktree-record-missing', 'The succeeded report could not be checked because its worktree record is missing.');
2973
2416
  this.memoizeQa(args.sessionId, args.workItemId, qa);
@@ -3086,248 +2529,31 @@ class AgentVariantsOrchestrator {
3086
2529
  resources.repairCompletionResults.set(repairCompletionKey(args), completionResult);
3087
2530
  return completionResult;
3088
2531
  }
3089
- async startVariantDevServer(args) {
3090
- if (args.record.port !== undefined && args.record.devServerProcess) {
3091
- return args.record.port;
3092
- }
3093
- if (!args.autoRestartTimer && args.record.pendingAutoRestart) {
3094
- clearTimeout(args.record.pendingAutoRestart);
3095
- args.record.pendingAutoRestart = undefined;
3096
- }
3097
- const canContinueStart = () => {
3098
- const resources = this.resources.get(args.sessionId);
3099
- const isAutoRestartCancelled = args.autoRestartTimer !== undefined &&
3100
- args.record.pendingAutoRestart !== args.autoRestartTimer;
3101
- return Boolean(resources && !resources.cleanupStarted && !isAutoRestartCancelled);
3102
- };
3103
- const previewStartedAt = Date.now();
3104
- const projectContextKind = args.isFresh ? 'fresh' : 'existing';
3105
- trackPreviewStarted(this.telemetry, {
3106
- sessionId: args.sessionId,
3107
- variantId: args.workItemId,
3108
- projectContextKind,
3109
- previewKind: 'dev_server',
3110
- });
3111
- const startPort = await this.resolveDevServerStartPort(args.sessionId, args.isFresh);
3112
- let scanFrom = startPort;
3113
- const maxAttempts = DEV_SERVER_START_MAX_ATTEMPTS;
3114
- let installRetried = false;
3115
- let lastDev;
3116
- for (let attempt = 1; attempt <= maxAttempts; attempt++) {
3117
- if (!canContinueStart())
3118
- return undefined;
3119
- let port;
3120
- try {
3121
- port = await this.worktrees.findFreePortFrom(scanFrom);
3122
- const dev = await this.resolveDevServer(args.sessionId, args.record.worktreePath, port, args.isFresh);
3123
- lastDev = dev;
3124
- if (!canContinueStart())
3125
- return undefined;
3126
- const proc = await this.worktrees.startDevServer(dev.cwd, port, dev.cmd, dev.args, dev.env);
3127
- if (!canContinueStart()) {
3128
- log.info(`Variant ${args.workItemId} dev server booted after preview cleanup; stopping it`);
3129
- void this.worktrees.stopDevServer(proc).catch(() => undefined);
3130
- return undefined;
3131
- }
3132
- if (args.autoRestartTimer) {
3133
- args.record.pendingAutoRestart = undefined;
3134
- }
3135
- args.record.port = port;
3136
- args.record.devServerProcess = proc;
3137
- args.record.devCommand = dev;
3138
- proc.once('exit', (code, signal) => {
3139
- if (args.record.devServerProcess !== proc)
3140
- return;
3141
- args.record.port = undefined;
3142
- args.record.devServerProcess = undefined;
3143
- const resources = this.resources.get(args.sessionId);
3144
- const restartsUsed = args.record.devServerAutoRestarts ?? 0;
3145
- const canRestart = restartsUsed < DEV_SERVER_MAX_AUTO_RESTARTS &&
3146
- resources !== undefined &&
3147
- !resources.cleanupStarted;
3148
- log.warn(`Variant ${args.workItemId} dev server exited unexpectedly (code=${code ?? 'null'}, signal=${signal ?? 'null'}); ` +
3149
- (canRestart
3150
- ? `auto-restarting (${restartsUsed + 1}/${DEV_SERVER_MAX_AUTO_RESTARTS})`
3151
- : 'preview disabled'));
3152
- if (canRestart) {
3153
- args.record.devServerAutoRestarts = restartsUsed + 1;
3154
- this.emitChange();
3155
- const timer = setTimeout(() => {
3156
- const current = this.resources.get(args.sessionId);
3157
- if (!current ||
3158
- current.cleanupStarted ||
3159
- args.record.pendingAutoRestart !== timer) {
3160
- return;
3161
- }
3162
- this.startVariantDevServer({
3163
- ...args,
3164
- autoRestartTimer: timer,
3165
- })
3166
- .catch((err) => log.warn(`Auto-restart of variant ${args.workItemId} dev server failed`, err))
3167
- .finally(() => {
3168
- if (args.record.pendingAutoRestart === timer) {
3169
- args.record.pendingAutoRestart = undefined;
3170
- }
3171
- });
3172
- }, this.devServerRestartDelayMs);
3173
- args.record.pendingAutoRestart = timer;
3174
- timer.unref?.();
3175
- return;
3176
- }
3177
- resources?.previewFailures.set(args.workItemId, {
3178
- reason: 'exited',
3179
- portInUse: false,
3180
- });
3181
- this.emitChange();
3182
- });
3183
- this.resources
3184
- .get(args.sessionId)
3185
- ?.previewFailures.delete(args.workItemId);
3186
- this.emitChange();
3187
- trackFreshDevServerStarted(this.telemetry, {
3188
- sessionId: args.sessionId,
3189
- variantId: args.workItemId,
3190
- port,
3191
- });
3192
- trackPreviewReady(this.telemetry, {
3193
- sessionId: args.sessionId,
3194
- variantId: args.workItemId,
3195
- projectContextKind,
3196
- previewKind: 'dev_server',
3197
- hasPort: true,
3198
- port,
3199
- durationMs: Date.now() - previewStartedAt,
3200
- });
3201
- log.info(`Variant ${args.workItemId} dev server up on port ${port} (worktree ${args.record.worktreePath}; cmd: ${dev.cmd} ${dev.args.join(' ')})`);
3202
- this.touchSession(args.sessionId);
3203
- return port;
3204
- }
3205
- catch (err) {
3206
- if (!canContinueStart())
3207
- return undefined;
3208
- const devErr = (0, devServerError_1.isDevServerStartError)(err) ? err : undefined;
3209
- const portInUse = devErr?.portInUse ?? false;
3210
- const reason = devErr?.reason ?? 'unknown';
3211
- const missingDepsOutput = (0, devServerError_1.isMissingDependencyOutput)(devErr?.detail ?? '');
3212
- const missingDeps = missingDepsOutput &&
3213
- reason === 'exited' &&
3214
- !installRetried &&
3215
- lastDev !== undefined &&
3216
- canInstallWithoutTouchingHost(lastDev.cwd);
3217
- const willRetry = attempt < maxAttempts &&
3218
- (portInUse || missingDeps || (reason === 'exited' && attempt === 1));
3219
- scanFrom = (port ?? scanFrom) + 1;
3220
- log.warn(`Failed to start dev server for variant ${args.workItemId} on port ${port ?? 'unallocated'} ` +
3221
- `(attempt ${attempt}/${maxAttempts}, reason=${reason}, portInUse=${portInUse}, missingDeps=${missingDeps})` +
3222
- (willRetry
3223
- ? missingDeps
3224
- ? '; installing dependencies and retrying'
3225
- : '; retrying on a fresh port'
3226
- : '; live preview disabled for this variant'), err);
3227
- if (willRetry) {
3228
- if (missingDeps && lastDev) {
3229
- installRetried = true;
3230
- try {
3231
- await this.installDependencies(lastDev.cwd, {
3232
- workspaceRoot: args.record.worktreePath,
3233
- force: true,
3234
- signal: this.resources.get(args.sessionId)
3235
- ?.dependencyAbortController.signal,
3236
- });
3237
- }
3238
- catch (installErr) {
3239
- log.warn(`Dependency preparation failed for variant ${args.workItemId}`, installErr);
3240
- this.resources
3241
- .get(args.sessionId)
3242
- ?.previewFailures.set(args.workItemId, {
3243
- reason: 'dependency_install_failed',
3244
- portInUse: false,
3245
- });
3246
- trackPreviewFailed(this.telemetry, {
3247
- sessionId: args.sessionId,
3248
- variantId: args.workItemId,
3249
- projectContextKind,
3250
- previewKind: 'dev_server',
3251
- errorCode: 'DEPENDENCY_INSTALL_FAILED',
3252
- reason: 'dependency_install_failed',
3253
- portInUse: false,
3254
- durationMs: Date.now() - previewStartedAt,
3255
- });
3256
- this.emitChange();
3257
- return undefined;
3258
- }
3259
- }
3260
- continue;
3261
- }
3262
- this.resources
3263
- .get(args.sessionId)
3264
- ?.previewFailures.set(args.workItemId, {
3265
- reason,
3266
- portInUse,
3267
- });
3268
- let errorCode = 'DEV_SERVER_START_FAILED';
3269
- if (reason === 'dependency_install_failed')
3270
- errorCode = 'DEPENDENCY_INSTALL_FAILED';
3271
- if (reason === 'missing_dev_script')
3272
- errorCode = 'DEV_SCRIPT_MISSING';
3273
- trackFreshDevServerFailed(this.telemetry, {
3274
- sessionId: args.sessionId,
3275
- variantId: args.workItemId,
3276
- errorCode,
3277
- reason,
3278
- portInUse,
3279
- exitCode: devErr?.exitCode,
3280
- signal: devErr?.signal,
3281
- missingDeps: missingDepsOutput,
3282
- });
3283
- trackPreviewFailed(this.telemetry, {
3284
- sessionId: args.sessionId,
3285
- variantId: args.workItemId,
3286
- projectContextKind,
3287
- previewKind: 'dev_server',
3288
- errorCode,
3289
- reason,
3290
- portInUse,
3291
- durationMs: Date.now() - previewStartedAt,
3292
- exitCode: devErr?.exitCode,
3293
- signal: devErr?.signal,
3294
- missingDeps: missingDepsOutput,
3295
- });
3296
- this.emitChange();
3297
- return undefined;
3298
- }
3299
- finally {
3300
- if (port !== undefined)
3301
- this.worktrees.releasePort?.(port);
3302
- }
3303
- }
3304
- return undefined;
3305
- }
3306
2532
  async handleSucceededReport(args) {
3307
2533
  const { sessionId, workItemId } = args;
3308
2534
  const resources = this.resources.get(sessionId);
3309
- const record = resources?.worktrees.get(workItemId);
2535
+ const record = this.workspaceInventory.getWorktree(sessionId, workItemId);
3310
2536
  if (resources && !record) {
3311
- const staticPreview = parseStaticPreviewOutput(normalizeOutput(args.output));
2537
+ const staticPreview = (0, variantWorkspaceFiles_1.parseStaticPreviewOutput)((0, variantWorkspaceFiles_1.normalizeOutput)(args.output));
3312
2538
  if (staticPreview) {
3313
2539
  const input = this.store.getWorkItemInput(sessionId, workItemId);
3314
2540
  if (input.briefId) {
3315
2541
  const projectContext = this.store.getProjectContext(sessionId);
3316
2542
  const previewStartedAt = Date.now();
3317
- trackPreviewStarted(this.telemetry, {
2543
+ (0, variantsTelemetry_1.trackPreviewStarted)(this.telemetry, {
3318
2544
  sessionId,
3319
2545
  variantId: workItemId,
3320
2546
  projectContextKind: projectContext.kind,
3321
2547
  previewKind: 'static_artifact',
3322
2548
  });
3323
- const resolvedAssetBase = resolveStaticPreviewAssetBase({
2549
+ const resolvedAssetBase = (0, variantWorkspaceFiles_1.resolveStaticPreviewAssetBase)({
3324
2550
  assetBase: staticPreview.assetBase,
3325
2551
  projectContext,
3326
2552
  });
3327
2553
  const workspacePath = projectContext.kind === 'fresh'
3328
2554
  ? projectContext.workspacePath
3329
2555
  : undefined;
3330
- const assignedWorkspace = resources.workspaceManager.getAssignedWorkspace(workItemId);
2556
+ const assignedWorkspace = this.workspaceInventory.getAssignedWorkspace(sessionId, workItemId);
3331
2557
  const assignedArtifactBase = assignedWorkspace?.kind === 'artifact_directory'
3332
2558
  ? assignedWorkspace.rootPath
3333
2559
  : undefined;
@@ -3338,7 +2564,7 @@ class AgentVariantsOrchestrator {
3338
2564
  : undefined);
3339
2565
  if (!dir) {
3340
2566
  log.warn(`static preview for ${workItemId} has no workspace dir; skipping`);
3341
- trackPreviewFailed(this.telemetry, {
2567
+ (0, variantsTelemetry_1.trackPreviewFailed)(this.telemetry, {
3342
2568
  sessionId,
3343
2569
  variantId: workItemId,
3344
2570
  projectContextKind: projectContext.kind,
@@ -3367,10 +2593,10 @@ class AgentVariantsOrchestrator {
3367
2593
  assetBase: dir,
3368
2594
  hasAssets: Boolean(resolvedAssetBase || assignedArtifactBase),
3369
2595
  };
3370
- resources.staticPreviews.set(workItemId, record);
2596
+ this.workspaceInventory.setStaticPreview(sessionId, record);
3371
2597
  if (assignedWorkspace?.kind === 'artifact_directory' &&
3372
2598
  assignedWorkspace.rootPath !== record.assetBase) {
3373
- resources.workspaceManager.removeArtifactWorkspace(workItemId);
2599
+ this.workspaceInventory.removeArtifactWorkspace(sessionId, workItemId);
3374
2600
  }
3375
2601
  this.trackHistoryPersist(this.persistCompletedArtifactVariant({
3376
2602
  sessionId,
@@ -3379,12 +2605,12 @@ class AgentVariantsOrchestrator {
3379
2605
  log.warn(`persistCompletedArtifactVariant failed for ${sessionId}/${workItemId}`, err);
3380
2606
  }));
3381
2607
  const leasedAt = resources.leasedAt.get(workItemId);
3382
- trackStaticPreviewCompleted(this.telemetry, {
2608
+ (0, variantsTelemetry_1.trackStaticPreviewCompleted)(this.telemetry, {
3383
2609
  sessionId,
3384
2610
  variantId: workItemId,
3385
2611
  durationMs: leasedAt !== undefined ? Date.now() - leasedAt : null,
3386
2612
  });
3387
- trackPreviewReady(this.telemetry, {
2613
+ (0, variantsTelemetry_1.trackPreviewReady)(this.telemetry, {
3388
2614
  sessionId,
3389
2615
  variantId: workItemId,
3390
2616
  projectContextKind: projectContext.kind,
@@ -3401,7 +2627,7 @@ class AgentVariantsOrchestrator {
3401
2627
  log.warn(`No worktree record for ${sessionId}/${workItemId}; skipping diff capture`);
3402
2628
  return;
3403
2629
  }
3404
- if (resources.scaffoldBaseWorkItemId === workItemId) {
2630
+ if (this.workspaceInventory.isScaffoldBaseWorkItem(sessionId, workItemId)) {
3405
2631
  log.info(`scaffold_base ${workItemId} succeeded; awaiting dependent code-gen`);
3406
2632
  return;
3407
2633
  }
@@ -3410,7 +2636,7 @@ class AgentVariantsOrchestrator {
3410
2636
  try {
3411
2637
  record.diff =
3412
2638
  record.diff ?? (await this.worktrees.getDiff(record.worktreePath));
3413
- log.info(`Variant ${workItemId} diff captured (${countDiffFiles(record.diff)} files)`);
2639
+ log.info(`Variant ${workItemId} diff captured (${(0, variantWorkspaceFiles_1.countDiffFiles)(record.diff)} files)`);
3414
2640
  this.emitChange();
3415
2641
  }
3416
2642
  catch (err) {
@@ -3440,10 +2666,10 @@ class AgentVariantsOrchestrator {
3440
2666
  return;
3441
2667
  }
3442
2668
  }
3443
- await this.startVariantDevServer({
2669
+ await this.previewRuntime.startVariant({
3444
2670
  sessionId,
3445
2671
  workItemId,
3446
- record,
2672
+ worktreePath: record.worktreePath,
3447
2673
  isFresh,
3448
2674
  });
3449
2675
  }
@@ -3456,7 +2682,7 @@ class AgentVariantsOrchestrator {
3456
2682
  if (!input.variantId) {
3457
2683
  throw new errors_1.AgentVariantsError('MISSING_REQUIRED_INPUT', 'refine_variant work item is missing variantId');
3458
2684
  }
3459
- const parsed = parseStaticPreviewOutput(normalizeOutput(args.output));
2685
+ const parsed = (0, variantWorkspaceFiles_1.parseStaticPreviewOutput)((0, variantWorkspaceFiles_1.normalizeOutput)(args.output));
3460
2686
  if (!parsed) {
3461
2687
  throw new errors_1.AgentVariantsError('SCHEMA_VALIDATION_FAILED', 'refine_variant report_variant_complete requires output.html');
3462
2688
  }
@@ -3464,12 +2690,12 @@ class AgentVariantsOrchestrator {
3464
2690
  if (!targetInput.briefId) {
3465
2691
  throw new errors_1.AgentVariantsError('VARIANT_NOT_EDITABLE', 'Target static_preview variant is missing briefId');
3466
2692
  }
3467
- const resolvedAssetBase = resolveStaticPreviewAssetBase({
2693
+ const resolvedAssetBase = (0, variantWorkspaceFiles_1.resolveStaticPreviewAssetBase)({
3468
2694
  assetBase: parsed.assetBase,
3469
2695
  projectContext: this.store.getProjectContext(args.sessionId),
3470
2696
  });
3471
- const previousRecord = resources.staticPreviews.get(input.variantId);
3472
- const refinementWorkspace = resources.workspaceManager.getAssignedWorkspace(args.workItemId);
2697
+ const previousRecord = this.workspaceInventory.getStaticPreview(args.sessionId, input.variantId);
2698
+ const refinementWorkspace = this.workspaceInventory.getAssignedWorkspace(args.sessionId, args.workItemId);
3473
2699
  const assignedArtifactBase = refinementWorkspace?.kind === 'artifact_directory'
3474
2700
  ? refinementWorkspace.rootPath
3475
2701
  : undefined;
@@ -3488,7 +2714,7 @@ class AgentVariantsOrchestrator {
3488
2714
  ...(dir ? { assetBase: dir } : {}),
3489
2715
  });
3490
2716
  if (dir) {
3491
- resources.staticPreviews.set(input.variantId, {
2717
+ this.workspaceInventory.setStaticPreview(args.sessionId, {
3492
2718
  workItemId: input.variantId,
3493
2719
  briefId: targetInput.briefId,
3494
2720
  entryPath: 'index.html',
@@ -3500,7 +2726,8 @@ class AgentVariantsOrchestrator {
3500
2726
  }
3501
2727
  if (refinementWorkspace?.kind === 'artifact_directory' &&
3502
2728
  dir === refinementWorkspace.rootPath) {
3503
- resources.workspaceManager.promoteArtifactWorkspace({
2729
+ this.workspaceInventory.promoteArtifactWorkspace({
2730
+ sessionId: args.sessionId,
3504
2731
  sourceWorkItemId: args.workItemId,
3505
2732
  targetWorkItemId: input.variantId,
3506
2733
  });
@@ -3575,9 +2802,7 @@ class AgentVariantsOrchestrator {
3575
2802
  !saved.diff?.trim() &&
3576
2803
  !saved.files?.length))
3577
2804
  return false;
3578
- const resources = this.resources.get(sessionId);
3579
- const preview = resources?.staticPreviews.get(variant.workItemId) ??
3580
- this.getStaticPreviewByBriefId(sessionId, variant.briefId);
2805
+ const preview = this.workspaceInventory.getStaticPreview(sessionId, variant.workItemId) ?? this.getStaticPreviewByBriefId(sessionId, variant.briefId);
3581
2806
  if (preview) {
3582
2807
  const currentHtml = await fs_1.default.promises.readFile(path_1.default.join(preview.assetBase, preview.entryPath), 'utf8');
3583
2808
  const savedHtml = await this.variantHistory.readStaticPreview({
@@ -3588,7 +2813,7 @@ class AgentVariantsOrchestrator {
3588
2813
  if (currentHtml !== savedHtml)
3589
2814
  return false;
3590
2815
  }
3591
- const diff = resources?.worktrees.get(variant.workItemId)?.diff;
2816
+ const diff = this.workspaceInventory.getWorktree(sessionId, variant.workItemId)?.diff;
3592
2817
  if (diff && saved.diff !== diff)
3593
2818
  return false;
3594
2819
  }
@@ -3607,9 +2832,7 @@ class AgentVariantsOrchestrator {
3607
2832
  log.warn(`persistCompletedExistingVariant: resolveEnv failed for ${args.sessionId}`, err);
3608
2833
  return;
3609
2834
  }
3610
- const record = this.resources
3611
- .get(args.sessionId)
3612
- ?.worktrees.get(args.workItemId);
2835
+ const record = this.workspaceInventory.getWorktree(args.sessionId, args.workItemId);
3613
2836
  let sourceDir;
3614
2837
  const staticEntryPath = env.framework === 'static' ? env.entryPath : undefined;
3615
2838
  if (env.framework === 'static' && record) {
@@ -3630,23 +2853,24 @@ class AgentVariantsOrchestrator {
3630
2853
  catch (err) {
3631
2854
  log.warn(`persistCompletedExistingVariant: git root resolution failed for ${args.sessionId}`, err);
3632
2855
  }
2856
+ const runningPreview = this.previewRuntime.getRunningVariant(args.sessionId, args.workItemId);
3633
2857
  let serve;
3634
2858
  if (sourceDir && record && staticEntryPath) {
3635
2859
  const entryPath = path_1.default.relative(record.worktreePath, path_1.default.join(sourceDir, staticEntryPath));
3636
2860
  serve = { kind: 'static', entryPath };
3637
2861
  }
3638
- else if (record?.devCommand && record.port !== undefined) {
2862
+ else if (record && runningPreview) {
3639
2863
  serve = {
3640
2864
  kind: 'dev-server',
3641
- recipe: buildServeRecipeFromResolved(record.worktreePath, record.devCommand, record.port),
2865
+ recipe: (0, variantWorkspaceFiles_1.buildServeRecipeFromResolved)(record.worktreePath, runningPreview.command, runningPreview.port),
3642
2866
  };
3643
2867
  }
3644
2868
  else if (record) {
3645
2869
  try {
3646
- const dev = await this.resolveDevServerCommand(args.sessionId, record.worktreePath, RECIPE_PORT_SENTINEL, false);
2870
+ const dev = await this.previewRuntime.resolveCommand(args.sessionId, record.worktreePath, RECIPE_PORT_SENTINEL, false);
3647
2871
  serve = {
3648
2872
  kind: 'dev-server',
3649
- recipe: buildServeRecipeFromResolved(record.worktreePath, dev, RECIPE_PORT_SENTINEL),
2873
+ recipe: (0, variantWorkspaceFiles_1.buildServeRecipeFromResolved)(record.worktreePath, dev, RECIPE_PORT_SENTINEL),
3650
2874
  };
3651
2875
  }
3652
2876
  catch (err) {
@@ -3674,7 +2898,7 @@ class AgentVariantsOrchestrator {
3674
2898
  preview: sourceDir && staticEntryPath
3675
2899
  ? { kind: 'static', entryPath: staticEntryPath }
3676
2900
  : { kind: 'none', reason: 'diff_only' },
3677
- changedFilesCount: countDiffFiles(args.diff),
2901
+ changedFilesCount: (0, variantWorkspaceFiles_1.countDiffFiles)(args.diff),
3678
2902
  projectKind: 'existing',
3679
2903
  snapshotDir,
3680
2904
  baseDir,
@@ -3699,7 +2923,7 @@ class AgentVariantsOrchestrator {
3699
2923
  if (env.framework !== 'static')
3700
2924
  return 'not_static';
3701
2925
  const previewStartedAt = Date.now();
3702
- trackPreviewStarted(this.telemetry, {
2926
+ (0, variantsTelemetry_1.trackPreviewStarted)(this.telemetry, {
3703
2927
  sessionId,
3704
2928
  variantId: workItemId,
3705
2929
  projectContextKind: 'existing',
@@ -3725,7 +2949,7 @@ class AgentVariantsOrchestrator {
3725
2949
  }
3726
2950
  catch (err) {
3727
2951
  log.warn(`registerExistingStaticPreview: no entry HTML at ${entryFile} for ${sessionId}/${workItemId}; preview unavailable`, err);
3728
- trackPreviewFailed(this.telemetry, {
2952
+ (0, variantsTelemetry_1.trackPreviewFailed)(this.telemetry, {
3729
2953
  sessionId,
3730
2954
  variantId: workItemId,
3731
2955
  projectContextKind: 'existing',
@@ -3734,7 +2958,7 @@ class AgentVariantsOrchestrator {
3734
2958
  reason: 'missing_static_entry',
3735
2959
  durationMs: Date.now() - previewStartedAt,
3736
2960
  });
3737
- resources.previewFailures.set(workItemId, {
2961
+ this.previewRuntime.setFailure(sessionId, workItemId, {
3738
2962
  reason: 'missing_static_entry',
3739
2963
  portInUse: false,
3740
2964
  });
@@ -3754,7 +2978,7 @@ class AgentVariantsOrchestrator {
3754
2978
  assetFallbackBase = hostRealRoot;
3755
2979
  }
3756
2980
  catch { }
3757
- resources.staticPreviews.set(workItemId, {
2981
+ this.workspaceInventory.setStaticPreview(sessionId, {
3758
2982
  workItemId,
3759
2983
  briefId: input.briefId,
3760
2984
  entryPath: env.entryPath,
@@ -3762,9 +2986,9 @@ class AgentVariantsOrchestrator {
3762
2986
  hasAssets: true,
3763
2987
  ...(assetFallbackBase ? { assetFallbackBase } : {}),
3764
2988
  });
3765
- resources.previewFailures.delete(workItemId);
2989
+ this.previewRuntime.clearFailure(sessionId, workItemId);
3766
2990
  log.info(`Variant ${workItemId} served as a static preview (existing/static, no dev server; root ${projectCwd})`);
3767
- trackPreviewReady(this.telemetry, {
2991
+ (0, variantsTelemetry_1.trackPreviewReady)(this.telemetry, {
3768
2992
  sessionId,
3769
2993
  variantId: workItemId,
3770
2994
  projectContextKind: 'existing',
@@ -3787,7 +3011,7 @@ class AgentVariantsOrchestrator {
3787
3011
  const env = await this.resolveEnv(args.sessionId);
3788
3012
  const entryPath = env.framework === 'static' ? env.entryPath : 'index.html';
3789
3013
  const entryFile = path_1.default.resolve(args.sourceDir, entryPath);
3790
- if (!isStrictlyInside(entryFile, args.sourceDir)) {
3014
+ if (!(0, variantWorkspaceFiles_1.isStrictlyInside)(entryFile, args.sourceDir)) {
3791
3015
  throw new errors_1.AgentVariantsError('RUNTIME_VALIDATION_FAILED', 'Artifact entry must be inside its project directory.');
3792
3016
  }
3793
3017
  const isEntryMapped = entryFile !== path_1.default.join(args.sourceDir, 'index.html');
@@ -3835,10 +3059,9 @@ class AgentVariantsOrchestrator {
3835
3059
  : [historyProjectPath];
3836
3060
  const input = this.store.getWorkItemInput(args.sessionId, args.workItemId);
3837
3061
  const sessionPrompt = this.store.getPrompt(args.sessionId);
3838
- const designArtifact = resolveDesignArtifact(input.designContextEntry);
3839
- const resources = this.resources.get(args.sessionId);
3840
- const worktreeRecord = resources?.worktrees.get(args.workItemId);
3841
- const staticPreview = resources?.staticPreviews.get(args.workItemId);
3062
+ const designArtifact = (0, designContextArtifacts_1.resolveDesignArtifact)(input.designContextEntry);
3063
+ const worktreeRecord = this.workspaceInventory.getWorktree(args.sessionId, args.workItemId);
3064
+ const staticPreview = this.workspaceInventory.getStaticPreview(args.sessionId, args.workItemId);
3842
3065
  let sourceDir = null;
3843
3066
  let tmpStagingDir = null;
3844
3067
  if (worktreeRecord && fs_1.default.existsSync(worktreeRecord.worktreePath)) {
@@ -3846,7 +3069,7 @@ class AgentVariantsOrchestrator {
3846
3069
  }
3847
3070
  else if (staticPreview) {
3848
3071
  tmpStagingDir = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), `rivet-variant-${args.workItemId}-`));
3849
- copyStaticPreviewDirectory(staticPreview.assetBase, tmpStagingDir);
3072
+ (0, variantWorkspaceFiles_1.copyStaticPreviewDirectory)(staticPreview.assetBase, tmpStagingDir);
3850
3073
  let stagedHtml = '';
3851
3074
  try {
3852
3075
  stagedHtml = fs_1.default.readFileSync(path_1.default.join(staticPreview.assetBase, 'index.html'), 'utf8');
@@ -3864,7 +3087,8 @@ class AgentVariantsOrchestrator {
3864
3087
  log.warn(`persistCompletedArtifactVariant: no source for ${args.workItemId} (session ${args.sessionId}); skipping`);
3865
3088
  return;
3866
3089
  }
3867
- const changedFilesCount = staticPreview ? 1 : countWorktreeFiles(sourceDir);
3090
+ const changedFilesCount = staticPreview ? 1 : (0, variantWorkspaceFiles_1.countWorktreeFiles)(sourceDir);
3091
+ const runningPreview = this.previewRuntime.getRunningVariant(args.sessionId, args.workItemId);
3868
3092
  const preview = staticPreview
3869
3093
  ? { kind: 'static', entryPath: 'index.html' }
3870
3094
  : {
@@ -3877,10 +3101,10 @@ class AgentVariantsOrchestrator {
3877
3101
  };
3878
3102
  const serve = staticPreview
3879
3103
  ? { kind: 'static', entryPath: 'index.html' }
3880
- : worktreeRecord?.devCommand && worktreeRecord.port !== undefined
3104
+ : worktreeRecord && runningPreview
3881
3105
  ? {
3882
3106
  kind: 'dev-server',
3883
- recipe: buildServeRecipeFromResolved(worktreeRecord.worktreePath, worktreeRecord.devCommand, worktreeRecord.port),
3107
+ recipe: (0, variantWorkspaceFiles_1.buildServeRecipeFromResolved)(worktreeRecord.worktreePath, runningPreview.command, runningPreview.port),
3884
3108
  }
3885
3109
  : {
3886
3110
  kind: 'dev-server',
@@ -3927,7 +3151,7 @@ class AgentVariantsOrchestrator {
3927
3151
  ...(diff !== undefined ? { diff } : {}),
3928
3152
  sourceDir,
3929
3153
  preview,
3930
- changedFilesCount: diff === undefined ? changedFilesCount : countDiffFiles(diff),
3154
+ changedFilesCount: diff === undefined ? changedFilesCount : (0, variantWorkspaceFiles_1.countDiffFiles)(diff),
3931
3155
  projectKind: projectContext.kind,
3932
3156
  designMarkdown: designArtifact?.markdown,
3933
3157
  designSource: designArtifact?.source,
@@ -4080,109 +3304,6 @@ class AgentVariantsOrchestrator {
4080
3304
  return undefined;
4081
3305
  }
4082
3306
  }
4083
- async resolveDevServerStartPort(sessionId, isFresh) {
4084
- if (isFresh)
4085
- return FRESH_DEV_SERVER_DEFAULT_PORT;
4086
- try {
4087
- const env = await this.resolveEnv(sessionId);
4088
- return env.framework === 'static' ? 3000 : (env.defaultPort ?? 3000);
4089
- }
4090
- catch {
4091
- return 3000;
4092
- }
4093
- }
4094
- async resolveDevServer(sessionId, worktreePath, port, isFresh) {
4095
- const resolved = await this.resolveDevServerCommand(sessionId, worktreePath, port, isFresh);
4096
- await this.ensureProjectDependencies({
4097
- signal: this.resources.get(sessionId)?.dependencyAbortController.signal,
4098
- cwd: resolved.cwd,
4099
- workspaceRoot: worktreePath,
4100
- });
4101
- return resolved;
4102
- }
4103
- async resolveDevServerCommand(sessionId, worktreePath, port, isFresh) {
4104
- if (isFresh) {
4105
- return {
4106
- cwd: worktreePath,
4107
- cmd: 'npm',
4108
- args: [
4109
- 'run',
4110
- 'dev',
4111
- '--',
4112
- '--port',
4113
- String(port),
4114
- '--host',
4115
- FRESH_DEV_SERVER_HOST,
4116
- ],
4117
- env: { PORT: String(port) },
4118
- packageManager: 'npm',
4119
- };
4120
- }
4121
- const env = await this.resolveEnv(sessionId);
4122
- if (env.framework === 'static') {
4123
- throw new Error('Static projects do not have a dev server command');
4124
- }
4125
- const cwd = await this.resolveExistingProjectCwd(sessionId, worktreePath, env);
4126
- if (env.devCommand) {
4127
- assertDevScriptExists(cwd, env.devCommand);
4128
- }
4129
- const detected = fs_1.default.existsSync(path_1.default.join(cwd, 'package.json'))
4130
- ? await (0, ProjectPackageManager_1.resolveProjectPackageManager)({
4131
- cwd,
4132
- workspaceRoot: worktreePath,
4133
- }).catch((error) => {
4134
- throw new devServerError_1.DevServerStartError({
4135
- message: error instanceof Error ? error.message : String(error),
4136
- detail: '',
4137
- reason: 'dependency_install_failed',
4138
- portInUse: false,
4139
- });
4140
- })
4141
- : null;
4142
- const packageManager = detected?.name ?? env.packageManager;
4143
- if (packageManager !== env.packageManager) {
4144
- return {
4145
- cwd,
4146
- packageManager,
4147
- ...(0, devServerCommand_1.buildDevServerCommand)(env.framework, packageManager, env.devCommand, port),
4148
- };
4149
- }
4150
- if (env.buildDevCommand) {
4151
- return { cwd, packageManager, ...env.buildDevCommand(port) };
4152
- }
4153
- return {
4154
- cwd,
4155
- cmd: env.packageManager,
4156
- args: [env.devCommand, '--port', String(port)],
4157
- env: { PORT: String(port) },
4158
- packageManager,
4159
- };
4160
- }
4161
- async ensureProjectDependencies(args) {
4162
- if (!fs_1.default.existsSync(path_1.default.join(args.cwd, 'package.json')))
4163
- return;
4164
- await this.installDependencies(args.cwd, {
4165
- workspaceRoot: args.workspaceRoot,
4166
- signal: args.signal,
4167
- });
4168
- }
4169
- async resolveExistingProjectCwd(sessionId, worktreePath, env) {
4170
- const fallback = await this.worktrees.getProjectCwdInWorktree(worktreePath);
4171
- try {
4172
- const projectContext = this.store.getProjectContext(sessionId);
4173
- if (projectContext.kind !== 'existing' || !projectContext.projectPath) {
4174
- return fallback;
4175
- }
4176
- const rel = path_1.default.relative(path_1.default.resolve(env.projectPath), path_1.default.resolve(projectContext.projectPath));
4177
- if (!rel || rel.startsWith('..') || path_1.default.isAbsolute(rel)) {
4178
- return fallback;
4179
- }
4180
- return path_1.default.join(worktreePath, rel);
4181
- }
4182
- catch {
4183
- return fallback;
4184
- }
4185
- }
4186
3307
  preserveUnchosenVariants(args) {
4187
3308
  const resources = this.resources.get(args.sessionId);
4188
3309
  const runnablePaths = new Map();
@@ -4216,7 +3337,7 @@ class AgentVariantsOrchestrator {
4216
3337
  });
4217
3338
  continue;
4218
3339
  }
4219
- const record = resources.worktrees.get(variant.workItemId);
3340
+ const record = this.workspaceInventory.getWorktree(args.sessionId, variant.workItemId);
4220
3341
  if (!record) {
4221
3342
  manifestEntries.push({
4222
3343
  variantId: variant.workItemId,
@@ -4281,7 +3402,7 @@ class AgentVariantsOrchestrator {
4281
3402
  catch (err) {
4282
3403
  log.warn(`Writing variants history manifest failed for ${historyDir}`, err);
4283
3404
  }
4284
- resources.vitePreservedSiblings = true;
3405
+ this.workspaceInventory.preserveViteSibling(args.sessionId);
4285
3406
  return runnablePaths;
4286
3407
  }
4287
3408
  async renameWorktreeIntoDestination(sourceWorktreePath, destinationPath) {
@@ -4357,218 +3478,31 @@ class AgentVariantsOrchestrator {
4357
3478
  log.warn(`Initial git history setup at ${destinationPath} failed`, err);
4358
3479
  }
4359
3480
  }
4360
- async stopAllDevServersForSession(resources) {
4361
- this.clearActivePreviewIdleTimer(resources);
4362
- const stops = [];
4363
- for (const record of resources.worktrees.values()) {
4364
- if (record.pendingAutoRestart) {
4365
- clearTimeout(record.pendingAutoRestart);
4366
- record.pendingAutoRestart = undefined;
4367
- }
4368
- if (!record.devServerProcess)
4369
- continue;
4370
- const proc = record.devServerProcess;
4371
- stops.push(this.worktrees
4372
- .stopDevServer(proc)
4373
- .catch((err) => log.warn(`stopDevServer pre-commit failed for ${resources.sessionId}/${record.workItemId}`, err)));
4374
- record.devServerProcess = undefined;
4375
- record.port = undefined;
4376
- }
4377
- await Promise.all(stops);
4378
- return stops.length;
4379
- }
4380
- scheduleActivePreviewIdleTimer(resources) {
4381
- if (!Number.isFinite(this.activeVariantServerIdleTtlMs) ||
4382
- this.activeVariantServerIdleTtlMs <= 0) {
4383
- return;
4384
- }
4385
- this.clearActivePreviewIdleTimer(resources);
4386
- resources.activePreviewIdleTimer = setTimeout(() => {
4387
- resources.activePreviewIdleTimer = undefined;
4388
- void this.stopIdleVariantDevServers(resources.sessionId);
4389
- }, this.activeVariantServerIdleTtlMs);
4390
- resources.activePreviewIdleTimer.unref?.();
4391
- }
4392
- clearActivePreviewIdleTimer(resources) {
4393
- if (!resources.activePreviewIdleTimer)
4394
- return;
4395
- clearTimeout(resources.activePreviewIdleTimer);
4396
- resources.activePreviewIdleTimer = undefined;
4397
- }
4398
- async stopIdleVariantDevServers(sessionId) {
4399
- const resources = this.resources.get(sessionId);
4400
- if (!resources || resources.cleanupStarted)
4401
- return;
4402
- if (this.uiClientCount > 0) {
4403
- this.scheduleActivePreviewIdleTimer(resources);
4404
- return;
4405
- }
4406
- if (resources.activePreviewIdleStop) {
4407
- await resources.activePreviewIdleStop;
4408
- return;
4409
- }
4410
- const stop = (async () => {
4411
- const expiringRecords = Array.from(resources.worktrees.values()).filter((record) => Boolean(record.devServerProcess) ||
4412
- Boolean(record.pendingAutoRestart));
4413
- const expiringWorkItemIds = expiringRecords.map((record) => record.workItemId);
4414
- const pendingRestartCount = expiringRecords.filter((record) => Boolean(record.pendingAutoRestart)).length;
4415
- const stoppedCount = await this.stopAllDevServersForSession(resources);
4416
- if (expiringWorkItemIds.length === 0)
4417
- return;
4418
- for (const workItemId of expiringWorkItemIds) {
4419
- resources.previewFailures.set(workItemId, {
4420
- reason: 'idle_timeout',
4421
- portInUse: false,
4422
- });
4423
- }
4424
- log.info(`Reclaimed ${expiringWorkItemIds.length} idle variant preview(s) for session ${sessionId} ` +
4425
- `(${stoppedCount} running, ${pendingRestartCount} restarting)`);
4426
- try {
4427
- this.switchPreviewPort?.(null);
4428
- }
4429
- catch (err) {
4430
- log.warn(`switchPreviewPort(null) after idle variant server cleanup failed for ${sessionId}`, err);
4431
- }
4432
- this.emitChange();
4433
- })().finally(() => {
4434
- if (resources.activePreviewIdleStop === stop) {
4435
- resources.activePreviewIdleStop = undefined;
4436
- }
4437
- });
4438
- resources.activePreviewIdleStop = stop;
4439
- await stop;
4440
- }
4441
- async startCommittedDevServer(args) {
4442
- const mode = args.mode ?? 'vite_app';
4443
- try {
4444
- const port = await this.worktrees.getFreePort();
4445
- const proc = mode === 'static_preview'
4446
- ? await this.startStaticPreviewServerImpl({
4447
- rootPath: args.destinationPath,
4448
- port,
4449
- host: FRESH_DEV_SERVER_HOST,
4450
- })
4451
- : await this.worktrees.startDevServer(args.destinationPath, port, 'npm', [
4452
- 'run',
4453
- 'dev',
4454
- '--',
4455
- '--port',
4456
- String(port),
4457
- '--host',
4458
- FRESH_DEV_SERVER_HOST,
4459
- ], { PORT: String(port) });
4460
- args.resources.committedDevServer = {
4461
- proc,
4462
- port,
4463
- path: args.destinationPath,
4464
- };
4465
- try {
4466
- this.setCommittedDevServerHealth?.(port);
4467
- }
4468
- catch (err) {
4469
- log.warn(`setCommittedDevServerHealth(${port}) after committed dev server start failed`, err);
4470
- }
4471
- try {
4472
- this.switchPreviewPort?.(port);
4473
- }
4474
- catch (err) {
4475
- log.warn(`switchPreviewPort(${port}) after committed dev server start failed`, err);
4476
- }
4477
- log.info(`Committed ${mode} dev server up at ${args.destinationPath} on port ${port}`);
4478
- return port;
4479
- }
4480
- catch (err) {
4481
- log.warn(`Failed to start committed ${mode} dev server at ${args.destinationPath} — iframe may show "preview disconnected" until user runs the dev server manually`, err);
4482
- return undefined;
4483
- }
4484
- }
4485
- async stopLingeringCommittedDevServers() {
4486
- const entries = [...this.lingeringCommittedDevServers.entries()];
4487
- this.lingeringCommittedDevServers.clear();
4488
- if (entries.length > 0) {
4489
- try {
4490
- this.setCommittedDevServerHealth?.(null);
4491
- }
4492
- catch (err) {
4493
- log.warn('setCommittedDevServerHealth(null) on lingering teardown failed', err);
4494
- }
4495
- }
4496
- await Promise.all(entries.map(async ([sessionId, entry]) => {
4497
- try {
4498
- await this.worktrees.stopDevServer(entry.proc);
4499
- }
4500
- catch (err) {
4501
- log.warn(`stopDevServer for lingering committed server (session ${sessionId}, port ${entry.port}) failed`, err);
4502
- }
4503
- }));
4504
- }
4505
3481
  async teardownSession(sessionId, reason) {
4506
3482
  const resources = this.resources.get(sessionId);
4507
3483
  if (!resources)
4508
3484
  return;
4509
- if (resources.cleanupStarted)
3485
+ if (!this.workspaceInventory.beginCleanup(sessionId))
4510
3486
  return;
4511
- resources.cleanupStarted = true;
4512
- this.clearActivePreviewIdleTimer(resources);
4513
3487
  log.info(`Tearing down session ${sessionId} (reason: ${reason})`);
4514
- resources.dependencyAbortController.abort();
4515
- await Promise.all([...resources.worktrees.values()].map((record) => DependencyPreparationService_1.previewDependencies.settle(record.worktreePath)));
3488
+ await Promise.all(this.workspaceInventory
3489
+ .getWorktrees(sessionId)
3490
+ .map((record) => DependencyPreparationService_1.previewDependencies.settle(record.worktreePath)));
4516
3491
  await Promise.race([
4517
3492
  this.settleHistoryPersists(),
4518
3493
  new Promise((resolve) => setTimeout(resolve, SETTLE_PERSISTS_TEARDOWN_TIMEOUT_MS)),
4519
3494
  ]);
4520
- const stops = [...resources.worktrees.values()]
4521
- .filter((r) => r.devServerProcess)
4522
- .map((r) => this.worktrees
4523
- .stopDevServer(r.devServerProcess)
4524
- .catch((err) => log.warn(`stopDevServer failed for ${sessionId}/${r.workItemId}`, err)));
4525
- await Promise.all(stops);
4526
- if (resources.committedDevServer && reason === 'committed') {
4527
- this.lingeringCommittedDevServers.set(sessionId, resources.committedDevServer);
4528
- }
4529
- else if (resources.committedDevServer) {
4530
- try {
4531
- await this.worktrees.stopDevServer(resources.committedDevServer.proc);
4532
- }
4533
- catch (err) {
4534
- log.warn(`stopDevServer for committed dev server (session ${sessionId}) failed`, err);
4535
- }
4536
- }
4537
- if (!resources.vitePreservedSiblings) {
4538
- try {
4539
- await this.worktrees.cleanupSession(sessionId, resources.freshDestinationParent);
4540
- }
4541
- catch (err) {
4542
- log.warn(`cleanupSession (worktree removal) failed for ${sessionId}`, err);
4543
- }
4544
- }
4545
- const ownedArtifactRoots = new Set(resources.workspaceManager.getOwnedArtifactRoots());
4546
- resources.workspaceManager.removeOwnedArtifactWorkspaces();
4547
- const staticDirectories = new Set();
4548
- if (reason !== 'committed') {
4549
- let isFresh = false;
4550
- try {
4551
- isFresh = this.store.getProjectContext(sessionId).kind === 'fresh';
4552
- }
4553
- catch {
4554
- isFresh = false;
4555
- }
4556
- if (isFresh) {
4557
- for (const preview of resources.staticPreviews.values()) {
4558
- if (!ownedArtifactRoots.has(preview.assetBase)) {
4559
- staticDirectories.add(preview.assetBase);
4560
- }
4561
- }
4562
- }
4563
- }
4564
- for (const staticDirectory of staticDirectories) {
4565
- try {
4566
- fs_1.default.rmSync(staticDirectory, { recursive: true, force: true });
4567
- }
4568
- catch (err) {
4569
- log.warn(`failed to remove static preview dir ${staticDirectory} for ${sessionId}`, err);
4570
- }
3495
+ await this.previewRuntime.closeSession(sessionId, reason);
3496
+ let isFresh = false;
3497
+ try {
3498
+ isFresh = this.store.getProjectContext(sessionId).kind === 'fresh';
4571
3499
  }
3500
+ catch { }
3501
+ await this.workspaceInventory.disposeAssignments({
3502
+ sessionId,
3503
+ isFresh,
3504
+ isCommitted: reason === 'committed',
3505
+ });
4572
3506
  this.resources.delete(sessionId);
4573
3507
  }
4574
3508
  ensureResources(sessionId) {
@@ -4576,12 +3510,6 @@ class AgentVariantsOrchestrator {
4576
3510
  if (!r) {
4577
3511
  r = {
4578
3512
  sessionId,
4579
- worktrees: new Map(),
4580
- staticPreviews: new Map(),
4581
- workspaceManager: new VariantWorkspaceManager_1.VariantWorkspaceManager(),
4582
- contextAssets: new Map(),
4583
- cleanupStarted: false,
4584
- dependencyAbortController: new AbortController(),
4585
3513
  committedVariantIds: new Set(),
4586
3514
  startedAt: Date.now(),
4587
3515
  nextRoundIndex: 0,
@@ -4590,964 +3518,13 @@ class AgentVariantsOrchestrator {
4590
3518
  qaResults: new Map(),
4591
3519
  qaRegenRetried: new Set(),
4592
3520
  repairCompletionResults: new Map(),
4593
- previewFailures: new Map(),
4594
- vitePreservedSiblings: false,
4595
3521
  };
4596
3522
  this.resources.set(sessionId, r);
3523
+ this.workspaceInventory.openSession(sessionId);
3524
+ this.previewRuntime.openSession(sessionId);
4597
3525
  }
4598
3526
  return r;
4599
3527
  }
4600
3528
  }
4601
3529
  exports.AgentVariantsOrchestrator = AgentVariantsOrchestrator;
4602
- function estimateOutputBytes(output) {
4603
- if (output === undefined || output === null)
4604
- return 0;
4605
- try {
4606
- return typeof output === 'string'
4607
- ? output.length
4608
- : JSON.stringify(output).length;
4609
- }
4610
- catch {
4611
- return 0;
4612
- }
4613
- }
4614
- function normalizeOutput(output) {
4615
- if (typeof output === 'string') {
4616
- try {
4617
- return JSON.parse(output);
4618
- }
4619
- catch {
4620
- return output;
4621
- }
4622
- }
4623
- return output;
4624
- }
4625
- function isStrictlyInside(child, parent) {
4626
- const c = path_1.default.resolve(child);
4627
- const p = path_1.default.resolve(parent);
4628
- return c !== p && c.startsWith(p + path_1.default.sep);
4629
- }
4630
- function resolveAssetWithinBase(base, requestedPath) {
4631
- const target = path_1.default.resolve(base, '.' + path_1.default.sep + requestedPath);
4632
- if (target !== base && !target.startsWith(base + path_1.default.sep)) {
4633
- return undefined;
4634
- }
4635
- let realTarget;
4636
- try {
4637
- realTarget = fs_1.default.realpathSync(target);
4638
- }
4639
- catch {
4640
- return undefined;
4641
- }
4642
- if (realTarget !== base && !realTarget.startsWith(base + path_1.default.sep)) {
4643
- return undefined;
4644
- }
4645
- try {
4646
- const stat = fs_1.default.statSync(realTarget);
4647
- if (!stat.isFile())
4648
- return undefined;
4649
- if (stat.nlink !== 1)
4650
- return undefined;
4651
- return realTarget;
4652
- }
4653
- catch {
4654
- return undefined;
4655
- }
4656
- }
4657
- function resolveStaticPreviewAssetBase(args) {
4658
- if (!args.assetBase)
4659
- return undefined;
4660
- if (args.projectContext.kind !== 'fresh')
4661
- return undefined;
4662
- let workspace;
4663
- let resolved;
4664
- try {
4665
- const wsStat = fs_1.default.lstatSync(args.projectContext.workspacePath);
4666
- if (wsStat.isSymbolicLink()) {
4667
- log.warn(`Static preview workspacePath ${args.projectContext.workspacePath} is a symlink — refusing assetBase`);
4668
- return undefined;
4669
- }
4670
- const realWorkspaceRoot = fs_1.default.realpathSync(args.projectContext.workspaceRoot);
4671
- workspace = fs_1.default.realpathSync(args.projectContext.workspacePath);
4672
- if (!workspace.startsWith(realWorkspaceRoot + path_1.default.sep)) {
4673
- log.warn(`Static preview workspacePath ${workspace} is not a strict descendant of workspaceRoot ${realWorkspaceRoot} — refusing assetBase`);
4674
- return undefined;
4675
- }
4676
- const candidate = path_1.default.isAbsolute(args.assetBase)
4677
- ? args.assetBase
4678
- : path_1.default.join(workspace, args.assetBase);
4679
- resolved = fs_1.default.realpathSync(candidate);
4680
- }
4681
- catch {
4682
- log.warn(`Static preview assetBase ${args.assetBase} could not be resolved — ignoring`);
4683
- return undefined;
4684
- }
4685
- if (resolved !== workspace && !resolved.startsWith(workspace + path_1.default.sep)) {
4686
- log.warn(`Static preview assetBase ${args.assetBase} escapes workspace ${workspace} — ignoring`);
4687
- return undefined;
4688
- }
4689
- try {
4690
- const stat = fs_1.default.statSync(resolved);
4691
- if (!stat.isDirectory()) {
4692
- log.warn(`Static preview assetBase ${resolved} is not a directory — ignoring`);
4693
- return undefined;
4694
- }
4695
- return resolved;
4696
- }
4697
- catch {
4698
- log.warn(`Static preview assetBase ${resolved} does not exist — ignoring`);
4699
- return undefined;
4700
- }
4701
- }
4702
- function parseStaticPreviewOutput(output) {
4703
- if (!output || typeof output !== 'object')
4704
- return null;
4705
- const html = output.html;
4706
- if (typeof html !== 'string' || html.trim().length === 0)
4707
- return null;
4708
- const css = output.css;
4709
- const js = output.js;
4710
- const assetBase = output.assetBase;
4711
- return {
4712
- html: buildStaticPreviewDocument({
4713
- html,
4714
- css: typeof css === 'string' ? css : undefined,
4715
- js: typeof js === 'string' ? js : undefined,
4716
- }),
4717
- ...(typeof assetBase === 'string' && assetBase.length > 0
4718
- ? { assetBase }
4719
- : {}),
4720
- };
4721
- }
4722
- function buildStaticPreviewDocument(input) {
4723
- const style = input.css ? `<style>\n${input.css}\n</style>` : '';
4724
- const script = input.js ? `<script>\n${input.js}\n</script>` : '';
4725
- if (/<!doctype html>|<html[\s>]/i.test(input.html)) {
4726
- let doc = input.html;
4727
- if (style) {
4728
- if (/<\/head>/i.test(doc)) {
4729
- doc = doc.replace(/<\/head>/i, () => `${style}\n</head>`);
4730
- }
4731
- else {
4732
- doc += `\n${style}`;
4733
- }
4734
- }
4735
- if (script) {
4736
- if (/<\/body>/i.test(doc)) {
4737
- doc = doc.replace(/<\/body>/i, () => `${script}\n</body>`);
4738
- }
4739
- else {
4740
- doc += `\n${script}`;
4741
- }
4742
- }
4743
- return doc;
4744
- }
4745
- return `<!doctype html>
4746
- <html lang="en">
4747
- <head>
4748
- <meta charset="UTF-8" />
4749
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
4750
- ${style}
4751
- </head>
4752
- <body>
4753
- ${input.html}
4754
- ${script}
4755
- </body>
4756
- </html>`;
4757
- }
4758
- function countDiffFiles(diff) {
4759
- return (diff.match(/^diff --git /gm) ?? []).length;
4760
- }
4761
- function countWorktreeFiles(destinationPath) {
4762
- let count = 0;
4763
- const walk = (dir) => {
4764
- let entries;
4765
- try {
4766
- entries = fs_1.default.readdirSync(dir, { withFileTypes: true });
4767
- }
4768
- catch {
4769
- return;
4770
- }
4771
- for (const entry of entries) {
4772
- if (MATERIALIZE_EXCLUDE.has(entry.name))
4773
- continue;
4774
- const full = path_1.default.join(dir, entry.name);
4775
- if (entry.isDirectory())
4776
- walk(full);
4777
- else if (entry.isFile())
4778
- count += 1;
4779
- }
4780
- };
4781
- walk(destinationPath);
4782
- return count;
4783
- }
4784
- const enrichDesignSource = (design) => {
4785
- if (!design || design.kind !== 'slug')
4786
- return design;
4787
- return {
4788
- ...design,
4789
- displayName: (0, designCatalog_1.getDesignSystemBySlug)(design.slug)?.name ?? design.slug,
4790
- };
4791
- };
4792
- const toActiveProjectContext = (projectContext) => {
4793
- const sourceCount = projectContext.variantContext?.userContext.filter((source) => source.transport !== 'prompt').length ?? 0;
4794
- const hasSourceContextSummary = Boolean(sourceCount > 0 ||
4795
- (projectContext.variantContext?.rivetMetadata.length ?? 0) > 0);
4796
- const activeSourceContext = hasSourceContextSummary
4797
- ? {
4798
- sourceCount,
4799
- isSourceGrounded: Boolean(sourceCount > 0 ||
4800
- projectContext.designContext?.length ||
4801
- projectContext.variantContext?.rivetMetadata.length),
4802
- }
4803
- : undefined;
4804
- if (projectContext.kind === 'existing') {
4805
- return {
4806
- kind: 'existing',
4807
- designContext: projectContext.designContext?.map((entry) => entry.kind === 'slug'
4808
- ? { kind: 'slug', slug: entry.slug }
4809
- : { kind: 'markdown', label: entry.label }),
4810
- ...(activeSourceContext ? { sourceContext: activeSourceContext } : {}),
4811
- };
4812
- }
4813
- return {
4814
- kind: 'fresh',
4815
- workspacePath: projectContext.workspacePath,
4816
- workspaceRoot: projectContext.workspaceRoot,
4817
- framework: projectContext.framework,
4818
- designContext: projectContext.designContext?.map((entry) => entry.kind === 'slug'
4819
- ? { kind: 'slug', slug: entry.slug }
4820
- : { kind: 'markdown', label: entry.label }),
4821
- ...(activeSourceContext ? { sourceContext: activeSourceContext } : {}),
4822
- ...(projectContext.executionPlan
4823
- ? {
4824
- executionPlan: {
4825
- mode: projectContext.executionPlan.mode,
4826
- confidence: projectContext.executionPlan.confidence,
4827
- reason: projectContext.executionPlan.reason,
4828
- assetCount: projectContext.executionPlan.assetPlan?.length ?? 0,
4829
- runtimeRequirementCount: projectContext.executionPlan.runtimeRequirements?.length ?? 0,
4830
- },
4831
- }
4832
- : {}),
4833
- };
4834
- };
4835
- const buildSessionArtifacts = (sessionId, projectContext) => {
4836
- const artifacts = buildResolvedDesignContextArtifacts(projectContext);
4837
- return artifacts.map(({ content: _content, ...artifact }) => ({
4838
- ...artifact,
4839
- rawUrl: buildDesignContextRawUrl(sessionId, artifact.id),
4840
- viewUrl: buildDesignContextViewUrl(sessionId, artifact.id),
4841
- }));
4842
- };
4843
- const findDesignContextArtifact = (projectContext, artifactId) => {
4844
- return buildResolvedDesignContextArtifacts(projectContext).find((artifact) => artifact.id === artifactId);
4845
- };
4846
- const buildResolvedDesignContextArtifacts = (projectContext) => {
4847
- const designContext = projectContext.designContext;
4848
- if (!designContext || designContext.length === 0)
4849
- return [];
4850
- const artifactsByContent = new Map();
4851
- designContext.forEach((entry, slot) => {
4852
- if (!entry)
4853
- return;
4854
- if (entry.kind === 'slug') {
4855
- const markdown = (0, designCatalog_1.loadDesignSystemMarkdown)(entry.slug);
4856
- if (!markdown)
4857
- return;
4858
- const catalogEntry = (0, designCatalog_1.getDesignSystemBySlug)(entry.slug);
4859
- addDesignContextArtifact(artifactsByContent, {
4860
- id: `design_context:${slot}:${entry.slug}`,
4861
- kind: 'design_context',
4862
- label: catalogEntry?.name ?? entry.slug,
4863
- ...(catalogEntry?.description
4864
- ? { summary: catalogEntry.description }
4865
- : {}),
4866
- status: 'ready',
4867
- source: 'static',
4868
- contentType: 'text/markdown',
4869
- content: markdown,
4870
- });
4871
- return;
4872
- }
4873
- if (!entry.content)
4874
- return;
4875
- addDesignContextArtifact(artifactsByContent, {
4876
- id: `design_context:${slot}:${entry.label}`,
4877
- kind: 'design_context',
4878
- label: entry.label,
4879
- status: 'ready',
4880
- source: 'agent_browser',
4881
- contentType: 'text/markdown',
4882
- content: entry.content,
4883
- });
4884
- });
4885
- return [...artifactsByContent.values()].map((artifact) => {
4886
- if (artifact.usedByVariantCount <= 1)
4887
- return artifact;
4888
- return {
4889
- ...artifact,
4890
- summary: artifact.summary
4891
- ? `${artifact.summary} Used by ${artifact.usedByVariantCount} directions.`
4892
- : `Used by ${artifact.usedByVariantCount} directions.`,
4893
- };
4894
- });
4895
- };
4896
- const addDesignContextArtifact = (artifactsByContent, artifact) => {
4897
- const existing = artifactsByContent.get(artifact.content);
4898
- if (existing) {
4899
- existing.usedByVariantCount += 1;
4900
- return;
4901
- }
4902
- artifactsByContent.set(artifact.content, {
4903
- ...artifact,
4904
- usedByVariantCount: 1,
4905
- });
4906
- };
4907
- const buildDesignContextRawUrl = (sessionId, artifactId) => {
4908
- return `/api/variants/${encodeURIComponent(sessionId)}/${DESIGN_CONTEXT_ROUTE_SEGMENT}/${encodeURIComponent(artifactId)}`;
4909
- };
4910
- const buildDesignContextViewUrl = (sessionId, artifactId) => {
4911
- return `${buildDesignContextRawUrl(sessionId, artifactId)}/${DESIGN_CONTEXT_VIEW_SEGMENT}`;
4912
- };
4913
- const buildDesignContextViewerDocument = (artifact) => {
4914
- const title = `${artifact.label} DESIGN.md`;
4915
- const visualHtml = renderDesignMarkdown(artifact.content, artifact.label);
4916
- return `<!doctype html>
4917
- <html lang="en">
4918
- <head>
4919
- <meta charset="utf-8" />
4920
- <meta name="viewport" content="width=device-width, initial-scale=1" />
4921
- <title>${escapeHtml(title)}</title>
4922
- <style>
4923
- :root {
4924
- color-scheme: dark;
4925
- --font-main: 'Satoshi', 'Inter', system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
4926
- --font-mono: ui-monospace, "SFMono-Regular", Menlo, Consolas, monospace;
4927
- --main: #1C1C20;
4928
- --main-light: #1E1E22;
4929
- --main-input: #2E2E2E;
4930
- --main-border: #232328;
4931
- --main-hover: #404040;
4932
- --content: #ffffff;
4933
- --content-muted: #d1d5db;
4934
- --content-subtle: #9ca3af;
4935
- --divider: #4b5563;
4936
- --primary: #FF3300;
4937
- --primary-border: #ff6b35;
4938
- }
4939
- * { box-sizing: border-box; }
4940
- body {
4941
- margin: 0;
4942
- min-height: 100vh;
4943
- background: var(--main);
4944
- color: var(--content);
4945
- font-family: var(--font-main);
4946
- }
4947
- .page-shell {
4948
- min-height: 100vh;
4949
- padding: 22px 28px 56px;
4950
- }
4951
- .topbar {
4952
- display: flex;
4953
- align-items: center;
4954
- justify-content: space-between;
4955
- margin-bottom: 28px;
4956
- }
4957
- .brand {
4958
- display: inline-flex;
4959
- align-items: center;
4960
- min-height: 28px;
4961
- }
4962
- .brand-logo {
4963
- display: block;
4964
- width: 64px;
4965
- height: auto;
4966
- }
4967
- .avatar {
4968
- width: 28px;
4969
- height: 28px;
4970
- border: 1px solid var(--main-border);
4971
- border-radius: 8px;
4972
- background: var(--main-light);
4973
- }
4974
- main {
4975
- width: min(1120px, calc(100vw - 56px));
4976
- margin: 0 auto;
4977
- }
4978
- .document {
4979
- overflow: hidden;
4980
- border: 1px solid var(--main-border);
4981
- border-radius: 22px;
4982
- background: var(--main-light);
4983
- box-shadow: 0 18px 60px rgb(0 0 0 / 18%);
4984
- }
4985
- .document-title {
4986
- padding: 42px 46px 32px;
4987
- }
4988
- .eyebrow {
4989
- margin: 0 0 14px;
4990
- color: var(--content-subtle);
4991
- font-size: 0.68rem;
4992
- font-weight: 700;
4993
- letter-spacing: 0.16em;
4994
- text-transform: uppercase;
4995
- }
4996
- .file-title {
4997
- margin: 0;
4998
- color: var(--content);
4999
- font-size: clamp(2.35rem, 6vw, 4.9rem);
5000
- font-weight: 600;
5001
- letter-spacing: -0.07em;
5002
- line-height: 0.94;
5003
- }
5004
- .visual {
5005
- border-top: 1px solid var(--main-border);
5006
- max-width: none;
5007
- margin: 0 auto;
5008
- padding: 36px 46px 48px;
5009
- }
5010
- .visual-inner {
5011
- max-width: 720px;
5012
- margin: 0 auto;
5013
- }
5014
- .visual h1,
5015
- .visual h2,
5016
- .visual h3 {
5017
- margin: 1.2em 0 0.4em;
5018
- color: var(--content);
5019
- font-weight: 600;
5020
- letter-spacing: -0.02em;
5021
- line-height: 1.15;
5022
- }
5023
- .visual h1:first-child,
5024
- .visual h2:first-child,
5025
- .visual h3:first-child {
5026
- margin-top: 0;
5027
- }
5028
- .visual h1 { font-size: 1.25rem; }
5029
- .visual h2 { font-size: 1.1rem; }
5030
- .visual h3 { font-size: 1rem; }
5031
- .visual h2 + p,
5032
- .visual h2 + ul {
5033
- border-left: 1px solid var(--main-border);
5034
- padding-left: 14px;
5035
- }
5036
- .visual p,
5037
- .visual li,
5038
- .visual blockquote {
5039
- color: var(--content-muted);
5040
- font-size: 0.88rem;
5041
- line-height: 1.6;
5042
- }
5043
- .visual strong {
5044
- color: var(--content);
5045
- font-weight: 650;
5046
- }
5047
- .visual a {
5048
- color: var(--primary-border);
5049
- }
5050
- .visual ul {
5051
- display: grid;
5052
- gap: 8px;
5053
- padding-left: 1.1rem;
5054
- }
5055
- .visual blockquote {
5056
- margin: 18px 0;
5057
- border-left: 3px solid var(--primary);
5058
- padding-left: 14px;
5059
- color: var(--content-subtle);
5060
- }
5061
- .visual code {
5062
- border-radius: 6px;
5063
- border: 1px solid var(--main-border);
5064
- background: var(--main-input);
5065
- color: var(--content);
5066
- padding: 0.12rem 0.34rem;
5067
- font-family: var(--font-mono);
5068
- font-size: 0.92em;
5069
- }
5070
- pre {
5071
- margin: 0;
5072
- overflow: auto;
5073
- background: var(--main);
5074
- color: var(--content-muted);
5075
- padding: 16px;
5076
- font: 0.78rem/1.55 var(--font-mono);
5077
- white-space: pre-wrap;
5078
- word-break: break-word;
5079
- }
5080
- @media (max-width: 900px) {
5081
- .page-shell { padding: 18px 12px 34px; }
5082
- .topbar { margin-bottom: 18px; padding: 0 4px; }
5083
- main { width: min(100vw - 24px, 760px); }
5084
- .document { border-radius: 18px; }
5085
- .document-title { padding: 30px 24px 24px; }
5086
- .file-title { font-size: clamp(2rem, 12vw, 3.6rem); }
5087
- .visual { padding: 26px 24px 34px; }
5088
- }
5089
- </style>
5090
- </head>
5091
- <body>
5092
- <div class="page-shell">
5093
- <header class="topbar" aria-label="Rivet">
5094
- <div class="brand"><img class="brand-logo" src="/assets/logo.png" alt="Rivet" /></div>
5095
- <div class="avatar" aria-hidden="true"></div>
5096
- </header>
5097
- <main>
5098
- <article class="document" id="designmd-visual" aria-label="DesignMD artifact">
5099
- <div class="document-title">
5100
- <p class="eyebrow">Design Context</p>
5101
- <h1 class="file-title">${escapeHtml(title)}</h1>
5102
- </div>
5103
- <div class="visual"><div class="visual-inner">${visualHtml}</div></div>
5104
- </article>
5105
- </main>
5106
- </div>
5107
- </body>
5108
- </html>`;
5109
- };
5110
- const renderDesignMarkdown = (markdown, artifactLabel) => {
5111
- const lines = markdown.replace(/\r\n/g, '\n').split('\n');
5112
- const output = [];
5113
- const paragraph = [];
5114
- const listItems = [];
5115
- const codeLines = [];
5116
- let isCodeBlock = false;
5117
- let skippedFirstH1 = false;
5118
- const flushParagraph = () => {
5119
- if (paragraph.length === 0)
5120
- return;
5121
- output.push(`<p>${renderInlineMarkdown(paragraph.join(' '))}</p>`);
5122
- paragraph.length = 0;
5123
- };
5124
- const flushList = () => {
5125
- if (listItems.length === 0)
5126
- return;
5127
- output.push(`<ul>${listItems.map((item) => `<li>${renderInlineMarkdown(item)}</li>`).join('')}</ul>`);
5128
- listItems.length = 0;
5129
- };
5130
- const flushCode = () => {
5131
- if (codeLines.length === 0)
5132
- return;
5133
- output.push(`<pre>${escapeHtml(codeLines.join('\n'))}</pre>`);
5134
- codeLines.length = 0;
5135
- };
5136
- lines.forEach((line) => {
5137
- if (line.trim().startsWith('```')) {
5138
- if (isCodeBlock) {
5139
- flushCode();
5140
- isCodeBlock = false;
5141
- return;
5142
- }
5143
- flushParagraph();
5144
- flushList();
5145
- isCodeBlock = true;
5146
- return;
5147
- }
5148
- if (isCodeBlock) {
5149
- codeLines.push(line);
5150
- return;
5151
- }
5152
- const trimmed = line.trim();
5153
- if (!trimmed) {
5154
- flushParagraph();
5155
- flushList();
5156
- return;
5157
- }
5158
- if (/^---+$/.test(trimmed)) {
5159
- flushParagraph();
5160
- flushList();
5161
- output.push('<hr />');
5162
- return;
5163
- }
5164
- const headingMatch = trimmed.match(/^(#{1,6})\s+(.+)$/);
5165
- if (headingMatch) {
5166
- flushParagraph();
5167
- flushList();
5168
- const level = Math.min(headingMatch[1].length, 3);
5169
- const headingText = headingMatch[2].trim();
5170
- const isDuplicateTitle = headingText === artifactLabel ||
5171
- headingText === `${artifactLabel} DESIGN.md`;
5172
- if (level === 1 && !skippedFirstH1 && isDuplicateTitle) {
5173
- skippedFirstH1 = true;
5174
- return;
5175
- }
5176
- output.push(`<h${level}>${renderInlineMarkdown(headingText)}</h${level}>`);
5177
- return;
5178
- }
5179
- const listMatch = trimmed.match(/^[-*]\s+(.+)$/);
5180
- if (listMatch) {
5181
- flushParagraph();
5182
- listItems.push(listMatch[1]);
5183
- return;
5184
- }
5185
- const quoteMatch = trimmed.match(/^>\s?(.+)$/);
5186
- if (quoteMatch) {
5187
- flushParagraph();
5188
- flushList();
5189
- output.push(`<blockquote>${renderInlineMarkdown(quoteMatch[1])}</blockquote>`);
5190
- return;
5191
- }
5192
- paragraph.push(trimmed);
5193
- });
5194
- flushCode();
5195
- flushParagraph();
5196
- flushList();
5197
- return output.join('\n');
5198
- };
5199
- const renderInlineMarkdown = (value) => {
5200
- return escapeHtml(value)
5201
- .replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>')
5202
- .replace(/`([^`]+)`/g, '<code>$1</code>')
5203
- .replace(/\*([^*]+)\*/g, '<em>$1</em>');
5204
- };
5205
- const escapeHtml = (value) => {
5206
- return value
5207
- .replace(/&/g, '&amp;')
5208
- .replace(/</g, '&lt;')
5209
- .replace(/>/g, '&gt;')
5210
- .replace(/"/g, '&quot;')
5211
- .replace(/'/g, '&#39;');
5212
- };
5213
- const resolveDesignArtifact = (entry) => {
5214
- if (!entry)
5215
- return null;
5216
- if (entry.kind === 'markdown') {
5217
- return {
5218
- markdown: entry.content,
5219
- source: { kind: 'markdown', label: entry.label },
5220
- };
5221
- }
5222
- const markdown = (0, designCatalog_1.loadDesignSystemMarkdown)(entry.slug);
5223
- if (!markdown)
5224
- return null;
5225
- const catalogEntry = (0, designCatalog_1.getDesignSystemBySlug)(entry.slug);
5226
- return {
5227
- markdown,
5228
- source: {
5229
- kind: 'slug',
5230
- slug: entry.slug,
5231
- label: catalogEntry?.name ?? entry.slug,
5232
- },
5233
- };
5234
- };
5235
- const summarizeDesignContext = (designContext) => {
5236
- if (!designContext)
5237
- return null;
5238
- return designContext.reduce((entries, entry, slot) => {
5239
- if (!entry)
5240
- return entries;
5241
- if (entry.kind === 'slug') {
5242
- entries.push({ slot, kind: 'slug', slug: entry.slug });
5243
- return entries;
5244
- }
5245
- entries.push({
5246
- slot,
5247
- kind: 'markdown',
5248
- markdownLabel: entry.label,
5249
- });
5250
- return entries;
5251
- }, []);
5252
- };
5253
- const trackScaffoldStarted = (telemetry, data) => {
5254
- if (telemetry.trackAgentVariantsScaffoldStarted) {
5255
- telemetry.trackAgentVariantsScaffoldStarted(data);
5256
- return;
5257
- }
5258
- telemetry.track('agent_variants.scaffold_started', {
5259
- source: 'mcp',
5260
- session_id: data.sessionId,
5261
- variant_count: data.variantCount,
5262
- design_context: data.designContext ?? null,
5263
- });
5264
- };
5265
- const trackScaffoldCompleted = (telemetry, data) => {
5266
- if (telemetry.trackAgentVariantsScaffoldCompleted) {
5267
- telemetry.trackAgentVariantsScaffoldCompleted(data);
5268
- return;
5269
- }
5270
- telemetry.track('agent_variants.scaffold_completed', {
5271
- source: 'mcp',
5272
- session_id: data.sessionId,
5273
- duration_ms: data.durationMs,
5274
- install_duration_ms: data.installDurationMs,
5275
- variant_count: data.variantCount,
5276
- });
5277
- };
5278
- const trackScaffoldFailed = (telemetry, data) => {
5279
- if (telemetry.trackAgentVariantsScaffoldFailed) {
5280
- telemetry.trackAgentVariantsScaffoldFailed(data);
5281
- return;
5282
- }
5283
- telemetry.track('agent_variants.scaffold_failed', {
5284
- source: 'mcp',
5285
- session_id: data.sessionId,
5286
- error_code: data.errorCode,
5287
- duration_ms: data.durationMs,
5288
- });
5289
- };
5290
- const trackFreshDevServerStarted = (telemetry, data) => {
5291
- if (telemetry.trackAgentVariantsFreshDevServerStarted) {
5292
- telemetry.trackAgentVariantsFreshDevServerStarted(data);
5293
- return;
5294
- }
5295
- telemetry.track('agent_variants.fresh_dev_server_started', {
5296
- source: 'mcp',
5297
- session_id: data.sessionId,
5298
- variant_id: data.variantId,
5299
- port: data.port,
5300
- });
5301
- };
5302
- const trackFreshDevServerFailed = (telemetry, data) => {
5303
- if (telemetry.trackAgentVariantsFreshDevServerFailed) {
5304
- telemetry.trackAgentVariantsFreshDevServerFailed(data);
5305
- return;
5306
- }
5307
- telemetry.track('agent_variants.fresh_dev_server_failed', {
5308
- source: 'mcp',
5309
- session_id: data.sessionId,
5310
- variant_id: data.variantId,
5311
- error_code: data.errorCode,
5312
- failure_reason: data.reason,
5313
- port_in_use: data.portInUse,
5314
- exit_code: data.exitCode,
5315
- term_signal: data.signal,
5316
- missing_deps: data.missingDeps,
5317
- });
5318
- };
5319
- const trackPreviewStarted = (telemetry, data) => {
5320
- if (telemetry.trackAgentVariantsPreviewStarted) {
5321
- telemetry.trackAgentVariantsPreviewStarted(data);
5322
- return;
5323
- }
5324
- telemetry.track('agent_variants.preview_started', {
5325
- source: 'mcp',
5326
- session_id: data.sessionId,
5327
- variant_id: data.variantId,
5328
- project_context_kind: data.projectContextKind,
5329
- preview_kind: data.previewKind,
5330
- });
5331
- };
5332
- const trackPreviewReady = (telemetry, data) => {
5333
- if (telemetry.trackAgentVariantsPreviewReady) {
5334
- telemetry.trackAgentVariantsPreviewReady(data);
5335
- return;
5336
- }
5337
- telemetry.track('agent_variants.preview_ready', {
5338
- source: 'mcp',
5339
- session_id: data.sessionId,
5340
- variant_id: data.variantId,
5341
- project_context_kind: data.projectContextKind,
5342
- preview_kind: data.previewKind,
5343
- has_port: data.hasPort,
5344
- port: data.port,
5345
- duration_ms: data.durationMs,
5346
- });
5347
- };
5348
- const trackPreviewFailed = (telemetry, data) => {
5349
- if (telemetry.trackAgentVariantsPreviewFailed) {
5350
- telemetry.trackAgentVariantsPreviewFailed(data);
5351
- return;
5352
- }
5353
- telemetry.track('agent_variants.preview_failed', {
5354
- source: 'mcp',
5355
- session_id: data.sessionId,
5356
- variant_id: data.variantId,
5357
- project_context_kind: data.projectContextKind,
5358
- preview_kind: data.previewKind,
5359
- error_code: data.errorCode,
5360
- failure_reason: data.reason,
5361
- port_in_use: data.portInUse,
5362
- duration_ms: data.durationMs,
5363
- exit_code: data.exitCode,
5364
- term_signal: data.signal,
5365
- missing_deps: data.missingDeps,
5366
- });
5367
- };
5368
- const trackStaticPreviewCompleted = (telemetry, data) => {
5369
- if (telemetry.trackAgentVariantsStaticPreviewCompleted) {
5370
- telemetry.trackAgentVariantsStaticPreviewCompleted(data);
5371
- return;
5372
- }
5373
- telemetry.track('agent_variants.static_preview_completed', {
5374
- source: 'mcp',
5375
- session_id: data.sessionId,
5376
- variant_id: data.variantId,
5377
- duration_ms: data.durationMs,
5378
- });
5379
- };
5380
- const MATERIALIZE_EXCLUDE = new Set([
5381
- 'node_modules',
5382
- '.git',
5383
- '.rivet',
5384
- 'dist',
5385
- '.next',
5386
- '.cache',
5387
- '.vite',
5388
- ]);
5389
- const STATIC_PREVIEW_HISTORY_EXCLUDE = new Set([
5390
- ...MATERIALIZE_EXCLUDE,
5391
- '.rivet',
5392
- ]);
5393
- const STATIC_PREVIEW_HISTORY_FILE_EXTENSIONS = new Set([
5394
- ...ALLOWED_INERT_ASSET_EXTENSIONS,
5395
- '.css',
5396
- '.htm',
5397
- '.html',
5398
- '.js',
5399
- ]);
5400
- function copyStaticPreviewDirectory(src, dest) {
5401
- fs_1.default.mkdirSync(dest, { recursive: true });
5402
- let entries;
5403
- try {
5404
- entries = fs_1.default.readdirSync(src, { withFileTypes: true });
5405
- }
5406
- catch {
5407
- return;
5408
- }
5409
- for (const entry of entries) {
5410
- if (entry.name.startsWith('.') ||
5411
- STATIC_PREVIEW_HISTORY_EXCLUDE.has(entry.name)) {
5412
- continue;
5413
- }
5414
- const from = path_1.default.join(src, entry.name);
5415
- const to = path_1.default.join(dest, entry.name);
5416
- if (entry.isDirectory()) {
5417
- copyStaticPreviewDirectory(from, to);
5418
- }
5419
- else if (entry.isFile()) {
5420
- const extension = path_1.default.extname(entry.name).toLowerCase();
5421
- if (!STATIC_PREVIEW_HISTORY_FILE_EXTENSIONS.has(extension))
5422
- continue;
5423
- let stat;
5424
- try {
5425
- stat = fs_1.default.statSync(from);
5426
- }
5427
- catch {
5428
- continue;
5429
- }
5430
- if (!stat.isFile() || stat.nlink !== 1)
5431
- continue;
5432
- fs_1.default.mkdirSync(path_1.default.dirname(to), { recursive: true });
5433
- fs_1.default.copyFileSync(from, to);
5434
- }
5435
- }
5436
- }
5437
- const defaultInstallDependencies = async (worktreePath, options) => {
5438
- await DependencyPreparationService_1.previewDependencies.prepare({
5439
- cwd: worktreePath,
5440
- workspaceRoot: options?.workspaceRoot ?? worktreePath,
5441
- force: options?.force,
5442
- signal: options?.signal,
5443
- });
5444
- };
5445
- const buildServeRecipeFromResolved = (snapshotRoot, dev, port) => {
5446
- const portPattern = new RegExp(`(?<![0-9])${port}(?![0-9])`, 'g');
5447
- const sub = (value) => value.replace(portPattern, '${PORT}');
5448
- const relativeCwd = path_1.default.relative(snapshotRoot, dev.cwd);
5449
- return {
5450
- cwd: relativeCwd === '' ? '.' : relativeCwd,
5451
- cmd: dev.cmd,
5452
- argsTemplate: dev.args.map(sub),
5453
- envTemplate: Object.fromEntries(Object.entries(dev.env).map(([key, value]) => [key, sub(value)])),
5454
- };
5455
- };
5456
- const canInstallWithoutTouchingHost = (cwd) => {
5457
- try {
5458
- return !fs_1.default.lstatSync(path_1.default.join(cwd, 'node_modules')).isSymbolicLink();
5459
- }
5460
- catch {
5461
- return true;
5462
- }
5463
- };
5464
- const assertDevScriptExists = (cwd, scriptName) => {
5465
- let scripts;
5466
- try {
5467
- const raw = fs_1.default.readFileSync(path_1.default.join(cwd, 'package.json'), 'utf8');
5468
- const parsed = JSON.parse(raw);
5469
- if (parsed && typeof parsed === 'object' && 'scripts' in parsed) {
5470
- const candidate = parsed.scripts;
5471
- if (candidate && typeof candidate === 'object') {
5472
- scripts = candidate;
5473
- }
5474
- }
5475
- }
5476
- catch {
5477
- return;
5478
- }
5479
- if (scripts && typeof scripts[scriptName] === 'string')
5480
- return;
5481
- const available = Object.keys(scripts ?? {});
5482
- throw new devServerError_1.DevServerStartError({
5483
- message: `package.json at ${cwd} has no "${scriptName}" script` +
5484
- (available.length ? ` (available: ${available.join(', ')})` : ''),
5485
- reason: 'missing_dev_script',
5486
- portInUse: false,
5487
- detail: available.length
5488
- ? `available scripts: ${available.join(', ')}`
5489
- : '',
5490
- });
5491
- };
5492
- function areSameVolume(a, b) {
5493
- try {
5494
- const statA = fs_1.default.statSync(a);
5495
- const parentB = fs_1.default.existsSync(b) ? b : path_1.default.dirname(b);
5496
- const statB = fs_1.default.statSync(parentB);
5497
- return statA.dev === statB.dev;
5498
- }
5499
- catch {
5500
- return false;
5501
- }
5502
- }
5503
- async function resolveNodeModulesSymlink(worktreePath) {
5504
- const nodeModulesPath = path_1.default.join(worktreePath, 'node_modules');
5505
- try {
5506
- if (!(await fs_1.default.promises.lstat(nodeModulesPath)).isSymbolicLink())
5507
- return false;
5508
- await fs_1.default.promises.rm(nodeModulesPath, { force: true });
5509
- return true;
5510
- }
5511
- catch (error) {
5512
- if (error.code === 'ENOENT')
5513
- return true;
5514
- throw error;
5515
- }
5516
- }
5517
- const defaultMaterializeProject = async (sourceWorktreePath, destinationPath) => {
5518
- await fs_1.default.promises.mkdir(destinationPath, { recursive: true });
5519
- await fs_1.default.promises.cp(sourceWorktreePath, destinationPath, {
5520
- recursive: true,
5521
- filter: (sourcePath) => {
5522
- const rel = path_1.default.relative(sourceWorktreePath, sourcePath);
5523
- if (!rel)
5524
- return true;
5525
- const top = rel.split(path_1.default.sep)[0];
5526
- return !MATERIALIZE_EXCLUDE.has(top);
5527
- },
5528
- });
5529
- const git = (0, simple_git_1.simpleGit)(destinationPath);
5530
- await git.raw(['init']);
5531
- await git.raw(['add', '-A']);
5532
- await git
5533
- .raw([
5534
- '-c',
5535
- 'user.name=Rivet',
5536
- '-c',
5537
- 'user.email=hello@tryrivet.design',
5538
- 'commit',
5539
- '--no-gpg-sign',
5540
- '-m',
5541
- 'Initial commit (created with Rivet)',
5542
- ])
5543
- .catch(() => { });
5544
- };
5545
- function synthesizeUnifiedBriefBody(prompt, index, total) {
5546
- const trimmed = prompt.trim().replace(/\s+/g, ' ').slice(0, 200);
5547
- const intent = trimmed.length > 0 ? trimmed : 'Create this variant';
5548
- if (total <= 1)
5549
- return intent;
5550
- const focus = FALLBACK_DIRECTION_FOCI[(Math.max(index, 1) - 1) % FALLBACK_DIRECTION_FOCI.length];
5551
- return `${intent} Direction ${index} of ${total}: ${focus}`;
5552
- }
5553
3530
  //# sourceMappingURL=WorktreeOrchestrator.js.map