SuperAPI AI Directory

ask-ui

Office & productivity

View repository

Updated 10-15 19:48

About this skill

Ask UI

Use Ask UI as a display and persistence adapter. Question generation and reasoning remain in the calling workflow.

Overview: three paths, take only one

Path When to take it How the answer comes back
Standard path ask Default. Runs in the background, blocks until the user submits Process exits, harness pushes a completion notification, read the stdout file
Failure recovery resume Only for the cases listed in the "Failure Quick Reference" table status: "submitted" contains the complete answer
Manual fallback create ask the last resort when it truly cannot be used After the user replies "submitted", run resume

The cost of choosing the wrong path is marked with 🔴 throughout the rest of the document. Read the overview first before going further; do not jump into the details of one path and get stuck there.

🔴 There is only one red-line checklist, in the "Anti-patterns" table at the end of the document—each row has three columns: what not to do, why, and what to do instead. Review it before every time you are about to send a command or reply. (The 🔴 in the intermediate sections are in-place reminders, not another checklist.)

Deciding whether to use the UI

🔴 CHECKPOINT: When the current batch of questions contains at least two independent questions the user can answer right now, you must use the UI. Questions whose answers depend on a previous question are written as conditional questions in the same asking (showWhen, see step 3); only questions that require the Agent to reason again after getting an answer are left for the next ask. When there is only one question, ask it directly in the conversation; the only exception is correcting or supplementing an already-submitted answer—even if there is only one question, initiate another ask, because verbal confirmation in the conversation is not persisted and subsequent processes cannot read it.

For grill-me、grill-with-docs, brainstorming, or other confirmation and question-collection workflows, whenever there are more than two questions at once, always use the UI.

Fallback order

Fall back in order, down to the first level that works:

  1. Ask UI(this skill)—default.
  2. AskUserQuestion—use this instead when this skill cannot be used. There are two cases where it cannot be used: the harness has no tool for executing commands (Bash or equivalent), or the service/browser truly cannot start.
  3. Numbered text questions in the conversation—only allowed when AskUserQuestion is also unavailable.

When falling back, state the real reason clearly; do not write "no Bash tool" as "the service cannot start"—the former is useless no matter what language you rewrite it in, while the latter is an environment issue. Use ToolSearch to confirm the tool truly does not exist before drawing a conclusion.

