rivet-design 0.9.2 → 0.9.4

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 (157) hide show
  1. package/dist/mcp/agent-variants/SessionStore.d.ts +78 -2
  2. package/dist/mcp/agent-variants/SessionStore.d.ts.map +1 -1
  3. package/dist/mcp/agent-variants/SessionStore.js +464 -62
  4. package/dist/mcp/agent-variants/SessionStore.js.map +1 -1
  5. package/dist/mcp/agent-variants/WorktreeOrchestrator.d.ts +331 -9
  6. package/dist/mcp/agent-variants/WorktreeOrchestrator.d.ts.map +1 -1
  7. package/dist/mcp/agent-variants/WorktreeOrchestrator.js +1985 -61
  8. package/dist/mcp/agent-variants/WorktreeOrchestrator.js.map +1 -1
  9. package/dist/mcp/agent-variants/WorktreeOrchestrator.testHelpers.d.ts +65 -0
  10. package/dist/mcp/agent-variants/WorktreeOrchestrator.testHelpers.d.ts.map +1 -0
  11. package/dist/mcp/agent-variants/WorktreeOrchestrator.testHelpers.js +162 -0
  12. package/dist/mcp/agent-variants/WorktreeOrchestrator.testHelpers.js.map +1 -0
  13. package/dist/mcp/agent-variants/contracts.d.ts +2508 -10
  14. package/dist/mcp/agent-variants/contracts.d.ts.map +1 -1
  15. package/dist/mcp/agent-variants/contracts.js +295 -5
  16. package/dist/mcp/agent-variants/contracts.js.map +1 -1
  17. package/dist/mcp/agent-variants/createProjectArtifacts.d.ts +78 -0
  18. package/dist/mcp/agent-variants/createProjectArtifacts.d.ts.map +1 -0
  19. package/dist/mcp/agent-variants/createProjectArtifacts.js +123 -0
  20. package/dist/mcp/agent-variants/createProjectArtifacts.js.map +1 -0
  21. package/dist/mcp/agent-variants/createZeroToOneTool.d.ts +241 -0
  22. package/dist/mcp/agent-variants/createZeroToOneTool.d.ts.map +1 -0
  23. package/dist/mcp/agent-variants/createZeroToOneTool.js +213 -0
  24. package/dist/mcp/agent-variants/createZeroToOneTool.js.map +1 -0
  25. package/dist/mcp/agent-variants/designContextStore.d.ts +160 -0
  26. package/dist/mcp/agent-variants/designContextStore.d.ts.map +1 -0
  27. package/dist/mcp/agent-variants/designContextStore.js +295 -0
  28. package/dist/mcp/agent-variants/designContextStore.js.map +1 -0
  29. package/dist/mcp/agent-variants/elementRefToTarget.d.ts +21 -0
  30. package/dist/mcp/agent-variants/elementRefToTarget.d.ts.map +1 -0
  31. package/dist/mcp/agent-variants/elementRefToTarget.js +47 -0
  32. package/dist/mcp/agent-variants/elementRefToTarget.js.map +1 -0
  33. package/dist/mcp/agent-variants/errors.d.ts +1 -1
  34. package/dist/mcp/agent-variants/errors.d.ts.map +1 -1
  35. package/dist/mcp/agent-variants/errors.js +7 -0
  36. package/dist/mcp/agent-variants/errors.js.map +1 -1
  37. package/dist/mcp/agent-variants/index.d.ts +4 -2
  38. package/dist/mcp/agent-variants/index.d.ts.map +1 -1
  39. package/dist/mcp/agent-variants/index.js +7 -1
  40. package/dist/mcp/agent-variants/index.js.map +1 -1
  41. package/dist/mcp/agent-variants/inspirationDesignContext.d.ts +440 -0
  42. package/dist/mcp/agent-variants/inspirationDesignContext.d.ts.map +1 -0
  43. package/dist/mcp/agent-variants/inspirationDesignContext.js +2467 -0
  44. package/dist/mcp/agent-variants/inspirationDesignContext.js.map +1 -0
  45. package/dist/mcp/agent-variants/pendingChangesAdapter.d.ts.map +1 -1
  46. package/dist/mcp/agent-variants/pendingChangesAdapter.js +21 -7
  47. package/dist/mcp/agent-variants/pendingChangesAdapter.js.map +1 -1
  48. package/dist/mcp/agent-variants/previewQa.d.ts +61 -0
  49. package/dist/mcp/agent-variants/previewQa.d.ts.map +1 -0
  50. package/dist/mcp/agent-variants/previewQa.js +374 -0
  51. package/dist/mcp/agent-variants/previewQa.js.map +1 -0
  52. package/dist/mcp/agent-variants/sourceContext.d.ts +8 -0
  53. package/dist/mcp/agent-variants/sourceContext.d.ts.map +1 -0
  54. package/dist/mcp/agent-variants/sourceContext.js +183 -0
  55. package/dist/mcp/agent-variants/sourceContext.js.map +1 -0
  56. package/dist/mcp/agent-variants/tools.d.ts +36 -0
  57. package/dist/mcp/agent-variants/tools.d.ts.map +1 -1
  58. package/dist/mcp/agent-variants/tools.js +451 -19
  59. package/dist/mcp/agent-variants/tools.js.map +1 -1
  60. package/dist/mcp/changeBatchClassification.d.ts +30 -0
  61. package/dist/mcp/changeBatchClassification.d.ts.map +1 -0
  62. package/dist/mcp/changeBatchClassification.js +65 -0
  63. package/dist/mcp/changeBatchClassification.js.map +1 -0
  64. package/dist/mcp/server.d.ts.map +1 -1
  65. package/dist/mcp/server.js +258 -41
  66. package/dist/mcp/server.js.map +1 -1
  67. package/dist/prompts/agentModPrompts.js +4 -4
  68. package/dist/prompts/agentModPrompts.js.map +1 -1
  69. package/dist/proxy-middleware/proxy-config.d.ts.map +1 -1
  70. package/dist/proxy-middleware/proxy-config.js +1 -15
  71. package/dist/proxy-middleware/proxy-config.js.map +1 -1
  72. package/dist/routes/agentVariants.d.ts +3 -1
  73. package/dist/routes/agentVariants.d.ts.map +1 -1
  74. package/dist/routes/agentVariants.js +138 -13
  75. package/dist/routes/agentVariants.js.map +1 -1
  76. package/dist/routes/mcp.d.ts +7 -1
  77. package/dist/routes/mcp.d.ts.map +1 -1
  78. package/dist/routes/mcp.js +139 -16
  79. package/dist/routes/mcp.js.map +1 -1
  80. package/dist/server.d.ts.map +1 -1
  81. package/dist/server.js +23 -5
  82. package/dist/server.js.map +1 -1
  83. package/dist/services/ProjectDetectionService.d.ts.map +1 -1
  84. package/dist/services/ProjectDetectionService.js +9 -0
  85. package/dist/services/ProjectDetectionService.js.map +1 -1
  86. package/dist/services/SessionBridgeService.d.ts +22 -0
  87. package/dist/services/SessionBridgeService.d.ts.map +1 -1
  88. package/dist/services/SessionBridgeService.js +61 -0
  89. package/dist/services/SessionBridgeService.js.map +1 -1
  90. package/dist/services/TelemetryService.d.ts +121 -0
  91. package/dist/services/TelemetryService.d.ts.map +1 -1
  92. package/dist/services/TelemetryService.js +155 -0
  93. package/dist/services/TelemetryService.js.map +1 -1
  94. package/dist/services/WorktreeManager.d.ts +116 -6
  95. package/dist/services/WorktreeManager.d.ts.map +1 -1
  96. package/dist/services/WorktreeManager.js +394 -19
  97. package/dist/services/WorktreeManager.js.map +1 -1
  98. package/dist/services/agent/AgentModService.js +6 -6
  99. package/dist/services/agent/AgentModService.js.map +1 -1
  100. package/dist/services/templates/designCatalog.d.ts +27 -0
  101. package/dist/services/templates/designCatalog.d.ts.map +1 -0
  102. package/dist/services/templates/designCatalog.js +141 -0
  103. package/dist/services/templates/designCatalog.js.map +1 -0
  104. package/dist/services/templates/designmd/airbnb.md +545 -0
  105. package/dist/services/templates/designmd/airtable.md +554 -0
  106. package/dist/services/templates/designmd/apple.md +562 -0
  107. package/dist/services/templates/designmd/binance.md +634 -0
  108. package/dist/services/templates/designmd/bmw-m.md +503 -0
  109. package/dist/services/templates/designmd/bmw.md +544 -0
  110. package/dist/services/templates/designmd/bugatti.md +454 -0
  111. package/dist/services/templates/designmd/cal.md +542 -0
  112. package/dist/services/templates/designmd/claude.md +589 -0
  113. package/dist/services/templates/designmd/clay.md +541 -0
  114. package/dist/services/templates/designmd/cohere.md +451 -0
  115. package/dist/services/templates/designmd/cursor.md +537 -0
  116. package/dist/services/templates/designmd/expo.md +526 -0
  117. package/dist/services/templates/designmd/figma.md +578 -0
  118. package/dist/services/templates/designmd/framer.md +544 -0
  119. package/dist/services/templates/designmd/hp.md +670 -0
  120. package/dist/services/templates/designmd/linear.app.md +548 -0
  121. package/dist/services/templates/designmd/mintlify.md +852 -0
  122. package/dist/services/templates/designmd/miro.md +825 -0
  123. package/dist/services/templates/designmd/notion.md +821 -0
  124. package/dist/services/templates/designmd/raycast.md +669 -0
  125. package/dist/services/templates/designmd/resend.md +585 -0
  126. package/dist/services/templates/designmd/sentry.md +262 -0
  127. package/dist/services/templates/designmd/shopify.md +350 -0
  128. package/dist/services/templates/designmd/spotify.md +246 -0
  129. package/dist/services/templates/designmd/stripe.md +322 -0
  130. package/dist/services/templates/designmd/supabase.md +255 -0
  131. package/dist/services/templates/designmd/superhuman.md +252 -0
  132. package/dist/services/templates/designmd/uber.md +295 -0
  133. package/dist/services/templates/designmd/vercel.md +310 -0
  134. package/dist/services/templates/viteReactTs.d.ts +48 -0
  135. package/dist/services/templates/viteReactTs.d.ts.map +1 -0
  136. package/dist/services/templates/viteReactTs.js +274 -0
  137. package/dist/services/templates/viteReactTs.js.map +1 -0
  138. package/dist/types/change-request-types.d.ts +29 -3
  139. package/dist/types/change-request-types.d.ts.map +1 -1
  140. package/dist/utils/skills/claude-skill.d.ts +2 -2
  141. package/dist/utils/skills/claude-skill.d.ts.map +1 -1
  142. package/dist/utils/skills/claude-skill.js +19 -98
  143. package/dist/utils/skills/claude-skill.js.map +1 -1
  144. package/dist/utils/skills/cursor-rules.d.ts +2 -2
  145. package/dist/utils/skills/cursor-rules.d.ts.map +1 -1
  146. package/dist/utils/skills/cursor-rules.js +15 -80
  147. package/dist/utils/skills/cursor-rules.js.map +1 -1
  148. package/dist/utils/skills/shared-variants-protocol.d.ts +23 -0
  149. package/dist/utils/skills/shared-variants-protocol.d.ts.map +1 -0
  150. package/dist/utils/skills/shared-variants-protocol.js +130 -0
  151. package/dist/utils/skills/shared-variants-protocol.js.map +1 -0
  152. package/package.json +6 -6
  153. package/src/ui/dist/assets/main-CpX7fB64.js +382 -0
  154. package/src/ui/dist/assets/main-Qqe2_oMT.css +1 -0
  155. package/src/ui/dist/index.html +2 -2
  156. package/src/ui/dist/assets/main-AsPCtLsx.js +0 -382
  157. package/src/ui/dist/assets/main-BzmseUDd.css +0 -1
