rivet-design 0.10.6 → 0.10.7
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 +11 -0
- package/dist/mcp/agent-variants/SessionStore.d.ts.map +1 -1
- package/dist/mcp/agent-variants/SessionStore.js +44 -14
- package/dist/mcp/agent-variants/SessionStore.js.map +1 -1
- package/dist/mcp/agent-variants/WorktreeOrchestrator.d.ts +44 -3
- package/dist/mcp/agent-variants/WorktreeOrchestrator.d.ts.map +1 -1
- package/dist/mcp/agent-variants/WorktreeOrchestrator.js +297 -63
- package/dist/mcp/agent-variants/WorktreeOrchestrator.js.map +1 -1
- package/dist/mcp/agent-variants/WorktreeOrchestrator.testHelpers.d.ts +4 -0
- package/dist/mcp/agent-variants/WorktreeOrchestrator.testHelpers.d.ts.map +1 -1
- package/dist/mcp/agent-variants/WorktreeOrchestrator.testHelpers.js +1 -1
- package/dist/mcp/agent-variants/WorktreeOrchestrator.testHelpers.js.map +1 -1
- package/dist/mcp/agent-variants/contracts.d.ts +28 -0
- package/dist/mcp/agent-variants/contracts.d.ts.map +1 -1
- package/dist/mcp/agent-variants/contracts.js.map +1 -1
- package/dist/mcp/agent-variants/createZeroToOneTool.d.ts +47 -265
- package/dist/mcp/agent-variants/createZeroToOneTool.d.ts.map +1 -1
- package/dist/mcp/agent-variants/createZeroToOneTool.js +147 -207
- package/dist/mcp/agent-variants/createZeroToOneTool.js.map +1 -1
- package/dist/mcp/agent-variants/generatedDestination.d.ts +75 -0
- package/dist/mcp/agent-variants/generatedDestination.d.ts.map +1 -0
- package/dist/mcp/agent-variants/generatedDestination.js +104 -0
- package/dist/mcp/agent-variants/generatedDestination.js.map +1 -0
- package/dist/mcp/agent-variants/index.d.ts +0 -1
- package/dist/mcp/agent-variants/index.d.ts.map +1 -1
- package/dist/mcp/agent-variants/index.js +1 -3
- package/dist/mcp/agent-variants/index.js.map +1 -1
- package/dist/mcp/agent-variants/pinterestSourceContext.d.ts +18 -0
- package/dist/mcp/agent-variants/pinterestSourceContext.d.ts.map +1 -0
- package/dist/mcp/agent-variants/pinterestSourceContext.js +134 -0
- package/dist/mcp/agent-variants/pinterestSourceContext.js.map +1 -0
- package/dist/mcp/agent-variants/runPlan.d.ts +107 -0
- package/dist/mcp/agent-variants/runPlan.d.ts.map +1 -0
- package/dist/mcp/agent-variants/runPlan.js +97 -0
- package/dist/mcp/agent-variants/runPlan.js.map +1 -0
- package/dist/mcp/agent-variants/tools.d.ts +48 -3
- package/dist/mcp/agent-variants/tools.d.ts.map +1 -1
- package/dist/mcp/agent-variants/tools.js +54 -47
- package/dist/mcp/agent-variants/tools.js.map +1 -1
- package/dist/mcp/integrations/tools.d.ts +14 -0
- package/dist/mcp/integrations/tools.d.ts.map +1 -0
- package/dist/mcp/integrations/tools.js +38 -0
- package/dist/mcp/integrations/tools.js.map +1 -0
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +18 -13
- package/dist/mcp/server.js.map +1 -1
- package/dist/routes/agentVariants.d.ts.map +1 -1
- package/dist/routes/agentVariants.js +5 -0
- package/dist/routes/agentVariants.js.map +1 -1
- package/dist/services/IntegrationsClient.d.ts +58 -0
- package/dist/services/IntegrationsClient.d.ts.map +1 -0
- package/dist/services/IntegrationsClient.js +81 -0
- package/dist/services/IntegrationsClient.js.map +1 -0
- package/dist/services/staticStarter.d.ts +1 -1
- package/dist/services/staticStarter.d.ts.map +1 -1
- package/dist/services/staticStarter.js +76 -19
- package/dist/services/staticStarter.js.map +1 -1
- package/dist/utils/skills/describe-motion-protocol.d.ts +1 -1
- package/dist/utils/skills/describe-motion-protocol.d.ts.map +1 -1
- package/dist/utils/skills/describe-motion-protocol.js +11 -11
- package/dist/utils/skills/shared-variants-protocol.d.ts.map +1 -1
- package/dist/utils/skills/shared-variants-protocol.js +6 -4
- package/dist/utils/skills/shared-variants-protocol.js.map +1 -1
- package/package.json +1 -1
- package/src/ui/dist/assets/main-DUIrSkV3.css +1 -0
- package/src/ui/dist/assets/main-DYpxGvCu.js +646 -0
- package/src/ui/dist/index.html +2 -2
- package/src/ui/dist/assets/main-B54sNpwl.css +0 -1
- package/src/ui/dist/assets/main-B9BHMA4s.js +0 -646
|
@@ -16,6 +16,7 @@ const logger_1 = require("../../utils/logger");
|
|
|
16
16
|
const errors_1 = require("./errors");
|
|
17
17
|
const createProjectArtifacts_1 = require("./createProjectArtifacts");
|
|
18
18
|
const contracts_1 = require("./contracts");
|
|
19
|
+
const runPlan_1 = require("./runPlan");
|
|
19
20
|
const viteReactTs_1 = require("../../services/templates/viteReactTs");
|
|
20
21
|
const StaticPreviewServer_1 = require("../../services/StaticPreviewServer");
|
|
21
22
|
const designCatalog_1 = require("../../services/templates/designCatalog");
|
|
@@ -359,6 +360,47 @@ class AgentVariantsOrchestrator {
|
|
|
359
360
|
this.events.off('change', listener);
|
|
360
361
|
};
|
|
361
362
|
}
|
|
363
|
+
/** Number of live Rivet UI clients subscribed to the variants SSE stream.
|
|
364
|
+
* A subscribed client means a Rivet page is open in the browser. */
|
|
365
|
+
uiClientCount = 0;
|
|
366
|
+
/** Wall-clock of the last UI-client connect/disconnect. `hasUiClients`
|
|
367
|
+
* treats a very recent disconnect as still-connected to ride out the brief
|
|
368
|
+
* gap when an EventSource drops and auto-reconnects (default retry ~3s) —
|
|
369
|
+
* otherwise a `propose`/`start` landing in that window reads zero clients
|
|
370
|
+
* and spawns a duplicate browser tab. */
|
|
371
|
+
lastUiClientActivityAt = 0;
|
|
372
|
+
/** Grace window covering an EventSource reconnect (its default retry is ~3s;
|
|
373
|
+
* the SSE heartbeat is 30s). Long enough to span a reconnect, short enough
|
|
374
|
+
* that a genuine page close frees the "reopen on next propose" path quickly. */
|
|
375
|
+
static UI_CLIENT_GRACE_MS = 6000;
|
|
376
|
+
/**
|
|
377
|
+
* Register a live Rivet UI client (called when the variants SSE stream opens).
|
|
378
|
+
* Returns a release fn to call on disconnect. Used as the "a Rivet page is
|
|
379
|
+
* already open" signal so the MCP server can avoid spawning a duplicate tab
|
|
380
|
+
* every time the variants flow re-enters an already-active editor session.
|
|
381
|
+
*/
|
|
382
|
+
registerUiClient() {
|
|
383
|
+
this.uiClientCount += 1;
|
|
384
|
+
this.lastUiClientActivityAt = Date.now();
|
|
385
|
+
let released = false;
|
|
386
|
+
return () => {
|
|
387
|
+
if (released)
|
|
388
|
+
return;
|
|
389
|
+
released = true;
|
|
390
|
+
this.uiClientCount = Math.max(0, this.uiClientCount - 1);
|
|
391
|
+
this.lastUiClientActivityAt = Date.now();
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
/** True when at least one Rivet UI page is currently connected — or one
|
|
395
|
+
* disconnected within the grace window (an EventSource mid-reconnect). */
|
|
396
|
+
hasUiClients() {
|
|
397
|
+
if (this.uiClientCount > 0)
|
|
398
|
+
return true;
|
|
399
|
+
if (this.lastUiClientActivityAt === 0)
|
|
400
|
+
return false;
|
|
401
|
+
return (Date.now() - this.lastUiClientActivityAt <
|
|
402
|
+
AgentVariantsOrchestrator.UI_CLIENT_GRACE_MS);
|
|
403
|
+
}
|
|
362
404
|
/** Build the snapshot the chip cares about. Reads from SessionStore +
|
|
363
405
|
* per-resource state; safe to call at any time. */
|
|
364
406
|
buildActiveSnapshot() {
|
|
@@ -466,18 +508,7 @@ class AgentVariantsOrchestrator {
|
|
|
466
508
|
if (seenVariantIds.has(target.variantId))
|
|
467
509
|
continue;
|
|
468
510
|
seenVariantIds.add(target.variantId);
|
|
469
|
-
const currentHtml = this.
|
|
470
|
-
.get(args.sessionId)
|
|
471
|
-
?.staticPreviews.get(target.variantId)?.html ??
|
|
472
|
-
(() => {
|
|
473
|
-
try {
|
|
474
|
-
const output = this.store.getWorkItemOutput(args.sessionId, target.variantId);
|
|
475
|
-
return output?.html;
|
|
476
|
-
}
|
|
477
|
-
catch {
|
|
478
|
-
return undefined;
|
|
479
|
-
}
|
|
480
|
-
})();
|
|
511
|
+
const currentHtml = this.getStaticPreviewHtml(args.sessionId, target.variantId);
|
|
481
512
|
try {
|
|
482
513
|
this.store.createRefineVariantWorkItem({
|
|
483
514
|
sessionId: args.sessionId,
|
|
@@ -532,18 +563,7 @@ class AgentVariantsOrchestrator {
|
|
|
532
563
|
throw error;
|
|
533
564
|
}
|
|
534
565
|
}
|
|
535
|
-
const currentHtml = this.
|
|
536
|
-
.get(args.sessionId)
|
|
537
|
-
?.staticPreviews.get(args.variantId)?.html ??
|
|
538
|
-
(() => {
|
|
539
|
-
try {
|
|
540
|
-
const output = this.store.getWorkItemOutput(args.sessionId, args.variantId);
|
|
541
|
-
return output?.html;
|
|
542
|
-
}
|
|
543
|
-
catch {
|
|
544
|
-
return undefined;
|
|
545
|
-
}
|
|
546
|
-
})();
|
|
566
|
+
const currentHtml = this.getStaticPreviewHtml(args.sessionId, args.variantId);
|
|
547
567
|
const refineItem = this.store.createRefineVariantWorkItem({
|
|
548
568
|
sessionId: args.sessionId,
|
|
549
569
|
variantId: args.variantId,
|
|
@@ -668,14 +688,25 @@ class AgentVariantsOrchestrator {
|
|
|
668
688
|
return this.resources.get(sessionId)?.worktrees.get(workItemId)?.port;
|
|
669
689
|
}
|
|
670
690
|
getStaticPreviewHtml(sessionId, workItemId) {
|
|
671
|
-
// Primary:
|
|
672
|
-
|
|
691
|
+
// Primary: read the materialized index.html from the variant's per-variant
|
|
692
|
+
// directory (written by handleSucceededReport). The directory is the source
|
|
693
|
+
// of truth for the static deliverable.
|
|
694
|
+
const dir = this.resources
|
|
673
695
|
.get(sessionId)
|
|
674
|
-
?.staticPreviews.get(workItemId)?.
|
|
675
|
-
if (
|
|
676
|
-
|
|
696
|
+
?.staticPreviews.get(workItemId)?.assetBase;
|
|
697
|
+
if (dir) {
|
|
698
|
+
try {
|
|
699
|
+
const fromDisk = fs_1.default.readFileSync(path_1.default.join(dir, 'index.html'), 'utf8');
|
|
700
|
+
if (fromDisk.length > 0)
|
|
701
|
+
return fromDisk;
|
|
702
|
+
}
|
|
703
|
+
catch {
|
|
704
|
+
// fall through to the work-item output fallback below
|
|
705
|
+
}
|
|
706
|
+
}
|
|
677
707
|
// Fallback: read directly from the work item's stored output — available
|
|
678
|
-
// as soon as reportComplete runs, before handleSucceededReport fires
|
|
708
|
+
// as soon as reportComplete runs, before handleSucceededReport fires (so
|
|
709
|
+
// the per-variant directory may not exist yet).
|
|
679
710
|
try {
|
|
680
711
|
const output = this.store.getWorkItemOutput(sessionId, workItemId);
|
|
681
712
|
return typeof output?.html === 'string' && output.html.length > 0
|
|
@@ -707,7 +738,7 @@ class AgentVariantsOrchestrator {
|
|
|
707
738
|
const record = this.resources
|
|
708
739
|
.get(sessionId)
|
|
709
740
|
?.staticPreviews.get(workItemId);
|
|
710
|
-
return Boolean(record?.
|
|
741
|
+
return Boolean(record?.hasAssets);
|
|
711
742
|
}
|
|
712
743
|
/**
|
|
713
744
|
* Resolve an absolute, sandboxed path for a sibling asset that the
|
|
@@ -818,9 +849,10 @@ class AgentVariantsOrchestrator {
|
|
|
818
849
|
* Supports both existing-project sessions (spawns code_gen work items
|
|
819
850
|
* for the agent to lease) and zero-to-one sessions (spawns
|
|
820
851
|
* static_preview work items; the server runs scaffold_base in the
|
|
821
|
-
* background).
|
|
822
|
-
*
|
|
823
|
-
*
|
|
852
|
+
* background). This is the single-call path; source-grounded zero-to-one
|
|
853
|
+
* sessions (source URLs / inspiration extraction) run through `propose`
|
|
854
|
+
* instead so the source-research flow can gate generation — start_variants
|
|
855
|
+
* routes them there. This method does not run source research.
|
|
824
856
|
*
|
|
825
857
|
* Host-agnostic: no LLM calls happen here. The agent (Claude Code,
|
|
826
858
|
* Cursor, Codex) generates the label and code when it leases the
|
|
@@ -839,7 +871,11 @@ class AgentVariantsOrchestrator {
|
|
|
839
871
|
Boolean(sourceContext?.sourceIntent) ||
|
|
840
872
|
Boolean(sourceContext?.artifact);
|
|
841
873
|
if (isSourceGrounded) {
|
|
842
|
-
|
|
874
|
+
// startUnified is the single-call path; source-grounded sessions must
|
|
875
|
+
// run through `propose` so the source-research flow gates generation.
|
|
876
|
+
// start_variants routes them there before reaching here — this guard is
|
|
877
|
+
// an internal invariant, not a user-facing dead end.
|
|
878
|
+
throw new errors_1.AgentVariantsError('INVALID_STAGE_ACTION', 'Source-grounded sessions must run through the source-research flow (propose), not the single-call startUnified path.');
|
|
843
879
|
}
|
|
844
880
|
const proposeResult = this.propose({
|
|
845
881
|
prompt: args.prompt,
|
|
@@ -922,6 +958,15 @@ class AgentVariantsOrchestrator {
|
|
|
922
958
|
};
|
|
923
959
|
}
|
|
924
960
|
async reportComplete(args) {
|
|
961
|
+
// Cooperative abort short-circuit: if the work item was removed/cancelled
|
|
962
|
+
// while in flight (delete-while-loading, cancel_variant, session cancel),
|
|
963
|
+
// skip the schema and QA gates entirely — there is no point validating or
|
|
964
|
+
// QA-ing output we are about to discard — and hand back the store's
|
|
965
|
+
// `aborted` signal so the agent stops this work item now.
|
|
966
|
+
if (this.store.hasSession(args.sessionId) &&
|
|
967
|
+
this.store.isWorkItemAborted(args.sessionId, args.workItemId)) {
|
|
968
|
+
return this.store.reportComplete(args);
|
|
969
|
+
}
|
|
925
970
|
const workItemKind = this.store.hasSession(args.sessionId)
|
|
926
971
|
? this.store.getWorkItemKind(args.sessionId, args.workItemId)
|
|
927
972
|
: undefined;
|
|
@@ -1303,9 +1348,12 @@ class AgentVariantsOrchestrator {
|
|
|
1303
1348
|
catch {
|
|
1304
1349
|
realDestination = undefined;
|
|
1305
1350
|
}
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1351
|
+
// Read the commit policy off the canonical run plan rather than
|
|
1352
|
+
// re-deriving from executionPlan.mode. `materialize_project` is the
|
|
1353
|
+
// vite_app lane (rename/copy the whole worktree); `write_static_site`
|
|
1354
|
+
// is the static_preview lane (inline HTML + sibling assets).
|
|
1355
|
+
const commitPolicy = (0, runPlan_1.resolveRunPlanPolicy)(projectContext).commit;
|
|
1356
|
+
const isViteApp = commitPolicy === 'materialize_project';
|
|
1309
1357
|
// vite_app: strict emptiness check — that lane renames a worktree into
|
|
1310
1358
|
// destinationPath and would clobber user files.
|
|
1311
1359
|
// static_preview: the agent intentionally wrote sibling assets under
|
|
@@ -1313,7 +1361,7 @@ class AgentVariantsOrchestrator {
|
|
|
1313
1361
|
// those per-variant subdirs ARE the deliverable. Allow exactly the
|
|
1314
1362
|
// assetBase entries known to this session; anything else still
|
|
1315
1363
|
// triggers DESTINATION_NOT_EMPTY.
|
|
1316
|
-
if (
|
|
1364
|
+
if (isViteApp) {
|
|
1317
1365
|
this.assertDestinationAvailable(destinationPath);
|
|
1318
1366
|
}
|
|
1319
1367
|
else {
|
|
@@ -1341,7 +1389,7 @@ class AgentVariantsOrchestrator {
|
|
|
1341
1389
|
this.assertDestinationAvailable(destinationPath, allowed);
|
|
1342
1390
|
}
|
|
1343
1391
|
}
|
|
1344
|
-
if (
|
|
1392
|
+
if (isViteApp) {
|
|
1345
1393
|
// Vite_app: the deliverable is the entire variant worktree, not a
|
|
1346
1394
|
// single HTML file. When the worktree lives on the same volume as
|
|
1347
1395
|
// the destination (the common case — provisionFreshWorktrees scaffolds
|
|
@@ -1463,6 +1511,15 @@ class AgentVariantsOrchestrator {
|
|
|
1463
1511
|
// so a process restart between report_variant_complete and commit
|
|
1464
1512
|
// doesn't strand the variant.
|
|
1465
1513
|
const staticPreview = resources.staticPreviews.get(args.variantId);
|
|
1514
|
+
// Resolve the committed index.html before the flatten step below
|
|
1515
|
+
// moves/removes the per-variant dir. `getStaticPreviewHtml` reads the
|
|
1516
|
+
// materialized `<dir>/index.html` and falls back to the work-item's
|
|
1517
|
+
// stored output (the reported HTML) when the on-disk file is missing
|
|
1518
|
+
// or empty — so a failed materialization still commits real content
|
|
1519
|
+
// rather than an empty file.
|
|
1520
|
+
const htmlFromDir = staticPreview
|
|
1521
|
+
? this.getStaticPreviewHtml(args.sessionId, args.variantId)
|
|
1522
|
+
: undefined;
|
|
1466
1523
|
const htmlFromSnapshot = await this.variantHistory.readStaticPreview({
|
|
1467
1524
|
projectPath: projectContext.workspaceRoot,
|
|
1468
1525
|
sessionId: args.sessionId,
|
|
@@ -1545,7 +1602,7 @@ class AgentVariantsOrchestrator {
|
|
|
1545
1602
|
fs_1.default.rmSync(other.assetBase, { recursive: true, force: true });
|
|
1546
1603
|
}
|
|
1547
1604
|
}
|
|
1548
|
-
fs_1.default.writeFileSync(path_1.default.join(destinationPath, 'index.html'),
|
|
1605
|
+
fs_1.default.writeFileSync(path_1.default.join(destinationPath, 'index.html'), htmlFromDir ?? htmlFromSnapshot ?? '', 'utf8');
|
|
1549
1606
|
}
|
|
1550
1607
|
catch (err) {
|
|
1551
1608
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -1731,9 +1788,10 @@ class AgentVariantsOrchestrator {
|
|
|
1731
1788
|
const projectContext = this.store.getProjectContext(sessionId);
|
|
1732
1789
|
const variantCount = approveResult.codeGenWorkItemIds.length +
|
|
1733
1790
|
(approveResult.scaffoldBaseWorkItemId ? 1 : 0);
|
|
1734
|
-
//
|
|
1735
|
-
// skip the timing/telemetry wrapper so the fast path stays untouched.
|
|
1736
|
-
|
|
1791
|
+
// Generated + static_preview provisions nothing (HTML is the deliverable)
|
|
1792
|
+
// — skip the timing/telemetry wrapper so the fast path stays untouched.
|
|
1793
|
+
// `provision: 'none'` is exactly the no-scaffold generated-static lane.
|
|
1794
|
+
if ((0, runPlan_1.resolveRunPlanPolicy)(projectContext).provision === 'none') {
|
|
1737
1795
|
return;
|
|
1738
1796
|
}
|
|
1739
1797
|
const startedAt = Date.now();
|
|
@@ -1795,7 +1853,7 @@ class AgentVariantsOrchestrator {
|
|
|
1795
1853
|
`project is very large or slow to clone (e.g. a big monorepo, or a ` +
|
|
1796
1854
|
`React Native tree with native build dirs that aren't git-ignored). ` +
|
|
1797
1855
|
`Try git-ignoring build artifacts, or generate standalone variants ` +
|
|
1798
|
-
`with
|
|
1856
|
+
`with start_variants(mode='zero_to_one') instead of cloning this project.`);
|
|
1799
1857
|
}
|
|
1800
1858
|
throw err;
|
|
1801
1859
|
}
|
|
@@ -2071,15 +2129,51 @@ class AgentVariantsOrchestrator {
|
|
|
2071
2129
|
if (staticPreview) {
|
|
2072
2130
|
const input = this.store.getWorkItemInput(sessionId, workItemId);
|
|
2073
2131
|
if (input.briefId) {
|
|
2132
|
+
const projectContext = this.store.getProjectContext(sessionId);
|
|
2074
2133
|
const resolvedAssetBase = resolveStaticPreviewAssetBase({
|
|
2075
2134
|
assetBase: staticPreview.assetBase,
|
|
2076
|
-
projectContext
|
|
2135
|
+
projectContext,
|
|
2077
2136
|
});
|
|
2137
|
+
// Materialize the variant into a per-variant directory so every
|
|
2138
|
+
// static variant is directory-backed (the same shape as the
|
|
2139
|
+
// worktree-based variants). Reuse the agent's assetBase dir when it
|
|
2140
|
+
// wrote sibling assets; otherwise create a Rivet-managed per-variant
|
|
2141
|
+
// dir under workspacePath. `staticPreview.html` is the complete built
|
|
2142
|
+
// document (html + css + js), written as index.html.
|
|
2143
|
+
const workspacePath = projectContext.kind === 'fresh'
|
|
2144
|
+
? projectContext.workspacePath
|
|
2145
|
+
: undefined;
|
|
2146
|
+
let dir = resolvedAssetBase ??
|
|
2147
|
+
(workspacePath
|
|
2148
|
+
? path_1.default.join(workspacePath, `.rivet-preview-${workItemId}`)
|
|
2149
|
+
: undefined);
|
|
2150
|
+
if (!dir) {
|
|
2151
|
+
log.warn(`static preview for ${workItemId} has no workspace dir; skipping`);
|
|
2152
|
+
return;
|
|
2153
|
+
}
|
|
2154
|
+
try {
|
|
2155
|
+
fs_1.default.mkdirSync(dir, { recursive: true });
|
|
2156
|
+
// Realpath the dir so it lines up with the realpath'd destination
|
|
2157
|
+
// the commit emptiness-check compares against (macOS symlinks
|
|
2158
|
+
// `/var` → `/private/var`). `resolveStaticPreviewAssetBase`
|
|
2159
|
+
// already realpaths the agent-provided case; do the same for the
|
|
2160
|
+
// Rivet-created dir.
|
|
2161
|
+
try {
|
|
2162
|
+
dir = fs_1.default.realpathSync(dir);
|
|
2163
|
+
}
|
|
2164
|
+
catch {
|
|
2165
|
+
// keep the un-realpath'd path if realpath fails
|
|
2166
|
+
}
|
|
2167
|
+
fs_1.default.writeFileSync(path_1.default.join(dir, 'index.html'), staticPreview.html, 'utf8');
|
|
2168
|
+
}
|
|
2169
|
+
catch (err) {
|
|
2170
|
+
log.warn(`failed to materialize static preview index.html for ${workItemId}`, err);
|
|
2171
|
+
}
|
|
2078
2172
|
const record = {
|
|
2079
2173
|
workItemId,
|
|
2080
2174
|
briefId: input.briefId,
|
|
2081
|
-
|
|
2082
|
-
|
|
2175
|
+
assetBase: dir,
|
|
2176
|
+
hasAssets: Boolean(resolvedAssetBase),
|
|
2083
2177
|
};
|
|
2084
2178
|
resources.staticPreviews.set(workItemId, record);
|
|
2085
2179
|
if (this.store.getProjectContext(sessionId).kind === 'fresh') {
|
|
@@ -2152,6 +2246,24 @@ class AgentVariantsOrchestrator {
|
|
|
2152
2246
|
log.warn(`persistCompletedFreshVariant failed for ${sessionId}/${workItemId}`, err);
|
|
2153
2247
|
});
|
|
2154
2248
|
}
|
|
2249
|
+
// Static existing-mode projects (framework='static', no dev command) have
|
|
2250
|
+
// no per-variant dev server to proxy the iframe at. Serve the variant's
|
|
2251
|
+
// materialized static files directly — the same renderer the chip/iframe
|
|
2252
|
+
// already uses for fresh static_preview variants — so the variant is
|
|
2253
|
+
// viewable/cyclable instead of being silently stuck on "Preview is
|
|
2254
|
+
// unavailable for this variant". When this succeeds there is no dev server
|
|
2255
|
+
// to bring up, so skip the proxy path below.
|
|
2256
|
+
if (!isFresh) {
|
|
2257
|
+
const servedStatic = await this.registerExistingStaticPreview(sessionId, workItemId, record);
|
|
2258
|
+
// Existing projects resolve their preview surface at success time (the
|
|
2259
|
+
// one genuinely-late axis of the run plan): a framework='static' project
|
|
2260
|
+
// materializes static files → static_artifact and needs no dev server;
|
|
2261
|
+
// anything else falls through to the dev-server proxy below.
|
|
2262
|
+
if ((0, runPlan_1.existingPreviewKind)(servedStatic) === 'static_artifact') {
|
|
2263
|
+
this.emitChange();
|
|
2264
|
+
return;
|
|
2265
|
+
}
|
|
2266
|
+
}
|
|
2155
2267
|
// Bring up a dev server in the variant's worktree so the user can cycle
|
|
2156
2268
|
// through live variants in the iframe via the chip. Failures here are
|
|
2157
2269
|
// logged but non-fatal — the user can still pick by reading the diff.
|
|
@@ -2216,23 +2328,37 @@ class AgentVariantsOrchestrator {
|
|
|
2216
2328
|
// the refined markup can still reference the original sibling assets
|
|
2217
2329
|
// (images, GLB, fonts). Fall back to the variant's existing asset base so
|
|
2218
2330
|
// those relative URLs keep resolving instead of 404ing after the swap.
|
|
2219
|
-
const
|
|
2220
|
-
const
|
|
2221
|
-
//
|
|
2222
|
-
//
|
|
2223
|
-
//
|
|
2224
|
-
//
|
|
2225
|
-
// success
|
|
2331
|
+
const previousRecord = resources.staticPreviews.get(input.variantId);
|
|
2332
|
+
const dir = resolvedAssetBase ?? previousRecord?.assetBase;
|
|
2333
|
+
// Re-materialize index.html in the per-variant dir FIRST. The directory is
|
|
2334
|
+
// the source of truth (`getStaticPreviewHtml` reads it disk-first), so the
|
|
2335
|
+
// write must succeed before we swap any in-memory / stored-output state to
|
|
2336
|
+
// the refined HTML. If it fails we throw — converting to a `failed` report
|
|
2337
|
+
// rather than a success that still serves the pre-refine document on disk.
|
|
2338
|
+
if (dir) {
|
|
2339
|
+
try {
|
|
2340
|
+
fs_1.default.mkdirSync(dir, { recursive: true });
|
|
2341
|
+
fs_1.default.writeFileSync(path_1.default.join(dir, 'index.html'), parsed.html, 'utf8');
|
|
2342
|
+
}
|
|
2343
|
+
catch (err) {
|
|
2344
|
+
throw new errors_1.AgentVariantsError('RUNTIME_VALIDATION_FAILED', `Failed to materialize refined static preview for ${input.variantId}: ${err instanceof Error ? err.message : String(err)}`);
|
|
2345
|
+
}
|
|
2346
|
+
}
|
|
2347
|
+
// Swap the served preview to the refined HTML. Runs before
|
|
2348
|
+
// store.reportComplete so any failure above converts to a `failed` report
|
|
2349
|
+
// rather than a success pointing at unrefreshed output.
|
|
2226
2350
|
this.store.replaceWorkItemOutput(args.sessionId, input.variantId, {
|
|
2227
2351
|
...parsed,
|
|
2228
|
-
...(
|
|
2229
|
-
});
|
|
2230
|
-
resources.staticPreviews.set(input.variantId, {
|
|
2231
|
-
workItemId: input.variantId,
|
|
2232
|
-
briefId: targetInput.briefId,
|
|
2233
|
-
html: parsed.html,
|
|
2234
|
-
...(assetBase ? { assetBase } : {}),
|
|
2352
|
+
...(dir ? { assetBase: dir } : {}),
|
|
2235
2353
|
});
|
|
2354
|
+
if (dir) {
|
|
2355
|
+
resources.staticPreviews.set(input.variantId, {
|
|
2356
|
+
workItemId: input.variantId,
|
|
2357
|
+
briefId: targetInput.briefId,
|
|
2358
|
+
assetBase: dir,
|
|
2359
|
+
hasAssets: Boolean(resolvedAssetBase) || Boolean(previousRecord?.hasAssets),
|
|
2360
|
+
});
|
|
2361
|
+
}
|
|
2236
2362
|
// History persistence is best-effort, mirroring handleSucceededReport's
|
|
2237
2363
|
// fire-and-forget persistence for freshly completed variants. A failure
|
|
2238
2364
|
// here (e.g. a transient filesystem race) must not fail the refinement or
|
|
@@ -2300,6 +2426,81 @@ class AgentVariantsOrchestrator {
|
|
|
2300
2426
|
projectKind: 'existing',
|
|
2301
2427
|
});
|
|
2302
2428
|
}
|
|
2429
|
+
/**
|
|
2430
|
+
* Existing-mode `framework: 'static'` projects have no dev command, so there
|
|
2431
|
+
* is no per-variant dev server for the iframe to proxy. Register the variant's
|
|
2432
|
+
* materialized static site as a {@link StaticPreviewRecord} so the existing
|
|
2433
|
+
* static-file plumbing — the `/api/variants/:sid/static/:vid[/asset]` routes,
|
|
2434
|
+
* {@link getStaticPreviewHtml}, and {@link getVariants} (preview.kind=
|
|
2435
|
+
* 'static_artifact', view enabled) — all light up exactly as they do for
|
|
2436
|
+
* fresh `static_preview` variants. The entry HTML and its sibling assets are
|
|
2437
|
+
* served straight from the variant's worktree project root (session-scoped,
|
|
2438
|
+
* pre-commit; the persisted `.rivet/variants/.../files/` copy backs the
|
|
2439
|
+
* post-session history route separately).
|
|
2440
|
+
*
|
|
2441
|
+
* Returns `true` when a static preview was registered (caller skips
|
|
2442
|
+
* dev-server startup); `false` for non-static projects (fall back to the
|
|
2443
|
+
* dev-server proxy) or when no renderable entry file is present.
|
|
2444
|
+
*/
|
|
2445
|
+
async registerExistingStaticPreview(sessionId, workItemId, record) {
|
|
2446
|
+
let env;
|
|
2447
|
+
try {
|
|
2448
|
+
env = await this.resolveEnv(sessionId);
|
|
2449
|
+
}
|
|
2450
|
+
catch (err) {
|
|
2451
|
+
log.warn(`registerExistingStaticPreview: resolveEnv failed for ${sessionId}`, err);
|
|
2452
|
+
return false;
|
|
2453
|
+
}
|
|
2454
|
+
if (env.framework !== 'static')
|
|
2455
|
+
return false;
|
|
2456
|
+
const resources = this.resources.get(sessionId);
|
|
2457
|
+
if (!resources)
|
|
2458
|
+
return false;
|
|
2459
|
+
const input = this.store.getWorkItemInput(sessionId, workItemId);
|
|
2460
|
+
if (!input.briefId)
|
|
2461
|
+
return false;
|
|
2462
|
+
let projectCwd;
|
|
2463
|
+
try {
|
|
2464
|
+
projectCwd = await this.worktrees.getProjectCwdInWorktree(record.worktreePath);
|
|
2465
|
+
}
|
|
2466
|
+
catch (err) {
|
|
2467
|
+
log.warn(`registerExistingStaticPreview: project cwd failed for ${sessionId}/${workItemId}`, err);
|
|
2468
|
+
return false;
|
|
2469
|
+
}
|
|
2470
|
+
// The materialized worktree IS the per-variant directory. Require its
|
|
2471
|
+
// entry index.html to exist; the directory is then served directly (no
|
|
2472
|
+
// in-memory HTML copy — getStaticPreviewHtml reads index.html from here).
|
|
2473
|
+
const entryFile = path_1.default.join(projectCwd, 'index.html');
|
|
2474
|
+
try {
|
|
2475
|
+
await fs_1.default.promises.access(entryFile, fs_1.default.constants.R_OK);
|
|
2476
|
+
}
|
|
2477
|
+
catch (err) {
|
|
2478
|
+
log.warn(`registerExistingStaticPreview: no entry HTML at ${entryFile} for ${sessionId}/${workItemId}; preview unavailable`, err);
|
|
2479
|
+
return false;
|
|
2480
|
+
}
|
|
2481
|
+
// The dir must be realpath'd so resolveStaticPreviewAssetPath's sandbox
|
|
2482
|
+
// comparison lines up; sibling assets (css/js/images) then resolve from the
|
|
2483
|
+
// same static root the entry HTML lives in.
|
|
2484
|
+
let dir;
|
|
2485
|
+
try {
|
|
2486
|
+
dir = fs_1.default.realpathSync(projectCwd);
|
|
2487
|
+
}
|
|
2488
|
+
catch {
|
|
2489
|
+
// Fall back to the un-realpath'd cwd; serving still works, only the
|
|
2490
|
+
// sandbox symlink-normalization is skipped.
|
|
2491
|
+
dir = projectCwd;
|
|
2492
|
+
}
|
|
2493
|
+
resources.staticPreviews.set(workItemId, {
|
|
2494
|
+
workItemId,
|
|
2495
|
+
briefId: input.briefId,
|
|
2496
|
+
assetBase: dir,
|
|
2497
|
+
// Existing-mode static projects are real multi-file sites — assume
|
|
2498
|
+
// sibling assets so the preview route resolves relative URLs.
|
|
2499
|
+
hasAssets: true,
|
|
2500
|
+
});
|
|
2501
|
+
log.info(`Variant ${workItemId} served as a static preview (existing/static, no dev server; root ${projectCwd})`);
|
|
2502
|
+
return true;
|
|
2503
|
+
}
|
|
2303
2504
|
/**
|
|
2304
2505
|
* Persist a completed fresh-project variant into
|
|
2305
2506
|
* `<projectContext.workspacePath>/.rivet/variants/<sessionId>/<variantId>/`.
|
|
@@ -2340,7 +2541,15 @@ class AgentVariantsOrchestrator {
|
|
|
2340
2541
|
}
|
|
2341
2542
|
else if (staticPreview) {
|
|
2342
2543
|
tmpStagingDir = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), `rivet-variant-${args.workItemId}-`));
|
|
2343
|
-
|
|
2544
|
+
// Read the materialized index.html from the variant's per-variant dir.
|
|
2545
|
+
let stagedHtml = '';
|
|
2546
|
+
try {
|
|
2547
|
+
stagedHtml = fs_1.default.readFileSync(path_1.default.join(staticPreview.assetBase, 'index.html'), 'utf8');
|
|
2548
|
+
}
|
|
2549
|
+
catch (err) {
|
|
2550
|
+
log.warn(`persistCompletedFreshVariant: could not read index.html for ${args.workItemId}`, err);
|
|
2551
|
+
}
|
|
2552
|
+
fs_1.default.writeFileSync(path_1.default.join(tmpStagingDir, 'index.html'), stagedHtml, 'utf8');
|
|
2344
2553
|
fs_1.default.writeFileSync(path_1.default.join(tmpStagingDir, 'brief.md'), `# ${input.briefLabel}\n\n${input.briefBody}\n`, 'utf8');
|
|
2345
2554
|
sourceDir = tmpStagingDir;
|
|
2346
2555
|
}
|
|
@@ -2942,6 +3151,31 @@ class AgentVariantsOrchestrator {
|
|
|
2942
3151
|
log.warn(`cleanupSession (worktree removal) failed for ${sessionId}`, err);
|
|
2943
3152
|
}
|
|
2944
3153
|
}
|
|
3154
|
+
// Remove fresh static_preview staging dirs (the per-variant directories
|
|
3155
|
+
// materialized under workspacePath at report time). On 'committed' the
|
|
3156
|
+
// commit flatten already moved/removed them; on cancel/shutdown they'd
|
|
3157
|
+
// otherwise orphan under `.rivet/<slug>/`. Existing-mode static previews
|
|
3158
|
+
// point at worktree dirs (cleaned by cleanupSession above), so scope this
|
|
3159
|
+
// to fresh sessions — where every staticPreviews dir is Rivet staging.
|
|
3160
|
+
if (reason !== 'committed') {
|
|
3161
|
+
let isFresh = false;
|
|
3162
|
+
try {
|
|
3163
|
+
isFresh = this.store.getProjectContext(sessionId).kind === 'fresh';
|
|
3164
|
+
}
|
|
3165
|
+
catch {
|
|
3166
|
+
isFresh = false;
|
|
3167
|
+
}
|
|
3168
|
+
if (isFresh) {
|
|
3169
|
+
for (const sp of resources.staticPreviews.values()) {
|
|
3170
|
+
try {
|
|
3171
|
+
fs_1.default.rmSync(sp.assetBase, { recursive: true, force: true });
|
|
3172
|
+
}
|
|
3173
|
+
catch (err) {
|
|
3174
|
+
log.warn(`failed to remove static preview dir ${sp.assetBase} for ${sessionId}`, err);
|
|
3175
|
+
}
|
|
3176
|
+
}
|
|
3177
|
+
}
|
|
3178
|
+
}
|
|
2945
3179
|
this.resources.delete(sessionId);
|
|
2946
3180
|
}
|
|
2947
3181
|
ensureResources(sessionId) {
|