rivet-design 0.11.11 → 0.11.12

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 (102) hide show
  1. package/dist/mcp/agent-variants/SessionStore.d.ts +150 -10
  2. package/dist/mcp/agent-variants/SessionStore.d.ts.map +1 -1
  3. package/dist/mcp/agent-variants/SessionStore.js +468 -23
  4. package/dist/mcp/agent-variants/SessionStore.js.map +1 -1
  5. package/dist/mcp/agent-variants/WorktreeOrchestrator.d.ts +143 -51
  6. package/dist/mcp/agent-variants/WorktreeOrchestrator.d.ts.map +1 -1
  7. package/dist/mcp/agent-variants/WorktreeOrchestrator.js +465 -299
  8. package/dist/mcp/agent-variants/WorktreeOrchestrator.js.map +1 -1
  9. package/dist/mcp/agent-variants/WorktreeOrchestrator.testHelpers.d.ts +5 -4
  10. package/dist/mcp/agent-variants/WorktreeOrchestrator.testHelpers.d.ts.map +1 -1
  11. package/dist/mcp/agent-variants/WorktreeOrchestrator.testHelpers.js +24 -5
  12. package/dist/mcp/agent-variants/WorktreeOrchestrator.testHelpers.js.map +1 -1
  13. package/dist/mcp/agent-variants/contracts.d.ts +102 -89
  14. package/dist/mcp/agent-variants/contracts.d.ts.map +1 -1
  15. package/dist/mcp/agent-variants/contracts.js +29 -14
  16. package/dist/mcp/agent-variants/contracts.js.map +1 -1
  17. package/dist/mcp/agent-variants/index.d.ts +3 -3
  18. package/dist/mcp/agent-variants/index.d.ts.map +1 -1
  19. package/dist/mcp/agent-variants/index.js +1 -2
  20. package/dist/mcp/agent-variants/index.js.map +1 -1
  21. package/dist/mcp/agent-variants/previewQa.d.ts.map +1 -1
  22. package/dist/mcp/agent-variants/previewQa.js +0 -6
  23. package/dist/mcp/agent-variants/previewQa.js.map +1 -1
  24. package/dist/mcp/agent-variants/tools.d.ts +12 -4
  25. package/dist/mcp/agent-variants/tools.d.ts.map +1 -1
  26. package/dist/mcp/agent-variants/tools.js +69 -13
  27. package/dist/mcp/agent-variants/tools.js.map +1 -1
  28. package/dist/mcp/changeBatchClassification.d.ts +30 -0
  29. package/dist/mcp/changeBatchClassification.d.ts.map +1 -1
  30. package/dist/mcp/changeBatchClassification.js +76 -0
  31. package/dist/mcp/changeBatchClassification.js.map +1 -1
  32. package/dist/mcp/instructions.d.ts.map +1 -1
  33. package/dist/mcp/instructions.js +7 -1
  34. package/dist/mcp/instructions.js.map +1 -1
  35. package/dist/mcp/server.d.ts +51 -3
  36. package/dist/mcp/server.d.ts.map +1 -1
  37. package/dist/mcp/server.js +404 -156
  38. package/dist/mcp/server.js.map +1 -1
  39. package/dist/mcp/watchLoopResumePrompt.d.ts +16 -0
  40. package/dist/mcp/watchLoopResumePrompt.d.ts.map +1 -0
  41. package/dist/mcp/watchLoopResumePrompt.js +21 -0
  42. package/dist/mcp/watchLoopResumePrompt.js.map +1 -0
  43. package/dist/routes/agentVariants.d.ts +10 -1
  44. package/dist/routes/agentVariants.d.ts.map +1 -1
  45. package/dist/routes/agentVariants.js +214 -3
  46. package/dist/routes/agentVariants.js.map +1 -1
  47. package/dist/routes/mcp.d.ts.map +1 -1
  48. package/dist/routes/mcp.js +106 -47
  49. package/dist/routes/mcp.js.map +1 -1
  50. package/dist/services/BridgeBatchPipeline.d.ts +99 -0
  51. package/dist/services/BridgeBatchPipeline.d.ts.map +1 -0
  52. package/dist/services/BridgeBatchPipeline.js +278 -0
  53. package/dist/services/BridgeBatchPipeline.js.map +1 -0
  54. package/dist/services/BridgeQueueStore.d.ts +28 -0
  55. package/dist/services/BridgeQueueStore.d.ts.map +1 -0
  56. package/dist/services/BridgeQueueStore.js +88 -0
  57. package/dist/services/BridgeQueueStore.js.map +1 -0
  58. package/dist/services/InlineVariantGenerationService.d.ts.map +1 -1
  59. package/dist/services/InlineVariantGenerationService.js +15 -3
  60. package/dist/services/InlineVariantGenerationService.js.map +1 -1
  61. package/dist/services/SessionBridgeService.d.ts +127 -31
  62. package/dist/services/SessionBridgeService.d.ts.map +1 -1
  63. package/dist/services/SessionBridgeService.js +428 -83
  64. package/dist/services/SessionBridgeService.js.map +1 -1
  65. package/dist/services/TelemetryService.d.ts +30 -0
  66. package/dist/services/TelemetryService.d.ts.map +1 -1
  67. package/dist/services/TelemetryService.js +42 -0
  68. package/dist/services/TelemetryService.js.map +1 -1
  69. package/dist/services/WorktreeManager.d.ts +29 -2
  70. package/dist/services/WorktreeManager.d.ts.map +1 -1
  71. package/dist/services/WorktreeManager.js +73 -7
  72. package/dist/services/WorktreeManager.js.map +1 -1
  73. package/dist/services/createAgentVariantsOrchestrator.d.ts +0 -8
  74. package/dist/services/createAgentVariantsOrchestrator.d.ts.map +1 -1
  75. package/dist/services/createAgentVariantsOrchestrator.js +1 -18
  76. package/dist/services/createAgentVariantsOrchestrator.js.map +1 -1
  77. package/dist/utils/portUtils.d.ts +9 -0
  78. package/dist/utils/portUtils.d.ts.map +1 -1
  79. package/dist/utils/portUtils.js +26 -0
  80. package/dist/utils/portUtils.js.map +1 -1
  81. package/dist/utils/skills/claude-skill.d.ts +1 -1
  82. package/dist/utils/skills/claude-skill.d.ts.map +1 -1
  83. package/dist/utils/skills/claude-skill.js +31 -12
  84. package/dist/utils/skills/claude-skill.js.map +1 -1
  85. package/dist/utils/skills/cursor-rules.d.ts +1 -1
  86. package/dist/utils/skills/cursor-rules.d.ts.map +1 -1
  87. package/dist/utils/skills/cursor-rules.js +26 -11
  88. package/dist/utils/skills/cursor-rules.js.map +1 -1
  89. package/dist/utils/skills/shared-variants-protocol.d.ts +28 -0
  90. package/dist/utils/skills/shared-variants-protocol.d.ts.map +1 -1
  91. package/dist/utils/skills/shared-variants-protocol.js +88 -14
  92. package/dist/utils/skills/shared-variants-protocol.js.map +1 -1
  93. package/package.json +2 -1
  94. package/src/ui/dist/assets/main-DREt3GGU.css +1 -0
  95. package/src/ui/dist/assets/main-DZ_9EXU9.js +655 -0
  96. package/src/ui/dist/index.html +2 -2
  97. package/dist/mcp/agent-variants/designCritique.d.ts +0 -167
  98. package/dist/mcp/agent-variants/designCritique.d.ts.map +0 -1
  99. package/dist/mcp/agent-variants/designCritique.js +0 -774
  100. package/dist/mcp/agent-variants/designCritique.js.map +0 -1
  101. package/src/ui/dist/assets/main-B1LAuLK_.js +0 -655
  102. package/src/ui/dist/assets/main-D-YwdYjm.css +0 -1