Standard path:ask eight steps

  1. Resolve the directory containing this SKILL.md as ASK_UI_SKILL_DIR。

  2. Before creating the JSON, read two files first:references/questionset.schema.json is the field list itself (JSON Schema 2020-12, each field with a Chinese description),references/schema.md covers the parts the schema cannot express—cross-field hard rules, actual page behavior, and why it is written this way.references/example-question-set.json is a complete starter template you can copy directly and change the fields in (one single-choice, one multiple-choice, and one free-text question, each with a recommended answer and context fields). For the answer structure, see references/answerset.schema.json。

  3. Create a QuestionSet JSON file. Each time ask is an independent question, and the id is generated automatically by the CLI. The old fields (sessionId / roundNumber / basedOnRound / sessionTitle / sessionSummary / sessionBackground) have all been removed; if you write them, you'll get an error pointing you in the right direction. Go through the following seven items one by one:

    • Required fields: Every question must have two fields written:type(single / multiple / text,There is no default value; omitting it causes an error) and text(the question body; both the question itself and the description go here).
    • Optional questions: Questions that are allowed to be left blank must explicitly set "required": false——required defaults to true; if omitted, it is required, the page shows a "Required" badge, and leaving it blank blocks submission.Don't write "(optional)" in text instead of this field: The copy and the badge won't match, and users will be forced to make something up. Questions like "Anything else to add?", "Other notes", and "Optional parameters" should always use type: "text" + "required": false。
    • Context fields: Write these as well so users can tell what is being asked without looking at the conversation:projectName / title / summary / background("Current context" in the left column, with a separate button in the upper right to enlarge it) / purpose; for questions that need separate background explanation, write a question-level background。
    • Options: These are always JSON objects {"text":"…","description":"…","recommended":true,"reason":"…"},Strings are not accepted;reason can only be written on the options of recommended: true . For an option's id , if omitted it is generated automatically in the order within that question option-1 / option-2…;must be referenced by showWhen ; options that are referenced must have their "id"written explicitly, don't guess the auto-generated number. Choice questions do not have an "Other" option. Answers outside the preset options are handled by each question's supplementary note, so options should list only the truly mutually exclusive choices; don't add "Other" just to fill space. Choice questions must have at least 2 options; the script will directly error with 第 X 题是选择题,至少要有两个选项 and exit—for a confirmation question with only one candidate, change it to type: "text", or just ask in the conversation.
    • Conditional questions (branches): Write questions with dependencies as conditional questions within the same prompt—q1 's options are written as {"id":"yes","text":"要,分阶段放量"}, and this question is written as "showWhen": {"questionId":"q1","optionIds":["yes"]}, so it appears only when q1 has selected that option; it appears or disappears immediately after the user selects. InoptionIds , what is filled in is theoption's id, not the option text—filling in text will error with 第 q2 题的 showWhen 引用了 q1 里不存在的选项:…。showWhen can only point to questions that come earlier; the branch tree is built through chained dependencies; when a text question is used as the trigger source, use answered / contains / matches. Hidden questions do not validate required fields and do not enter answers.json(ids are listed in hiddenQuestionIds). For the complete rules, see references/schema.md 's "Conditional questions (branches)"—showWhen 's three cross-field hard rules (point to an earlier question, matching method fits the question type, option id actually exists) cannot be caught by the schema; only runtime will error.
    • Rich text and file links:background, the question's text and background support Markdown (GFM: headings, bold, inline code, code blocks, lists, links, quotes, tables) + Mermaid; the option's description only supports Markdown.When you mention a file, write it as a link:[审计报告](.scratch/model-audit.html)(paths are relative to the workspace; absolute paths and file:///…are also accepted), and clicking it opens it in a new tab in the default browser, with a real file://. Only documents and images are allowed (.html / .md / .txt / .log / .json / .yaml / .csv / .pdf / images); things like.sh、.app will be blocked. Just writing a path is not clickable; the user has to open it themselves. For details, see references/schema.md 's "Local file links".
    • Diagrams and nested fences: write things that are hard to explain clearly, such as flows, sequences, and architecture, as ```mermaid code blocks, and they will render as diagrams that follow the theme. Tables, diagrams, and code blocks can all be clicked to enlarge, zoom, and drag on the page. If you mark the language on a code block's fence (```ts、```sql), it will be highlighted according to the language.Nesting rules: when you want to show a passage that itself contains 围栏的 markdown(或代码里含), the outer fence must use four backticks ````—three backticks will be consumed by the inner first 提前闭合,后面的内容漏成正文,页面上出现裸 character.
  4. Run the command in the background, redirect only stdout to a file, and leave stderr on the console:

    node <ASK_UI_SKILL_DIR>/scripts/ask-ui.mjs ask --input <questions.json> > <run>.stdout.json
    

    Start it using the harness's background mechanism (in Claude Code, the Bash tool's run_in_background: true). stdout is the result JSON and must be written to a file; stderr is the human-readable progress lines (URL,ask-ui-id), and leaving them on the console lets the user see them immediately.

  5. **Repeat the page URL on the first line of the reply.** At startup, stderr immediately prints Ask UI ready at <url> and ask-ui-id: <id>, and use TaskOutput to read the background task output once and take these two lines into the reply. The browser is opened automatically by the script, but it may not pop up (no GUI, default browser not configured, window blocked); putting the URL out lets the user open it themselves. Reading once is enough; if you cannot read it, end this round as usual, do not sleep retry.

  6. 🛑 STOP: after outputting the URL, end this round immediately and wait for nothing.ask has no timeout and will block until the user submits; after the user submits, the process exits, and the harness proactively pushes the task completion notification to you, which is the wake-up signal.

  7. After receiving the completion notification, read the full question and answer in <run>.stdout.json , then continue the original workflow. The standard ask automatically cleans up the question directory after writing to stdout; after reading, delete only the <questions.json> and <run>.stdout.jsoncreated this time (do not clean up other request files). When using create , immediately run node <ASK_UI_SKILL_DIR>/scripts/ask-ui.mjs complete --id <askId>(for a custom data directory with --data-dir)。complete , read the answer first, then delete only the specified question directory and its index entry, and then output the answer that was read; if reading the answer fails, keep the files and recover according to the troubleshooting quick reference.

  8. If you need more independent questions, create another QuestionSet JSON and call askagain—each ask is naturally independent and does not interfere with the others.

Service and browser lifecycle

  • The run log is in $TEMP/ask-ui.log: one JSON per line (time / pid / event / askId), 10MB rotation, keeping at most 3 backups (.1 .2 .3). When troubleshooting service start/stop, submission, or wake-up failures, look at it first;<dataRoot>/server.log Only the stderr crash stack of the resident serve process.

  • The URL format is http://127.0.0.1:<port>/<askId>, without /ask/;askId is a random 12-digit hexadecimal string, not a standard full UUID. Every ask opens the browser: after submission, first save answers.json, then write the complete question, answer, and URL to stdout, and finally exit the command; the page closes 3 seconds after submission. Standard ask immediately calls complete to clean up after stdout is written; if using create, the answer is retained until the Agent finishes reading and executes complete。

  • The user's half-filled answer is written to <ask 目录>/draft.jsonon every change (saved once after 400 milliseconds of inactivity, and sent once more when the tab is closed). Refresh, closing and reopening the page, and service restarts can all resume it; after successful submission this draft is deleted.Don't treat draft.json as an answer, it is a work in progress and does not pass validation; the formal answer is only answers.json。

  • The resident service does not need manual cleanup; it manages its own entry and exit—for exit rules see the previous item and the row "form has been hanging for a long time with no one answering" in the "Troubleshooting Quick Reference" table.

  • Use --no-openonly when the browser opening is managed separately by an external party. Use --port <number>。

Troubleshooting Quick Reference: what happened, what to do

node <ASK_UI_SKILL_DIR>/scripts/ask-ui.mjs resume --id <askId>

🔴 Only the situations listed in the table below require resume. The normal path is always to read <run>.stdout.json, do not treat resume as a routine action.

askId from the task output (stderr) ask-ui-id: <id> marker; there is also a line ask-ui-submitted: <id>, which is a backup signal that submission is complete.resume returns status: "submitted" when it contains the complete answer.

Trigger condition First-line fix Fallback if still failing
The task exits immediately, and the task output does not even contain the ask-ui-id marker The QuestionSet JSON is invalid, and the question was never created at all: read the error in the task output (all questions are listed at once), fix the JSON, and rerun ask 🔴 Do not run resume—there is no data to recover, and --id without resume will only retrieve old questions from other tasks
The background task was killed, crashed, or exited with a non-zero code (the task output has ask-ui-id) After taking askId , run resume Run --id without resume, filter by title / summary to find candidates for the current task, and take submittedAt the latest one
<run>.stdout.json is empty or not valid JSON Same as above, use resume to retrieve the result again When the data truly does not exist, state truthfully that the answer has been lost, and use the same batch of questions to re- ask
A new Agent session was started, and the original background task cannot be obtained Take the id from the most recent ask-ui-id marker in the conversation, then resume If the marker is also lost, run --id without resume to list candidates
resume returns {"status":"waiting"} The user has not submitted yet: do nothing, end this round immediately, and continue waiting for the harness's completion notification 🔴 Do not reopen the form, do not resend the question, do not urge the user, do not sleep
The form has been hanging for a long time with no one answering, and you are worried the service is occupying it the whole time Do nothing: if there are questions waiting to be answered, the service should keep running; after all are answered, it exits on its own after 24 hours with no access, and exits immediately if the data directory is deleted,complete / cancel Stop it on the spot when the last question is finished 🔴 Do not manually kill the process; if you want to change the idle duration, use ASK_UI_IDLE_TIMEOUT_MINUTES
Local browser cannot connect to the temporary service First check <dataRoot>/server.log the last line to confirm why the service exited (received SIGTERM / idle for N minutes / data directory is gone / crash stack), then follow the create detached flow (see "Manual fallback and recovery") Only if it still cannot connect, fall back to AskUserQuestion
The harness has no Bash or equivalent execution tool Use ToolSearch to confirm the tool really does not exist, and fall back to AskUserQuestion AskUserQuestion Only when that also fails, use the numbered text questions in the conversation
Failed to wake up the adapter Preserve the answer and go back to the manual "submitted" flow The answer has been written to disk; use resume to retrieve it again

Manual fallback and recovery

🔴 CHECKPOINT: This is the last resort; only use it when ask is truly unusable—it is the only path that requires the user to reply "submitted".ask Run in the backgroundDoes not countUnusable; that is the standard path, just wait for the notification as above.

Use the detached flow in the following situations: foreground tool calls cannot stay active, the local browser cannot connect to the temporary service, or an interrupted direct question needs to be recovered:

node <ASK_UI_SKILL_DIR>/scripts/ask-ui.mjs create --input <questions.json>

Parse the returned JSON. In the conversation, provide both its URL and a visible marker:

ask-ui-id: <askId>

Tell the user to reply only "submitted" after submitting the form.create The command starts or reuses a detached localhost service and returns immediately.

When the user says "submitted", "done submitting", or "finished answering":

  1. From the most recent ask-ui-id marker in the conversation, recover askId。

  2. Run:

    node <ASK_UI_SKILL_DIR>/scripts/ask-ui.mjs resume --id <askId>
    
  3. If the result is submitted, first read the complete question and answer; immediately run the complete --id <askId> cleanup from step 4, then delete the temporary input/output files created this time, and then continue the original workflow.

  4. Clean up immediately after each time you read the answer, even if the next step is to ask another question:

    node <ASK_UI_SKILL_DIR>/scripts/ask-ui.mjs complete --id <askId>
    
  5. If more independent questions are still needed, prefer returning to the foreground ask command (new JSON, new question). Use create。

If that marker cannot be obtained from the conversation, run the --id without the resume. When there are multiple candidates, it returns status: "ambiguous" and a candidates list (including askId / title / summary / workspace / submittedAt). The data directory is by default the .ask-uiunder the current working directory, so all candidates come from this workspace. Filter in this order:

  1. First filter by title and summary , keeping only candidates that are about the current task.
  2. If only one remains, use it; if multiple remain, take the submittedAt most recent one.
  3. If none of them match the current task, list the askId、title、submittedAt of each candidate and let the user choose; do not pick the most recent one and make do with it.

🔴 Duplicate "submitted" messages must not create duplicate questions. Only after successfully reading an answer set with submitted may a new question be initiated.

Optional active wake-up

Ask UI supports optional wake-up metadata for Claude Code and Codex App Server. Treat it as an enhancement, not a requirement.

  • Enable automatic wake-up only after the user agrees.
  • Claude Code requires a recorded session id.
  • Codex requires a host-provided thread id. Never guess the Codex thread id.
  • If the adapter fails, preserve the answer and fall back to the manual "submitted" flow.
  • Direct ask mode never triggers the wake-up adapter, because the waiting process itself is the return channel.

Anti-patterns: never do these things

Check against this list every time before sending a command or replying.

🔴 Don't do Why Change to
Use nohup ... & and similar hand-written background the harness never receives the exit event, and the entire chain falls back to manual follow-up Use the harness's own background mechanism
Don't redirect stdout; parse the result directly from the task output The two output streams get mixed together,JSON.parse will inevitably fail > <run>.stdout.json, keep stderr on the console
Redirect stderr into the file as well The URL and ask-ui-id get buried in the file, so the user can't see them; if the page doesn't pop up, they have no way to open it themselves Redirect only stdout
sleep polling, nag the user, make the user reply "submitted" The background task's completion notification is the wake-up signal; just wait for it End the round immediately after launching
Find the answer from the task output, or hand-assemble the .ask-ui/ path The task output only has stderr progress lines; the answer isn't there Read the answer from <run>.stdout.json or run resume; use the task output only to get the URL,ask-ui-id and errors
Write bare file paths in the body text They can't be clicked on the page, so the user has to go find them in the file manager Write them as Markdown links [报告](.scratch/report.html), so clicking opens a new tab
Add an "Other" option to multiple-choice questions Answers outside the presets are carried by each question's supplementary notes List only the options that are truly mutually exclusive
Write multiple-choice questions with only one option The script hard-rejects it, and the entire batch of questions can't even create a session Add a second truly mutually exclusive option, or change it to type: "text"
Write "(optional)" in text without writing "required": false required defaults to true, the page still shows the "required" badge and blocks submission when left blank, so the text and validation don't match Explicitly write "required": false
for optional questions typeOmit , or write the options as strings type , both are hard-rejected, and the entire batch of questions can't even create a session text one of the three must be written; always write options as JSON objects with
Use question-level recommendedOptionIds to mark recommendations no longer recognizes this field, and the script will error Recommendations go in the options:"recommended": true with "reason"
make showWhen points to a question placed later The order is the dependency order; backward references will be hard-rejected Place the trigger question earlier
split into multiple asks just because "the question has dependencies" each time you have to reopen the browser, and the Agent also has to wake up one more time In the same question, use showWhen to branch
Overwrite a submitted question or answer answers.json is immutable after submission For corrections and additions, initiate another ask
describe "no Bash tool" as "the service won't start" The attribution is wrong, and the user will go fix a nonexistent environment problem State clearly whether it is a missing tool or a service failure
Guess the Codex thread id A wrong guess will send the wake-up to another session The thread id can only be provided by the host; if it is unavailable, use the manual process

Common commands

node <ASK_UI_SKILL_DIR>/scripts/ask-ui.mjs ask --input <questions.json>    # 标准路径
node <ASK_UI_SKILL_DIR>/scripts/ask-ui.mjs create --input <questions.json> # 手动回退
node <ASK_UI_SKILL_DIR>/scripts/ask-ui.mjs resume --id <askId>             # 故障恢复
node <ASK_UI_SKILL_DIR>/scripts/ask-ui.mjs status --id <askId>             # 查提问状态
node <ASK_UI_SKILL_DIR>/scripts/ask-ui.mjs serve                           # 常驻服务(ask/create 自动管理,一般不单跑)
node <ASK_UI_SKILL_DIR>/scripts/ask-ui.mjs complete --id <askId>           # 正常结束提问
node <ASK_UI_SKILL_DIR>/scripts/ask-ui.mjs cancel --id <askId>             # 作废提问(问题问错了、任务取消)
node <ASK_UI_SKILL_DIR>/scripts/self-test.mjs                              # 自检,改完 skill 或排查环境时跑

Development: TS source and compiled artifacts

  • When changing code, only change scripts/**/*.mts. The same-named .mjs is a tsc compiled artifact and is committed to the repository together (the installed copy in~/.agents directly runs .mjs, without a build step); manually editing .mjs will be overwritten on the next compilation.
  • Compilation is type checking (strict):npm --prefix <ASK_UI_SKILL_DIR> run build(first run npm --prefix <ASK_UI_SKILL_DIR> install); to check only without modifying, use run typecheck. The pre-commit hook automatically compiles, stages artifacts, and runs self-tests.
  • Runtime requires Node ≥ 18 (fetch / AbortSignal.timeout); package.json's engines already declares it.
  • Browser assets assets/app/*.js remain .js (shared by Node and the browser, no build); the type contract is in the same directory .d.ts—if you change .js 's export signature, you must also update .d.ts。