mirror of
https://github.com/nexu-io/open-design.git
synced 2026-07-08 07:19:17 +08:00
codex/amr-client-structured-errors
4 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
ca22620b4f |
refactor(chat): unify ask-user flow on <question-form>, delete AskUserQuestion mechanism (#4114)
* refactor(chat): unify ask-user flow on <question-form>, delete AskUserQuestion mechanism
There were two parallel "ask the user a clarifying question" mechanisms:
A. The AskUserQuestion SDK tool — an inline interactive card in the chat
stream (AskUserQuestionCard / StreamingAskUserQuestionCard), whose answer
was fed back into the still-open stream-json child via
POST /api/runs/:id/tool-result + submitChatRunToolResult.
B. The <question-form> markdown artifact — a QuestionsBanner entry point in
chat, the form rendered in the right-hand Questions tab
(QuestionsPanel / QuestionFormView), answers returned as the next user
message.
Two visual languages for the same job, and mechanism A's card was visually
broken (the text status "Awaiting your answer" was stuffed into the 24px
.op-status icon badge and overflowed onto the title). Mechanism A was pinned to
mid-conversation clarification while B was pinned to turn-1 discovery — an
artificial split.
This deletes mechanism A entirely and keeps only B, and widens <question-form>
from "turn-1 discovery only" to ANY turn so mid-conversation clarification flows
through the same banner → Questions tab → next user message loop.
Web:
- Delete apps/web/src/runtime/ask-user-question.ts (parser trio).
- ToolCard.tsx: drop the AUQ card/streaming-card/dispatch/imports and the
onAnswerToolUse / onSubmitForm props.
- AssistantMessage.tsx: drop the live AUQ render path,
suppressAskUserQuestionFallbackText, the AUQ name from SNAPSHOT_TOOL_NAMES,
and the onSubmitForm bridge. Keep suppressDuplicateQuestionForms.
- Delete the whole onSubmitForm / onAssistantFormSubmitStart return chain
across AssistantMessage / ChatPane / ProjectView / SideChatTab, and
submitChatRunToolResult in providers/daemon.ts.
Daemon (conservative — keep the generic stream-json input skeleton):
- applyClaudeStreamJsonRunBookkeeping: drop the AUQ detection branch and
run.pendingHostAnswers; stdin now closes on any non-tool_use clean terminal
turn.
- Delete run-tool-results.ts, submitToolResultToRun, and the
POST /api/runs/:id/tool-result endpoint.
- run-artifacts.ts:runAskedUserQuestion now scans streamed text for a
<question-form> marker (reassembled across text_delta chunks) instead of an
AUQ tool_use, preserving the run_finished.asked_user_question analytics
signal.
- Keep --input-format stream-json / promptInputFormat: 'stream-json' as generic
mid-turn input infra; refresh the comments that referenced AUQ.
System prompt / contracts:
- Replace the Claude-only AskUserQuestion clarification section with generic
"emit <question-form> for mid-conversation clarification" guidance; relax the
API-mode override wording; drop "Do not call AskUserQuestion" from the
skip-discovery override.
i18n / CSS:
- Remove the tool.askQuestion* keys from types.ts + all locales.
- Remove the op-ask-question* block and .op-status-awaiting from tools.css;
keep .op-status (the icon badge) and the qf-* classes that mechanism B uses.
Docs / tests:
- AGENTS.md: rewrite the runtime-conventions + chat-UI sections; add an
"Asking the user questions" section documenting the single mechanism.
- Delete the AUQ-only tests; rewrite the quiet-period bookkeeping, run-artifacts,
prompt, and plugin-folder tests to the new behavior.
Validation: pnpm guard (54), full typecheck, web tests, daemon suite
(4351 pass), contracts tests; replayed two Claude stream-json traces through the
mock CLI (incl. the [form answers — discovery] trace) — 0 parse failures, clean
end_turn termination.
* fix(chat): unify mid-conversation question-form across API/BYOK + analytics alias
Address review feedback on the ask-user unification:
- contracts composer (used by API/BYOK 'plain' mode) now mirrors the
daemon-side '## Clarifying questions mid-conversation' section and widens
the API_MODE_OVERRIDE allowed-output list from 'discovery on turn 1' to
'discovery (turn 1) and mid-conversation clarification', so BYOK/API runs
route follow-up choices through the unified Questions tab instead of
drifting back to plain markdown option lists. Makes the AGENTS.md
'mirrored through packages/contracts/src/prompts/system.ts' claim true.
- runAskedUserQuestion now matches the '<ask-question>' alias (already
whitelisted by the UI parser and daemon open-tag matcher), so a model that
drifts to the alias still records run_finished.asked_user_question instead
of being misclassified in the artifact funnel.
- Tests: contracts api-mode asserts mid-conversation forms are permitted in
both daemon and plain mode; run-artifacts asserts the alias is detected.
* fix(chat): make <ask-question> alias consistent across all paths + legacy AUQ renderer
Follow-ups from re-review of the ask-user unification:
- The <ask-question> alias is already accepted by the UI parser and the
daemon open-tag matcher, but two consumers still only recognized the
canonical <question-form>: db.listProjectsAwaitingInput's awaiting-input
filter and the next-turn transcript sanitizer. Both now match either tag,
so an alias-form turn (a) marks the project awaiting input and (b) gets
scrubbed from the prior assistant turn instead of replaying verbatim and
re-triggering the discovery-form loop.
- Re-add a read-only legacy AskUserQuestion renderer in ToolCard. The
interactive mechanism is gone, but persisted AUQ tool_use events survive
in upgraded chat history; without this they fell through to GenericCard and
surfaced the raw {"questions":[...]} JSON. The card now renders an inert
question summary (model-authored text, no new i18n keys, no submit path).
Tests: alias awaiting-input case (project-status), alias-form sanitizer
regression (sse), and legacy AUQ render + unparseable fallback (tool-renderers).
* fix(chat): correct asked_user_question detector + legacy AUQ answer rendering
Re-review follow-ups:
- runAskedUserQuestion read `data.text` for text_delta events, but the
persisted SSE shape carries the chunk on `delta` (packages/contracts/src/
sse/chat.ts), so production <question-form> runs appended nothing and
run_finished.asked_user_question was permanently false (the old tests passed
only because they built the wrong `text` field). Now reads `delta` (still
accepts `text` defensively).
- It also matched a bare open-tag, so a run that merely shows the literal
markup in a generated doc / code sample / HTML artifact could be
misclassified as a clarification turn and excluded from the artifact funnel.
Now requires a renderable closed block via a shared detector.
- Extracted the renderable-form detector + helpers out of server.ts into
apps/daemon/src/question-form-detect.ts so the missing-artifacts guard,
awaiting-input status, and run analytics share ONE check (the codebase
comment already anticipated promoting this once it had a third consumer).
The `<ask-question>` alias is covered by that shared matcher.
- LegacyAskUserQuestionCard now parses the persisted answer from
result.content and surfaces it ("question -> answer") so two historical AUQ
runs with different answers no longer render identically.
Tests: run-artifacts now uses the real `delta` shape + renderable bodies, adds
a literal-markup negative case; tool-renderers asserts the persisted answer is
surfaced.
---------
Co-authored-by: qiongyu1999 <2694684348@qq.com>
Co-authored-by: lefarcen <935902669@qq.com>
|
||
|
|
10e2019c59 |
Fix plugin publish and Open Design PR workflow UX (#2564)
* Fix plugin publish and PR workflow UX * Update plugin workflow test expectations * Fix fake gh repo view verification path * Fix plugin publish headless tests and preserve PATH in shell wrappers. The publish-repo flow needs real git commits and fake gh auth output that matches gh auth status parsing. Login shells no longer drop PATH so test fakes and agent wrappers stay visible to nested gh/git calls. Co-authored-by: Cursor <cursoragent@cursor.com> * Restore plugin action card when share-task startup fails. If startGeneratedPluginShareTask rejects before a task is created, clear hiddenAssistantPluginActionPaths so the assistant action card reappears. Co-authored-by: Cursor <cursoragent@cursor.com> * Make daemon vitest self-contained for publish-github CLI shell-outs. Build dist/cli.js in tests/setup.ts when missing and set OD_DAEMON_CLI_PATH before server.ts resolves OD_BIN, so headless plugin tests pass from a clean checkout without a prior manual daemon build. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com> |
||
|
|
c617e30e27 |
fix(plugins): make Publish repo actually create the author's repo (#2332) (#2363)
* fix(plugins): make Publish repo actually create the author's repo (#2332) QA repro from 0.8.0 preview: clicking "Publish repo" on a generated plugin's `DesignFilesPanel` card ran the agent down a path that produced an Open Design registry-submission URL but never created the author's GitHub repo. After the action finished, `gh repo view nuomi/cat` still returned 404 and `git ls-remote https://github.com/nuomi/cat.git HEAD` failed with "Repository not found". Root cause is the action prompt at `apps/web/src/components/design-files/pluginFolderActions.ts:11-12`: publish: 'Use the supported `od plugin publish` or repository-publish flow after confirming the manifest.' That sentence let the agent pick the legacy registry-link CLI (`od plugin publish --to open-design`), which mirrors the path the "Open Design PR" button takes and emits an issue URL instead of creating a public repo. The button label said "Publish repo" but the behavior collapsed onto the registry-submission flow. This PR rewrites the `publish` prompt in the same shape PR #2182 used for `contribute` — a numbered gh + git sequence that drives the real action end-to-end: 1. Pre-flight `gh --version` / `gh auth status`. Invalid / expired tokens are treated the same as not-logged-in (the bug-report agent kept going past an "invalid token" warning). 2. Read manifest, capture `name`, `version`, `description`, `plugin.repo`. Fall back to `https://github.com/<gh-login>/<name>` when `plugin.repo` is missing and write it back into the manifest. 3. `gh repo view <owner>/<name>` to decide create-vs-update. 4a. Repo does not exist → `git init` + commit + tag + `gh repo create <owner>/<name> --public --source . --push`. 4b. Repo exists → reuse the remote, `git add -A` + `git commit -m "Update: <name> v<version>"` (skip if working tree is clean), `git tag v<version>` (skip if already published), `git push`. 5. Verify with `gh repo view <owner>/<name> --json url,nameWithOwner`. 6. Hand off the resolved `https://github.com/<owner>/<name>` URL to chat. End the turn. Hard constraints encoded in the prompt: * Do NOT call `od plugin publish --to open-design` (or any `--to <catalog>` variant). That is the registry-submission flow. * Do NOT call `AskUserQuestion` — fire-and-forget, same as the `contribute` flow's stall fix. * Do NOT auto-install gh/git. Detect-and-instruct only. * Do NOT force-push or overwrite a published tag. * Do NOT retry a failed step. Report and stop. Refactor: pulled `publish` out of the shared `ACTION_TITLES`/ `ACTION_NOTES` template into its own `buildPublishPrompt(folderPath)` function (mirrors `buildContributePrompt` from PR #2182). `install` keeps the simple shared template — that action stays inferrable from the manifest and doesn't need the same blast radius. Tests: * `apps/web/tests/components/pluginFolderActions.test.ts` — extends the existing contract suite with seven new `publish` assertions: targets `plugin.repo` not the registry catalog, drives the full gh + git command list, handles both new-repo and existing-repo branches, explicit ban on the registry-submission CLI, hard-bans on AskUserQuestion / auto-install / force-push / retry, "invalid token" treated as STOP, `${folderPath}` interpolation guard, ends by handing the repo URL back to chat. Validation: * `pnpm --filter @open-design/web exec vitest run tests/components/pluginFolderActions.test.ts` → 16/16 passed (was 9/9 before this PR; +7 new publish-flow assertions, the old generic "mentions od plugin publish" assertion replaced with the precise contract above) (Local `pnpm --filter @open-design/web typecheck` fails on `tests/runtime/exports.test.ts` because `packages/host/dist/testing` isn't built in this checkout — pre-existing breakage from `2c128e0e refactor desktop host bridge` on main, unrelated to this prompt change. CI runs a fresh install and was green on the four previous prompt-only PRs that touched the same module.) Closes #2332. * fix(plugins): don't assume standalone jq when reading the manifest QA repro from the Open Design PR button (transcript shared with the PR #2363 thread): the agent reached step 2 of the contribute prompt, ran `jq '{name,title,description,version}' generated-plugin/open-design.json`, got `zsh:1: command not found: jq`, and stopped per the prompt's "stop on first hard failure" rule. No fork, no branch, no PR. jq is not part of the OD agent runtime baseline — default macOS and Windows shells don't ship it. The agent reached for it first because "jq" is the default JSON tool in claude/codex's shell training distribution, not because the prompt asked for it. The prompt just said "Load and capture", which the agent interpreted as "shell out to the most common JSON parser". Updates both step-2 instructions (contribute + publish prompts) to: - List portable manifest-read alternatives in priority order: the built-in Read tool (always available); `cat` + manual JSON parsing; `node -e 'JSON.parse(...)'` as the shell-only fallback. - Add an explicit "Do not assume the standalone `jq` binary is installed" guard with the macOS / Windows shell rationale. - Disambiguate the standalone `jq` CLI from `gh ... --jq`. The gh flag uses an embedded library and is fine — without the disambiguation the agent reads the ban literally and stops using `gh api user --jq .login` at step 3. Tests: * `apps/web/tests/components/pluginFolderActions.test.ts` — two new contract assertions: - publish prompt: warns against assuming standalone jq is installed; lists cat and node -e as alternatives. Closes the regression on its own surface. - shared block: both contribute and publish prompts disambiguate standalone jq from `gh ... --jq`. One assertion guards both flows so a future prose edit can't drop the carve-out on one side. Validation: * `pnpm --filter @open-design/web exec vitest run tests/components/pluginFolderActions.test.ts` → 18/18 passed (was 16/16 on the previous PR #2363 commit; +2 new jq-guidance assertions) Continues PR #2363. Same source-of-bug shape as the registry-submission fallback issue this PR was opened to fix: agent picks a tool the prompt didn't actually ask for because the prompt was loose. |
||
|
|
387dc83b27 |
fix(plugins): wire Open Design "Open Design PR" button end-to-end (#2182)
Two-part fix for the Plugin folder card's "Open Design PR" button. The
flow was broken end-to-end: the CLI emitted a 404 URL, and the agent
prompt under-specified the contribution steps so the agent stalled
mid-turn or fell back to the legacy issue-URL path.
Catalog target — `apps/daemon/src/plugins/publish.ts`:
`buildPublishLink({ catalog: 'open-design' })` hardcoded a submission URL
at `github.com/open-design/plugin-registry`, the dedicated registry repo
proposed in docs/plans/plugin-registry.md §1.2. That repo doesn't exist
yet (P3.1 notes "creating the external GitHub repo is an operational
launch step, not a code blocker"), so every generated URL 404'd. Retarget
the catalog at the live `nexu-io/open-design` monorepo and update the
target-path hint in the PR body to `plugins/community/<plugin-name>/`
(the actual layout under main). Plan §1.2 stays the long-term goal — see
the code comment.
Also updates the matching `od plugin yank` issue URL in cli.ts.
Agent prompt — `apps/web/src/components/design-files/pluginFolderActions.ts`:
The `contribute` action prompt only said "use the supported `od plugin
publish` Open Design registry flow", which produces an issue URL (the
legacy path) and left the agent to invent the remaining steps. The agent
ended up calling `AskUserQuestion` mid-turn waiting for input that the
DesignFilesPanel buttons couldn't satisfy, then stalled for 600s.
Rewrite the `contribute` prompt to drive the full PR flow via raw `gh`
commands:
1. preflight `gh --version` / `gh auth status` (detect-and-instruct on
missing CLI, never auto-install anything)
2. read manifest, resolve author login
3. `gh repo fork nexu-io/open-design --remote=false`
4. clone fork, branch, cp plugin into `plugins/community/<name>/`,
commit/push using author's git identity (not a hardcoded bot)
5. `gh pr create ... --web` so the author reviews and clicks Create
Hard bans on `AskUserQuestion`, retry-on-failure, and the legacy
`od plugin publish --to open-design` CLI keep the turn fire-and-forget.
The `install` / `publish` action prompts are unchanged.
Tests:
* `apps/daemon/tests/plugins-publish.test.ts` — assert URL host +
catalogLabel match `nexu-io/open-design`, body contains the new path
hint.
* `apps/web/tests/components/pluginFolderActions.test.ts` (new) — lock
the contribute prompt's command surface, the `--web` review window,
the `AskUserQuestion` ban, the install-tool ban, and folder-path
interpolation. Nine assertions covering the prompt contract without
coupling to exact wording.
Validation:
* `pnpm --filter @open-design/daemon exec vitest run tests/plugins-publish.test.ts`
→ 11/11 passed
* `pnpm --filter @open-design/web exec vitest run tests/components/pluginFolderActions.test.ts`
→ 9/9 passed
* `pnpm --filter @open-design/web typecheck` clean
* E2E: button click in DesignFilesPanel under namespace=e2e-pr-test ran
`gh --version` → fork → clone → branch → commit/push → ended at
`gh pr create --web` opening the GitHub PR draft in browser. No
AskUserQuestion stall this time.
Doesn't touch: `5f71968f` server-side endpoints (`/contribute-open-design`
+ `/publish-github` still in main as dead code — separate cleanup),
plan/spec docs (per maintainer call to keep the dedicated-repo as the
long-term goal), or `registry-backends.test.ts` (plan terminal-state
fixture; no production caller).
|