rivet-design 0.9.1 → 0.9.3

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 (138) hide show
  1. package/dist/mcp/agent-variants/SessionStore.d.ts +63 -2
  2. package/dist/mcp/agent-variants/SessionStore.d.ts.map +1 -1
  3. package/dist/mcp/agent-variants/SessionStore.js +331 -71
  4. package/dist/mcp/agent-variants/SessionStore.js.map +1 -1
  5. package/dist/mcp/agent-variants/WorktreeOrchestrator.d.ts +197 -8
  6. package/dist/mcp/agent-variants/WorktreeOrchestrator.d.ts.map +1 -1
  7. package/dist/mcp/agent-variants/WorktreeOrchestrator.js +805 -46
  8. package/dist/mcp/agent-variants/WorktreeOrchestrator.js.map +1 -1
  9. package/dist/mcp/agent-variants/contracts.d.ts +1101 -9
  10. package/dist/mcp/agent-variants/contracts.d.ts.map +1 -1
  11. package/dist/mcp/agent-variants/contracts.js +131 -5
  12. package/dist/mcp/agent-variants/contracts.js.map +1 -1
  13. package/dist/mcp/agent-variants/createZeroToOneTool.d.ts +205 -0
  14. package/dist/mcp/agent-variants/createZeroToOneTool.d.ts.map +1 -0
  15. package/dist/mcp/agent-variants/createZeroToOneTool.js +295 -0
  16. package/dist/mcp/agent-variants/createZeroToOneTool.js.map +1 -0
  17. package/dist/mcp/agent-variants/designContextStore.d.ts +160 -0
  18. package/dist/mcp/agent-variants/designContextStore.d.ts.map +1 -0
  19. package/dist/mcp/agent-variants/designContextStore.js +295 -0
  20. package/dist/mcp/agent-variants/designContextStore.js.map +1 -0
  21. package/dist/mcp/agent-variants/elementRefToTarget.d.ts +21 -0
  22. package/dist/mcp/agent-variants/elementRefToTarget.d.ts.map +1 -0
  23. package/dist/mcp/agent-variants/elementRefToTarget.js +47 -0
  24. package/dist/mcp/agent-variants/elementRefToTarget.js.map +1 -0
  25. package/dist/mcp/agent-variants/errors.d.ts +1 -1
  26. package/dist/mcp/agent-variants/errors.d.ts.map +1 -1
  27. package/dist/mcp/agent-variants/errors.js +6 -0
  28. package/dist/mcp/agent-variants/errors.js.map +1 -1
  29. package/dist/mcp/agent-variants/index.d.ts +4 -1
  30. package/dist/mcp/agent-variants/index.d.ts.map +1 -1
  31. package/dist/mcp/agent-variants/index.js +7 -1
  32. package/dist/mcp/agent-variants/index.js.map +1 -1
  33. package/dist/mcp/agent-variants/inspirationDesignContext.d.ts +430 -0
  34. package/dist/mcp/agent-variants/inspirationDesignContext.d.ts.map +1 -0
  35. package/dist/mcp/agent-variants/inspirationDesignContext.js +2379 -0
  36. package/dist/mcp/agent-variants/inspirationDesignContext.js.map +1 -0
  37. package/dist/mcp/agent-variants/pendingChangesAdapter.d.ts.map +1 -1
  38. package/dist/mcp/agent-variants/pendingChangesAdapter.js +10 -7
  39. package/dist/mcp/agent-variants/pendingChangesAdapter.js.map +1 -1
  40. package/dist/mcp/agent-variants/sourceContext.d.ts +7 -0
  41. package/dist/mcp/agent-variants/sourceContext.d.ts.map +1 -0
  42. package/dist/mcp/agent-variants/sourceContext.js +158 -0
  43. package/dist/mcp/agent-variants/sourceContext.js.map +1 -0
  44. package/dist/mcp/agent-variants/tools.d.ts +14 -0
  45. package/dist/mcp/agent-variants/tools.d.ts.map +1 -1
  46. package/dist/mcp/agent-variants/tools.js +330 -15
  47. package/dist/mcp/agent-variants/tools.js.map +1 -1
  48. package/dist/mcp/changeBatchClassification.d.ts +30 -0
  49. package/dist/mcp/changeBatchClassification.d.ts.map +1 -0
  50. package/dist/mcp/changeBatchClassification.js +65 -0
  51. package/dist/mcp/changeBatchClassification.js.map +1 -0
  52. package/dist/mcp/server.d.ts.map +1 -1
  53. package/dist/mcp/server.js +237 -39
  54. package/dist/mcp/server.js.map +1 -1
  55. package/dist/proxy-middleware/proxy-config.d.ts.map +1 -1
  56. package/dist/proxy-middleware/proxy-config.js +1 -15
  57. package/dist/proxy-middleware/proxy-config.js.map +1 -1
  58. package/dist/routes/agentVariants.d.ts +3 -1
  59. package/dist/routes/agentVariants.d.ts.map +1 -1
  60. package/dist/routes/agentVariants.js +131 -13
  61. package/dist/routes/agentVariants.js.map +1 -1
  62. package/dist/routes/mcp.d.ts +7 -1
  63. package/dist/routes/mcp.d.ts.map +1 -1
  64. package/dist/routes/mcp.js +139 -16
  65. package/dist/routes/mcp.js.map +1 -1
  66. package/dist/server.d.ts.map +1 -1
  67. package/dist/server.js +2 -2
  68. package/dist/server.js.map +1 -1
  69. package/dist/services/SessionBridgeService.d.ts +22 -0
  70. package/dist/services/SessionBridgeService.d.ts.map +1 -1
  71. package/dist/services/SessionBridgeService.js +61 -0
  72. package/dist/services/SessionBridgeService.js.map +1 -1
  73. package/dist/services/TelemetryService.d.ts +121 -0
  74. package/dist/services/TelemetryService.d.ts.map +1 -1
  75. package/dist/services/TelemetryService.js +155 -0
  76. package/dist/services/TelemetryService.js.map +1 -1
  77. package/dist/services/WorktreeManager.d.ts +57 -5
  78. package/dist/services/WorktreeManager.d.ts.map +1 -1
  79. package/dist/services/WorktreeManager.js +205 -13
  80. package/dist/services/WorktreeManager.js.map +1 -1
  81. package/dist/services/templates/designCatalog.d.ts +27 -0
  82. package/dist/services/templates/designCatalog.d.ts.map +1 -0
  83. package/dist/services/templates/designCatalog.js +141 -0
  84. package/dist/services/templates/designCatalog.js.map +1 -0
  85. package/dist/services/templates/designmd/airbnb.md +545 -0
  86. package/dist/services/templates/designmd/airtable.md +554 -0
  87. package/dist/services/templates/designmd/apple.md +562 -0
  88. package/dist/services/templates/designmd/binance.md +634 -0
  89. package/dist/services/templates/designmd/bmw-m.md +503 -0
  90. package/dist/services/templates/designmd/bmw.md +544 -0
  91. package/dist/services/templates/designmd/bugatti.md +454 -0
  92. package/dist/services/templates/designmd/cal.md +542 -0
  93. package/dist/services/templates/designmd/claude.md +589 -0
  94. package/dist/services/templates/designmd/clay.md +541 -0
  95. package/dist/services/templates/designmd/cohere.md +451 -0
  96. package/dist/services/templates/designmd/cursor.md +537 -0
  97. package/dist/services/templates/designmd/expo.md +526 -0
  98. package/dist/services/templates/designmd/figma.md +578 -0
  99. package/dist/services/templates/designmd/framer.md +544 -0
  100. package/dist/services/templates/designmd/hp.md +670 -0
  101. package/dist/services/templates/designmd/linear.app.md +548 -0
  102. package/dist/services/templates/designmd/mintlify.md +852 -0
  103. package/dist/services/templates/designmd/miro.md +825 -0
  104. package/dist/services/templates/designmd/notion.md +821 -0
  105. package/dist/services/templates/designmd/raycast.md +669 -0
  106. package/dist/services/templates/designmd/resend.md +585 -0
  107. package/dist/services/templates/designmd/sentry.md +262 -0
  108. package/dist/services/templates/designmd/shopify.md +350 -0
  109. package/dist/services/templates/designmd/spotify.md +246 -0
  110. package/dist/services/templates/designmd/stripe.md +322 -0
  111. package/dist/services/templates/designmd/supabase.md +255 -0
  112. package/dist/services/templates/designmd/superhuman.md +252 -0
  113. package/dist/services/templates/designmd/uber.md +295 -0
  114. package/dist/services/templates/designmd/vercel.md +310 -0
  115. package/dist/services/templates/viteReactTs.d.ts +42 -0
  116. package/dist/services/templates/viteReactTs.d.ts.map +1 -0
  117. package/dist/services/templates/viteReactTs.js +267 -0
  118. package/dist/services/templates/viteReactTs.js.map +1 -0
  119. package/dist/types/change-request-types.d.ts +15 -3
  120. package/dist/types/change-request-types.d.ts.map +1 -1
  121. package/dist/utils/skills/claude-skill.d.ts +2 -2
  122. package/dist/utils/skills/claude-skill.d.ts.map +1 -1
  123. package/dist/utils/skills/claude-skill.js +19 -98
  124. package/dist/utils/skills/claude-skill.js.map +1 -1
  125. package/dist/utils/skills/cursor-rules.d.ts +2 -2
  126. package/dist/utils/skills/cursor-rules.d.ts.map +1 -1
  127. package/dist/utils/skills/cursor-rules.js +15 -80
  128. package/dist/utils/skills/cursor-rules.js.map +1 -1
  129. package/dist/utils/skills/shared-variants-protocol.d.ts +23 -0
  130. package/dist/utils/skills/shared-variants-protocol.d.ts.map +1 -0
  131. package/dist/utils/skills/shared-variants-protocol.js +131 -0
  132. package/dist/utils/skills/shared-variants-protocol.js.map +1 -0
  133. package/package.json +4 -3
  134. package/src/ui/dist/assets/main-C9jfEp80.css +1 -0
  135. package/src/ui/dist/assets/main-DejhsBWR.js +382 -0
  136. package/src/ui/dist/index.html +2 -2
  137. package/src/ui/dist/assets/main-Bv0LuxKz.js +0 -382
  138. package/src/ui/dist/assets/main-BzmseUDd.css +0 -1
