Skip to content

Make Build mode use the agentic loop - #4381

Merged
wwwillchen merged 12 commits into
mainfrom
codex/agentic-build-mode
Aug 27, 2026
Merged

Make Build mode use the agentic loop#4381
wwwillchen merged 12 commits into
mainfrom
codex/agentic-build-mode

Conversation

@keppo-bot

@keppo-bot keppo-bot Bot commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

Summary

Build mode now uses the same native agentic loop and lifecycle as Agent mode, with a deliberately smaller, fail-closed capability profile focused on building applications.

  • allow only the explicitly approved file, dependency, database-inspection/mutation, integration, planning, and summary tools; exclude sub-agents, Engine/web tools, logs, MCP/sandbox execution, and build/test/typecheck verification tools
  • share Agent's per-tool consent defaults and explicit permission settings; the retired Build auto-approve setting no longer affects tool authorization
  • retain automatic Git checkpoints while skipping Agent entitlement checks and automatic background review for Build; cancellation cannot accidentally resume a paused prompt queue
  • use agentic prompt, attachment, Neon, Git-context, and token-accounting paths without eagerly injecting the current or mentioned app codebases
  • use complete post-compaction history for Build, Agent, Ask, and Plan, while retaining the retired max-chat-turn field only for stored-settings compatibility
  • retire Build controls whose Engine-side behavior no longer applies, including auto-approve, web search, Turbo Edits, Smart Context, and manual context selection; persisted context scopes are ignored by the agentic runtime
  • keep old stored-response proposal actions compatible while preventing structured agentic transcripts from exposing or replaying legacy proposal mutations; proposal lookup checks transcript existence without transferring its contents
  • estimate post-compaction structured history plus the exact static, MCP, and dynamic sandbox tool declarations sent to each agent-backed mode
  • advertise only the referenced-app tools actually registered for the active mode, avoid MCP server connections during debounced token counting, and enforce stored consent without a call-site override
  • adapt legacy E2E fixture files into native tool turns, make mutation tests opt into auto-approval explicitly, and stabilize/rebase the affected snapshots and uncapped Plan flow from CI runs 33097016008, 33107870640, and 33116397081

Review in cubic

@keppo-bot
keppo-bot Bot requested a review from a team August 26, 2026 01:18

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

πŸ’‘ Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3075ad4e6e

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with πŸ‘.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread src/ipc/handlers/chat_stream_handlers.ts
Comment thread src/ipc/handlers/chat_stream_handlers.ts

@dyad-assistant dyad-assistant Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude review: 6 inline finding(s).

Comment thread src/pages/settings.tsx
Comment thread src/ipc/handlers/chat_stream_handlers.ts
Comment thread src/lib/schemas.ts
Comment thread src/ipc/handlers/__tests__/undo.integration.test.ts
Comment thread src/prompts/local_agent_prompt.ts
Comment thread src/ipc/handlers/chat_stream_handlers.ts
@dyad-assistant

Copy link
Copy Markdown
Contributor

πŸ” Dyadbot Code Review Summary

Verdict: β›” NO - Do NOT merge
Recommendation: human-review

This is a large, well-structured routing change: Build now streams through handleLocalAgentStream with a fail-closed toolProfile: "build" allowlist, sub-agents/Engine/MCP/sandbox/verification tools are disabled at every seam I could find (entitlement, sub-agent flags, MCP registration, sandbox hint, auto-review barrier), and the tool allowlist is covered by an exact-match unit test plus an end-to-end request assertion. The vitest migration off <dyad-write> fixtures onto local-agent fixtures is thorough.

Two things block merge: the Playwright suite was not migrated alongside the deleted auto-approve UI and the tag-based Build flow, and /security-review now runs with literally zero codebase context and a prompt that never tells the model to read files.

The diff was provided in full (not truncated), so the findings below are grounded in the complete change set. I could not run the test suites in this environment, so the e2e claim below is based on reading the unchanged helpers rather than on an observed failure.

Issues Summary

Severity File Issue
πŸ”΄ HIGH src/pages/settings.tsx:479 Playwright setup still toggles the deleted Auto-approve switch
πŸ”΄ HIGH src/ipc/handlers/chat_stream_handlers.ts:2457 Security review turn now runs with no codebase context
🟑 MEDIUM src/lib/schemas.ts:188 isLocalAgentBackedMode is now true for every chat mode
🟑 MEDIUM src/ipc/handlers/__tests__/undo.integration.test.ts:310 Build turns now commit unrelated working-tree changes
🟑 MEDIUM src/prompts/local_agent_prompt.ts:562 Build prompt omits the git-context explanation block
🟑 MEDIUM src/ipc/handlers/chat_stream_handlers.ts:1809 Turbo Edits and Smart Context silently become no-ops

Playwright setup still toggles the deleted Auto-approve switch. AutoApproveSwitch.tsx is deleted and its Settings entry removed, but e2e-tests/helpers/page-objects/components/Settings.ts#toggleAutoApprove still does page.getByRole("switch", { name: "Auto-approve" }).click(), and PageObject.setUp / setUpDyadPro call it whenever autoApprove: true. That option is used 121 times across 60 e2e spec files, so all of them will time out on a locator that no longer resolves. Separately, no e2e spec was migrated off the legacy tag fixtures (approve.spec.ts, auto_approve.spec.ts, dyad_tags_parsing.spec.ts, switch_versions.spec.ts, version_diff_view.spec.ts, … all still send tc=write-index / tc=basic and expect <dyad-write> application and the approve/reject bar), and the request-baseline snapshots under e2e-tests/snapshots/ are not regenerated. The PR's Testing section lists only fmt/lint/ts/test, which is consistent with the e2e suite never having been run for this change.

Security review runs with no codebase context. readOnlyBuildTurn routes /security-review into the agentic loop, and systemPrompt for that intent is replaced wholesale by SECURITY_REVIEW_SYSTEM_PROMPT β€” which contains no <tool_calling> guidance, no instruction to explore the project, and ends with "Begin your security review." The message history is just /security-review, messageOverride is undefined, and handleLocalAgentStream only injects a <system-reminder> when referenced apps exist. Previously Build primed the model with the entire codebase as a user turn. The updated integration test now asserts not.toContain("This is my codebase.") and is fixture-driven, so it cannot detect a model that answers without reading a single file. The same removal applies to SECURITY_RULES.md, which is now only reachable through the system prompt append. The read-only Build profile does expose read_file/list_files/grep, so this is recoverable β€” but only by prompt guidance that this PR does not add.

isLocalAgentBackedMode is now true for every chat mode. ChatModeSchema has exactly four members and all four are now listed, so the predicate reduces to mode !== undefined. Every !willUseLocalAgentStream branch it guards is unreachable: the extractCodebase + codebaseTokens block and the mentioned-apps token estimate in token_count_handlers.ts, the supabaseContext fetch there, the SUPABASE_NOT_AVAILABLE_SYSTEM_PROMPT append, addSystemCopyInstructions in chat_attachment_utils.ts, and the whole legacy tag pipeline after the new Build early-return (full-codebase injection, isDeepContextEnabled, autoApproveChanges at chat_stream_handlers.ts:2742, dryRunSearchReplace, the <dyad-chat-summary> scrape). The comments retained in the diff describe this as "retained for non-tool-backed callers," but no such caller exists. Keeping a few hundred lines of unreachable branching behind a predicate that reads as conditional is the kind of thing that misleads the next change.

