rivet-design 0.11.5 → 0.11.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/mcp/agent-variants/SessionStore.d.ts +39 -9
- package/dist/mcp/agent-variants/SessionStore.d.ts.map +1 -1
- package/dist/mcp/agent-variants/SessionStore.js +36 -16
- package/dist/mcp/agent-variants/SessionStore.js.map +1 -1
- package/dist/mcp/agent-variants/WorktreeOrchestrator.d.ts +1 -1
- package/dist/mcp/agent-variants/WorktreeOrchestrator.d.ts.map +1 -1
- package/dist/mcp/agent-variants/WorktreeOrchestrator.js +111 -63
- package/dist/mcp/agent-variants/WorktreeOrchestrator.js.map +1 -1
- package/dist/mcp/agent-variants/contracts.d.ts +18 -2
- package/dist/mcp/agent-variants/contracts.d.ts.map +1 -1
- package/dist/mcp/agent-variants/contracts.js +6 -3
- package/dist/mcp/agent-variants/contracts.js.map +1 -1
- package/dist/mcp/agent-variants/createZeroToOneTool.d.ts.map +1 -1
- package/dist/mcp/agent-variants/createZeroToOneTool.js +26 -1
- package/dist/mcp/agent-variants/createZeroToOneTool.js.map +1 -1
- package/dist/mcp/agent-variants/designCritique.d.ts.map +1 -1
- package/dist/mcp/agent-variants/designCritique.js +57 -0
- package/dist/mcp/agent-variants/designCritique.js.map +1 -1
- package/dist/mcp/agent-variants/tools.d.ts +7 -0
- package/dist/mcp/agent-variants/tools.d.ts.map +1 -1
- package/dist/mcp/agent-variants/tools.js +40 -4
- package/dist/mcp/agent-variants/tools.js.map +1 -1
- package/dist/mcp/agent-variants/variantContext.d.ts +1 -0
- package/dist/mcp/agent-variants/variantContext.d.ts.map +1 -1
- package/dist/mcp/agent-variants/variantContext.js +3 -3
- package/dist/mcp/agent-variants/variantContext.js.map +1 -1
- package/dist/mcp/hostedServer.d.ts.map +1 -1
- package/dist/mcp/hostedServer.js +8 -1
- package/dist/mcp/hostedServer.js.map +1 -1
- package/dist/mcp/instructions.d.ts +16 -0
- package/dist/mcp/instructions.d.ts.map +1 -0
- package/dist/mcp/instructions.js +18 -0
- package/dist/mcp/instructions.js.map +1 -0
- package/dist/mcp/server.d.ts +9 -0
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +28 -5
- package/dist/mcp/server.js.map +1 -1
- package/dist/services/createAgentVariantsOrchestrator.d.ts +8 -0
- package/dist/services/createAgentVariantsOrchestrator.d.ts.map +1 -1
- package/dist/services/createAgentVariantsOrchestrator.js +18 -8
- package/dist/services/createAgentVariantsOrchestrator.js.map +1 -1
- package/package.json +1 -1
- package/src/ui/dist/assets/main-CtRINpOq.css +1 -0
- package/src/ui/dist/assets/main-DM4pFbJk.js +641 -0
- package/src/ui/dist/index.html +2 -2
- package/src/ui/dist/assets/main-E5hevKg2.css +0 -1
- package/src/ui/dist/assets/main-SvoqOpMt.js +0 -641
|
@@ -42,7 +42,7 @@ const DESIGN_CONTEXT_VIEW_SEGMENT = 'view';
|
|
|
42
42
|
// fast and legibly instead. Per-git-op hangs are already caught by simple-git's
|
|
43
43
|
// block timeout in WorktreeManager; this is the cross-op backstop. Override via
|
|
44
44
|
// env for unusually large repos.
|
|
45
|
-
const DEFAULT_PROVISION_TIMEOUT_MS =
|
|
45
|
+
const DEFAULT_PROVISION_TIMEOUT_MS = 540_000;
|
|
46
46
|
const PROVISION_TIMEOUT_MS = (() => {
|
|
47
47
|
const raw = process.env.RIVET_VARIANTS_PROVISION_TIMEOUT_MS;
|
|
48
48
|
const parsed = raw ? Number.parseInt(raw, 10) : NaN;
|
|
@@ -188,10 +188,18 @@ function copyAssetIntoWorktree(worktreePath, entry, assetSourceRoot) {
|
|
|
188
188
|
if (!path_1.default.isAbsolute(assetSourceRoot)) {
|
|
189
189
|
throw new errors_1.AgentVariantsError('RUNTIME_VALIDATION_FAILED', `assetSourceRoot must be an absolute path, got '${assetSourceRoot}'`);
|
|
190
190
|
}
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
191
|
+
// A relative source is resolved against the approved asset root. The
|
|
192
|
+
// agent-facing protocol requires the source to live under that root but not
|
|
193
|
+
// to be absolute, and the root path is internal to Rivet (a staged context
|
|
194
|
+
// bundle) — so a relative name such as 'avatar.glb' is the natural, safe
|
|
195
|
+
// form. Every downstream guard (symlink, extension, within-root, sensitive
|
|
196
|
+
// segments) runs against this resolved absolute path, so the resolution
|
|
197
|
+
// cannot widen what is copyable: a relative `..` escape still fails the
|
|
198
|
+
// within-root check below.
|
|
199
|
+
const absoluteSource = path_1.default.isAbsolute(entry.source)
|
|
200
|
+
? entry.source
|
|
201
|
+
: path_1.default.resolve(assetSourceRoot, entry.source);
|
|
202
|
+
const ext = path_1.default.extname(absoluteSource).toLowerCase();
|
|
195
203
|
if (!ALLOWED_ASSET_EXTENSIONS.has(ext)) {
|
|
196
204
|
throw new errors_1.AgentVariantsError('RUNTIME_VALIDATION_FAILED', `assetPlan.source extension '${ext || '(none)'}' is not on the allowlist (got '${entry.source}'). Only inert media/font/document assets may be copied.`);
|
|
197
205
|
}
|
|
@@ -201,7 +209,7 @@ function copyAssetIntoWorktree(worktreePath, entry, assetSourceRoot) {
|
|
|
201
209
|
// assetPlan entry is to name a concrete file.
|
|
202
210
|
let lstat;
|
|
203
211
|
try {
|
|
204
|
-
lstat = fs_1.default.lstatSync(
|
|
212
|
+
lstat = fs_1.default.lstatSync(absoluteSource);
|
|
205
213
|
}
|
|
206
214
|
catch {
|
|
207
215
|
throw new errors_1.AgentVariantsError('RUNTIME_VALIDATION_FAILED', `assetPlan.source not found on disk: ${entry.source}`);
|
|
@@ -217,7 +225,7 @@ function copyAssetIntoWorktree(worktreePath, entry, assetSourceRoot) {
|
|
|
217
225
|
// file kind on the resolved path.
|
|
218
226
|
let resolvedSource;
|
|
219
227
|
try {
|
|
220
|
-
resolvedSource = fs_1.default.realpathSync(
|
|
228
|
+
resolvedSource = fs_1.default.realpathSync(absoluteSource);
|
|
221
229
|
}
|
|
222
230
|
catch {
|
|
223
231
|
throw new errors_1.AgentVariantsError('RUNTIME_VALIDATION_FAILED', `assetPlan.source could not be resolved: ${entry.source}`);
|
|
@@ -233,21 +241,28 @@ function copyAssetIntoWorktree(worktreePath, entry, assetSourceRoot) {
|
|
|
233
241
|
if (!resolvedStat.isFile()) {
|
|
234
242
|
throw new errors_1.AgentVariantsError('RUNTIME_VALIDATION_FAILED', `assetPlan.source resolved target must be a regular file: ${entry.source}`);
|
|
235
243
|
}
|
|
236
|
-
//
|
|
237
|
-
//
|
|
238
|
-
//
|
|
239
|
-
//
|
|
240
|
-
//
|
|
241
|
-
//
|
|
242
|
-
//
|
|
243
|
-
//
|
|
244
|
-
if (hasSensitivePathSegment(entry.source) ||
|
|
245
|
-
hasSensitivePathSegment(resolvedSource)) {
|
|
246
|
-
throw new errors_1.AgentVariantsError('RUNTIME_VALIDATION_FAILED', `assetPlan.source traverses a sensitive directory and is refused: ${entry.source}`);
|
|
247
|
-
}
|
|
244
|
+
// Containment is the security boundary and must be checked before any
|
|
245
|
+
// sensitive-segment scan. The approved asset root is system-chosen and
|
|
246
|
+
// trusted (a staged context bundle), and its realpath can legitimately
|
|
247
|
+
// pass through a "sensitive" segment that is NOT agent-controlled — on
|
|
248
|
+
// macOS `fs.realpathSync(os.tmpdir())` resolves to `/private/var/folders/...`,
|
|
249
|
+
// so the temp-staged root contains `private`. A symlinked intermediate dir
|
|
250
|
+
// that forwards into a sensitive ancestor (e.g. `<asset-root>/avatar -> ~/.ssh`)
|
|
251
|
+
// pushes the realpath OUTSIDE the root and is rejected here.
|
|
248
252
|
if (!isPathWithinRoot(resolvedSource, resolvedAssetSourceRoot)) {
|
|
249
253
|
throw new errors_1.AgentVariantsError('RUNTIME_VALIDATION_FAILED', `assetPlan.source must stay inside the approved asset root: ${entry.source}`);
|
|
250
254
|
}
|
|
255
|
+
// Sensitive-segment defense, scoped to the agent-controlled portion BELOW
|
|
256
|
+
// the trusted root. Only this relative remainder is chosen by the agent's
|
|
257
|
+
// source plan, so a sensitive segment it introduces under the root (a real
|
|
258
|
+
// `<asset-root>/.ssh/key.glb`, or a within-root symlink that realpaths into
|
|
259
|
+
// one) is still refused, while the trusted root prefix is never scanned.
|
|
260
|
+
// `resolvedSource` is the fully symlink-resolved real path, so the scan
|
|
261
|
+
// covers what the file actually resolves to, not just its literal name.
|
|
262
|
+
const relativeResolvedSource = path_1.default.relative(resolvedAssetSourceRoot, resolvedSource);
|
|
263
|
+
if (hasSensitivePathSegment(relativeResolvedSource)) {
|
|
264
|
+
throw new errors_1.AgentVariantsError('RUNTIME_VALIDATION_FAILED', `assetPlan.source traverses a sensitive directory and is refused: ${entry.source}`);
|
|
265
|
+
}
|
|
251
266
|
const resolvedExt = path_1.default.extname(resolvedSource).toLowerCase();
|
|
252
267
|
if (!ALLOWED_ASSET_EXTENSIONS.has(resolvedExt)) {
|
|
253
268
|
throw new errors_1.AgentVariantsError('RUNTIME_VALIDATION_FAILED', `assetPlan.source resolved extension '${resolvedExt || '(none)'}' is not on the allowlist (resolved from '${entry.source}').`);
|
|
@@ -913,49 +928,16 @@ class AgentVariantsOrchestrator {
|
|
|
913
928
|
return undefined;
|
|
914
929
|
if (!requestedPath || requestedPath.length === 0)
|
|
915
930
|
return undefined;
|
|
916
|
-
//
|
|
917
|
-
//
|
|
918
|
-
//
|
|
919
|
-
//
|
|
920
|
-
//
|
|
921
|
-
//
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
const target = path_1.default.resolve(record.assetBase, '.' + path_1.default.sep + requestedPath);
|
|
927
|
-
if (target !== record.assetBase &&
|
|
928
|
-
!target.startsWith(record.assetBase + path_1.default.sep)) {
|
|
929
|
-
return undefined;
|
|
930
|
-
}
|
|
931
|
-
let realTarget;
|
|
932
|
-
try {
|
|
933
|
-
realTarget = fs_1.default.realpathSync(target);
|
|
934
|
-
}
|
|
935
|
-
catch {
|
|
936
|
-
return undefined;
|
|
937
|
-
}
|
|
938
|
-
if (realTarget !== record.assetBase &&
|
|
939
|
-
!realTarget.startsWith(record.assetBase + path_1.default.sep)) {
|
|
940
|
-
return undefined;
|
|
941
|
-
}
|
|
942
|
-
try {
|
|
943
|
-
const stat = fs_1.default.statSync(realTarget);
|
|
944
|
-
if (!stat.isFile())
|
|
945
|
-
return undefined;
|
|
946
|
-
// Hardlinks aren't dereferenced by `realpath` — they ARE the file from
|
|
947
|
-
// a path-resolution perspective, so the realpath check above can't
|
|
948
|
-
// detect them. An agent could `ln /path/to/.env assetBase/x.glb`
|
|
949
|
-
// (hardlink, not symlink) and the resolver would happily serve the
|
|
950
|
-
// secret. Reject anything with multiple links — legitimate
|
|
951
|
-
// agent-generated assets always have nlink === 1.
|
|
952
|
-
if (stat.nlink !== 1)
|
|
953
|
-
return undefined;
|
|
954
|
-
return realTarget;
|
|
955
|
-
}
|
|
956
|
-
catch {
|
|
957
|
-
return undefined;
|
|
958
|
-
}
|
|
931
|
+
// Resolve against the variant's own directory first so any asset the agent
|
|
932
|
+
// actually wrote/modified wins. Only when the worktree clone lacks the file
|
|
933
|
+
// do we fall back to the host project root — that recovers git-ignored or
|
|
934
|
+
// binary assets the worktree provisioning couldn't carry over, which the
|
|
935
|
+
// original preview already serves (see `assetFallbackBase`). Both roots are
|
|
936
|
+
// held to the identical traversal/realpath/hardlink sandbox.
|
|
937
|
+
return (resolveAssetWithinBase(record.assetBase, requestedPath) ??
|
|
938
|
+
(record.assetFallbackBase
|
|
939
|
+
? resolveAssetWithinBase(record.assetFallbackBase, requestedPath)
|
|
940
|
+
: undefined));
|
|
959
941
|
}
|
|
960
942
|
getStaticPreviewByBriefId(sessionId, briefId) {
|
|
961
943
|
const resources = this.resources.get(sessionId);
|
|
@@ -2565,7 +2547,12 @@ class AgentVariantsOrchestrator {
|
|
|
2565
2547
|
overall: qa.dimensionScores?.overall ?? null,
|
|
2566
2548
|
});
|
|
2567
2549
|
this.emitChange();
|
|
2568
|
-
|
|
2550
|
+
// Carry the requeue signal + critique so the tool layer can tell the agent
|
|
2551
|
+
// to re-lease and regenerate. The store result is an opaque
|
|
2552
|
+
// `waiting_for_results` (the variant is pending again); on its own that is
|
|
2553
|
+
// indistinguishable from a sibling still in flight, so the agent would
|
|
2554
|
+
// otherwise abandon the variant and the session would stall at ready:0.
|
|
2555
|
+
return { ...result, requeuedForRegeneration: true, qa };
|
|
2569
2556
|
}
|
|
2570
2557
|
async startVariantDevServer(args) {
|
|
2571
2558
|
if (args.record.port !== undefined && args.record.devServerProcess) {
|
|
@@ -2992,6 +2979,22 @@ class AgentVariantsOrchestrator {
|
|
|
2992
2979
|
// sandbox symlink-normalization is skipped.
|
|
2993
2980
|
dir = projectCwd;
|
|
2994
2981
|
}
|
|
2982
|
+
// Host project root, used as a read-only fallback for assets the worktree
|
|
2983
|
+
// clone is missing (git-ignored or binary files that `git worktree add` +
|
|
2984
|
+
// text-patch + untracked-copy can't carry). Without this the original
|
|
2985
|
+
// preview shows such an asset while the variant 404s. Realpath'd so the
|
|
2986
|
+
// sandbox containment check in `resolveAssetWithinBase` lines up.
|
|
2987
|
+
let assetFallbackBase;
|
|
2988
|
+
try {
|
|
2989
|
+
const hostRealRoot = fs_1.default.realpathSync(env.projectPath);
|
|
2990
|
+
// Skip the fallback if it would collapse onto the worktree root (can't
|
|
2991
|
+
// happen in practice, but keeps the two roots genuinely distinct).
|
|
2992
|
+
if (hostRealRoot !== dir)
|
|
2993
|
+
assetFallbackBase = hostRealRoot;
|
|
2994
|
+
}
|
|
2995
|
+
catch {
|
|
2996
|
+
// No host root resolvable — serve worktree assets only.
|
|
2997
|
+
}
|
|
2995
2998
|
resources.staticPreviews.set(workItemId, {
|
|
2996
2999
|
workItemId,
|
|
2997
3000
|
briefId: input.briefId,
|
|
@@ -2999,6 +3002,7 @@ class AgentVariantsOrchestrator {
|
|
|
2999
3002
|
// Existing-mode static projects are real multi-file sites — assume
|
|
3000
3003
|
// sibling assets so the preview route resolves relative URLs.
|
|
3001
3004
|
hasAssets: true,
|
|
3005
|
+
...(assetFallbackBase ? { assetFallbackBase } : {}),
|
|
3002
3006
|
});
|
|
3003
3007
|
log.info(`Variant ${workItemId} served as a static preview (existing/static, no dev server; root ${projectCwd})`);
|
|
3004
3008
|
return true;
|
|
@@ -3840,6 +3844,50 @@ function isStrictlyInside(child, parent) {
|
|
|
3840
3844
|
const p = path_1.default.resolve(parent);
|
|
3841
3845
|
return c !== p && c.startsWith(p + path_1.default.sep);
|
|
3842
3846
|
}
|
|
3847
|
+
/**
|
|
3848
|
+
* Resolve `requestedPath` to an absolute, sandboxed file under `base`, or
|
|
3849
|
+
* `undefined` if it escapes the base, doesn't exist, isn't a regular file, or
|
|
3850
|
+
* is a hardlink. Shared by `resolveStaticPreviewAssetPath` across the variant's
|
|
3851
|
+
* own directory and the host-root fallback so both get identical containment.
|
|
3852
|
+
*
|
|
3853
|
+
* `base` is expected to be realpath'd by the caller. The requested target is
|
|
3854
|
+
* realpath'd here too — without it, a symlink planted under the base (e.g. via
|
|
3855
|
+
* a follow-up Write tool call) could pivot outside the sandbox. `requestedPath`
|
|
3856
|
+
* is used as Express delivered it (already URL-decoded); decoding again would
|
|
3857
|
+
* mangle filenames that legitimately contain `%` (e.g. `100%25.png` on disk).
|
|
3858
|
+
*/
|
|
3859
|
+
function resolveAssetWithinBase(base, requestedPath) {
|
|
3860
|
+
const target = path_1.default.resolve(base, '.' + path_1.default.sep + requestedPath);
|
|
3861
|
+
if (target !== base && !target.startsWith(base + path_1.default.sep)) {
|
|
3862
|
+
return undefined;
|
|
3863
|
+
}
|
|
3864
|
+
let realTarget;
|
|
3865
|
+
try {
|
|
3866
|
+
realTarget = fs_1.default.realpathSync(target);
|
|
3867
|
+
}
|
|
3868
|
+
catch {
|
|
3869
|
+
return undefined;
|
|
3870
|
+
}
|
|
3871
|
+
if (realTarget !== base && !realTarget.startsWith(base + path_1.default.sep)) {
|
|
3872
|
+
return undefined;
|
|
3873
|
+
}
|
|
3874
|
+
try {
|
|
3875
|
+
const stat = fs_1.default.statSync(realTarget);
|
|
3876
|
+
if (!stat.isFile())
|
|
3877
|
+
return undefined;
|
|
3878
|
+
// Hardlinks aren't dereferenced by `realpath` — they ARE the file from a
|
|
3879
|
+
// path-resolution perspective, so the realpath check above can't detect
|
|
3880
|
+
// them. An agent could `ln /path/to/.env base/x.glb` (hardlink, not
|
|
3881
|
+
// symlink) and the resolver would happily serve the secret. Reject
|
|
3882
|
+
// anything with multiple links — legitimate assets always have nlink === 1.
|
|
3883
|
+
if (stat.nlink !== 1)
|
|
3884
|
+
return undefined;
|
|
3885
|
+
return realTarget;
|
|
3886
|
+
}
|
|
3887
|
+
catch {
|
|
3888
|
+
return undefined;
|
|
3889
|
+
}
|
|
3890
|
+
}
|
|
3843
3891
|
/**
|
|
3844
3892
|
* Sandbox the agent's `output.assetBase` to a path inside the session
|
|
3845
3893
|
* workspace. Accepts either an absolute path (used as-is after the
|