@@ -1,11 +1,21 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
3
6
  exports.AgentVariantsOrchestrator = void 0;
4
7
  const events_1 = require("events");
8
+ const fs_1 = __importDefault(require("fs"));
9
+ const path_1 = __importDefault(require("path"));
10
+ const child_process_1 = require("child_process");
11
+ const simple_git_1 = require("simple-git");
5
12
  const logger_1 = require("../../utils/logger");
6
13
  const errors_1 = require("./errors");
7
14
  const contracts_1 = require("./contracts");
15
+ const viteReactTs_1 = require("../../services/templates/viteReactTs");
16
+ const designCatalog_1 = require("../../services/templates/designCatalog");
8
17
  const log = (0, logger_1.createLogger)('AgentVariantsOrchestrator');
18
+ const FRESH_DEV_SERVER_HOST = '127.0.0.1';
9
19
  const NOOP_TELEMETRY = { track: () => undefined };
10
20
  /**
11
21
  * Wraps SessionStore for the operations that have side effects: approve
@@ -25,6 +35,8 @@ class AgentVariantsOrchestrator {
25
35
  adapter;
26
36
  resolveEnv;
27
37
  telemetry;
38
+ installDependencies;
39
+ materializeProject;
28
40
  resources = new Map();
29
41
  /** Most recent agent-variants sessionId — read by the iframe chip via
30
42
  * GET /api/variants/active. Cleared on cancel or commit. */
@@ -38,6 +50,10 @@ class AgentVariantsOrchestrator {
38
50
  this.adapter = deps.pendingChangesAdapter;
39
51
  this.resolveEnv = deps.resolveProjectEnvironment;
40
52
  this.telemetry = deps.telemetry ?? NOOP_TELEMETRY;
53
+ this.installDependencies =
54
+ deps.installDependencies ?? defaultInstallDependencies;
55
+ this.materializeProject =
56
+ deps.materializeProject ?? defaultMaterializeProject;
41
57
  }
42
58
  // --- Pure delegations (no side effects) ---------------------------------
