Commit Graph

4 Commits

Author SHA1 Message Date
elihahah666
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>
2026-06-10 19:21:07 +00:00
shangxinyu1
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>
2026-05-21 19:21:17 +08:00
lefarcen
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.
2026-05-20 15:38:29 +08:00
lefarcen
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).
2026-05-19 15:26:59 +08:00