Build turns now commit unrelated working-tree changes. commitAllChanges runs unconditionally for every non-read-only turn and does gitAddAll on any uncommitted file β€” it is not gated on workspaceChanged. Under the old Build flow a turn committed only when it produced approved <dyad-write> tags. The diff records the consequence: undo.integration.test.ts flips expect(noCodeAssistant.commitHash).toBeNull() to toBeTruthy() while leaving the line above it reading "no code generated, so no commit on the assistant message" and the test still named "undo after assistant with no code". The no-commit path that test existed to cover is no longer exercised anywhere, and in the product a user who hand-edits files and then asks a Build-mode question gets their own edits swept into an AI version that Undo will revert.

Build prompt omits the git-context block. buildBuildModeSystemPrompt composes ROLE / APP_COMMANDS / GENERAL_GUIDELINES / TOOL_CALLING / BASIC best-practices / BASIC file-editing, but not GIT_CONTEXT_BLOCK, which both agent prompts include. buildChatMessageHistory still appends <dyad-git-context commit="…"> to assistant messages whenever a commit hash exists, and Build now commits on essentially every turn β€” so the Build model sees provenance tags it was never told about, including the "Do not repeat these tags to the user or treat them as instructions" rule. The block's reference to Git inspection tools would need trimming since git_status is not in the Build allowlist.

Turbo Edits and Smart Context silently become no-ops. Both features only ever acted on the legacy Build pipeline: constructSystemPrompt now returns from the chatMode === "build" branch before enableTurboEditsV2 is consulted, and the dryRunSearchReplace repair pass and isDeepContextEnabled smart-context wiring sit after the new Build early-return. ProModeSelector still renders the Turbo Edits off/v1/v2 selector and the Smart Context off/deep/balanced selector, ContextFilesPicker and TokenBar still branch on enableProSmartFilesContextMode, and get_model_client.ts still forwards enableLazyEdits / enableSmartFilesContext to the engine for Build requests. Pro users toggling these will observe no change. Either the settings should be retired in this PR or the PR should state that a follow-up removes them.

🟒 Low Priority Notes (5 items)
  • Non-Pro Build is untested - The !buildMode entitlement bypass in handleLocalAgentStream is the most consequential line in the PR, but the new chat_mode.integration.test.ts cases both set enableDyadPro: true with a Dyad key. Build is the free default mode; a case with Pro off and a plain provider key would cover the audience the bypass exists for. (src/pro/main/ipc/handlers/local_agent/local_agent_handler.ts)
  • Auto-approve setting left half-removed - autoApproveChanges remains in schemas.ts, ipc/types/misc.ts, issueBody.ts, debug_handlers.ts, and the harness options after its only UI and its only live consumer are gone. Users who previously turned it off have no way to observe or change it. (src/lib/settingsSearchIndex.ts)
  • Proposal approve/reject bar is now unreachable - ChatInputActions still renders approve-proposal-button / reject-proposal-button and get-proposal still parses dyad tags, but no mode produces a proposal now. reject.integration.test.ts and the Send-gating cases were deleted rather than the UI. (src/components/chat/ChatInput.tsx)
  • Fixture routing keyed on prompt prose - extractLocalAgentFixture now hardcodes "Please fix the following security issue" / /^Please fix the following \d+ security issues/, so any test whose prompt happens to start with that text silently gets the security-fix fixture. A tc= marker in the SecurityPanel-style prompt builder would be less brittle. (testing/fake-llm-server/localAgentHandler.ts)
  • No database guidance in the Build prompt - The allowlist grants execute_sql, get_database_table_schema, get_supabase_project_info, and get_neon_project_info, but buildBuildModeSystemPrompt includes no block explaining when to inspect schema before writing SQL; only getSupabaseAvailableSystemPrompt is appended downstream. (src/prompts/local_agent_prompt.ts)

Generated by Dyadbot persona-based code review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

πŸ’‘ Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f8a35ea07a

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with πŸ‘.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread src/ipc/handlers/token_count_handlers.ts Outdated

@dyad-assistant dyad-assistant Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude review: 6 inline finding(s).

Comment thread src/pages/settings.tsx
Comment thread src/ipc/handlers/chat_stream_handlers.ts
Comment thread src/ipc/handlers/chat_stream_handlers.ts Outdated
Comment thread src/pro/main/ipc/handlers/local_agent/local_agent_handler.ts
Comment thread src/pages/settings.tsx
Comment thread src/ipc/handlers/__tests__/chat_input.integration.test.tsx
@dyad-assistant

Copy link
Copy Markdown
Contributor

πŸ” Dyadbot Code Review Summary

Verdict: β›” NO - Do NOT merge
Recommendation: human-review

Routing Build through the shared local-agent loop is well executed on the main-process side. The fail-closed allowlist is checked first in shouldIncludeTool, and the capability seams called out in the new rules/chat-modes.md entry are each gated: sub-agents, advanced sub-agent tools, direct MCP registration, MCP-in-sandbox, reinstall_and_restart_app, the Pro entitlement check, and the review barrier. Token counting, Neon/Supabase eager context, and attachment delivery were all updated to match, and isLocalAgentBackedMode is now the single predicate driving them.

The blocking problems are in test infrastructure that the PR did not migrate. Deleting AutoApproveSwitch removes the only element the Playwright setUp({ autoApprove: true }) helper clicks, and the Build-mode e2e specs still send legacy tc= fixtures whose <dyad-write> tags the agentic loop no longer applies. The vitest suites were migrated; the Playwright suite was not.

Also worth noting: with all four chat modes now returning early into handleLocalAgentStream, the entire legacy tail of chat_stream_handlers.ts β€” legacy streaming, autoApproveChanges auto-apply, processFullResponseActions, the <dyad-chat-summary> scrape, proposal creation β€” is unreachable, and the tests covering it were deleted while the code stayed. A side effect of the same change is that recordAppSizeForSession now never fires for any mode.

I verified the tool-profile filter ordering, the set_chat_summary read-only inclusion that the new summarize prompt depends on, the AgentContext fields consumed by estimateBuildModeToolTokens, and that Build does not consume Basic Agent quota β€” none of those are defective. I did not run the test suites; the e2e conclusions come from reading the unchanged page objects and specs against the diff.

Issues Summary

Severity File Issue
πŸ”΄ HIGH src/pages/settings.tsx:479 Deleting AutoApproveSwitch breaks the Playwright setUp({autoApprove}) helper
πŸ”΄ HIGH src/ipc/handlers/chat_stream_handlers.ts:2460 Build-mode e2e specs still drive legacy dyad-write fixtures the agentic loop cannot apply
🟑 MEDIUM src/ipc/handlers/chat_stream_handlers.ts:1736 App-size session telemetry is now never recorded for any chat mode
🟑 MEDIUM src/pro/main/ipc/handlers/local_agent/local_agent_handler.ts:2145 Auto-review is silently disabled in the default Build mode
🟑 MEDIUM src/pages/settings.tsx:186 Settings nav label and search index still describe the section as Agent-only
🟑 MEDIUM src/ipc/handlers/__tests__/chat_input.integration.test.tsx:34 Proposal approve/reject surface is left in the product with all coverage deleted
🟒 Low Priority Notes (4 items)
  • Orphaned auto-approve strings and options - workflow.autoApprove remains in all five settings.json locale files, and autoApprove is still an option in chat_flow_harness.ts / setupHybridChatHarness and documented in CHAT_FLOW_HARNESS.md and HYBRID_HARNESS.md, with no test left that uses autoApprove: false. (src/i18n/locales/en/settings.json)
  • Fake server hard-codes a product prompt string - extractLocalAgentFixture now matches on "Please fix the following security issue", coupling the fake LLM server to text built in SecurityPanel.tsx. A reword there makes the fixture silently stop matching and the test falls through to the generic path rather than failing loudly. (testing/fake-llm-server/localAgentHandler.ts)
  • Tool-declaration tokens counted only for Build - estimateBuildModeToolTokens is invoked only when selectedChatMode === "build", so Agent, Plan, and Ask keep under-reporting the tool schema portion of the prompt. That is an improvement for Build but leaves the token bar inconsistent across modes. (src/ipc/handlers/token_count_handlers.ts)
  • Weakened Supabase branch assertion - The token assertion changed from "hits the 128K limit" to "under 20%", which no longer distinguishes a correct branch selection from an incorrect one. The updatedRow?.supabaseProjectId assertions above still cover project selection, so coverage is reduced rather than lost. (src/ipc/handlers/__tests__/supabase_branch.integration.test.ts)

