Skip to content

fix(plugin-chatbot): a plan that overlaps an existing app asks whether to extend it or build a new one, instead of offering Build it - #27595

Merged
objectstack-fleet[bot] merged 5 commits into
mainfrom
claude/project-thread-n5xd7s
Oct 29, 2026
Merged

objectstack-fleet[bot] merged 5 commits into
mainfrom
claude/project-thread-n5xd7s

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Bill · project thread

Fixes #27480
Clause-②: yes (widening)
Metadata-protocol: none

维护者速读

  • 改了什么:AI 搭建方案若与用户已有的应用重叠(结果带 decision: 'iterate_or_new'),方案卡不再显示「开始搭建」,改为提问「在「X」上继续完善,还是另建一个独立应用」,两个选项各自发出对应回复;普通方案不变。
  • 为什么改:之前这类结果被画成普通方案卡,一键「开始搭建」就会造出一个和已有应用重复的新应用。
  • 风险与回滚:只在识别到 iterate_or_new 时换掉按钮行,其余方案走原路径;回复文字按会话语言发出,且不会被确认门当作同意;回滚即还原本 PR。
  • 先看:ui/packages/plugin-chatbot/src/ChatbotEnhanced.tsx 的 IterateOrNewQuestion 与按钮行三分支;其次 mapMessages.ts 的 liftIterateOrNew;再看 useChatConversation.ts 缓存回写。
  • 两分钟验证:pnpm --dir ui exec vitest run packages/plugin-chatbot/src/__tests__/ChatbotEnhanced.iterateOrNew-27480.test.tsx,预期 10 个测试全绿。
  • 需要你做的:无,席位落地。

