feat: Studio Pipeline Hardening — Session Drawer, Gemini v1beta, and Execution Visibility#21
feat: Studio Pipeline Hardening — Session Drawer, Gemini v1beta, and Execution Visibility#21
Conversation
…ults - Add StepPlanData and StepCommandResult types to workflow-timeline.tsx - Extract planning metadata and commandSuggestions from complete event - Display plan execution summary card (success/fail, step count, errors) - Render executed command results with status indicators - Fix workflowMode: autopilot (not studio) for real execution - Switch Gemini to v1beta endpoint + gemini-3.1-pro-preview model
📝 WalkthroughWalkthroughAdds execution planning and per-command results to workflow steps, runs planner+executor in autopilot mode and records planData/commandResults (with a follow-up LLM pass), updates Gemini defaults and embedding endpoints, changes viewport screenshot API to use maxSize with a file fallback, and surfaces Plan Summary / Executed Commands in the UI. Changes
Sequence DiagramsequenceDiagram
participant Studio as Studio Layout
participant Gemini as Gemini API
participant ChatAPI as Chat Route
participant Timeline as Workflow Timeline
participant Drawer as Step Session Drawer
Studio->>Gemini: Start plan+execute (workflowMode: "autopilot")
Gemini-->>Studio: Stream events (deltas, command events, complete, followup_delta)
Studio->>ChatAPI: On complete -> request follow-up LLM summary
ChatAPI-->>Studio: Return follow-up text (or fallback)
Studio->>Timeline: Update step with planData & commandResults (mark done)
Timeline-->>Drawer: Provide updated step
Drawer->>Drawer: Render Plan Summary (if planData)
Drawer->>Drawer: Render Executed Commands (if commandResults)
Estimated Code Review Effort🎯 4 (Complex) | ⏱️ ~45 minutes Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
Tip Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs). Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 6
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@components/projects/step-session-drawer.tsx`:
- Around line 294-297: The cmd.error output in
components/projects/step-session-drawer.tsx is being clipped by the "truncate"
class and max-w style so long failure text is invisible; remove the "truncate"
class and inline max-w style from the span rendering cmd.error and instead allow
wrapping (e.g., use break-words / whitespace-pre-wrap) and add an expansion
affordance: implement a per-command toggle in the StepSessionDrawer render
(track by cmd.id or index) that on click opens the full error either inline as a
<pre>-style block or in a small modal/popover so the complete cmd.error content
is readable and copyable. Ensure the changed span no longer truncates and that
the expansion UI shows the raw cmd.error text.
In `@components/projects/studio-layout.tsx`:
- Around line 163-190: The handler currently always calls updateStep(stepId, {
status: "done", ... }) even when the planning metadata indicates failure; change
it to derive the status from planning.executionSuccess (e.g., set status to
"done" when planning.executionSuccess === true, otherwise set status to "failed"
or an appropriate error state), using the existing planning/planData variables
and leaving other fields (planData, commandResults) unchanged when calling
updateStep.
- Around line 176-184: The mapping of event.commandSuggestions into
commandResults trusts raw values: ensure you normalize and validate fields
before storing by (1) validating status from event.commandSuggestions against
the allowed StepCommandResult["status"] union (e.g., map unknown values to
"pending" or a safe default) when constructing status for each StepCommandResult
and (2) guaranteeing a stable, non-empty unique id for each command (do not use
String(cmd.id ?? "") directly—generate or derive a unique id when cmd.id is
missing/empty, e.g., use a prefixed index or UUID) and also coerce
confidence/description/error safely; update the mapping that produces
commandResults (the rawCommands -> commandResults block) to apply these checks
so drawer row keys and displayed statuses cannot be corrupted by malformed API
payloads.
In `@lib/ai/index.ts`:
- Line 18: DEFAULT_MODEL currently falls back to "gemini-3.1-pro-preview",
causing createGeminiModel (and downstream functions generatePlan, validateStep,
generateRecovery, generateCode) to use the wrong LLM; change the fallback value
so DEFAULT_MODEL = process.env.GEMINI_MODEL ?? "gemini-2.5-pro" (Gemini 2.5 Pro)
so createGeminiModel and all chain functions use Gemini 2.5 Pro by default.
In `@lib/gemini.ts`:
- Line 4: DEFAULT_MODEL is set to "gemini-3.1-pro-preview" but
generateGeminiResponse and streamGeminiResponse expect the repo default to be
Gemini 2.5 Pro; change the DEFAULT_MODEL constant to "gemini-2.5-pro" so both
functions fallback to the correct model, and run tests/lint to ensure no other
places rely on the preview value (update any docstring/comments referencing the
old default if present).
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 4c817bcb-bc73-4f3d-a425-bac529bdf431
📒 Files selected for processing (5)
components/projects/step-session-drawer.tsxcomponents/projects/studio-layout.tsxcomponents/projects/workflow-timeline.tsxlib/ai/index.tslib/gemini.ts
| {/* Plan Summary */} | ||
| {step.planData && ( | ||
| <div | ||
| className="rounded-xl border px-4 py-3 space-y-2" | ||
| style={{ | ||
| borderColor: step.planData.executionSuccess | ||
| ? "hsl(153 60% 53% / 0.3)" | ||
| : "hsl(0 84% 60% / 0.3)", | ||
| backgroundColor: step.planData.executionSuccess | ||
| ? "rgba(52,211,153,0.08)" | ||
| : "rgba(248,113,113,0.08)", | ||
| }} | ||
| > | ||
| <div className="flex items-center justify-between"> | ||
| <span | ||
| className="text-xs font-semibold uppercase tracking-wider" | ||
| style={{ color: "hsl(var(--forge-text-subtle))" }} | ||
| > | ||
| Execution Plan | ||
| </span> | ||
| <span | ||
| className="text-xs font-medium px-2 py-0.5 rounded-full" | ||
| style={{ | ||
| backgroundColor: step.planData.executionSuccess | ||
| ? "rgba(52,211,153,0.2)" | ||
| : "rgba(248,113,113,0.2)", | ||
| color: step.planData.executionSuccess | ||
| ? "hsl(153 60% 53%)" | ||
| : "hsl(0 84% 60%)", | ||
| }} | ||
| > | ||
| {step.planData.executionSuccess ? "✓ Success" : "✕ Failed"} · {step.planData.stepCount} step{step.planData.stepCount !== 1 ? "s" : ""} | ||
| </span> | ||
| </div> | ||
| <p className="text-sm" style={{ color: "hsl(var(--forge-text))" }}> | ||
| {step.planData.planSummary} | ||
| </p> | ||
| {step.planData.errors && step.planData.errors.length > 0 && ( | ||
| <div className="text-xs space-y-1" style={{ color: "hsl(0 84% 60%)" }}> | ||
| {step.planData.errors.map((err, i) => ( | ||
| <p key={i}>⚠ {err}</p> | ||
| ))} | ||
| </div> | ||
| )} | ||
| </div> | ||
| )} | ||
|
|
||
| {/* Command Results */} | ||
| {step.commandResults && step.commandResults.length > 0 && ( | ||
| <div className="space-y-1.5"> | ||
| <span | ||
| className="text-xs font-semibold uppercase tracking-wider" | ||
| style={{ color: "hsl(var(--forge-text-subtle))" }} | ||
| > | ||
| Executed Commands ({step.commandResults.length}) | ||
| </span> | ||
| <div className="space-y-1"> | ||
| {step.commandResults.map((cmd) => ( | ||
| <div | ||
| key={cmd.id} | ||
| className="flex items-center gap-2 rounded-lg px-3 py-2 text-xs font-mono" | ||
| style={{ | ||
| backgroundColor: "hsl(var(--forge-surface-dim))", | ||
| border: "1px solid hsl(var(--forge-border))", | ||
| color: "hsl(var(--forge-text))", | ||
| }} | ||
| > | ||
| <span className="shrink-0"> | ||
| {cmd.status === "executed" ? "✅" : cmd.status === "failed" ? "❌" : "⏳"} | ||
| </span> | ||
| <span className="truncate flex-1">{cmd.tool}</span> | ||
| {cmd.description && ( | ||
| <span | ||
| className="text-[10px] truncate max-w-[40%]" | ||
| style={{ color: "hsl(var(--forge-text-subtle))" }} | ||
| > | ||
| {cmd.description} | ||
| </span> | ||
| )} | ||
| {cmd.error && ( | ||
| <span className="text-[10px] truncate max-w-[40%]" style={{ color: "hsl(0 84% 60%)" }}> | ||
| {cmd.error} | ||
| </span> | ||
| )} | ||
| </div> | ||
| ))} | ||
| </div> | ||
| </div> | ||
| )} |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
Move these new state styles into Tailwind/theme classes.
This block adds a large new batch of inline style props for borders, backgrounds, and text colors. That keeps the drawer harder to theme and is out of line with the component styling rule used elsewhere in the repo.
As per coding guidelines, "Use Tailwind CSS utility classes for styling instead of inline styles or CSS files".
| {cmd.error && ( | ||
| <span className="text-[10px] truncate max-w-[40%]" style={{ color: "hsl(0 84% 60%)" }}> | ||
| {cmd.error} | ||
| </span> |
There was a problem hiding this comment.
Don’t hide command failures behind truncate.
The whole point of this drawer change is execution visibility, but long cmd.error values are cut off with no expansion path. That makes the per-command failure details non-actionable in exactly the scenarios this UI is meant to debug.
Suggested fix
- {cmd.error && (
- <span className="text-[10px] truncate max-w-[40%]" style={{ color: "hsl(0 84% 60%)" }}>
- {cmd.error}
- </span>
- )}
+ {cmd.error && (
+ <span className="text-[10px] max-w-[40%] whitespace-pre-wrap break-words text-red-400">
+ {cmd.error}
+ </span>
+ )}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| {cmd.error && ( | |
| <span className="text-[10px] truncate max-w-[40%]" style={{ color: "hsl(0 84% 60%)" }}> | |
| {cmd.error} | |
| </span> | |
| {cmd.error && ( | |
| <span className="text-[10px] max-w-[40%] whitespace-pre-wrap break-words text-red-400"> | |
| {cmd.error} | |
| </span> | |
| )} |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@components/projects/step-session-drawer.tsx` around lines 294 - 297, The
cmd.error output in components/projects/step-session-drawer.tsx is being clipped
by the "truncate" class and max-w style so long failure text is invisible;
remove the "truncate" class and inline max-w style from the span rendering
cmd.error and instead allow wrapping (e.g., use break-words /
whitespace-pre-wrap) and add an expansion affordance: implement a per-command
toggle in the StepSessionDrawer render (track by cmd.id or index) that on click
opens the full error either inline as a <pre>-style block or in a small
modal/popover so the complete cmd.error content is readable and copyable. Ensure
the changed span no longer truncates and that the expansion UI shows the raw
cmd.error text.
| // Extract plan data from the planning metadata | ||
| const planning = event.planning as Record<string, unknown> | undefined | ||
| let planData: StepPlanData | null = null | ||
| if (planning) { | ||
| planData = { | ||
| planSummary: typeof planning.planSummary === "string" ? planning.planSummary : "No summary", | ||
| stepCount: Array.isArray(planning.planSteps) ? planning.planSteps.length : 0, | ||
| executionSuccess: planning.executionSuccess === true, | ||
| errors: Array.isArray(planning.errors) ? planning.errors as string[] : undefined, | ||
| } | ||
| } | ||
|
|
||
| // Extract command results | ||
| const rawCommands = Array.isArray(event.commandSuggestions) ? event.commandSuggestions as Record<string, unknown>[] : [] | ||
| const commandResults: StepCommandResult[] = rawCommands.map((cmd) => ({ | ||
| id: String(cmd.id ?? ""), | ||
| tool: String(cmd.tool ?? ""), | ||
| status: (cmd.status as StepCommandResult["status"]) ?? "pending", | ||
| confidence: typeof cmd.confidence === "number" ? cmd.confidence : undefined, | ||
| description: typeof cmd.description === "string" ? cmd.description : undefined, | ||
| error: typeof cmd.error === "string" ? cmd.error : undefined, | ||
| })) | ||
|
|
||
| updateStep(stepId, { | ||
| status: "done", | ||
| planData, | ||
| commandResults: commandResults.length > 0 ? commandResults : undefined, | ||
| }) |
There was a problem hiding this comment.
Failed autopilot runs are still being marked as done.
The complete handler always writes status: "done", but the API explicitly sends planning.executionSuccess = false for unsuccessful executions. That means the timeline pill and drawer header can say “Complete” while the new plan summary card says the run failed.
Suggested fix
if (planning) {
planData = {
planSummary: typeof planning.planSummary === "string" ? planning.planSummary : "No summary",
stepCount: Array.isArray(planning.planSteps) ? planning.planSteps.length : 0,
executionSuccess: planning.executionSuccess === true,
errors: Array.isArray(planning.errors) ? planning.errors as string[] : undefined,
}
}
// Extract command results
const rawCommands = Array.isArray(event.commandSuggestions) ? event.commandSuggestions as Record<string, unknown>[] : []
const commandResults: StepCommandResult[] = rawCommands.map((cmd) => ({
id: String(cmd.id ?? ""),
tool: String(cmd.tool ?? ""),
status: (cmd.status as StepCommandResult["status"]) ?? "pending",
confidence: typeof cmd.confidence === "number" ? cmd.confidence : undefined,
description: typeof cmd.description === "string" ? cmd.description : undefined,
error: typeof cmd.error === "string" ? cmd.error : undefined,
}))
+ const hasCommandFailure = commandResults.some((cmd) => cmd.status === "failed")
+ const executionSucceeded = planData?.executionSuccess !== false && !hasCommandFailure
updateStep(stepId, {
- status: "done",
+ status: executionSucceeded ? "done" : "failed",
planData,
commandResults: commandResults.length > 0 ? commandResults : undefined,
+ error: executionSucceeded ? undefined : planData?.errors?.[0],
})🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@components/projects/studio-layout.tsx` around lines 163 - 190, The handler
currently always calls updateStep(stepId, { status: "done", ... }) even when the
planning metadata indicates failure; change it to derive the status from
planning.executionSuccess (e.g., set status to "done" when
planning.executionSuccess === true, otherwise set status to "failed" or an
appropriate error state), using the existing planning/planData variables and
leaving other fields (planData, commandResults) unchanged when calling
updateStep.
| const rawCommands = Array.isArray(event.commandSuggestions) ? event.commandSuggestions as Record<string, unknown>[] : [] | ||
| const commandResults: StepCommandResult[] = rawCommands.map((cmd) => ({ | ||
| id: String(cmd.id ?? ""), | ||
| tool: String(cmd.tool ?? ""), | ||
| status: (cmd.status as StepCommandResult["status"]) ?? "pending", | ||
| confidence: typeof cmd.confidence === "number" ? cmd.confidence : undefined, | ||
| description: typeof cmd.description === "string" ? cmd.description : undefined, | ||
| error: typeof cmd.error === "string" ? cmd.error : undefined, | ||
| })) |
There was a problem hiding this comment.
Normalize commandSuggestions before storing them.
This mapping trusts raw API data in two places: cmd.status as StepCommandResult["status"] accepts arbitrary strings as valid union members, and String(cmd.id ?? "") can produce duplicate empty IDs. The drawer keys rows by cmd.id, so malformed payloads can lead to unstable rendering and misleading statuses.
Suggested fix
- const rawCommands = Array.isArray(event.commandSuggestions) ? event.commandSuggestions as Record<string, unknown>[] : []
- const commandResults: StepCommandResult[] = rawCommands.map((cmd) => ({
- id: String(cmd.id ?? ""),
- tool: String(cmd.tool ?? ""),
- status: (cmd.status as StepCommandResult["status"]) ?? "pending",
- confidence: typeof cmd.confidence === "number" ? cmd.confidence : undefined,
- description: typeof cmd.description === "string" ? cmd.description : undefined,
- error: typeof cmd.error === "string" ? cmd.error : undefined,
- }))
+ const rawCommands = Array.isArray(event.commandSuggestions)
+ ? event.commandSuggestions as Record<string, unknown>[]
+ : []
+ const allowedStatuses: ReadonlySet<StepCommandResult["status"]> = new Set([
+ "pending",
+ "ready",
+ "executed",
+ "failed",
+ ])
+ const commandResults: StepCommandResult[] = rawCommands.map((cmd, index) => {
+ const rawStatus = typeof cmd.status === "string" ? cmd.status : undefined
+ const status: StepCommandResult["status"] =
+ rawStatus && allowedStatuses.has(rawStatus as StepCommandResult["status"])
+ ? rawStatus as StepCommandResult["status"]
+ : "pending"
+
+ return {
+ id: typeof cmd.id === "string" && cmd.id.length > 0 ? cmd.id : `${stepId}-cmd-${index}`,
+ tool: typeof cmd.tool === "string" ? cmd.tool : "unknown",
+ status,
+ confidence: typeof cmd.confidence === "number" ? cmd.confidence : undefined,
+ description: typeof cmd.description === "string" ? cmd.description : undefined,
+ error: typeof cmd.error === "string" ? cmd.error : undefined,
+ }
+ })📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const rawCommands = Array.isArray(event.commandSuggestions) ? event.commandSuggestions as Record<string, unknown>[] : [] | |
| const commandResults: StepCommandResult[] = rawCommands.map((cmd) => ({ | |
| id: String(cmd.id ?? ""), | |
| tool: String(cmd.tool ?? ""), | |
| status: (cmd.status as StepCommandResult["status"]) ?? "pending", | |
| confidence: typeof cmd.confidence === "number" ? cmd.confidence : undefined, | |
| description: typeof cmd.description === "string" ? cmd.description : undefined, | |
| error: typeof cmd.error === "string" ? cmd.error : undefined, | |
| })) | |
| const rawCommands = Array.isArray(event.commandSuggestions) | |
| ? event.commandSuggestions as Record<string, unknown>[] | |
| : [] | |
| const allowedStatuses: ReadonlySet<StepCommandResult["status"]> = new Set([ | |
| "pending", | |
| "ready", | |
| "executed", | |
| "failed", | |
| ]) | |
| const commandResults: StepCommandResult[] = rawCommands.map((cmd, index) => { | |
| const rawStatus = typeof cmd.status === "string" ? cmd.status : undefined | |
| const status: StepCommandResult["status"] = | |
| rawStatus && allowedStatuses.has(rawStatus as StepCommandResult["status"]) | |
| ? rawStatus as StepCommandResult["status"] | |
| : "pending" | |
| return { | |
| id: typeof cmd.id === "string" && cmd.id.length > 0 ? cmd.id : `${stepId}-cmd-${index}`, | |
| tool: typeof cmd.tool === "string" ? cmd.tool : "unknown", | |
| status, | |
| confidence: typeof cmd.confidence === "number" ? cmd.confidence : undefined, | |
| description: typeof cmd.description === "string" ? cmd.description : undefined, | |
| error: typeof cmd.error === "string" ? cmd.error : undefined, | |
| } | |
| }) |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@components/projects/studio-layout.tsx` around lines 176 - 184, The mapping of
event.commandSuggestions into commandResults trusts raw values: ensure you
normalize and validate fields before storing by (1) validating status from
event.commandSuggestions against the allowed StepCommandResult["status"] union
(e.g., map unknown values to "pending" or a safe default) when constructing
status for each StepCommandResult and (2) guaranteeing a stable, non-empty
unique id for each command (do not use String(cmd.id ?? "") directly—generate or
derive a unique id when cmd.id is missing/empty, e.g., use a prefixed index or
UUID) and also coerce confidence/description/error safely; update the mapping
that produces commandResults (the rawCommands -> commandResults block) to apply
these checks so drawer row keys and displayed statuses cannot be corrupted by
malformed API payloads.
| // ============================================================================ | ||
|
|
||
| const DEFAULT_MODEL = process.env.GEMINI_MODEL ?? "gemini-3.1-pro-preview-customtools" | ||
| const DEFAULT_MODEL = process.env.GEMINI_MODEL ?? "gemini-3.1-pro-preview" |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
git ls-files | head -20Repository: Ker102/ModelForge
Length of output: 668
🏁 Script executed:
find . -name "index.ts" -o -name "chains.ts" | grep -E "(lib/ai|app)" | head -10Repository: Ker102/ModelForge
Length of output: 97
🏁 Script executed:
find . -path "*/lib/ai/*" -o -path "*/app/*" | grep -E "\.(ts|tsx|js)$" | head -20Repository: Ker102/ModelForge
Length of output: 744
🏁 Script executed:
cat -n lib/ai/index.tsRepository: Ker102/ModelForge
Length of output: 3909
🏁 Script executed:
cat -n lib/ai/chains.ts 2>/dev/null || echo "File not found, searching for chains..."Repository: Ker102/ModelForge
Length of output: 10318
🏁 Script executed:
fd "chains" --type fRepository: Ker102/ModelForge
Length of output: 77
🏁 Script executed:
rg "createGeminiModel" --type ts -lRepository: Ker102/ModelForge
Length of output: 186
🏁 Script executed:
rg "DEFAULT_MODEL" --type ts -A 2 -B 2Repository: Ker102/ModelForge
Length of output: 1624
🏁 Script executed:
rg "gemini-2.5-pro\|gemini-3.1-pro" --type tsRepository: Ker102/ModelForge
Length of output: 43
🏁 Script executed:
rg "GEMINI_MODEL" --type ts -B 2 -A 2Repository: Ker102/ModelForge
Length of output: 845
🏁 Script executed:
rg "gemini" --type ts | grep -i "pro\|model" | head -20Repository: Ker102/ModelForge
Length of output: 587
🏁 Script executed:
cat lib/ai/index.ts | head -10Repository: Ker102/ModelForge
Length of output: 318
Use Gemini 2.5 Pro as the primary LLM model, not gemini-3.1-pro-preview
The DEFAULT_MODEL fallback at line 18 causes all chain functions (generatePlan, validateStep, generateRecovery, generateCode) to default to gemini-3.1-pro-preview since none of them pass an explicit model parameter to createGeminiModel(). This violates the requirement to use Gemini 2.5 Pro for all AI operations.
Suggested fix
-const DEFAULT_MODEL = process.env.GEMINI_MODEL ?? "gemini-3.1-pro-preview"
+const DEFAULT_MODEL = process.env.GEMINI_MODEL ?? "gemini-2.5-pro"📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const DEFAULT_MODEL = process.env.GEMINI_MODEL ?? "gemini-3.1-pro-preview" | |
| const DEFAULT_MODEL = process.env.GEMINI_MODEL ?? "gemini-2.5-pro" |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@lib/ai/index.ts` at line 18, DEFAULT_MODEL currently falls back to
"gemini-3.1-pro-preview", causing createGeminiModel (and downstream functions
generatePlan, validateStep, generateRecovery, generateCode) to use the wrong
LLM; change the fallback value so DEFAULT_MODEL = process.env.GEMINI_MODEL ??
"gemini-2.5-pro" (Gemini 2.5 Pro) so createGeminiModel and all chain functions
use Gemini 2.5 Pro by default.
| const GEMINI_API_ENDPOINT = "https://generativelanguage.googleapis.com/v1" | ||
| const DEFAULT_MODEL = "gemini-3.1-pro-preview-customtools" | ||
| const GEMINI_API_ENDPOINT = "https://generativelanguage.googleapis.com/v1beta" | ||
| const DEFAULT_MODEL = "gemini-3.1-pro-preview" |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# Check the current state of lib/gemini.ts around the DEFAULT_MODEL
cat -n lib/gemini.ts | head -20Repository: Ker102/ModelForge
Length of output: 673
🏁 Script executed:
# Search for DEFAULT_MODEL usage in the file
rg -n "DEFAULT_MODEL" lib/gemini.ts -A 3 -B 3Repository: Ker102/ModelForge
Length of output: 883
🏁 Script executed:
# Check the function implementations that use DEFAULT_MODEL
rg -n "generateGeminiResponse|streamGeminiResponse" lib/gemini.ts -A 10Repository: Ker102/ModelForge
Length of output: 804
🏁 Script executed:
# Search for all usages of these functions across the codebase
rg -n "generateGeminiResponse|streamGeminiResponse" --type ts --type tsxRepository: Ker102/ModelForge
Length of output: 88
🏁 Script executed:
# Search for function usages with ts files only
rg "generateGeminiResponse|streamGeminiResponse" -t ts --type-add 'ts:*.ts' --type-add 'tsx:*.tsx'Repository: Ker102/ModelForge
Length of output: 774
🏁 Script executed:
# Alternative: search for imports of these functions
rg "from.*gemini" -t ts --type-add 'ts:*.ts'Repository: Ker102/ModelForge
Length of output: 274
🏁 Script executed:
# Check lib/llm/index.ts mentioned in the scratchpad
head -200 lib/llm/index.ts | tail -50Repository: Ker102/ModelForge
Length of output: 1259
🏁 Script executed:
# Search around line 177-191 in llm/index.ts
sed -n '170,200p' lib/llm/index.tsRepository: Ker102/ModelForge
Length of output: 788
🏁 Script executed:
# Check if there are any references to gemini-2.5-pro anywhere in the codebase
rg "gemini-2.5" -iRepository: Ker102/ModelForge
Length of output: 502
🏁 Script executed:
# Check for any environment variable documentation or setup files
rg "GEMINI_MODEL" -i -B 2 -A 2Repository: Ker102/ModelForge
Length of output: 3184
🏁 Script executed:
# Check if there's a .env or config file with model references
fd -e env -e example -e sampleRepository: Ker102/ModelForge
Length of output: 43
🏁 Script executed:
# Search for any documentation about model selection
fd -e md | xargs rg -l "gemini|model" -iRepository: Ker102/ModelForge
Length of output: 815
Keep Gemini 2.5 Pro as the default model.
Both generateGeminiResponse() and streamGeminiResponse() use DEFAULT_MODEL as a fallback when GEMINI_MODEL env var is not set. Using "gemini-3.1-pro-preview" as the default conflicts with the repo requirement to use Gemini 2.5 Pro as the primary model for all AI operations, making a preview model the baseline for all callers that don't explicitly override it.
Suggested fix
-const DEFAULT_MODEL = "gemini-3.1-pro-preview"
+const DEFAULT_MODEL = "gemini-2.5-pro"📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const DEFAULT_MODEL = "gemini-3.1-pro-preview" | |
| const DEFAULT_MODEL = "gemini-2.5-pro" |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@lib/gemini.ts` at line 4, DEFAULT_MODEL is set to "gemini-3.1-pro-preview"
but generateGeminiResponse and streamGeminiResponse expect the repo default to
be Gemini 2.5 Pro; change the DEFAULT_MODEL constant to "gemini-2.5-pro" so both
functions fallback to the correct model, and run tests/lint to ensure no other
places rely on the preview value (update any docstring/comments referencing the
old default if present).
The cookie-clearing logic was hardcoded to port 3000, but the PORT env var can override the default. This caused Electron to clear cookies for the wrong origin when PORT=8081 was set in the environment.
- Fix get_viewport_screenshot prompt: use max_size instead of invalid width/height - Fix screenshot.ts: pass max_size to MCP instead of width/height - Fix executor.ts: defensive normalization strips hallucinated width/height params - Add post-execution LLM summary/follow-up generation in chat route - Wire followup_delta event handler in studio-layout.tsx and project-chat.tsx - Update gemini.md with current progress
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@app/api/ai/chat/route.ts`:
- Around line 752-755: The saved assistant message is replacing earlier streamed
content because assistantText is overwritten with followUpText.trim(); instead,
append the follow-up to preserve the initial content: update the logic around
assistantText and followUpText so that if followUpText.trim() is non-empty you
set assistantText = (assistantText || "").trimEnd() + "\n\n" +
followUpText.trim() (or similar spacing), ensuring you preserve any existing
assistantText and only add the follow-up; adjust any trimming to avoid
duplicating whitespace.
- Around line 740-750: The follow-up LLM stream (streamLlmResponse) loop that
builds followUpText does not collect token usage, so its consumption isn't added
to the overall tokenUsage; modify the follow-up call to capture and aggregate
token counts (e.g., maintain a followUpTokenUsage object or numbers and, for
each chunk or final result from streamLlmResponse, add chunk.tokenUsage or the
returned token counts into the existing tokenUsage totals used later for
logging/billing), updating variables referenced here (streamLlmResponse,
followUpText, and the tokenUsage aggregate) so the follow-up call's tokens are
included in the final usage/billing.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 2730081c-0179-4f60-9639-8bc7a93f0f20
📒 Files selected for processing (6)
app/api/ai/chat/route.tscomponents/projects/project-chat.tsxcomponents/projects/studio-layout.tsxlib/ai/prompts.tslib/mcp/screenshot.tslib/orchestration/executor.ts
| for await (const chunk of streamLlmResponse(llmProvider, { | ||
| history: [], | ||
| messages: [{ role: "user", content: summaryPromptText }], | ||
| maxOutputTokens: 256, | ||
| systemPrompt: "You are ModelForge, a helpful Blender assistant. Respond conversationally. Do NOT use markdown headers. Keep your response to 2-4 sentences.", | ||
| })) { | ||
| if (chunk.textDelta) { | ||
| followUpText += chunk.textDelta | ||
| send({ type: "followup_delta", delta: chunk.textDelta }) | ||
| } | ||
| } |
There was a problem hiding this comment.
Token usage from the follow-up LLM call is not tracked.
The secondary streamLlmResponse call for follow-up generation doesn't capture or aggregate token usage. This creates a gap in usage accounting — the tokenUsage logged at line 779 and billed at line 841 only reflects the primary call.
Suggested fix — accumulate follow-up tokens
let followUpText = ""
+ let followUpUsage: typeof tokenUsage | undefined
for await (const chunk of streamLlmResponse(llmProvider, {
history: [],
messages: [{ role: "user", content: summaryPromptText }],
maxOutputTokens: 256,
systemPrompt: "You are ModelForge, a helpful Blender assistant. Respond conversationally. Do NOT use markdown headers. Keep your response to 2-4 sentences.",
})) {
if (chunk.textDelta) {
followUpText += chunk.textDelta
send({ type: "followup_delta", delta: chunk.textDelta })
}
+ if (chunk.usage) {
+ followUpUsage = chunk.usage
+ }
}
+
+ // Merge follow-up usage into total
+ if (followUpUsage && tokenUsage) {
+ tokenUsage = {
+ promptTokens: (tokenUsage.promptTokens ?? 0) + (followUpUsage.promptTokens ?? 0),
+ responseTokens: (tokenUsage.responseTokens ?? 0) + (followUpUsage.responseTokens ?? 0),
+ totalTokens: (tokenUsage.totalTokens ?? 0) + (followUpUsage.totalTokens ?? 0),
+ }
+ } else if (followUpUsage) {
+ tokenUsage = followUpUsage
+ }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@app/api/ai/chat/route.ts` around lines 740 - 750, The follow-up LLM stream
(streamLlmResponse) loop that builds followUpText does not collect token usage,
so its consumption isn't added to the overall tokenUsage; modify the follow-up
call to capture and aggregate token counts (e.g., maintain a followUpTokenUsage
object or numbers and, for each chunk or final result from streamLlmResponse,
add chunk.tokenUsage or the returned token counts into the existing tokenUsage
totals used later for logging/billing), updating variables referenced here
(streamLlmResponse, followUpText, and the tokenUsage aggregate) so the follow-up
call's tokens are included in the final usage/billing.
app/api/ai/chat/route.ts
Outdated
| // Append follow-up to the assistant text so it's saved to DB | ||
| if (followUpText.trim()) { | ||
| assistantText = followUpText.trim() | ||
| } |
There was a problem hiding this comment.
Initial assistant content is discarded when follow-up is generated.
The code replaces assistantText entirely with followUpText.trim() instead of appending. This means the initial streamed content (from lines 511-514) is lost when the message is persisted to the database. Users may see different content during streaming vs. what's stored.
Suggested fix — append instead of replace
// Append follow-up to the assistant text so it's saved to DB
if (followUpText.trim()) {
- assistantText = followUpText.trim()
+ assistantText += "\n\n" + followUpText.trim()
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // Append follow-up to the assistant text so it's saved to DB | |
| if (followUpText.trim()) { | |
| assistantText = followUpText.trim() | |
| } | |
| // Append follow-up to the assistant text so it's saved to DB | |
| if (followUpText.trim()) { | |
| assistantText += "\n\n" + followUpText.trim() | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@app/api/ai/chat/route.ts` around lines 752 - 755, The saved assistant message
is replacing earlier streamed content because assistantText is overwritten with
followUpText.trim(); instead, append the follow-up to preserve the initial
content: update the logic around assistantText and followUpText so that if
followUpText.trim() is non-empty you set assistantText = (assistantText ||
"").trimEnd() + "\n\n" + followUpText.trim() (or similar spacing), ensuring you
preserve any existing assistantText and only add the follow-up; adjust any
trimming to avoid duplicating whitespace.
…ow-up failure - buildContents() in gemini.ts puts system prompt as user role, so when follow-up call sends history=[] + user message, two consecutive user turns are created which Gemini API silently rejects - Fixed by inserting model acknowledgement turn between system prompt and first user message when needed - Added visible error fallback in route.ts follow-up catch block - Added monitor.error() call so failures appear in session logs
- vectorstore.ts: Fixed Prisma ::vector(768) cast using Prisma.raw() - embeddings.ts: Switched from deprecated Together.ai GTE-ModernBERT to Gemini gemini-embedding-001 (768-dim, free) - executor.ts: Added filepath param for viewport screenshot + file-read fallback + nested error detection - agents.ts: Fixed isMcpSuccess() to check nested result.error BEFORE trusting top-level status - screenshot.ts: Added filepath param + file-based fallback read - route.ts: Fixed LogNamespace type error (followup -> system) - Successfully ingested 135 blender-scripts documents into pgvector
There was a problem hiding this comment.
Actionable comments posted: 6
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
lib/ai/vectorstore.ts (1)
66-89:⚠️ Potential issue | 🟠 MajorMake
addDocuments()atomic.A failure between Line 75 and Line 86 commits a prefix of the batch and then throws, so a retry can duplicate already-inserted rows. Wrap the loop in
prisma.$transaction(...)or replace it with one multi-row insert.🔁 Proposed fix
export async function addDocuments(docs: Document[]): Promise<string[]> { const embeddings = await embedTexts(docs.map((d) => d.content)) - const ids: string[] = [] - - for (let i = 0; i < docs.length; i++) { - const doc = docs[i] - const embedding = embeddings[i].embedding - - const vec = vectorLiteral(embedding) - const result = await prisma.$queryRaw<{ id: string }[]>` + return prisma.$transaction(async (tx) => { + const ids: string[] = [] + + for (let i = 0; i < docs.length; i++) { + const doc = docs[i] + const embedding = embeddings[i].embedding + const vec = vectorLiteral(embedding) + const result = await tx.$queryRaw<{ id: string }[]>` INSERT INTO document_embeddings (id, content, embedding, metadata, source, "createdAt") VALUES ( gen_random_uuid(), ${doc.content}, ${vec}, ${JSON.stringify(doc.metadata ?? {})}::jsonb, ${doc.source ?? null}, NOW() ) RETURNING id::text ` - ids.push(result[0].id) - } - - return ids + ids.push(result[0].id) + } + + return ids + }) }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/ai/vectorstore.ts` around lines 66 - 89, The addDocuments function currently inserts rows one-by-one using prisma.$queryRaw inside a loop (see addDocuments, prisma.$queryRaw, vectorLiteral), which can leave a partially-committed batch on error; make the operation atomic by wrapping the loop in a single prisma.$transaction that executes all inserts as one transaction or by building and executing a single multi-row INSERT for all docs (ensuring embeddings and vectorLiteral values are prepared beforehand), then return the collected IDs from the transaction result; update error handling to propagate/rollback as needed.
♻️ Duplicate comments (1)
app/api/ai/chat/route.ts (1)
766-768:⚠️ Potential issue | 🟡 MinorInconsistent handling between success and error paths.
On success (line 757),
assistantTextis replaced with the follow-up. On error (lines 766-768), the fallback is appended to the existingassistantText. This inconsistency means:
- Successful follow-up → DB stores only follow-up text
- Failed follow-up → DB stores initial response + fallback
Consider making both paths consistent (either always replace or always append).
Suggested fix — consistent append behavior
// Replace assistant text with follow-up so it's saved to DB if (followUpText.trim()) { - assistantText = followUpText.trim() + assistantText += "\n\n" + followUpText.trim() }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/api/ai/chat/route.ts` around lines 766 - 768, The success and error paths handle the follow-up inconsistently: in the success branch the code replaces assistantText with the follow-up, while in the error branch it appends the fallback to assistantText; make them consistent by changing the success path to append the follow-up instead of replacing it (update the code around the send({ type: "followup_delta", delta: ... }) success handling so it does assistantText += `\n\n${followupText}` rather than assistantText = `\n\n${followupText}`), ensuring both send calls and the stored assistantText contain appended follow-up/fallback text.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@debug_error.json`:
- Around line 1-5: The file debug_error.json contains a stale embedding error
referencing models/text-embedding-004 which conflicts with the current model
used in lib/ai/embeddings.ts (gemini-embedding-001); either delete this debug
artifact, move it to a debug/ directory or .gitignore it, or update its
contents/messages to accurately reflect the current model name and call stack
(e.g., reference embedTexts and addDocuments locations) so future developers
aren’t misled by an outdated model name; perform the chosen action and ensure no
other debug JSON files with outdated model names remain in the repo.
In `@GEMINI.md`:
- Around line 1-55: Add missing blank lines around top-level and secondary
headings and the markdown table to satisfy markdownlint: ensure there is an
empty line before and after each heading such as "ModelForge – Active
Development Notes", "## Current Task", "## Progress Summary", "### Unified
Session Monitor (`lib/monitoring/logger.ts`)" etc., and add a blank line above
the table header and below the table block so the pipe-table is isolated; update
GEMINI.md accordingly to remove the linter warnings.
In `@ingestion_log.txt`:
- Line 13: The ingestion failed because docstring-style metadata (fields like
"Category:", "Blender:", "Source:") in the listed files (e.g.,
blender_api_pitfalls.py, displacement_textures.py, hdri_lighting.py,
import_neural_mesh.py, interior_rooms.py, photorealistic_materials.py,
professional_materials.py, render_settings.py, volumetric_effects.py) is not
valid JSON for the metadata parser; fix by either (A) converting the top-of-file
docstrings in each of those Python files into a valid JSON metadata block
(ensuring keys/values are proper JSON and the parser can detect it), or (B)
update the metadata parser (the ingestion function that reads file metadata) to
detect and parse plain-text docstring metadata: implement a docstring parser
that recognizes lines like "Category: <value>", "Blender: <value>", "Source:
<value>", maps them to the metadata schema, and falls back to existing JSON
parsing; apply the chosen fix consistently and re-run the ingestion so titles
are populated instead of "(Untitled)".
In `@lib/ai/vectorstore.ts`:
- Around line 36-38: The vectorLiteral function must validate the embedding
before casting: check that the embedding array length equals the expected
dimension (e.g. EMBEDDING_DIM or 768) and that every element is a finite number
(no NaN/Infinity); if validation fails, throw a clear application error instead
of constructing the SQL. Modify vectorLiteral to perform these checks on the
embedding parameter and only return Prisma.sql`${...}${VECTOR_CAST}` when
validation passes, referencing the existing VECTOR_CAST symbol and an
EMBEDDING_DIM constant (or literal 768) to locate where to enforce the expected
shape.
In `@lib/mcp/screenshot.ts`:
- Line 26: The temp file created as filepath/screenshotPath can leak if an error
occurs before the unlink because deletion is only inside the try; update the
function that writes/reads the screenshot so the unlink is performed in a
finally block (or equivalent cleanup path) to guarantee removal regardless of
success or failure: keep the readFile/readStream logic in the try, but move
fs.unlink/fs.unlinkSync for filepath into a finally block and guard it (check
existence / swallow ENOENT) so the temp file is always attempted to be removed
even on early exceptions.
In `@lib/orchestration/executor.ts`:
- Around line 304-315: The file-read + cleanup logic in executor.ts duplicates
the same pattern in screenshot.ts; extract that block into a shared async
utility (e.g., readAndCleanupTempFile(filepath)) that imports fs/promises, reads
the file, returns its base64 string (or null on failure), and always attempts to
unlink the temp file swallowing unlink errors; then replace the duplicated code
in the executor (the imageBase64 fallback) and the corresponding block in
screenshot.ts to call readAndCleanupTempFile(screenshotPath) so both sites share
consistent read+cleanup behavior.
---
Outside diff comments:
In `@lib/ai/vectorstore.ts`:
- Around line 66-89: The addDocuments function currently inserts rows one-by-one
using prisma.$queryRaw inside a loop (see addDocuments, prisma.$queryRaw,
vectorLiteral), which can leave a partially-committed batch on error; make the
operation atomic by wrapping the loop in a single prisma.$transaction that
executes all inserts as one transaction or by building and executing a single
multi-row INSERT for all docs (ensuring embeddings and vectorLiteral values are
prepared beforehand), then return the collected IDs from the transaction result;
update error handling to propagate/rollback as needed.
---
Duplicate comments:
In `@app/api/ai/chat/route.ts`:
- Around line 766-768: The success and error paths handle the follow-up
inconsistently: in the success branch the code replaces assistantText with the
follow-up, while in the error branch it appends the fallback to assistantText;
make them consistent by changing the success path to append the follow-up
instead of replacing it (update the code around the send({ type:
"followup_delta", delta: ... }) success handling so it does assistantText +=
`\n\n${followupText}` rather than assistantText = `\n\n${followupText}`),
ensuring both send calls and the stored assistantText contain appended
follow-up/fallback text.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 42344d72-a349-4ec8-9ffe-52102c5fa835
📒 Files selected for processing (10)
GEMINI.mdapp/api/ai/chat/route.tsdebug_error.jsoningestion_log.txtlib/ai/agents.tslib/ai/embeddings.tslib/ai/vectorstore.tslib/gemini.tslib/mcp/screenshot.tslib/orchestration/executor.ts
| { | ||
| "message": "400 Unable to access non-serverless model togethercomputer/m2-bert-80M-32k-retrieval. Please visit https://api.together.ai/models/togethercomputer/m2-bert-80M-32k-retrieval to create and start a new dedicated endpoint for the model.", | ||
| "code": "model_not_available", | ||
| "stack": "Error: 400 Unable to access non-serverless model togethercomputer/m2-bert-80M-32k-retrieval. Please visit https://api.together.ai/models/togethercomputer/m2-bert-80M-32k-retrieval to create and start a new dedicated endpoint for the model.\n at Function.generate (/media/krist/CrucialX9/cursor-projects/projects/project02/BlenderAI/node_modules/openai/src/error.ts:72:14)\n at OpenAI.makeStatusError (/media/krist/CrucialX9/cursor-projects/projects/project02/BlenderAI/node_modules/openai/src/core.ts:462:21)\n at OpenAI.makeRequest (/media/krist/CrucialX9/cursor-projects/projects/project02/BlenderAI/node_modules/openai/src/core.ts:526:24)\n at process.processTicksAndRejections (node:internal/process/task_queues:105:5)\n at async embedTexts (/media/krist/CrucialX9/cursor-projects/projects/project02/BlenderAI/lib/ai/embeddings.ts:51:26)\n at async addDocuments (/media/krist/CrucialX9/cursor-projects/projects/project02/BlenderAI/lib/ai/vectorstore.ts:51:22)\n at async ingest (/media/krist/CrucialX9/cursor-projects/projects/project02/BlenderAI/scripts/ingest-blender-docs.ts:114:25)", | ||
| "error": { | ||
| "status": 400, | ||
| "headers": { | ||
| "access-control-allow-origin": "*", | ||
| "alt-svc": "h3=\":443\"; ma=86400", | ||
| "cf-cache-status": "DYNAMIC", | ||
| "cf-ray": "9cbca10fc8337122-TLL", | ||
| "connection": "keep-alive", | ||
| "content-length": "400", | ||
| "content-type": "application/json; charset=utf-8", | ||
| "date": "Tue, 10 Feb 2026 15:27:13 GMT", | ||
| "etag": "W/\"190-oVQ8W47NePIU7/SEdtJPLNSlchs\"", | ||
| "retry-after": "2", | ||
| "server": "cloudflare", | ||
| "strict-transport-security": "max-age=15552000; includeSubDomains", | ||
| "vary": "Accept-Encoding", | ||
| "x-amzn-trace-id": "7d8f5a65-17d2-4eb8-a397-02a6e560a3e9-noamzn", | ||
| "x-api-received": "2026-02-10T15:27:12.131Z", | ||
| "x-ratelimit": "false", | ||
| "x-ratelimit-limit": "50", | ||
| "x-ratelimit-limit-tokens": "0", | ||
| "x-ratelimit-remaining": "99", | ||
| "x-ratelimit-remaining-tokens": "0", | ||
| "x-ratelimit-reset": "2", | ||
| "x-request-id": "oWpzAYx-4msxKE-9cbca10fc8337122" | ||
| }, | ||
| "request_id": "oWpzAYx-4msxKE-9cbca10fc8337122", | ||
| "error": { | ||
| "message": "Unable to access non-serverless model togethercomputer/m2-bert-80M-32k-retrieval. Please visit https://api.together.ai/models/togethercomputer/m2-bert-80M-32k-retrieval to create and start a new dedicated endpoint for the model.", | ||
| "type": "invalid_request_error", | ||
| "param": null, | ||
| "code": "model_not_available" | ||
| }, | ||
| "code": "model_not_available", | ||
| "param": null, | ||
| "type": "invalid_request_error" | ||
| } | ||
| "message": "Gemini Embedding API error 404: {\n \"error\": {\n \"code\": 404,\n \"message\": \"models/text-embedding-004 is not found for API version v1beta, or is not supported for embedContent. Call ListModels to see the list of available models and their supported methods.\",\n \"status\": \"NOT_FOUND\"\n }\n}\n", | ||
| "stack": "Error: Gemini Embedding API error 404: {\n \"error\": {\n \"code\": 404,\n \"message\": \"models/text-embedding-004 is not found for API version v1beta, or is not supported for embedContent. Call ListModels to see the list of available models and their supported methods.\",\n \"status\": \"NOT_FOUND\"\n }\n}\n\n at embedTexts (C:\\Users\\krist\\Desktop\\Cursor-Projects\\Projects\\modelforge\\ModelForge\\lib\\ai\\embeddings.ts:61:19)\n at process.processTicksAndRejections (node:internal/process/task_queues:105:5)\n at async addDocuments (C:\\Users\\krist\\Desktop\\Cursor-Projects\\Projects\\modelforge\\ModelForge\\lib\\ai\\vectorstore.ts:67:22)\n at async ingest (C:\\Users\\krist\\Desktop\\Cursor-Projects\\Projects\\modelforge\\ModelForge\\scripts\\ingest-blender-docs.ts:114:25)", | ||
| "error": {} | ||
| } No newline at end of file |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
Consider removing or updating this debug artifact.
This error log references models/text-embedding-004, but the current lib/ai/embeddings.ts uses gemini-embedding-001. This stale debug file could confuse future developers investigating embedding issues. Either remove it, update it to reflect the current model, or move it to a dedicated debug/ or .gitignore-excluded location.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@debug_error.json` around lines 1 - 5, The file debug_error.json contains a
stale embedding error referencing models/text-embedding-004 which conflicts with
the current model used in lib/ai/embeddings.ts (gemini-embedding-001); either
delete this debug artifact, move it to a debug/ directory or .gitignore it, or
update its contents/messages to accurately reflect the current model name and
call stack (e.g., reference embedTexts and addDocuments locations) so future
developers aren’t misled by an outdated model name; perform the chosen action
and ensure no other debug JSON files with outdated model names remain in the
repo.
| # ModelForge – Active Development Notes | ||
|
|
||
| ## Current Task | ||
| **Studio Pipeline Hardening** – Fixing Blender agent bugs and improving post-execution UX. | ||
|
|
||
| ## Progress Summary | ||
| - Gemini API switched to `gemini-3.1-pro-preview` model with `v1beta` endpoint | ||
| - Studio `workflowMode` fixed: `autopilot` (not `studio`) for actual execution | ||
| - Monitoring system (MonitoringPanel, StepSessionDrawer) fully wired | ||
| - Session drawer now shows plan execution summary, command results, and pipeline monitor | ||
| - Added `StepPlanData` + `StepCommandResult` types for structured execution data | ||
| - Updated `complete` event handler to extract planning metadata and command suggestions | ||
| - **Fixed**: `get_viewport_screenshot` → `max_size` param instead of invalid `width`/`height` | ||
| - **Fixed**: Defensive param normalization in executor for LLM-hallucinated width/height | ||
| - **Fixed**: Gemini API `buildContents()` consecutive user messages bug (caused follow-up to silently fail) | ||
| - **Fixed**: Post-execution follow-up with visible error fallback in catch block | ||
| - **Fixed**: CRAG pipeline — `vectorstore.ts` used `::vector(768)` in Prisma tagged templates which PostgreSQL rejected. Switched to `Prisma.raw()` for type casts. | ||
| - **Fixed**: Embedding model — switched from Together.ai `GTE-ModernBERT-base` (deprecated) to Gemini `gemini-embedding-001` (768-dim, free). No DB migration needed. | ||
| - **Fixed**: Viewport screenshot — Blender MCP server requires `filepath` param. Added file-based fallback (read from disk if no inline base64). | ||
| - **Fixed**: `isMcpSuccess()` — now checks nested `result.error` BEFORE trusting top-level `status: "success"`. | ||
| - **Ingested**: 135 blender-scripts documents into pgvector vectorstore across 39 categories. | ||
|
|
||
| ## Monitoring & Logging Reference | ||
| **IMPORTANT**: Always check these files when debugging the Blender agent pipeline. | ||
|
|
||
| ### Unified Session Monitor (`lib/monitoring/logger.ts`) | ||
| - **Class**: `MonitoringSession` (created via `createMonitoringSession(sessionId)`) | ||
| - **Console output**: Colorized, structured logs in the Next.js dev server terminal | ||
| - **File output**: `logs/pipeline.log` — NDJSON, one JSON entry per log call | ||
| - **Session summary**: `logs/session-{first8chars-of-id}.json` — full session JSON (timers, counts, costs) | ||
| - **Namespaces**: planner, executor, crag, neural, vision, rag, strategy, mcp, workflow, system | ||
| - **Features**: Timers (`startTimer`/`endTimer`), neural cost tracking, RAG stats, log callbacks | ||
|
|
||
| ### Execution Monitor (`lib/orchestration/monitor.ts`) | ||
| - **Function**: `recordExecutionLog(record)` — appends to `logs/orchestration.ndjson` | ||
| - **Content**: Full execution records with plan summary, command results, scene summary | ||
|
|
||
| ### Log File Locations | ||
| | File | Content | | ||
| |------|---------| | ||
| | `logs/pipeline.log` | Per-entry NDJSON from MonitoringSession | | ||
| | `logs/session-{id}.json` | Full session summary (timers, costs, stats) | | ||
| | `logs/orchestration.ndjson` | Execution records (plan + command results) | | ||
| | `logs/latest-run.json` | Last execution record (legacy) | | ||
|
|
||
| ### Visual Feedback Flow | ||
| 1. `route.ts:626` sets `enableVisualFeedback: true` | ||
| 2. `executor.ts:265-380` captures viewport screenshot via MCP (`get_viewport_screenshot`) | ||
| 3. `executor.ts:315` calls `suggestImprovements(imageBase64, userRequest)` — Gemini Vision analysis | ||
| 4. If high-priority issues found → generates correction code → executes fix → re-captures | ||
| 5. Max 2 visual iterations by default (`maxVisualIterations ?? 2`) | ||
|
|
||
| ## Next Steps | ||
| - End-to-end test: verify follow-up text appears in UI after Blender agent execution | ||
| - Review CodeRabbit feedback on PR |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
Fix markdown formatting per linter warnings.
Static analysis (markdownlint) flags missing blank lines around headings and tables. While non-blocking, fixing these improves consistency with standard markdown style.
Add blank lines around headings and table
# ModelForge – Active Development Notes
## Current Task
+
**Studio Pipeline Hardening** – Fixing Blender agent bugs and improving post-execution UX.
## Progress Summary
+
- Gemini API switched to `gemini-3.1-pro-preview` model with `v1beta` endpoint
...
## Monitoring & Logging Reference
+
**IMPORTANT**: Always check these files when debugging the Blender agent pipeline.
### Unified Session Monitor (`lib/monitoring/logger.ts`)
+
- **Class**: `MonitoringSession` (created via `createMonitoringSession(sessionId)`)
...
### Execution Monitor (`lib/orchestration/monitor.ts`)
+
- **Function**: `recordExecutionLog(record)` — appends to `logs/orchestration.ndjson`
...
### Log File Locations
+
| File | Content |
|------|---------|
...
+
### Visual Feedback Flow
+
1. `route.ts:626` sets `enableVisualFeedback: true`
...
## Next Steps
+
- End-to-end test: verify follow-up text appears in UI after Blender agent execution📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| # ModelForge – Active Development Notes | |
| ## Current Task | |
| **Studio Pipeline Hardening** – Fixing Blender agent bugs and improving post-execution UX. | |
| ## Progress Summary | |
| - Gemini API switched to `gemini-3.1-pro-preview` model with `v1beta` endpoint | |
| - Studio `workflowMode` fixed: `autopilot` (not `studio`) for actual execution | |
| - Monitoring system (MonitoringPanel, StepSessionDrawer) fully wired | |
| - Session drawer now shows plan execution summary, command results, and pipeline monitor | |
| - Added `StepPlanData` + `StepCommandResult` types for structured execution data | |
| - Updated `complete` event handler to extract planning metadata and command suggestions | |
| - **Fixed**: `get_viewport_screenshot` → `max_size` param instead of invalid `width`/`height` | |
| - **Fixed**: Defensive param normalization in executor for LLM-hallucinated width/height | |
| - **Fixed**: Gemini API `buildContents()` consecutive user messages bug (caused follow-up to silently fail) | |
| - **Fixed**: Post-execution follow-up with visible error fallback in catch block | |
| - **Fixed**: CRAG pipeline — `vectorstore.ts` used `::vector(768)` in Prisma tagged templates which PostgreSQL rejected. Switched to `Prisma.raw()` for type casts. | |
| - **Fixed**: Embedding model — switched from Together.ai `GTE-ModernBERT-base` (deprecated) to Gemini `gemini-embedding-001` (768-dim, free). No DB migration needed. | |
| - **Fixed**: Viewport screenshot — Blender MCP server requires `filepath` param. Added file-based fallback (read from disk if no inline base64). | |
| - **Fixed**: `isMcpSuccess()` — now checks nested `result.error` BEFORE trusting top-level `status: "success"`. | |
| - **Ingested**: 135 blender-scripts documents into pgvector vectorstore across 39 categories. | |
| ## Monitoring & Logging Reference | |
| **IMPORTANT**: Always check these files when debugging the Blender agent pipeline. | |
| ### Unified Session Monitor (`lib/monitoring/logger.ts`) | |
| - **Class**: `MonitoringSession` (created via `createMonitoringSession(sessionId)`) | |
| - **Console output**: Colorized, structured logs in the Next.js dev server terminal | |
| - **File output**: `logs/pipeline.log` — NDJSON, one JSON entry per log call | |
| - **Session summary**: `logs/session-{first8chars-of-id}.json` — full session JSON (timers, counts, costs) | |
| - **Namespaces**: planner, executor, crag, neural, vision, rag, strategy, mcp, workflow, system | |
| - **Features**: Timers (`startTimer`/`endTimer`), neural cost tracking, RAG stats, log callbacks | |
| ### Execution Monitor (`lib/orchestration/monitor.ts`) | |
| - **Function**: `recordExecutionLog(record)` — appends to `logs/orchestration.ndjson` | |
| - **Content**: Full execution records with plan summary, command results, scene summary | |
| ### Log File Locations | |
| | File | Content | | |
| |------|---------| | |
| | `logs/pipeline.log` | Per-entry NDJSON from MonitoringSession | | |
| | `logs/session-{id}.json` | Full session summary (timers, costs, stats) | | |
| | `logs/orchestration.ndjson` | Execution records (plan + command results) | | |
| | `logs/latest-run.json` | Last execution record (legacy) | | |
| ### Visual Feedback Flow | |
| 1. `route.ts:626` sets `enableVisualFeedback: true` | |
| 2. `executor.ts:265-380` captures viewport screenshot via MCP (`get_viewport_screenshot`) | |
| 3. `executor.ts:315` calls `suggestImprovements(imageBase64, userRequest)` — Gemini Vision analysis | |
| 4. If high-priority issues found → generates correction code → executes fix → re-captures | |
| 5. Max 2 visual iterations by default (`maxVisualIterations ?? 2`) | |
| ## Next Steps | |
| - End-to-end test: verify follow-up text appears in UI after Blender agent execution | |
| - Review CodeRabbit feedback on PR | |
| # ModelForge – Active Development Notes | |
| ## Current Task | |
| **Studio Pipeline Hardening** – Fixing Blender agent bugs and improving post-execution UX. | |
| ## Progress Summary | |
| - Gemini API switched to `gemini-3.1-pro-preview` model with `v1beta` endpoint | |
| - Studio `workflowMode` fixed: `autopilot` (not `studio`) for actual execution | |
| - Monitoring system (MonitoringPanel, StepSessionDrawer) fully wired | |
| - Session drawer now shows plan execution summary, command results, and pipeline monitor | |
| - Added `StepPlanData` + `StepCommandResult` types for structured execution data | |
| - Updated `complete` event handler to extract planning metadata and command suggestions | |
| - **Fixed**: `get_viewport_screenshot` → `max_size` param instead of invalid `width`/`height` | |
| - **Fixed**: Defensive param normalization in executor for LLM-hallucinated width/height | |
| - **Fixed**: Gemini API `buildContents()` consecutive user messages bug (caused follow-up to silently fail) | |
| - **Fixed**: Post-execution follow-up with visible error fallback in catch block | |
| - **Fixed**: CRAG pipeline — `vectorstore.ts` used `::vector(768)` in Prisma tagged templates which PostgreSQL rejected. Switched to `Prisma.raw()` for type casts. | |
| - **Fixed**: Embedding model — switched from Together.ai `GTE-ModernBERT-base` (deprecated) to Gemini `gemini-embedding-001` (768-dim, free). No DB migration needed. | |
| - **Fixed**: Viewport screenshot — Blender MCP server requires `filepath` param. Added file-based fallback (read from disk if no inline base64). | |
| - **Fixed**: `isMcpSuccess()` — now checks nested `result.error` BEFORE trusting top-level `status: "success"`. | |
| - **Ingested**: 135 blender-scripts documents into pgvector vectorstore across 39 categories. | |
| ## Monitoring & Logging Reference | |
| **IMPORTANT**: Always check these files when debugging the Blender agent pipeline. | |
| ### Unified Session Monitor (`lib/monitoring/logger.ts`) | |
| - **Class**: `MonitoringSession` (created via `createMonitoringSession(sessionId)`) | |
| - **Console output**: Colorized, structured logs in the Next.js dev server terminal | |
| - **File output**: `logs/pipeline.log` — NDJSON, one JSON entry per log call | |
| - **Session summary**: `logs/session-{first8chars-of-id}.json` — full session JSON (timers, counts, costs) | |
| - **Namespaces**: planner, executor, crag, neural, vision, rag, strategy, mcp, workflow, system | |
| - **Features**: Timers (`startTimer`/`endTimer`), neural cost tracking, RAG stats, log callbacks | |
| ### Execution Monitor (`lib/orchestration/monitor.ts`) | |
| - **Function**: `recordExecutionLog(record)` — appends to `logs/orchestration.ndjson` | |
| - **Content**: Full execution records with plan summary, command results, scene summary | |
| ### Log File Locations | |
| | File | Content | | |
| |------|---------| | |
| | `logs/pipeline.log` | Per-entry NDJSON from MonitoringSession | | |
| | `logs/session-{id}.json` | Full session summary (timers, costs, stats) | | |
| | `logs/orchestration.ndjson` | Execution records (plan + command results) | | |
| | `logs/latest-run.json` | Last execution record (legacy) | | |
| ### Visual Feedback Flow | |
| 1. `route.ts:626` sets `enableVisualFeedback: true` | |
| 2. `executor.ts:265-380` captures viewport screenshot via MCP (`get_viewport_screenshot`) | |
| 3. `executor.ts:315` calls `suggestImprovements(imageBase64, userRequest)` — Gemini Vision analysis | |
| 4. If high-priority issues found → generates correction code → executes fix → re-captures | |
| 5. Max 2 visual iterations by default (`maxVisualIterations ?? 2`) | |
| ## Next Steps | |
| - End-to-end test: verify follow-up text appears in UI after Blender agent execution | |
| - Review CodeRabbit feedback on PR |
🧰 Tools
🪛 markdownlint-cli2 (0.21.0)
[warning] 3-3: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
[warning] 6-6: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
[warning] 23-23: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
[warning] 26-26: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
[warning] 34-34: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
[warning] 38-38: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
[warning] 39-39: Tables should be surrounded by blank lines
(MD058, blanks-around-tables)
[warning] 46-46: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
[warning] 53-53: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@GEMINI.md` around lines 1 - 55, Add missing blank lines around top-level and
secondary headings and the markdown table to satisfy markdownlint: ensure there
is an empty line before and after each heading such as "ModelForge – Active
Development Notes", "## Current Task", "## Progress Summary", "### Unified
Session Monitor (`lib/monitoring/logger.ts`)" etc., and add a blank line above
the table header and below the table block so the pipe-table is isolated; update
GEMINI.md accordingly to remove the linter warnings.
| ƒôä Prepared: auto_rigify.py (Automated Rigify Rigging Pipeline) [utility] | ||
| ƒôä Prepared: auto_uv_unwrap.py (Automated UV Unwrap Pipeline) [utility] | ||
| ƒôä Prepared: batch_utils.py (Batch Operations Utilities) [utility] | ||
| ÔÜá´©Å Could not parse metadata in blender_api_pitfalls.py: Unexpected token 'B', "Blender Py"... is not valid JSON |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Description: Inspect metadata headers in files that failed JSON parsing
files=(
"blender_api_pitfalls.py"
"displacement_textures.py"
"hdri_lighting.py"
"import_neural_mesh.py"
"interior_rooms.py"
"photorealistic_materials.py"
"professional_materials.py"
"render_settings.py"
"volumetric_effects.py"
)
for file in "${files[@]}"; do
echo "=== $file ==="
fd -t f "$file" data/blender-scripts --exec head -n 20 {}
echo ""
doneRepository: Ker102/ModelForge
Length of output: 7439
Address metadata format mismatch in 9 ingested files.
The ingestion process encountered JSON parse errors for metadata in these 9 files, all falling back to "(Untitled)":
blender_api_pitfalls.pydisplacement_textures.pyhdri_lighting.pyimport_neural_mesh.pyinterior_rooms.pyphotorealistic_materials.pyprofessional_materials.pyrender_settings.pyvolumetric_effects.py
All 9 files contain Python docstrings with plain-text structured metadata (fields like "Category:", "Blender:", "Source:"), but the metadata parser expects JSON format. This format mismatch causes parse failures and loss of descriptive titles, which may impact searchability and RAG retrieval quality.
Either convert these docstrings to JSON metadata or update the parser to handle docstring-based metadata. Given the retrieved learning about re-ingesting Blender scripts after RAG library updates, address this inconsistency in the next ingestion cycle.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@ingestion_log.txt` at line 13, The ingestion failed because docstring-style
metadata (fields like "Category:", "Blender:", "Source:") in the listed files
(e.g., blender_api_pitfalls.py, displacement_textures.py, hdri_lighting.py,
import_neural_mesh.py, interior_rooms.py, photorealistic_materials.py,
professional_materials.py, render_settings.py, volumetric_effects.py) is not
valid JSON for the metadata parser; fix by either (A) converting the top-of-file
docstrings in each of those Python files into a valid JSON metadata block
(ensuring keys/values are proper JSON and the parser can detect it), or (B)
update the metadata parser (the ingestion function that reads file metadata) to
detect and parse plain-text docstring metadata: implement a docstring parser
that recognizes lines like "Category: <value>", "Blender: <value>", "Source:
<value>", maps them to the metadata schema, and falls back to existing JSON
parsing; apply the chosen fix consistently and re-run the ingestion so titles
are populated instead of "(Untitled)".
| function vectorLiteral(embedding: number[]) { | ||
| return Prisma.sql`${`[${embedding.join(",")}]`}${VECTOR_CAST}` | ||
| } |
There was a problem hiding this comment.
Validate embedding shape before handing it to Postgres.
A non-768 vector or non-finite value currently turns into a pgvector cast/operator failure. Fail fast here so writes and searches surface a deterministic application error instead.
🛡️ Proposed fix
-function vectorLiteral(embedding: number[]) {
- return Prisma.sql`${`[${embedding.join(",")}]`}${VECTOR_CAST}`
+function vectorLiteral(embedding: number[]): Prisma.Sql {
+ if (embedding.length !== EMBEDDING_DIMENSIONS) {
+ throw new Error(
+ `Expected ${EMBEDDING_DIMENSIONS} embedding values, got ${embedding.length}`
+ )
+ }
+ if (embedding.some((value) => !Number.isFinite(value))) {
+ throw new Error("Embedding contains a non-finite value")
+ }
+ return Prisma.sql`${`[${embedding.join(",")}]`}${VECTOR_CAST}`
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| function vectorLiteral(embedding: number[]) { | |
| return Prisma.sql`${`[${embedding.join(",")}]`}${VECTOR_CAST}` | |
| } | |
| function vectorLiteral(embedding: number[]): Prisma.Sql { | |
| if (embedding.length !== EMBEDDING_DIMENSIONS) { | |
| throw new Error( | |
| `Expected ${EMBEDDING_DIMENSIONS} embedding values, got ${embedding.length}` | |
| ) | |
| } | |
| if (embedding.some((value) => !Number.isFinite(value))) { | |
| throw new Error("Embedding contains a non-finite value") | |
| } | |
| return Prisma.sql`${`[${embedding.join(",")}]`}${VECTOR_CAST}` | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@lib/ai/vectorstore.ts` around lines 36 - 38, The vectorLiteral function must
validate the embedding before casting: check that the embedding array length
equals the expected dimension (e.g. EMBEDDING_DIM or 768) and that every element
is a finite number (no NaN/Infinity); if validation fails, throw a clear
application error instead of constructing the SQL. Modify vectorLiteral to
perform these checks on the embedding parameter and only return
Prisma.sql`${...}${VECTOR_CAST}` when validation passes, referencing the
existing VECTOR_CAST symbol and an EMBEDDING_DIM constant (or literal 768) to
locate where to enforce the expected shape.
| format?: "png" | "jpeg" | ||
| } = {}): Promise<ViewportScreenshotResponse> { | ||
| const client = createMcpClient() | ||
| const filepath = path.join(tmpdir(), `modelforge_viewport_${Date.now()}.png`) |
There was a problem hiding this comment.
Temp file cleanup not guaranteed on early errors.
The temp file created at screenshotPath is only deleted inside the try block (line 57). If an error is thrown after the MCP server writes the file but before the read/unlink (e.g., at line 45), the temp file will leak. While OS-level temp cleanup mitigates this, consider adding cleanup in a finally block for robustness.
Suggested fix — cleanup in finally block
export async function getViewportScreenshot(options: {
maxSize?: number
format?: "png" | "jpeg"
} = {}): Promise<ViewportScreenshotResponse> {
const client = createMcpClient()
const filepath = path.join(tmpdir(), `modelforge_viewport_${Date.now()}.png`)
+ let needsCleanup = false
try {
const response = await client.execute<ViewportScreenshotResponse>({
...
})
...
+ needsCleanup = true // MCP server likely wrote the file
+
// If not inline, read from the saved filepath
if (!imageBase64) {
try {
const buffer = await readFile(filepath)
imageBase64 = buffer.toString("base64")
- // Clean up temp file
- await unlink(filepath).catch(() => { })
+ needsCleanup = false // Will cleanup in finally
} catch {
throw new Error("Screenshot file not found - Blender MCP may not have saved it")
}
}
...
} finally {
+ if (needsCleanup) {
+ await unlink(filepath).catch(() => { })
+ }
await client.close()
}
}Also applies to: 51-61
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@lib/mcp/screenshot.ts` at line 26, The temp file created as
filepath/screenshotPath can leak if an error occurs before the unlink because
deletion is only inside the try; update the function that writes/reads the
screenshot so the unlink is performed in a finally block (or equivalent cleanup
path) to guarantee removal regardless of success or failure: keep the
readFile/readStream logic in the try, but move fs.unlink/fs.unlinkSync for
filepath into a finally block and guard it (check existence / swallow ENOENT) so
the temp file is always attempted to be removed even on early exceptions.
| // If no inline image, try reading from the saved filepath | ||
| if (!imageBase64) { | ||
| try { | ||
| const { readFile, unlink } = await import("fs/promises") | ||
| const buffer = await readFile(screenshotPath) | ||
| imageBase64 = buffer.toString("base64") | ||
| // Clean up temp file | ||
| await unlink(screenshotPath).catch(() => { }) | ||
| } catch { | ||
| // File not found — screenshot wasn't saved | ||
| } | ||
| } |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
File-reading fallback duplicates logic from lib/mcp/screenshot.ts.
The file read + cleanup pattern here mirrors lines 51-61 in screenshot.ts. Consider extracting to a shared utility (e.g., readAndCleanupTempFile(filepath)) to reduce duplication and ensure consistent cleanup behavior.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@lib/orchestration/executor.ts` around lines 304 - 315, The file-read +
cleanup logic in executor.ts duplicates the same pattern in screenshot.ts;
extract that block into a shared async utility (e.g.,
readAndCleanupTempFile(filepath)) that imports fs/promises, reads the file,
returns its base64 string (or null on failure), and always attempts to unlink
the temp file swallowing unlink errors; then replace the duplicated code in the
executor (the imageBase64 fallback) and the corresponding block in screenshot.ts
to call readAndCleanupTempFile(screenshotPath) so both sites share consistent
read+cleanup behavior.
Studio Pipeline Hardening
Changes
Session Drawer Enhancements
StepPlanDataandStepCommandResultfor structured execution dataplanningmetadata andcommandSuggestionsfrom API responseGemini API Fixes
v1tov1beta(required for preview models)gemini-3.1-pro-previewStudio Mode Fix
workflowModefrom\"studio\"to\"autopilot\"when executing stepsFiles Changed
components/projects/workflow-timeline.tsx— AddedStepPlanData,StepCommandResulttypescomponents/projects/studio-layout.tsx— Updated complete event handler, fixed workflowModecomponents/projects/step-session-drawer.tsx— Added plan summary and command results displaylib/gemini.ts— v1beta endpoint + model updatelib/ai/index.ts— Model constant updateTesting
Summary by CodeRabbit
New Features
Updates