@@ -19,6 +19,17 @@ exports.DEFAULT_LEASE_TTL_MS = 20 * 60 * 1000;
19
19
  * code_gen bump; scaffold timing has never bound, but a longer lease is safe. */
20
20
  exports.SCAFFOLD_LEASE_TTL_MS = 30 * 60 * 1000;
21
21
  const SYSTEM_CLOCK = { now: () => Date.now() };
22
+ /** True when a work item is a past variant adopted into the active session
23
+ * (see {@link SessionStore.appendAdoptedStaticVariant}). Adopted variants are
24
+ * surfaced as session members but excluded from generation accounting. */
25
+ function isAdoptedVariant(item) {
26
+ return Boolean(item.input.adoptedFrom);
27
+ }
28
+ /** True when a static preview is a Vary sibling forked from another variant. */
29
+ function isForkedStaticPreviewVariant(item) {
30
+ return (item.kind === 'static_preview' &&
31
+ Boolean(item.input.sourceVariantId));
32
+ }
22
33
  function fingerprint(parts) {
23
34
  return parts.map((p) => (p === undefined ? '∅' : String(p))).join('|');
24
35
  }
@@ -718,7 +729,23 @@ class SessionStore {
718
729
  }
719
730
  leasable = [target];
720
731
  }
721
- const limit = args.requestedLeaseCount ?? leasable.length;
732
+ else if (args.workItemIds !== undefined) {
733
+ // Scoped lease: restrict to a specific batch's work items so concurrent
734
+ // owners (e.g. several watchers each handling their own Vary run) don't
735
+ // greedily lease each other's items. Leases whatever subset of the
736
+ // requested ids is still leasable; only errors if NONE are.
737
+ const wanted = new Set(args.workItemIds);
738
+ leasable = leasable.filter((item) => wanted.has(item.id));
739
+ if (leasable.length === 0) {
740
+ throw new errors_1.AgentVariantsError('INVALID_STAGE_ACTION', 'None of the requested work items are currently leasable (already leased, running, or done)');
741
+ }
742
+ }
743
+ // `workItemIds` takes precedence over `requestedLeaseCount`: a scoped batch
744
+ // leases ALL of its still-leasable items, not a count-capped subset (the
745
+ // scoping above already narrowed `leasable` to exactly that batch).
746
+ const limit = args.workItemIds !== undefined
747
+ ? leasable.length
748
+ : (args.requestedLeaseCount ?? leasable.length);
722
749
  const issued = leasable.slice(0, Math.max(0, limit));