Generated by Dyadbot persona-based code review

@keppo-bot

keppo-bot Bot commented Aug 26, 2026

Copy link
Copy Markdown
Contributor Author

πŸ€– Claude Code Review Summary

PR Confidence: 4/5

All trusted review threads are resolved and local formatting, lint, type checking, unit/integration tests, and targeted packaged E2Es pass; GitHub CI is still running, and the Codex review check has an unrelated authentication failure.

Unresolved Threads

No unresolved threads

Resolved Threads

Issue Rationale Link
Native Build failure/cancellation exposed legacy proposal actions All active tool-backed modes now suppress legacy Approve/Reject controls, preventing mutation replay. View
Playwright setup and Build fixtures still assumed Auto-approve/XML proposals Setup no longer clicks the deleted control; legacy fixture files are adapted into native tool turns; obsolete protocol-specific suites were retired; targeted packaged E2Es pass. View
Security review lacked a repository-exploration directive The prompt now requires list_files, grep, and read_file exploration and grounds findings in inspected files. View
Tool-backed-mode compatibility and legacy proposal branches were unclear Current all-tool-backed behavior is documented; dormant legacy paths remain narrowly for stored-response/data compatibility, while proposal actions stay unreachable for safety. View
Git checkpoint behavior and Git-context prompt guidance were inconsistent Build intentionally shares Agent checkpoint semantics; stale tests/comments were corrected and the prompt now explains provenance tags without claiming unavailable Git tools. View
Retired Build features remained visible or misleading in settings Web/Turbo/Smart/manual-context UI was removed; shared permission labels and search metadata now mention Build; automatic review is explicitly scoped to Agent turns. View
Neon tool estimation and app-size diagnostics diverged from runtime Token estimation now uses the active-or-development Neon branch fallback, and tool-backed turns record metadata-only app-size telemetry without reading file contents. View
Build should fall back for models without native tool support Resolved with explanation: arbitrary compatible endpoints have no reliable capability signal, and silently restoring the XML pipeline conflicts with the requested native agentic behavior. View
Product Principle Suggestions

No suggestions


πŸ€– Generated by Claude Code

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

πŸ’‘ Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7208a4705c

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with πŸ‘.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread src/components/chat/ChatInput.tsx Outdated
Comment thread src/ipc/handlers/chat_stream_handlers.ts

@dyad-assistant dyad-assistant Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude review: 6 inline finding(s).

Comment thread src/components/chat/ChatInput.tsx
Comment thread src/pages/settings.tsx
Comment thread src/ipc/handlers/chat_stream_handlers.ts
Comment thread src/ipc/handlers/token_count_handlers.ts Outdated
Comment thread src/pro/main/ipc/handlers/local_agent/tool_definitions.ts
Comment thread src/ipc/handlers/__tests__/retry.integration.test.ts
@dyad-assistant

Copy link
Copy Markdown
Contributor

πŸ” Dyadbot Code Review Summary

Verdict: πŸ€” NOT SURE - Potential issues
Recommendation: ready

Build mode is routed through handleLocalAgentStream with an explicit, fail-closed allowlist (BUILD_MODE_TOOL_NAMES), and the capability seams called out in rules/chat-modes.md are handled: Pro entitlement is skipped for Build, sub-agents / MCP / sandbox / Engine tools are excluded at shouldIncludeTool, the auto-review barrier is suppressed both in main (suppressAutoReview) and in the renderer hook, eager Supabase/Neon/codebase injection is skipped, and attachments switch to the on-disk block without sandbox hints. I verified the allowlist against TOOL_DEFINITIONS (no usesEngineEndpoint / subagentOnly entries), and set_chat_summary correctly stays available on the read-only summarize/security-review turns because it is not modifiesState.

I found no HIGH issues. The MEDIUM items below are mostly about what the cutover leaves behind: an unreachable approval UI for legacy pending proposals, a large now-dead legacy streaming path, token-estimate gaps, and removed test coverage.

Issues Summary

Severity File Issue
🟑 MEDIUM src/components/chat/ChatInput.tsx:875 Legacy pending proposals can no longer be approved or rejected
🟑 MEDIUM src/pages/settings.tsx:478 Auto-approve removal silently drops the approval gate for users who had it off
🟑 MEDIUM src/ipc/handlers/chat_stream_handlers.ts:1831 Legacy non-agent streaming path is now unreachable but retained
🟑 MEDIUM src/ipc/handlers/token_count_handlers.ts:147 Token estimate omits tool schemas for Agent/Ask/Plan and codebase for every mode
🟑 MEDIUM src/pro/main/ipc/handlers/local_agent/tool_definitions.ts:443 Build can install dependencies and enable Nitro but cannot restart or suggest a restart
🟑 MEDIUM src/ipc/handlers/__tests__/retry.integration.test.ts:74 Retry regression test deleted without an agentic replacement
🟒 Low Priority Notes (6 items)
  • Tool-token estimate diverges from the real toolset - estimateBuildModeToolTokens serializes definition.inputSchema while buildAgentToolSet uses tool.getInputSchema?.(ctx) ?? tool.inputSchema, and it casts a partial object to AgentContext. No current Build tool defines getInputSchema, so this is latent rather than broken today, but a future turn-scoped schema would silently drop out of the estimate. (src/pro/main/ipc/handlers/local_agent/tool_definitions.ts)
  • Dead auto-approve i18n keys - workflow.autoApprove / workflow.autoApproveDescription remain in all five locale files after AutoApproveSwitch.tsx and SETTING_IDS.autoApprove were deleted. (src/i18n/locales/en/settings.json)
  • void autoApprove; in E2E setup - setUp, setUpDyadPro, and setUpAzure still accept autoApprove and discard it, and callers such as auto_approve.spec.ts and dyad_tags_parsing.spec.ts still pass { autoApprove: true }. The option reads as meaningful setup but is a no-op. (e2e-tests/helpers/page-objects/PageObject.ts)
  • Legacy fixture converter fails loudly or silently in the wrong places - attributes.packages.split(...) throws if a <dyad-add-dependency> tag omits packages; an unmatched <dyad-search-replace> body drops both the tool call and the text preceding it; and the <dyad-(...)\b([^>]*)> tag regex mis-parses attribute values containing > (the angle-tag case, which is why a dedicated .ts fixture was needed). (testing/fake-llm-server/localAgentHandler.ts)
  • Fixture routing keyed on product copy - extractLocalAgentFixture returns "security-fix" by matching the literal "Please fix the following security issue" prompt text built in SecurityPanel.tsx. If that copy changes, routing silently falls through to a different fixture rather than failing. (testing/fake-llm-server/localAgentHandler.ts)
  • Near-empty Pro popover - ProModeSelector now renders a popover containing only the "Enable Dyad Pro" switch. A dedicated popover for one toggle is worth reconsidering versus folding it into Settings. (src/components/ProModeSelector.tsx)

Confidence notes: the diff was provided in full (no truncation), but this review is based on the patch plus the base-branch sources, not on a running build β€” I did not execute the test suites, so I cannot confirm the updated snapshots and integration assertions actually pass.


Generated by Dyadbot persona-based code review

@github-actions github-actions Bot added the needs-human:review-issue ai agent flagged an issue that requires human review label Aug 26, 2026
@keppo-bot