What changes

  • Detection (mapMessages.ts). detectProposedPlan lifts decision: 'iterate_or_new' into a new optional ProposedPlan.iterateOrNew, with the overlapping app's name and optional label read from existingAppOverlap. A result with the decision but no objects still yields a plan, so the card asks the question instead of falling back to the generic "Build it" card. An overlap that names no app yields iterateOrNew: {}.
  • The question (ChatbotEnhanced.tsx). For such a plan the card keeps its rows and replaces "Build it" / "Adjust" with IterateOrNewQuestion. The question names the app (label, else the host's resolveAppLabel(name), else name). Its two choices are "Keep improving X" and "Build a separate, new app", and each sends its answer through onSendMessage. Without a send channel, or without an app name, the card shows the question and a reply hint and offers no one-click choice. Once a build has run after the plan, the question stays and the choices go, as the plan's own buttons already do.
  • Outbound answers. Two new optional props, planExtendExistingMessage(app) and planBuildNewAppMessage, with English defaults. The console wires both through resolveOutboundAgentText, so they follow the conversation's language like the plan card's other outbound messages. The new keys console.ai.planExtendExistingMessage and console.ai.planBuildNewAppMessage are in the en and zh packs only, as the other outbound keys are.
  • Reload. Server history re-parses the stored result with the same detector. The message cache's compact plan envelope (proposedPlanToCachedResult) now writes back decision and existingAppOverlap, so a cache-fallback reload asks the question too.
  • Labels. chatbot.plan.iterateOrNew.{question, questionUnnamed, extend, buildNew, replyHint} in all ten packs.
  • Docs. One paragraph in content/docs/frontend/plugins/plugin-chatbot.mdx after the plan-card description.
  • One patch changeset for @object-ui/plugin-chatbot, @object-ui/app-shell and @object-ui/i18n.

Tests

  • mapMessages.iterateOrNew-27480.test.ts (7): the lift from the bare result and from the {type, value} wrapper; a plain result has no iterateOrNew; another decision value is ignored; a result with no objects still yields a plan; label is optional and an overlap with no app gives {}; the live mapper carries the field.
  • ChatbotEnhanced.iterateOrNew-27480.test.tsx (10): no approve, adjust or fallback button and the question names the app (asserted first); the plain proposal keeps "Build it" and "Adjust" (control); each choice sends its answer; the host's outbound text is sent when supplied; zh labels; no objects still asks; resolveAppLabel; the unnamed overlap; no onSendMessage; the settled state after a build.
  • AiChatPage.iterateOrNewReload-27480.test.tsx (3): server history re-parses to the question; the cache fallback keeps both keys and both re-maps render the question; a plain proposal is cached without the keys (control).
  • AiChatPage.planCardLocale.test.tsx: the two answers follow the conversation, not the UI locale (zh thread under en/zh/de UI, en thread under zh/en/de UI).
  • outboundAgentText.test.ts, outbound-agent-messages.test.ts, all-locales-key-parity.test.ts, console-namespace-3546.test.tsx: the two outbound keys are registered. i18n.test.ts: neither answer, in zh or en, matches the confirm gate's approval pattern.

Acceptance notes

  • The result shape is read flat and unverified against the producer. The cloud half (cloud#3369) was not readable from this container, so the reader takes existingAppOverlap.name / .label as the card quotes them and tolerates their absence: a missing or differently nested app gives the unnamed question with a reply hint, never the "Build it" card. The cloud#3369 owner should confirm the nesting.
  • Ablation through scripts/ablation-replace.mjs, each anchor x1 to x0 with the blob changed, and each file restored to equal HEAD with git diff HEAD empty:
    • A1, the lift always returns nothing (mapMessages.ts): 16 red, 4 green (the plain controls) over the three new files.
    • A2, the question face off (ChatbotEnhanced.tsx): 11 red, 9 green, with the mapper tests green. A2b, run alone on the first test, fails on the missing-approve assertion.
    • A3, the cache write-back off (useChatConversation.ts): only the cache-fallback test red.
    • A4, the console's outbound wiring bypassed (AiChatPage.tsx): the six conversation-language tests red.
  • Three i18n test files outside the named surface (all-locales-key-parity, console-namespace-3546, i18n.test.ts) gained the two keys. They are the same key ledgers the named outbound-agent-messages.test.ts reads.
  • The plugin README lists no plan-card props, so it is unchanged. The docs paragraph is the documented surface.

Verification

See the PR's check-runs for every family CI runs. Local readings (targeted vitest, type-check of the three packages, the derived gate list) are in the os-dev report on #27480.


Generated by Claude Code

🤖 Generated with Claude Code

https://claude.ai/code/session_01Utp6Ca9ZbSdPg2iuzBYA3d

…tead of offering Build it

A propose_blueprint result whose decision is iterate_or_new overlaps an app
the user already has. The chat drew it as an ordinary plan card with a
"Build it" button, so one click could build a duplicate of that app.

detectProposedPlan now lifts the decision and existingAppOverlap's name /
label as ProposedPlan.iterateOrNew, and the plan card draws the
extend-or-new question in place of its approve / adjust actions: the
question names the existing app, and its two choices send "keep improving
APP" or "build a separate app". The two replies are outbound text, so the
console resolves them by the conversation's language through
resolveOutboundAgentText, like every other outbound message. The
cache-fallback reload keeps the question (proposedPlanToCachedResult writes
both keys back); the server-history reload re-parses the stored result.

Claude-Session: https://claude.ai/code/session_01Utp6Ca9ZbSdPg2iuzBYA3d
Co-authored-by: Claude <noreply@anthropic.com>
… reload

The mapper lifts the question from the envelope (plain proposal and other
decisions lift nothing; no objects still asks); the card asks instead of
offering Build it and each choice sends its answer; the question survives
both reload paths (server history and cache fallback); the two answers
follow the conversation's language and never read as approval to the cloud
confirm gate.

Claude-Session: https://claude.ai/code/session_01Utp6Ca9ZbSdPg2iuzBYA3d
Co-authored-by: Claude <noreply@anthropic.com>
…estion

The done-when assertion goes first, so an ablation of the question face
fails on the Build it button it brings back.

Claude-Session: https://claude.ai/code/session_01Utp6Ca9ZbSdPg2iuzBYA3d
Co-authored-by: Claude <noreply@anthropic.com>
…te_or_new question

Claude-Session: https://claude.ai/code/session_01Utp6Ca9ZbSdPg2iuzBYA3d
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/l documentation Improvements or additions to documentation tests tooling labels Oct 29, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:ui#3 · os-bill · session_01Utp6CaJoinvGQSbHeDaWBe
Contract-review-claim: session_01Utp6CaJoinvGQSbHeDaWBe · head f54fe51 · 2026-10-29T14:25Z

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:ui#3 · os-bill · session_01Utp6CaJoinvGQSbHeDaWBe

Contract review

Served-tier: TIER_DEFAULT
Head-sha: f54fe51d45d48e18670edbc5d0239d3fbe951201
Local-runs: none

Face: declaration only (Clause-②: yes (widening) on the amended claim, the PR body and the changeset). The committed tree touches no packages/spec/src/**, no governed path and no narrowing line. Reviewed 2026-10-29T14:25Z from the diff, the card and the head's check-runs.

① Derived judgments

  • @object-ui/plugin-chatbot (public, exports . → dist/index.d.ts): ChatbotEnhancedProps gains two OPTIONAL props, planExtendExistingMessage?: (app: string) => string and planBuildNewAppMessage?: string, each defaulted in the component to the English text, so every existing host renders as before. Correct and additive.
  • ProposedPlan (re-exported from src/index.tsx) gains optional iterateOrNew?: ProposedPlanIterateOrNew; the new interface is reachable through that prop. detectProposedPlan now also returns a plan for an iterate_or_new envelope with zero objects — a widening of what the detector accepts, scoped to that one decision value; every other envelope with no objects still returns undefined. Correct: the card is the question, and the fallback "Build it" card is exactly the defect.
  • @object-ui/i18n (public; en and TranslationKeys = typeof en are exported): gains chatbot.plan.iterateOrNew.{question, questionUnnamed, extend, buildNew, replyHint} in all ten packs and console.ai.{planExtendExistingMessage, planBuildNewAppMessage} in en/zh only, the existing convention for outbound keys (outbound-agent-messages.test.ts is the ledger). Additive; no key renamed or removed.
  • @object-ui/app-shell is private: true: the OutboundAgentTextKey union and GATE_TEXT widening are not a published surface.
  • No accept set narrows. The two outbound answers are pinned (i18n.test.ts) not to match the confirm gate's approval pattern, so the widening adds no approval path.

② Semver level

patch for the three packages, matching Metadata-protocol: none (AGENTS.md Post-Task Checklist step 3: the level follows the metadata-protocol kind; none ships patch). Consistent with the claim and the PR body.

③ Boundary flags

  • The dev's open_questions is empty.
  • Deviation "result shape read flat, unverified against cloud": answered — the reader tolerates absence (unnamed question + reply hint, never the approve card), so a different nesting fails safe. The confirmation belongs to the cloud#3369 owner and is recorded in the PR's Acceptance notes; not a must-fix.
  • Deviation "three i18n ledger tests outside the named surface": same key ledger as the named file; accepted.
  • PR author is objectstack-fleet[bot] (relay write).

Implemented-by: claude/project-thread-n5xd7s
Reviewed-by: session_01Utp6CaJoinvGQSbHeDaWBe

VERDICT: PASS


Generated by Claude Code

…te census

The de pack's quote census pins every correctly paired „…“ span. The
iterate_or_new question and its extend choice each name the existing app
(„{{app}}“), so the census moves from 98 to 100 paired spans.

Claude-Session: https://claude.ai/code/session_01Utp6Ca9ZbSdPg2iuzBYA3d
Co-authored-by: Claude <noreply@anthropic.com>
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:ui#3 · os-bill · session_01Utp6CaJoinvGQSbHeDaWBe
Contract-review-claim: session_01Utp6CaJoinvGQSbHeDaWBe · head e03da18 · 2026-10-29T14:57Z

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:ui#3 · os-bill · session_01Utp6CaJoinvGQSbHeDaWBe

Contract review

Served-tier: TIER_DEFAULT
Head-sha: e03da181e7c19749b6943ab17a7cddadc6db6138
Local-runs: none

Face: declaration only. Delta review by the same reviewer as the PASS on f54fe51d45d48e18670edbc5d0239d3fbe951201: the new head is that head plus one commit (compare ahead_by 1), touching only ui/packages/i18n/src/__tests__/de-quote-pairing-3876.test.ts (+8/-2). Reviewed 2026-10-29T14:58Z from the compare and the card.

① Derived judgments

  • The delta moves the de-pack quote ledger from 98 to 100 paired spans (okSpans, and { open, close, rdq } 98/98/0 → 100/100/0), with a comment naming chatbot.plan.iterateOrNew.question and .extend, each one interpolated „{{app}}“ span. That matches the two de keys the PASSed head added; the durable identity close === open + rdq is unchanged and still asserted. No source, export, key or accept set changes; the derived judgments of the earlier record stand unchanged.

② Semver level

Unchanged: patch for the three packages, Metadata-protocol: none. A test-only commit adds no changeset and needs none.

③ Boundary flags

  • No new open_questions or deviations; the commit is the census update the red Test UI (4/16) asked for (reproduced red at the old counts by the dev).
  • PR author is objectstack-fleet[bot] (relay write).

Implemented-by: claude/project-thread-n5xd7s
Reviewed-by: session_01Utp6CaJoinvGQSbHeDaWBe

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:ui#3 · os-bill · session_01Utp6CaJoinvGQSbHeDaWBe

席位核对

ACCEPT · head e03da181e7c19749b6943ab17a7cddadc6db6138 · 2026-10-29T15:15Z

  • Shape: draft → main, Fixes #27480 on its own line, no other card number beside a closing keyword. Clause-②: yes (widening) and Metadata-protocol: none match the amended claim; changesets patch for @object-ui/plugin-chatbot, @object-ui/i18n and @object-ui/app-shell.
  • Contract review: same-head TIER_DEFAULT declaration-face record PASS (comment 6500179221, delta over the full PASS 6499363514 on f54fe51d). No spec, governed or narrowing path.
  • Premise, measured: an iterate_or_new plan envelope with no objects now renders the extend-or-new question (IterateOrNewQuestion) instead of the fallback "Build it" card; both answers go out through the outbound-agent text keys, pinned not to match the confirm gate's approval pattern; the conversation cache write-back keeps the question across a reload.
  • Gates (check-runs on the head): 51 success, 8 skipped, 0 failure (the earlier red Test UI (4/16) was the de quote census, fixed by the delta commit). check-governed-merges --pr 27595: NOT governed, 786 changed lines.
  • Accepted deviations: result shape read flat, unverified against cloud (fails safe to the unnamed question; confirmation belongs to the cloud#3369 owner, noted in Acceptance notes); three i18n ledger tests outside the named surface (same ledger).
  • Landing: non-governed, all checks green, mergeable_state clean ⇒ the seat lands it (pr_ready + automerge_enable).

Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 29, 2026 15:17
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 29, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Oct 29, 2026
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 29, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:ui#3 · os-bill · session_01Utp6CaJoinvGQSbHeDaWBe

Merge-queue kick-out, 2026-10-29T15:24Z: the only red in group gh-readonly-queue/main/pr-27595-178b5238 was Governed Surface Queue Guard (run 40900057502), exit 9. Per check-governed-queue-guard.mjs exit 9 = the SIZE leg could not READ the queued pull request's size: a read failure, not a verdict. The PR is NOT governed (check-governed-merges --pr 27595) and 786 changed lines, under the human-merge line; every other check in the group (CI, Lint & Type Check, Spec Liveness, PR-Body Guards) was green or still running, and the guard passed on the groups queued just before and after (#27612, #27613, #27621). Head unchanged (e03da181), ACCEPT and contract PASS stand. One re-enqueue (automerge_enable); a second exit 9 stops it and goes to the queue guard's owner. · 2026-10-29T15:26Z

Merged via the queue into main with commit a552e86 Oct 29, 2026
60 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/project-thread-n5xd7s branch October 29, 2026 15:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/l tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ai chat: render a blueprint proposal with decision iterate_or_new as the extend-or-new question, not as a 开始搭建 approval card

1 participant