rivet-design 0.11.11 → 0.11.13
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/install/harnesses.d.ts.map +1 -1
- package/dist/install/harnesses.js +122 -4
- package/dist/install/harnesses.js.map +1 -1
- package/dist/mcp/agent-variants/SessionStore.d.ts +150 -10
- package/dist/mcp/agent-variants/SessionStore.d.ts.map +1 -1
- package/dist/mcp/agent-variants/SessionStore.js +468 -23
- package/dist/mcp/agent-variants/SessionStore.js.map +1 -1
- package/dist/mcp/agent-variants/WorktreeOrchestrator.d.ts +143 -51
- package/dist/mcp/agent-variants/WorktreeOrchestrator.d.ts.map +1 -1
- package/dist/mcp/agent-variants/WorktreeOrchestrator.js +465 -299
- package/dist/mcp/agent-variants/WorktreeOrchestrator.js.map +1 -1
- package/dist/mcp/agent-variants/WorktreeOrchestrator.testHelpers.d.ts +5 -4
- package/dist/mcp/agent-variants/WorktreeOrchestrator.testHelpers.d.ts.map +1 -1
- package/dist/mcp/agent-variants/WorktreeOrchestrator.testHelpers.js +24 -5
- package/dist/mcp/agent-variants/WorktreeOrchestrator.testHelpers.js.map +1 -1
- package/dist/mcp/agent-variants/contracts.d.ts +102 -89
- package/dist/mcp/agent-variants/contracts.d.ts.map +1 -1
- package/dist/mcp/agent-variants/contracts.js +29 -14
- package/dist/mcp/agent-variants/contracts.js.map +1 -1
- package/dist/mcp/agent-variants/index.d.ts +3 -3
- package/dist/mcp/agent-variants/index.d.ts.map +1 -1
- package/dist/mcp/agent-variants/index.js +1 -2
- package/dist/mcp/agent-variants/index.js.map +1 -1
- package/dist/mcp/agent-variants/previewQa.d.ts.map +1 -1
- package/dist/mcp/agent-variants/previewQa.js +0 -6
- package/dist/mcp/agent-variants/previewQa.js.map +1 -1
- package/dist/mcp/agent-variants/tools.d.ts +12 -4
- package/dist/mcp/agent-variants/tools.d.ts.map +1 -1
- package/dist/mcp/agent-variants/tools.js +69 -13
- package/dist/mcp/agent-variants/tools.js.map +1 -1
- package/dist/mcp/changeBatchClassification.d.ts +30 -0
- package/dist/mcp/changeBatchClassification.d.ts.map +1 -1
- package/dist/mcp/changeBatchClassification.js +76 -0
- package/dist/mcp/changeBatchClassification.js.map +1 -1
- package/dist/mcp/instructions.d.ts.map +1 -1
- package/dist/mcp/instructions.js +7 -1
- package/dist/mcp/instructions.js.map +1 -1
- package/dist/mcp/server.d.ts +51 -3
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +404 -156
- package/dist/mcp/server.js.map +1 -1
- package/dist/mcp/watchLoopResumePrompt.d.ts +16 -0
- package/dist/mcp/watchLoopResumePrompt.d.ts.map +1 -0
- package/dist/mcp/watchLoopResumePrompt.js +21 -0
- package/dist/mcp/watchLoopResumePrompt.js.map +1 -0
- package/dist/routes/agentVariants.d.ts +10 -1
- package/dist/routes/agentVariants.d.ts.map +1 -1
- package/dist/routes/agentVariants.js +214 -3
- package/dist/routes/agentVariants.js.map +1 -1
- package/dist/routes/mcp.d.ts.map +1 -1
- package/dist/routes/mcp.js +106 -47
- package/dist/routes/mcp.js.map +1 -1
- package/dist/services/BridgeBatchPipeline.d.ts +99 -0
- package/dist/services/BridgeBatchPipeline.d.ts.map +1 -0
- package/dist/services/BridgeBatchPipeline.js +278 -0
- package/dist/services/BridgeBatchPipeline.js.map +1 -0
- package/dist/services/BridgeQueueStore.d.ts +28 -0
- package/dist/services/BridgeQueueStore.d.ts.map +1 -0
- package/dist/services/BridgeQueueStore.js +88 -0
- package/dist/services/BridgeQueueStore.js.map +1 -0
- package/dist/services/InlineVariantGenerationService.d.ts.map +1 -1
- package/dist/services/InlineVariantGenerationService.js +15 -3
- package/dist/services/InlineVariantGenerationService.js.map +1 -1
- package/dist/services/SessionBridgeService.d.ts +127 -31
- package/dist/services/SessionBridgeService.d.ts.map +1 -1
- package/dist/services/SessionBridgeService.js +428 -83
- package/dist/services/SessionBridgeService.js.map +1 -1
- package/dist/services/TelemetryService.d.ts +30 -0
- package/dist/services/TelemetryService.d.ts.map +1 -1
- package/dist/services/TelemetryService.js +42 -0
- package/dist/services/TelemetryService.js.map +1 -1
- package/dist/services/WorktreeManager.d.ts +29 -2
- package/dist/services/WorktreeManager.d.ts.map +1 -1
- package/dist/services/WorktreeManager.js +73 -7
- package/dist/services/WorktreeManager.js.map +1 -1
- package/dist/services/createAgentVariantsOrchestrator.d.ts +0 -8
- package/dist/services/createAgentVariantsOrchestrator.d.ts.map +1 -1
- package/dist/services/createAgentVariantsOrchestrator.js +1 -18
- package/dist/services/createAgentVariantsOrchestrator.js.map +1 -1
- package/dist/utils/portUtils.d.ts +9 -0
- package/dist/utils/portUtils.d.ts.map +1 -1
- package/dist/utils/portUtils.js +26 -0
- package/dist/utils/portUtils.js.map +1 -1
- package/dist/utils/skills/claude-skill.d.ts +1 -1
- package/dist/utils/skills/claude-skill.d.ts.map +1 -1
- package/dist/utils/skills/claude-skill.js +31 -12
- package/dist/utils/skills/claude-skill.js.map +1 -1
- package/dist/utils/skills/cursor-rules.d.ts +1 -1
- package/dist/utils/skills/cursor-rules.d.ts.map +1 -1
- package/dist/utils/skills/cursor-rules.js +26 -11
- package/dist/utils/skills/cursor-rules.js.map +1 -1
- package/dist/utils/skills/shared-variants-protocol.d.ts +28 -0
- package/dist/utils/skills/shared-variants-protocol.d.ts.map +1 -1
- package/dist/utils/skills/shared-variants-protocol.js +88 -14
- package/dist/utils/skills/shared-variants-protocol.js.map +1 -1
- package/package.json +3 -2
- package/src/ui/dist/assets/main-DREt3GGU.css +1 -0
- package/src/ui/dist/assets/main-Dt7ShmjM.js +655 -0
- package/src/ui/dist/index.html +2 -2
- package/dist/mcp/agent-variants/designCritique.d.ts +0 -167
- package/dist/mcp/agent-variants/designCritique.d.ts.map +0 -1
- package/dist/mcp/agent-variants/designCritique.js +0 -774
- package/dist/mcp/agent-variants/designCritique.js.map +0 -1
- package/src/ui/dist/assets/main-B1LAuLK_.js +0 -655
- package/src/ui/dist/assets/main-D-YwdYjm.css +0 -1
|
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.AgentVariantsOrchestrator =
|
|
6
|
+
exports.AgentVariantsOrchestrator = void 0;
|
|
7
7
|
exports.buildStaticPreviewDocument = buildStaticPreviewDocument;
|
|
8
8
|
const crypto_1 = require("crypto");
|
|
9
9
|
const events_1 = require("events");
|
|
@@ -17,12 +17,12 @@ const devServerError_1 = require("../../utils/devServerError");
|
|
|
17
17
|
const errors_1 = require("./errors");
|
|
18
18
|
const createProjectArtifacts_1 = require("./createProjectArtifacts");
|
|
19
19
|
const contracts_1 = require("./contracts");
|
|
20
|
+
const runLabel_1 = require("./runLabel");
|
|
20
21
|
const runPlan_1 = require("./runPlan");
|
|
21
22
|
const viteReactTs_1 = require("../../services/templates/viteReactTs");
|
|
22
23
|
const StaticPreviewServer_1 = require("../../services/StaticPreviewServer");
|
|
23
24
|
const designCatalog_1 = require("../../services/templates/designCatalog");
|
|
24
25
|
const previewQa_1 = require("./previewQa");
|
|
25
|
-
const designCritique_1 = require("./designCritique");
|
|
26
26
|
const diffQa_1 = require("./diffQa");
|
|
27
27
|
const VariantHistoryService_1 = require("../../services/VariantHistoryService");
|
|
28
28
|
const variantContext_1 = require("./variantContext");
|
|
@@ -281,94 +281,19 @@ const defaultPreviewQaRunner = ({ html, assetBase }) => (0, previewQa_1.runPrevi
|
|
|
281
281
|
...(assetBase ? { localAssetBasePath: assetBase } : {}),
|
|
282
282
|
},
|
|
283
283
|
});
|
|
284
|
-
/**
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
? {
|
|
297
|
-
dimensionScores: {
|
|
298
|
-
...outcome.scores,
|
|
299
|
-
overall: outcome.overallScore ?? outcome.scores.overall,
|
|
300
|
-
},
|
|
301
|
-
}
|
|
302
|
-
: {}),
|
|
303
|
-
...(outcome.summary ? { designSummary: outcome.summary } : {}),
|
|
304
|
-
...(outcome.findings.length > 0 ? { designFindings: outcome.findings } : {}),
|
|
305
|
-
...(outcome.checks ? { designChecks: outcome.checks } : {}),
|
|
306
|
-
...(outcome.scoreCaps.length > 0
|
|
307
|
-
? { designScoreCaps: outcome.scoreCaps }
|
|
308
|
-
: {}),
|
|
309
|
-
});
|
|
310
|
-
/** Merge passing design scores onto a base QA verdict (status unchanged). */
|
|
311
|
-
const attachDesignScores = (base, outcome) => attachCritiqueDetail(base, outcome);
|
|
312
|
-
const designScoreFragment = (outcome) => {
|
|
313
|
-
const score = outcome.overallScore ?? outcome.scores?.overall;
|
|
314
|
-
return score !== undefined ? `overall ${score.toFixed(1)}/10` : 'below bar';
|
|
315
|
-
};
|
|
316
|
-
/**
|
|
317
|
-
* Compose the actionable critique summary handed to a re-leased variant: the
|
|
318
|
-
* model's prose plus an explicit, prioritized fix list drawn from the
|
|
319
|
-
* critical/major findings so the agent knows exactly what to change.
|
|
320
|
-
*/
|
|
321
|
-
const buildRetryCritiqueSummary = (qa) => {
|
|
322
|
-
const base = qa.designSummary ?? qa.summary;
|
|
323
|
-
const actionable = (qa.designFindings ?? []).filter((f) => f.severity === 'critical' || f.severity === 'major');
|
|
324
|
-
if (actionable.length === 0)
|
|
325
|
-
return base;
|
|
326
|
-
const fixes = actionable
|
|
327
|
-
.map((f) => `- [${f.severity} ${f.category}] ${f.evidence} → ${f.fix}`)
|
|
328
|
-
.join('\n');
|
|
329
|
-
return `${base}\n\nMust fix before re-reporting:\n${fixes}`;
|
|
330
|
-
};
|
|
331
|
-
/** One-line, actionable summary of the worst findings for chip/prompt copy. */
|
|
332
|
-
const findingsFragment = (outcome) => {
|
|
333
|
-
const ranked = outcome.findings.filter((f) => f.severity === 'critical' || f.severity === 'major');
|
|
334
|
-
if (ranked.length === 0)
|
|
335
|
-
return '';
|
|
336
|
-
const first = ranked[0];
|
|
337
|
-
const more = ranked.length > 1 ? ` (+${ranked.length - 1} more)` : '';
|
|
338
|
-
return `${first.severity} ${first.category}: ${first.evidence}${more}`;
|
|
339
|
-
};
|
|
340
|
-
/** Build a failed QA verdict for a below-bar variant (drives the re-lease). */
|
|
341
|
-
const buildDesignFailureQa = (base, outcome) => {
|
|
342
|
-
const findings = findingsFragment(outcome);
|
|
343
|
-
const detail = findings || designScoreFragment(outcome);
|
|
344
|
-
return attachCritiqueDetail({
|
|
345
|
-
status: 'failed',
|
|
346
|
-
issues: [
|
|
347
|
-
...base.issues,
|
|
348
|
-
{
|
|
349
|
-
kind: 'design_quality',
|
|
350
|
-
detail,
|
|
351
|
-
...(outcome.summary ? { message: outcome.summary } : {}),
|
|
352
|
-
},
|
|
353
|
-
],
|
|
354
|
-
summary: outcome.blocked
|
|
355
|
-
? `Design critical: ${detail}.`
|
|
356
|
-
: `Design quality below bar (${detail}).`,
|
|
357
|
-
...(base.checkedSource ? { checkedSource: base.checkedSource } : {}),
|
|
358
|
-
}, outcome);
|
|
284
|
+
/** Disabled-reason copy for a variant whose preview cannot be opened. */
|
|
285
|
+
const buildViewDisabledReason = (args) => {
|
|
286
|
+
if (args.status === 'failed') {
|
|
287
|
+
return args.qa?.summary ?? 'Generation failed';
|
|
288
|
+
}
|
|
289
|
+
if (args.status === 'cancelled') {
|
|
290
|
+
return 'Variant was cancelled';
|
|
291
|
+
}
|
|
292
|
+
if (args.isSucceeded) {
|
|
293
|
+
return 'Preview is unavailable for this variant';
|
|
294
|
+
}
|
|
295
|
+
return 'Variant is still running';
|
|
359
296
|
};
|
|
360
|
-
/**
|
|
361
|
-
* Build a degraded (but committable) QA verdict for a variant that stayed
|
|
362
|
-
* below the bar after its one retry. Status is `passed` so commit stays
|
|
363
|
-
* enabled — the low score is surfaced via `dimensionScores`/`designSummary`
|
|
364
|
-
* rather than hard-blocking the user forever. Only used for non-critical
|
|
365
|
-
* failures; an unresolved critical hard-fails instead of degrading.
|
|
366
|
-
*/
|
|
367
|
-
const buildDegradedDesignQa = (base, outcome) => attachCritiqueDetail({
|
|
368
|
-
...base,
|
|
369
|
-
status: 'passed',
|
|
370
|
-
summary: `Accepted with low design score (${designScoreFragment(outcome)}) after retry.`,
|
|
371
|
-
}, outcome);
|
|
372
297
|
const NOOP_TELEMETRY = { track: () => undefined };
|
|
373
298
|
const DESIGN_CREATION_ACTION_RE = /\b(create|build|design|make|redesign|refresh|update)\b/i;
|
|
374
299
|
const DESIGN_CREATION_SURFACE_RE = /\b(home\s?page|landing page|dashboard|screen|site|website|app|page|ui|interface|component|hero)\b/i;
|
|
@@ -416,7 +341,6 @@ class AgentVariantsOrchestrator {
|
|
|
416
341
|
installDependencies;
|
|
417
342
|
materializeProject;
|
|
418
343
|
previewQaRunner;
|
|
419
|
-
designCritiqueRunner;
|
|
420
344
|
switchPreviewPort;
|
|
421
345
|
setCommittedDevServerHealth;
|
|
422
346
|
variantHistory;
|
|
@@ -460,7 +384,6 @@ class AgentVariantsOrchestrator {
|
|
|
460
384
|
this.materializeProject =
|
|
461
385
|
deps.materializeProject ?? defaultMaterializeProject;
|
|
462
386
|
this.previewQaRunner = deps.previewQaRunner ?? defaultPreviewQaRunner;
|
|
463
|
-
this.designCritiqueRunner = deps.designCritiqueRunner;
|
|
464
387
|
this.switchPreviewPort = deps.switchPreviewPort;
|
|
465
388
|
this.setCommittedDevServerHealth = deps.setCommittedDevServerHealth;
|
|
466
389
|
this.variantHistory = deps.variantHistory ?? new VariantHistoryService_1.VariantHistoryService();
|
|
@@ -661,44 +584,242 @@ class AgentVariantsOrchestrator {
|
|
|
661
584
|
}
|
|
662
585
|
beginStaticPreviewRefinements(args) {
|
|
663
586
|
const seenVariantIds = new Set();
|
|
587
|
+
// Targets whose in-place refinement could neither be created nor folded
|
|
588
|
+
// into a pending one (e.g. the variant was already sent). We finish the
|
|
589
|
+
// batch's other targets, then RETURN these so the caller can surface them
|
|
590
|
+
// in its response — rather than throwing (which would break the agent poll
|
|
591
|
+
// and lose the already-consumed batch) or silently dropping them.
|
|
592
|
+
const droppedRefinements = [];
|
|
593
|
+
// Work items THIS batch created, so the caller can tell the agent to lease
|
|
594
|
+
// exactly these — letting concurrent runs (several watchers) each pick up
|
|
595
|
+
// their own batch instead of one greedily leasing everything.
|
|
596
|
+
const createdWorkItemIds = [];
|
|
664
597
|
for (const target of args.targets) {
|
|
665
598
|
if (seenVariantIds.has(target.variantId))
|
|
666
599
|
continue;
|
|
667
600
|
seenVariantIds.add(target.variantId);
|
|
668
601
|
const currentHtml = this.getStaticPreviewHtml(args.sessionId, target.variantId);
|
|
602
|
+
if (target.count == null) {
|
|
603
|
+
// Plain Apply (no variantRequest) → refine the variant IN PLACE: one
|
|
604
|
+
// edit that overwrites the source, no new sibling cards. Only an
|
|
605
|
+
// explicit "Vary" (which carries a count) fans out new variants.
|
|
606
|
+
try {
|
|
607
|
+
const refineItem = this.store.createRefineVariantWorkItem({
|
|
608
|
+
sessionId: args.sessionId,
|
|
609
|
+
variantId: target.variantId,
|
|
610
|
+
instruction: target.instruction,
|
|
611
|
+
currentHtml,
|
|
612
|
+
targetElement: target.element,
|
|
613
|
+
requestId: target.requestId,
|
|
614
|
+
});
|
|
615
|
+
createdWorkItemIds.push(refineItem.id);
|
|
616
|
+
}
|
|
617
|
+
catch (error) {
|
|
618
|
+
// A refinement is already queued for this variant — fold the newer
|
|
619
|
+
// instruction in rather than dropping it (mirrors the apply queue).
|
|
620
|
+
if (error instanceof errors_1.AgentVariantsError &&
|
|
621
|
+
error.code === 'PENDING_CHANGE_CONFLICT') {
|
|
622
|
+
const folded = this.store.appendPendingRefineVariantInstruction({
|
|
623
|
+
sessionId: args.sessionId,
|
|
624
|
+
variantId: target.variantId,
|
|
625
|
+
instruction: target.instruction,
|
|
626
|
+
});
|
|
627
|
+
if (!folded) {
|
|
628
|
+
// Nothing to fold into — the conflict is terminal (the variant
|
|
629
|
+
// was already sent / is no longer editable). Don't swallow it.
|
|
630
|
+
droppedRefinements.push({
|
|
631
|
+
variantId: target.variantId,
|
|
632
|
+
reason: error.message,
|
|
633
|
+
});
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
else if (error instanceof errors_1.AgentVariantsError &&
|
|
637
|
+
error.code === 'VARIANT_NOT_EDITABLE') {
|
|
638
|
+
droppedRefinements.push({
|
|
639
|
+
variantId: target.variantId,
|
|
640
|
+
reason: error.message,
|
|
641
|
+
});
|
|
642
|
+
}
|
|
643
|
+
else {
|
|
644
|
+
throw error;
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
continue;
|
|
648
|
+
}
|
|
649
|
+
// "Vary" fans out: create `count` NEW sibling variants seeded from the
|
|
650
|
+
// source HTML + instruction, rather than overwriting the source in place.
|
|
651
|
+
// The agent leases them through the standard request-work path.
|
|
669
652
|
try {
|
|
670
|
-
this.store.
|
|
653
|
+
const forks = this.store.createForkedStaticPreviewWorkItems({
|
|
671
654
|
sessionId: args.sessionId,
|
|
672
|
-
|
|
655
|
+
sourceVariantId: target.variantId,
|
|
673
656
|
instruction: target.instruction,
|
|
657
|
+
count: target.count,
|
|
674
658
|
currentHtml,
|
|
659
|
+
targetElement: target.element,
|
|
660
|
+
requestId: target.requestId,
|
|
675
661
|
});
|
|
662
|
+
createdWorkItemIds.push(...forks.map((item) => item.id));
|
|
676
663
|
}
|
|
677
664
|
catch (error) {
|
|
665
|
+
// Terminal conflict — the source is no longer editable, or a variant
|
|
666
|
+
// was already sent for this session (the fork path has nothing to fold
|
|
667
|
+
// into). Surface it structurally like the in-place branch instead of
|
|
668
|
+
// throwing, which would break the agent poll AND lose this batch (the
|
|
669
|
+
// server already consumed it from the FIFO before calling us).
|
|
678
670
|
if (error instanceof errors_1.AgentVariantsError &&
|
|
679
|
-
error.code === 'PENDING_CHANGE_CONFLICT'
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
// still-pending item so it isn't silently dropped.
|
|
683
|
-
const folded = this.store.appendPendingRefineVariantInstruction({
|
|
684
|
-
sessionId: args.sessionId,
|
|
671
|
+
(error.code === 'PENDING_CHANGE_CONFLICT' ||
|
|
672
|
+
error.code === 'VARIANT_NOT_EDITABLE')) {
|
|
673
|
+
droppedRefinements.push({
|
|
685
674
|
variantId: target.variantId,
|
|
686
|
-
|
|
675
|
+
reason: error.message,
|
|
687
676
|
});
|
|
688
|
-
if (!folded) {
|
|
689
|
-
this.store.createRefineVariantWorkItem({
|
|
690
|
-
sessionId: args.sessionId,
|
|
691
|
-
variantId: target.variantId,
|
|
692
|
-
instruction: target.instruction,
|
|
693
|
-
currentHtml,
|
|
694
|
-
});
|
|
695
|
-
}
|
|
696
677
|
continue;
|
|
697
678
|
}
|
|
698
679
|
throw error;
|
|
699
680
|
}
|
|
700
681
|
}
|
|
701
682
|
this.emitChange();
|
|
683
|
+
return { droppedRefinements, createdWorkItemIds };
|
|
684
|
+
}
|
|
685
|
+
/**
|
|
686
|
+
* Adopt a PAST variant (from a prior session's persisted history) into the
|
|
687
|
+
* active session, rehydrating it from its on-disk static snapshot so the
|
|
688
|
+
* comment / refine / Vary machinery treats it as a live, first-class variant.
|
|
689
|
+
*
|
|
690
|
+
* Triggered when the user makes their first edit on a selected past direction.
|
|
691
|
+
* The store appends an already-`succeeded` `static_preview` work item keyed by
|
|
692
|
+
* the past identity (idempotent); this method registers the matching static-
|
|
693
|
+
* preview record so {@link getStaticPreviewHtml}, {@link getVariants}
|
|
694
|
+
* (preview.kind = 'static_artifact', view enabled), and the
|
|
695
|
+
* `/api/variants/:sid/static/:vid` routes all light up exactly as they do for a
|
|
696
|
+
* freshly-generated static_preview variant.
|
|
697
|
+
*
|
|
698
|
+
* `filesDir` is the absolute path to the persisted `files/` snapshot (resolved
|
|
699
|
+
* by the caller from history) and must hold a renderable `index.html`. Returns
|
|
700
|
+
* the stable active-session `workItemId` so the caller can route the
|
|
701
|
+
* triggering edit to it, plus whether it was already adopted.
|
|
702
|
+
*/
|
|
703
|
+
async adoptPastVariant(args) {
|
|
704
|
+
const { assetBase, hasAssets } = await this.resolveAdoptedStaticPreviewFiles(args.filesDir);
|
|
705
|
+
const append = this.store.appendAdoptedStaticVariant({
|
|
706
|
+
sessionId: args.sessionId,
|
|
707
|
+
source: args.source,
|
|
708
|
+
label: args.label,
|
|
709
|
+
description: args.description,
|
|
710
|
+
runLabel: args.runLabel,
|
|
711
|
+
});
|
|
712
|
+
// Register (or re-register, harmlessly) the static-preview record so the
|
|
713
|
+
// snapshot surfaces the adopted variant as a static_artifact.
|
|
714
|
+
const resources = this.ensureResources(args.sessionId);
|
|
715
|
+
resources.staticPreviews.set(append.workItemId, {
|
|
716
|
+
workItemId: append.workItemId,
|
|
717
|
+
briefId: append.briefId,
|
|
718
|
+
assetBase,
|
|
719
|
+
hasAssets,
|
|
720
|
+
});
|
|
721
|
+
this.emitChange();
|
|
722
|
+
return {
|
|
723
|
+
workItemId: append.workItemId,
|
|
724
|
+
alreadyAdopted: !append.created,
|
|
725
|
+
};
|
|
726
|
+
}
|
|
727
|
+
/**
|
|
728
|
+
* Rehydrate a static history variant into the current active session when it
|
|
729
|
+
* can still accept edits; otherwise create a replacement active session whose
|
|
730
|
+
* only visible direction is the selected history artifact.
|
|
731
|
+
*/
|
|
732
|
+
async bootstrapPastVariantSession(args) {
|
|
733
|
+
const usableSessionId = this.getUsableActiveSessionId();
|
|
734
|
+
if (usableSessionId) {
|
|
735
|
+
const adopted = await this.adoptPastVariant({
|
|
736
|
+
sessionId: usableSessionId,
|
|
737
|
+
source: args.source,
|
|
738
|
+
filesDir: args.filesDir,
|
|
739
|
+
label: args.label,
|
|
740
|
+
description: args.description,
|
|
741
|
+
runLabel: args.runLabel,
|
|
742
|
+
});
|
|
743
|
+
return {
|
|
744
|
+
sessionId: usableSessionId,
|
|
745
|
+
workItemId: adopted.workItemId,
|
|
746
|
+
alreadyAdopted: adopted.alreadyAdopted,
|
|
747
|
+
bootstrapped: false,
|
|
748
|
+
};
|
|
749
|
+
}
|
|
750
|
+
const { assetBase, hasAssets } = await this.resolveAdoptedStaticPreviewFiles(args.filesDir);
|
|
751
|
+
void this.stopLingeringCommittedDevServers().catch((err) => {
|
|
752
|
+
log.warn('Stopping lingering committed dev servers failed', err);
|
|
753
|
+
});
|
|
754
|
+
const created = this.store.createSessionFromStaticHistoryVariant({
|
|
755
|
+
source: args.source,
|
|
756
|
+
prompt: args.prompt ?? args.description ?? args.label,
|
|
757
|
+
label: args.label,
|
|
758
|
+
description: args.description,
|
|
759
|
+
runLabel: args.runLabel,
|
|
760
|
+
projectContext: args.projectContext,
|
|
761
|
+
});
|
|
762
|
+
this.activeSessionId = created.sessionId;
|
|
763
|
+
const resources = this.ensureResources(created.sessionId);
|
|
764
|
+
resources.staticPreviews.set(created.workItemId, {
|
|
765
|
+
workItemId: created.workItemId,
|
|
766
|
+
briefId: created.briefId,
|
|
767
|
+
assetBase,
|
|
768
|
+
hasAssets,
|
|
769
|
+
});
|
|
770
|
+
this.emitChange();
|
|
771
|
+
return {
|
|
772
|
+
sessionId: created.sessionId,
|
|
773
|
+
workItemId: created.workItemId,
|
|
774
|
+
alreadyAdopted: false,
|
|
775
|
+
bootstrapped: true,
|
|
776
|
+
};
|
|
777
|
+
}
|
|
778
|
+
getUsableActiveSessionId() {
|
|
779
|
+
const sessionId = this.activeSessionId;
|
|
780
|
+
if (!sessionId || !this.store.hasSession(sessionId)) {
|
|
781
|
+
return null;
|
|
782
|
+
}
|
|
783
|
+
const stage = this.store.getStage(sessionId);
|
|
784
|
+
return stage === 'failed' || stage === 'cancelled' ? null : sessionId;
|
|
785
|
+
}
|
|
786
|
+
async resolveAdoptedStaticPreviewFiles(filesDir) {
|
|
787
|
+
const entryFile = path_1.default.join(filesDir, 'index.html');
|
|
788
|
+
try {
|
|
789
|
+
await fs_1.default.promises.access(entryFile, fs_1.default.constants.R_OK);
|
|
790
|
+
}
|
|
791
|
+
catch {
|
|
792
|
+
throw new errors_1.AgentVariantsError('RUNTIME_VALIDATION_FAILED', `Cannot adopt past variant: no readable index.html under ${filesDir}`);
|
|
793
|
+
}
|
|
794
|
+
let assetBase;
|
|
795
|
+
try {
|
|
796
|
+
assetBase = fs_1.default.realpathSync(filesDir);
|
|
797
|
+
}
|
|
798
|
+
catch {
|
|
799
|
+
assetBase = filesDir;
|
|
800
|
+
}
|
|
801
|
+
let hasAssets = false;
|
|
802
|
+
try {
|
|
803
|
+
hasAssets = fs_1.default.readdirSync(assetBase).some((name) => name !== 'index.html');
|
|
804
|
+
}
|
|
805
|
+
catch {
|
|
806
|
+
hasAssets = false;
|
|
807
|
+
}
|
|
808
|
+
return { assetBase, hasAssets };
|
|
809
|
+
}
|
|
810
|
+
/**
|
|
811
|
+
* True when the variant is an HTML-only static_preview work item. Adopted past
|
|
812
|
+
* variants and fresh `static_preview` variants both qualify. Existing-project
|
|
813
|
+
* `code_gen` variants can still expose a static artifact for preview, but Vary
|
|
814
|
+
* must keep those on the worktree-backed code_gen append path.
|
|
815
|
+
*/
|
|
816
|
+
isStaticPreviewVariant(sessionId, workItemId) {
|
|
817
|
+
try {
|
|
818
|
+
return this.store.getWorkItemKind(sessionId, workItemId) === 'static_preview';
|
|
819
|
+
}
|
|
820
|
+
catch {
|
|
821
|
+
return false;
|
|
822
|
+
}
|
|
702
823
|
}
|
|
703
824
|
getVariantPick(sessionId) {
|
|
704
825
|
return this.store.getVariantPick(sessionId);
|
|
@@ -756,6 +877,13 @@ class AgentVariantsOrchestrator {
|
|
|
756
877
|
const resources = this.resources.get(sessionId);
|
|
757
878
|
return this.store.getVariants(sessionId).map((variant) => {
|
|
758
879
|
const port = this.getDevServerPort(sessionId, variant.workItemId);
|
|
880
|
+
// Captured worktree diff (existing-project code_gen). Surfaced as a
|
|
881
|
+
// changed-files count so a diff-only variant (no dev server / static
|
|
882
|
+
// artifact) still reads as a real result in the panel.
|
|
883
|
+
const capturedDiff = resources?.worktrees.get(variant.workItemId)?.diff;
|
|
884
|
+
const changedFilesCount = typeof capturedDiff === 'string' && capturedDiff.length > 0
|
|
885
|
+
? countDiffFiles(capturedDiff)
|
|
886
|
+
: undefined;
|
|
759
887
|
// For fresh sessions, static_preview items ARE the variants — look up
|
|
760
888
|
// directly by workItemId. Fall back to briefId search for existing-project
|
|
761
889
|
// sessions where code_gen items were paired with companion static_preview items.
|
|
@@ -813,6 +941,7 @@ class AgentVariantsOrchestrator {
|
|
|
813
941
|
design: enrichDesignSource(variant.design),
|
|
814
942
|
...(preview ? { preview } : {}),
|
|
815
943
|
port,
|
|
944
|
+
...(changedFilesCount !== undefined ? { changedFilesCount } : {}),
|
|
816
945
|
...(qa ? { qa } : {}),
|
|
817
946
|
...(previewUnavailable ? { previewUnavailable } : {}),
|
|
818
947
|
actions: {
|
|
@@ -820,9 +949,11 @@ class AgentVariantsOrchestrator {
|
|
|
820
949
|
? { enabled: true }
|
|
821
950
|
: {
|
|
822
951
|
enabled: false,
|
|
823
|
-
reason:
|
|
824
|
-
|
|
825
|
-
|
|
952
|
+
reason: buildViewDisabledReason({
|
|
953
|
+
status: variant.status,
|
|
954
|
+
isSucceeded,
|
|
955
|
+
qa,
|
|
956
|
+
}),
|
|
826
957
|
},
|
|
827
958
|
commit: canCommit
|
|
828
959
|
? { enabled: true }
|
|
@@ -1011,60 +1142,107 @@ class AgentVariantsOrchestrator {
|
|
|
1011
1142
|
if (isSourceGrounded) {
|
|
1012
1143
|
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.');
|
|
1013
1144
|
}
|
|
1014
|
-
|
|
1145
|
+
// A second "Vary" on an existing project that still has a live session
|
|
1146
|
+
// ACCUMULATES into that session instead of starting a fresh one that
|
|
1147
|
+
// would orphan the prior variants (and overwrite `activeSessionId`, so
|
|
1148
|
+
// the RHS would only ever show the latest batch). Gated to existing
|
|
1149
|
+
// projects so fresh 0→1 generation is unaffected.
|
|
1150
|
+
const appendSessionId = args.allowAccumulate &&
|
|
1151
|
+
projectContext.kind === 'existing' &&
|
|
1152
|
+
projectContext.projectPath
|
|
1153
|
+
? this.getAppendableExistingSessionId(projectContext.projectPath)
|
|
1154
|
+
: null;
|
|
1155
|
+
const briefs = this.buildUnifiedBriefs({
|
|
1015
1156
|
prompt: args.prompt,
|
|
1016
|
-
count,
|
|
1017
|
-
target: args.target,
|
|
1018
|
-
projectContext,
|
|
1019
1157
|
runLabel: args.runLabel,
|
|
1020
|
-
...(args.sessionId ? { sessionId: args.sessionId } : {}),
|
|
1021
|
-
});
|
|
1022
|
-
if (proposeResult.stage !== 'awaiting_briefs' ||
|
|
1023
|
-
!proposeResult.briefWorkItem) {
|
|
1024
|
-
throw new errors_1.AgentVariantsError('INVALID_STAGE_ACTION', `start_variants expected awaiting_briefs from propose, got ${proposeResult.stage}`);
|
|
1025
|
-
}
|
|
1026
|
-
const briefs = args.briefs
|
|
1027
|
-
? args.briefs.map((b, i) => ({
|
|
1028
|
-
briefId: `v${i + 1}`,
|
|
1029
|
-
label: b.label,
|
|
1030
|
-
body: b.body,
|
|
1031
|
-
...(b.visualReferenceUrl
|
|
1032
|
-
? { visualReferenceUrl: b.visualReferenceUrl }
|
|
1033
|
-
: {}),
|
|
1034
|
-
}))
|
|
1035
|
-
: Array.from({ length: count }, (_, i) => ({
|
|
1036
|
-
briefId: `v${i + 1}`,
|
|
1037
|
-
label: `Variant ${i + 1}`,
|
|
1038
|
-
body: synthesizeUnifiedBriefBody(args.prompt, i + 1, count),
|
|
1039
|
-
}));
|
|
1040
|
-
this.store.reportBriefs({
|
|
1041
|
-
sessionId: proposeResult.sessionId,
|
|
1042
|
-
workItemId: proposeResult.briefWorkItem.id,
|
|
1043
|
-
attempt: proposeResult.briefWorkItem.attempt,
|
|
1044
|
-
briefs,
|
|
1045
|
-
});
|
|
1046
|
-
const approveResult = await this.approve({
|
|
1047
|
-
sessionId: proposeResult.sessionId,
|
|
1048
|
-
selections: briefs.map((b) => ({ briefId: b.briefId })),
|
|
1049
|
-
});
|
|
1050
|
-
this.telemetry.track('agent_variants.unified_started', {
|
|
1051
|
-
source: 'mcp',
|
|
1052
|
-
sessionId: proposeResult.sessionId,
|
|
1053
1158
|
count,
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
projectContextKind: projectContext.kind,
|
|
1159
|
+
briefs: args.briefs,
|
|
1160
|
+
uniqueBriefIds: Boolean(appendSessionId),
|
|
1057
1161
|
});
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
.
|
|
1067
|
-
|
|
1162
|
+
let sessionId;
|
|
1163
|
+
let variantIds;
|
|
1164
|
+
let scaffoldBaseWorkItemId = null;
|
|
1165
|
+
if (appendSessionId) {
|
|
1166
|
+
sessionId = appendSessionId;
|
|
1167
|
+
const forkArgs = {
|
|
1168
|
+
sessionId,
|
|
1169
|
+
briefs,
|
|
1170
|
+
runLabel: args.runLabel?.trim() || (0, runLabel_1.deriveRunLabel)(args.prompt),
|
|
1171
|
+
...(projectContext.kind === 'existing' && projectContext.variantContext
|
|
1172
|
+
? { variantContext: projectContext.variantContext }
|
|
1173
|
+
: {}),
|
|
1174
|
+
};
|
|
1175
|
+
const { createdWorkItemIds } = args.sourceVariantId
|
|
1176
|
+
? this.store.createForkedCodeGenWorkItems({
|
|
1177
|
+
...forkArgs,
|
|
1178
|
+
sourceVariantId: args.sourceVariantId,
|
|
1179
|
+
})
|
|
1180
|
+
: this.store.appendExistingCodeGenVariants(forkArgs);
|
|
1181
|
+
variantIds = createdWorkItemIds;
|
|
1182
|
+
if (args.sourceVariantId) {
|
|
1183
|
+
await this.provisionForkedFromVariantWorktrees(sessionId, args.sourceVariantId, createdWorkItemIds);
|
|
1184
|
+
}
|
|
1185
|
+
else {
|
|
1186
|
+
await this.provisionAppendedExistingWorktrees(sessionId, createdWorkItemIds);
|
|
1187
|
+
}
|
|
1188
|
+
this.telemetry.track('agent_variants.variants_appended', {
|
|
1189
|
+
source: 'mcp',
|
|
1190
|
+
sessionId,
|
|
1191
|
+
count: createdWorkItemIds.length,
|
|
1192
|
+
hasTarget: Boolean(args.target),
|
|
1193
|
+
targetType: args.target?.type ?? null,
|
|
1194
|
+
forkedFromVariantId: args.sourceVariantId ?? null,
|
|
1195
|
+
});
|
|
1196
|
+
this.emitChange();
|
|
1197
|
+
}
|
|
1198
|
+
else {
|
|
1199
|
+
const proposeResult = this.propose({
|
|
1200
|
+
prompt: args.prompt,
|
|
1201
|
+
count,
|
|
1202
|
+
target: args.target,
|
|
1203
|
+
projectContext,
|
|
1204
|
+
runLabel: args.runLabel,
|
|
1205
|
+
...(args.sessionId ? { sessionId: args.sessionId } : {}),
|
|
1206
|
+
});
|
|
1207
|
+
if (proposeResult.stage !== 'awaiting_briefs' ||
|
|
1208
|
+
!proposeResult.briefWorkItem) {
|
|
1209
|
+
throw new errors_1.AgentVariantsError('INVALID_STAGE_ACTION', `start_variants expected awaiting_briefs from propose, got ${proposeResult.stage}`);
|
|
1210
|
+
}
|
|
1211
|
+
this.store.reportBriefs({
|
|
1212
|
+
sessionId: proposeResult.sessionId,
|
|
1213
|
+
workItemId: proposeResult.briefWorkItem.id,
|
|
1214
|
+
attempt: proposeResult.briefWorkItem.attempt,
|
|
1215
|
+
briefs,
|
|
1216
|
+
});
|
|
1217
|
+
const approveResult = await this.approve({
|
|
1218
|
+
sessionId: proposeResult.sessionId,
|
|
1219
|
+
selections: briefs.map((b) => ({ briefId: b.briefId })),
|
|
1220
|
+
});
|
|
1221
|
+
this.telemetry.track('agent_variants.unified_started', {
|
|
1222
|
+
source: 'mcp',
|
|
1223
|
+
sessionId: proposeResult.sessionId,
|
|
1224
|
+
count,
|
|
1225
|
+
hasTarget: Boolean(args.target),
|
|
1226
|
+
targetType: args.target?.type ?? null,
|
|
1227
|
+
projectContextKind: projectContext.kind,
|
|
1228
|
+
});
|
|
1229
|
+
sessionId = proposeResult.sessionId;
|
|
1230
|
+
scaffoldBaseWorkItemId = approveResult.scaffoldBaseWorkItemId ?? null;
|
|
1231
|
+
// For existing-project sessions, approve() returns the new code_gen
|
|
1232
|
+
// ids in codeGenWorkItemIds. For zero-to-one, approve() spawns
|
|
1233
|
+
// static_preview items instead — those ids aren't in codeGenWorkItemIds,
|
|
1234
|
+
// so read them out of the active-variants snapshot which covers both
|
|
1235
|
+
// kinds. Snapshot order mirrors brief order (insertion order).
|
|
1236
|
+
variantIds =
|
|
1237
|
+
projectContext.kind === 'fresh'
|
|
1238
|
+
? this.store.getVariants(sessionId).map((v) => v.workItemId)
|
|
1239
|
+
: approveResult.codeGenWorkItemIds;
|
|
1240
|
+
}
|
|
1241
|
+
// Stamp the UI correlation id so the panel maps its optimistic placeholders
|
|
1242
|
+
// to these real cards (the same mechanism the static-preview fork uses).
|
|
1243
|
+
if (args.requestId) {
|
|
1244
|
+
this.store.assignVariantRequestId(sessionId, variantIds, args.requestId);
|
|
1245
|
+
}
|
|
1068
1246
|
const variants = variantIds.map((workItemId, i) => ({
|
|
1069
1247
|
variantId: workItemId,
|
|
1070
1248
|
briefId: briefs[i].briefId,
|
|
@@ -1076,22 +1254,111 @@ class AgentVariantsOrchestrator {
|
|
|
1076
1254
|
let leasedWorkItems;
|
|
1077
1255
|
if (args.leaseOwner) {
|
|
1078
1256
|
const lease = this.requestWork({
|
|
1079
|
-
sessionId
|
|
1257
|
+
sessionId,
|
|
1080
1258
|
leaseOwner: args.leaseOwner,
|
|
1259
|
+
// Append: lease ONLY the new batch so a still-leasable prior item
|
|
1260
|
+
// (e.g. a re-leased one) isn't swept into this Vary's lease.
|
|
1261
|
+
...(appendSessionId ? { workItemIds: variantIds } : {}),
|
|
1081
1262
|
});
|
|
1082
1263
|
leaseId = lease.leaseId;
|
|
1083
1264
|
leaseTtlMs = lease.leaseTtlMs;
|
|
1084
1265
|
leasedWorkItems = lease.leasedWorkItems;
|
|
1085
1266
|
}
|
|
1086
1267
|
return {
|
|
1087
|
-
sessionId
|
|
1268
|
+
sessionId,
|
|
1088
1269
|
variants,
|
|
1089
|
-
scaffoldBaseWorkItemId
|
|
1270
|
+
scaffoldBaseWorkItemId,
|
|
1090
1271
|
leaseId,
|
|
1091
1272
|
leaseTtlMs,
|
|
1092
1273
|
leasedWorkItems,
|
|
1093
1274
|
};
|
|
1094
1275
|
}
|
|
1276
|
+
/**
|
|
1277
|
+
* Build the per-variant briefs for a unified start. Agent-supplied briefs win
|
|
1278
|
+
* verbatim. The fallback derives a short run-intent label from the prompt
|
|
1279
|
+
* (e.g. "make it brighter" → "Brighter 1/2/…") rather than an indistinct
|
|
1280
|
+
* "Variant N", so two sequential Vary batches stay distinguishable in the
|
|
1281
|
+
* Directions list. `uniqueBriefIds` is set for the append path so the new
|
|
1282
|
+
* batch's briefIds can't collide with the live session's existing ones.
|
|
1283
|
+
*/
|
|
1284
|
+
buildUnifiedBriefs(args) {
|
|
1285
|
+
const briefId = (i) => args.uniqueBriefIds ? `v-${(0, crypto_1.randomUUID)().slice(0, 8)}` : `v${i + 1}`;
|
|
1286
|
+
if (args.briefs) {
|
|
1287
|
+
return args.briefs.map((b, i) => ({
|
|
1288
|
+
briefId: briefId(i),
|
|
1289
|
+
label: b.label,
|
|
1290
|
+
body: b.body,
|
|
1291
|
+
...(b.visualReferenceUrl
|
|
1292
|
+
? { visualReferenceUrl: b.visualReferenceUrl }
|
|
1293
|
+
: {}),
|
|
1294
|
+
}));
|
|
1295
|
+
}
|
|
1296
|
+
const runLabel = args.runLabel?.trim() || (0, runLabel_1.deriveRunLabel)(args.prompt);
|
|
1297
|
+
return Array.from({ length: args.count }, (_, i) => ({
|
|
1298
|
+
briefId: briefId(i),
|
|
1299
|
+
label: args.count > 1 ? `${runLabel} ${i + 1}` : runLabel,
|
|
1300
|
+
body: synthesizeUnifiedBriefBody(args.prompt, i + 1, args.count),
|
|
1301
|
+
}));
|
|
1302
|
+
}
|
|
1303
|
+
/**
|
|
1304
|
+
* The active sessionId IF it is an existing-project session for `projectPath`
|
|
1305
|
+
* that can still accept appended variants — i.e. not committed, not
|
|
1306
|
+
* cancelled/failed. Returns null otherwise, so start_variants falls back to
|
|
1307
|
+
* creating a fresh session (first Vary, a different project, or after the
|
|
1308
|
+
* prior session was committed/cancelled).
|
|
1309
|
+
*/
|
|
1310
|
+
getAppendableExistingSessionId(projectPath) {
|
|
1311
|
+
const sessionId = this.activeSessionId;
|
|
1312
|
+
if (!sessionId || !this.store.hasSession(sessionId))
|
|
1313
|
+
return null;
|
|
1314
|
+
const ctx = this.store.getProjectContext(sessionId);
|
|
1315
|
+
if (ctx.kind !== 'existing' || ctx.projectPath !== projectPath)
|
|
1316
|
+
return null;
|
|
1317
|
+
if (this.store.getVariantPick(sessionId))
|
|
1318
|
+
return null;
|
|
1319
|
+
const stage = this.store.getStage(sessionId);
|
|
1320
|
+
if (stage === 'cancelled' || stage === 'failed')
|
|
1321
|
+
return null;
|
|
1322
|
+
return sessionId;
|
|
1323
|
+
}
|
|
1324
|
+
/**
|
|
1325
|
+
* Provision a worktree for each appended existing-project variant. Mirrors
|
|
1326
|
+
* {@link provisionExistingWorktrees} but only for the newly appended ids, and
|
|
1327
|
+
* passes a per-batch `instanceKeyPrefix` so the new worktree dirs do not
|
|
1328
|
+
* collide with the first batch's `${sessionId}-${i}` paths.
|
|
1329
|
+
*/
|
|
1330
|
+
async provisionAppendedExistingWorktrees(sessionId, workItemIds) {
|
|
1331
|
+
if (workItemIds.length === 0)
|
|
1332
|
+
return;
|
|
1333
|
+
const resources = this.ensureResources(sessionId);
|
|
1334
|
+
const instanceKeyPrefix = `a${(0, crypto_1.randomUUID)().slice(0, 8)}-`;
|
|
1335
|
+
const paths = await withProvisionTimeout(this.worktrees.createWorktrees(sessionId, workItemIds.length, instanceKeyPrefix), this.provisionTimeoutMs);
|
|
1336
|
+
workItemIds.forEach((id, i) => {
|
|
1337
|
+
resources.worktrees.set(id, { workItemId: id, worktreePath: paths[i] });
|
|
1338
|
+
});
|
|
1339
|
+
}
|
|
1340
|
+
/**
|
|
1341
|
+
* Provision worktrees for a code_gen Vary forked from a parent variant.
|
|
1342
|
+
* Copies the parent's worktree tree so each sibling starts from the parent's
|
|
1343
|
+
* diff rather than git HEAD / Original.
|
|
1344
|
+
*/
|
|
1345
|
+
async provisionForkedFromVariantWorktrees(sessionId, sourceVariantId, workItemIds) {
|
|
1346
|
+
if (workItemIds.length === 0)
|
|
1347
|
+
return;
|
|
1348
|
+
const sourcePath = this.getWorktreePath(sessionId, sourceVariantId);
|
|
1349
|
+
if (!sourcePath) {
|
|
1350
|
+
throw new errors_1.AgentVariantsError('VARIANT_NOT_EDITABLE', `Cannot fork from variant ${sourceVariantId}: no worktree path`);
|
|
1351
|
+
}
|
|
1352
|
+
if (!this.worktrees.forkWorktreesFrom) {
|
|
1353
|
+
throw new errors_1.AgentVariantsError('RUNTIME_VALIDATION_FAILED', 'worktreeManager does not implement forkWorktreesFrom');
|
|
1354
|
+
}
|
|
1355
|
+
const resources = this.ensureResources(sessionId);
|
|
1356
|
+
const instanceKeyPrefix = `f${(0, crypto_1.randomUUID)().slice(0, 8)}-`;
|
|
1357
|
+
const paths = await withProvisionTimeout(this.worktrees.forkWorktreesFrom(sourcePath, sessionId, workItemIds.length, instanceKeyPrefix), this.provisionTimeoutMs);
|
|
1358
|
+
workItemIds.forEach((id, i) => {
|
|
1359
|
+
resources.worktrees.set(id, { workItemId: id, worktreePath: paths[i] });
|
|
1360
|
+
});
|
|
1361
|
+
}
|
|
1095
1362
|
async reportComplete(args) {
|
|
1096
1363
|
// Cooperative abort short-circuit: if the work item was removed/cancelled
|
|
1097
1364
|
// while in flight (delete-while-loading, cancel_variant, session cancel),
|
|
@@ -1129,10 +1396,11 @@ class AgentVariantsOrchestrator {
|
|
|
1129
1396
|
const qaEval = workItemKind === 'refine_variant'
|
|
1130
1397
|
? null
|
|
1131
1398
|
: await this.evaluateQaForReport(args);
|
|
1132
|
-
// A `retry` verdict re-leases the variant for one
|
|
1133
|
-
// instead of recording success —
|
|
1399
|
+
// A `retry` verdict re-leases the variant for one regeneration (the
|
|
1400
|
+
// existing-project diff task-fit gate) instead of recording success —
|
|
1401
|
+
// short-circuit the normal record path.
|
|
1134
1402
|
if (qaEval?.decision === 'retry') {
|
|
1135
|
-
return this.
|
|
1403
|
+
return this.handleQaRegenRetry(args, qaEval.qa);
|
|
1136
1404
|
}
|
|
1137
1405
|
let effectiveArgs = qaEval?.decision === 'fail_terminal' ? qaEval.overrideArgs : args;
|
|
1138
1406
|
// For a successful refine_variant, swap the refined static preview into
|
|
@@ -2227,11 +2495,9 @@ class AgentVariantsOrchestrator {
|
|
|
2227
2495
|
}
|
|
2228
2496
|
return null;
|
|
2229
2497
|
}
|
|
2230
|
-
// Resolve the objective base QA
|
|
2231
|
-
// critique render target for this deliverable kind.
|
|
2498
|
+
// Resolve the objective base QA for this deliverable kind.
|
|
2232
2499
|
let baseQa = null;
|
|
2233
|
-
|
|
2234
|
-
let checkedSource;
|
|
2500
|
+
const checkedSource = undefined;
|
|
2235
2501
|
if (kind === 'static_preview') {
|
|
2236
2502
|
const parsed = parseStaticPreviewOutput(normalizeOutput(args.output));
|
|
2237
2503
|
if (!parsed)
|
|
@@ -2241,22 +2507,16 @@ class AgentVariantsOrchestrator {
|
|
|
2241
2507
|
projectContext,
|
|
2242
2508
|
});
|
|
2243
2509
|
baseQa = await this.runPreviewQaSafely(args, parsed.html, assetBase);
|
|
2244
|
-
critiqueTarget = {
|
|
2245
|
-
kind: 'html',
|
|
2246
|
-
html: buildStaticPreviewDocument({ html: parsed.html }),
|
|
2247
|
-
...(assetBase ? { assetBase } : {}),
|
|
2248
|
-
};
|
|
2249
2510
|
}
|
|
2250
2511
|
else if (kind === 'code_gen') {
|
|
2251
|
-
// Fresh vite_app variants run agent-authored dev-server code.
|
|
2252
|
-
//
|
|
2512
|
+
// Fresh vite_app variants run agent-authored dev-server code. No
|
|
2513
|
+
// preview document to inspect — behave exactly as before.
|
|
2253
2514
|
return null;
|
|
2254
2515
|
}
|
|
2255
2516
|
else {
|
|
2256
2517
|
return null;
|
|
2257
2518
|
}
|
|
2258
|
-
// Objective base QA failure is terminal exactly as before
|
|
2259
|
-
// aesthetic critique on a variant that's already broken.
|
|
2519
|
+
// Objective base QA failure is terminal exactly as before.
|
|
2260
2520
|
if (baseQa && baseQa.status === 'failed') {
|
|
2261
2521
|
this.memoizeQa(args.sessionId, args.workItemId, baseQa);
|
|
2262
2522
|
return {
|
|
@@ -2276,54 +2536,8 @@ class AgentVariantsOrchestrator {
|
|
|
2276
2536
|
summary: 'Preview QA passed — dev server reachable.',
|
|
2277
2537
|
checkedSource,
|
|
2278
2538
|
};
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
this.memoizeQa(args.sessionId, args.workItemId, passedBase);
|
|
2282
|
-
return { decision: 'pass', qa: passedBase };
|
|
2283
|
-
}
|
|
2284
|
-
const outcome = await this.runDesignCritiqueSafely({
|
|
2285
|
-
sessionId: args.sessionId,
|
|
2286
|
-
workItemId: args.workItemId,
|
|
2287
|
-
target: critiqueTarget,
|
|
2288
|
-
});
|
|
2289
|
-
// Critique skipped (render/model unavailable) → non-blocking, keep base.
|
|
2290
|
-
if (!outcome.ran) {
|
|
2291
|
-
this.memoizeQa(args.sessionId, args.workItemId, passedBase);
|
|
2292
|
-
return { decision: 'pass', qa: passedBase };
|
|
2293
|
-
}
|
|
2294
|
-
if (outcome.passed) {
|
|
2295
|
-
const qa = attachDesignScores(passedBase, outcome);
|
|
2296
|
-
this.memoizeQa(args.sessionId, args.workItemId, qa);
|
|
2297
|
-
return { decision: 'pass', qa };
|
|
2298
|
-
}
|
|
2299
|
-
// Below the bar. First failure → re-lease for one regeneration.
|
|
2300
|
-
const resources = this.ensureResources(args.sessionId);
|
|
2301
|
-
if (!resources.designCritiqueRetried.has(args.workItemId)) {
|
|
2302
|
-
const qa = buildDesignFailureQa(passedBase, outcome);
|
|
2303
|
-
return { decision: 'retry', qa };
|
|
2304
|
-
}
|
|
2305
|
-
// Retry already spent. A `critical` finding is a hard gate: an unusable
|
|
2306
|
-
// variant must fail terminally (commit disabled) rather than degrade into
|
|
2307
|
-
// a committable accept — a high vibe/brand score can never carry it past a
|
|
2308
|
-
// usability-breaking defect. Non-critical (score- or major-driven) failures
|
|
2309
|
-
// degrade to committable so a single fixable flaw doesn't reject a
|
|
2310
|
-
// promising 0→1 direction forever.
|
|
2311
|
-
if (outcome.blocked) {
|
|
2312
|
-
const qa = buildDesignFailureQa(passedBase, outcome);
|
|
2313
|
-
this.memoizeQa(args.sessionId, args.workItemId, qa);
|
|
2314
|
-
return {
|
|
2315
|
-
decision: 'fail_terminal',
|
|
2316
|
-
qa,
|
|
2317
|
-
overrideArgs: {
|
|
2318
|
-
...args,
|
|
2319
|
-
status: 'failed',
|
|
2320
|
-
error: { code: 'VARIANT_QA_FAILED', message: qa.summary },
|
|
2321
|
-
},
|
|
2322
|
-
};
|
|
2323
|
-
}
|
|
2324
|
-
const qa = buildDegradedDesignQa(passedBase, outcome);
|
|
2325
|
-
this.memoizeQa(args.sessionId, args.workItemId, qa);
|
|
2326
|
-
return { decision: 'degrade', qa };
|
|
2539
|
+
this.memoizeQa(args.sessionId, args.workItemId, passedBase);
|
|
2540
|
+
return { decision: 'pass', qa: passedBase };
|
|
2327
2541
|
}
|
|
2328
2542
|
async evaluateExistingCodeGenQa(args) {
|
|
2329
2543
|
const input = this.store.getWorkItemInput(args.sessionId, args.workItemId);
|
|
@@ -2425,6 +2639,10 @@ class AgentVariantsOrchestrator {
|
|
|
2425
2639
|
],
|
|
2426
2640
|
summary: 'No generated content was added for this design variant; regenerate it with real homepage UI changes.',
|
|
2427
2641
|
};
|
|
2642
|
+
const sessionResources = this.ensureResources(args.sessionId);
|
|
2643
|
+
if (!sessionResources.qaRegenRetried.has(args.workItemId)) {
|
|
2644
|
+
return { decision: 'retry', qa };
|
|
2645
|
+
}
|
|
2428
2646
|
this.memoizeQa(args.sessionId, args.workItemId, qa);
|
|
2429
2647
|
return {
|
|
2430
2648
|
decision: 'fail_terminal',
|
|
@@ -2458,93 +2676,33 @@ class AgentVariantsOrchestrator {
|
|
|
2458
2676
|
};
|
|
2459
2677
|
}
|
|
2460
2678
|
}
|
|
2461
|
-
/** Run the injected design critique runner; a thrown runner degrades to a
|
|
2462
|
-
* non-blocking skip so the aesthetic layer can never break the gate. */
|
|
2463
|
-
async runDesignCritiqueSafely(args) {
|
|
2464
|
-
try {
|
|
2465
|
-
const designContextMarkdown = this.resolveBoundDesignMarkdown(args.sessionId, args.workItemId);
|
|
2466
|
-
const designContract = this.resolveDesignContract(args.sessionId, args.workItemId);
|
|
2467
|
-
return await this.designCritiqueRunner({
|
|
2468
|
-
sessionId: args.sessionId,
|
|
2469
|
-
workItemId: args.workItemId,
|
|
2470
|
-
target: args.target,
|
|
2471
|
-
designContextMarkdown,
|
|
2472
|
-
designContract,
|
|
2473
|
-
});
|
|
2474
|
-
}
|
|
2475
|
-
catch (err) {
|
|
2476
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
2477
|
-
log.warn(`Design critique runner threw for ${args.sessionId}/${args.workItemId}: ${message}`);
|
|
2478
|
-
return {
|
|
2479
|
-
ran: false,
|
|
2480
|
-
passed: true,
|
|
2481
|
-
blocked: false,
|
|
2482
|
-
findings: [],
|
|
2483
|
-
scoreCaps: [],
|
|
2484
|
-
skippedReason: `runner: ${message}`,
|
|
2485
|
-
};
|
|
2486
|
-
}
|
|
2487
|
-
}
|
|
2488
|
-
/** Resolve the DESIGN.md markdown bound to a variant work item, if any. */
|
|
2489
|
-
resolveBoundDesignMarkdown(sessionId, workItemId) {
|
|
2490
|
-
try {
|
|
2491
|
-
const input = this.store.getWorkItemInput(sessionId, workItemId);
|
|
2492
|
-
return resolveDesignArtifact(input.designContextEntry)?.markdown;
|
|
2493
|
-
}
|
|
2494
|
-
catch {
|
|
2495
|
-
return undefined;
|
|
2496
|
-
}
|
|
2497
|
-
}
|
|
2498
|
-
/**
|
|
2499
|
-
* Derive the task contract (artifact type + explicit requirements) for a
|
|
2500
|
-
* variant from the session prompt and the variant's brief body, so the
|
|
2501
|
-
* critique judges the variant against what was actually requested. Returns
|
|
2502
|
-
* undefined when no request copy is available.
|
|
2503
|
-
*/
|
|
2504
|
-
resolveDesignContract(sessionId, workItemId) {
|
|
2505
|
-
try {
|
|
2506
|
-
const prompt = this.store.getPrompt(sessionId);
|
|
2507
|
-
const input = this.store.getWorkItemInput(sessionId, workItemId);
|
|
2508
|
-
const contract = (0, designCritique_1.buildDesignContract)({
|
|
2509
|
-
prompt,
|
|
2510
|
-
brief: input.briefBody,
|
|
2511
|
-
});
|
|
2512
|
-
if (contract.requirements.length === 0)
|
|
2513
|
-
return undefined;
|
|
2514
|
-
return contract;
|
|
2515
|
-
}
|
|
2516
|
-
catch {
|
|
2517
|
-
return undefined;
|
|
2518
|
-
}
|
|
2519
|
-
}
|
|
2520
2679
|
memoizeQa(sessionId, workItemId, qa) {
|
|
2521
2680
|
this.ensureResources(sessionId).qaResults.set(workItemId, qa);
|
|
2522
2681
|
}
|
|
2523
2682
|
/**
|
|
2524
2683
|
* Handle a `retry` QA verdict: consume the agent's report, re-open the
|
|
2525
|
-
* variant work item so it can be re-leased and regenerated against the
|
|
2526
|
-
*
|
|
2684
|
+
* variant work item so it can be re-leased and regenerated against the QA
|
|
2685
|
+
* notes, and record that it has now used its single retry.
|
|
2527
2686
|
*/
|
|
2528
|
-
async
|
|
2687
|
+
async handleQaRegenRetry(args, qa) {
|
|
2529
2688
|
const resources = this.ensureResources(args.sessionId);
|
|
2530
|
-
const result = this.store.
|
|
2689
|
+
const result = this.store.requeueForRegeneration({
|
|
2531
2690
|
sessionId: args.sessionId,
|
|
2532
2691
|
workItemId: args.workItemId,
|
|
2533
2692
|
leaseId: args.leaseId,
|
|
2534
2693
|
attempt: args.attempt,
|
|
2535
|
-
|
|
2536
|
-
summary:
|
|
2537
|
-
dimensionScores: qa.dimensionScores,
|
|
2694
|
+
qaRetry: {
|
|
2695
|
+
summary: qa.summary,
|
|
2538
2696
|
},
|
|
2539
2697
|
});
|
|
2540
|
-
resources.
|
|
2698
|
+
resources.qaRegenRetried.add(args.workItemId);
|
|
2541
2699
|
resources.qaResults.delete(args.workItemId);
|
|
2542
2700
|
this.telemetry.track('agent_variants.variant_design_requeued', {
|
|
2543
2701
|
source: 'mcp',
|
|
2544
2702
|
sessionId: args.sessionId,
|
|
2545
2703
|
workItemId: args.workItemId,
|
|
2546
2704
|
attempt: args.attempt,
|
|
2547
|
-
overall:
|
|
2705
|
+
overall: null,
|
|
2548
2706
|
});
|
|
2549
2707
|
this.emitChange();
|
|
2550
2708
|
// Carry the requeue signal + critique so the tool layer can tell the agent
|
|
@@ -3872,7 +4030,7 @@ class AgentVariantsOrchestrator {
|
|
|
3872
4030
|
startedAt: Date.now(),
|
|
3873
4031
|
leasedAt: new Map(),
|
|
3874
4032
|
qaResults: new Map(),
|
|
3875
|
-
|
|
4033
|
+
qaRegenRetried: new Set(),
|
|
3876
4034
|
previewFailures: new Map(),
|
|
3877
4035
|
vitePreservedSiblings: false,
|
|
3878
4036
|
};
|
|
@@ -4855,13 +5013,21 @@ const buildExistingProjectDevCommand = (packageManager, devCommand, port) => {
|
|
|
4855
5013
|
if (packageManager === 'npm') {
|
|
4856
5014
|
return {
|
|
4857
5015
|
cmd: 'npm',
|
|
4858
|
-
args: [
|
|
5016
|
+
args: [
|
|
5017
|
+
'run',
|
|
5018
|
+
devCommand,
|
|
5019
|
+
'--',
|
|
5020
|
+
'--port',
|
|
5021
|
+
String(port),
|
|
5022
|
+
'--host',
|
|
5023
|
+
FRESH_DEV_SERVER_HOST,
|
|
5024
|
+
],
|
|
4859
5025
|
env,
|
|
4860
5026
|
};
|
|
4861
5027
|
}
|
|
4862
5028
|
return {
|
|
4863
5029
|
cmd: packageManager,
|
|
4864
|
-
args: [devCommand, '--port', String(port)],
|
|
5030
|
+
args: [devCommand, '--port', String(port), '--host', FRESH_DEV_SERVER_HOST],
|
|
4865
5031
|
env,
|
|
4866
5032
|
};
|
|
4867
5033
|
};
|