723
750
  if (issued.length === 0) {
724
751
  throw new errors_1.AgentVariantsError('INVALID_STAGE_ACTION', 'No work items currently leasable (all running, blocked by deps, or done)');
@@ -811,6 +838,16 @@ class SessionStore {
811
838
  item.status = args.status;
812
839
  item.output = args.output;
813
840
  item.error = args.error;
841
+ // Apply an agent-synthesized title/description so the variant card shows a
842
+ // meaningful name (parent identity + the change) instead of the provisional
843
+ // instruction text. Only on success; capped so the label stays short.
844
+ if (args.status === 'succeeded' && (args.title || args.description)) {
845
+ const input = item.input;
846
+ if (args.title)
847
+ input.briefLabel = args.title.trim().slice(0, 80);
848
+ if (args.description)
849
+ input.briefBody = args.description.trim();
850
+ }
814
851
  item.acceptedReports.push(fp);
815
852
  item.lease = undefined;
816
853
  if (args.status === 'failed') {
@@ -824,21 +861,20 @@ class SessionStore {
824
861
  return result;
825
862
  }
826
863
  /**
827
- * Re-open a succeeded variant work item for one aesthetic regeneration.
864
+ * Re-open a succeeded variant work item for one QA-driven regeneration.
828
865
  * Consumes the agent's report (validating the lease), then resets the item
829
866
  * to `pending` so it becomes leasable again via `request_work`, stashing the
830
- * design critique onto its input so the re-leased descriptor carries the
831
- * notes the agent must address before re-reporting.
867
+ * QA notes onto its input so the re-leased descriptor carries the notes the
868
+ * agent must address before re-reporting.
832
869
  *
833
870
  * The work item never reaches a terminal status, so the session stays in
834
- * `waiting_for_results` and never goes terminal off the back of a
835
- * design-quality failure. Idempotent on replay via the accepted-fingerprint
836
- * cache.
871
+ * `waiting_for_results` and never goes terminal off the back of a QA
872
+ * failure. Idempotent on replay via the accepted-fingerprint cache.
837
873
  */
838
- requeueForDesignCritique(args) {
874
+ requeueForRegeneration(args) {
839
875
  const session = this.requireSession(args.sessionId);
840
876
  const fp = fingerprint([
841
- 'requeueForDesignCritique',
877
+ 'requeueForRegeneration',
842
878
  args.sessionId,
843
879
  args.workItemId,
844
880
  args.leaseId,
@@ -857,7 +893,7 @@ class SessionStore {
857
893
  item.output = undefined;
858
894
  item.error = undefined;
859
895
  const input = (item.input ?? {});
860
- item.input = { ...input, designCritique: args.critique };
896
+ item.input = { ...input, qaRetry: args.qaRetry };
861
897
  const result = {
862
898
  stage: session.stage,
863
899
  progress: this.progressOf(session),
@@ -902,6 +938,8 @@ class SessionStore {
902
938
  briefLabel: targetInput.briefLabel,
903
939
  briefBody: targetInput.briefBody,
904
940
  ...(args.currentHtml ? { currentHtml: args.currentHtml } : {}),
941
+ ...(args.targetElement ? { targetElement: args.targetElement } : {}),
942
+ ...(args.requestId ? { requestId: args.requestId } : {}),
905
943
  projectContext: targetInput.projectContext ?? session.projectContext,
906
944
  ...(targetInput.context ? { context: targetInput.context } : {}),
907
945
  },
@@ -912,6 +950,373 @@ class SessionStore {
912
950
  session.stage = 'work_items_ready';
913
951
  return item;
914
952
  }
953
+ /**
954
+ * Fork a completed static_preview variant into N NEW sibling variants. Unlike
955
+ * {@link createRefineVariantWorkItem} (which overwrites the source variant in
956
+ * place), each forked item is a fresh `static_preview` work item with its own
957
+ * id, seeded from the source variant's HTML (`currentHtml`) plus the user's
958
+ * instruction. This backs the "Vary" action, where `variantRequest.count`
959
+ * asks for N new explorations rather than an in-place edit. Returns the
960
+ * created items so the caller can lease them via the normal request-work path.
961
+ */
962
+ createForkedStaticPreviewWorkItems(args) {
963
+ const session = this.requireSession(args.sessionId);
964
+ const source = this.requireWorkItem(session, args.sourceVariantId);
965
+ if (source.kind !== 'static_preview' || source.status !== 'succeeded') {
966
+ throw new errors_1.AgentVariantsError('VARIANT_NOT_EDITABLE', 'Only completed static_preview variants can be varied');
967
+ }
968
+ if (session.variantPick) {
969
+ throw new errors_1.AgentVariantsError('PENDING_CHANGE_CONFLICT', 'Cannot vary a variant after one has already been sent');
970
+ }
971
+ const activeRefinement = [...session.workItems.values()].find((item) => {
972
+ if (item.kind !== 'refine_variant')
973
+ return false;
974
+ const input = item.input;
975
+ return (input.variantId === args.sourceVariantId &&
976
+ (item.status === 'pending' || item.status === 'running'));
977
+ });
978
+ if (activeRefinement) {
979
+ throw new errors_1.AgentVariantsError('PENDING_CHANGE_CONFLICT', 'Cannot vary a variant while an in-place refinement is pending or running');
980
+ }
981
+ const sourceInput = source.input;
982
+ const seedHtml = args.currentHtml ??
983
+ source.output?.html;
984
+ const total = Math.min(Math.max(Math.trunc(args.count) || 1, 1), contracts_1.VARIANT_COUNT_MAX);
985
+ // Label/describe forked variants by the CHANGE requested (the Vary
986
+ // instruction), not the source brief — otherwise every run on the same
987
+ // variant reads identically ("<source> · variation N") and concurrent runs
988
+ // are indistinguishable in the Directions list.
989
+ const instruction = args.instruction.trim();
990
+ const prettyInstruction = instruction
991
+ ? instruction.charAt(0).toUpperCase() + instruction.slice(1)
992
+ : (sourceInput.briefLabel ?? 'Variant');
993
+ const LABEL_MAX = 48;
994
+ const created = [];
995
+ for (let i = 0; i < total; i++) {
996
+ const suffix = total > 1 ? ` · ${i + 1}` : '';
997
+ const labelBase = prettyInstruction.length > LABEL_MAX - suffix.length
998
+ ? `${prettyInstruction.slice(0, LABEL_MAX - suffix.length - 1)}…`
999
+ : prettyInstruction;
1000
+ const item = {
1001
+ id: (0, crypto_1.randomUUID)(),
1002
+ kind: 'static_preview',
1003
+ status: 'pending',
1004
+ attempt: 0,
1005
+ dependsOn: [],
1006
+ input: {
1007
+ briefId: `${sourceInput.briefId ?? 'v'}-vary-${(0, crypto_1.randomUUID)().slice(0, 8)}`,
1008
+ // Provisional name/description (the change) shown while generating;
1009
+ // the agent reports a synthesized title/description on completion.
1010
+ briefLabel: `${labelBase}${suffix}`,
1011
+ briefBody: prettyInstruction,
1012
+ instruction: args.instruction,
1013
+ sourceVariantId: args.sourceVariantId,
1014
+ // Parent identity, so the agent can synthesize a name/description that
1015
+ // combines the source variant with the requested change.
1016
+ ...(sourceInput.briefLabel
1017
+ ? { sourceLabel: sourceInput.briefLabel }
1018
+ : {}),
1019
+ ...(sourceInput.briefBody
1020
+ ? { sourceBody: sourceInput.briefBody }
1021
+ : {}),
1022
+ ...(seedHtml ? { currentHtml: seedHtml } : {}),
1023
+ ...(args.targetElement ? { targetElement: args.targetElement } : {}),
1024
+ ...(args.requestId ? { requestId: args.requestId } : {}),
1025
+ projectContext: sourceInput.projectContext ?? session.projectContext,
1026
+ ...(sourceInput.context ? { context: sourceInput.context } : {}),
1027
+ },
1028
+ acceptedReports: [],
1029
+ };
1030
+ session.workItems.set(item.id, item);
1031
+ // Surface newest siblings at the top of the Directions list: unshift each
1032
+ // in creation order so the batch ends up [variation N … variation 1]
1033
+ // ahead of the pre-existing variants.
1034
+ session.workItemOrder.unshift(item.id);
1035
+ created.push(item);
1036
+ }
1037
+ session.stage = 'work_items_ready';
1038
+ return created;
1039
+ }
1040
+ /**
1041
+ * Append new code_gen variants to an EXISTING-project session that already
1042
+ * holds variants — the worktree-backed analog of
1043
+ * {@link createForkedStaticPreviewWorkItems}. A second "Vary" on an existing
1044
+ * project lands here so its variants ACCUMULATE in the live session instead
1045
+ * of orphaning the prior ones in a fresh, superseding session. The new items
1046
+ * are leasable via {@link requestWork} exactly like the initial code_gen
1047
+ * batch; the orchestrator provisions a worktree per item and runs each
1048
+ * through the normal completion/preview pipeline.
1049
+ *
1050
+ * Reopens the session to `work_items_ready` so the appended pending work is
1051
+ * visible and drives the stage back through generation to ready/degraded.
1052
+ */
1053
+ appendExistingCodeGenVariants(args) {
1054
+ const session = this.requireSession(args.sessionId);
1055
+ if (session.projectContext.kind !== 'existing') {
1056
+ throw new errors_1.AgentVariantsError('INVALID_STAGE_ACTION', 'Only existing-project sessions can append code_gen variants');
1057
+ }
1058
+ if (session.variantPick) {
1059
+ throw new errors_1.AgentVariantsError('PENDING_CHANGE_CONFLICT', 'Cannot append variants after one has already been committed');
1060
+ }
1061
+ if (session.stage === 'cancelled' || session.stage === 'failed') {
1062
+ throw new errors_1.AgentVariantsError('INVALID_STAGE_ACTION', `Cannot append variants to a ${session.stage} session`);
1063
+ }
1064
+ if (args.briefs.length === 0) {
1065
+ throw new errors_1.AgentVariantsError('MISSING_REQUIRED_INPUT', 'At least one brief is required to append variants');
1066
+ }
1067
+ const createdWorkItemIds = [];
1068
+ for (const brief of args.briefs) {
1069
+ const context = resolveVariantWorkItemContext({
1070
+ bundle: args.variantContext ?? session.projectContext.variantContext,
1071
+ projectContext: session.projectContext,
1072
+ brief,
1073
+ slotIndex: session.briefs.length,
1074
+ });
1075
+ const visualRef = brief.visualReferenceUrl
1076
+ ? { visualReferenceUrl: brief.visualReferenceUrl }
1077
+ : {};
1078
+ const item = {
1079
+ id: (0, crypto_1.randomUUID)(),
1080
+ kind: 'code_gen',
1081
+ status: 'pending',
1082
+ attempt: 0,
1083
+ dependsOn: [],
1084
+ input: {
1085
+ briefId: brief.briefId,
1086
+ briefLabel: brief.label,
1087
+ briefBody: brief.body,
1088
+ ...visualRef,
1089
+ ...(args.runLabel ? { runLabel: args.runLabel } : {}),
1090
+ target: session.target,
1091
+ projectContext: session.projectContext,
1092
+ ...(context ? { context } : {}),
1093
+ },
1094
+ acceptedReports: [],
1095
+ };
1096
+ session.workItems.set(item.id, item);
1097
+ // Surface the newest Vary batch at the top of the Directions list, matching
1098
+ // the static-preview fork ordering.
1099
+ session.workItemOrder.unshift(item.id);
1100
+ session.briefs.push(brief);
1101
+ createdWorkItemIds.push(item.id);
1102
+ }
1103
+ session.stage = 'work_items_ready';
1104
+ return { createdWorkItemIds, briefs: args.briefs };
1105
+ }
1106
+ /**
1107
+ * Fork a succeeded code_gen variant into N NEW sibling variants. Each forked
1108
+ * item is seeded from the source variant's worktree (not git HEAD / Original)
1109
+ * and carries `sourceVariantId` so the orchestrator can copy the parent's
1110
+ * tree when provisioning. Backs code_gen "Vary" when the UI targets a parent
1111
+ * variant with `variantRequest.count > 0`.
1112
+ */
1113
+ createForkedCodeGenWorkItems(args) {
1114
+ const session = this.requireSession(args.sessionId);
1115
+ const source = this.requireWorkItem(session, args.sourceVariantId);
1116
+ if (source.kind !== 'code_gen' || source.status !== 'succeeded') {
1117
+ throw new errors_1.AgentVariantsError('VARIANT_NOT_EDITABLE', 'Only completed code_gen variants can be varied');
1118
+ }
1119
+ if (session.projectContext.kind !== 'existing') {
1120
+ throw new errors_1.AgentVariantsError('INVALID_STAGE_ACTION', 'Only existing-project sessions can fork code_gen variants');
1121
+ }
1122
+ if (session.variantPick) {
1123
+ throw new errors_1.AgentVariantsError('PENDING_CHANGE_CONFLICT', 'Cannot vary a variant after one has already been committed');
1124
+ }
1125
+ if (session.stage === 'cancelled' || session.stage === 'failed') {
1126
+ throw new errors_1.AgentVariantsError('INVALID_STAGE_ACTION', `Cannot fork variants in a ${session.stage} session`);
1127
+ }
1128
+ if (args.briefs.length === 0) {
1129
+ throw new errors_1.AgentVariantsError('MISSING_REQUIRED_INPUT', 'At least one brief is required to fork variants');
1130
+ }
1131
+ const activeRefinement = [...session.workItems.values()].find((item) => {
1132
+ if (item.kind !== 'refine_variant')
1133
+ return false;
1134
+ const input = item.input;
1135
+ return (input.variantId === args.sourceVariantId &&
1136
+ (item.status === 'pending' || item.status === 'running'));
1137
+ });
1138
+ if (activeRefinement) {
1139
+ throw new errors_1.AgentVariantsError('PENDING_CHANGE_CONFLICT', 'Cannot vary a variant while an in-place refinement is pending or running');
1140
+ }
1141
+ const sourceInput = source.input;
1142
+ const createdWorkItemIds = [];
1143
+ for (const brief of args.briefs) {
1144
+ const context = resolveVariantWorkItemContext({
1145
+ bundle: args.variantContext ?? session.projectContext.variantContext,
1146
+ projectContext: session.projectContext,
1147
+ brief,
1148
+ slotIndex: session.briefs.length,
1149
+ });
1150
+ const visualRef = brief.visualReferenceUrl
1151
+ ? { visualReferenceUrl: brief.visualReferenceUrl }
1152
+ : {};
1153
+ const item = {
1154
+ id: (0, crypto_1.randomUUID)(),
1155
+ kind: 'code_gen',
1156
+ status: 'pending',
1157
+ attempt: 0,
1158
+ dependsOn: [],
1159
+ input: {
1160
+ briefId: brief.briefId,
1161
+ briefLabel: brief.label,
1162
+ briefBody: brief.body,
1163
+ ...visualRef,
1164
+ ...(args.runLabel ? { runLabel: args.runLabel } : {}),
1165
+ sourceVariantId: args.sourceVariantId,
1166
+ ...(sourceInput.briefLabel
1167
+ ? { sourceLabel: sourceInput.briefLabel }
1168
+ : {}),
1169
+ ...(sourceInput.briefBody ? { sourceBody: sourceInput.briefBody } : {}),
1170
+ target: session.target,
1171
+ projectContext: session.projectContext,
1172
+ ...(context ? { context } : {}),
1173
+ },
1174
+ acceptedReports: [],
1175
+ };
1176
+ session.workItems.set(item.id, item);
1177
+ session.workItemOrder.unshift(item.id);
1178
+ session.briefs.push(brief);
1179
+ createdWorkItemIds.push(item.id);
1180
+ }
1181
+ session.stage = 'work_items_ready';
1182
+ return { createdWorkItemIds, briefs: args.briefs };
1183
+ }
1184
+ /**
1185
+ * Adopt a PAST variant (from a prior session's persisted history) into THIS
1186
+ * active session as a first-class, already-`succeeded` `static_preview`
1187
+ * variant. Triggered when the user makes their first edit (a comment or a
1188
+ * Vary) on a selected past direction: the past variant is rehydrated as a
1189
+ * live session member so the comment/refine machinery scopes and routes to it
1190
+ * exactly like a freshly-generated variant. The caller (orchestrator) registers
1191
+ * the static-preview record that points at the rehydrated files so the snapshot
1192
+ * surfaces it as a `static_artifact`.
1193
+ *
1194
+ * Kept as `static_preview` regardless of the host session's primary variant
1195
+ * kind so {@link createRefineVariantWorkItem} / {@link createForkedStaticPreviewWorkItems}
1196
+ * accept it. Adopted variants are pre-completed artifacts, so they are excluded
1197
+ * from progress/summary/terminal accounting and never reopen the session.
1198
+ *
1199
+ * Idempotent: re-adopting the same `{sessionId, variantId}` returns the
1200
+ * existing work item id (`created: false`) rather than duplicating it.
1201
+ */
1202
+ appendAdoptedStaticVariant(args) {
1203
+ const session = this.requireSession(args.sessionId);
1204
+ if (session.stage === 'cancelled' || session.stage === 'failed') {
1205
+ throw new errors_1.AgentVariantsError('INVALID_STAGE_ACTION', `Cannot adopt a variant into a ${session.stage} session`);
1206
+ }
1207
+ const existing = this.findAdoptedVariant(session, args.source);
1208
+ if (existing) {
1209
+ const input = existing.input;
1210
+ return {
1211
+ workItemId: existing.id,
1212
+ briefId: input.briefId ?? '',
1213
+ created: false,
1214
+ };
1215
+ }
1216
+ if (session.variantPick) {
1217
+ throw new errors_1.AgentVariantsError('PENDING_CHANGE_CONFLICT', 'Cannot adopt a variant after one has already been committed');
1218
+ }
1219
+ const briefId = `adopted-${(0, crypto_1.randomUUID)().slice(0, 8)}`;
1220
+ const item = {
1221
+ id: (0, crypto_1.randomUUID)(),
1222
+ kind: 'static_preview',
1223
+ status: 'succeeded',
1224
+ attempt: 0,
1225
+ dependsOn: [],
1226
+ input: {
1227
+ briefId,
1228
+ briefLabel: args.label,
1229
+ ...(args.description ? { briefBody: args.description } : {}),
1230
+ ...(args.runLabel ? { runLabel: args.runLabel } : {}),
1231
+ adoptedFrom: { ...args.source },
1232
+ projectContext: session.projectContext,
1233
+ },
1234
+ acceptedReports: [],
1235
+ };
1236
+ session.workItems.set(item.id, item);
1237
+ // Adoption is triggered by the user's first in-place edit (a comment /
1238
+ // refine) on an ALREADY-DISPLAYED past direction — not a request for a new
1239
+ // exploration. Appending (rather than unshifting to the top) preserves the
1240
+ // positions of the existing active variants so a refined card does NOT
1241
+ // leapfrog a newer Vary/fork batch. Genuinely new variants (Vary / fork /
1242
+ // append) still surface at the top via their own `unshift` paths. Stage is
1243
+ // intentionally left untouched — the adopted variant is already complete,
1244
+ // so the session must not reopen to `work_items_ready`.
1245
+ session.workItemOrder.push(item.id);
1246
+ return { workItemId: item.id, briefId, created: true };
1247
+ }
1248
+ /**
1249
+ * Create an active session whose initial visible direction is a static
1250
+ * variant rehydrated from persisted history.
1251
+ *
1252
+ * This is the no-usable-session counterpart to
1253
+ * {@link appendAdoptedStaticVariant}: the session starts terminal/ready
1254
+ * because the adopted artifact is already generated, and the adopted work item
1255
+ * remains excluded from generation progress because this run did not generate
1256
+ * fresh variants.
1257
+ */
1258
+ createSessionFromStaticHistoryVariant(args) {
1259
+ const sessionId = (0, crypto_1.randomUUID)();
1260
+ const record = {
1261
+ sessionId,
1262
+ stage: 'ready',
1263
+ prompt: args.prompt,
1264
+ runLabel: args.runLabel?.trim() || undefined,
1265
+ count: 0,
1266
+ projectContext: args.projectContext,
1267
+ briefs: [],
1268
+ approvedBriefs: [],
1269
+ workItems: new Map(),
1270
+ workItemOrder: [],
1271
+ acceptedFingerprints: new Map(),
1272
+ createdAt: this.clock.now(),
1273
+ };
1274
+ this.sessions.set(sessionId, record);
1275
+ const adopted = this.appendAdoptedStaticVariant({
1276
+ sessionId,
1277
+ source: args.source,
1278
+ label: args.label,
1279
+ description: args.description,
1280
+ runLabel: args.runLabel,
1281
+ });
1282
+ return {
1283
+ sessionId,
1284
+ workItemId: adopted.workItemId,
1285
+ briefId: adopted.briefId,
1286
+ };
1287
+ }
1288
+ /** Find an already-adopted variant by its source identity, or undefined. */
1289
+ findAdoptedVariant(session, source) {
1290
+ for (const item of session.workItems.values()) {
1291
+ if (item.removed)
1292
+ continue;
1293
+ const adoptedFrom = item.input
1294
+ .adoptedFrom;
1295
+ if (adoptedFrom &&
1296
+ adoptedFrom.sessionId === source.sessionId &&
1297
+ adoptedFrom.variantId === source.variantId) {
1298
+ return item;
1299
+ }
1300
+ }
1301
+ return undefined;
1302
+ }
1303
+ /**
1304
+ * Stamp a UI correlation id onto already-created variant work items so the
1305
+ * snapshot surfaces it and the panel can map its optimistic placeholders to
1306
+ * the real cards (see {@link ActiveVariantSnapshot.requestId}). Skips ids
1307
+ * that are unknown to the session. Used by `start_variants` for both the
1308
+ * initial batch and the appended (accumulated) batch.
1309
+ */
1310
+ assignVariantRequestId(sessionId, workItemIds, requestId) {
1311
+ const session = this.requireSession(sessionId);
1312
+ for (const id of workItemIds) {
1313
+ const item = session.workItems.get(id);
1314
+ if (!item)
1315
+ continue;
1316
+ const input = (item.input ?? {});
1317
+ item.input = { ...input, requestId };
1318
+ }
1319
+ }
915
1320
  /**
916
1321
  * Fold a newer instruction into a variant's still-pending refinement. Used
917
1322
  * when a second refinement is requested for a variant that already has a
@@ -1348,8 +1753,13 @@ class SessionStore {
1348
1753
  const kind = this.variantWorkItemKind(session);
1349
1754
  // Removed variants are excluded everywhere progress/summary/terminal
1350
1755
  // accounting reads from, so a removed variant neither blocks the session
1351
- // from going terminal nor inflates the ready/total counts.
1352
- return [...session.workItems.values()].filter((i) => i.kind === kind && !i.removed);
1756
+ // from going terminal nor inflates the ready/total counts. Adopted past
1757
+ // variants are likewise excluded: they are pre-completed artifacts pulled in
1758
+ // for editing, not part of this run's generation, so they must not inflate
1759
+ // progress/total or flip a failed run to degraded.
1760
+ return [...session.workItems.values()].filter((i) => (i.kind === kind || isForkedStaticPreviewVariant(i)) &&
1761
+ !i.removed &&
1762
+ !isAdoptedVariant(i));
1353
1763
  }
1354
1764
  /**
1355
1765
  * The work-item kind that represents a user-facing variant for this
@@ -1433,6 +1843,13 @@ class SessionStore {
1433
1843
  }),
1434
1844
  };
1435
1845
  }
1846
+ refinementSnapshotPriority(item) {
1847
+ if (item.status === 'running')
1848
+ return 2;
1849
+ if (item.status === 'pending')
1850
+ return 1;
1851
+ return 0;
1852
+ }
1436
1853
  variantsOf(session) {
1437
1854
  const kind = this.variantWorkItemKind(session);
1438
1855
  const refinementsByVariantId = new Map();
@@ -1442,30 +1859,58 @@ class SessionStore {
1442
1859
  const input = item.input;
1443
1860
  if (!input.variantId)
1444
1861
  continue;
1445
- refinementsByVariantId.set(input.variantId, item);
1862
+ const existing = refinementsByVariantId.get(input.variantId);
1863
+ if (!existing ||
1864
+ this.refinementSnapshotPriority(item) >
1865
+ this.refinementSnapshotPriority(existing)) {
1866
+ refinementsByVariantId.set(input.variantId, item);
1867
+ }
1446
1868
  }
1447
- return [...session.workItems.values()]
1448
- .filter((i) => i.kind === kind && !i.removed)
1869
+ return session.workItemOrder
1870
+ .map((id) => session.workItems.get(id))
1871
+ .filter((i) => !!i &&
1872
+ !i.removed &&
1873
+ (i.kind === kind ||
1874
+ isAdoptedVariant(i) ||
1875
+ isForkedStaticPreviewVariant(i)))
1449
1876
  .map((item) => {
1450
1877
  const input = item.input;
1878
+ const refineItem = refinementsByVariantId.get(item.id);
1879
+ const refineRequestId = refineItem?.input
1880
+ ?.requestId;
1451
1881
  return {
1452
1882
  workItemId: item.id,
1453
1883
  briefId: input.briefId ?? '',
1454
1884
  label: input.briefLabel ?? 'Variant',
1455
1885
  status: item.status,
1456
1886
  createdAt: new Date(session.createdAt).toISOString(),
1457
- runLabel: session.runLabel ?? (0, runLabel_1.deriveRunLabel)(session.prompt),
1887
+ // Prefer the per-item label stamped by an appended Vary batch; the
1888
+ // session-level fallback is correct only for the first batch (its
1889
+ // prompt is the session prompt). Without this, accumulated batches
1890
+ // all show batch 1's tag.
1891
+ runLabel: input.runLabel ??
1892
+ session.runLabel ??
1893
+ (0, runLabel_1.deriveRunLabel)(session.prompt),
1894
+ // Correlation id of the UI request that produced this variant (a fork)
1895
+ // or, for an in-place refine, the request driving its refinement —
1896
+ // lets the UI map its optimistic placeholders to real cards.
1897
+ ...(input.requestId
1898
+ ? { requestId: input.requestId }
1899
+ : refineRequestId
1900
+ ? { requestId: refineRequestId }
1901
+ : {}),
1458
1902
  ...(input.briefBody ? { description: input.briefBody } : {}),
1903
+ ...(input.adoptedFrom
1904
+ ? { adoptedSource: { ...input.adoptedFrom } }
1905
+ : {}),
1459
1906
  design: variantDesignFromInput(input.designContextEntry),
1460
- ...(refinementsByVariantId.get(item.id)
1907
+ ...(refineItem
1461
1908
  ? {
1462
1909
  refinement: {
1463
- workItemId: refinementsByVariantId.get(item.id).id,
1464
- status: refinementsByVariantId.get(item.id).status,
1465
- ...(refinementsByVariantId.get(item.id).error?.code
1466
- ? {
1467
- errorCode: String(refinementsByVariantId.get(item.id).error?.code),
1468
- }
1910
+ workItemId: refineItem.id,
1911
+ status: refineItem.status,
1912
+ ...(refineItem.error?.code
1913
+ ? { errorCode: String(refineItem.error.code) }
1469
1914
  : {}),
1470
1915
  },
1471
1916
  }