@@ -1,9 +1,10 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.SessionStore = exports.SCAFFOLD_LEASE_TTL_MS = exports.DEFAULT_LEASE_TTL_MS = void 0;
3
+ exports.SessionStore = exports.SOURCE_PLAN_OUTPUT_SCHEMA = exports.SCAFFOLD_LEASE_TTL_MS = exports.DEFAULT_LEASE_TTL_MS = void 0;
4
4
  const crypto_1 = require("crypto");
5
5
  const errors_1 = require("./errors");
6
6
  const contracts_1 = require("./contracts");
7
+ const sourceContext_1 = require("./sourceContext");
7
8
  const DEFAULT_VARIANT_COUNT = 4;
8
9
  const MAX_VARIANT_COUNT = 8;
9
10
  /** TTL for a lease on a code_gen work item. Bumped to 10min after observing
@@ -21,6 +22,7 @@ function fingerprint(parts) {
21
22
  function leaseTtlForKind(kind) {
22
23
  return kind === 'scaffold_base' ? exports.SCAFFOLD_LEASE_TTL_MS : exports.DEFAULT_LEASE_TTL_MS;
23
24
  }
25
+ /** Describes the work item contract returned to an agent lease holder. */
24
26
  function descriptorFor(item) {
25
27
  return {
26
28
  id: item.id,
@@ -31,18 +33,77 @@ function descriptorFor(item) {
31
33
  output_schema: outputSchemaFor(item.kind),
32
34
  };
33
35
  }
36
+ exports.SOURCE_PLAN_OUTPUT_SCHEMA = {
37
+ sourceIntent: 'Array<{url, role: design_source|interaction_reference|asset_source|content_reference|other, confidence: low|medium|high, reason}> — agent-decided URL classification; classify by prompt intent only, no deterministic URL heuristics.',
38
+ sourceContext: 'sourceFindings, sectionInventory, visualObservations, sourceRoles, qualityBar, screenshotReferences?, risks?',
39
+ designContexts: 'Array<{url, label, markdown}> REQUIRED for every design_source URL from sourceIntent (full browser-extracted DESIGN.md).',
40
+ executionPlan: "{ mode: 'static_preview' | 'vite_app', confidence: low|medium|high, reason, assetPlan?: Array<{source, destination, referenceAs}>, runtimeRequirements?, userQuestion? } — choose vite_app for large local assets, model-viewer, Three.js, route structure, package deps, or files under public/; assetPlan.source must live under the approved asset root; static_preview for self-contained HTML/CSS/JS prototypes.",
41
+ };
42
+ /** Returns the completion payload shape required for each leased work item. */
34
43
  function outputSchemaFor(kind) {
35
44
  switch (kind) {
45
+ case 'source_plan':
46
+ return exports.SOURCE_PLAN_OUTPUT_SCHEMA;
36
47
  case 'brief':
37
- return { briefs: 'Array<{briefId, label, body}>' };
48
+ return { briefs: 'Array<{briefId, label, body, visualReferenceUrl?}>' };
38
49
  case 'scaffold_base':
39
50
  return { workspacePath: 'string', framework: 'string' };
51
+ case 'static_preview':
52
+ return { html: 'string', css: 'string?', js: 'string?' };
40
53
  case 'code_gen':
41
54
  return { worktreePath: 'string', changedFiles: 'string[]' };
42
55
  case 'cleanup_runtime':
43
56
  return { released: 'boolean' };
44
57
  }
45
58
  }
59
+ const variantDesignFromInput = (entry) => {
60
+ if (!entry)
61
+ return undefined;
62
+ if (entry.kind === 'slug') {
63
+ return {
64
+ kind: 'slug',
65
+ slug: entry.slug,
66
+ displayName: entry.slug,
67
+ };
68
+ }
69
+ return {
70
+ kind: 'markdown',
71
+ label: entry.label,
72
+ };
73
+ };
74
+ const createBriefWorkItem = (args) => ({
75
+ id: (0, crypto_1.randomUUID)(),
76
+ kind: 'brief',
77
+ status: 'pending',
78
+ attempt: 0,
79
+ dependsOn: [],
80
+ input: {
81
+ prompt: args.prompt,
82
+ count: args.count,
83
+ target: args.target,
84
+ projectContext: args.projectContext,
85
+ },
86
+ acceptedReports: [],
87
+ });
88
+ /**
89
+ * Source-grounded fresh sessions require a single `source_plan` planning hop
90
+ * before briefs. Sessions without source URLs (or with the plan already
91
+ * materialized via `artifact`) skip straight to `awaiting_briefs`.
92
+ */
93
+ const requiresSourcePlan = (projectContext) => {
94
+ if (projectContext.kind !== 'fresh')
95
+ return false;
96
+ const sourceContext = projectContext.sourceContext;
97
+ if (!sourceContext)
98
+ return false;
99
+ const hasSources = (sourceContext.sourceUrls?.length ?? 0) > 0;
100
+ if (!hasSources)
101
+ return false;
102
+ const hasArtifact = Boolean(sourceContext.artifact);
103
+ const hasIntent = Boolean(sourceContext.sourceIntent);
104
+ const hasExecutionPlan = Boolean(projectContext.executionPlan);
105
+ return !(hasArtifact && hasIntent && hasExecutionPlan);
106
+ };
46
107
  class SessionStore {
47
108
  sessions = new Map();
48
109
  clock;
@@ -57,36 +118,58 @@ class SessionStore {
57
118
  }
58
119
  const projectContext = args.projectContext ?? { kind: 'existing' };
59
120
  const count = clampCount(args.count);
60
- const briefItem = {
61
- id: (0, crypto_1.randomUUID)(),
62
- kind: 'brief',
63
- status: 'pending',
64
- attempt: 0,
65
- dependsOn: [],
66
- input: {
121
+ const sourceContextForPlan = projectContext.kind === 'fresh' ? projectContext.sourceContext : undefined;
122
+ const needsSourcePlan = requiresSourcePlan(projectContext);
123
+ const initialItem = needsSourcePlan
124
+ ? {
125
+ id: (0, crypto_1.randomUUID)(),
126
+ kind: 'source_plan',
127
+ status: 'pending',
128
+ attempt: 0,
129
+ dependsOn: [],
130
+ input: {
131
+ prompt: args.prompt,
132
+ count,
133
+ target: args.target,
134
+ sourceUrls: sourceContextForPlan?.sourceUrls ?? [],
135
+ sourceArtifacts: sourceContextForPlan?.sourceArtifacts ?? [],
136
+ preserveBrand: sourceContextForPlan?.preserveBrand ?? false,
137
+ projectContext,
138
+ },
139
+ acceptedReports: [],
140
+ }
141
+ : createBriefWorkItem({
67
142
  prompt: args.prompt,
68
143
  count,
69
144
  target: args.target,
70
145
  projectContext,
71
- },
72
- acceptedReports: [],
73
- };
146
+ });
74
147
  const record = {
75
148
  sessionId,
76
- stage: 'awaiting_briefs',
149
+ stage: needsSourcePlan ? 'awaiting_source_plan' : 'awaiting_briefs',
77
150
  prompt: args.prompt,
78
151
  count,
79
152
  target: args.target,
80
153
  projectContext,
81
154
  briefs: [],
82
155
  approvedBriefs: [],
83
- workItems: new Map([[briefItem.id, briefItem]]),
84
- workItemOrder: [briefItem.id],
156
+ workItems: new Map([[initialItem.id, initialItem]]),
157
+ workItemOrder: [initialItem.id],
85
158
  acceptedFingerprints: new Map(),
86
159
  createdAt: this.clock.now(),
87
160
  };
88
161
  this.sessions.set(sessionId, record);
89
- return { sessionId, briefWorkItem: briefItem };
162
+ return needsSourcePlan
163
+ ? {
164
+ sessionId,
165
+ stage: 'awaiting_source_plan',
166
+ sourcePlanWorkItem: initialItem,
167
+ }
168
+ : {
169
+ sessionId,
170
+ stage: 'awaiting_briefs',
171
+ briefWorkItem: initialItem,
172
+ };
90
173
  }
91
174
  reportBriefs(args) {
92
175
  const session = this.requireSession(args.sessionId);
@@ -122,6 +205,107 @@ class SessionStore {
122
205
  session.acceptedFingerprints.set(fp, result);
123
206
  return result;
124
207
  }
208
+ reportSourcePlan(args) {
209
+ const session = this.requireSession(args.sessionId);
210
+ const fp = fingerprint([
211
+ 'reportSourcePlan',
212
+ args.sessionId,
213
+ args.workItemId,
214
+ args.leaseId,
215
+ args.attempt,
216
+ 'succeeded',
217
+ ]);
218
+ const cached = session.acceptedFingerprints.get(fp);
219
+ if (cached)
220
+ return cached;
221
+ this.assertStage(session, ['awaiting_source_plan']);
222
+ const item = this.requireWorkItem(session, args.workItemId);
223
+ if (item.kind !== 'source_plan') {
224
+ throw new errors_1.AgentVariantsError('INVALID_STAGE_ACTION', `Work item ${args.workItemId} is not a source_plan item`);
225
+ }
226
+ this.validateLease(item, args.leaseId, args.attempt);
227
+ const { sourceIntent, sourceContext, designContexts, executionPlan } = args.sourcePlan;
228
+ // Cross-check: every source URL on the session must be classified, and
229
+ // every design_source URL must come with a browser-extracted DESIGN.md.
230
+ const sessionSourceUrls = session.projectContext.kind === 'fresh'
231
+ ? (session.projectContext.sourceContext?.sourceUrls ?? [])
232
+ : [];
233
+ const classifiedUrls = new Set(sourceIntent.sources.map((e) => e.url));
234
+ const missingClassification = sessionSourceUrls.filter((url) => !classifiedUrls.has(url));
235
+ if (missingClassification.length > 0) {
236
+ throw new errors_1.AgentVariantsError('SOURCE_CONTEXT_INVALID', `source_plan missing sourceIntent classification for: ${missingClassification.join(', ')}`);
237
+ }
238
+ const designSourceUrls = sourceIntent.sources
239
+ .filter((entry) => entry.role === 'design_source')
240
+ .map((entry) => entry.url);
241
+ const reportedDesignUrls = new Set((designContexts ?? []).map((entry) => entry.url));
242
+ const missingDesignContexts = designSourceUrls.filter((url) => !reportedDesignUrls.has(url));
243
+ if (missingDesignContexts.length > 0) {
244
+ throw new errors_1.AgentVariantsError('SOURCE_CONTEXT_INVALID', `Missing browser-extracted DESIGN.md for design source URL(s): ${missingDesignContexts.join(', ')}`);
245
+ }
246
+ const normalizedArtifact = (0, sourceContext_1.normalizeSourceContextArtifact)(sourceContext);
247
+ if (session.projectContext.kind === 'fresh') {
248
+ // Persist one designContext entry PER design_source URL, aligned
249
+ // to the SOURCE_INTENT design_source order (NOT the agent's
250
+ // designContexts submission order). Brief fan-out
251
+ // (provisionFreshWorktrees) reads slot [min(briefIndex, length-1)]
252
+ // and falls back to slot 0 — a single-source session still ships
253
+ // one shared markdown, but a multi-source session puts each
254
+ // design_source's DESIGN.md at a deterministic slot regardless of
255
+ // how many non-design_source entries the agent classified or the
256
+ // order it shipped designContexts in. Without this alignment, an
257
+ // extra interaction_reference URL ahead of a design_source in the
258
+ // agent's payload would push the wrong DESIGN.md onto variant 0.
259
+ const designContextByUrl = new Map();
260
+ for (const entry of designContexts ?? []) {
261
+ designContextByUrl.set(entry.url, {
262
+ label: entry.label,
263
+ markdown: entry.markdown,
264
+ });
265
+ }
266
+ const alignedDesignContexts = designSourceUrls
267
+ .map((url) => designContextByUrl.get(url))
268
+ .filter((entry) => Boolean(entry));
269
+ session.projectContext = {
270
+ ...session.projectContext,
271
+ ...(alignedDesignContexts.length > 0
272
+ ? {
273
+ designContext: alignedDesignContexts.map((entry) => ({
274
+ kind: 'markdown',
275
+ label: entry.label,
276
+ content: entry.markdown,
277
+ })),
278
+ }
279
+ : {}),
280
+ sourceContext: {
281
+ ...(session.projectContext.sourceContext ?? {}),
282
+ sourceIntent,
283
+ artifact: normalizedArtifact,
284
+ },
285
+ executionPlan,
286
+ };
287
+ }
288
+ item.status = 'succeeded';
289
+ item.output = {
290
+ sourceIntent,
291
+ sourceContext: normalizedArtifact,
292
+ ...(designContexts ? { designContexts } : {}),
293
+ executionPlan,
294
+ };
295
+ item.lease = undefined;
296
+ const briefItem = createBriefWorkItem({
297
+ prompt: session.prompt,
298
+ count: session.count,
299
+ target: session.target,
300
+ projectContext: session.projectContext,
301
+ });
302
+ session.workItems.set(briefItem.id, briefItem);
303
+ session.workItemOrder.push(briefItem.id);
304
+ session.stage = 'awaiting_briefs';
305
+ const result = { briefWorkItem: briefItem };
306
+ session.acceptedFingerprints.set(fp, result);
307
+ return result;
308
+ }
125
309
  approve(args) {
126
310
  const session = this.requireSession(args.sessionId);
127
311
  this.assertStage(session, ['awaiting_approval']);
@@ -135,48 +319,114 @@ class SessionStore {
135
319
  }
136
320
  }
137
321
  session.approvedBriefs = args.selections;
322
+ const isFresh = session.projectContext.kind === 'fresh';
323
+ const executionPlan = session.projectContext.kind === 'fresh'
324
+ ? session.projectContext.executionPlan
325
+ : undefined;
326
+ // Surface an agent-flagged clarification as a hard blocker rather than
327
+ // silently downgrading to a static preview. The agent sets
328
+ // `userQuestion` when it cannot decide between static_preview and
329
+ // vite_app without more info from the user.
330
+ if (executionPlan?.userQuestion) {
331
+ throw new errors_1.AgentVariantsError('MISSING_REQUIRED_INPUT', executionPlan.userQuestion);
332
+ }
333
+ // Fresh sessions default to static_preview when no plan was reported
334
+ // (source-less zero-to-one). Source-grounded sessions carry an
335
+ // executionPlan from `report_source_plan`.
336
+ const freshMode = isFresh
337
+ ? (executionPlan?.mode ?? 'static_preview')
338
+ : 'static_preview';
339
+ const needsViteApp = isFresh && freshMode === 'vite_app';
340
+ const codeGenIds = [];
341
+ // For fresh+vite_app sessions, code_gen items depend on a single
342
+ // scaffold_base item so the orchestrator can do the Vite skeleton +
343
+ // npm install + asset copy before the agent leases the code_gen.
138
344
  let scaffoldBaseId;
139
- if (session.projectContext.kind === 'fresh') {
345
+ if (needsViteApp) {
346
+ scaffoldBaseId = (0, crypto_1.randomUUID)();
140
347
  const scaffoldItem = {
141
- id: (0, crypto_1.randomUUID)(),
348
+ id: scaffoldBaseId,
142
349
  kind: 'scaffold_base',
143
350
  status: 'pending',
144
351
  attempt: 0,
145
352
  dependsOn: [],
146
- input: {
147
- workspacePath: session.projectContext.workspacePath,
148
- framework: session.projectContext.framework,
149
- },
353
+ input: { projectContext: session.projectContext },
150
354
  acceptedReports: [],
355
+ internal: true,
151
356
  };
152
357
  session.workItems.set(scaffoldItem.id, scaffoldItem);
153
358
  session.workItemOrder.push(scaffoldItem.id);
154
- scaffoldBaseId = scaffoldItem.id;
155
- }
156
- const codeGenIds = [];
157
- for (const sel of args.selections) {
158
- const brief = session.briefs.find((b) => b.briefId === sel.briefId);
159
- if (!brief)
160
- continue;
161
- const codeGenItem = {
162
- id: (0, crypto_1.randomUUID)(),
163
- kind: 'code_gen',
164
- status: 'pending',
165
- attempt: 0,
166
- dependsOn: scaffoldBaseId ? [scaffoldBaseId] : [],
167
- input: {
168
- briefId: brief.briefId,
169
- briefLabel: brief.label,
170
- briefBody: sel.bodyOverride ?? brief.body,
171
- target: session.target,
172
- projectContext: session.projectContext,
173
- },
174
- acceptedReports: [],
175
- };
176
- session.workItems.set(codeGenItem.id, codeGenItem);
177
- session.workItemOrder.push(codeGenItem.id);
178
- codeGenIds.push(codeGenItem.id);
179
359
  }
360
+ args.selections.forEach((sel) => {
361
+ const briefIndex = session.briefs.findIndex((b) => b.briefId === sel.briefId);
362
+ if (briefIndex === -1)
363
+ return;
364
+ const brief = session.briefs[briefIndex];
365
+ // D-VR2: Plumb the optional per-brief visualReferenceUrl into the
366
+ // resulting work-item input. Omitted (not nulled) when absent so the
367
+ // shape stays backward-compatible for callers that destructure.
368
+ const visualRef = brief.visualReferenceUrl
369
+ ? { visualReferenceUrl: brief.visualReferenceUrl }
370
+ : {};
371
+ if (isFresh && !needsViteApp) {
372
+ // Fresh + static_preview: HTML is the deliverable. No scaffold or
373
+ // code_gen — the static_preview item is leased directly.
374
+ const staticPreviewItem = {
375
+ id: (0, crypto_1.randomUUID)(),
376
+ kind: 'static_preview',
377
+ status: 'pending',
378
+ attempt: 0,
379
+ dependsOn: [],
380
+ input: {
381
+ briefId: brief.briefId,
382
+ briefLabel: brief.label,
383
+ briefBody: sel.bodyOverride ?? brief.body,
384
+ ...visualRef,
385
+ projectContext: session.projectContext,
386
+ },
387
+ acceptedReports: [],
388
+ };
389
+ session.workItems.set(staticPreviewItem.id, staticPreviewItem);
390
+ session.workItemOrder.push(staticPreviewItem.id);
391
+ }
392
+ else {
393
+ // Existing project OR fresh+vite_app: code_gen against a worktree.
394
+ // Fresh+vite_app additionally depends on scaffold_base so the agent
395
+ // doesn't lease code_gen before the Vite skeleton lands.
396
+ //
397
+ // Per-slot designContextEntry is read by provisionFreshWorktrees to
398
+ // decide what DESIGN.md to write per variant. Falls back to slot 0
399
+ // when the session has fewer designContext entries than variants
400
+ // (the common case after report_source_plan ships one shared
401
+ // markdown).
402
+ const freshDesignContext = session.projectContext.kind === 'fresh'
403
+ ? session.projectContext.designContext
404
+ : undefined;
405
+ const designContextEntry = freshDesignContext
406
+ ? freshDesignContext[Math.min(briefIndex, freshDesignContext.length - 1)]
407
+ : undefined;
408
+ const codeGenItem = {
409
+ id: (0, crypto_1.randomUUID)(),
410
+ kind: 'code_gen',
411
+ status: 'pending',
412
+ attempt: 0,
413
+ dependsOn: scaffoldBaseId ? [scaffoldBaseId] : [],
414
+ input: {
415
+ briefId: brief.briefId,
416
+ briefLabel: brief.label,
417
+ briefBody: sel.bodyOverride ?? brief.body,
418
+ ...visualRef,
419
+ target: session.target,
420
+ projectContext: session.projectContext,
421
+ ...(designContextEntry ? { designContextEntry } : {}),
422
+ },
423
+ acceptedReports: [],
424
+ };
425
+ session.workItems.set(codeGenItem.id, codeGenItem);
426
+ session.workItemOrder.push(codeGenItem.id);
427
+ codeGenIds.push(codeGenItem.id);
428
+ }
429
+ });
180
430
  session.stage = 'work_items_ready';
181
431
  return {
182
432
  approvedCount: args.selections.length,
@@ -188,7 +438,11 @@ class SessionStore {
188
438
  // --- Lease management ---------------------------------------------------
189
439
  requestWork(args) {
190
440
  const session = this.requireSession(args.sessionId);
191
- this.assertStage(session, ['work_items_ready', 'waiting_for_results']);
441
+ this.assertStage(session, [
442
+ 'awaiting_source_plan',
443
+ 'work_items_ready',
444
+ 'waiting_for_results',
445
+ ]);
192
446
  this.expireStaleLeases(session);
193
447
  const leasable = this.leasableItems(session);
194
448
  const limit = args.requestedLeaseCount ?? leasable.length;
@@ -210,11 +464,14 @@ class SessionStore {
210
464
  };
211
465
  item.status = 'running';
212
466
  }
213
- session.stage = 'waiting_for_results';
467
+ session.stage =
468
+ session.stage === 'awaiting_source_plan'
469
+ ? 'awaiting_source_plan'
470
+ : 'waiting_for_results';
214
471
  return {
215
472
  leaseId,
216
473
  leaseTtlMs: Math.max(...issued.map((i) => leaseTtlForKind(i.kind))),
217
- leasedWorkItems: issued.map(descriptorFor),
474
+ leasedWorkItems: issued.map((item) => descriptorFor(item)),
218
475
  };
219
476
  }
220
477
  reportComplete(args) {
@@ -275,6 +532,49 @@ class SessionStore {
275
532
  session.acceptedFingerprints.set(fp, result);
276
533
  return result;
277
534
  }
535
+ /**
536
+ * Mark a server-owned work item as succeeded without going through the
537
+ * lease + agent-report path. Used for `scaffold_base` items where the
538
+ * orchestrator runs the scaffold + npm install in the background and needs
539
+ * to unblock dependent code_gen items when it's done.
540
+ *
541
+ * Bypasses lease validation because no lease was ever issued for internal
542
+ * items. Idempotent: completing an already-succeeded item is a no-op.
543
+ */
544
+ completeInternal(args) {
545
+ const session = this.requireSession(args.sessionId);
546
+ const item = this.requireWorkItem(session, args.workItemId);
547
+ if (!item.internal) {
548
+ throw new errors_1.AgentVariantsError('INVALID_STAGE_ACTION', `completeInternal called on non-internal work item ${args.workItemId}`);
549
+ }
550
+ if (item.status === 'succeeded') {
551
+ return this.evaluateTerminal(session);
552
+ }
553
+ item.status = 'succeeded';
554
+ item.output = args.output;
555
+ item.lease = undefined;
556
+ return this.evaluateTerminal(session);
557
+ }
558
+ /**
559
+ * Mark a server-owned work item as failed, cascading DEPENDENCY_FAILED to
560
+ * its dependents. Used when the orchestrator's background scaffold work
561
+ * (npm install, file writes) fails and we need the session to degrade.
562
+ */
563
+ failInternal(args) {
564
+ const session = this.requireSession(args.sessionId);
565
+ const item = this.requireWorkItem(session, args.workItemId);
566
+ if (!item.internal) {
567
+ throw new errors_1.AgentVariantsError('INVALID_STAGE_ACTION', `failInternal called on non-internal work item ${args.workItemId}`);
568
+ }
569
+ if (item.status === 'succeeded' || item.status === 'failed') {
570
+ return this.evaluateTerminal(session);
571
+ }
572
+ item.status = 'failed';
573
+ item.error = args.error;
574
+ item.lease = undefined;
575
+ this.cascadeDependencyFailure(session, item.id);
576
+ return this.evaluateTerminal(session);
577
+ }
278
578
  // --- Cancel / cleanup ---------------------------------------------------
279
579
  cancel(args) {
280
580
  const session = this.requireSession(args.sessionId);
@@ -294,6 +594,37 @@ class SessionStore {
294
594
  // Phase 2 wires the worktree teardown.
295
595
  return { stage: 'cancelled', cleanupStatus: 'pending' };
296
596
  }
597
+ /**
598
+ * Cancel a single work item without tearing down its sibling variants or
599
+ * the session itself. Idempotent on terminal states — already-cancelled,
600
+ * succeeded, or failed items return their current status with
601
+ * `alreadyTerminal: true` and no state change. Triggers
602
+ * `evaluateTerminal` so the session can reach a terminal stage if this
603
+ * was the last in-flight item.
604
+ */
605
+ cancelWorkItem(args) {
606
+ const session = this.requireSession(args.sessionId);
607
+ const item = this.requireWorkItem(session, args.workItemId);
608
+ if (item.status === 'cancelled' ||
609
+ item.status === 'succeeded' ||
610
+ item.status === 'failed') {
611
+ return {
612
+ workItemId: item.id,
613
+ finalStatus: item.status,
614
+ sessionStage: session.stage,
615
+ alreadyTerminal: true,
616
+ };
617
+ }
618
+ item.status = 'cancelled';
619
+ item.lease = undefined;
620
+ this.evaluateTerminal(session);
621
+ return {
622
+ workItemId: item.id,
623
+ finalStatus: 'cancelled',
624
+ sessionStage: session.stage,
625
+ alreadyTerminal: false,
626
+ };
627
+ }
297
628
  // --- Variant pick handoff (queues to pending-changes adapter) -----------
298
629
  recordVariantPick(args) {
299
630
  const session = this.requireSession(args.sessionId);
@@ -324,6 +655,10 @@ class SessionStore {
324
655
  const session = this.requireSession(sessionId);
325
656
  return this.summaryOf(session);
326
657
  }
658
+ getVariants(sessionId) {
659
+ const session = this.requireSession(sessionId);
660
+ return this.variantsOf(session);
661
+ }
327
662
  getVariantPick(sessionId) {
328
663
  return this.requireSession(sessionId).variantPick;
329
664
  }
@@ -337,6 +672,11 @@ class SessionStore {
337
672
  const item = this.requireWorkItem(session, workItemId);
338
673
  return item.input;
339
674
  }
675
+ getWorkItemOutput(sessionId, workItemId) {
676
+ const session = this.requireSession(sessionId);
677
+ const item = this.requireWorkItem(session, workItemId);
678
+ return item.output;
679
+ }
340
680
  /** Read the projectContext for a session (existing | fresh). */
341
681
  getProjectContext(sessionId) {
342
682
  return this.requireSession(sessionId).projectContext;
@@ -392,14 +732,38 @@ class SessionStore {
392
732
  const item = session.workItems.get(id);
393
733
  if (item.kind === 'brief')
394
734
  continue;
735
+ // Server-owned items never surface to external agents.
736
+ if (item.internal)
737
+ continue;
395
738
  if (item.status !== 'pending')
396
739
  continue;
397
740
  if (!this.dependenciesMet(session, item))
398
741
  continue;
742
+ if (item.kind === 'static_preview' &&
743
+ this.hasLeasableCodeGenForStaticPreview(session, item)) {
744
+ continue;
745
+ }
399
746
  out.push(item);
400
747
  }
401
748
  return out;
402
749
  }
750
+ hasLeasableCodeGenForStaticPreview(session, staticPreview) {
751
+ const staticInput = staticPreview.input;
752
+ if (!staticInput.briefId)
753
+ return false;
754
+ for (const candidate of session.workItems.values()) {
755
+ if (candidate.kind !== 'code_gen')
756
+ continue;
757
+ if (candidate.status !== 'pending')
758
+ continue;
759
+ if (!this.dependenciesMet(session, candidate))
760
+ continue;
761
+ const codeInput = candidate.input;
762
+ if (codeInput.briefId === staticInput.briefId)
763
+ return true;
764
+ }
765
+ return false;
766
+ }
403
767
  dependenciesMet(session, item) {
404
768
  for (const depId of item.dependsOn) {
405
769
  const dep = session.workItems.get(depId);
@@ -419,9 +783,26 @@ class SessionStore {
419
783
  }
420
784
  }
421
785
  }
786
+ terminalItems(session) {
787
+ const kind = this.variantWorkItemKind(session);
788
+ return [...session.workItems.values()].filter((i) => i.kind === kind);
789
+ }
790
+ /**
791
+ * The work-item kind that represents a user-facing variant for this
792
+ * session. Drives both progress accounting and the active variants
793
+ * snapshot. Fresh+vite_app sessions use `code_gen` like existing
794
+ * projects; fresh+static_preview (the default for source-less 0→1)
795
+ * keeps `static_preview` as the deliverable.
796
+ */
797
+ variantWorkItemKind(session) {
798
+ if (session.projectContext.kind !== 'fresh')
799
+ return 'code_gen';
800
+ const mode = session.projectContext.executionPlan?.mode ?? 'static_preview';
801
+ return mode === 'vite_app' ? 'code_gen' : 'static_preview';
802
+ }
422
803
  evaluateTerminal(session) {
423
- const codeGenItems = [...session.workItems.values()].filter((i) => i.kind === 'code_gen');
424
- const allDone = codeGenItems.every((i) => i.status === 'succeeded' ||
804
+ const items = this.terminalItems(session);
805
+ const allDone = items.every((i) => i.status === 'succeeded' ||
425
806
  i.status === 'failed' ||
426
807
  i.status === 'cancelled');
427
808
  if (!allDone) {
@@ -430,9 +811,9 @@ class SessionStore {
430
811
  progress: this.progressOf(session),
431
812
  };
432
813
  }
433
- const succeeded = codeGenItems.filter((i) => i.status === 'succeeded').length;
814
+ const succeeded = items.filter((i) => i.status === 'succeeded').length;
434
815
  let terminal;
435
- if (succeeded === codeGenItems.length)
816
+ if (succeeded === items.length)
436
817
  terminal = 'ready';
437
818
  else if (succeeded > 0)
438
819
  terminal = 'degraded';
@@ -446,17 +827,22 @@ class SessionStore {
446
827
  };
447
828
  }
448
829
  progressOf(session) {
449
- const codeGen = [...session.workItems.values()].filter((i) => i.kind === 'code_gen');
450
- const ready = codeGen.filter((i) => i.status === 'succeeded').length;
451
- return { ready, total: codeGen.length };
830
+ if (session.stage === 'awaiting_source_plan') {
831
+ const planItems = [...session.workItems.values()].filter((item) => item.kind === 'source_plan');
832
+ const ready = planItems.filter((item) => item.status === 'succeeded').length;
833
+ return { ready, total: planItems.length };
834
+ }
835
+ const items = this.terminalItems(session);
836
+ const ready = items.filter((i) => i.status === 'succeeded').length;
837
+ return { ready, total: items.length };
452
838
  }
453
839
  summaryOf(session) {
454
- const codeGen = [...session.workItems.values()].filter((i) => i.kind === 'code_gen');
840
+ const items = this.terminalItems(session);
455
841
  return {
456
- successCount: codeGen.filter((i) => i.status === 'succeeded').length,
457
- failureCount: codeGen.filter((i) => i.status === 'failed').length,
458
- cancelledCount: codeGen.filter((i) => i.status === 'cancelled').length,
459
- variants: codeGen.map((i) => {
842
+ successCount: items.filter((i) => i.status === 'succeeded').length,
843
+ failureCount: items.filter((i) => i.status === 'failed').length,
844
+ cancelledCount: items.filter((i) => i.status === 'cancelled').length,
845
+ variants: items.map((i) => {
460
846
  const input = i.input;
461
847
  return {
462
848
  workItemId: i.id,
@@ -466,6 +852,22 @@ class SessionStore {
466
852
  }),
467
853
  };
468
854
  }
855
+ variantsOf(session) {
856
+ const kind = this.variantWorkItemKind(session);
857
+ return [...session.workItems.values()]
858
+ .filter((i) => i.kind === kind)
859
+ .map((item) => {
860
+ const input = item.input;
861
+ return {
862
+ workItemId: item.id,
863
+ briefId: input.briefId ?? '',
864
+ label: input.briefLabel ?? 'Variant',
865
+ status: item.status,
866
+ design: variantDesignFromInput(input.designContextEntry),
867
+ ...(item.error?.code ? { errorCode: String(item.error.code) } : {}),
868
+ };
869
+ });
870
+ }
469
871
  }
470
872
  exports.SessionStore = SessionStore;
471
873
  function clampCount(count) {