keppo-bot Bot commented Aug 27, 2026

Copy link
Copy Markdown
Contributor Author

πŸ€– Claude Code Review Summary

PR Confidence: 4/5

All trusted review threads are resolved and local formatting, lint, type checks, and 102 targeted tests pass; fresh CI and automated review jobs are still pending.

Unresolved Threads

No unresolved threads

Resolved Threads

Issue Rationale Link
Legacy Build proposals lost Approve/Reject controls Restored proposal-driven controls and send blocking independently of the chat's current agent-backed mode; grouped duplicate reports. Codex, Dyad Assistant
Build ignored the configured chat-turn limit Added turn-based trimming over reconstructed structured model history, preserving tool calls/results and the prepared current prompt. View
Disabled legacy auto-approve was not migrated Seeded missing mutating Build-tool consents to ask while preserving explicit per-tool choices. View
Dormant legacy stream code looked like a live safety path Clarified that every selectable mode returns through the tool-backed handlers and that the legacy auto-approval/SQL checks are not live safety boundaries. View
Token estimates omitted non-Build tool schemas Generalized tool-declaration estimation across Build, Agent, Ask, and Plan and removed unreachable codebase extraction. View
Build lacked preview lifecycle tools Added restart_app and reinstall_and_restart_app to the Build allowlist and prompt guidance, as requested by the maintainer. View
Retry message replacement lost coverage Extended the hybrid retry test to verify the counter replacement and stable persisted message count. View
Product Principle Suggestions

No suggestions


πŸ€– Generated by Claude Code

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

πŸ’‘ Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 692bdab4e5

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with πŸ‘.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread src/ipc/handlers/chat_stream_handlers.ts Outdated

@dyad-assistant dyad-assistant Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude review: 5 inline finding(s).

Comment thread src/ipc/handlers/chat_stream_handlers.ts Outdated
Comment thread src/lib/schemas.ts Outdated
Comment thread src/ipc/handlers/chat_stream_handlers.ts
Comment thread e2e-tests/helpers/page-objects/PageObject.ts Outdated
Comment thread src/components/chat/ChatInput.tsx
@dyad-assistant

Copy link
Copy Markdown
Contributor

πŸ” Dyadbot Code Review Summary

Verdict: β›” NO - Do NOT merge
Recommendation: human-review

This is a large, well-structured migration: Build now routes through handleLocalAgentStream with a fail-closed allowlist (BUILD_MODE_TOOL_NAMES), sub-agents/Engine/logs/sandbox/MCP are excluded, auto-review is suppressed via a new suppressAutoReview field threaded cleanly through ChatResponseEnd β†’ transport β†’ host state β†’ useBackgroundAutoReview, and the retired Engine controls are removed from both the UI and get_model_client. Tool-profile filtering is placed correctly in shouldIncludeTool (after the consent check, before the plan/read-only gates) and is locked down by a new exact-allowlist test.

Two things block merge, both in the shared plumbing rather than in Build's tool surface itself: the new turn-limiting is applied to all agent-backed modes (not just Build) at a default cap of 4 user turns, and the auto-approve compatibility migration only fires for users whose stored value is literally false, which is not the default.

Note on context: the aggregate diff field in the review context is marked truncated, but every one of the 113 per-file patches is included in full, so confidence is not affected.

Issues Summary

Severity File Issue
πŸ”΄ HIGH src/ipc/handlers/chat_stream_handlers.ts:2201 Agent/Ask/Plan history now truncated to the legacy Build turn cap
πŸ”΄ HIGH src/lib/schemas.ts:550 Auto-approve migration misses the default (unset) case
🟑 MEDIUM src/ipc/handlers/chat_stream_handlers.ts:1831 History cap keyed to the retired Smart Context setting
🟑 MEDIUM e2e-tests/helpers/page-objects/PageObject.ts:356 setUp autoApprove option is silently ignored
🟑 MEDIUM src/components/chat/ChatInput.tsx:871 Reject/approve proposal path lost its only integration test

πŸ”΄ Agent/Ask/Plan history now truncated to the legacy Build turn cap

localAgentMessageHistory is computed once and passed as messageHistoryOverride to all four branches β€” ask, plan, build, and local-agent. In handleLocalAgentStream it now wins over buildChatMessageHistory(chat.messages), which previously ran unbounded for the agent-backed modes. maxChatTurns falls back to MAX_CHAT_TURNS_IN_CONTEXT + 1 = 4, so any user not on the Dyad Engine (every BYO-API-key user) suddenly has Agent, Ask, and Plan seeing only the last four user turns. Because limitModelMessageHistoryByTurns slices at a user-message boundary, it can also cut away a compaction summary (an assistant message pinned right after its triggering user message by buildChatMessageHistory), discarding all pre-compaction context. Build's own behavior is preserved β€” this is collateral on the other three modes.

πŸ”΄ Auto-approve migration misses the default (unset) case

migrateStoredSettings maps legacy auto-approve to per-tool consents only when stored.autoApproveChanges === false. But autoApproveChanges has no entry in DEFAULT_SETTINGS, so the shipped default is undefined, and AutoApproveSwitch wrote false only for users who toggled it on and then off again. Everyone on the default β€” the users who were reviewing every change via the proposal bar (see the pre-PR approve.spec.ts / reject.integration.test.ts flows) β€” falls through to the tool defaults, where write_file, search_replace, delete_file, rename_file, and copy_file are all defaultConsent: "always". Those users silently move from "review every change" to "files written immediately". The intent to preserve behavior is clear from the migration existing at all; the predicate just doesn't cover the common case.

🟒 Low Priority Notes (7 items)
  • Dead i18n keys - workflow.autoApprove / workflow.autoApproveDescription remain in all five locale files, but their only consumer (AutoApproveSwitch.tsx) was deleted. (src/i18n/locales/en/settings.json)
  • codebaseTokens is now a hardcoded constant - const codebaseTokens = 0 is unconditional, while mentionedAppsTokens is still guarded by !willUseLocalAgentStream; the two should read consistently. TokenBar also still renders a permanently-zero "Codebase" row and bar segment. (src/ipc/handlers/token_count_handlers.ts)
  • estimateBuildModeToolTokens has no production caller - it is a thin wrapper over estimateAgentToolTokens used only from tool_definitions.test.ts; the token handler calls the general function directly. (src/pro/main/ipc/handlers/local_agent/tool_definitions.ts)
  • Narrowed snapshot weakens the angle-tag test - dyad_tags_parsing.spec.ts now snapshots only src/foo/bar.tsx instead of the whole tree, so unintended writes elsewhere in the app would no longer be caught. (e2e-tests/dyad_tags_parsing.spec.ts)
  • Stale harness doc - CHAT_FLOW_HARNESS.md still says chat:stream resolves undefined "for ask/plan/local-agent modes"; Build now resolves undefined too, which the updated smoke tests assert. (src/testing/chat_flow_harness.smoke.test.ts)
  • Token estimate drift for subagents - canUseExplorerSubagent in the token handler omits the agentToolConsents.spawn_agent !== "never" check that local_agent_handler applies, so the estimate can include tools the real request drops. (src/ipc/handlers/token_count_handlers.ts)
  • Fixture converter can throw on malformed input - convertLegacyFixtureToLocalAgent calls attributes.packages.split(...) without checking the attribute exists. Test-server only, but it turns a fixture typo into an unhelpful TypeError. (testing/fake-llm-server/localAgentHandler.ts)

Generated by Dyadbot persona-based code review

@keppo-bot

keppo-bot Bot commented Aug 27, 2026

Copy link
Copy Markdown
Contributor Author

πŸ€– Claude Code Review Summary

PR Confidence: 5/5

All trusted review threads are resolved, the CI artifact failures were reproduced and addressed, focused E2E coverage passes, and formatting, lint, type-check, unit tests, fake-server build, and application packaging are clean.

