rivet-design 0.10.6 → 0.10.8
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/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +42 -23
- package/dist/index.js.map +1 -1
- 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 +68 -3
- package/dist/mcp/agent-variants/WorktreeOrchestrator.d.ts.map +1 -1
- package/dist/mcp/agent-variants/WorktreeOrchestrator.js +411 -98
- package/dist/mcp/agent-variants/WorktreeOrchestrator.js.map +1 -1
- package/dist/mcp/agent-variants/WorktreeOrchestrator.testHelpers.d.ts +5 -0
- package/dist/mcp/agent-variants/WorktreeOrchestrator.testHelpers.d.ts.map +1 -1
- package/dist/mcp/agent-variants/WorktreeOrchestrator.testHelpers.js +18 -4
- package/dist/mcp/agent-variants/WorktreeOrchestrator.testHelpers.js.map +1 -1
- package/dist/mcp/agent-variants/contracts.d.ts +40 -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 +1 -2
- 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 +144 -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 +61 -52
- 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 +31 -25
- package/dist/mcp/server.js.map +1 -1
- package/dist/routes/agentVariants.d.ts +2 -1
- package/dist/routes/agentVariants.d.ts.map +1 -1
- package/dist/routes/agentVariants.js +272 -19
- package/dist/routes/agentVariants.js.map +1 -1
- package/dist/routes/design.d.ts.map +1 -1
- package/dist/routes/design.js +0 -122
- package/dist/routes/design.js.map +1 -1
- package/dist/server.d.ts +6 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +11 -6
- package/dist/server.js.map +1 -1
- package/dist/services/AgentSessionService.d.ts +5 -1
- package/dist/services/AgentSessionService.d.ts.map +1 -1
- package/dist/services/AgentSessionService.js +11 -4
- package/dist/services/AgentSessionService.js.map +1 -1
- package/dist/services/InlineVariantGenerationService.d.ts +2 -3
- package/dist/services/InlineVariantGenerationService.d.ts.map +1 -1
- package/dist/services/InlineVariantGenerationService.js +7 -5
- package/dist/services/InlineVariantGenerationService.js.map +1 -1
- package/dist/services/IntegrationsClient.d.ts +78 -0
- package/dist/services/IntegrationsClient.d.ts.map +1 -0
- package/dist/services/IntegrationsClient.js +139 -0
- package/dist/services/IntegrationsClient.js.map +1 -0
- package/dist/services/TelemetryService.d.ts +2 -0
- package/dist/services/TelemetryService.d.ts.map +1 -1
- package/dist/services/TelemetryService.js +2 -0
- package/dist/services/TelemetryService.js.map +1 -1
- package/dist/services/VariantHistoryService.d.ts +8 -0
- package/dist/services/VariantHistoryService.d.ts.map +1 -1
- package/dist/services/VariantHistoryService.js +23 -0
- package/dist/services/VariantHistoryService.js.map +1 -1
- package/dist/services/VariantRunService.d.ts +56 -0
- package/dist/services/VariantRunService.d.ts.map +1 -0
- package/dist/services/VariantRunService.js +56 -0
- package/dist/services/VariantRunService.js.map +1 -0
- package/dist/services/VariantsRuntime.d.ts +22 -0
- package/dist/services/VariantsRuntime.d.ts.map +1 -0
- package/dist/services/VariantsRuntime.js +32 -0
- package/dist/services/VariantsRuntime.js.map +1 -0
- package/dist/services/VisualVariantAgentRunner.d.ts +20 -0
- package/dist/services/VisualVariantAgentRunner.d.ts.map +1 -0
- package/dist/services/VisualVariantAgentRunner.js +66 -0
- package/dist/services/VisualVariantAgentRunner.js.map +1 -0
- package/dist/services/WorktreeManager.d.ts +34 -0
- package/dist/services/WorktreeManager.d.ts.map +1 -1
- package/dist/services/WorktreeManager.js +172 -23
- package/dist/services/WorktreeManager.js.map +1 -1
- package/dist/services/createAgentVariantsOrchestrator.d.ts.map +1 -1
- package/dist/services/createAgentVariantsOrchestrator.js +2 -0
- package/dist/services/createAgentVariantsOrchestrator.js.map +1 -1
- 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/devServerCommand.d.ts +11 -4
- package/dist/utils/devServerCommand.d.ts.map +1 -1
- package/dist/utils/devServerCommand.js +17 -8
- package/dist/utils/devServerCommand.js.map +1 -1
- package/dist/utils/devServerError.d.ts +34 -0
- package/dist/utils/devServerError.d.ts.map +1 -0
- package/dist/utils/devServerError.js +39 -0
- package/dist/utils/devServerError.js.map +1 -0
- package/dist/utils/elementRefToContext.d.ts +4 -0
- package/dist/utils/elementRefToContext.d.ts.map +1 -0
- package/dist/utils/elementRefToContext.js +63 -0
- package/dist/utils/elementRefToContext.js.map +1 -0
- 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 +3 -1
- package/src/ui/dist/assets/main-WqlDU4Ou.js +645 -0
- package/src/ui/dist/assets/main-auZA25j4.css +1 -0
- package/src/ui/dist/index.html +2 -2
- package/dist/services/CommentVariationService.d.ts +0 -34
- package/dist/services/CommentVariationService.d.ts.map +0 -1
- package/dist/services/CommentVariationService.js +0 -136
- package/dist/services/CommentVariationService.js.map +0 -1
- package/dist/services/VariantCodeGeneratorService.d.ts +0 -39
- package/dist/services/VariantCodeGeneratorService.d.ts.map +0 -1
- package/dist/services/VariantCodeGeneratorService.js +0 -109
- package/dist/services/VariantCodeGeneratorService.js.map +0 -1
- package/src/ui/dist/assets/main-B54sNpwl.css +0 -1
- package/src/ui/dist/assets/main-B9BHMA4s.js +0 -646
|
@@ -13,9 +13,11 @@ const path_1 = __importDefault(require("path"));
|
|
|
13
13
|
const child_process_1 = require("child_process");
|
|
14
14
|
const simple_git_1 = require("simple-git");
|
|
15
15
|
const logger_1 = require("../../utils/logger");
|
|
16
|
+
const devServerError_1 = require("../../utils/devServerError");
|
|
16
17
|
const errors_1 = require("./errors");
|
|
17
18
|
const createProjectArtifacts_1 = require("./createProjectArtifacts");
|
|
18
19
|
const contracts_1 = require("./contracts");
|
|
20
|
+
const runPlan_1 = require("./runPlan");
|
|
19
21
|
const viteReactTs_1 = require("../../services/templates/viteReactTs");
|
|
20
22
|
const StaticPreviewServer_1 = require("../../services/StaticPreviewServer");
|
|
21
23
|
const designCatalog_1 = require("../../services/templates/designCatalog");
|
|
@@ -23,6 +25,9 @@ const previewQa_1 = require("./previewQa");
|
|
|
23
25
|
const VariantHistoryService_1 = require("../../services/VariantHistoryService");
|
|
24
26
|
const log = (0, logger_1.createLogger)('AgentVariantsOrchestrator');
|
|
25
27
|
const FRESH_DEV_SERVER_HOST = '127.0.0.1';
|
|
28
|
+
// Fresh worktrees run the Vite React template, whose dev server defaults to
|
|
29
|
+
// 5173. Used as the scan start when allocating a fresh variant's preview port.
|
|
30
|
+
const FRESH_DEV_SERVER_DEFAULT_PORT = 5173;
|
|
26
31
|
const DESIGN_CONTEXT_ROUTE_SEGMENT = 'design-md';
|
|
27
32
|
const DESIGN_CONTEXT_VIEW_SEGMENT = 'view';
|
|
28
33
|
// Hard ceiling on worktree provisioning so a slow/large host project can never
|
|
@@ -323,8 +328,7 @@ class AgentVariantsOrchestrator {
|
|
|
323
328
|
this.variantHistory = deps.variantHistory ?? new VariantHistoryService_1.VariantHistoryService();
|
|
324
329
|
this.startStaticPreviewServerImpl =
|
|
325
330
|
deps.startStaticPreviewServer ?? StaticPreviewServer_1.startStaticPreviewServer;
|
|
326
|
-
this.provisionTimeoutMs =
|
|
327
|
-
deps.provisionTimeoutMs ?? PROVISION_TIMEOUT_MS;
|
|
331
|
+
this.provisionTimeoutMs = deps.provisionTimeoutMs ?? PROVISION_TIMEOUT_MS;
|
|
328
332
|
}
|
|
329
333
|
// --- Pure delegations (no side effects) ---------------------------------
|
|
330
334
|
propose(args) {
|
|
@@ -359,6 +363,47 @@ class AgentVariantsOrchestrator {
|
|
|
359
363
|
this.events.off('change', listener);
|
|
360
364
|
};
|
|
361
365
|
}
|
|
366
|
+
/** Number of live Rivet UI clients subscribed to the variants SSE stream.
|
|
367
|
+
* A subscribed client means a Rivet page is open in the browser. */
|
|
368
|
+
uiClientCount = 0;
|
|
369
|
+
/** Wall-clock of the last UI-client connect/disconnect. `hasUiClients`
|
|
370
|
+
* treats a very recent disconnect as still-connected to ride out the brief
|
|
371
|
+
* gap when an EventSource drops and auto-reconnects (default retry ~3s) —
|
|
372
|
+
* otherwise a `propose`/`start` landing in that window reads zero clients
|
|
373
|
+
* and spawns a duplicate browser tab. */
|
|
374
|
+
lastUiClientActivityAt = 0;
|
|
375
|
+
/** Grace window covering an EventSource reconnect (its default retry is ~3s;
|
|
376
|
+
* the SSE heartbeat is 30s). Long enough to span a reconnect, short enough
|
|
377
|
+
* that a genuine page close frees the "reopen on next propose" path quickly. */
|
|
378
|
+
static UI_CLIENT_GRACE_MS = 6000;
|
|
379
|
+
/**
|
|
380
|
+
* Register a live Rivet UI client (called when the variants SSE stream opens).
|
|
381
|
+
* Returns a release fn to call on disconnect. Used as the "a Rivet page is
|
|
382
|
+
* already open" signal so the MCP server can avoid spawning a duplicate tab
|
|
383
|
+
* every time the variants flow re-enters an already-active editor session.
|
|
384
|
+
*/
|
|
385
|
+
registerUiClient() {
|
|
386
|
+
this.uiClientCount += 1;
|
|
387
|
+
this.lastUiClientActivityAt = Date.now();
|
|
388
|
+
let released = false;
|
|
389
|
+
return () => {
|
|
390
|
+
if (released)
|
|
391
|
+
return;
|
|
392
|
+
released = true;
|
|
393
|
+
this.uiClientCount = Math.max(0, this.uiClientCount - 1);
|
|
394
|
+
this.lastUiClientActivityAt = Date.now();
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
/** True when at least one Rivet UI page is currently connected — or one
|
|
398
|
+
* disconnected within the grace window (an EventSource mid-reconnect). */
|
|
399
|
+
hasUiClients() {
|
|
400
|
+
if (this.uiClientCount > 0)
|
|
401
|
+
return true;
|
|
402
|
+
if (this.lastUiClientActivityAt === 0)
|
|
403
|
+
return false;
|
|
404
|
+
return (Date.now() - this.lastUiClientActivityAt <
|
|
405
|
+
AgentVariantsOrchestrator.UI_CLIENT_GRACE_MS);
|
|
406
|
+
}
|
|
362
407
|
/** Build the snapshot the chip cares about. Reads from SessionStore +
|
|
363
408
|
* per-resource state; safe to call at any time. */
|
|
364
409
|
buildActiveSnapshot() {
|
|
@@ -466,18 +511,7 @@ class AgentVariantsOrchestrator {
|
|
|
466
511
|
if (seenVariantIds.has(target.variantId))
|
|
467
512
|
continue;
|
|
468
513
|
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
|
-
})();
|
|
514
|
+
const currentHtml = this.getStaticPreviewHtml(args.sessionId, target.variantId);
|
|
481
515
|
try {
|
|
482
516
|
this.store.createRefineVariantWorkItem({
|
|
483
517
|
sessionId: args.sessionId,
|
|
@@ -532,18 +566,7 @@ class AgentVariantsOrchestrator {
|
|
|
532
566
|
throw error;
|
|
533
567
|
}
|
|
534
568
|
}
|
|
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
|
-
})();
|
|
569
|
+
const currentHtml = this.getStaticPreviewHtml(args.sessionId, args.variantId);
|
|
547
570
|
const refineItem = this.store.createRefineVariantWorkItem({
|
|
548
571
|
sessionId: args.sessionId,
|
|
549
572
|
variantId: args.variantId,
|
|
@@ -614,6 +637,16 @@ class AgentVariantsOrchestrator {
|
|
|
614
637
|
const isSucceeded = variant.status === 'succeeded';
|
|
615
638
|
const qaFailed = qa?.status === 'failed';
|
|
616
639
|
const canView = Boolean(preview) || (isSucceeded && Boolean(port));
|
|
640
|
+
// A succeeded variant with no live preview whose dev server we tried and
|
|
641
|
+
// failed to start: surface the cause so the UI can show an accurate
|
|
642
|
+
// per-direction message instead of the generic disconnected overlay.
|
|
643
|
+
const previewFailure = resources?.previewFailures.get(variant.workItemId);
|
|
644
|
+
const previewUnavailable = !canView && isSucceeded && previewFailure
|
|
645
|
+
? {
|
|
646
|
+
reason: previewFailure.reason,
|
|
647
|
+
...(previewFailure.portInUse ? { portInUse: true } : {}),
|
|
648
|
+
}
|
|
649
|
+
: undefined;
|
|
617
650
|
const canCommit = isSucceeded && !qaFailed;
|
|
618
651
|
const commitDisabledReason = qaFailed
|
|
619
652
|
? (qa?.summary ?? 'Variant failed QA')
|
|
@@ -624,6 +657,7 @@ class AgentVariantsOrchestrator {
|
|
|
624
657
|
...(preview ? { preview } : {}),
|
|
625
658
|
port,
|
|
626
659
|
...(qa ? { qa } : {}),
|
|
660
|
+
...(previewUnavailable ? { previewUnavailable } : {}),
|
|
627
661
|
actions: {
|
|
628
662
|
view: canView
|
|
629
663
|
? { enabled: true }
|
|
@@ -668,14 +702,25 @@ class AgentVariantsOrchestrator {
|
|
|
668
702
|
return this.resources.get(sessionId)?.worktrees.get(workItemId)?.port;
|
|
669
703
|
}
|
|
670
704
|
getStaticPreviewHtml(sessionId, workItemId) {
|
|
671
|
-
// Primary:
|
|
672
|
-
|
|
705
|
+
// Primary: read the materialized index.html from the variant's per-variant
|
|
706
|
+
// directory (written by handleSucceededReport). The directory is the source
|
|
707
|
+
// of truth for the static deliverable.
|
|
708
|
+
const dir = this.resources
|
|
673
709
|
.get(sessionId)
|
|
674
|
-
?.staticPreviews.get(workItemId)?.
|
|
675
|
-
if (
|
|
676
|
-
|
|
710
|
+
?.staticPreviews.get(workItemId)?.assetBase;
|
|
711
|
+
if (dir) {
|
|
712
|
+
try {
|
|
713
|
+
const fromDisk = fs_1.default.readFileSync(path_1.default.join(dir, 'index.html'), 'utf8');
|
|
714
|
+
if (fromDisk.length > 0)
|
|
715
|
+
return fromDisk;
|
|
716
|
+
}
|
|
717
|
+
catch {
|
|
718
|
+
// fall through to the work-item output fallback below
|
|
719
|
+
}
|
|
720
|
+
}
|
|
677
721
|
// Fallback: read directly from the work item's stored output — available
|
|
678
|
-
// as soon as reportComplete runs, before handleSucceededReport fires
|
|
722
|
+
// as soon as reportComplete runs, before handleSucceededReport fires (so
|
|
723
|
+
// the per-variant directory may not exist yet).
|
|
679
724
|
try {
|
|
680
725
|
const output = this.store.getWorkItemOutput(sessionId, workItemId);
|
|
681
726
|
return typeof output?.html === 'string' && output.html.length > 0
|
|
@@ -707,7 +752,7 @@ class AgentVariantsOrchestrator {
|
|
|
707
752
|
const record = this.resources
|
|
708
753
|
.get(sessionId)
|
|
709
754
|
?.staticPreviews.get(workItemId);
|
|
710
|
-
return Boolean(record?.
|
|
755
|
+
return Boolean(record?.hasAssets);
|
|
711
756
|
}
|
|
712
757
|
/**
|
|
713
758
|
* Resolve an absolute, sandboxed path for a sibling asset that the
|
|
@@ -818,9 +863,10 @@ class AgentVariantsOrchestrator {
|
|
|
818
863
|
* Supports both existing-project sessions (spawns code_gen work items
|
|
819
864
|
* for the agent to lease) and zero-to-one sessions (spawns
|
|
820
865
|
* static_preview work items; the server runs scaffold_base in the
|
|
821
|
-
* background).
|
|
822
|
-
*
|
|
823
|
-
*
|
|
866
|
+
* background). This is the single-call path; source-grounded zero-to-one
|
|
867
|
+
* sessions (source URLs / inspiration extraction) run through `propose`
|
|
868
|
+
* instead so the source-research flow can gate generation — start_variants
|
|
869
|
+
* routes them there. This method does not run source research.
|
|
824
870
|
*
|
|
825
871
|
* Host-agnostic: no LLM calls happen here. The agent (Claude Code,
|
|
826
872
|
* Cursor, Codex) generates the label and code when it leases the
|
|
@@ -839,7 +885,11 @@ class AgentVariantsOrchestrator {
|
|
|
839
885
|
Boolean(sourceContext?.sourceIntent) ||
|
|
840
886
|
Boolean(sourceContext?.artifact);
|
|
841
887
|
if (isSourceGrounded) {
|
|
842
|
-
|
|
888
|
+
// startUnified is the single-call path; source-grounded sessions must
|
|
889
|
+
// run through `propose` so the source-research flow gates generation.
|
|
890
|
+
// start_variants routes them there before reaching here — this guard is
|
|
891
|
+
// an internal invariant, not a user-facing dead end.
|
|
892
|
+
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
893
|
}
|
|
844
894
|
const proposeResult = this.propose({
|
|
845
895
|
prompt: args.prompt,
|
|
@@ -922,6 +972,15 @@ class AgentVariantsOrchestrator {
|
|
|
922
972
|
};
|
|
923
973
|
}
|
|
924
974
|
async reportComplete(args) {
|
|
975
|
+
// Cooperative abort short-circuit: if the work item was removed/cancelled
|
|
976
|
+
// while in flight (delete-while-loading, cancel_variant, session cancel),
|
|
977
|
+
// skip the schema and QA gates entirely — there is no point validating or
|
|
978
|
+
// QA-ing output we are about to discard — and hand back the store's
|
|
979
|
+
// `aborted` signal so the agent stops this work item now.
|
|
980
|
+
if (this.store.hasSession(args.sessionId) &&
|
|
981
|
+
this.store.isWorkItemAborted(args.sessionId, args.workItemId)) {
|
|
982
|
+
return this.store.reportComplete(args);
|
|
983
|
+
}
|
|
925
984
|
const workItemKind = this.store.hasSession(args.sessionId)
|
|
926
985
|
? this.store.getWorkItemKind(args.sessionId, args.workItemId)
|
|
927
986
|
: undefined;
|
|
@@ -1303,9 +1362,12 @@ class AgentVariantsOrchestrator {
|
|
|
1303
1362
|
catch {
|
|
1304
1363
|
realDestination = undefined;
|
|
1305
1364
|
}
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1365
|
+
// Read the commit policy off the canonical run plan rather than
|
|
1366
|
+
// re-deriving from executionPlan.mode. `materialize_project` is the
|
|
1367
|
+
// vite_app lane (rename/copy the whole worktree); `write_static_site`
|
|
1368
|
+
// is the static_preview lane (inline HTML + sibling assets).
|
|
1369
|
+
const commitPolicy = (0, runPlan_1.resolveRunPlanPolicy)(projectContext).commit;
|
|
1370
|
+
const isViteApp = commitPolicy === 'materialize_project';
|
|
1309
1371
|
// vite_app: strict emptiness check — that lane renames a worktree into
|
|
1310
1372
|
// destinationPath and would clobber user files.
|
|
1311
1373
|
// static_preview: the agent intentionally wrote sibling assets under
|
|
@@ -1313,7 +1375,7 @@ class AgentVariantsOrchestrator {
|
|
|
1313
1375
|
// those per-variant subdirs ARE the deliverable. Allow exactly the
|
|
1314
1376
|
// assetBase entries known to this session; anything else still
|
|
1315
1377
|
// triggers DESTINATION_NOT_EMPTY.
|
|
1316
|
-
if (
|
|
1378
|
+
if (isViteApp) {
|
|
1317
1379
|
this.assertDestinationAvailable(destinationPath);
|
|
1318
1380
|
}
|
|
1319
1381
|
else {
|
|
@@ -1341,7 +1403,7 @@ class AgentVariantsOrchestrator {
|
|
|
1341
1403
|
this.assertDestinationAvailable(destinationPath, allowed);
|
|
1342
1404
|
}
|
|
1343
1405
|
}
|
|
1344
|
-
if (
|
|
1406
|
+
if (isViteApp) {
|
|
1345
1407
|
// Vite_app: the deliverable is the entire variant worktree, not a
|
|
1346
1408
|
// single HTML file. When the worktree lives on the same volume as
|
|
1347
1409
|
// the destination (the common case — provisionFreshWorktrees scaffolds
|
|
@@ -1463,6 +1525,15 @@ class AgentVariantsOrchestrator {
|
|
|
1463
1525
|
// so a process restart between report_variant_complete and commit
|
|
1464
1526
|
// doesn't strand the variant.
|
|
1465
1527
|
const staticPreview = resources.staticPreviews.get(args.variantId);
|
|
1528
|
+
// Resolve the committed index.html before the flatten step below
|
|
1529
|
+
// moves/removes the per-variant dir. `getStaticPreviewHtml` reads the
|
|
1530
|
+
// materialized `<dir>/index.html` and falls back to the work-item's
|
|
1531
|
+
// stored output (the reported HTML) when the on-disk file is missing
|
|
1532
|
+
// or empty — so a failed materialization still commits real content
|
|
1533
|
+
// rather than an empty file.
|
|
1534
|
+
const htmlFromDir = staticPreview
|
|
1535
|
+
? this.getStaticPreviewHtml(args.sessionId, args.variantId)
|
|
1536
|
+
: undefined;
|
|
1466
1537
|
const htmlFromSnapshot = await this.variantHistory.readStaticPreview({
|
|
1467
1538
|
projectPath: projectContext.workspaceRoot,
|
|
1468
1539
|
sessionId: args.sessionId,
|
|
@@ -1545,7 +1616,7 @@ class AgentVariantsOrchestrator {
|
|
|
1545
1616
|
fs_1.default.rmSync(other.assetBase, { recursive: true, force: true });
|
|
1546
1617
|
}
|
|
1547
1618
|
}
|
|
1548
|
-
fs_1.default.writeFileSync(path_1.default.join(destinationPath, 'index.html'),
|
|
1619
|
+
fs_1.default.writeFileSync(path_1.default.join(destinationPath, 'index.html'), htmlFromDir ?? htmlFromSnapshot ?? '', 'utf8');
|
|
1549
1620
|
}
|
|
1550
1621
|
catch (err) {
|
|
1551
1622
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -1731,9 +1802,10 @@ class AgentVariantsOrchestrator {
|
|
|
1731
1802
|
const projectContext = this.store.getProjectContext(sessionId);
|
|
1732
1803
|
const variantCount = approveResult.codeGenWorkItemIds.length +
|
|
1733
1804
|
(approveResult.scaffoldBaseWorkItemId ? 1 : 0);
|
|
1734
|
-
//
|
|
1735
|
-
// skip the timing/telemetry wrapper so the fast path stays untouched.
|
|
1736
|
-
|
|
1805
|
+
// Generated + static_preview provisions nothing (HTML is the deliverable)
|
|
1806
|
+
// — skip the timing/telemetry wrapper so the fast path stays untouched.
|
|
1807
|
+
// `provision: 'none'` is exactly the no-scaffold generated-static lane.
|
|
1808
|
+
if ((0, runPlan_1.resolveRunPlanPolicy)(projectContext).provision === 'none') {
|
|
1737
1809
|
return;
|
|
1738
1810
|
}
|
|
1739
1811
|
const startedAt = Date.now();
|
|
@@ -1795,7 +1867,7 @@ class AgentVariantsOrchestrator {
|
|
|
1795
1867
|
`project is very large or slow to clone (e.g. a big monorepo, or a ` +
|
|
1796
1868
|
`React Native tree with native build dirs that aren't git-ignored). ` +
|
|
1797
1869
|
`Try git-ignoring build artifacts, or generate standalone variants ` +
|
|
1798
|
-
`with
|
|
1870
|
+
`with start_variants(mode='zero_to_one') instead of cloning this project.`);
|
|
1799
1871
|
}
|
|
1800
1872
|
throw err;
|
|
1801
1873
|
}
|
|
@@ -2071,15 +2143,51 @@ class AgentVariantsOrchestrator {
|
|
|
2071
2143
|
if (staticPreview) {
|
|
2072
2144
|
const input = this.store.getWorkItemInput(sessionId, workItemId);
|
|
2073
2145
|
if (input.briefId) {
|
|
2146
|
+
const projectContext = this.store.getProjectContext(sessionId);
|
|
2074
2147
|
const resolvedAssetBase = resolveStaticPreviewAssetBase({
|
|
2075
2148
|
assetBase: staticPreview.assetBase,
|
|
2076
|
-
projectContext
|
|
2149
|
+
projectContext,
|
|
2077
2150
|
});
|
|
2151
|
+
// Materialize the variant into a per-variant directory so every
|
|
2152
|
+
// static variant is directory-backed (the same shape as the
|
|
2153
|
+
// worktree-based variants). Reuse the agent's assetBase dir when it
|
|
2154
|
+
// wrote sibling assets; otherwise create a Rivet-managed per-variant
|
|
2155
|
+
// dir under workspacePath. `staticPreview.html` is the complete built
|
|
2156
|
+
// document (html + css + js), written as index.html.
|
|
2157
|
+
const workspacePath = projectContext.kind === 'fresh'
|
|
2158
|
+
? projectContext.workspacePath
|
|
2159
|
+
: undefined;
|
|
2160
|
+
let dir = resolvedAssetBase ??
|
|
2161
|
+
(workspacePath
|
|
2162
|
+
? path_1.default.join(workspacePath, `.rivet-preview-${workItemId}`)
|
|
2163
|
+
: undefined);
|
|
2164
|
+
if (!dir) {
|
|
2165
|
+
log.warn(`static preview for ${workItemId} has no workspace dir; skipping`);
|
|
2166
|
+
return;
|
|
2167
|
+
}
|
|
2168
|
+
try {
|
|
2169
|
+
fs_1.default.mkdirSync(dir, { recursive: true });
|
|
2170
|
+
// Realpath the dir so it lines up with the realpath'd destination
|
|
2171
|
+
// the commit emptiness-check compares against (macOS symlinks
|
|
2172
|
+
// `/var` → `/private/var`). `resolveStaticPreviewAssetBase`
|
|
2173
|
+
// already realpaths the agent-provided case; do the same for the
|
|
2174
|
+
// Rivet-created dir.
|
|
2175
|
+
try {
|
|
2176
|
+
dir = fs_1.default.realpathSync(dir);
|
|
2177
|
+
}
|
|
2178
|
+
catch {
|
|
2179
|
+
// keep the un-realpath'd path if realpath fails
|
|
2180
|
+
}
|
|
2181
|
+
fs_1.default.writeFileSync(path_1.default.join(dir, 'index.html'), staticPreview.html, 'utf8');
|
|
2182
|
+
}
|
|
2183
|
+
catch (err) {
|
|
2184
|
+
log.warn(`failed to materialize static preview index.html for ${workItemId}`, err);
|
|
2185
|
+
}
|
|
2078
2186
|
const record = {
|
|
2079
2187
|
workItemId,
|
|
2080
2188
|
briefId: input.briefId,
|
|
2081
|
-
|
|
2082
|
-
|
|
2189
|
+
assetBase: dir,
|
|
2190
|
+
hasAssets: Boolean(resolvedAssetBase),
|
|
2083
2191
|
};
|
|
2084
2192
|
resources.staticPreviews.set(workItemId, record);
|
|
2085
2193
|
if (this.store.getProjectContext(sessionId).kind === 'fresh') {
|
|
@@ -2152,43 +2260,105 @@ class AgentVariantsOrchestrator {
|
|
|
2152
2260
|
log.warn(`persistCompletedFreshVariant failed for ${sessionId}/${workItemId}`, err);
|
|
2153
2261
|
});
|
|
2154
2262
|
}
|
|
2263
|
+
// Static existing-mode projects (framework='static', no dev command) have
|
|
2264
|
+
// no per-variant dev server to proxy the iframe at. Serve the variant's
|
|
2265
|
+
// materialized static files directly — the same renderer the chip/iframe
|
|
2266
|
+
// already uses for fresh static_preview variants — so the variant is
|
|
2267
|
+
// viewable/cyclable instead of being silently stuck on "Preview is
|
|
2268
|
+
// unavailable for this variant". When this succeeds there is no dev server
|
|
2269
|
+
// to bring up, so skip the proxy path below.
|
|
2270
|
+
if (!isFresh) {
|
|
2271
|
+
const servedStatic = await this.registerExistingStaticPreview(sessionId, workItemId, record);
|
|
2272
|
+
// Existing projects resolve their preview surface at success time (the
|
|
2273
|
+
// one genuinely-late axis of the run plan): a framework='static' project
|
|
2274
|
+
// materializes static files → static_artifact and needs no dev server;
|
|
2275
|
+
// anything else falls through to the dev-server proxy below.
|
|
2276
|
+
if ((0, runPlan_1.existingPreviewKind)(servedStatic) === 'static_artifact') {
|
|
2277
|
+
this.emitChange();
|
|
2278
|
+
return;
|
|
2279
|
+
}
|
|
2280
|
+
}
|
|
2155
2281
|
// Bring up a dev server in the variant's worktree so the user can cycle
|
|
2156
2282
|
// through live variants in the iframe via the chip. Failures here are
|
|
2157
2283
|
// logged but non-fatal — the user can still pick by reading the diff.
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
record.port
|
|
2173
|
-
|
|
2174
|
-
|
|
2284
|
+
//
|
|
2285
|
+
// Pick a sensible port near the framework's default (3000 for Next, 5173
|
|
2286
|
+
// for Vite) and inject it via PORT + the CLI flag (see buildDevServerCommand)
|
|
2287
|
+
// so the server actually binds it — that's what moves a preview off a port
|
|
2288
|
+
// the user's own dev server already holds instead of colliding on it. On a
|
|
2289
|
+
// retry we scan past the port we just tried, so a transient race or a
|
|
2290
|
+
// briefly-held port resolves onto the next free one.
|
|
2291
|
+
const startPort = await this.resolveDevServerStartPort(sessionId, isFresh);
|
|
2292
|
+
let scanFrom = startPort;
|
|
2293
|
+
const maxAttempts = 2;
|
|
2294
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
2295
|
+
let port;
|
|
2296
|
+
try {
|
|
2297
|
+
port = await this.worktrees.findFreePortFrom(scanFrom);
|
|
2298
|
+
const dev = await this.resolveDevServer(sessionId, record.worktreePath, port, isFresh);
|
|
2299
|
+
const proc = await this.worktrees.startDevServer(dev.cwd, port, dev.cmd, dev.args, dev.env);
|
|
2300
|
+
record.port = port;
|
|
2301
|
+
record.devServerProcess = proc;
|
|
2302
|
+
// If the dev server dies on its own (crash, OOM, Vite hard-fail),
|
|
2303
|
+
// clear the port so the iframe stops routing the chip onto a dead
|
|
2304
|
+
// socket. Without this the proxy retargets onto an unreachable port
|
|
2305
|
+
// and the chip shows "upstream_unreachable" instead of the accurate
|
|
2306
|
+
// "Preview is unavailable for this variant".
|
|
2307
|
+
proc.once('exit', (code, signal) => {
|
|
2308
|
+
if (record.devServerProcess !== proc)
|
|
2309
|
+
return;
|
|
2310
|
+
record.port = undefined;
|
|
2311
|
+
record.devServerProcess = undefined;
|
|
2312
|
+
log.warn(`Variant ${workItemId} dev server exited unexpectedly (code=${code ?? 'null'}, signal=${signal ?? 'null'}); preview disabled`);
|
|
2313
|
+
this.emitChange();
|
|
2314
|
+
});
|
|
2175
2315
|
this.emitChange();
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
port
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2316
|
+
trackFreshDevServerStarted(this.telemetry, {
|
|
2317
|
+
sessionId,
|
|
2318
|
+
variantId: workItemId,
|
|
2319
|
+
port,
|
|
2320
|
+
});
|
|
2321
|
+
log.info(`Variant ${workItemId} dev server up on port ${port} (worktree ${record.worktreePath}; cmd: ${dev.cmd} ${dev.args.join(' ')})`);
|
|
2322
|
+
return;
|
|
2323
|
+
}
|
|
2324
|
+
catch (err) {
|
|
2325
|
+
const portInUse = (0, devServerError_1.isDevServerStartError)(err) && err.portInUse;
|
|
2326
|
+
const reason = (0, devServerError_1.isDevServerStartError)(err) ? err.reason : 'unknown';
|
|
2327
|
+
const willRetry = portInUse && attempt < maxAttempts;
|
|
2328
|
+
// Next attempt scans past the port we just tried so we don't re-pick a
|
|
2329
|
+
// port that's persistently held (vs. a transient race that freed it).
|
|
2330
|
+
scanFrom = (port ?? scanFrom) + 1;
|
|
2331
|
+
log.warn(`Failed to start dev server for variant ${workItemId} on port ${port ?? 'unallocated'} ` +
|
|
2332
|
+
`(attempt ${attempt}/${maxAttempts}, reason=${reason}, portInUse=${portInUse})` +
|
|
2333
|
+
(willRetry
|
|
2334
|
+
? '; retrying on a fresh port'
|
|
2335
|
+
: '; live preview disabled for this variant'), err);
|
|
2336
|
+
if (willRetry)
|
|
2337
|
+
continue;
|
|
2338
|
+
// Record the failure so getVariants can tell the UI this direction's
|
|
2339
|
+
// preview couldn't start (and why), then push a snapshot so the chip
|
|
2340
|
+
// updates from "loading" to the accurate per-direction message.
|
|
2341
|
+
this.resources.get(sessionId)?.previewFailures.set(workItemId, {
|
|
2342
|
+
reason,
|
|
2343
|
+
portInUse,
|
|
2344
|
+
});
|
|
2345
|
+
trackFreshDevServerFailed(this.telemetry, {
|
|
2346
|
+
sessionId,
|
|
2347
|
+
variantId: workItemId,
|
|
2348
|
+
errorCode: 'DEV_SERVER_START_FAILED',
|
|
2349
|
+
reason,
|
|
2350
|
+
portInUse,
|
|
2351
|
+
});
|
|
2352
|
+
this.emitChange();
|
|
2353
|
+
return;
|
|
2354
|
+
}
|
|
2355
|
+
finally {
|
|
2356
|
+
// Hand the reservation back whether the dev server bound the port
|
|
2357
|
+
// (now visible to the listener check, so the reservation is redundant),
|
|
2358
|
+
// it failed (port free again), or we're about to retry on the next port.
|
|
2359
|
+
if (port !== undefined)
|
|
2360
|
+
this.worktrees.releasePort?.(port);
|
|
2361
|
+
}
|
|
2192
2362
|
}
|
|
2193
2363
|
}
|
|
2194
2364
|
async handleStaticPreviewRefinement(args) {
|
|
@@ -2216,23 +2386,37 @@ class AgentVariantsOrchestrator {
|
|
|
2216
2386
|
// the refined markup can still reference the original sibling assets
|
|
2217
2387
|
// (images, GLB, fonts). Fall back to the variant's existing asset base so
|
|
2218
2388
|
// those relative URLs keep resolving instead of 404ing after the swap.
|
|
2219
|
-
const
|
|
2220
|
-
const
|
|
2221
|
-
//
|
|
2222
|
-
//
|
|
2223
|
-
//
|
|
2224
|
-
//
|
|
2225
|
-
// success
|
|
2389
|
+
const previousRecord = resources.staticPreviews.get(input.variantId);
|
|
2390
|
+
const dir = resolvedAssetBase ?? previousRecord?.assetBase;
|
|
2391
|
+
// Re-materialize index.html in the per-variant dir FIRST. The directory is
|
|
2392
|
+
// the source of truth (`getStaticPreviewHtml` reads it disk-first), so the
|
|
2393
|
+
// write must succeed before we swap any in-memory / stored-output state to
|
|
2394
|
+
// the refined HTML. If it fails we throw — converting to a `failed` report
|
|
2395
|
+
// rather than a success that still serves the pre-refine document on disk.
|
|
2396
|
+
if (dir) {
|
|
2397
|
+
try {
|
|
2398
|
+
fs_1.default.mkdirSync(dir, { recursive: true });
|
|
2399
|
+
fs_1.default.writeFileSync(path_1.default.join(dir, 'index.html'), parsed.html, 'utf8');
|
|
2400
|
+
}
|
|
2401
|
+
catch (err) {
|
|
2402
|
+
throw new errors_1.AgentVariantsError('RUNTIME_VALIDATION_FAILED', `Failed to materialize refined static preview for ${input.variantId}: ${err instanceof Error ? err.message : String(err)}`);
|
|
2403
|
+
}
|
|
2404
|
+
}
|
|
2405
|
+
// Swap the served preview to the refined HTML. Runs before
|
|
2406
|
+
// store.reportComplete so any failure above converts to a `failed` report
|
|
2407
|
+
// rather than a success pointing at unrefreshed output.
|
|
2226
2408
|
this.store.replaceWorkItemOutput(args.sessionId, input.variantId, {
|
|
2227
2409
|
...parsed,
|
|
2228
|
-
...(
|
|
2229
|
-
});
|
|
2230
|
-
resources.staticPreviews.set(input.variantId, {
|
|
2231
|
-
workItemId: input.variantId,
|
|
2232
|
-
briefId: targetInput.briefId,
|
|
2233
|
-
html: parsed.html,
|
|
2234
|
-
...(assetBase ? { assetBase } : {}),
|
|
2410
|
+
...(dir ? { assetBase: dir } : {}),
|
|
2235
2411
|
});
|
|
2412
|
+
if (dir) {
|
|
2413
|
+
resources.staticPreviews.set(input.variantId, {
|
|
2414
|
+
workItemId: input.variantId,
|
|
2415
|
+
briefId: targetInput.briefId,
|
|
2416
|
+
assetBase: dir,
|
|
2417
|
+
hasAssets: Boolean(resolvedAssetBase) || Boolean(previousRecord?.hasAssets),
|
|
2418
|
+
});
|
|
2419
|
+
}
|
|
2236
2420
|
// History persistence is best-effort, mirroring handleSucceededReport's
|
|
2237
2421
|
// fire-and-forget persistence for freshly completed variants. A failure
|
|
2238
2422
|
// here (e.g. a transient filesystem race) must not fail the refinement or
|
|
@@ -2300,6 +2484,81 @@ class AgentVariantsOrchestrator {
|
|
|
2300
2484
|
projectKind: 'existing',
|
|
2301
2485
|
});
|
|
2302
2486
|
}
|
|
2487
|
+
/**
|
|
2488
|
+
* Existing-mode `framework: 'static'` projects have no dev command, so there
|
|
2489
|
+
* is no per-variant dev server for the iframe to proxy. Register the variant's
|
|
2490
|
+
* materialized static site as a {@link StaticPreviewRecord} so the existing
|
|
2491
|
+
* static-file plumbing — the `/api/variants/:sid/static/:vid[/asset]` routes,
|
|
2492
|
+
* {@link getStaticPreviewHtml}, and {@link getVariants} (preview.kind=
|
|
2493
|
+
* 'static_artifact', view enabled) — all light up exactly as they do for
|
|
2494
|
+
* fresh `static_preview` variants. The entry HTML and its sibling assets are
|
|
2495
|
+
* served straight from the variant's worktree project root (session-scoped,
|
|
2496
|
+
* pre-commit; the persisted `.rivet/variants/.../files/` copy backs the
|
|
2497
|
+
* post-session history route separately).
|
|
2498
|
+
*
|
|
2499
|
+
* Returns `true` when a static preview was registered (caller skips
|
|
2500
|
+
* dev-server startup); `false` for non-static projects (fall back to the
|
|
2501
|
+
* dev-server proxy) or when no renderable entry file is present.
|
|
2502
|
+
*/
|
|
2503
|
+
async registerExistingStaticPreview(sessionId, workItemId, record) {
|
|
2504
|
+
let env;
|
|
2505
|
+
try {
|
|
2506
|
+
env = await this.resolveEnv(sessionId);
|
|
2507
|
+
}
|
|
2508
|
+
catch (err) {
|
|
2509
|
+
log.warn(`registerExistingStaticPreview: resolveEnv failed for ${sessionId}`, err);
|
|
2510
|
+
return false;
|
|
2511
|
+
}
|
|
2512
|
+
if (env.framework !== 'static')
|
|
2513
|
+
return false;
|
|
2514
|
+
const resources = this.resources.get(sessionId);
|
|
2515
|
+
if (!resources)
|
|
2516
|
+
return false;
|
|
2517
|
+
const input = this.store.getWorkItemInput(sessionId, workItemId);
|
|
2518
|
+
if (!input.briefId)
|
|
2519
|
+
return false;
|
|
2520
|
+
let projectCwd;
|
|
2521
|
+
try {
|
|
2522
|
+
projectCwd = await this.worktrees.getProjectCwdInWorktree(record.worktreePath);
|
|
2523
|
+
}
|
|
2524
|
+
catch (err) {
|
|
2525
|
+
log.warn(`registerExistingStaticPreview: project cwd failed for ${sessionId}/${workItemId}`, err);
|
|
2526
|
+
return false;
|
|
2527
|
+
}
|
|
2528
|
+
// The materialized worktree IS the per-variant directory. Require its
|
|
2529
|
+
// entry index.html to exist; the directory is then served directly (no
|
|
2530
|
+
// in-memory HTML copy — getStaticPreviewHtml reads index.html from here).
|
|
2531
|
+
const entryFile = path_1.default.join(projectCwd, 'index.html');
|
|
2532
|
+
try {
|
|
2533
|
+
await fs_1.default.promises.access(entryFile, fs_1.default.constants.R_OK);
|
|
2534
|
+
}
|
|
2535
|
+
catch (err) {
|
|
2536
|
+
log.warn(`registerExistingStaticPreview: no entry HTML at ${entryFile} for ${sessionId}/${workItemId}; preview unavailable`, err);
|
|
2537
|
+
return false;
|
|
2538
|
+
}
|
|
2539
|
+
// The dir must be realpath'd so resolveStaticPreviewAssetPath's sandbox
|
|
2540
|
+
// comparison lines up; sibling assets (css/js/images) then resolve from the
|
|
2541
|
+
// same static root the entry HTML lives in.
|
|
2542
|
+
let dir;
|
|
2543
|
+
try {
|
|
2544
|
+
dir = fs_1.default.realpathSync(projectCwd);
|
|
2545
|
+
}
|
|
2546
|
+
catch {
|
|
2547
|
+
// Fall back to the un-realpath'd cwd; serving still works, only the
|
|
2548
|
+
// sandbox symlink-normalization is skipped.
|
|
2549
|
+
dir = projectCwd;
|
|
2550
|
+
}
|
|
2551
|
+
resources.staticPreviews.set(workItemId, {
|
|
2552
|
+
workItemId,
|
|
2553
|
+
briefId: input.briefId,
|
|
2554
|
+
assetBase: dir,
|
|
2555
|
+
// Existing-mode static projects are real multi-file sites — assume
|
|
2556
|
+
// sibling assets so the preview route resolves relative URLs.
|
|
2557
|
+
hasAssets: true,
|
|
2558
|
+
});
|
|
2559
|
+
log.info(`Variant ${workItemId} served as a static preview (existing/static, no dev server; root ${projectCwd})`);
|
|
2560
|
+
return true;
|
|
2561
|
+
}
|
|
2303
2562
|
/**
|
|
2304
2563
|
* Persist a completed fresh-project variant into
|
|
2305
2564
|
* `<projectContext.workspacePath>/.rivet/variants/<sessionId>/<variantId>/`.
|
|
@@ -2340,7 +2599,15 @@ class AgentVariantsOrchestrator {
|
|
|
2340
2599
|
}
|
|
2341
2600
|
else if (staticPreview) {
|
|
2342
2601
|
tmpStagingDir = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), `rivet-variant-${args.workItemId}-`));
|
|
2343
|
-
|
|
2602
|
+
// Read the materialized index.html from the variant's per-variant dir.
|
|
2603
|
+
let stagedHtml = '';
|
|
2604
|
+
try {
|
|
2605
|
+
stagedHtml = fs_1.default.readFileSync(path_1.default.join(staticPreview.assetBase, 'index.html'), 'utf8');
|
|
2606
|
+
}
|
|
2607
|
+
catch (err) {
|
|
2608
|
+
log.warn(`persistCompletedFreshVariant: could not read index.html for ${args.workItemId}`, err);
|
|
2609
|
+
}
|
|
2610
|
+
fs_1.default.writeFileSync(path_1.default.join(tmpStagingDir, 'index.html'), stagedHtml, 'utf8');
|
|
2344
2611
|
fs_1.default.writeFileSync(path_1.default.join(tmpStagingDir, 'brief.md'), `# ${input.briefLabel}\n\n${input.briefBody}\n`, 'utf8');
|
|
2345
2612
|
sourceDir = tmpStagingDir;
|
|
2346
2613
|
}
|
|
@@ -2521,6 +2788,24 @@ class AgentVariantsOrchestrator {
|
|
|
2521
2788
|
return undefined;
|
|
2522
2789
|
}
|
|
2523
2790
|
}
|
|
2791
|
+
/**
|
|
2792
|
+
* The port to start scanning from when allocating a variant's preview dev
|
|
2793
|
+
* server. Fresh worktrees run the Vite template (5173); existing projects use
|
|
2794
|
+
* their detected framework's default (3000 for Next, etc.), falling back to
|
|
2795
|
+
* 3000 if detection fails. Scanning upward from here lands the preview on a
|
|
2796
|
+
* sensible, predictable port near the project's normal one.
|
|
2797
|
+
*/
|
|
2798
|
+
async resolveDevServerStartPort(sessionId, isFresh) {
|
|
2799
|
+
if (isFresh)
|
|
2800
|
+
return FRESH_DEV_SERVER_DEFAULT_PORT;
|
|
2801
|
+
try {
|
|
2802
|
+
const env = await this.resolveEnv(sessionId);
|
|
2803
|
+
return env.defaultPort ?? 3000;
|
|
2804
|
+
}
|
|
2805
|
+
catch {
|
|
2806
|
+
return 3000;
|
|
2807
|
+
}
|
|
2808
|
+
}
|
|
2524
2809
|
/**
|
|
2525
2810
|
* Resolve dev server invocation for a worktree. Fresh-project worktrees
|
|
2526
2811
|
* always use the Vite template's npm command at the worktree root; existing
|
|
@@ -2942,6 +3227,31 @@ class AgentVariantsOrchestrator {
|
|
|
2942
3227
|
log.warn(`cleanupSession (worktree removal) failed for ${sessionId}`, err);
|
|
2943
3228
|
}
|
|
2944
3229
|
}
|
|
3230
|
+
// Remove fresh static_preview staging dirs (the per-variant directories
|
|
3231
|
+
// materialized under workspacePath at report time). On 'committed' the
|
|
3232
|
+
// commit flatten already moved/removed them; on cancel/shutdown they'd
|
|
3233
|
+
// otherwise orphan under `.rivet/<slug>/`. Existing-mode static previews
|
|
3234
|
+
// point at worktree dirs (cleaned by cleanupSession above), so scope this
|
|
3235
|
+
// to fresh sessions — where every staticPreviews dir is Rivet staging.
|
|
3236
|
+
if (reason !== 'committed') {
|
|
3237
|
+
let isFresh = false;
|
|
3238
|
+
try {
|
|
3239
|
+
isFresh = this.store.getProjectContext(sessionId).kind === 'fresh';
|
|
3240
|
+
}
|
|
3241
|
+
catch {
|
|
3242
|
+
isFresh = false;
|
|
3243
|
+
}
|
|
3244
|
+
if (isFresh) {
|
|
3245
|
+
for (const sp of resources.staticPreviews.values()) {
|
|
3246
|
+
try {
|
|
3247
|
+
fs_1.default.rmSync(sp.assetBase, { recursive: true, force: true });
|
|
3248
|
+
}
|
|
3249
|
+
catch (err) {
|
|
3250
|
+
log.warn(`failed to remove static preview dir ${sp.assetBase} for ${sessionId}`, err);
|
|
3251
|
+
}
|
|
3252
|
+
}
|
|
3253
|
+
}
|
|
3254
|
+
}
|
|
2945
3255
|
this.resources.delete(sessionId);
|
|
2946
3256
|
}
|
|
2947
3257
|
ensureResources(sessionId) {
|
|
@@ -2956,6 +3266,7 @@ class AgentVariantsOrchestrator {
|
|
|
2956
3266
|
startedAt: Date.now(),
|
|
2957
3267
|
leasedAt: new Map(),
|
|
2958
3268
|
qaResults: new Map(),
|
|
3269
|
+
previewFailures: new Map(),
|
|
2959
3270
|
vitePreservedSiblings: false,
|
|
2960
3271
|
};
|
|
2961
3272
|
this.resources.set(sessionId, r);
|
|
@@ -3711,6 +4022,8 @@ const trackFreshDevServerFailed = (telemetry, data) => {
|
|
|
3711
4022
|
session_id: data.sessionId,
|
|
3712
4023
|
variant_id: data.variantId,
|
|
3713
4024
|
error_code: data.errorCode,
|
|
4025
|
+
failure_reason: data.reason,
|
|
4026
|
+
port_in_use: data.portInUse,
|
|
3714
4027
|
});
|
|
3715
4028
|
};
|
|
3716
4029
|
const trackStaticPreviewCompleted = (telemetry, data) => {
|