rivet-design 0.13.2 → 0.13.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 (92) hide show
  1. package/README.md +5 -1
  2. package/README.npm.md +5 -1
  3. package/dist/agent-variants/SessionStore.d.ts +22 -2
  4. package/dist/agent-variants/SessionStore.d.ts.map +1 -1
  5. package/dist/agent-variants/SessionStore.js +31 -0
  6. package/dist/agent-variants/SessionStore.js.map +1 -1
  7. package/dist/agent-variants/VariantWorkspaceManager.d.ts +43 -0
  8. package/dist/agent-variants/VariantWorkspaceManager.d.ts.map +1 -0
  9. package/dist/agent-variants/VariantWorkspaceManager.js +216 -0
  10. package/dist/agent-variants/VariantWorkspaceManager.js.map +1 -0
  11. package/dist/agent-variants/WorktreeOrchestrator.d.ts +27 -2
  12. package/dist/agent-variants/WorktreeOrchestrator.d.ts.map +1 -1
  13. package/dist/agent-variants/WorktreeOrchestrator.js +370 -60
  14. package/dist/agent-variants/WorktreeOrchestrator.js.map +1 -1
  15. package/dist/agent-variants/contracts.d.ts +62 -1
  16. package/dist/agent-variants/contracts.d.ts.map +1 -1
  17. package/dist/agent-variants/contracts.js +10 -1
  18. package/dist/agent-variants/contracts.js.map +1 -1
  19. package/dist/agent-variants/workItemBriefing.d.ts +26 -0
  20. package/dist/agent-variants/workItemBriefing.d.ts.map +1 -0
  21. package/dist/agent-variants/workItemBriefing.js +110 -0
  22. package/dist/agent-variants/workItemBriefing.js.map +1 -0
  23. package/dist/cli/client.d.ts +2 -0
  24. package/dist/cli/client.d.ts.map +1 -1
  25. package/dist/cli/client.js +1 -1
  26. package/dist/cli/client.js.map +1 -1
  27. package/dist/cli/commands/mcpServe.d.ts.map +1 -1
  28. package/dist/cli/commands/mcpServe.js +36 -8
  29. package/dist/cli/commands/mcpServe.js.map +1 -1
  30. package/dist/cli/commands/status.d.ts.map +1 -1
  31. package/dist/cli/commands/status.js +3 -1
  32. package/dist/cli/commands/status.js.map +1 -1
  33. package/dist/cli/commands/variants.d.ts.map +1 -1
  34. package/dist/cli/commands/variants.js +45 -8
  35. package/dist/cli/commands/variants.js.map +1 -1
  36. package/dist/cli/hostWorkNextAction.d.ts +33 -26
  37. package/dist/cli/hostWorkNextAction.d.ts.map +1 -1
  38. package/dist/cli/hostWorkNextAction.js +47 -24
  39. package/dist/cli/hostWorkNextAction.js.map +1 -1
  40. package/dist/hosts/workerDefaults.d.ts +62 -0
  41. package/dist/hosts/workerDefaults.d.ts.map +1 -0
  42. package/dist/hosts/workerDefaults.js +77 -0
  43. package/dist/hosts/workerDefaults.js.map +1 -0
  44. package/dist/install/codexGuidance.d.ts.map +1 -1
  45. package/dist/install/codexGuidance.js +5 -4
  46. package/dist/install/codexGuidance.js.map +1 -1
  47. package/dist/install/harnesses.d.ts.map +1 -1
  48. package/dist/install/harnesses.js +44 -16
  49. package/dist/install/harnesses.js.map +1 -1
  50. package/dist/install/mcpRegistration.d.ts +1 -1
  51. package/dist/install/mcpRegistration.d.ts.map +1 -1
  52. package/dist/install/mcpRegistration.js +23 -10
  53. package/dist/install/mcpRegistration.js.map +1 -1
  54. package/dist/routes/agentVariants.js +2 -2
  55. package/dist/routes/agentVariants.js.map +1 -1
  56. package/dist/services/TelemetryService.d.ts +8 -0
  57. package/dist/services/TelemetryService.d.ts.map +1 -1
  58. package/dist/services/TelemetryService.js +24 -0
  59. package/dist/services/TelemetryService.js.map +1 -1
  60. package/dist/services/VariantGenerationService.d.ts.map +1 -1
  61. package/dist/services/VariantGenerationService.js +8 -0
  62. package/dist/services/VariantGenerationService.js.map +1 -1
  63. package/dist/services/VariantRunService.d.ts +5 -2
  64. package/dist/services/VariantRunService.d.ts.map +1 -1
  65. package/dist/services/VariantRunService.js +39 -14
  66. package/dist/services/VariantRunService.js.map +1 -1
  67. package/dist/services/WorktreeManager.d.ts.map +1 -1
  68. package/dist/services/WorktreeManager.js +7 -2
  69. package/dist/services/WorktreeManager.js.map +1 -1
  70. package/dist/types/change-request-types.d.ts +3 -0
  71. package/dist/types/change-request-types.d.ts.map +1 -1
  72. package/dist/utils/cloneOrCopyDirSync.d.ts +14 -0
  73. package/dist/utils/cloneOrCopyDirSync.d.ts.map +1 -0
  74. package/dist/utils/cloneOrCopyDirSync.js +72 -0
  75. package/dist/utils/cloneOrCopyDirSync.js.map +1 -0
  76. package/dist/utils/skillWriter.d.ts +8 -0
  77. package/dist/utils/skillWriter.d.ts.map +1 -1
  78. package/dist/utils/skillWriter.js +35 -0
  79. package/dist/utils/skillWriter.js.map +1 -1
  80. package/dist/utils/skills/claude-skill.d.ts +2 -2
  81. package/dist/utils/skills/claude-skill.d.ts.map +1 -1
  82. package/dist/utils/skills/claude-skill.js +1 -1
  83. package/dist/utils/skills/cli-guidance.d.ts +2 -2
  84. package/dist/utils/skills/cli-guidance.d.ts.map +1 -1
  85. package/dist/utils/skills/cli-guidance.js +8 -8
  86. package/dist/utils/skills/cursor-rules.d.ts +1 -1
  87. package/dist/utils/skills/cursor-rules.d.ts.map +1 -1
  88. package/package.json +12 -9
  89. package/scripts/verify-node-version.cjs +52 -0
  90. package/src/ui/dist/assets/main-T3U4Gt16.js +419 -0
  91. package/src/ui/dist/index.html +1 -1
  92. package/src/ui/dist/assets/main-BBHg643G.js +0 -419