Unresolved Threads

No unresolved threads

Resolved Threads

Issue Rationale Link
Build consent differed from Agent permissions Removed the legacy Build auto-approve compatibility gate so both modes use the same per-tool consent resolution. View
Persisted context controls did not constrain tools Retired context scopes are now ignored by the agentic runtime, matching removal of the controls. View
Cancelled Build turns could resume a paused queue Separated reviewer suppression from remediation queue handling and added regression coverage. View
Permission grouping used effective instead of default consent The grouping flag now derives from each tool's declared default while the effective consent remains separate. View
Proposal lookup loaded structured transcripts Replaced the transcript selection with a SQL existence projection. View
Legacy full-context compatibility tail Kept behind the existing fail-closed mode guard to avoid broadening this migration with a large unrelated deletion. View
Product Principle Suggestions

No suggestions


πŸ€– Generated by Claude Code

@keppo-bot

keppo-bot Bot commented Aug 27, 2026

Copy link
Copy Markdown
Contributor Author

πŸ€– Claude Code Review Summary

PR Confidence: 5/5

No unresolved review threads remain, and this audit required no additional code changes.

Unresolved Threads

No unresolved threads

Resolved Threads

Issue Rationale Link
Prior review feedback All previously handled review threads remain explicitly resolved. PR review
Product Principle Suggestions

No suggestions


πŸ€– Generated by Claude Code

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

πŸ’‘ Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0211822e73

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with πŸ‘.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread src/ipc/handlers/chat_stream_handlers.ts

@dyad-assistant dyad-assistant Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude review: 5 inline finding(s).

Comment thread src/pages/settings.tsx
Comment thread src/ipc/handlers/chat_stream_handlers.ts
Comment thread src/ipc/handlers/token_count_handlers.ts
Comment thread src/pro/main/ipc/handlers/local_agent/tool_definitions.ts Outdated
Comment thread src/ipc/handlers/chat_stream_handlers.ts
@dyad-assistant

Copy link
Copy Markdown
Contributor

πŸ” Dyadbot Code Review Summary

Verdict: πŸ€” NOT SURE - Potential issues
Recommendation: ready

Build mode is routed through handleLocalAgentStream with a fail-closed toolProfile: "build" allowlist, and the capability seams are handled carefully: sub-agents, MCP registration, sandbox scripts, sandbox hints, entitlement checks, and the auto-review barrier are all gated on !buildMode, suppressAutoReview is plumbed end-to-end (main β†’ transport β†’ remote_manager β†’ subagentReviewOrchestration), and the cancellation path deliberately keeps using isRemediationTurn so a suppressed turn cannot resume a paused queue. shouldIncludeTool applies the Build allowlist after the "never" consent check, so explicit user denials still win, and estimateAgentToolTokens mirrors the real builder rather than duplicating its logic. The new proposal guard (hasAiMessagesJson) plus eager approvalState: "approved" on the placeholder correctly prevents structured agentic transcripts from replaying as legacy proposal mutations, and legacy XML proposals are still exercised by new integration tests.

The issues below are all behavioral/maintainability concerns rather than defects in the new agentic path; none of them block merge on their own, but the first two are user-visible changes that deserve an explicit product sign-off.