43
59
  propose(args) {
@@ -82,13 +98,9 @@ class AgentVariantsOrchestrator {
82
98
  stage === 'cancelled'
83
99
  ? this.store.getSummary(sessionId)
84
100
  : null;
85
- const variants = (summary?.variants ?? []).map((v) => ({
86
- workItemId: v.workItemId,
87
- status: v.status,
88
- label: v.label,
89
- previewPort: this.getDevServerPort(sessionId, v.workItemId),
90
- worktreePath: this.getWorktreePath(sessionId, v.workItemId),
91
- }));
101
+ const variants = this.getVariants(sessionId);
102
+ const projectContext = toActiveProjectContext(this.store.getProjectContext(sessionId));
103
+ const destinationPath = projectContext.kind === 'fresh' ? projectContext.workspacePath : undefined;
92
104
  return {
93
105
  active: true,
94
106
  sessionId,
@@ -97,6 +109,8 @@ class AgentVariantsOrchestrator {
97
109
  progress,
98
110
  summary,
99
111
  variants,
112
+ projectContext,
113
+ ...(destinationPath ? { destinationPath } : {}),
100
114
  };
101
115
  }
102
116
  emitChange() {
@@ -109,6 +123,26 @@ class AgentVariantsOrchestrator {
109
123
  }
110
124
  reportBriefs(args) {
111
125
  const result = this.store.reportBriefs(args);
126
+ const evidenceBackedCount = result.briefs.filter((brief) => /\b(source|evidence|preserve|borrow)\b/i.test(brief.body)).length;
127
+ const shortBriefCount = result.briefs.filter((brief) => brief.body.length <= 200).length;
128
+ this.telemetry.trackAgentVariantsBriefQuality?.({
129
+ sessionId: args.sessionId,
130
+ briefCount: result.briefs.length,
131
+ evidenceBackedCount,
132
+ shortBriefCount,
133
+ });
134
+ this.emitChange();
135
+ return result;
136
+ }
137
+ reportSourceContext(args) {
138
+ const result = this.store.reportSourceContext(args);
139
+ this.telemetry.trackAgentVariantsSourceContextQuality?.({
140
+ sessionId: args.sessionId,
141
+ sourceUrlCount: args.sourceContext.sourceRoles.length,
142
+ artifactCount: args.sourceContext.sourceFindings.length,
143
+ hasScreenshotReferences: (args.sourceContext.screenshotReferences?.length ?? 0) > 0,
144
+ preserveBrand: args.sourceContext.sourceRoles.some((entry) => entry.role === 'primary'),
145
+ });
112
146
  this.emitChange();
113
147
  return result;
114
148
  }
@@ -156,9 +190,77 @@ class AgentVariantsOrchestrator {
156
190
  getSummary(sessionId) {
157
191
  return this.store.getSummary(sessionId);
158
192
  }
193
+ getVariants(sessionId) {
194
+ const resources = this.resources.get(sessionId);
195
+ return this.store.getVariants(sessionId).map((variant) => {
196
+ const port = this.getDevServerPort(sessionId, variant.workItemId);
197
+ // For fresh sessions, static_preview items ARE the variants — look up
198
+ // directly by workItemId. Fall back to briefId search for existing-project
199
+ // sessions where code_gen items were paired with companion static_preview items.
200
+ const staticPreview = resources?.staticPreviews.get(variant.workItemId) ??
201
+ this.getStaticPreviewByBriefId(sessionId, variant.briefId);
202
+ let preview;
203
+ if (staticPreview) {
204
+ preview = {
205
+ kind: 'static_artifact',
206
+ url: this.buildStaticPreviewUrl(sessionId, staticPreview.workItemId),
207
+ };
208
+ }
209
+ else if (!port) {
210
+ // handleSucceededReport is fire-and-forget so staticPreviews may not be
211
+ // populated yet when getVariants is called (e.g. in the terminal response).
212
+ // item.output is set synchronously by store.reportComplete, so it's always
213
+ // available. Read it directly as a fallback so previewUrls in terminal
214
+ // responses include the variant that just triggered terminal state.
215
+ try {
216
+ const output = this.store.getWorkItemOutput(sessionId, variant.workItemId);
217
+ if (typeof output?.html === 'string' && output.html.length > 0) {
218
+ preview = {
219
+ kind: 'static_artifact',
220
+ url: this.buildStaticPreviewUrl(sessionId, variant.workItemId),
221
+ };
222
+ }
223
+ }
224
+ catch { /* work item may not exist in edge cases */ }
225
+ }
226
+ if (!preview && port) {
227
+ preview = { kind: 'dev_server', port };
228
+ }
229
+ const isSucceeded = variant.status === 'succeeded';
230
+ const canView = Boolean(preview) || (isSucceeded && Boolean(port));
231
+ const canCommit = isSucceeded;
232
+ return {
233
+ ...variant,
234
+ design: enrichDesignSource(variant.design),
235
+ ...(preview ? { preview } : {}),
236
+ port,
237
+ actions: {
238
+ view: canView
239
+ ? { enabled: true }
240
+ : {
241
+ enabled: false,
242
+ reason: isSucceeded
243
+ ? 'Preview is unavailable for this variant'
244
+ : 'Variant is still running',
245
+ },
246
+ commit: canCommit
247
+ ? { enabled: true }
248
+ : {
249
+ enabled: false,
250
+ reason: 'Wait for a successful variant',
251
+ },
252
+ },
253
+ };
254
+ });
255
+ }
159
256
  hasSession(sessionId) {
160
257
  return this.store.hasSession(sessionId);
161
258
  }
259
+ /** Read the projectContext for a session — used by Express routes that
260
+ * build the chip snapshot via `stateForSession`. */
261
+ getProjectContext(sessionId) {
262
+ return this.store.getProjectContext(sessionId);
263
+ }
162
264
  /** Resolve a worktree path for a code-gen work item, if provisioned. */
163
265
  getWorktreePath(sessionId, workItemId) {
164
266
  return this.resources.get(sessionId)?.worktrees.get(workItemId)
@@ -168,16 +270,45 @@ class AgentVariantsOrchestrator {
168
270
  getDevServerPort(sessionId, workItemId) {
169
271
  return this.resources.get(sessionId)?.worktrees.get(workItemId)?.port;
170
272
  }
273
+ getStaticPreviewHtml(sessionId, workItemId) {
274
+ // Primary: from the staticPreviews Map populated by handleSucceededReport.
275
+ const fromMap = this.resources.get(sessionId)?.staticPreviews.get(workItemId)?.html;
276
+ if (fromMap)
277
+ return fromMap;
278
+ // Fallback: read directly from the work item's stored output — available
279
+ // as soon as reportComplete runs, before handleSucceededReport fires.
280
+ try {
281
+ const output = this.store.getWorkItemOutput(sessionId, workItemId);
282
+ return typeof output?.html === 'string' && output.html.length > 0
283
+ ? buildStaticPreviewDocument({ html: output.html })
284
+ : undefined;
285
+ }
286
+ catch {
287
+ return undefined;
288
+ }
289
+ }
290
+ getStaticPreviewByBriefId(sessionId, briefId) {
291
+ const resources = this.resources.get(sessionId);
292
+ if (!resources)
293
+ return undefined;
294
+ return [...resources.staticPreviews.values()].find((preview) => preview.briefId === briefId);
295
+ }
296
+ buildStaticPreviewUrl(sessionId, workItemId) {
297
+ const safeSessionId = encodeURIComponent(sessionId);
298
+ const safeWorkItemId = encodeURIComponent(workItemId);
299
+ return `/api/variants/${safeSessionId}/static/${safeWorkItemId}`;
300
+ }
171
301
  // --- Mutation wrappers (state + side effects) ---------------------------
172
302
  async approve(args) {
173
303
  const result = this.store.approve(args);
174
- this.ensureResources(args.sessionId);
304
+ const resources = this.ensureResources(args.sessionId);
305
+ resources.approveStartedAt = Date.now();
175
306
  this.telemetry.track('agent_variants.approved', {
176
307
  source: 'mcp',
177
308
  sessionId: args.sessionId,
178
309
  approvedCount: result.approvedCount,
179
310
  totalBriefs: result.totalCount,
180
- isFresh: Boolean(result.scaffoldBaseWorkItemId),
311
+ isFresh: this.store.getProjectContext(args.sessionId).kind === 'fresh',
181
312
  });
182
313
  this.emitChange();
183
314
  // Await provisioning before returning so request_work always sees a
@@ -195,6 +326,99 @@ class AgentVariantsOrchestrator {
195
326
  }
196
327
  return result;
197
328
  }
329
+ /**
330
+ * Unified one-call kickoff that collapses propose → report_briefs →
331
+ * approve. Synthesizes placeholder briefs server-side (the agent's
332
+ * streamed first-line label replaces them in the UI) and immediately
333
+ * approves so the session lands in `work_items_ready`.
334
+ *
335
+ * Supports both existing-project sessions (spawns code_gen work items
336
+ * for the agent to lease) and zero-to-one sessions (spawns
337
+ * static_preview work items; the server runs scaffold_base in the
338
+ * background). For zero-to-one sessions with source URLs / inspiration
339
+ * extraction, callers must use create_zero_to_one_project instead —
340
+ * this method does not run source research.
341
+ *
342
+ * Host-agnostic: no LLM calls happen here. The agent (Claude Code,
343
+ * Cursor, Codex) generates the label and code when it leases the
344
+ * work items via continue_variants(action="request_work").
345
+ */
346
+ async startUnified(args) {
347
+ const count = args.count ?? 4;
348
+ const projectContext = args.projectContext ?? { kind: 'existing' };
349
+ const proposeResult = this.propose({
350
+ prompt: args.prompt,
351
+ count,
352
+ target: args.target,
353
+ projectContext,
354
+ });
355
+ if (proposeResult.stage === 'awaiting_source_research') {
356
+ throw new errors_1.AgentVariantsError('INVALID_STAGE_ACTION', 'start_variants cannot handle source-research sessions. Use create_zero_to_one_project for inspiration-grounded fresh projects.');
357
+ }
358
+ if (proposeResult.stage !== 'awaiting_briefs' ||
359
+ !proposeResult.briefWorkItem) {
360
+ throw new errors_1.AgentVariantsError('INVALID_STAGE_ACTION', `start_variants expected awaiting_briefs from propose, got ${proposeResult.stage}`);
361
+ }
362
+ const briefs = Array.from({ length: count }, (_, i) => ({
363
+ briefId: `v${i + 1}`,
364
+ label: `Variant ${i + 1}`,
365
+ body: synthesizeUnifiedBriefBody(args.prompt, i + 1, count),
366
+ }));
367
+ this.store.reportBriefs({
368
+ sessionId: proposeResult.sessionId,
369
+ workItemId: proposeResult.briefWorkItem.id,
370
+ attempt: proposeResult.briefWorkItem.attempt,
371
+ briefs,
372
+ });
373
+ const approveResult = await this.approve({
374
+ sessionId: proposeResult.sessionId,
375
+ selections: briefs.map((b) => ({ briefId: b.briefId })),
376
+ });
377
+ this.telemetry.track('agent_variants.unified_started', {
378
+ source: 'mcp',
379
+ sessionId: proposeResult.sessionId,
380
+ count,
381
+ hasTarget: Boolean(args.target),
382
+ targetType: args.target?.type ?? null,
383
+ projectContextKind: projectContext.kind,
384
+ });
385
+ // For existing-project sessions, approve() returns the new code_gen
386
+ // ids in codeGenWorkItemIds. For zero-to-one, approve() spawns
387
+ // static_preview items instead — those ids aren't in codeGenWorkItemIds,
388
+ // so read them out of the active-variants snapshot which covers both
389
+ // kinds. Snapshot order mirrors brief order (insertion order).
390
+ const variantIds = projectContext.kind === 'fresh'
391
+ ? this.store
392
+ .getVariants(proposeResult.sessionId)
393
+ .map((v) => v.workItemId)
394
+ : approveResult.codeGenWorkItemIds;
395
+ const variants = variantIds.map((workItemId, i) => ({
396
+ variantId: workItemId,
397
+ briefId: briefs[i].briefId,
398
+ label: briefs[i].label,
399
+ status: 'pending',
400
+ }));
401
+ let leaseId;
402
+ let leaseTtlMs;
403
+ let leasedWorkItems;
404
+ if (args.leaseOwner) {
405
+ const lease = this.requestWork({
406
+ sessionId: proposeResult.sessionId,
407
+ leaseOwner: args.leaseOwner,
408
+ });
409
+ leaseId = lease.leaseId;
410
+ leaseTtlMs = lease.leaseTtlMs;
411
+ leasedWorkItems = lease.leasedWorkItems;
412
+ }
413
+ return {
414
+ sessionId: proposeResult.sessionId,
415
+ variants,
416
+ scaffoldBaseWorkItemId: approveResult.scaffoldBaseWorkItemId ?? null,
417
+ leaseId,
418
+ leaseTtlMs,
419
+ leasedWorkItems,
420
+ };
421
+ }
198
422
  async reportComplete(args) {
199
423
  const result = this.store.reportComplete(args);
200
424
  this.emitChange();
@@ -207,6 +431,11 @@ class AgentVariantsOrchestrator {
207
431
  const resources = this.resources.get(args.sessionId);
208
432
  const leasedAt = resources?.leasedAt.get(args.workItemId);
209
433
  const durationMs = leasedAt !== undefined ? Date.now() - leasedAt : null;
434
+ const approveStartedAt = resources?.approveStartedAt;
435
+ const approveToCompleteMs = approveStartedAt !== undefined ? Date.now() - approveStartedAt : null;
436
+ // Byte size of generated output for HTML vs full-stack comparison.
437
+ const outputBytes = estimateOutputBytes(args.output);
438
+ const projectContextKind = this.store.getProjectContext(args.sessionId).kind;
210
439
  this.telemetry.track('agent_variants.variant_completed', {
211
440
  source: 'mcp',
212
441
  sessionId: args.sessionId,
@@ -214,9 +443,15 @@ class AgentVariantsOrchestrator {
214
443
  status: args.status,
215
444
  attempt: args.attempt,
216
445
  durationMs,
446
+ approveToCompleteMs,
217
447
  hasError: Boolean(args.error),
218
448
  errorCode: args.error?.code ?? null,
219
449
  isScaffold: resources?.scaffoldBaseWorkItemId === args.workItemId,
450
+ projectContextKind,
451
+ tokensIn: args.tokensIn ?? null,
452
+ tokensOut: args.tokensOut ?? null,
453
+ model: args.model ?? null,
454
+ outputBytes,
220
455
  });
221
456
  }
222
457
  if (contracts_1.TERMINAL_STAGES.has(result.stage)) {
@@ -224,6 +459,9 @@ class AgentVariantsOrchestrator {
224
459
  if (resources && resources.terminalAt === undefined) {
225
460
  resources.terminalAt = Date.now();
226
461
  const totalLatencyMs = resources.terminalAt - resources.startedAt;
462
+ const approveToTerminalMs = resources.approveStartedAt !== undefined
463
+ ? resources.terminalAt - resources.approveStartedAt
464
+ : null;
227
465
  const summary = result.summary ?? this.store.getSummary(args.sessionId);
228
466
  this.telemetry.track('agent_variants.terminal_state', {
229
467
  source: 'mcp',
@@ -233,11 +471,17 @@ class AgentVariantsOrchestrator {
233
471
  failureCount: summary.failureCount,
234
472
  cancelledCount: summary.cancelledCount,
235
473
  totalLatencyMs,
474
+ approveToTerminalMs,
475
+ projectContextKind: this.store.getProjectContext(args.sessionId).kind,
236
476
  });
237
477
  }
238
478
  }
239
479
  if (args.status === 'succeeded') {
240
- void this.handleSucceededReport(args.sessionId, args.workItemId).catch((err) => {
480
+ void this.handleSucceededReport({
481
+ sessionId: args.sessionId,
482
+ workItemId: args.workItemId,
483
+ output: args.output,
484
+ }).catch((err) => {
241
485
  log.error(`handleSucceededReport failed for ${args.sessionId}/${args.workItemId}`, err);
242
486
  });
243
487
  }
@@ -267,6 +511,37 @@ class AgentVariantsOrchestrator {
267
511
  });
268
512
  return result;
269
513
  }
514
+ /**
515
+ * Cancel a single variant within a session. Siblings continue. Idempotent
516
+ * on terminal states. Per-variant worktree teardown is deferred to a
517
+ * follow-up; the lease is released, so requestWork won't re-surface this
518
+ * variant and the agent's report_variant_complete for it (if it lands
519
+ * after cancellation) is rejected by the stale-lease check.
520
+ */
521
+ async cancelVariant(args) {
522
+ const result = this.store.cancelWorkItem({
523
+ sessionId: args.sessionId,
524
+ workItemId: args.variantId,
525
+ reason: args.reason,
526
+ });
527
+ this.telemetry.track('agent_variants.variant_cancelled', {
528
+ source: 'mcp',
529
+ sessionId: args.sessionId,
530
+ variantId: args.variantId,
531
+ finalStatus: result.finalStatus,
532
+ sessionStage: result.sessionStage,
533
+ alreadyTerminal: result.alreadyTerminal,
534
+ reason: args.reason ?? null,
535
+ });
536
+ this.emitChange();
537
+ return {
538
+ sessionId: args.sessionId,
539
+ variantId: args.variantId,
540
+ finalStatus: result.finalStatus,
541
+ sessionStage: result.sessionStage,
542
+ alreadyTerminal: result.alreadyTerminal,
543
+ };
544
+ }
270
545
  /**
271
546
  * User has reviewed the rendered variants in chat and picked one. Look up
272
547
  * the captured diff, build a VariantPickEnvelope, and enqueue to the
@@ -284,43 +559,74 @@ class AgentVariantsOrchestrator {
284
559
  enqueued: false,
285
560
  duplicate: true,
286
561
  changedFilesCount: existingPick.changedFilesCount,
562
+ payloadKind: existingPick.payload.kind === 'new-project'
563
+ ? 'project-created'
564
+ : existingPick.payload.kind,
565
+ destinationPath: existingPick.destinationPath,
287
566
  };
288
567
  }
289
568
  const resources = this.resources.get(args.sessionId);
290
569
  if (!resources) {
291
570
  throw new errors_1.AgentVariantsError('SESSION_NOT_FOUND', `No live resources for session ${args.sessionId} (already torn down?)`);
292
571
  }
293
- const record = resources.worktrees.get(args.variantId);
294
- if (!record) {
295
- throw new errors_1.AgentVariantsError('WORK_ITEM_NOT_FOUND', `Unknown variantId ${args.variantId} for session ${args.sessionId}`);
296
- }
297
- if (record.diff === undefined) {
298
- throw new errors_1.AgentVariantsError('INVALID_STAGE_ACTION', `Variant ${args.variantId} has no captured diff yet — wait for report_variant_complete(succeeded) first`);
572
+ const variantSnapshot = this.getVariants(args.sessionId).find((variant) => variant.workItemId === args.variantId);
573
+ if (!variantSnapshot || variantSnapshot.actions?.commit?.enabled !== true) {
574
+ throw new errors_1.AgentVariantsError('INVALID_STAGE_ACTION', variantSnapshot?.actions?.commit?.reason ?? 'Variant is not committable');
299
575
  }
300
576
  const env = await this.resolveEnv(args.sessionId);
301
577
  const input = this.store.getWorkItemInput(args.sessionId, args.variantId);
302
578
  const projectContext = this.store.getProjectContext(args.sessionId);
303
- const changedFilesCount = countDiffFiles(record.diff);
304
- const payload = projectContext.kind === 'fresh'
305
- ? {
306
- kind: 'new-project',
307
- destinationPath: projectContext.workspacePath,
308
- sourceWorktreePath: record.worktreePath,
579
+ let payload;
580
+ let envelopeDestination;
581
+ let changedFilesCount;
582
+ if (projectContext.kind === 'fresh') {
583
+ // Fresh sessions: static preview HTML is the deliverable. Write index.html
584
+ // to the destination directory.
585
+ const destinationPath = projectContext.workspacePath;
586
+ this.assertDestinationAvailable(destinationPath);
587
+ const staticPreview = resources.staticPreviews.get(args.variantId);
588
+ if (!staticPreview) {
589
+ throw new errors_1.AgentVariantsError('INVALID_STAGE_ACTION', `No static preview found for variant ${args.variantId} — wait for report_variant_complete(succeeded) first`);
590
+ }
591
+ try {
592
+ fs_1.default.mkdirSync(destinationPath, { recursive: true });
593
+ fs_1.default.writeFileSync(path_1.default.join(destinationPath, 'index.html'), staticPreview.html, 'utf8');
594
+ }
595
+ catch (err) {
596
+ const message = err instanceof Error ? err.message : String(err);
597
+ throw new errors_1.AgentVariantsError('RUNTIME_VALIDATION_FAILED', `Failed to write static preview to ${destinationPath}: ${message}`);
598
+ }
599
+ changedFilesCount = 1;
600
+ payload = {
601
+ kind: 'project-created',
602
+ destinationPath,
309
603
  changedFilesCount,
604
+ note: 'Static preview written to index.html at destinationPath.',
605
+ };
606
+ envelopeDestination = destinationPath;
607
+ }
608
+ else {
609
+ const record = resources.worktrees.get(args.variantId);
610
+ if (!record) {
611
+ throw new errors_1.AgentVariantsError('WORK_ITEM_NOT_FOUND', `Unknown variantId ${args.variantId} for session ${args.sessionId}`);
612
+ }
613
+ if (record.diff === undefined) {
614
+ throw new errors_1.AgentVariantsError('INVALID_STAGE_ACTION', `Variant ${args.variantId} has no captured diff yet — wait for report_variant_complete(succeeded) first`);
310
615
  }
311
- : {
616
+ changedFilesCount = countDiffFiles(record.diff);
617
+ payload = {
312
618
  kind: 'diff',
313
619
  diff: record.diff,
314
620
  target: input.target,
315
621
  changedFilesCount,
316
622
  };
623
+ envelopeDestination = env.projectPath;
624
+ }
317
625
  const envelope = {
318
626
  sourceSessionId: args.sessionId,
319
627
  variantId: args.variantId,
320
628
  variantLabel: input.briefLabel,
321
- destinationPath: projectContext.kind === 'fresh'
322
- ? projectContext.workspacePath
323
- : env.projectPath,
629
+ destinationPath: envelopeDestination,
324
630
  changedFilesCount,
325
631
  payload,
326
632
  };
@@ -350,24 +656,57 @@ class AgentVariantsOrchestrator {
350
656
  });
351
657
  this.emitChange();
352
658
  log.info(`Variant ${args.variantId} (${input.briefLabel}) committed by user → enqueued=${enqueueResult.enqueued}`);
353
- // Once a variant is committed, the unpicked worktrees are no longer
354
- // useful. Schedule teardown in the background so the agent's
355
- // commit_variant call returns immediately.
356
- void this.teardownSession(args.sessionId, 'committed').catch((err) => {
357
- log.warn(`teardownSession after commit failed for ${args.sessionId}`, err);
358
- });
659
+ if (!args.skipTeardown) {
660
+ // Once a variant is committed, the unpicked worktrees are no longer
661
+ // useful. Schedule teardown in the background so the agent's
662
+ // commit_variant call returns immediately.
663
+ void this.teardownSession(args.sessionId, 'committed').catch((err) => {
664
+ log.warn(`teardownSession after commit failed for ${args.sessionId}`, err);
665
+ });
666
+ }
359
667
  return {
360
668
  enqueued: enqueueResult.enqueued,
361
669
  duplicate: enqueueResult.duplicate,
362
670
  changedFilesCount,
671
+ payloadKind: payload.kind,
672
+ destinationPath: envelopeDestination,
363
673
  };
364
674
  }
675
+ async cleanupCommittedSession(sessionId) {
676
+ await this.teardownSession(sessionId, 'committed');
677
+ }
678
+ /**
679
+ * Ensure the user-facing destination path can receive the new project.
680
+ * Rejects when the path exists and is non-empty.
681
+ */
682
+ assertDestinationAvailable(destinationPath) {
683
+ if (!fs_1.default.existsSync(destinationPath))
684
+ return;
685
+ const entries = fs_1.default.readdirSync(destinationPath);
686
+ if (entries.length === 0)
687
+ return;
688
+ throw new errors_1.AgentVariantsError('DESTINATION_NOT_EMPTY', `Destination ${destinationPath} is not empty (${entries.length} entries) — refuse to materialize.`);
689
+ }
365
690
  /** Read the captured diff for a code-gen variant, if available. */
366
691
  getVariantDiff(sessionId, variantId) {
367
692
  return this.resources.get(sessionId)?.worktrees.get(variantId)?.diff;
368
693
  }
369
694
  // --- Side-effect implementations ----------------------------------------
370
695
  async provisionWorktrees(sessionId, approveResult) {
696
+ const projectContext = this.store.getProjectContext(sessionId);
697
+ if (projectContext.kind === 'fresh') {
698
+ // Fresh sessions use static_preview as the final deliverable — no
699
+ // worktrees, no scaffold, no npm install needed.
700
+ return;
701
+ }
702
+ await this.provisionExistingWorktrees(sessionId, approveResult);
703
+ }
704
+ /**
705
+ * Existing-project flow: clone the user's repo N times via git worktree.
706
+ * Runs synchronously because the operation is fast (no install needed —
707
+ * node_modules is symlinked from the source project).
708
+ */
709
+ async provisionExistingWorktrees(sessionId, approveResult) {
371
710
  const resources = this.ensureResources(sessionId);
372
711
  const totalCount = approveResult.codeGenWorkItemIds.length +
373
712
  (approveResult.scaffoldBaseWorkItemId ? 1 : 0);
@@ -385,14 +724,143 @@ class AgentVariantsOrchestrator {
385
724
  cursor += 1;
386
725
  }
387
726
  for (const id of approveResult.codeGenWorkItemIds) {
388
- const path = paths[cursor];
727
+ const p = paths[cursor];
389
728
  cursor += 1;
390
- resources.worktrees.set(id, { workItemId: id, worktreePath: path });
729
+ resources.worktrees.set(id, { workItemId: id, worktreePath: p });
730
+ }
731
+ }
732
+ /**
733
+ * Fresh-project flow: scaffold one template per variant + DESIGN.md per
734
+ * slot synchronously (fast — just file writes + git init), then dispatch a
735
+ * background `npm install` task per worktree. Returns immediately so the
736
+ * `approve_variant_briefs` MCP tool doesn't time out on cold installs.
737
+ *
738
+ * On install success: `sessionStore.completeInternal(scaffold_base)` runs,
739
+ * which unblocks the dependent code_gen items.
740
+ *
741
+ * On install failure: the scaffold_base work item is marked failed via
742
+ * `failInternal`, which cascades DEPENDENCY_FAILED to all dependent
743
+ * code_gen items so the session degrades cleanly.
744
+ */
745
+ async provisionFreshWorktrees(sessionId, approveResult, projectContext) {
746
+ const resources = this.ensureResources(sessionId);
747
+ const scaffoldId = approveResult.scaffoldBaseWorkItemId;
748
+ if (!scaffoldId) {
749
+ throw new errors_1.AgentVariantsError('INVALID_STAGE_ACTION', `Fresh session ${sessionId} approved without a scaffold_base item`);
750
+ }
751
+ const codeGenIds = approveResult.codeGenWorkItemIds;
752
+ if (codeGenIds.length === 0)
753
+ return;
754
+ const designContext = projectContext.designContext
755
+ ? codeGenIds.map((codeGenId) => {
756
+ const input = this.store.getWorkItemInput(sessionId, codeGenId);
757
+ return input.designContextEntry;
758
+ })
759
+ : undefined;
760
+ const sourceContext = projectContext.sourceContext;
761
+ const createFresh = this.worktrees.createFreshWorktrees;
762
+ if (!createFresh) {
763
+ throw new errors_1.AgentVariantsError('RUNTIME_VALIDATION_FAILED', 'worktreeManager does not implement createFreshWorktrees');
764
+ }
765
+ const scaffoldStartedAt = Date.now();
766
+ trackScaffoldStarted(this.telemetry, {
767
+ sessionId,
768
+ variantCount: codeGenIds.length,
769
+ designContext: summarizeDesignContext(designContext),
770
+ });
771
+ log.info(`Provisioning ${codeGenIds.length} fresh worktree(s) for session ${sessionId}`);
772
+ const paths = await createFresh.call(this.worktrees, sessionId, codeGenIds.length, viteReactTs_1.VITE_REACT_TS_TEMPLATE, designContext, sourceContext);
773
+ resources.scaffoldBaseWorkItemId = scaffoldId;
774
+ // Each code_gen item maps 1:1 to a fresh worktree. The scaffold_base
775
+ // work item is internal — no dedicated worktree.
776
+ for (let i = 0; i < codeGenIds.length; i++) {
777
+ resources.worktrees.set(codeGenIds[i], {
778
+ workItemId: codeGenIds[i],
779
+ worktreePath: paths[i],
780
+ });
781
+ }
782
+ // Background install: don't await. The orchestrator finishes the
783
+ // approve→provision call immediately; the agent polls for scaffold
784
+ // completion via continue_variants(action='check').
785
+ void this.runBackgroundInstall(sessionId, scaffoldId, paths, scaffoldStartedAt).catch((err) => {
786
+ log.error(`runBackgroundInstall failed for session ${sessionId}`, err);
787
+ });
788
+ }
789
+ /**
790
+ * Run `npm install` in parallel across all fresh worktrees. On all-success,
791
+ * mark scaffold_base succeeded via `completeInternal`. On any failure,
792
+ * mark scaffold_base failed via `failInternal` and emit telemetry.
793
+ */
794
+ async runBackgroundInstall(sessionId, scaffoldWorkItemId, worktreePaths, scaffoldStartedAt) {
795
+ const installStartedAt = Date.now();
796
+ try {
797
+ await Promise.all(worktreePaths.map((p) => this.installDependencies(p)));
391
798
  }
799
+ catch (err) {
800
+ const message = err instanceof Error ? err.message : String(err);
801
+ log.warn(`npm install failed for session ${sessionId}; marking scaffold_base failed`, err);
802
+ trackScaffoldFailed(this.telemetry, {
803
+ sessionId,
804
+ errorCode: 'SCAFFOLD_FAILED',
805
+ durationMs: Date.now() - scaffoldStartedAt,
806
+ });
807
+ try {
808
+ this.store.failInternal({
809
+ sessionId,
810
+ workItemId: scaffoldWorkItemId,
811
+ error: { code: 'SCAFFOLD_FAILED', message },
812
+ });
813
+ this.emitChange();
814
+ }
815
+ catch (storeErr) {
816
+ log.error(`failInternal failed for ${sessionId}/${scaffoldWorkItemId}`, storeErr);
817
+ }
818
+ return;
819
+ }
820
+ try {
821
+ this.store.completeInternal({
822
+ sessionId,
823
+ workItemId: scaffoldWorkItemId,
824
+ output: { worktreePaths },
825
+ });
826
+ this.emitChange();
827
+ }
828
+ catch (err) {
829
+ log.warn(`completeInternal failed for ${sessionId}/${scaffoldWorkItemId}`, err);
830
+ }
831
+ trackScaffoldCompleted(this.telemetry, {
832
+ sessionId,
833
+ durationMs: Date.now() - scaffoldStartedAt,
834
+ installDurationMs: Date.now() - installStartedAt,
835
+ variantCount: worktreePaths.length,
836
+ });
392
837
  }
393
- async handleSucceededReport(sessionId, workItemId) {
838
+ async handleSucceededReport(args) {
839
+ const { sessionId, workItemId } = args;
394
840
  const resources = this.resources.get(sessionId);
395
841
  const record = resources?.worktrees.get(workItemId);
842
+ if (resources && !record) {
843
+ const staticPreview = parseStaticPreviewOutput(normalizeOutput(args.output));
844
+ if (staticPreview) {
845
+ const input = this.store.getWorkItemInput(sessionId, workItemId);
846
+ if (input.briefId) {
847
+ const record = {
848
+ workItemId,
849
+ briefId: input.briefId,
850
+ html: staticPreview.html,
851
+ };
852
+ resources.staticPreviews.set(workItemId, record);
853
+ const leasedAt = resources.leasedAt.get(workItemId);
854
+ trackStaticPreviewCompleted(this.telemetry, {
855
+ sessionId,
856
+ variantId: workItemId,
857
+ durationMs: leasedAt !== undefined ? Date.now() - leasedAt : null,
858
+ });
859
+ this.emitChange();
860
+ }
861
+ return;
862
+ }
863
+ }
396
864
  if (!resources || !record) {
397
865
  log.warn(`No worktree record for ${sessionId}/${workItemId}; skipping diff capture`);
398
866
  return;
@@ -403,6 +871,8 @@ class AgentVariantsOrchestrator {
403
871
  log.info(`scaffold_base ${workItemId} succeeded; awaiting dependent code-gen`);
404
872
  return;
405
873
  }
874
+ const projectContext = this.store.getProjectContext(sessionId);
875
+ const isFresh = projectContext.kind === 'fresh';
406
876
  try {
407
877
  record.diff = await this.worktrees.getDiff(record.worktreePath);
408
878
  log.info(`Variant ${workItemId} diff captured (${countDiffFiles(record.diff)} files)`);
@@ -415,26 +885,62 @@ class AgentVariantsOrchestrator {
415
885
  // through live variants in the iframe via the chip. Failures here are
416
886
  // logged but non-fatal — the user can still pick by reading the diff.
417
887
  try {
418
- const env = await this.resolveEnv(sessionId);
419
888
  const port = await this.worktrees.getFreePort();
420
- const cwd = await this.worktrees.getProjectCwdInWorktree(record.worktreePath);
421
- const { cmd, args, env: spawnEnv } = env.buildDevCommand
422
- ? env.buildDevCommand(port)
423
- : {
424
- cmd: env.packageManager,
425
- args: [env.devCommand, '--port', String(port)],
426
- env: { PORT: String(port) },
427
- };
428
- const proc = await this.worktrees.startDevServer(cwd, port, cmd, args, spawnEnv);
889
+ const dev = await this.resolveDevServer(sessionId, record.worktreePath, port, isFresh);
890
+ const proc = await this.worktrees.startDevServer(dev.cwd, port, dev.cmd, dev.args, dev.env);
429
891
  record.port = port;
430
892
  record.devServerProcess = proc;
431
893
  this.emitChange();
432
- log.info(`Variant ${workItemId} dev server up on port ${port} (worktree ${record.worktreePath}; cmd: ${cmd} ${args.join(' ')})`);
894
+ trackFreshDevServerStarted(this.telemetry, {
895
+ sessionId,
896
+ variantId: workItemId,
897
+ port,
898
+ });
899
+ log.info(`Variant ${workItemId} dev server up on port ${port} (worktree ${record.worktreePath}; cmd: ${dev.cmd} ${dev.args.join(' ')})`);
433
900
  }
434
901
  catch (err) {
902
+ trackFreshDevServerFailed(this.telemetry, {
903
+ sessionId,
904
+ variantId: workItemId,
905
+ errorCode: 'DEV_SERVER_START_FAILED',
906
+ });
435
907
  log.warn(`Failed to start dev server for variant ${workItemId}; live preview disabled for this variant`, err);
436
908
  }
437
909
  }
910
+ /**
911
+ * Resolve dev server invocation for a worktree. Fresh-project worktrees
912
+ * always use the Vite template's npm command at the worktree root; existing
913
+ * projects defer to the user's framework/packageManager config.
914
+ */
915
+ async resolveDevServer(sessionId, worktreePath, port, isFresh) {
916
+ if (isFresh) {
917
+ return {
918
+ cwd: worktreePath,
919
+ cmd: 'npm',
920
+ args: [
921
+ 'run',
922
+ 'dev',
923
+ '--',
924
+ '--port',
925
+ String(port),
926
+ '--host',
927
+ FRESH_DEV_SERVER_HOST,
928
+ ],
929
+ env: { PORT: String(port) },
930
+ };
931
+ }
932
+ const env = await this.resolveEnv(sessionId);
933
+ const cwd = await this.worktrees.getProjectCwdInWorktree(worktreePath);
934
+ if (env.buildDevCommand) {
935
+ return { cwd, ...env.buildDevCommand(port) };
936
+ }
937
+ return {
938
+ cwd,
939
+ cmd: env.packageManager,
940
+ args: [env.devCommand, '--port', String(port)],
941
+ env: { PORT: String(port) },
942
+ };
943
+ }
438
944
  async teardownSession(sessionId, reason) {
439
945
  const resources = this.resources.get(sessionId);
440
946
  if (!resources)
@@ -464,6 +970,7 @@ class AgentVariantsOrchestrator {
464
970
  r = {
465
971
  sessionId,
466
972
  worktrees: new Map(),
973
+ staticPreviews: new Map(),
467
974
  cleanupStarted: false,
468
975
  committedVariantIds: new Set(),
469
976
  startedAt: Date.now(),
@@ -475,8 +982,260 @@ class AgentVariantsOrchestrator {
475
982
  }
476
983
  }
477
984
  exports.AgentVariantsOrchestrator = AgentVariantsOrchestrator;
985
+ /** MCP bridges sometimes serialize the output field as a JSON string instead
986
+ * of a parsed object. Normalize it before field-level inspection. */
987
+ /** Rough byte-size of the agent's output. Used as a proxy for "how much
988
+ * code did the agent write" when comparing HTML vs full-stack flows. */
989
+ function estimateOutputBytes(output) {
990
+ if (output === undefined || output === null)
991
+ return 0;
992
+ try {
993
+ return typeof output === 'string'
994
+ ? output.length
995
+ : JSON.stringify(output).length;
996
+ }
997
+ catch {
998
+ return 0;
999
+ }
1000
+ }
1001
+ function normalizeOutput(output) {
1002
+ if (typeof output === 'string') {
1003
+ try {
1004
+ return JSON.parse(output);
1005
+ }
1006
+ catch {
1007
+ return output;
1008
+ }
1009
+ }
1010
+ return output;
1011
+ }
1012
+ function parseStaticPreviewOutput(output) {
1013
+ if (!output || typeof output !== 'object')
1014
+ return null;
1015
+ const html = output.html;
1016
+ if (typeof html !== 'string' || html.trim().length === 0)
1017
+ return null;
1018
+ const css = output.css;
1019
+ const js = output.js;
1020
+ return {
1021
+ html: buildStaticPreviewDocument({
1022
+ html,
1023
+ css: typeof css === 'string' ? css : undefined,
1024
+ js: typeof js === 'string' ? js : undefined,
1025
+ }),
1026
+ };
1027
+ }
1028
+ function buildStaticPreviewDocument(input) {
1029
+ if (/<!doctype html>|<html[\s>]/i.test(input.html)) {
1030
+ return input.html;
1031
+ }
1032
+ const style = input.css ? `<style>\n${input.css}\n</style>` : '';
1033
+ const script = input.js ? `<script>\n${input.js}\n</script>` : '';
1034
+ return `<!doctype html>
1035
+ <html lang="en">
1036
+ <head>
1037
+ <meta charset="UTF-8" />
1038
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
1039
+ ${style}
1040
+ </head>
1041
+ <body>
1042
+ ${input.html}
1043
+ ${script}
1044
+ </body>
1045
+ </html>`;
1046
+ }
478
1047
  function countDiffFiles(diff) {
479
1048
  // Each file in a unified diff starts with "diff --git ".
480
1049
  return (diff.match(/^diff --git /gm) ?? []).length;
481
1050
  }
1051
+ const enrichDesignSource = (design) => {
1052
+ if (!design || design.kind !== 'slug')
1053
+ return design;
1054
+ return {
1055
+ ...design,
1056
+ displayName: (0, designCatalog_1.getDesignSystemBySlug)(design.slug)?.name ?? design.slug,
1057
+ };
1058
+ };
1059
+ const toActiveProjectContext = (projectContext) => {
1060
+ if (projectContext.kind === 'existing') {
1061
+ return { kind: 'existing' };
1062
+ }
1063
+ return {
1064
+ kind: 'fresh',
1065
+ workspacePath: projectContext.workspacePath,
1066
+ framework: projectContext.framework,
1067
+ designContext: projectContext.designContext?.map((entry) => entry.kind === 'slug'
1068
+ ? { kind: 'slug', slug: entry.slug }
1069
+ : { kind: 'markdown', label: entry.label }),
1070
+ ...(projectContext.sourceContext
1071
+ ? {
1072
+ sourceContext: {
1073
+ sourceCount: projectContext.sourceContext.sourceUrls?.length ?? 0,
1074
+ isSourceGrounded: Boolean(projectContext.sourceContext.artifact),
1075
+ },
1076
+ }
1077
+ : {}),
1078
+ };
1079
+ };
1080
+ const summarizeDesignContext = (designContext) => {
1081
+ if (!designContext)
1082
+ return null;
1083
+ return designContext.reduce((entries, entry, slot) => {
1084
+ if (!entry)
1085
+ return entries;
1086
+ if (entry.kind === 'slug') {
1087
+ entries.push({ slot, kind: 'slug', slug: entry.slug });
1088
+ return entries;
1089
+ }
1090
+ entries.push({
1091
+ slot,
1092
+ kind: 'markdown',
1093
+ markdownLabel: entry.label,
1094
+ });
1095
+ return entries;
1096
+ }, []);
1097
+ };
1098
+ const trackScaffoldStarted = (telemetry, data) => {
1099
+ if (telemetry.trackAgentVariantsScaffoldStarted) {
1100
+ telemetry.trackAgentVariantsScaffoldStarted(data);
1101
+ return;
1102
+ }
1103
+ telemetry.track('agent_variants.scaffold_started', {
1104
+ source: 'mcp',
1105
+ session_id: data.sessionId,
1106
+ variant_count: data.variantCount,
1107
+ design_context: data.designContext ?? null,
1108
+ });
1109
+ };
1110
+ const trackScaffoldCompleted = (telemetry, data) => {
1111
+ if (telemetry.trackAgentVariantsScaffoldCompleted) {
1112
+ telemetry.trackAgentVariantsScaffoldCompleted(data);
1113
+ return;
1114
+ }
1115
+ telemetry.track('agent_variants.scaffold_completed', {
1116
+ source: 'mcp',
1117
+ session_id: data.sessionId,
1118
+ duration_ms: data.durationMs,
1119
+ install_duration_ms: data.installDurationMs,
1120
+ variant_count: data.variantCount,
1121
+ });
1122
+ };
1123
+ const trackScaffoldFailed = (telemetry, data) => {
1124
+ if (telemetry.trackAgentVariantsScaffoldFailed) {
1125
+ telemetry.trackAgentVariantsScaffoldFailed(data);
1126
+ return;
1127
+ }
1128
+ telemetry.track('agent_variants.scaffold_failed', {
1129
+ source: 'mcp',
1130
+ session_id: data.sessionId,
1131
+ error_code: data.errorCode,
1132
+ duration_ms: data.durationMs,
1133
+ });
1134
+ };
1135
+ const trackFreshDevServerStarted = (telemetry, data) => {
1136
+ if (telemetry.trackAgentVariantsFreshDevServerStarted) {
1137
+ telemetry.trackAgentVariantsFreshDevServerStarted(data);
1138
+ return;
1139
+ }
1140
+ telemetry.track('agent_variants.fresh_dev_server_started', {
1141
+ source: 'mcp',
1142
+ session_id: data.sessionId,
1143
+ variant_id: data.variantId,
1144
+ port: data.port,
1145
+ });
1146
+ };
1147
+ const trackFreshDevServerFailed = (telemetry, data) => {
1148
+ if (telemetry.trackAgentVariantsFreshDevServerFailed) {
1149
+ telemetry.trackAgentVariantsFreshDevServerFailed(data);
1150
+ return;
1151
+ }
1152
+ telemetry.track('agent_variants.fresh_dev_server_failed', {
1153
+ source: 'mcp',
1154
+ session_id: data.sessionId,
1155
+ variant_id: data.variantId,
1156
+ error_code: data.errorCode,
1157
+ });
1158
+ };
1159
+ const trackStaticPreviewCompleted = (telemetry, data) => {
1160
+ if (telemetry.trackAgentVariantsStaticPreviewCompleted) {
1161
+ telemetry.trackAgentVariantsStaticPreviewCompleted(data);
1162
+ return;
1163
+ }
1164
+ telemetry.track('agent_variants.static_preview_completed', {
1165
+ source: 'mcp',
1166
+ session_id: data.sessionId,
1167
+ variant_id: data.variantId,
1168
+ duration_ms: data.durationMs,
1169
+ });
1170
+ };
1171
+ const MATERIALIZE_EXCLUDE = new Set([
1172
+ 'node_modules',
1173
+ '.git',
1174
+ 'dist',
1175
+ '.next',
1176
+ '.cache',
1177
+ '.vite',
1178
+ ]);
1179
+ const defaultInstallDependencies = (worktreePath) => {
1180
+ return new Promise((resolve, reject) => {
1181
+ const proc = (0, child_process_1.spawn)('npm', ['install', '--no-audit', '--no-fund', '--ignore-scripts'], {
1182
+ cwd: worktreePath,
1183
+ stdio: ['ignore', 'ignore', 'pipe'],
1184
+ });
1185
+ let stderr = '';
1186
+ proc.stderr.on('data', (chunk) => {
1187
+ stderr += chunk.toString();
1188
+ });
1189
+ proc.on('error', reject);
1190
+ proc.on('exit', (code) => {
1191
+ if (code === 0) {
1192
+ resolve();
1193
+ return;
1194
+ }
1195
+ reject(new Error(`npm install in ${worktreePath} failed (code ${code}): ${stderr.slice(-512)}`));
1196
+ });
1197
+ });
1198
+ };
1199
+ const defaultMaterializeProject = async (sourceWorktreePath, destinationPath) => {
1200
+ await fs_1.default.promises.mkdir(destinationPath, { recursive: true });
1201
+ await fs_1.default.promises.cp(sourceWorktreePath, destinationPath, {
1202
+ recursive: true,
1203
+ filter: (sourcePath) => {
1204
+ const rel = path_1.default.relative(sourceWorktreePath, sourcePath);
1205
+ if (!rel)
1206
+ return true;
1207
+ const top = rel.split(path_1.default.sep)[0];
1208
+ return !MATERIALIZE_EXCLUDE.has(top);
1209
+ },
1210
+ });
1211
+ const git = (0, simple_git_1.simpleGit)(destinationPath);
1212
+ await git.raw(['init']);
1213
+ await git.raw(['add', '-A']);
1214
+ await git
1215
+ .raw([
1216
+ '-c',
1217
+ 'user.name=Rivet',
1218
+ '-c',
1219
+ 'user.email=hello@tryrivet.design',
1220
+ 'commit',
1221
+ '--no-gpg-sign',
1222
+ '-m',
1223
+ 'Initial commit (created with Rivet)',
1224
+ ])
1225
+ .catch(() => {
1226
+ // Best-effort: a missing user identity or empty tree leaves the
1227
+ // commit step un-applied. The destination still has the files.
1228
+ });
1229
+ };
1230
+ /**
1231
+ * Synthesize a placeholder brief body for a unified start_variants slot.
1232
+ * The body must fit `briefSchema.body` (≤ 200 chars, ≥ 1 char). The agent's
1233
+ * streamed first-line label replaces this in the UI once code_gen output
1234
+ * arrives. Index/total are unused for now but kept in the signature so
1235
+ * future diversity hints can be added without a call-site change.
1236
+ */
1237
+ function synthesizeUnifiedBriefBody(prompt, _index, _total) {
1238
+ const trimmed = prompt.trim().replace(/\s+/g, ' ').slice(0, 200);
1239
+ return trimmed.length > 0 ? trimmed : 'variant';
1240
+ }
482
1241
  //# sourceMappingURL=WorktreeOrchestrator.js.map