@@ -17,6 +17,7 @@ const devServerError_1 = require("../utils/devServerError");
17
17
  const errors_1 = require("./errors");
18
18
  const createProjectArtifacts_1 = require("./createProjectArtifacts");
19
19
  const contracts_1 = require("./contracts");
20
+ const workItemBriefing_1 = require("./workItemBriefing");
20
21
  const briefContent_1 = require("./briefContent");
21
22
  const runLabel_1 = require("./runLabel");
22
23
  const runPlan_1 = require("./runPlan");
@@ -24,6 +25,7 @@ const viteReactTs_1 = require("../services/templates/viteReactTs");
24
25
  const StaticPreviewServer_1 = require("../services/StaticPreviewServer");
25
26
  const designCatalog_1 = require("../services/templates/designCatalog");
26
27
  const previewQa_1 = require("./previewQa");
28
+ const VariantWorkspaceManager_1 = require("./VariantWorkspaceManager");
27
29
  const diffQa_1 = require("./diffQa");
28
30
  const VariantHistoryService_1 = require("../services/VariantHistoryService");
29
31
  const variantContext_1 = require("./variantContext");
@@ -60,6 +62,16 @@ const BRIEF_LABEL_ALTERNATIVES = [
60
62
  'Density',
61
63
  'Color',
62
64
  ];
65
+ const FALLBACK_DIRECTION_FOCI = [
66
+ 'Emphasize hierarchy and one unmistakable primary action.',
67
+ 'Use stronger contrast, scale shifts, and a more expressive visual system.',
68
+ 'Favor a quieter, spacious composition with restrained supporting detail.',
69
+ 'Make information density and fast scanning the organizing principle.',
70
+ 'Lead with imagery and let content frame the visual story.',
71
+ 'Use modular cards and clear grouping to expose the product structure.',
72
+ 'Explore tactile depth, layering, and purposeful interaction cues.',
73
+ 'Reduce the concept to its most essential typography and geometry.',
74
+ ];
63
75
  // Upper bound on waiting for in-flight history persists during session
64
76
  // teardown. Generous enough for a full-repo snapshot on a large project;
65
77
  // small enough that a wedged git subprocess (which has its own 120s block
@@ -350,6 +362,12 @@ const buildViewDisabledReason = (args) => {
350
362
  return 'Variant is still running';
351
363
  };
352
364
  const NOOP_TELEMETRY = { track: () => undefined };
365
+ const isImplementationWorkItemKind = (kind) => kind === 'static_preview' || kind === 'refine_variant' || kind === 'code_gen';
366
+ const DEFAULT_EXECUTION_MODE_BY_KIND = {
367
+ static_preview: 'create',
368
+ refine_variant: 'refine',
369
+ code_gen: 'create',
370
+ };
353
371
  const DESIGN_CREATION_ACTION_RE = /\b(create|build|design|make|redesign|refresh|update)\b/i;
354
372
  const DESIGN_CREATION_SURFACE_RE = /\b(home\s?page|landing page|dashboard|screen|site|website|app|page|ui|interface|component|hero)\b/i;
355
373
  const NON_VISUAL_CHANGE_RE = /\b(api|backend|database|event|lint|query|server|test|tracking|typecheck)\b/i;
@@ -658,16 +676,71 @@ class AgentVariantsOrchestrator {
658
676
  }
