Skip to content

Commit c4a474f

Browse files
committed
fix: [#971][#972][#974] dedupe /mcps autocomplete, surface MCP auth command, calm /feedback
- #971: autocomplete concatenated TUI palette slash entries with server commands without a collision check, so `/mcps` showed twice. Dedupe generically by command name; the TUI entry wins (opens `DialogMcp`) while typed `/mcps` / `/mcps enable <name>` still reaches the server direct handler - #972: MCP surfaces now show how to authenticate — `DialogMcp` rows and the `/mcps` status table both render `Needs authentication (run: altimate mcp auth <name>)`, matching `/status`; formatter extracted as `SessionPrompt.formatMcpStatusForDisplay` with unit tests - #974: `/feedback` template now asks at most ONE round of clarifying questions and only for essential missing fields; keeps the category/label follow-up; drops the confirm-before-filing and separate session-context interview steps
1 parent a30e477 commit c4a474f

6 files changed

Lines changed: 81 additions & 21 deletions

File tree

‎packages/opencode/src/command/template/feedback.txt‎

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,45 @@
11
You are helping the user submit product feedback for altimate-code. Feedback is filed as a GitHub issue.
22

3-
If $ARGUMENTS is provided, use it as the initial description and skip asking for a description. Still confirm the title and category before submitting.
3+
// altimate_change start — reduce /feedback follow-up loop (#974)
4+
If $ARGUMENTS is provided, use it as the initial description. Infer a concise title and category from it when reasonable.
5+
6+
Do not interview the user. Ask at most one round of clarifying questions, and only when essential information is genuinely missing. Essential information is: title, category, and description. If the description is straightforward, proceed directly to filing.
7+
8+
Keep the category/label follow-up behavior: if the category is unclear, ask the user to pick one of the labels below. Bundle that label question with any other essential missing question in the same message. After the user's answer, do not ask more clarifying questions; use best judgment and submit.
9+
10+
Do not ask optional "anything else?", wording-confirmation, or separate session context questions. Do not ask the user to confirm before filing unless they explicitly requested a review step.
11+
// altimate_change end
412

513
Step 1 — Collect feedback details:
614

7-
Ask the user for the following information. Collect each piece one at a time:
15+
Infer these fields from the user's message and $ARGUMENTS first. Ask one combined follow-up only for essential fields that cannot be inferred:
816

917
1. **Title**: A short summary of the feedback (one line).
10-
2. **Category**: Ask the user to pick one:
18+
2. **Category**: Choose one, or ask the user to pick one if unclear:
1119
- bug — Something is broken or not working as expected
1220
- feature — A new capability or feature request
1321
- improvement — An enhancement to existing functionality
1422
- ux — Feedback on usability, flow, or developer experience
15-
3. **Description**: A detailed explanation of the feedback. If $ARGUMENTS was provided, present it back and ask if they want to add anything or if it looks good.
23+
3. **Description**: A detailed explanation of the feedback. If $ARGUMENTS was provided, use it as the description unless it is empty or too vague to file.
1624

1725
Step 2 — Session context (opt-in):
1826

19-
Ask the user if they want to include session context with their feedback. Explain what this includes:
27+
Do not ask a separate session-context question. Set `include_context` to true only if the user explicitly asks to include session/debug context; otherwise set it to false.
28+
29+
If the single essential follow-up is needed, you may mention that they can optionally say "include context". Explain that this includes:
2030
- Working directory name (basename only, not the full path)
2131
- Session ID (for debugging correlation)
2232
- No code, credentials, or personal data is included
2333

24-
If they opt in, set `include_context` to true when submitting.
25-
26-
Step 3 — Confirm and submit:
34+
Step 3 — Submit:
2735

28-
Show a summary of the feedback before submitting:
36+
Once the essential fields are available, submit directly. Internally prepare this summary:
2937
- **Title**: ...
3038
- **Category**: ...
3139
- **Description**: ...
3240
- **Session context**: included / not included
3341

34-
Ask the user to confirm. If they confirm, call the `feedback_submit` tool with:
42+
Call the `feedback_submit` tool with:
3543
- `title`: the feedback title
3644
- `category`: the selected category
3745
- `description`: the full description

‎packages/opencode/src/session/prompt.ts‎

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2664,6 +2664,16 @@ NOTE: At any point in time through this workflow you should feel free to ask the
26642664
* Does not match when preceded by word characters or backticks (to avoid email addresses and quoted references)
26652665
*/
26662666

2667+
// altimate_change start — shared text formatter for /mcps runtime status (#972)
2668+
/** @internal Exported for tests. */
2669+
export function formatMcpStatusForDisplay(name: string, status: MCP.Status) {
2670+
const icon = status.status === "connected" ? "\u2713" : "\u25cb"
2671+
if (status.status === "failed") return icon + " " + status.status + " (" + status.error + ")"
2672+
if (status.status === "needs_auth") return icon + " Needs authentication (run: altimate mcp auth " + name + ")"
2673+
return icon + " " + status.status
2674+
}
2675+
// altimate_change end
2676+
26672677
export async function command(input: CommandInput): Promise<MessageV2.WithParts> {
26682678
log.info("command", input)
26692679

@@ -2715,14 +2725,7 @@ NOTE: At any point in time through this workflow you should feel free to ask the
27152725
const model = await lastModel(input.sessionID)
27162726
const statusMap = await MCP.status()
27172727
const rows = Object.entries(statusMap)
2718-
.map(([srv, s]) => {
2719-
const icon = s.status === "connected" ? "\u2713" : "\u25cb"
2720-
const label =
2721-
s.status === "failed"
2722-
? icon + " " + s.status + " (" + s.error + ")"
2723-
: icon + " " + s.status
2724-
return "| `" + srv + "` | " + label + " |"
2725-
})
2728+
.map(([srv, s]) => "| `" + srv + "` | " + formatMcpStatusForDisplay(srv, s) + " |")
27262729
.join("\n")
27272730
const responseText = rows
27282731
? "MCP servers:\n\n| Server | Status |\n|---|---|\n" + rows

‎packages/opencode/test/command/feedback.test.ts‎

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,11 +88,23 @@ describe("/feedback command", () => {
8888
})
8989
})
9090

