rivet-design 0.9.4 → 0.9.5
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.
- package/dist/mcp/agent-variants/SessionStore.d.ts +1 -0
- package/dist/mcp/agent-variants/SessionStore.d.ts.map +1 -1
- package/dist/mcp/agent-variants/SessionStore.js +3 -0
- package/dist/mcp/agent-variants/SessionStore.js.map +1 -1
- package/dist/mcp/agent-variants/WorktreeOrchestrator.d.ts +66 -3
- package/dist/mcp/agent-variants/WorktreeOrchestrator.d.ts.map +1 -1
- package/dist/mcp/agent-variants/WorktreeOrchestrator.js +369 -106
- package/dist/mcp/agent-variants/WorktreeOrchestrator.js.map +1 -1
- package/dist/mcp/agent-variants/contracts.d.ts +106 -0
- package/dist/mcp/agent-variants/contracts.d.ts.map +1 -1
- package/dist/mcp/agent-variants/contracts.js +30 -1
- package/dist/mcp/agent-variants/contracts.js.map +1 -1
- package/dist/mcp/agent-variants/createZeroToOneTool.d.ts +8 -2
- package/dist/mcp/agent-variants/createZeroToOneTool.d.ts.map +1 -1
- package/dist/mcp/agent-variants/createZeroToOneTool.js +24 -8
- package/dist/mcp/agent-variants/createZeroToOneTool.js.map +1 -1
- package/dist/mcp/agent-variants/tools.d.ts +17 -2
- package/dist/mcp/agent-variants/tools.d.ts.map +1 -1
- package/dist/mcp/agent-variants/tools.js +122 -15
- package/dist/mcp/agent-variants/tools.js.map +1 -1
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +13 -7
- package/dist/mcp/server.js.map +1 -1
- package/dist/prompts/agentModPrompts.d.ts.map +1 -1
- package/dist/prompts/agentModPrompts.js +9 -8
- package/dist/prompts/agentModPrompts.js.map +1 -1
- package/dist/proxy-middleware/proxy-config.d.ts +2 -2
- package/dist/proxy-middleware/proxy-config.d.ts.map +1 -1
- package/dist/proxy-middleware/proxy-config.js +66 -22
- package/dist/proxy-middleware/proxy-config.js.map +1 -1
- package/dist/routes/agentVariants.d.ts +2 -13
- package/dist/routes/agentVariants.d.ts.map +1 -1
- package/dist/routes/agentVariants.js +148 -1
- package/dist/routes/agentVariants.js.map +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +58 -1
- package/dist/server.js.map +1 -1
- package/dist/services/ProjectDetectionService.d.ts.map +1 -1
- package/dist/services/ProjectDetectionService.js +12 -0
- package/dist/services/ProjectDetectionService.js.map +1 -1
- package/dist/services/VariantHistoryService.d.ts +117 -0
- package/dist/services/VariantHistoryService.d.ts.map +1 -0
- package/dist/services/VariantHistoryService.js +385 -0
- package/dist/services/VariantHistoryService.js.map +1 -0
- package/dist/services/agent/AgentCore.d.ts +1 -1
- package/dist/services/agent/AgentCore.d.ts.map +1 -1
- package/dist/services/agent/AgentCore.js +24 -1
- package/dist/services/agent/AgentCore.js.map +1 -1
- package/dist/services/agent/AgentModService.d.ts +1 -1
- package/dist/services/agent/AgentModService.js +1 -1
- package/package.json +2 -2
- package/src/ui/dist/assets/main-OdmwI8Od.css +1 -0
- package/src/ui/dist/assets/{main-CpX7fB64.js → main-SuZlKEi0.js} +41 -41
- package/src/ui/dist/index.html +2 -2
- package/src/ui/dist/assets/main-Qqe2_oMT.css +0 -1
|
@@ -4,9 +4,11 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.AgentVariantsOrchestrator = void 0;
|
|
7
|
+
exports.buildStaticPreviewDocument = buildStaticPreviewDocument;
|
|
7
8
|
const crypto_1 = require("crypto");
|
|
8
9
|
const events_1 = require("events");
|
|
9
10
|
const fs_1 = __importDefault(require("fs"));
|
|
11
|
+
const os_1 = __importDefault(require("os"));
|
|
10
12
|
const path_1 = __importDefault(require("path"));
|
|
11
13
|
const child_process_1 = require("child_process");
|
|
12
14
|
const simple_git_1 = require("simple-git");
|
|
@@ -17,6 +19,7 @@ const contracts_1 = require("./contracts");
|
|
|
17
19
|
const viteReactTs_1 = require("../../services/templates/viteReactTs");
|
|
18
20
|
const designCatalog_1 = require("../../services/templates/designCatalog");
|
|
19
21
|
const previewQa_1 = require("./previewQa");
|
|
22
|
+
const VariantHistoryService_1 = require("../../services/VariantHistoryService");
|
|
20
23
|
const log = (0, logger_1.createLogger)('AgentVariantsOrchestrator');
|
|
21
24
|
const FRESH_DEV_SERVER_HOST = '127.0.0.1';
|
|
22
25
|
/**
|
|
@@ -209,6 +212,7 @@ class AgentVariantsOrchestrator {
|
|
|
209
212
|
materializeProject;
|
|
210
213
|
previewQaRunner;
|
|
211
214
|
switchPreviewPort;
|
|
215
|
+
variantHistory;
|
|
212
216
|
resources = new Map();
|
|
213
217
|
/**
|
|
214
218
|
* Committed dev servers from prior sessions that survived teardown. The
|
|
@@ -248,6 +252,7 @@ class AgentVariantsOrchestrator {
|
|
|
248
252
|
deps.materializeProject ?? defaultMaterializeProject;
|
|
249
253
|
this.previewQaRunner = deps.previewQaRunner ?? defaultPreviewQaRunner;
|
|
250
254
|
this.switchPreviewPort = deps.switchPreviewPort;
|
|
255
|
+
this.variantHistory = deps.variantHistory ?? new VariantHistoryService_1.VariantHistoryService();
|
|
251
256
|
}
|
|
252
257
|
// --- Pure delegations (no side effects) ---------------------------------
|
|
253
258
|
propose(args) {
|
|
@@ -736,6 +741,9 @@ class AgentVariantsOrchestrator {
|
|
|
736
741
|
fromStage: stageBefore,
|
|
737
742
|
});
|
|
738
743
|
this.emitChange();
|
|
744
|
+
void this.markPersistedVariantsCancelled(args.sessionId).catch((err) => {
|
|
745
|
+
log.warn(`markPersistedVariantsCancelled failed for ${args.sessionId}`, err);
|
|
746
|
+
});
|
|
739
747
|
void this.teardownSession(args.sessionId, 'cancel').catch((err) => {
|
|
740
748
|
log.error(`teardownSession failed for ${args.sessionId}`, err);
|
|
741
749
|
});
|
|
@@ -763,6 +771,11 @@ class AgentVariantsOrchestrator {
|
|
|
763
771
|
alreadyTerminal: result.alreadyTerminal,
|
|
764
772
|
reason: args.reason ?? null,
|
|
765
773
|
});
|
|
774
|
+
if (!result.alreadyTerminal) {
|
|
775
|
+
void this.markPersistedVariantCancelled(args.sessionId, args.variantId).catch((err) => {
|
|
776
|
+
log.warn(`markPersistedVariantCancelled failed for ${args.sessionId}/${args.variantId}`, err);
|
|
777
|
+
});
|
|
778
|
+
}
|
|
766
779
|
this.emitChange();
|
|
767
780
|
return {
|
|
768
781
|
sessionId: args.sessionId,
|
|
@@ -808,6 +821,38 @@ class AgentVariantsOrchestrator {
|
|
|
808
821
|
// without going back through resources (which may have been torn down).
|
|
809
822
|
const existingPick = this.store.getVariantPick(args.sessionId);
|
|
810
823
|
if (existingPick && existingPick.variantId === args.variantId) {
|
|
824
|
+
// Retry-safe history flip. If the first commit attempt enqueued
|
|
825
|
+
// successfully but the history-persist task crashed (it's
|
|
826
|
+
// fire-and-forget — see persistVariantHistoryAtCommit call site),
|
|
827
|
+
// the chosen variant stays at `completed` instead of `committed`
|
|
828
|
+
// on disk. Re-run the flip on every duplicate attempt; the
|
|
829
|
+
// terminal-status guard in `markStatus` makes this safely
|
|
830
|
+
// idempotent.
|
|
831
|
+
const projectContext = this.store.getProjectContext(args.sessionId);
|
|
832
|
+
let historyProjectPath;
|
|
833
|
+
if (projectContext.kind === 'fresh') {
|
|
834
|
+
historyProjectPath = projectContext.workspaceRoot;
|
|
835
|
+
}
|
|
836
|
+
else {
|
|
837
|
+
try {
|
|
838
|
+
historyProjectPath =
|
|
839
|
+
(await this.resolveEnv(args.sessionId))?.projectPath;
|
|
840
|
+
}
|
|
841
|
+
catch {
|
|
842
|
+
historyProjectPath = undefined;
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
if (historyProjectPath) {
|
|
846
|
+
void this.persistVariantHistoryAtCommit({
|
|
847
|
+
sessionId: args.sessionId,
|
|
848
|
+
chosenVariantId: args.variantId,
|
|
849
|
+
projectPath: historyProjectPath,
|
|
850
|
+
projectKind: projectContext.kind,
|
|
851
|
+
destinationPath: existingPick.destinationPath ?? historyProjectPath,
|
|
852
|
+
}).catch((err) => {
|
|
853
|
+
log.warn(`persistVariantHistoryAtCommit (duplicate retry) failed for session ${args.sessionId}`, err);
|
|
854
|
+
});
|
|
855
|
+
}
|
|
811
856
|
return {
|
|
812
857
|
enqueued: false,
|
|
813
858
|
duplicate: true,
|
|
@@ -831,16 +876,9 @@ class AgentVariantsOrchestrator {
|
|
|
831
876
|
let payload;
|
|
832
877
|
let envelopeDestination;
|
|
833
878
|
let changedFilesCount;
|
|
834
|
-
let freshVariantFolderName;
|
|
835
879
|
if (projectContext.kind === 'fresh') {
|
|
836
880
|
const destinationPath = projectContext.workspacePath;
|
|
837
881
|
this.assertDestinationAvailable(destinationPath);
|
|
838
|
-
const variantFolderName = this.getFreshVariantFolderName({
|
|
839
|
-
sessionId: args.sessionId,
|
|
840
|
-
variantId: args.variantId,
|
|
841
|
-
variantName: input.briefLabel,
|
|
842
|
-
});
|
|
843
|
-
freshVariantFolderName = variantFolderName;
|
|
844
882
|
const freshMode = projectContext.executionPlan?.mode === 'vite_app'
|
|
845
883
|
? 'vite_app'
|
|
846
884
|
: 'static_preview';
|
|
@@ -950,11 +988,16 @@ class AgentVariantsOrchestrator {
|
|
|
950
988
|
}
|
|
951
989
|
else {
|
|
952
990
|
// Static_preview: HTML is the entire deliverable. Write index.html.
|
|
991
|
+
// Prefer the in-memory record; fall back to the persisted history at
|
|
992
|
+
// `<workspaceRoot>/.rivet/variants/<sessionId>/<variantId>/files/index.html`
|
|
993
|
+
// so a process restart between report_variant_complete and commit
|
|
994
|
+
// doesn't strand the variant.
|
|
953
995
|
const staticPreview = resources.staticPreviews.get(args.variantId);
|
|
954
|
-
const
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
:
|
|
996
|
+
const htmlFromSnapshot = await this.variantHistory.readStaticPreview({
|
|
997
|
+
projectPath: projectContext.workspaceRoot,
|
|
998
|
+
sessionId: args.sessionId,
|
|
999
|
+
variantId: args.variantId,
|
|
1000
|
+
});
|
|
958
1001
|
if (!staticPreview && !htmlFromSnapshot) {
|
|
959
1002
|
throw new errors_1.AgentVariantsError('INVALID_STAGE_ACTION', `No static preview found for variant ${args.variantId} — wait for report_variant_complete(succeeded) first`);
|
|
960
1003
|
}
|
|
@@ -1028,27 +1071,25 @@ class AgentVariantsOrchestrator {
|
|
|
1028
1071
|
sessionId: args.sessionId,
|
|
1029
1072
|
envelope,
|
|
1030
1073
|
});
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
}
|
|
1051
|
-
}
|
|
1074
|
+
// History: flip every persisted variant in this session to its terminal
|
|
1075
|
+
// status. Variants were persisted at code_gen success time; this only
|
|
1076
|
+
// patches the status (+ destinationPath on the chosen one). For fresh
|
|
1077
|
+
// sessions, history lives at `<workspaceRoot>/.rivet/variants/` — the
|
|
1078
|
+
// user's working dir — so all variants from any session in the workspace
|
|
1079
|
+
// accumulate in one place, not per-subproject. Best-effort, never blocks
|
|
1080
|
+
// the commit.
|
|
1081
|
+
const historyProjectPath = projectContext.kind === 'fresh'
|
|
1082
|
+
? projectContext.workspaceRoot
|
|
1083
|
+
: envelopeDestination;
|
|
1084
|
+
void this.persistVariantHistoryAtCommit({
|
|
1085
|
+
sessionId: args.sessionId,
|
|
1086
|
+
chosenVariantId: args.variantId,
|
|
1087
|
+
projectPath: historyProjectPath,
|
|
1088
|
+
projectKind: projectContext.kind,
|
|
1089
|
+
destinationPath: envelopeDestination,
|
|
1090
|
+
}).catch((err) => {
|
|
1091
|
+
log.warn(`persistVariantHistoryAtCommit failed for session ${args.sessionId}`, err);
|
|
1092
|
+
});
|
|
1052
1093
|
const enqueueResult = this.adapter.enqueue(envelope);
|
|
1053
1094
|
resources.committedVariantIds.add(args.variantId);
|
|
1054
1095
|
if (this.activeSessionId === args.sessionId) {
|
|
@@ -1090,12 +1131,17 @@ class AgentVariantsOrchestrator {
|
|
|
1090
1131
|
/**
|
|
1091
1132
|
* Ensure the user-facing destination path can receive the new project.
|
|
1092
1133
|
* Rejects when the path exists and is non-empty.
|
|
1134
|
+
*
|
|
1135
|
+
* `.rivet/` (pre-commit snapshots + history manifests) and `.gitignore`
|
|
1136
|
+
* (written by VariantHistoryService.ensureGitignore when variants persist
|
|
1137
|
+
* at success time) are both tolerated — neither is user-authored content
|
|
1138
|
+
* that would be clobbered by the materialize step.
|
|
1093
1139
|
*/
|
|
1094
1140
|
assertDestinationAvailable(destinationPath) {
|
|
1095
1141
|
if (!fs_1.default.existsSync(destinationPath))
|
|
1096
1142
|
return;
|
|
1097
1143
|
const entries = fs_1.default.readdirSync(destinationPath);
|
|
1098
|
-
const userVisibleEntries = entries.filter((entry) => entry !== '.rivet');
|
|
1144
|
+
const userVisibleEntries = entries.filter((entry) => entry !== '.rivet' && entry !== '.gitignore');
|
|
1099
1145
|
if (userVisibleEntries.length === 0)
|
|
1100
1146
|
return;
|
|
1101
1147
|
throw new errors_1.AgentVariantsError('DESTINATION_NOT_EMPTY', `Destination ${destinationPath} is not empty (${userVisibleEntries.length} entries) — refuse to materialize.`);
|
|
@@ -1187,10 +1233,16 @@ class AgentVariantsOrchestrator {
|
|
|
1187
1233
|
designContext: summarizeDesignContext(designContext),
|
|
1188
1234
|
});
|
|
1189
1235
|
log.info(`Provisioning ${codeGenIds.length} fresh worktree(s) for session ${sessionId}`);
|
|
1190
|
-
// destinationParent for fresh worktrees:
|
|
1191
|
-
//
|
|
1192
|
-
// commit into a directory rename
|
|
1193
|
-
|
|
1236
|
+
// destinationParent for fresh worktrees: the user's workspace root
|
|
1237
|
+
// (sibling to `.rivet/`). Keeping the worktree on the same volume as
|
|
1238
|
+
// the materialize destination turns commit into a directory rename
|
|
1239
|
+
// instead of a recursive copy. `path.dirname(workspacePath)` *used*
|
|
1240
|
+
// to equal `workspaceRoot`, but after nesting subprojects under
|
|
1241
|
+
// `<workspaceRoot>/.rivet/<slug>/` the dirname is now `.rivet/`,
|
|
1242
|
+
// which would stage worktrees inside `.rivet/.rivet-variants/`.
|
|
1243
|
+
// Use workspaceRoot directly so staging lives at
|
|
1244
|
+
// `<workspaceRoot>/.rivet-variants/` as originally intended.
|
|
1245
|
+
const destinationParent = projectContext.workspaceRoot;
|
|
1194
1246
|
const paths = await createFresh.call(this.worktrees, sessionId, codeGenIds.length, viteReactTs_1.VITE_REACT_TS_TEMPLATE, designContext, sourceContext, destinationParent);
|
|
1195
1247
|
resources.scaffoldBaseWorkItemId = scaffoldId;
|
|
1196
1248
|
resources.freshDestinationParent = destinationParent;
|
|
@@ -1387,10 +1439,17 @@ class AgentVariantsOrchestrator {
|
|
|
1387
1439
|
};
|
|
1388
1440
|
resources.staticPreviews.set(workItemId, record);
|
|
1389
1441
|
if (this.store.getProjectContext(sessionId).kind === 'fresh') {
|
|
1390
|
-
|
|
1442
|
+
// History at `<workspaceRoot>/.rivet/variants/<sessionId>/<variantId>/`
|
|
1443
|
+
// is the sole on-disk record. The legacy per-subproject snapshot
|
|
1444
|
+
// tree (`<slug>/.rivet/<variantName>/`) is no longer written —
|
|
1445
|
+
// it duplicated this data in a parallel layout and cluttered
|
|
1446
|
+
// `.rivet/` with slug-named directories before the user ever
|
|
1447
|
+
// committed.
|
|
1448
|
+
this.persistCompletedFreshVariant({
|
|
1391
1449
|
sessionId,
|
|
1392
1450
|
workItemId,
|
|
1393
|
-
|
|
1451
|
+
}).catch((err) => {
|
|
1452
|
+
log.warn(`persistCompletedFreshVariant failed for ${sessionId}/${workItemId}`, err);
|
|
1394
1453
|
});
|
|
1395
1454
|
}
|
|
1396
1455
|
const leasedAt = resources.leasedAt.get(workItemId);
|
|
@@ -1424,6 +1483,31 @@ class AgentVariantsOrchestrator {
|
|
|
1424
1483
|
catch (err) {
|
|
1425
1484
|
log.warn(`getDiff failed for ${record.worktreePath}`, err);
|
|
1426
1485
|
}
|
|
1486
|
+
// History: persist every completed variant immediately to
|
|
1487
|
+
// `<projectPath>/.rivet/variants/`. Existing projects pass the captured
|
|
1488
|
+
// diff (or an empty string when capture itself failed — the variant
|
|
1489
|
+
// still succeeded code-gen-wise, and the history row is the only
|
|
1490
|
+
// record the UI has). Fresh-project variants copy their worktree
|
|
1491
|
+
// (vite_app) or the staged HTML (static_preview). Best-effort — a
|
|
1492
|
+
// failure here must never block dev-server startup or the user's pick
|
|
1493
|
+
// flow.
|
|
1494
|
+
if (!isFresh) {
|
|
1495
|
+
this.persistCompletedExistingVariant({
|
|
1496
|
+
sessionId,
|
|
1497
|
+
workItemId,
|
|
1498
|
+
diff: record.diff ?? '',
|
|
1499
|
+
}).catch((err) => {
|
|
1500
|
+
log.warn(`persistCompletedExistingVariant failed for ${sessionId}/${workItemId}`, err);
|
|
1501
|
+
});
|
|
1502
|
+
}
|
|
1503
|
+
else {
|
|
1504
|
+
this.persistCompletedFreshVariant({
|
|
1505
|
+
sessionId,
|
|
1506
|
+
workItemId,
|
|
1507
|
+
}).catch((err) => {
|
|
1508
|
+
log.warn(`persistCompletedFreshVariant failed for ${sessionId}/${workItemId}`, err);
|
|
1509
|
+
});
|
|
1510
|
+
}
|
|
1427
1511
|
// Bring up a dev server in the variant's worktree so the user can cycle
|
|
1428
1512
|
// through live variants in the iframe via the chip. Failures here are
|
|
1429
1513
|
// logged but non-fatal — the user can still pick by reading the diff.
|
|
@@ -1463,80 +1547,206 @@ class AgentVariantsOrchestrator {
|
|
|
1463
1547
|
log.warn(`Failed to start dev server for variant ${workItemId}; live preview disabled for this variant`, err);
|
|
1464
1548
|
}
|
|
1465
1549
|
}
|
|
1466
|
-
|
|
1550
|
+
/**
|
|
1551
|
+
* Persist a completed existing-project code_gen variant into
|
|
1552
|
+
* `<env.projectPath>/.rivet/variants/<sessionId>/<variantId>/`. Called from
|
|
1553
|
+
* `handleSucceededReport` once the worktree diff has been captured but
|
|
1554
|
+
* before the user picks. Status is `completed` until `commitVariant` or a
|
|
1555
|
+
* cancellation transitions it.
|
|
1556
|
+
*/
|
|
1557
|
+
async persistCompletedExistingVariant(args) {
|
|
1467
1558
|
const projectContext = this.store.getProjectContext(args.sessionId);
|
|
1468
|
-
if (projectContext.kind !== '
|
|
1559
|
+
if (projectContext.kind !== 'existing')
|
|
1469
1560
|
return;
|
|
1561
|
+
let projectPath;
|
|
1562
|
+
try {
|
|
1563
|
+
const env = await this.resolveEnv(args.sessionId);
|
|
1564
|
+
projectPath = env.projectPath;
|
|
1470
1565
|
}
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
selectedVariantId: existingProjectManifest?.selectedVariantId,
|
|
1480
|
-
}));
|
|
1481
|
-
const briefInput = this.store.getWorkItemInput(args.sessionId, args.workItemId);
|
|
1482
|
-
const variantFolderName = this.getFreshVariantFolderName({
|
|
1483
|
-
sessionId: args.sessionId,
|
|
1484
|
-
variantId: args.workItemId,
|
|
1485
|
-
variantName: briefInput.briefLabel,
|
|
1486
|
-
});
|
|
1487
|
-
const snapshotPath = (0, createProjectArtifacts_1.createProjectVariantSnapshotPath)(projectPath, variantFolderName);
|
|
1488
|
-
fs_1.default.rmSync(snapshotPath, { recursive: true, force: true });
|
|
1489
|
-
fs_1.default.mkdirSync(snapshotPath, { recursive: true });
|
|
1490
|
-
const briefPath = (0, createProjectArtifacts_1.createProjectVariantBriefPath)(projectPath, variantFolderName);
|
|
1491
|
-
fs_1.default.writeFileSync(briefPath, `# ${briefInput.briefLabel}\n\n${briefInput.briefBody}\n`, 'utf8');
|
|
1492
|
-
const variantManifestPath = (0, createProjectArtifacts_1.createProjectVariantManifestPath)(projectPath, variantFolderName);
|
|
1493
|
-
(0, createProjectArtifacts_1.writeCreateProjectManifestFile)(variantManifestPath, {
|
|
1494
|
-
schemaVersion: createProjectArtifacts_1.CREATE_PROJECT_MANIFEST_SCHEMA_VERSION,
|
|
1495
|
-
createdAt: now,
|
|
1566
|
+
catch (err) {
|
|
1567
|
+
log.warn(`persistCompletedExistingVariant: resolveEnv failed for ${args.sessionId}`, err);
|
|
1568
|
+
return;
|
|
1569
|
+
}
|
|
1570
|
+
const input = this.store.getWorkItemInput(args.sessionId, args.workItemId);
|
|
1571
|
+
const sessionPrompt = this.store.getPrompt(args.sessionId);
|
|
1572
|
+
await this.variantHistory.persistVariant({
|
|
1573
|
+
projectPath,
|
|
1496
1574
|
sessionId: args.sessionId,
|
|
1497
1575
|
variantId: args.workItemId,
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
const now = new Date().toISOString();
|
|
1506
|
-
const projectManifestPath = (0, createProjectArtifacts_1.createProjectManifestPath)(args.projectPath);
|
|
1507
|
-
const existingProjectManifest = this.readManifest(projectManifestPath);
|
|
1508
|
-
(0, createProjectArtifacts_1.writeCreateProjectManifestFile)(projectManifestPath, (0, createProjectArtifacts_1.createProjectManifest)({
|
|
1509
|
-
createdAt: existingProjectManifest?.createdAt ?? now,
|
|
1510
|
-
selectedDesignSlug: existingProjectManifest?.selectedDesignSlug,
|
|
1511
|
-
latestVariantSessionId: args.sessionId,
|
|
1512
|
-
selectedVariantId: args.variantId,
|
|
1513
|
-
}));
|
|
1514
|
-
const variantManifestPath = (0, createProjectArtifacts_1.createProjectVariantManifestPath)(args.projectPath, args.variantFolderName);
|
|
1515
|
-
const existingVariantManifest = this.readManifest(variantManifestPath);
|
|
1516
|
-
(0, createProjectArtifacts_1.writeCreateProjectManifestFile)(variantManifestPath, {
|
|
1517
|
-
...existingVariantManifest,
|
|
1518
|
-
schemaVersion: createProjectArtifacts_1.CREATE_PROJECT_MANIFEST_SCHEMA_VERSION,
|
|
1519
|
-
createdAt: existingVariantManifest?.createdAt ?? now,
|
|
1520
|
-
sessionId: args.sessionId,
|
|
1521
|
-
variantId: args.variantId,
|
|
1522
|
-
variantName: args.variantName,
|
|
1523
|
-
variantFolderName: args.variantFolderName,
|
|
1524
|
-
changedFilesCount: args.changedFilesCount,
|
|
1525
|
-
selectedVariantId: args.variantId,
|
|
1576
|
+
label: input.briefLabel,
|
|
1577
|
+
brief: input.briefBody,
|
|
1578
|
+
sessionPrompt,
|
|
1579
|
+
kind: 'diff',
|
|
1580
|
+
diff: args.diff,
|
|
1581
|
+
changedFilesCount: countDiffFiles(args.diff),
|
|
1582
|
+
projectKind: 'existing',
|
|
1526
1583
|
});
|
|
1527
1584
|
}
|
|
1528
|
-
|
|
1529
|
-
|
|
1585
|
+
/**
|
|
1586
|
+
* Persist a completed fresh-project variant into
|
|
1587
|
+
* `<projectContext.workspacePath>/.rivet/variants/<sessionId>/<variantId>/`.
|
|
1588
|
+
* Called from `handleSucceededReport` right after the per-variant snapshot
|
|
1589
|
+
* lands on disk — for static_preview that's the `.rivet/<slug>/` snapshot
|
|
1590
|
+
* dir; for vite_app fresh variants it's the worktree itself. Status starts
|
|
1591
|
+
* as 'completed' and transitions to 'committed' / 'rejected' / 'cancelled'
|
|
1592
|
+
* via `markStatus` at commit or teardown time.
|
|
1593
|
+
*
|
|
1594
|
+
* Running at success time (rather than commit time) means the history panel
|
|
1595
|
+
* populates live as variants generate — including when the user never picks
|
|
1596
|
+
* one. Mirrors the existing-project persistCompletedExistingVariant flow.
|
|
1597
|
+
*/
|
|
1598
|
+
async persistCompletedFreshVariant(args) {
|
|
1599
|
+
const projectContext = this.store.getProjectContext(args.sessionId);
|
|
1600
|
+
if (projectContext.kind !== 'fresh')
|
|
1601
|
+
return;
|
|
1602
|
+
// Variant history lives at the workspace root — the user's working
|
|
1603
|
+
// dir (e.g. `fable-eng-demo/`). All variants from any session in this
|
|
1604
|
+
// workspace accumulate at
|
|
1605
|
+
// `<workspaceRoot>/.rivet/variants/<sessionId>/<variantId>/`.
|
|
1606
|
+
const historyProjectPath = projectContext.workspaceRoot;
|
|
1607
|
+
const input = this.store.getWorkItemInput(args.sessionId, args.workItemId);
|
|
1608
|
+
const sessionPrompt = this.store.getPrompt(args.sessionId);
|
|
1609
|
+
const designArtifact = resolveDesignArtifact(input.designContextEntry);
|
|
1610
|
+
const resources = this.resources.get(args.sessionId);
|
|
1611
|
+
const worktreeRecord = resources?.worktrees.get(args.workItemId);
|
|
1612
|
+
const staticPreview = resources?.staticPreviews.get(args.workItemId);
|
|
1613
|
+
// Vite_app deliverables are full scaffolded worktrees — pass the worktree
|
|
1614
|
+
// directory as sourceDir and let copyDirFiltered handle it (excludes
|
|
1615
|
+
// node_modules, .rivet, etc). Static_preview deliverables are inline HTML
|
|
1616
|
+
// captured in `resources.staticPreviews`; stage them in a tmp dir so the
|
|
1617
|
+
// existing copy path works without poking the user's workspace.
|
|
1618
|
+
let sourceDir = null;
|
|
1619
|
+
let tmpStagingDir = null;
|
|
1620
|
+
if (worktreeRecord && fs_1.default.existsSync(worktreeRecord.worktreePath)) {
|
|
1621
|
+
sourceDir = worktreeRecord.worktreePath;
|
|
1622
|
+
}
|
|
1623
|
+
else if (staticPreview) {
|
|
1624
|
+
tmpStagingDir = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), `rivet-variant-${args.workItemId}-`));
|
|
1625
|
+
fs_1.default.writeFileSync(path_1.default.join(tmpStagingDir, 'index.html'), staticPreview.html, 'utf8');
|
|
1626
|
+
fs_1.default.writeFileSync(path_1.default.join(tmpStagingDir, 'brief.md'), `# ${input.briefLabel}\n\n${input.briefBody}\n`, 'utf8');
|
|
1627
|
+
sourceDir = tmpStagingDir;
|
|
1628
|
+
}
|
|
1629
|
+
if (!sourceDir) {
|
|
1630
|
+
log.warn(`persistCompletedFreshVariant: no source for ${args.workItemId} (session ${args.sessionId}); skipping`);
|
|
1631
|
+
return;
|
|
1632
|
+
}
|
|
1633
|
+
// For static_preview, only `index.html` is a real deliverable — `brief.md`
|
|
1634
|
+
// is implementation-detail staging that we copy alongside it. Hardcode
|
|
1635
|
+
// the count so the history row matches what `commit_variant` reports
|
|
1636
|
+
// (always 1) instead of double-counting the brief.
|
|
1637
|
+
const changedFilesCount = staticPreview
|
|
1638
|
+
? 1
|
|
1639
|
+
: countWorktreeFiles(sourceDir);
|
|
1640
|
+
try {
|
|
1641
|
+
await this.variantHistory.persistVariant({
|
|
1642
|
+
projectPath: historyProjectPath,
|
|
1643
|
+
sessionId: args.sessionId,
|
|
1644
|
+
variantId: args.workItemId,
|
|
1645
|
+
label: input.briefLabel,
|
|
1646
|
+
brief: input.briefBody,
|
|
1647
|
+
sessionPrompt,
|
|
1648
|
+
kind: 'project-created',
|
|
1649
|
+
sourceDir,
|
|
1650
|
+
changedFilesCount,
|
|
1651
|
+
projectKind: 'fresh',
|
|
1652
|
+
designMarkdown: designArtifact?.markdown,
|
|
1653
|
+
designSource: designArtifact?.source,
|
|
1654
|
+
});
|
|
1655
|
+
}
|
|
1656
|
+
finally {
|
|
1657
|
+
if (tmpStagingDir) {
|
|
1658
|
+
try {
|
|
1659
|
+
fs_1.default.rmSync(tmpStagingDir, { recursive: true, force: true });
|
|
1660
|
+
}
|
|
1661
|
+
catch (err) {
|
|
1662
|
+
log.warn(`Failed to clean up variant staging dir ${tmpStagingDir}: ${err instanceof Error ? err.message : String(err)}`);
|
|
1663
|
+
}
|
|
1664
|
+
}
|
|
1665
|
+
}
|
|
1666
|
+
}
|
|
1667
|
+
/**
|
|
1668
|
+
* At commit time, flip persisted variant manifests to terminal statuses
|
|
1669
|
+
* ('committed' for the chosen, 'rejected' for the rest). Both existing- and
|
|
1670
|
+
* fresh-project variants have already been persisted at code_gen success
|
|
1671
|
+
* time, so this is a pure status update — no source-dir copy required.
|
|
1672
|
+
* Also records the destination path on the chosen variant so history can
|
|
1673
|
+
* surface "this variant became <path>".
|
|
1674
|
+
*/
|
|
1675
|
+
async persistVariantHistoryAtCommit(args) {
|
|
1530
1676
|
const variants = this.store.getVariants(args.sessionId);
|
|
1531
|
-
const
|
|
1532
|
-
|
|
1533
|
-
|
|
1677
|
+
for (const variant of variants) {
|
|
1678
|
+
if (variant.status !== 'succeeded')
|
|
1679
|
+
continue;
|
|
1680
|
+
const workItemId = variant.workItemId;
|
|
1681
|
+
const isChosen = workItemId === args.chosenVariantId;
|
|
1682
|
+
const status = isChosen
|
|
1683
|
+
? 'committed'
|
|
1684
|
+
: 'rejected';
|
|
1685
|
+
await this.variantHistory.markStatus({
|
|
1686
|
+
projectPath: args.projectPath,
|
|
1687
|
+
sessionId: args.sessionId,
|
|
1688
|
+
variantId: workItemId,
|
|
1689
|
+
status,
|
|
1690
|
+
destinationPath: isChosen ? args.destinationPath : undefined,
|
|
1691
|
+
});
|
|
1692
|
+
}
|
|
1693
|
+
}
|
|
1694
|
+
/**
|
|
1695
|
+
* Flip persisted variant manifests to status='cancelled' for every variant
|
|
1696
|
+
* in the session that was already snapshotted to disk. Fresh- and existing-
|
|
1697
|
+
* project variants both persist at success time now, so both need their
|
|
1698
|
+
* manifests flipped here. Best-effort — missing manifests are silently
|
|
1699
|
+
* skipped by VariantHistoryService.markStatus.
|
|
1700
|
+
*/
|
|
1701
|
+
async markPersistedVariantsCancelled(sessionId) {
|
|
1702
|
+
if (!this.store.hasSession(sessionId))
|
|
1703
|
+
return;
|
|
1704
|
+
const projectPath = await this.resolveHistoryProjectPath(sessionId);
|
|
1705
|
+
if (!projectPath)
|
|
1706
|
+
return;
|
|
1707
|
+
const variants = this.store.getVariants(sessionId);
|
|
1708
|
+
for (const variant of variants) {
|
|
1709
|
+
await this.variantHistory.markStatus({
|
|
1710
|
+
projectPath,
|
|
1711
|
+
sessionId,
|
|
1712
|
+
variantId: variant.workItemId,
|
|
1713
|
+
status: 'cancelled',
|
|
1714
|
+
});
|
|
1715
|
+
}
|
|
1716
|
+
}
|
|
1717
|
+
async markPersistedVariantCancelled(sessionId, variantId) {
|
|
1718
|
+
if (!this.store.hasSession(sessionId))
|
|
1719
|
+
return;
|
|
1720
|
+
const projectPath = await this.resolveHistoryProjectPath(sessionId);
|
|
1721
|
+
if (!projectPath)
|
|
1722
|
+
return;
|
|
1723
|
+
await this.variantHistory.markStatus({
|
|
1724
|
+
projectPath,
|
|
1725
|
+
sessionId,
|
|
1726
|
+
variantId,
|
|
1727
|
+
status: 'cancelled',
|
|
1534
1728
|
});
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1729
|
+
}
|
|
1730
|
+
/**
|
|
1731
|
+
* Resolve the project path that owns `.rivet/variants/` for a session.
|
|
1732
|
+
* Existing sessions: the user's project (via `resolveEnv`). Fresh sessions:
|
|
1733
|
+
* the *workspace root*, which is the parent of `workspacePath` — variants
|
|
1734
|
+
* accumulate there across sessions instead of being scattered under each
|
|
1735
|
+
* subproject. Must match what `persistCompletedFreshVariant` writes to.
|
|
1736
|
+
*/
|
|
1737
|
+
async resolveHistoryProjectPath(sessionId) {
|
|
1738
|
+
const projectContext = this.store.getProjectContext(sessionId);
|
|
1739
|
+
if (projectContext.kind === 'fresh') {
|
|
1740
|
+
return projectContext.workspaceRoot;
|
|
1741
|
+
}
|
|
1742
|
+
try {
|
|
1743
|
+
const env = await this.resolveEnv(sessionId);
|
|
1744
|
+
return env.projectPath;
|
|
1745
|
+
}
|
|
1746
|
+
catch (err) {
|
|
1747
|
+
log.warn(`resolveHistoryProjectPath: resolveEnv failed for ${sessionId}`, err);
|
|
1748
|
+
return null;
|
|
1538
1749
|
}
|
|
1539
|
-
return `${baseSlug}-${index + 1}`;
|
|
1540
1750
|
}
|
|
1541
1751
|
readManifest(manifestPath) {
|
|
1542
1752
|
if (!fs_1.default.existsSync(manifestPath)) {
|
|
@@ -1995,11 +2205,33 @@ function parseStaticPreviewOutput(output) {
|
|
|
1995
2205
|
};
|
|
1996
2206
|
}
|
|
1997
2207
|
function buildStaticPreviewDocument(input) {
|
|
1998
|
-
if (/<!doctype html>|<html[\s>]/i.test(input.html)) {
|
|
1999
|
-
return input.html;
|
|
2000
|
-
}
|
|
2001
2208
|
const style = input.css ? `<style>\n${input.css}\n</style>` : '';
|
|
2002
2209
|
const script = input.js ? `<script>\n${input.js}\n</script>` : '';
|
|
2210
|
+
if (/<!doctype html>|<html[\s>]/i.test(input.html)) {
|
|
2211
|
+
// Full document: inject css before </head> and js before </body>. The
|
|
2212
|
+
// agent often passes a complete `<!doctype html>...` blob with css/js
|
|
2213
|
+
// alongside; without this they're silently dropped and the variant ships
|
|
2214
|
+
// unstyled and non-interactive. Falls back to appending to the end if
|
|
2215
|
+
// the closing tag isn't found.
|
|
2216
|
+
let doc = input.html;
|
|
2217
|
+
if (style) {
|
|
2218
|
+
if (/<\/head>/i.test(doc)) {
|
|
2219
|
+
doc = doc.replace(/<\/head>/i, () => `${style}\n</head>`);
|
|
2220
|
+
}
|
|
2221
|
+
else {
|
|
2222
|
+
doc += `\n${style}`;
|
|
2223
|
+
}
|
|
2224
|
+
}
|
|
2225
|
+
if (script) {
|
|
2226
|
+
if (/<\/body>/i.test(doc)) {
|
|
2227
|
+
doc = doc.replace(/<\/body>/i, () => `${script}\n</body>`);
|
|
2228
|
+
}
|
|
2229
|
+
else {
|
|
2230
|
+
doc += `\n${script}`;
|
|
2231
|
+
}
|
|
2232
|
+
}
|
|
2233
|
+
return doc;
|
|
2234
|
+
}
|
|
2003
2235
|
return `<!doctype html>
|
|
2004
2236
|
<html lang="en">
|
|
2005
2237
|
<head>
|
|
@@ -2061,6 +2293,7 @@ const toActiveProjectContext = (projectContext) => {
|
|
|
2061
2293
|
return {
|
|
2062
2294
|
kind: 'fresh',
|
|
2063
2295
|
workspacePath: projectContext.workspacePath,
|
|
2296
|
+
workspaceRoot: projectContext.workspaceRoot,
|
|
2064
2297
|
framework: projectContext.framework,
|
|
2065
2298
|
designContext: projectContext.designContext?.map((entry) => entry.kind === 'slug'
|
|
2066
2299
|
? { kind: 'slug', slug: entry.slug }
|
|
@@ -2161,6 +2394,36 @@ const addDesignContextArtifact = (artifactsByContent, artifact) => {
|
|
|
2161
2394
|
usedByVariantCount: 1,
|
|
2162
2395
|
});
|
|
2163
2396
|
};
|
|
2397
|
+
/**
|
|
2398
|
+
* Resolve a per-variant design context entry into the raw DESIGN.md markdown
|
|
2399
|
+
* the worktree scaffolder writes, plus a small `designSource` descriptor for
|
|
2400
|
+
* the variant manifest. Slug entries resolve through the bundled catalog;
|
|
2401
|
+
* markdown entries (Agent Browser / inspiration extractor output) carry their
|
|
2402
|
+
* stored markdown verbatim. Returns null when the entry is missing or the
|
|
2403
|
+
* slug doesn't resolve to bundled markdown.
|
|
2404
|
+
*/
|
|
2405
|
+
const resolveDesignArtifact = (entry) => {
|
|
2406
|
+
if (!entry)
|
|
2407
|
+
return null;
|
|
2408
|
+
if (entry.kind === 'markdown') {
|
|
2409
|
+
return {
|
|
2410
|
+
markdown: entry.content,
|
|
2411
|
+
source: { kind: 'markdown', label: entry.label },
|
|
2412
|
+
};
|
|
2413
|
+
}
|
|
2414
|
+
const markdown = (0, designCatalog_1.loadDesignSystemMarkdown)(entry.slug);
|
|
2415
|
+
if (!markdown)
|
|
2416
|
+
return null;
|
|
2417
|
+
const catalogEntry = (0, designCatalog_1.getDesignSystemBySlug)(entry.slug);
|
|
2418
|
+
return {
|
|
2419
|
+
markdown,
|
|
2420
|
+
source: {
|
|
2421
|
+
kind: 'slug',
|
|
2422
|
+
slug: entry.slug,
|
|
2423
|
+
label: catalogEntry?.name ?? entry.slug,
|
|
2424
|
+
},
|
|
2425
|
+
};
|
|
2426
|
+
};
|
|
2164
2427
|
const summarizeDesignContext = (designContext) => {
|
|
2165
2428
|
if (!designContext)
|
|
2166
2429
|
return null;
|