659
677
  getHostWork(sessionId) {
660
678
  return this.store.getHostWork(sessionId).map((item) => {
661
- const worktreePath = this.getWorktreePath(sessionId, item.workId);
662
- const baseInput = item.input && typeof item.input === 'object'
663
- ? item.input
664
- : {};
665
- return {
666
- ...item,
667
- input: { ...baseInput, worktreePath },
679
+ const kind = item.kind;
680
+ if (!isImplementationWorkItemKind(kind)) {
681
+ const planningItem = {
682
+ ...item,
683
+ kind,
684
+ };
685
+ return planningItem;
686
+ }
687
+ const input = (item.input ?? {});
688
+ const workspace = this.getAssignedWorkspace({
689
+ sessionId,
690
+ workItemId: item.workId,
691
+ kind,
692
+ });
693
+ const mode = input.sourceVariantId
694
+ ? 'vary'
695
+ : DEFAULT_EXECUTION_MODE_BY_KIND[kind];
696
+ const sourceLabel = input.sourceLabel ?? (mode === 'refine' ? input.briefLabel : undefined);
697
+ const sourceBody = input.sourceBody ??
698
+ (mode === 'refine' ? input.briefBody : undefined) ??
699
+ sourceLabel;
700
+ const implementationItem = {
701
+ workId: item.workId,
702
+ variantId: item.variantId,
703
+ kind,
704
+ status: item.status,
705
+ leaseId: item.leaseId,
706
+ attempt: item.attempt,
707
+ leaseExpiresAt: item.leaseExpiresAt,
708
+ workspace,
709
+ mode,
710
+ direction: {
711
+ label: input.briefLabel ?? 'Variant',
712
+ brief: input.briefBody ?? input.instruction ?? 'Create this variant',
713
+ ...(input.briefNotes ? { notes: input.briefNotes } : {}),
714
+ },
715
+ ...(sourceLabel && sourceBody
716
+ ? { sourceDirection: { label: sourceLabel, brief: sourceBody } }
717
+ : {}),
718
+ ...(input.relevantFiles?.length
719
+ ? { relevantFiles: input.relevantFiles }
720
+ : {}),
721
+ ...(input.instruction ? { instruction: input.instruction } : {}),
722
+ ...(input.target ? { target: input.target } : {}),
723
+ ...(input.targetElement ? { targetElement: input.targetElement } : {}),
724
+ ...(input.context?.contextBundlePath
725
+ ? { contextBundlePath: input.context.contextBundlePath }
726
+ : {}),
727
+ completion: kind === 'code_gen'
728
+ ? { kind: 'worktree_diff' }
729
+ : { kind: 'static_artifact' },
668
730
  };
731
+ return implementationItem;
669
732
  });
670
733
  }
734
+ getAssignedWorkspace(args) {
735
+ const workspace = this.resources
736
+ .get(args.sessionId)
737
+ ?.workspaceManager.getAssignedWorkspace(args.workItemId);
738
+ const expectedKind = args.kind === 'code_gen' ? 'git_worktree' : 'artifact_directory';
739
+ if (!workspace || workspace.kind !== expectedKind) {
740
+ throw new errors_1.AgentVariantsError('RUNTIME_VALIDATION_FAILED', `Variant ${args.workItemId} has no assigned ${expectedKind} workspace`);
741
+ }
742
+ return workspace;
743
+ }
671
744
  getVariantsTelemetryMode(projectContext, _target) {
672
745
  if (projectContext.kind === 'fresh' && projectContext.variantContext) {
673
746
  return 'source_grounded';
@@ -967,6 +1040,35 @@ class AgentVariantsOrchestrator {
967
1040
  }
968
1041
  requestWork(args) {
969
1042
  const result = this.store.requestWork(args);
1043
+ try {
1044
+ this.ensureStaticWorkspaces(args.sessionId, result.leasedWorkItems);
1045
+ }
1046
+ catch (error) {
1047
+ for (const item of result.leasedWorkItems) {
1048
+ if (item.kind !== 'static_preview' && item.kind !== 'refine_variant') {
1049
+ continue;
1050
+ }
1051
+ try {
1052
+ this.store.reportComplete({
1053
+ sessionId: args.sessionId,
1054
+ workItemId: item.id,
1055
+ leaseId: result.leaseId,
1056
+ attempt: item.attempt,
1057
+ status: 'failed',
1058
+ error: {
1059
+ code: 'RUNTIME_VALIDATION_FAILED',
1060
+ message: error instanceof Error
1061
+ ? error.message
1062
+ : 'Failed to assign static variant workspace',
1063
+ },
1064
+ });
1065
+ }
1066
+ catch (reportError) {
1067
+ log.warn(`Failed to record static workspace provisioning failure for ${item.id}`, reportError);
1068
+ }
1069
+ }
1070
+ throw error;
1071
+ }
970
1072
  // Record lease times so variant_completed can report lease→report
971
1073
  // duration. (We don't fire a separate code_gen_leased event — the
972
1074
  // funnel signal we care about is per-variant completion duration.)
@@ -977,6 +1079,7 @@ class AgentVariantsOrchestrator {
977
1079
  resources.leasedAt.set(item.id, now);
978
1080
  }
979
1081
  }
1082
+ this.writeLeasedWorkItemBriefings(args.sessionId, result.leasedWorkItems.map((item) => item.id));
980
1083
  this.trackRefinementLeased({ sessionId: args.sessionId, lease: result });
981
1084
  // Decorate each leased item's input with the worktreePath provisioned
982
1085
  // by approve(). The skill instructs agents to write files to
@@ -998,6 +1101,94 @@ class AgentVariantsOrchestrator {
998
1101
  this.emitChange();
999
1102
  return decorated;
1000
1103
  }
1104
+ /**
1105
+ * @effect Writes each leased implementation item's server-authored briefing
1106
+ * BESIDE its workspace so the coordinating host dispatches workers with a
1107
+ * short pointer prompt instead of composing prose per worker. Best-effort:
1108
+ * a briefing write failure never blocks the lease (the host guidance's
1109
+ * packet fields still carry the same data).
1110
+ */
1111
+ writeLeasedWorkItemBriefings(sessionId, leasedWorkItemIds) {
1112
+ if (leasedWorkItemIds.length === 0)
1113
+ return;
1114
+ const leased = new Set(leasedWorkItemIds);
1115
+ for (const item of this.getHostWork(sessionId)) {
1116
+ if (!leased.has(item.workId))
1117
+ continue;
1118
+ if (!isImplementationWorkItemKind(item.kind))
1119
+ continue;
1120
+ const implementation = item;
1121
+ try {
1122
+ const briefingPath = (0, workItemBriefing_1.workItemBriefingPath)(sessionId, item.workId);
1123
+ fs_1.default.mkdirSync(path_1.default.dirname(briefingPath), { recursive: true });
1124
+ fs_1.default.writeFileSync(briefingPath, (0, workItemBriefing_1.renderWorkItemBriefing)(implementation), 'utf8');
1125
+ }
1126
+ catch (error) {
1127
+ log.warn(`Failed to write work-item briefing for ${item.workId}`, error);
1128
+ }
1129
+ }
1130
+ }
1131
+ /** @effect Assigns isolated, server-owned artifact workspaces to leased static work. */
1132
+ ensureStaticWorkspaces(sessionId, items) {
1133
+ const resources = this.ensureResources(sessionId);
1134
+ const projectContext = this.store.getProjectContext(sessionId);
1135
+ // This lane provisions by copying the source artifact tree (vary forks) or
1136
+ // seeding an entry file — it bypasses provisionWorktrees, so it gets its
1137
+ // own provisioning telemetry here.
1138
+ const provisionStartedAt = Date.now();
1139
+ let assignedCount = 0;
1140
+ for (const item of items) {
1141
+ if (item.kind !== 'static_preview' && item.kind !== 'refine_variant') {
1142
+ continue;
1143
+ }
1144
+ if (resources.workspaceManager.getAssignedWorkspace(item.id))
1145
+ continue;
1146
+ assignedCount += 1;
1147
+ const input = (item.input ?? {});
1148
+ const existingProjectPath = projectContext.kind === 'existing'
1149
+ ? projectContext.projectPath
1150
+ : undefined;
1151
+ const existingParent = existingProjectPath && fs_1.default.existsSync(existingProjectPath)
1152
+ ? path_1.default.join(existingProjectPath, '.rivet', 'variant-work', sessionId)
1153
+ : path_1.default.join(os_1.default.tmpdir(), 'rivet-variant-work', sessionId);
1154
+ const parent = projectContext.kind === 'fresh'
1155
+ ? projectContext.workspacePath
1156
+ : existingParent;
1157
+ fs_1.default.mkdirSync(parent, { recursive: true });
1158
+ const rootPath = projectContext.kind === 'fresh'
1159
+ ? path_1.default.join(parent, `.rivet-preview-${item.id}`)
1160
+ : path_1.default.join(parent, item.id);
1161
+ const sourceVariantId = input.sourceVariantId ?? input.variantId;
1162
+ const sourceDirectory = sourceVariantId
1163
+ ? resources.staticPreviews.get(sourceVariantId)?.assetBase
1164
+ : undefined;
1165
+ resources.workspaceManager.assignArtifactWorkspace({
1166
+ workItemId: item.id,
1167
+ rootPath,
1168
+ allowedRoot: parent,
1169
+ entryPath: 'index.html',
1170
+ sourceDirectory,
1171
+ seedHtml: input.currentHtml,
1172
+ });
1173
+ }
1174
+ if (assignedCount > 0) {
1175
+ this.telemetry.track('agent_variants.provisioning_completed', {
1176
+ source: 'mcp',
1177
+ sessionId,
1178
+ kind: 'artifact_directory',
1179
+ variantCount: assignedCount,
1180
+ durationMs: Date.now() - provisionStartedAt,
1181
+ });
1182
+ }
1183
+ }
1184
+ /** Reads a completed static artifact while enforcing its assigned directory boundary. */
1185
+ readAssignedStaticArtifact(sessionId, workItemId) {
1186
+ const workspaceManager = this.resources.get(sessionId)?.workspaceManager;
1187
+ if (!workspaceManager) {
1188
+ throw new errors_1.AgentVariantsError('SESSION_NOT_FOUND', `No live resources for session ${sessionId}`);
1189
+ }
1190
+ return workspaceManager.readArtifact(workItemId);
1191
+ }
1001
1192
  beginStaticPreviewRefinements(args) {
1002
1193
  const seenVariantIds = new Set();
1003
1194
  // Targets whose in-place refinement could neither be created nor folded
@@ -1039,6 +1230,7 @@ class AgentVariantsOrchestrator {
1039
1230
  instruction: target.instruction,
1040
1231
  currentHtml,
1041
1232
  targetElement: target.element,
1233
+ relevantFiles: target.relevantFiles,
1042
1234
  requestId: target.requestId,
1043
1235
  });
1044
1236
  createdWorkItemIds.push(refineItem.id);
@@ -1087,6 +1279,7 @@ class AgentVariantsOrchestrator {
1087
1279
  ...(target.briefs ? { briefs: target.briefs } : {}),
1088
1280
  currentHtml,
1089
1281
  targetElement: target.element,
1282
+ relevantFiles: target.relevantFiles,
1090
1283
  requestId: target.requestId,
1091
1284
  });
1092
1285
  createdWorkItemIds.push(...forks.map((item) => item.id));
@@ -1369,6 +1562,10 @@ class AgentVariantsOrchestrator {
1369
1562
  getWorkItemInput(sessionId, workItemId) {
1370
1563
  return this.store.getWorkItemInput(sessionId, workItemId);
1371
1564
  }
1565
+ /** Returns status after orchestration QA and refinement post-processing. */
1566
+ getWorkItemStatus(sessionId, workItemId) {
1567
+ return this.store.getWorkItemStatus(sessionId, workItemId);
1568
+ }
1372
1569
  getBriefs(sessionId) {
1373
1570
  return this.store.getBriefs(sessionId);
1374
1571
  }
@@ -1713,6 +1910,9 @@ class AgentVariantsOrchestrator {
1713
1910
  sessionId,
1714
1911
  briefs,
1715
1912
  runLabel: args.runLabel?.trim() || (0, runLabel_1.deriveRunLabel)(args.prompt),
1913
+ ...(args.relevantFiles?.length
1914
+ ? { relevantFiles: args.relevantFiles }
1915
+ : {}),
1716
1916
  ...(projectContext.kind === 'existing' && projectContext.variantContext
1717
1917
  ? { variantContext: projectContext.variantContext }
1718
1918
  : {}),
@@ -1755,6 +1955,7 @@ class AgentVariantsOrchestrator {
1755
1955
  prompt: args.prompt,
1756
1956
  count,
1757
1957
  target: args.target,
1958
+ relevantFiles: args.relevantFiles,
1758
1959
  projectContext,
1759
1960
  runLabel: args.runLabel,
1760
1961
  ...(args.sessionId ? { sessionId: args.sessionId } : {}),
@@ -1905,20 +2106,79 @@ class AgentVariantsOrchestrator {
1905
2106
  return null;
1906
2107
  return sessionId;
1907
2108
  }
2109
+ /** @effect Registers a provisioned git worktree with runtime and workspace state. */
2110
+ async registerWorktreeWorkspace(resources, record) {
2111
+ let projectPath = record.worktreePath;
2112
+ try {
2113
+ projectPath = await this.worktrees.getProjectCwdInWorktree(record.worktreePath);
2114
+ }
2115
+ catch (err) {
2116
+ log.warn(`Failed to resolve project path in worktree ${record.worktreePath}; using its root`, err);
2117
+ }
2118
+ resources.worktrees.set(record.workItemId, record);
2119
+ resources.workspaceManager.registerGitWorkspace({
2120
+ workItemId: record.workItemId,
2121
+ rootPath: record.worktreePath,
2122
+ projectPath,
2123
+ });
2124
+ }
1908
2125
  /**
1909
2126
  * Provision a worktree for each appended existing-project variant. Mirrors
1910
2127
  * {@link provisionExistingWorktrees} but only for the newly appended ids, and
1911
2128
  * passes a per-batch `instanceKeyPrefix` so the new worktree dirs do not
1912
2129
  * collide with the first batch's `${sessionId}-${i}` paths.
1913
2130
  */
2131
+ /** Emits provisioning started/completed/failed telemetry around `run` for
2132
+ * the vary-fork/append lanes that bypass provisionWorktrees (and its
2133
+ * telemetry). Rethrows failures untouched. */
2134
+ async withProvisioningTelemetry(args, run) {
2135
+ const startedAt = Date.now();
2136
+ this.telemetry.track('agent_variants.provisioning_started', {
2137
+ source: 'mcp',
2138
+ sessionId: args.sessionId,
2139
+ kind: args.kind,
2140
+ variantCount: args.variantCount,
2141
+ });
2142
+ try {
2143
+ const result = await run();
2144
+ this.telemetry.track('agent_variants.provisioning_completed', {
2145
+ source: 'mcp',
2146
+ sessionId: args.sessionId,
2147
+ kind: args.kind,
2148
+ variantCount: args.variantCount,
2149
+ durationMs: Date.now() - startedAt,
2150
+ });
2151
+ return result;
2152
+ }
2153
+ catch (err) {
2154
+ const timedOut = err instanceof ProvisionTimeoutError;
2155
+ this.telemetry.track('agent_variants.provisioning_failed', {
2156
+ source: 'mcp',
2157
+ sessionId: args.sessionId,
2158
+ kind: args.kind,
2159
+ variantCount: args.variantCount,
2160
+ durationMs: Date.now() - startedAt,
2161
+ timedOut,
2162
+ errorCode: timedOut
2163
+ ? 'PROVISION_TIMEOUT'
2164
+ : err instanceof errors_1.AgentVariantsError
2165
+ ? err.code
2166
+ : 'PROVISION_FAILED',
2167
+ });
2168
+ throw err;
2169
+ }
2170
+ }
1914
2171
  async provisionAppendedExistingWorktrees(sessionId, workItemIds) {
1915
2172
  if (workItemIds.length === 0)
1916
2173
  return;
1917
- const resources = this.ensureResources(sessionId);
1918
- const instanceKeyPrefix = `a${(0, crypto_1.randomUUID)().slice(0, 8)}-`;
1919
- const paths = await withProvisionTimeout(this.worktrees.createWorktrees(sessionId, workItemIds.length, instanceKeyPrefix), this.provisionTimeoutMs);
1920
- workItemIds.forEach((id, i) => {
1921
- resources.worktrees.set(id, { workItemId: id, worktreePath: paths[i] });
2174
+ await this.withProvisioningTelemetry({ sessionId, kind: 'appended_existing', variantCount: workItemIds.length }, async () => {
2175
+ const resources = this.ensureResources(sessionId);
2176
+ const instanceKeyPrefix = `a${(0, crypto_1.randomUUID)().slice(0, 8)}-`;
2177
+ const paths = await withProvisionTimeout(this.worktrees.createWorktrees(sessionId, workItemIds.length, instanceKeyPrefix), this.provisionTimeoutMs);
2178
+ await Promise.all(workItemIds.map((id, index) => this.registerWorktreeWorkspace(resources, {
2179
+ workItemId: id,
2180
+ worktreePath: paths[index],
2181
+ })));
1922
2182
  });
1923
2183
  }
1924
2184
  /**
@@ -1936,14 +2196,21 @@ class AgentVariantsOrchestrator {
1936
2196
  if (!this.worktrees.forkWorktreesFrom) {
1937
2197
  throw new errors_1.AgentVariantsError('RUNTIME_VALIDATION_FAILED', 'worktreeManager does not implement forkWorktreesFrom');
1938
2198
  }
1939
- const resources = this.ensureResources(sessionId);
1940
- const instanceKeyPrefix = `f${(0, crypto_1.randomUUID)().slice(0, 8)}-`;
1941
- const paths = await withProvisionTimeout(this.worktrees.forkWorktreesFrom(sourcePath, sessionId, workItemIds.length, instanceKeyPrefix), this.provisionTimeoutMs);
1942
- workItemIds.forEach((id, i) => {
1943
- resources.worktrees.set(id, { workItemId: id, worktreePath: paths[i] });
2199
+ await this.withProvisioningTelemetry({ sessionId, kind: 'forked_variant', variantCount: workItemIds.length }, async () => {
2200
+ const resources = this.ensureResources(sessionId);
2201
+ const instanceKeyPrefix = `f${(0, crypto_1.randomUUID)().slice(0, 8)}-`;
2202
+ const paths = await withProvisionTimeout(this.worktrees.forkWorktreesFrom(sourcePath, sessionId, workItemIds.length, instanceKeyPrefix), this.provisionTimeoutMs);
2203
+ await Promise.all(workItemIds.map((id, index) => this.registerWorktreeWorkspace(resources, {
2204
+ workItemId: id,
2205
+ worktreePath: paths[index],
2206
+ })));
1944
2207
  });
1945
2208
  }
1946
2209
  async reportComplete(args) {
2210
+ // Phase-timing anchor: lease→report (agent work) vs report→recorded
2211
+ // (QA gate + post-processing) are attributed separately on
2212
+ // `variant_completed` so latency work can tell them apart.
2213
+ const reportReceivedAt = Date.now();
1947
2214
  // Cooperative abort short-circuit: if the work item was removed/cancelled
1948
2215
  // while in flight (delete-while-loading, cancel_variant, session cancel),
1949
2216
  // skip the schema and QA gates entirely — there is no point validating or
@@ -1956,18 +2223,23 @@ class AgentVariantsOrchestrator {
1956
2223
  const workItemKind = this.store.hasSession(args.sessionId)
1957
2224
  ? this.store.getWorkItemKind(args.sessionId, args.workItemId)
1958
2225
  : undefined;
1959
- // Contract validation: a `succeeded` static_preview report MUST carry the
1960
- // deliverable inline as `output.html` (with optional `css` / `js`). Agents
1961
- // sometimes default to writing files to disk and reporting back a file
1962
- // list — that leaves the orchestrator with no HTML to serve, the variant
1963
- // shows "Preview unavailable" in the panel, and `/api/variants/.../static`
1964
- // returns 404. Reject loudly so the agent self-corrects on retry instead
1965
- // of silently producing an empty-preview run.
1966
- if (args.status === 'succeeded' &&
2226
+ const resolvedArgs = args.status === 'succeeded' &&
2227
+ (workItemKind === 'static_preview' ||
2228
+ workItemKind === 'refine_variant') &&
2229
+ args.output === undefined
2230
+ ? {
2231
+ ...args,
2232
+ output: this.readAssignedStaticArtifact(args.sessionId, args.workItemId),
2233
+ }
2234
+ : args;
2235
+ // Contract validation keeps internal callers that provide an explicit
2236
+ // static output honest. Host workers complete assigned artifacts without
2237
+ // transporting the HTML through this report.
2238
+ if (resolvedArgs.status === 'succeeded' &&
1967
2239
  this.store.hasSession(args.sessionId) &&
1968
2240
  (workItemKind === 'static_preview' ||
1969
2241
  workItemKind === 'refine_variant') &&
1970
- !parseStaticPreviewOutput(normalizeOutput(args.output))) {
2242
+ !parseStaticPreviewOutput(normalizeOutput(resolvedArgs.output))) {
1971
2243
  const telemetry = this.refinementTelemetryForWorkItem({
1972
2244
  sessionId: args.sessionId,
1973
2245
  workItemId: args.workItemId,
@@ -1978,25 +2250,25 @@ class AgentVariantsOrchestrator {
1978
2250
  if (telemetry) {
1979
2251
  this.telemetry.trackAgentVariantsRefinementFailed?.(telemetry);
1980
2252
  }
1981
- throw new errors_1.AgentVariantsError('SCHEMA_VALIDATION_FAILED', `${workItemKind} report_variant_complete requires output.html (string, non-empty). ` +
1982
- 'Pass the full HTML inline as output: { html: "<!doctype html>...", css?: "...", js?: "..." }. ' +
1983
- 'Do NOT write the HTML to a file and report a file list — Rivet renders the HTML inline; on-disk files are not read.');
2253
+ throw new errors_1.AgentVariantsError('SCHEMA_VALIDATION_FAILED', `${workItemKind} output.html must be a string and non-empty when explicit output is provided. Host workers should write the assigned index.html instead.`);
1984
2254
  }
1985
2255
  // QA gate: for `succeeded` static_preview reports, run preview QA
1986
2256
  // synchronously before recording success in the store. A failed QA
1987
2257
  // verdict converts the report to `failed` with code `VARIANT_QA_FAILED`
1988
2258
  // so the variant never reaches `ready` and `getVariants` can disable
1989
2259
  // its commit action.
2260
+ const qaStartedAt = Date.now();
1990
2261
  const qaEval = workItemKind === 'refine_variant'
1991
2262
  ? null
1992
- : await this.evaluateQaForReport(args);
2263
+ : await this.evaluateQaForReport(resolvedArgs);
2264
+ const qaMs = qaEval === null ? null : Date.now() - qaStartedAt;
1993
2265
  // A `retry` verdict re-leases the variant for one regeneration (the
1994
2266
  // existing-project diff task-fit gate) instead of recording success —
1995
2267
  // short-circuit the normal record path.
1996
2268
  if (qaEval?.decision === 'retry') {
1997
- return this.handleQaRegenRetry(args, qaEval.qa);
2269
+ return this.handleQaRegenRetry(resolvedArgs, qaEval.qa);
1998
2270
  }
1999
- let effectiveArgs = qaEval?.decision === 'fail_terminal' ? qaEval.overrideArgs : args;
2271
+ let effectiveArgs = qaEval?.decision === 'fail_terminal' ? qaEval.overrideArgs : resolvedArgs;
2000
2272
  // For a successful refine_variant, swap the refined static preview into
2001
2273
  // place and persist history BEFORE recording success in the store. This
2002
2274
  // keeps the user-visible `refinement.status: succeeded` (and the snapshot
@@ -2016,7 +2288,7 @@ class AgentVariantsOrchestrator {
2016
2288
  await this.handleStaticPreviewRefinement({
2017
2289
  sessionId: args.sessionId,
2018
2290
  workItemId: args.workItemId,
2019
- output: args.output,
2291
+ output: effectiveArgs.output,
2020
2292
  });
2021
2293
  }
2022
2294
  catch (err) {
@@ -2062,8 +2334,15 @@ class AgentVariantsOrchestrator {
2062
2334
  const approveStartedAt = resources?.approveStartedAt;
2063
2335
  const approveToCompleteMs = approveStartedAt !== undefined ? Date.now() - approveStartedAt : null;
2064
2336
  // Byte size of generated output for HTML vs full-stack comparison.
2065
- const outputBytes = estimateOutputBytes(args.output);
2337
+ const outputBytes = estimateOutputBytes(effectiveArgs.output);
2066
2338
  const projectContextKind = this.store.getProjectContext(args.sessionId).kind;
2339
+ // Execution mode mirrors the host work-item snapshot derivation so
2340
+ // prod latency can finally segment vary vs create vs refine.
2341
+ const mode = workItemKind !== undefined && isImplementationWorkItemKind(workItemKind)
2342
+ ? this.store.isVaryForkWorkItem(args.sessionId, args.workItemId)
2343
+ ? 'vary'
2344
+ : DEFAULT_EXECUTION_MODE_BY_KIND[workItemKind]
2345
+ : null;
2067
2346
  this.telemetry.track('agent_variants.variant_completed', {
2068
2347
  source: 'mcp',
2069
2348
  sessionId: args.sessionId,
@@ -2072,6 +2351,12 @@ class AgentVariantsOrchestrator {
2072
2351
  attempt: args.attempt,
2073
2352
  durationMs,
2074
2353
  approveToCompleteMs,
2354
+ kind: workItemKind ?? null,
2355
+ mode,
2356
+ // Phase split of `durationMs`: agent work vs server-side gating.
2357
+ leaseToReportMs: leasedAt !== undefined ? reportReceivedAt - leasedAt : null,
2358
+ reportProcessingMs: Date.now() - reportReceivedAt,
2359
+ qaMs,
2075
2360
  hasError: Boolean(effectiveArgs.error),
2076
2361
  errorCode: effectiveArgs.error?.code ?? null,
2077
2362
  isScaffold: resources?.scaffoldBaseWorkItemId === args.workItemId,
@@ -2151,7 +2436,7 @@ class AgentVariantsOrchestrator {
2151
2436
  void this.handleSucceededReport({
2152
2437
  sessionId: args.sessionId,
2153
2438
  workItemId: args.workItemId,
2154
- output: args.output,
2439
+ output: effectiveArgs.output,
2155
2440
  }).catch((err) => {
2156
2441
  log.error(`handleSucceededReport failed for ${args.sessionId}/${args.workItemId}`, err);
2157
2442
  });
@@ -2947,7 +3232,7 @@ class AgentVariantsOrchestrator {
2947
3232
  let cursor = 0;
2948
3233
  if (approveResult.scaffoldBaseWorkItemId) {
2949
3234
  resources.scaffoldBaseWorkItemId = approveResult.scaffoldBaseWorkItemId;
2950
- resources.worktrees.set(approveResult.scaffoldBaseWorkItemId, {
3235
+ await this.registerWorktreeWorkspace(resources, {
2951
3236
  workItemId: approveResult.scaffoldBaseWorkItemId,
2952
3237
  worktreePath: paths[cursor],
2953
3238
  });
@@ -2956,7 +3241,10 @@ class AgentVariantsOrchestrator {
2956
3241
  for (const id of approveResult.codeGenWorkItemIds) {
2957
3242
  const p = paths[cursor];
2958
3243
  cursor += 1;
2959
- resources.worktrees.set(id, { workItemId: id, worktreePath: p });
3244
+ await this.registerWorktreeWorkspace(resources, {
3245
+ workItemId: id,
3246
+ worktreePath: p,
3247
+ });
2960
3248
  }
2961
3249
  }
2962
3250
  /**
@@ -3013,12 +3301,10 @@ class AgentVariantsOrchestrator {
3013
3301
  resources.freshDestinationParent = destinationParent;
3014
3302
  // Each code_gen item maps 1:1 to a fresh worktree. The scaffold_base
3015
3303
  // work item is internal — no dedicated worktree.
3016
- for (let i = 0; i < codeGenIds.length; i++) {
3017
- resources.worktrees.set(codeGenIds[i], {
3018
- workItemId: codeGenIds[i],
3019
- worktreePath: paths[i],
3020
- });
3021
- }
3304
+ await Promise.all(codeGenIds.map((workItemId, index) => this.registerWorktreeWorkspace(resources, {
3305
+ workItemId,
3306
+ worktreePath: paths[index],
3307
+ })));
3022
3308
  // Copy agent-planned assets (e.g. avatar.glb) into each worktree so
3023
3309
  // generated code can reference them via plan.referenceAs. Done before
3024
3310
  // the background install so the assets are on disk by the time the
@@ -3565,6 +3851,11 @@ class AgentVariantsOrchestrator {
3565
3851
  hasAssets: Boolean(resolvedAssetBase),
3566
3852
  };
3567
3853
  resources.staticPreviews.set(workItemId, record);
3854
+ const assignedWorkspace = resources.workspaceManager.getAssignedWorkspace(workItemId);
3855
+ if (assignedWorkspace?.kind === 'artifact_directory' &&
3856
+ assignedWorkspace.rootPath !== record.assetBase) {
3857
+ resources.workspaceManager.removeArtifactWorkspace(workItemId);
3858
+ }
3568
3859
  if (this.store.getProjectContext(sessionId).kind === 'fresh') {
3569
3860
  // History at `<workspacePath>/.rivet/variants/<sessionId>/<variantId>/`
3570
3861
  // is the on-disk record for this generated project.
@@ -3721,6 +4012,14 @@ class AgentVariantsOrchestrator {
3721
4012
  hasAssets: Boolean(resolvedAssetBase) || Boolean(previousRecord?.hasAssets),
3722
4013
  });
3723
4014
  }
4015
+ const refinementWorkspace = resources.workspaceManager.getAssignedWorkspace(args.workItemId);
4016
+ if (refinementWorkspace?.kind === 'artifact_directory' &&
4017
+ dir === refinementWorkspace.rootPath) {
4018
+ resources.workspaceManager.promoteArtifactWorkspace({
4019
+ sourceWorkItemId: args.workItemId,
4020
+ targetWorkItemId: input.variantId,
4021
+ });
4022
+ }
3724
4023
  // History persistence is best-effort, mirroring handleSucceededReport's
3725
4024
  // fire-and-forget persistence for freshly completed variants. A failure
3726
4025
  // here (e.g. a transient filesystem race) must not fail the refinement or
@@ -4848,12 +5147,13 @@ class AgentVariantsOrchestrator {
4848
5147
  log.warn(`cleanupSession (worktree removal) failed for ${sessionId}`, err);
4849
5148
  }
4850
5149
  }
4851
- // Remove fresh static_preview staging dirs (the per-variant directories
4852
- // materialized under workspacePath at report time). On 'committed' the
4853
- // commit flatten already moved/removed them; on cancel/shutdown they'd
4854
- // otherwise orphan under `.rivet/<slug>/`. Existing-mode static previews
4855
- // point at worktree dirs (cleaned by cleanupSession above), so scope this
4856
- // to fresh sessions — where every staticPreviews dir is Rivet staging.
5150
+ // Assigned static workspaces are always Rivet-owned, including existing-
5151
+ // project refinement work staged under `.rivet/variant-work` or /tmp.
5152
+ // Fresh staticPreviews are also Rivet staging unless commit already moved
5153
+ // the selected directory into the destination.
5154
+ const ownedArtifactRoots = new Set(resources.workspaceManager.getOwnedArtifactRoots());
5155
+ resources.workspaceManager.removeOwnedArtifactWorkspaces();
5156
+ const staticDirectories = new Set();
4857
5157
  if (reason !== 'committed') {
4858
5158
  let isFresh = false;
4859
5159
  try {
@@ -4863,16 +5163,21 @@ class AgentVariantsOrchestrator {
4863
5163
  isFresh = false;
4864
5164
  }
4865
5165
  if (isFresh) {
4866
- for (const sp of resources.staticPreviews.values()) {
4867
- try {
4868
- fs_1.default.rmSync(sp.assetBase, { recursive: true, force: true });
4869
- }
4870
- catch (err) {
4871
- log.warn(`failed to remove static preview dir ${sp.assetBase} for ${sessionId}`, err);
5166
+ for (const preview of resources.staticPreviews.values()) {
5167
+ if (!ownedArtifactRoots.has(preview.assetBase)) {
5168
+ staticDirectories.add(preview.assetBase);
4872
5169
  }
4873
5170
  }
4874
5171
  }
4875
5172
  }
5173
+ for (const staticDirectory of staticDirectories) {
5174
+ try {
5175
+ fs_1.default.rmSync(staticDirectory, { recursive: true, force: true });
5176
+ }
5177
+ catch (err) {
5178
+ log.warn(`failed to remove static preview dir ${staticDirectory} for ${sessionId}`, err);
5179
+ }
5180
+ }
4876
5181
  this.resources.delete(sessionId);
4877
5182
  }
4878
5183
  ensureResources(sessionId) {
@@ -4882,6 +5187,7 @@ class AgentVariantsOrchestrator {
4882
5187
  sessionId,
4883
5188
  worktrees: new Map(),
4884
5189
  staticPreviews: new Map(),
5190
+ workspaceManager: new VariantWorkspaceManager_1.VariantWorkspaceManager(),
4885
5191
  cleanupStarted: false,
4886
5192
  committedVariantIds: new Set(),
4887
5193
  startedAt: Date.now(),
@@ -6023,12 +6329,16 @@ const defaultMaterializeProject = async (sourceWorktreePath, destinationPath) =>
6023
6329
  };
6024
6330
  /**
6025
6331
  * Synthesize a fallback brief body for a human-direct unified start slot.
6026
- * Agent callers provide structured briefs before starting. Index/total are
6027
- * unused for now but kept in the signature so future human-direct diversity
6028
- * hints can be added without a call-site change.
6332
+ * Agent callers provide structured briefs before starting. Human-direct
6333
+ * batches get stable, slot-specific focus statements without another model
6334
+ * call so workers do not independently invent overlapping directions.
6029
6335
  */
6030
- function synthesizeUnifiedBriefBody(prompt, _index, _total) {
6336
+ function synthesizeUnifiedBriefBody(prompt, index, total) {
6031
6337
  const trimmed = prompt.trim().replace(/\s+/g, ' ').slice(0, 200);
6032
- return trimmed.length > 0 ? trimmed : 'variant';
6338
+ const intent = trimmed.length > 0 ? trimmed : 'Create this variant';
6339
+ if (total <= 1)
6340
+ return intent;
6341
+ const focus = FALLBACK_DIRECTION_FOCI[(Math.max(index, 1) - 1) % FALLBACK_DIRECTION_FOCI.length];
6342
+ return `${intent} Direction ${index} of ${total}: ${focus}`;
6033
6343
  }
6034
6344
  //# sourceMappingURL=WorktreeOrchestrator.js.map