91-
test("template includes confirmation step", async () => {
91+
test("template proceeds directly when enough detail is available", async () => {
9292
await withInstance(async () => {
9393
const cmd = (await Command.get("feedback"))!
9494
const template = await cmd.template
95-
expect(template).toContain("confirm")
95+
expect(template).toContain("proceed directly to filing")
96+
expect(template).toContain("submit directly")
97+
expect(template).not.toContain("Ask the user to confirm")
98+
})
99+
})
100+
101+
test("template limits clarifying questions but preserves label follow-up", async () => {
102+
await withInstance(async () => {
103+
const cmd = (await Command.get("feedback"))!
104+
const template = await cmd.template
105+
expect(template).toContain("Ask at most one round of clarifying questions")
106+
expect(template).toContain("Keep the category/label follow-up behavior")
107+
expect(template).toContain("ask the user to pick one if unclear")
96108
})
97109
})
98110
})
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { describe, expect, test } from "bun:test"
2+
import { SessionPrompt } from "../../src/session/prompt"
3+
4+
describe("/mcps command status formatting", () => {
5+
test("needs_auth status includes the auth command hint", () => {
6+
expect(SessionPrompt.formatMcpStatusForDisplay("github", { status: "needs_auth" })).toBe(
7+
"\u25cb Needs authentication (run: altimate mcp auth github)",
8+
)
9+
})
10+
11+
test("failed status keeps the runtime error detail", () => {
12+
expect(SessionPrompt.formatMcpStatusForDisplay("broken", { status: "failed", error: "boom" })).toBe(
13+
"\u25cb failed (boom)",
14+
)
15+
})
16+
})

‎packages/tui/src/component/dialog-mcp.tsx‎

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,14 @@ export function DialogMcp() {
3737
map(([name, status]) => ({
3838
value: name,
3939
title: name,
40-
description: status.status === "failed" ? "failed" : status.status,
40+
// altimate_change start — show MCP auth command in MCP dialog (#972)
41+
description:
42+
status.status === "failed"
43+
? "failed"
44+
: status.status === "needs_auth"
45+
? `Needs authentication (run: altimate mcp auth ${name})`
46+
: status.status,
47+
// altimate_change end
4148
footer: <Status enabled={local.mcp.isEnabled(name)} loading={loadingMcp === name} />,
4249
category: undefined,
4350
})),

‎packages/tui/src/component/prompt/autocomplete.tsx‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -436,9 +436,23 @@ export function Autocomplete(props: {
436436

437437
const commands = createMemo((): AutocompleteOption[] => {
438438
const results: AutocompleteOption[] = [...slashes()]
439+
// altimate_change start — keep TUI slash actions on autocomplete collisions
440+
// If a local slashName matches a server command name, autocomplete should show
441+
// the local palette action because selecting it can open TUI-only UI (for
442+
// example /mcps -> DialogMcp). Submitting typed text like `/mcps` or
443+
// `/mcps enable foo` still reaches the server command handler.
444+
const localSlashNames = new Set(
445+
results
446+
.filter((option) => option.display.startsWith("/"))
447+
.map((option) => option.display.slice(1)),
448+
)
449+
// altimate_change end
439450

440451
for (const serverCommand of sync.data.command) {
441452
if (serverCommand.source === "skill") continue
453+
// altimate_change start — keep one autocomplete row per slash command name
454+
if (localSlashNames.has(serverCommand.name)) continue
455+
// altimate_change end
442456
const label = serverCommand.source === "mcp" ? ":mcp" : ""
443457
results.push({
444458
display: "/" + serverCommand.name + label,

0 commit comments

Comments
 (0)