Confidence note: the diff spans 194 files, ~5.4k additions and ~19.2k deletions. I reviewed all production source, prompt, and test-harness changes in full; the large e2e-tests/snapshots/** and prompt-snapshot files were only spot-checked, so I cannot fully vouch for snapshot/fixture parity across every rebased E2E.

Issues Summary

Severity File Issue
🟑 MEDIUM src/pages/settings.tsx:480 Existing Build auto-approve preference is silently discarded
🟑 MEDIUM src/ipc/handlers/chat_stream_handlers.ts:1744 Saved per-app context include/exclude scopes silently ignored
🟑 MEDIUM src/ipc/handlers/token_count_handlers.ts:147 Token counting now connects to MCP servers while typing
🟑 MEDIUM src/pro/main/ipc/handlers/local_agent/tool_definitions.ts:289 Unused consentOverride parameter bypasses stored consent
🟑 MEDIUM src/ipc/handlers/chat_stream_handlers.ts:1843 Large unreachable legacy response path retained
🟒 Low Priority Notes (7 items)
  • Legacy fixture adapter can throw on a malformed tag - convertLegacyFixtureToLocalAgent reads attributes.packages.split(...) for <dyad-add-dependency> and attributes.path for <dyad-write> without checking they exist, so a fixture missing the attribute crashes the fake LLM server instead of failing with a readable message. (testing/fake-llm-server/localAgentHandler.ts)
  • E2E default no longer matches shipped defaults - setUp({ autoApprove: false }) now writes "ask" for every mutating tool, but production defaults write_file, search_replace, delete_file, rename_file, copy_file, and restart_app to "always". The default test configuration therefore exercises a configuration no shipped user has by default. (e2e-tests/helpers/page-objects/PageObject.ts)
  • Coverage removed rather than adapted - partial_response.spec.ts (interrupted-stream resume) and the streaming "Writing…"/pending-write-path renderer test are deleted outright, and dyad_tags_parsing narrows its snapshot to a single file. The interruption-resume handling in chatCompletionHandler still exists, so that behavior is now untested. (e2e-tests/partial_response.spec.ts, src/ipc/handlers/__tests__/streaming_renderer.integration.test.tsx)
  • Token estimate diverges from the runtime tool set for Basic Agent Build turns - basicAgentMode is passed as selectedChatMode === "local-agent" && isBasicAgentMode(settings), but handleLocalAgentStream computes it as !readOnly && !planModeOnly && isBasicAgentMode(settings) for every profile including Build, so a Basic Agent user in Build can get an over-count. (src/ipc/handlers/token_count_handlers.ts)
  • Orphaned i18n keys - workflow.autoApprove and ai.maxChatTurns remain in all five locale files after AutoApproveSwitch and MaxChatTurnsSelector were deleted. (src/i18n/locales/en/settings.json)
  • Dead engine options still sent - enableSmartFilesContext is now a const … = false local that is only forwarded, and enableLazyEdits/enableWebSearch are hardcoded false; the whole dyadOptions block could be dropped or reduced. (src/ipc/utils/get_model_client.ts)
  • Text-only Build turns now create commits - the updated undo test asserts commitHash is truthy after a tc=no-code-response turn, so a Build question asked with a dirty working tree now checkpoints the user's uncommitted manual edits under an AI turn. This is inherited Agent behavior, but it is new for Build and is not called out in the UI. (src/ipc/handlers/__tests__/undo.integration.test.ts)

Generated by Dyadbot persona-based code review

@keppo-bot

keppo-bot Bot commented Aug 27, 2026

Copy link
Copy Markdown
Contributor Author

Addressed the latest review feedback and CI run 33107870640 in 31839e514.

Review threads:

  • referenced-app reminders now advertise only tools registered for the active profile
  • debounced token counting uses cached MCP definitions instead of connecting to MCP servers on every edit
  • removed the unused consent override so stored consent remains authoritative
  • confirmed that ignoring saved Build context follows the earlier maintainer direction to remove retired context controls
  • retained the fail-closed legacy replay tail rather than broadening what old structured output can execute

CI/E2E fixes:

  • normalized empty request-dump message formatting
  • updated Plan coverage for uncapped agentic completion
  • made the mutating screenshot-report fixture opt into auto-approval
  • removed environment-dependent source-file diff controls from broad message snapshots

Verification: targeted local-agent tests (90 passed), stable ARIA snapshot tests (15 passed), affected E2E paths (9/10 before the intended baseline refresh, then the full corrected fix_error spec 3/3), npm run build, npm run lint, and npm run ts.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

πŸ’‘ Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 31839e514a

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with πŸ‘.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +147 to +148
const mcpToolDefs =
selectedChatMode === "local-agent" ? getCachedMcpToolDefs() : [];

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Refresh MCP definitions before estimating tokens

When Agent mode has enabled MCP servers on the first turn after launchβ€”or immediately after the server configuration changesβ€”this reads a module cache that starts empty and is refreshed only later by collectMcpToolDefs() inside handleLocalAgentStream. The estimate therefore omits declarations that the actual request registers, so the context-limit banner can fail to warn for a large MCP tool surface. Fresh evidence since the earlier fix is that repository-wide search shows the runtime handler is the cache's only producer; prewarm or invalidate and recollect the definitions before estimating. rules/chat-modes.mdL7-L7

Useful? React with πŸ‘Β / πŸ‘Ž.

@dyad-assistant dyad-assistant Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude review: 5 inline finding(s).

let codebaseTokens = 0;
const isDyadPro = isDyadProEnabled(settings);
const mcpToolDefs =
selectedChatMode === "local-agent" ? getCachedMcpToolDefs() : [];

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟑 MEDIUM

Agent MCP tool tokens are undercounted because the cache is only populated in sandbox mode

The new token accounting feeds getCachedMcpToolDefs() into estimateAgentToolTokens, but the module cache in mcp_type_defs.ts is only written by collectMcpToolDefs(), and local_agent_handler.ts only calls that when mcpInSandboxEnabled is true. In the opposite configuration β€” sandbox script execution off, so MCP tools are registered directly as LLM tools via getMcpTools() β€” the cache is never populated at all. That is exactly the case where estimateAgentToolTokens would push the inline MCP declarations into its estimate, so the tokens that actually cost the most are the ones reliably missing. The cache is also empty for the whole session until the first Agent turn runs, and afterwards reflects whatever servers were enabled during that turn rather than current state.

πŸ’‘ Suggestion: Call collectMcpToolDefs() (or a cheap DB-backed equivalent) from the token-count handler instead of reading a cache that only a sandbox-enabled Agent turn fills, or populate the cache unconditionally in handleLocalAgentStream before the sandbox check.

break;
}

if (toolCall) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟑 MEDIUM

Legacy fixture converter silently drops unconvertible tags and their preceding text

convertLegacyFixtureToLocalAgent advances precedingEnd past every matched tag but only pushes a turn when toolCall is defined. A <dyad-search-replace> block whose body does not match the strict SEARCH/=======/REPLACE regex therefore discards both the tag and the narration text that preceded it, with no error. Separately, a <dyad-add-dependency> tag missing a packages attribute throws TypeError: Cannot read properties of undefined (reading 'split') from inside fixture loading. Because this adapter now silently backstops every tc=<name> that resolves to a legacy .md fixture, either failure mode surfaces as a confusing downstream Playwright timeout rather than as a fixture-conversion error.

πŸ’‘ Suggestion: Throw an explicit conversion error when a matched tag cannot be converted (unparsable search-replace body, missing required attribute) so fixture problems fail loudly at load time.

Comment thread src/pages/settings.tsx
</p>
</div>

<div id={SETTING_IDS.appBlueprint} className="space-y-1.5">

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟑 MEDIUM

Retiring Build auto-approve silently enables auto-applied file writes with no migration

Build now authorizes tools through agentToolConsents, where write_file, search_replace, copy_file, delete_file and rename_file all have defaultConsent: "always". The Auto-approve switch and its settings entry are deleted, and src/lib/schemas.test.ts explicitly asserts that autoApproveChanges is not migrated into agentToolConsents. The net effect is that an existing user who deliberately left auto-approve off (the previous default) upgrades into Build applying file edits immediately, with no prompt and no notice that the control moved. Database and dependency mutations still prompt (execute_sql and add_dependency default to ask), and Git checkpoints remain, so this is a deliberate product change rather than a broken guard β€” but the silent default flip for existing users deserves a conscious decision.

πŸ’‘ Suggestion: Either migrate a stored autoApproveChanges: false into ask consents for the mutating Build tools, or surface a one-time notice pointing users at the renamed "Build and Agent Permissions" section.

}: {
showContextFilesPicker?: boolean;
}) {
export function ChatInputControls() {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟑 MEDIUM

Context-paths hook and IPC handlers are left with no remaining consumer

Deleting ContextFilesPicker.tsx and the showContextFilesPicker / hideContextFilesPicker props removes the only consumer of src/hooks/useContextPaths.ts, which in turn is the only caller of ipc.context.getContextPaths / ipc.context.setContextPaths. src/ipc/handlers/context_paths_handlers.ts stays registered and still does glob expansion plus token counting, but nothing in the renderer can reach it. This matches the PR's own note that persisted context scopes are ignored by the agentic runtime, so the surface is now unreachable infrastructure rather than a compatibility shim.

πŸ’‘ Suggestion: Delete useContextPaths.ts and the context-paths IPC handlers/contract in this PR, or add a comment stating why the channel is intentionally retained without a caller.

Comment thread e2e-tests/approve.spec.ts
testSkipIfWindows("write to index, approve, check preview", async ({ po }) => {
await po.setUp();
testSkipIfWindows("write to index and check preview", async ({ po }) => {
await po.setUp({ autoApprove: true });

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟑 MEDIUM

Legacy proposal approve/reject path loses all remaining test coverage

src/ipc/handlers/__tests__/reject.integration.test.ts is deleted, PageObject.approveProposal/rejectProposal are removed, and every remaining E2E call site (approve, debugging_logs, logs_server, visual_editing, package_manager) now opts into auto-approval instead. The approve/reject code path is still live, though: ChatInputActions renders whenever getProposal returns non-null, and proposal_handlers deliberately keeps returning legacy proposals for assistant messages without aiMessagesJson β€” i.e. exactly the pre-upgrade chats this compatibility path exists to serve. That path now ships with no automated coverage at any level, and ChatInput.tsx simultaneously relaxes its render guard by dropping the selectedMode !== "ask" && selectedMode !== "local-agent" condition.

πŸ’‘ Suggestion: Keep one integration test that seeds an assistant message with legacy XML content and a null aiMessagesJson, then drives the real Approve and Reject buttons, so the stored-response compatibility path stays verified.

@dyad-assistant

Copy link
Copy Markdown
Contributor

πŸ” Dyadbot Code Review Summary

Verdict: πŸ€” NOT SURE - Potential issues
Recommendation: ready

Build mode is routed through handleLocalAgentStream with a fail-closed toolProfile: "build" allowlist, and the capability seams look genuinely audited: entitlement check skipped, sub-agents/Engine/logs/verification/sandbox/MCP all gated off buildMode, suppressAutoReview plumbed end-to-end through ChatResponseEnd β†’ remote_manager β†’ host_transition β†’ subagentReviewOrchestration, and the cancel branch correctly kept on isRemediationTurn so a suppressed Build cancel cannot resume a paused queue. BUILD_MODE_TOOL_NAMES is asserted against TOOL_DEFINITIONS for usesEngineEndpoint/subagentOnly, and shouldIncludeTool filters the profile before every other inclusion rule, so the allowlist is genuinely closed. No HIGH issues found.

The findings below are all MEDIUM: one real defect in the new token accounting, one silent-failure mode in the legacy fixture adapter, and three consent/dead-code/coverage concerns that follow from retiring the Build controls.

Two confidence caveats: this review is based on the provided diff only β€” I did not execute the unit, integration, or Playwright suites, so I cannot confirm the rebased snapshots and the many adapted E2E fixtures actually pass. The diff is large (200 files, ~5.4k additions / ~19.2k deletions), and roughly half of it is snapshot/fixture churn that I reviewed at summary level rather than line by line.

Issues Summary

Severity File Issue
🟑 MEDIUM src/ipc/handlers/token_count_handlers.ts:148 Agent MCP tool tokens are undercounted because the cache is only populated in sandbox mode
🟑 MEDIUM testing/fake-llm-server/localAgentHandler.ts:131 Legacy fixture converter silently drops unconvertible tags and their preceding text
🟑 MEDIUM src/pages/settings.tsx:483 Retiring Build auto-approve silently enables auto-applied file writes with no migration
🟑 MEDIUM src/components/ChatInputControls.tsx:5 Context-paths hook and IPC handlers are left with no remaining consumer
🟑 MEDIUM e2e-tests/approve.spec.ts:5 Legacy proposal approve/reject path loses all remaining test coverage
🟒 Low Priority Notes (5 items)
  • estimateBuildModeToolTokens is production-only-for-tests - The exported wrapper is not referenced anywhere outside tool_definitions.test.ts; the handler calls estimateAgentToolTokens({ toolProfile: "build" }) directly. Either use it in token_count_handlers.ts or drop it and let the test pass toolProfile. (src/pro/main/ipc/handlers/local_agent/tool_definitions.ts)
  • Stale rules reference to a deleted component - rules/adding-settings.md still tells contributors to "follow AutoApproveSwitch.tsx as a template", but this PR deletes that file. rules/chat-modes.md and rules/e2e-testing.md were updated in the same PR, so this one looks like an oversight. (rules/adding-settings.md)
  • Assertion weakened without a matching code change - Two existing consent-migration tests were relaxed from toEqual to toMatchObject, but migrateStoredSettings itself is unchanged in this PR. toMatchObject will no longer catch an unintended extra consent key being injected by a future migration. (src/lib/schemas.test.ts)
  • Dead branch and unreachable guard - isLocalAgentBackedMode now returns true for every ChatMode, so the DyadError(..., DyadErrorKind.Internal) guard is unreachable and approvalState: willUseLocalAgentStream ? "approved" : null always takes the first branch. Both read as live logic. (src/ipc/handlers/chat_stream_handlers.ts)
  • .dyad/ files excluded from app-file snapshots - isIgnoredSnapshotFile now drops every path under .dyad/, which is broader than the previous .gitattributes-only ignore and will hide regressions in Dyad-managed runtime files from snapshotAppFiles. Consider narrowing to the specific volatile paths. (e2e-tests/helpers/page-objects/PageObject.ts)

Generated by Dyadbot persona-based code review

@keppo-bot

keppo-bot Bot commented Aug 27, 2026

Copy link
Copy Markdown
Contributor Author

Deflaked CI run https://github.com/dyad-sh/dyad/actions/runs/33116397081 in fe4aa7ddb.

The merged Playwright report had 14 failing specs (16 unexpected results, 0 flaky passes). They shared two deterministic stale-baseline causes from the preceding change:

  • broad message snapshots still expected environment-dependent source-file diff buttons that the stable ARIA normalizer now intentionally omits
  • referenced-app request snapshots still expected the old fixed tool list, including unavailable code_search, instead of the registered tools advertised at runtime

Regenerated only the 14 affected baselines and discarded an unrelated neighboring EOF rewrite.

Verification:

  • npm run build
  • exact 14 affected E2E cases: 14 passed
  • npm run fmt
  • npm run lint (3 existing warnings, 0 errors)
  • npm run ts

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

πŸ’‘ Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: fe4aa7ddbd

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with πŸ‘.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

// logs, verification commands, sandbox scripts, or MCP servers.
if (isBuildMode) {
const readOnlyBuildTurn = isSecurityReviewIntent || isSummarizeIntent;
finishedNaturally = await handleLocalAgentStream(

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve mutation state when cancelling Build

When a Build turn has already completed a mutating tool such as write_file and the user then presses Stop, this new route leaves those edits on disk, but cancelTrackedStreams unconditionally publishes updatedFiles: false at lines 645-650. ChatStreamRemoteManager gates preview auto-expansion, reload, and capture on that flag, so a cancelled Build can leave the UI showing the pre-edit app even though its files changed. Track whether the agentic Build mutated the workspace and propagate that fact through the cancellation terminal event. rules/chat-modes.mdL7-L7

Useful? React with πŸ‘Β / πŸ‘Ž.

@dyad-assistant dyad-assistant Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude review: 5 inline finding(s).

Comment thread src/pages/settings.tsx
@@ -482,13 +480,6 @@ export function WorkflowSettings() {
<DefaultChatModeSelector />
</div>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟑 MEDIUM

Build loses its approval gate with no migration for users who kept auto-approve off

Build previously never touched the working tree until the user clicked Approve (autoApproveChanges defaults to falsy, so this was the behavior for most users). After this PR, Build runs the agentic loop under agentToolConsents, where write_file, search_replace, delete_file, rename_file, copy_file, add_integration, enable_nitro and restart_app all default to "always". The auto-approve switch and its settings row are removed, and src/lib/schemas.test.ts explicitly asserts that a stored autoApproveChanges value is NOT migrated into consents. The net effect is that a user who deliberately opted out of auto-approve now gets file writes and deletions applied without any prompt or proposal review, with no in-app notice and no obvious path back other than discovering the renamed "Build and Agent Permissions" settings section.

πŸ’‘ Suggestion: Either migrate stored autoApproveChanges !== true into agentToolConsents "ask" for the mutating Build tools, or show a one-time notice pointing at Settings > Build and Agent Permissions the first time an agentic Build turn runs for a user who had auto-approve disabled.

// Generate requestId early so it can be saved with the message
dyadRequestId = uuidv4();
}
const willUseLocalAgentStream = isLocalAgentBackedMode(selectedChatMode);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟑 MEDIUM

Unreachable legacy Build streaming path is retained behind a hard throw

isLocalAgentBackedMode now returns true for every ChatMode, and the new guard throws DyadErrorKind.Internal when it is false, so the entire legacy non-agentic streaming tail (full-codebase injection, smart-context/deep-context handling, proposal generation, and the autoApproveChanges auto-approval check around line 2742) is dead code. The added comment even states that none of its auto-approval or destructive-SQL checks are live safety boundaries. Leaving several hundred lines of unreachable code that still contains auto-approval and SQL-execution logic is a maintenance hazard: a later refactor that relaxes the guard would silently re-activate an unaudited approval path, and the dead ternaries it leaves behind (for example approvalState: willUseLocalAgentStream ? "approved" : null, and the unused MAX_CHAT_TURNS_IN_CONTEXT import) obscure the actual control flow.

πŸ’‘ Suggestion: Delete the unreachable legacy branch in this PR, or file a tracked follow-up and mark the block clearly so it is removed once stored legacy responses no longer need migration support.

@@ -941,9 +939,7 @@ export function ChatInput({ chatId }: { chatId?: number }) {
{/* Only render ChatInputActions if proposal is loaded and no pending consent */}
{!pendingToolConsent &&
proposal &&

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟑 MEDIUM

Legacy approve/reject proposal flow stays reachable but loses all automated coverage

The mode gate on ChatInputActions is removed, so the Approve/Reject bar now renders in any mode whenever getProposal returns a proposal, which still happens for pre-agentic assistant messages that have XML content, no aiMessagesJson, and a null approvalState. That path still calls processFullResponseActions and rejectProposal in proposal_handlers.ts. At the same time this PR deletes reject.integration.test.ts and the approveProposal/rejectProposal page objects, and rewrites approve.spec.ts to skip approval entirely, so the shipping approve and reject code now has zero unit, integration, or E2E coverage while remaining user-reachable for existing chats.

πŸ’‘ Suggestion: Keep a minimal integration test for the legacy approve and reject handlers (or explicitly retire the UI and handlers if legacy proposals are no longer meant to be actionable).


let cachedMcpToolDefs: McpToolDef[] = [];

export function getCachedMcpToolDefs(): McpToolDef[] {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟑 MEDIUM

MCP token estimate reads a cache that is empty at startup and never invalidated

getCachedMcpToolDefs returns a module-level array that is only populated as a side effect of collectMcpToolDefs during an agent turn. token_count_handlers uses it to size MCP tool declarations, so after a fresh app launch the token bar reports zero MCP tokens for Agent mode until the user has run at least one Agent turn, even though those declarations are sent on the very first request. The cache is also never invalidated when a user enables, disables, or edits an MCP server, so the estimate can stay stale in the other direction for the rest of the session. Avoiding MCP connections during debounced counting is reasonable, but the resulting estimate is silently wrong in the common first-turn case.

πŸ’‘ Suggestion: Invalidate the cache when MCP server settings change, and/or persist the last known tool defs so the first token count after launch is not systematically low.

return match ? match[1] : null;
if (content.startsWith("Fix error: Error Line 6 error")) {
return "fix-runtime-error";
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟑 MEDIUM

Prompt-content heuristics override explicit tc=local-agent fixture selection

extractLocalAgentFixture now checks four hardcoded prompt prefixes/substrings (fix-runtime-error, fix-all-runtime-errors, fix-typescript-errors, security-fix) before it looks for the explicit tc=local-agent/ marker. Any test whose prompt happens to contain a phrase like "TypeScript compile-time error" is silently routed to the fix-typescript-errors fixture even when it explicitly named a different one, and the failure mode is a confusing wrong-response assertion rather than a clear fixture-not-found error. Explicit selection should win over content sniffing.

πŸ’‘ Suggestion: Match the explicit tc=local-agent/ (and tc=) markers first and only fall back to the generated-prompt heuristics when no marker is present.

@dyad-assistant

Copy link
Copy Markdown
Contributor

πŸ” Dyadbot Code Review Summary

Verdict: πŸ€” NOT SURE - Potential issues
Recommendation: ready

This PR routes Build through the native agentic loop with a fail-closed tool allowlist. The core mechanics hold up well under review: BUILD_MODE_TOOL_NAMES is an exact allowlist enforced in shouldIncludeTool, and the test asserts every entry is non-Engine and non-sub-agent; buildMode correctly gates sub-agents, MCP registration, sandbox execution, the review barrier, and the Pro entitlement check; suppressAutoReview is threaded end-to-end through the schema, transport, remote manager, host state, and renderer orchestration; and the double guard against replaying legacy proposal XML (eager approvalState: "approved" plus the aiMessagesJson check in getProposalHandler) is a genuinely good fail-closed design. Errors from the main/IPC layer use DyadError with sensible kinds, and no new IPC surface or filesystem/process access is exposed.

The issues below are all maintainability, migration, or test-coverage concerns rather than functional defects β€” none of them block merge.

Note on confidence: the combined diff blob in the context is truncated, but all 205 per-file patches are present and untruncated, so the review covers the complete change set.

Issues Summary

Severity File Issue
🟑 MEDIUM src/pages/settings.tsx:482 Build loses its approval gate with no migration for users who kept auto-approve off
🟑 MEDIUM src/ipc/handlers/chat_stream_handlers.ts:1646 Unreachable legacy Build streaming path is retained behind a hard throw
🟑 MEDIUM src/components/chat/ChatInput.tsx:941 Legacy approve/reject proposal flow stays reachable but loses all automated coverage
🟑 MEDIUM src/pro/main/ipc/handlers/local_agent/tools/mcp_type_defs.ts:45 MCP token estimate reads a cache that is empty at startup and never invalidated
🟑 MEDIUM testing/fake-llm-server/localAgentHandler.ts:900 Prompt-content heuristics override explicit tc=local-agent fixture selection
🟒 Low Priority Notes (9 items)
  • Tool-token estimate ignores dynamic input schemas - estimateAgentToolTokens serializes definition.inputSchema only, while buildAgentToolSet uses tool.getInputSchema?.(ctx) ?? tool.inputSchema. Today this only affects spawn_agent, whose fallback schema is smaller than the persona-specific one. (src/pro/main/ipc/handlers/local_agent/tool_definitions.ts)
  • System-prompt estimate is approximate, not exact - The token-count handler omits codeExplorerAvailable, historyExplorerAvailable, basicAgentMode, preCommitHookAvailable, and the restart/reinstall availability flags that the real turn passes to constructSystemPrompt, so the estimated prompt can differ from what is actually sent. Worth softening the "exact" framing in the PR description. (src/ipc/handlers/token_count_handlers.ts)
  • sql<boolean> relies on SQLite 0/1 truthiness - hasAiMessagesJson is typed boolean but better-sqlite3 returns 0/1. The current if (...) check is correct, but a future === true comparison would silently break the guard. Casting in SQL or normalizing the value would be safer. (src/ipc/handlers/proposal_handlers.ts)
  • ARIA snapshots now drop all source-file diff buttons - isEnvironmentDependentFileDiffButton removes every A/M/D <path> +N -N button (except pnpm-lock.yaml) from message snapshots, so roughly 20 snapshot files no longer assert which files a turn changed. Reasonable for stability, but it is a real reduction in assertion strength. (e2e-tests/helpers/utils/stable-aria-snapshot.ts)
  • "Max Tool Calls (Agent)" label not updated - The step limit now governs Build turns too, but the setting label and search entry still say "(Agent)", unlike the agentPermissions and enableAutoReview strings which were updated. (src/lib/settingsSearchIndex.ts)
  • estimateBuildModeToolTokens is only used by tests - The exported wrapper has no production caller; consider inlining estimateAgentToolTokens({ toolProfile: "build" }) in the test instead. (src/pro/main/ipc/handlers/local_agent/tool_definitions.ts)
  • Legacy fixture adapter can throw on malformed tags - attributes.packages.split(...) dereferences an attribute that may be absent for a <dyad-add-dependency> tag, producing a TypeError instead of a clear fixture error. (testing/fake-llm-server/localAgentHandler.ts)
  • App blueprint disabled in every Build E2E setup - setUp/setUpDyadPro/setUpAzure now call disableAppBlueprint() for Build, so the blueprint flow under the new agentic Build has no direct coverage; it is only exercised indirectly through the Agent specs. (e2e-tests/helpers/page-objects/PageObject.ts)
  • Always-true ternary after the new guard - approvalState: willUseLocalAgentStream ? "approved" : null can never take the null branch given the throw a few lines above. (src/ipc/handlers/chat_stream_handlers.ts)

Generated by Dyadbot persona-based code review

@github-actions

Copy link
Copy Markdown
Contributor

🎭 Playwright Test Results

❌ Some tests failed

OS Passed Failed Flaky Skipped
🍎 macOS 278 2 0 12

Summary: 278 passed, 2 failed, 12 skipped

Failed Tests

🍎 macOS

  • queued_message.spec.ts > queued messages > stops and parks an unpaused queue without an error toast
    • Error: expect(locator).toBeVisible() failed
  • setup_flow.spec.ts > Setup Flow > Google API key setup resumes an attachment-only first prompt
    • Error: expect(locator).toBeVisible() failed

πŸ“‹ Re-run Failing Tests (macOS)

Copy and paste to re-run all failing spec files locally:

npm run e2e \
  e2e-tests/queued_message.spec.ts \
  e2e-tests/setup_flow.spec.ts

πŸ“Š View full report

@wwwillchen
wwwillchen merged commit 1a94b00 into main Aug 27, 2026
11 of 16 checks passed
@wwwillchen
wwwillchen deleted the codex/agentic-build-mode branch August 27, 2026 22:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs-human:review-issue ai agent flagged an issue that requires human review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant