nox-qwen-agent-cli 1.1.5 → 1.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -22,10 +22,10 @@ PowerShell can also run `.\install.ps1` directly.
22
22
 
23
23
  ## Configure
24
24
 
25
- Interactive mode asks for a masked API key on every launch. The key remains in
26
- memory for that process only and is never stored in source code, settings, or
27
- chat history. Non-interactive `-p` runs read `NOX_API_KEY`,
28
- `OPENAI_COMPAT_API_KEY`, or `OPENAI_API_KEY`.
25
+ Interactive runs always ask for a masked API key, including `qwen -p` when a
26
+ terminal is attached. Non-interactive runs read `NOX_API_KEY`,
27
+ `OPENAI_COMPAT_API_KEY`, or `OPENAI_API_KEY`. The key remains in memory for that
28
+ process only and is never stored in source code, settings, or chat history.
29
29
 
30
30
  Linux, macOS, and Termux:
31
31
 
@@ -45,6 +45,7 @@ $env:NOX_API_KEY = "your-key"
45
45
  qwen
46
46
  qwen -m qwen3.7-plus
47
47
  qwen -m gpt-5.5 -p "Inspect and test this project."
48
+ qwen --clarify -p "Add authentication."
48
49
  qwen --yes --no-stream
49
50
  ```
50
51
 
@@ -56,8 +57,13 @@ The interactive composer is multiline:
56
57
  - Arrow keys, Home, End, Backspace, and Delete edit the message.
57
58
  - Multiline clipboard paste is preserved.
58
59
 
59
- Commands: `/plan TASK`, `/image PATH PROMPT`, `/file PATH PROMPT`, `/new`, `/sessions`,
60
- `/resume SESSION`, `/approval ask|auto`, `/model`, and `/exit`.
60
+ Commands: `/plan TASK`, `/clarify TASK`, `/image PATH PROMPT`, `/file PATH PROMPT`,
61
+ `/new`, `/sessions`, `/resume SESSION`, `/approval ask|auto`, `/model`, and `/exit`.
62
+
63
+ Use `/clarify TASK` or `--clarify -p "TASK"` when you want the agent to ask
64
+ precise requirements questions first. In clarification mode the agent should
65
+ not edit files or run side-effecting commands; it returns focused questions and
66
+ safe assumptions before implementation.
61
67
 
62
68
  Images and files are sent as structured OpenAI attachment parts. Their bytes
63
69
  are uploaded through the selected provider's file endpoint and are never
@@ -67,8 +73,9 @@ Streaming is enabled by default. Reasoning is shown in a separate Thinking
67
73
  section when the provider returns it, and is never mixed into the final answer.
68
74
 
69
75
  The agent includes bounded file reads, batch reads and writes, tree and text
70
- search, path metadata, file operations, Git status/diff, and cross-platform
71
- shell execution. Mutating actions require approval unless `--yes` is used.
76
+ search, path metadata, file operations, Git status/diff, cross-platform shell
77
+ execution, and background terminals for dev servers and long-running commands.
78
+ Mutating actions require approval unless `--yes` is used.
72
79
  For substantial tasks the model can call `update_plan`; plan steps and their
73
80
  live status are rendered separately from chat and tool output.
74
81
 
@@ -77,12 +84,18 @@ live status are rendered separately from chat and tool output.
77
84
  The Agent prompt and file tools are maintained centrally. Available workspace
78
85
  tools include `file_info`, `list_files`, `search_files`, `find_in_file`,
79
86
  `read_file`, `read_lines`, `update_file`, `apply_patch`, `create_file`,
80
- `delete_file`, `move_file`, `copy_file`, and `make_directory`.
81
-
82
- Every Agent run sends fresh runtime context in the system prompt: OS/version,
83
- processor, Node.js version, workspace root, top-level workspace entries,
84
- available tools, and approval mode. The prompt includes an explicit Agent
85
- reminder at startup.
87
+ `delete_file`, `move_file`, `copy_file`, `make_directory`,
88
+ `start_background_terminal`, `read_background_terminal`, and
89
+ `stop_background_terminal`.
90
+
91
+ Every Agent run sends fresh runtime context in the first user message:
92
+ OS/version, processor, Node.js version, workspace root, top-level workspace
93
+ entries, available tools, and approval mode. The CLI and VS Code extension
94
+ intentionally transport the full Agent instructions by prefixing the first user
95
+ message with `<agent_instructions>`. Every fourth user turn is prefixed with
96
+ `<agent_reminder>` so the model keeps the active workspace and agent role in
97
+ context. This avoids gateways that drop or weaken `system` messages while still
98
+ sending a SHA-256 prompt proof in request metadata.
86
99
 
87
100
  The file tools enforce workspace-only paths, reject traversal and symlink
88
101
  escapes, deny protected secret-looking files by default, detect binary files,
@@ -28,9 +28,13 @@ Tool-selection rules:
28
28
  - Use create_file only when a new file is genuinely required.
29
29
  - Use delete_file only when deletion is explicitly required and safe.
30
30
  - Use run_shell_command only for commands relevant to the user's task.
31
+ - Use start_background_terminal for long-running commands such as dev servers, watchers, and commands that need later polling.
32
+ - Use read_background_terminal to inspect background output before claiming a server, watcher, or long task is ready.
33
+ - Use stop_background_terminal when a background process is no longer needed or is producing failures.
31
34
  - Run focused tests first, followed by the broader suite when appropriate.
32
35
 
33
36
  File-access rules:
37
+ - A file may be available through an uploaded file id, an attachment in the current request, a client-provided excerpt, workspace tools, a line-range read, or a search result followed by a targeted read.
34
38
  - Do not ask the user to upload a file when it is already available in the workspace.
35
39
  - Do not read an entire large file when a targeted range is sufficient.
36
40
  - Do not invent missing file contents.
@@ -56,6 +60,7 @@ Clarification rules:
56
60
 
57
61
  Progress rules:
58
62
  For longer operations, provide brief progress updates after meaningful milestones. Do not narrate every low-level command.
63
+ When a background terminal is running, summarize only meaningful state changes such as started, ready, failed, or stopped.
59
64
 
60
65
  Safety rules:
61
66
  - Stay inside the configured workspace root.
@@ -64,6 +69,7 @@ Safety rules:
64
69
  - Do not read protected secret files unless explicitly authorised.
65
70
  - Do not execute destructive commands without explicit permission.
66
71
  - Do not install untrusted packages or extensions silently.
72
+ - Do not leave background terminals running after they are no longer needed unless the user asked to keep them running.
67
73
  - Do not bypass tests merely to produce a successful-looking result.
68
74
 
69
75
  Completion rules:
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nox-qwen-agent-cli",
3
- "version": "1.1.5",
3
+ "version": "1.1.7",
4
4
  "description": "Cross-platform OpenAI-compatible Qwen coding agent CLI",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",
package/qwen.js CHANGED
@@ -19,6 +19,8 @@ const MAX_FILE_BYTES = 20 * 1024 * 1024;
19
19
  const MAX_COMMAND_OUTPUT = 128 * 1024;
20
20
  const MAX_API_RETRIES = 2;
21
21
  const MAX_SHELL_TIMEOUT_MS = 10 * 60 * 1000;
22
+ const MAX_BACKGROUND_OUTPUT = 256 * 1024;
23
+ const MAX_BACKGROUND_TERMINALS = 8;
22
24
  const CLARIFY_REQUEST_PROMPT = `Clarify this request before execution.
23
25
  Do not modify files, run commands with side effects, or implement the task yet.
24
26
  Ask the minimum set of precise questions needed to remove ambiguity, identify acceptance criteria, and confirm risky choices.
@@ -40,6 +42,7 @@ When a tool is required, output exactly one block and no other text:
40
42
  <NOX_TOOL_CALL>{"id":"short-stable-id","tool":"exact_tool_name","args":{}}</NOX_TOOL_CALL>
41
43
  Arguments must match the selected tool schema. Never emit more than one tool call per response.
42
44
  After receiving a <tool_result> block, continue the same objective with the next tool call or the final answer.`;
45
+ const AGENT_REMINDER_PROMPT = `Agent reminder: continue following the initial Agent instructions, use the available tools when needed, keep workspace operations inside the active workspace, and finish only after the requested task is handled.`;
43
46
 
44
47
  const TOOL_DEFINITIONS = [
45
48
  tool("update_plan", "Create or update the visible execution plan.", {
@@ -152,6 +155,19 @@ const TOOL_DEFINITIONS = [
152
155
  cwd: stringProperty("Relative working directory; defaults to ."),
153
156
  timeout_ms: integerProperty("Timeout from 1 to 600000 ms"),
154
157
  }, ["command"]),
158
+ tool("start_background_terminal", "Start a long-running shell command in the background and return a terminal id for later polling.", {
159
+ command: stringProperty("Shell command"),
160
+ cwd: stringProperty("Relative working directory; defaults to ."),
161
+ }, ["command"]),
162
+ tool("read_background_terminal", "Read buffered output and status from a background terminal.", {
163
+ terminal_id: stringProperty("Background terminal id returned by start_background_terminal"),
164
+ max_bytes: integerProperty("Maximum stdout/stderr bytes to return; defaults to 12000"),
165
+ wait_ms: integerProperty("Optional polling wait from 0 to 10000 ms for new output or process exit"),
166
+ }, ["terminal_id"]),
167
+ tool("stop_background_terminal", "Stop a running background terminal.", {
168
+ terminal_id: stringProperty("Background terminal id returned by start_background_terminal"),
169
+ signal: stringProperty("Signal name; defaults to SIGTERM"),
170
+ }, ["terminal_id"]),
155
171
  ];
156
172
 
157
173
  const EFFECTIVE_TOOL_DEFINITIONS = [
@@ -199,6 +215,57 @@ function refreshSystemPrompt(state) {
199
215
  else state.messages.unshift({ role: "system", content: prompt });
200
216
  }
201
217
 
218
+ function prefixTextContent(content, prompt, kind) {
219
+ const firstTurn = kind === "instructions";
220
+ return [
221
+ firstTurn ? "<agent_instructions>" : "<agent_reminder>",
222
+ prompt,
223
+ firstTurn ? "</agent_instructions>" : "</agent_reminder>",
224
+ "<user_message>",
225
+ String(content ?? ""),
226
+ "</user_message>",
227
+ ].join("\n");
228
+ }
229
+
230
+ function promptForUserTurn(userTurn, prompt) {
231
+ if (userTurn === 1) return { kind: "instructions", prompt };
232
+ if (userTurn > 1 && userTurn % 4 === 0) return { kind: "reminder", prompt: AGENT_REMINDER_PROMPT };
233
+ return null;
234
+ }
235
+
236
+ function prefixUserContent(content, transport) {
237
+ if (!transport) return content;
238
+ if (Array.isArray(content)) {
239
+ let inserted = false;
240
+ const parts = content.map((part) => {
241
+ if (!inserted && part?.type === "text") {
242
+ inserted = true;
243
+ return { ...part, text: prefixTextContent(part.text, transport.prompt, transport.kind) };
244
+ }
245
+ return part;
246
+ });
247
+ if (inserted) return parts;
248
+ return [{ type: "text", text: prefixTextContent("", transport.prompt, transport.kind) }, ...parts];
249
+ }
250
+ return prefixTextContent(content, transport.prompt, transport.kind);
251
+ }
252
+
253
+ function outboundMessages(state) {
254
+ const prompt = systemPrompt(state.workspace, state.options);
255
+ let userTurn = 1;
256
+ return state.messages
257
+ .filter((message) => message?.role !== "system" && message?.role !== "developer")
258
+ .map((message) => {
259
+ if (message?.role !== "user") return message;
260
+ const transport = promptForUserTurn(userTurn, prompt);
261
+ userTurn += 1;
262
+ return {
263
+ ...message,
264
+ content: prefixUserContent(message.content, transport),
265
+ };
266
+ });
267
+ }
268
+
202
269
  function tool(name, description, properties, required = []) {
203
270
  return {
204
271
  type: "function",
@@ -685,6 +752,9 @@ async function executeToolCall(call, context) {
685
752
  }
686
753
  return await runShell(String(args.command), cwd, args.timeout_ms);
687
754
  }
755
+ if (name === "start_background_terminal") return await startBackgroundTerminal(context, args);
756
+ if (name === "read_background_terminal") return await readBackgroundTerminal(context, args);
757
+ if (name === "stop_background_terminal") return await stopBackgroundTerminal(context, args);
688
758
  return { ok: false, error: `Unknown tool: ${name}` };
689
759
  } catch (error) {
690
760
  return { ok: false, error: String(error?.message || error) };
@@ -853,6 +923,125 @@ async function runShell(command, cwd, timeoutValue) {
853
923
  });
854
924
  }
855
925
 
926
+ function ensureBackgroundTerminals(context) {
927
+ if (!context.backgroundTerminals) context.backgroundTerminals = new Map();
928
+ return context.backgroundTerminals;
929
+ }
930
+
931
+ function backgroundSnapshot(entry, maxBytes = 12000) {
932
+ const limit = Math.max(1, Math.min(MAX_BACKGROUND_OUTPUT, Number(maxBytes || 12000)));
933
+ const stdout = entry.stdout.slice(-limit);
934
+ const stderr = entry.stderr.slice(-limit);
935
+ return {
936
+ ok: true,
937
+ terminal_id: entry.id,
938
+ command: entry.command,
939
+ cwd: entry.relativeCwd,
940
+ running: entry.exit === null,
941
+ pid: entry.child.pid,
942
+ started_at: entry.startedAt,
943
+ exited_at: entry.exitedAt,
944
+ exit_code: entry.exit?.code ?? null,
945
+ signal: entry.exit?.signal ?? null,
946
+ stdout,
947
+ stderr,
948
+ truncated: entry.stdout.length > stdout.length || entry.stderr.length > stderr.length || entry.truncated,
949
+ };
950
+ }
951
+
952
+ function appendBackgroundOutput(entry, target, chunk) {
953
+ const text = chunk.toString("utf8");
954
+ entry[target] += text;
955
+ if (entry.stdout.length + entry.stderr.length <= MAX_BACKGROUND_OUTPUT) return;
956
+ const overflow = entry.stdout.length + entry.stderr.length - MAX_BACKGROUND_OUTPUT;
957
+ if (entry[target].length >= overflow) entry[target] = entry[target].slice(overflow);
958
+ else {
959
+ const remaining = overflow - entry[target].length;
960
+ entry[target] = "";
961
+ const other = target === "stdout" ? "stderr" : "stdout";
962
+ entry[other] = entry[other].slice(Math.min(remaining, entry[other].length));
963
+ }
964
+ entry.truncated = true;
965
+ }
966
+
967
+ async function startBackgroundTerminal(context, args) {
968
+ const terminals = ensureBackgroundTerminals(context);
969
+ for (const [id, entry] of terminals) {
970
+ if (entry.exit !== null) terminals.delete(id);
971
+ }
972
+ if (terminals.size >= MAX_BACKGROUND_TERMINALS) {
973
+ throw new Error(`At most ${MAX_BACKGROUND_TERMINALS} background terminals can run at once.`);
974
+ }
975
+ const cwd = safeWorkspacePath(context.workspace, args.cwd || ".");
976
+ const command = String(args.command || "");
977
+ if (!command.trim()) throw new Error("command must not be empty.");
978
+ if (!await confirm(context.terminal, context.options, "start background terminal", `$ ${command}\n cwd: ${path.relative(context.workspace, cwd) || "."}`)) {
979
+ return { ok: false, error: "User denied background terminal." };
980
+ }
981
+ const windows = process.platform === "win32";
982
+ const executable = windows ? (process.env.ComSpec || "cmd.exe") : "/bin/sh";
983
+ const shellArgs = windows ? ["/d", "/s", "/c", command] : ["-lc", command];
984
+ const child = spawn(executable, shellArgs, { cwd, env: process.env, windowsHide: true });
985
+ const id = `bg_${crypto.randomUUID().slice(0, 8)}`;
986
+ const entry = {
987
+ id,
988
+ command,
989
+ relativeCwd: path.relative(context.workspace, cwd) || ".",
990
+ child,
991
+ stdout: "",
992
+ stderr: "",
993
+ truncated: false,
994
+ startedAt: new Date().toISOString(),
995
+ exitedAt: null,
996
+ exit: null,
997
+ };
998
+ child.stdout.on("data", (chunk) => appendBackgroundOutput(entry, "stdout", chunk));
999
+ child.stderr.on("data", (chunk) => appendBackgroundOutput(entry, "stderr", chunk));
1000
+ child.on("error", (error) => {
1001
+ entry.stderr += error.message;
1002
+ entry.exit = { code: null, signal: "ERROR" };
1003
+ entry.exitedAt = new Date().toISOString();
1004
+ });
1005
+ child.on("close", (code, signal) => {
1006
+ entry.exit = { code, signal };
1007
+ entry.exitedAt = new Date().toISOString();
1008
+ });
1009
+ terminals.set(id, entry);
1010
+ return backgroundSnapshot(entry, args.max_bytes);
1011
+ }
1012
+
1013
+ async function readBackgroundTerminal(context, args) {
1014
+ const terminals = ensureBackgroundTerminals(context);
1015
+ const id = String(args.terminal_id || "");
1016
+ const entry = terminals.get(id);
1017
+ if (!entry) return { ok: false, error: `Unknown background terminal: ${id}` };
1018
+ const waitMs = Math.max(0, Math.min(10000, Number(args.wait_ms || 0)));
1019
+ if (waitMs > 0 && entry.exit === null) {
1020
+ const stdoutLength = entry.stdout.length;
1021
+ const stderrLength = entry.stderr.length;
1022
+ const deadline = Date.now() + waitMs;
1023
+ while (Date.now() < deadline && entry.exit === null && entry.stdout.length === stdoutLength && entry.stderr.length === stderrLength) {
1024
+ await new Promise((resolve) => setTimeout(resolve, Math.min(50, deadline - Date.now())));
1025
+ }
1026
+ }
1027
+ const snapshot = backgroundSnapshot(entry, args.max_bytes);
1028
+ if (!snapshot.running) terminals.delete(id);
1029
+ return snapshot;
1030
+ }
1031
+
1032
+ async function stopBackgroundTerminal(context, args) {
1033
+ const terminals = ensureBackgroundTerminals(context);
1034
+ const id = String(args.terminal_id || "");
1035
+ const entry = terminals.get(id);
1036
+ if (!entry) return { ok: false, error: `Unknown background terminal: ${id}` };
1037
+ const signal = String(args.signal || "SIGTERM");
1038
+ if (entry.exit === null) entry.child.kill(signal);
1039
+ await new Promise((resolve) => setTimeout(resolve, 25));
1040
+ const snapshot = backgroundSnapshot(entry, args.max_bytes);
1041
+ if (entry.exit !== null) terminals.delete(id);
1042
+ return snapshot;
1043
+ }
1044
+
856
1045
  async function requestCompletion(state) {
857
1046
  for (let attempt = 0; attempt <= MAX_API_RETRIES; attempt += 1) {
858
1047
  try {
@@ -872,9 +1061,10 @@ async function requestCompletion(state) {
872
1061
 
873
1062
  async function requestCompletionOnce(state) {
874
1063
  const providerSession = state.providerSessions?.[state.model] || null;
1064
+ const prompt = systemPrompt(state.workspace, state.options);
875
1065
  const body = {
876
1066
  model: state.model,
877
- messages: state.messages,
1067
+ messages: outboundMessages(state),
878
1068
  tools: EFFECTIVE_TOOL_DEFINITIONS,
879
1069
  tool_choice: "auto",
880
1070
  parallel_tool_calls: false,
@@ -886,6 +1076,8 @@ async function requestCompletionOnce(state) {
886
1076
  nox_new_provider_chat: !providerSession,
887
1077
  ...(providerSession ? { nox_provider_session: providerSession } : {}),
888
1078
  qwen_cli_session_id: state.sessionId,
1079
+ nox_prompt_transport: "user-prefix",
1080
+ nox_system_prompt_sha256: crypto.createHash("sha256").update(prompt).digest("hex"),
889
1081
  nox_thinking_level: "medium",
890
1082
  },
891
1083
  };
@@ -929,9 +1121,26 @@ async function collectChatStream(body, { onText, onThinking }) {
929
1121
  let buffer = "";
930
1122
  let content = "";
931
1123
  let reasoning = "";
1124
+ let pendingText = "";
1125
+ let streamingText = false;
932
1126
  const toolCalls = [];
933
1127
  let providerSession = null;
934
1128
  let completed = false;
1129
+ const maybeTextToolPrefix = (value) => {
1130
+ const trimmed = String(value || "").trimStart();
1131
+ return "<NOX_TOOL_CALL>".startsWith(trimmed) || trimmed.startsWith("<NOX_TOOL_CALL>");
1132
+ };
1133
+ const handleText = (chunk) => {
1134
+ if (streamingText) {
1135
+ onText(chunk);
1136
+ return;
1137
+ }
1138
+ pendingText += chunk;
1139
+ if (maybeTextToolPrefix(pendingText)) return;
1140
+ streamingText = true;
1141
+ onText(pendingText);
1142
+ pendingText = "";
1143
+ };
935
1144
  while (true) {
936
1145
  const { value, done } = await reader.read();
937
1146
  if (done) break;
@@ -966,7 +1175,7 @@ async function collectChatStream(body, { onText, onThinking }) {
966
1175
  }
967
1176
  if (typeof delta.content === "string" && delta.content) {
968
1177
  content += delta.content;
969
- onText(delta.content);
1178
+ handleText(delta.content);
970
1179
  }
971
1180
  for (const part of delta.tool_calls || []) {
972
1181
  const index = Number(part.index || 0);
@@ -988,6 +1197,17 @@ async function collectChatStream(body, { onText, onThinking }) {
988
1197
  error.partialResponse = Boolean(content || toolCalls.length);
989
1198
  throw error;
990
1199
  }
1200
+ const textToolCall = parseTextToolCall(content);
1201
+ if (textToolCall && !toolCalls.length) {
1202
+ return {
1203
+ role: "assistant",
1204
+ content: null,
1205
+ ...(reasoning ? { reasoning_content: reasoning } : {}),
1206
+ ...(providerSession ? { provider_session: providerSession } : {}),
1207
+ tool_calls: [textToolCall],
1208
+ };
1209
+ }
1210
+ if (!streamingText && pendingText) onText(pendingText);
991
1211
  if (content && !onText) process.stdout.write("\n");
992
1212
  return {
993
1213
  role: "assistant",
@@ -1004,7 +1224,7 @@ async function runAgentTurn(state, userMessage) {
1004
1224
  await persistSession(state);
1005
1225
  for (let step = 0; step < MAX_TOOL_STEPS; step += 1) {
1006
1226
  state.ui?.working(step + 1);
1007
- const assistant = await requestCompletion(state);
1227
+ const assistant = normalizeAssistantToolTransport(await requestCompletion(state));
1008
1228
  if (assistant.provider_session && typeof assistant.provider_session === "object") {
1009
1229
  state.providerSessions[state.model] = assistant.provider_session;
1010
1230
  }
@@ -1123,6 +1343,7 @@ async function main() {
1123
1343
  workspace,
1124
1344
  sessionId: "",
1125
1345
  providerSessions: {},
1346
+ backgroundTerminals: new Map(),
1126
1347
  messages: [],
1127
1348
  ui,
1128
1349
  historyErrorShown: false,
@@ -1153,7 +1374,7 @@ async function main() {
1153
1374
 
1154
1375
  try {
1155
1376
  if (options.prompt) {
1156
- await runAgentTurn(state, { role: "user", content: options.prompt });
1377
+ await runAgentTurn(state, { role: "user", content: options.clarify ? clarifyPrompt(options.prompt) : options.prompt });
1157
1378
  return;
1158
1379
  }
1159
1380
  if (!ui) throw new Error("Interactive mode requires a terminal or --prompt.");
@@ -1203,13 +1424,16 @@ async function main() {
1203
1424
  const image = parseImageCommand(input);
1204
1425
  const file = parseFileCommand(input);
1205
1426
  const planTask = input.startsWith("/plan ") ? input.slice(6).trim() : "";
1427
+ const clarifyTask = input.startsWith("/clarify ") ? input.slice(9).trim() : "";
1206
1428
  try {
1207
1429
  await runAgentTurn(state,
1208
1430
  image ? await imageMessage(image.filePath, image.prompt)
1209
1431
  : file ? await fileMessage(file.filePath, file.prompt)
1210
1432
  : {
1211
1433
  role: "user",
1212
- content: planTask
1434
+ content: clarifyTask
1435
+ ? clarifyPrompt(clarifyTask)
1436
+ : planTask
1213
1437
  ? `Create and show a concrete plan with update_plan before executing this task, then keep it updated:\n\n${planTask}`
1214
1438
  : input,
1215
1439
  });
package/terminal-ui.js CHANGED
@@ -218,7 +218,7 @@ export class TerminalUI {
218
218
  if (approvalMode === "auto") {
219
219
  this.write(`${this.yellow(" Auto approval is active: model-requested changes and commands run without prompts.")}\n`);
220
220
  }
221
- this.write(`${this.dim(" /plan /image /file /model /sessions /approval /exit")}\n`);
221
+ this.write(`${this.dim(" /plan /clarify /image /file /model /sessions /approval /exit")}\n`);
222
222
  }
223
223
 
224
224
  async readMessage() {