Skip to content

feat: Update opinionated CLI config to include Opus, Sonnet, and Haiku models in order#10966

Draft
sestinj wants to merge 13 commits intomainfrom
nate/cn-cn-cn
Draft

feat: Update opinionated CLI config to include Opus, Sonnet, and Haiku models in order#10966
sestinj wants to merge 13 commits intomainfrom
nate/cn-cn-cn

Conversation

@sestinj
Copy link
Contributor

@sestinj sestinj commented Mar 2, 2026

Summary

Updates the CLI's opinionated default configuration to include all three Claude 4-6 models in the preferred order when using the ANTHROPIC_API_KEY fallback.

Changes

  • Modified updateAnthropicModelInYaml() to create all three Claude models instead of just Sonnet
  • Model order: Opus (first/default), Sonnet (second), Haiku (third)
  • Updated filtering logic to handle all three Claude model types
  • Updated all test cases to verify the new three-model configuration

When This Gets Used

This opinionated config is created when:

  • Headless mode with ANTHROPIC_API_KEY environment variable set (no config file)
  • Interactive onboarding when user chooses Enter your Anthropic API key
  • Normal flow fallback when user has completed onboarding but no config exists and ANTHROPIC_API_KEY is available

Testing

  • All existing tests pass
  • Updated test expectations for three-model configuration
  • E2E tests with ANTHROPIC_API_KEY flow still work

Continue Tasks: ✅ 7 no changes — View all


Summary by cubic

Updates the CLI’s default Anthropic config to include Opus, Sonnet, and Haiku in order, adds skill slash commands with direct invocation, enables image-aware file reads, and introduces a structured plan mode with an ExitPlanMode tool and UI polish. Also fixes assistant prefill errors after compaction by appending a user “continue” message.

  • New Features

    • Fallback config (when ANTHROPIC_API_KEY is set) now creates Opus, Sonnet, and Haiku in that order.
    • Skills: invoke via /skills, /skills , or /; autocomplete shows skills loaded from .continue/skills/, .claude/skills/, or ~/.continue/skills/. Optional when_to_use frontmatter; structured tool prompts/output with <available_skills>, , and <skill_files>.
    • Plan mode: new ExitPlanMode tool (plan-only), clearer system message/workflow, and permission policy updated to allow ExitPlanMode.
    • Read tool supports images (png/jpg/gif/webp) and returns multipart results; added an AI SDK adapter so image parts are sent to models. Tests cover text and image cases.
    • UI: user messages render as full-width white blocks, leading newlines are trimmed from assistant output, Bash output lines capped at 200 chars, and Esc dismisses Quiz prompts. Permission selector replaces the “dangerous commands” warning with an Auto mode tip. AGENTS.md adds local build/link steps.
  • Bug Fixes

    • Compaction now appends a user “continue” message to avoid assistant prefill errors with Anthropic models.

Written for commit d546f88. Summary will update on new commits.

sestinj and others added 3 commits March 1, 2026 17:14
Skills can now be invoked as slash commands in the Continue CLI:
- /skills - lists all available skills
- /skills <name> - invokes a specific skill by name
- /<skill-name> - directly invoke any skill as a slash command

Skills appear in the slash command autocomplete dropdown alongside
system commands, assistant prompts, and invokable rules.

The getAllSlashCommands function is now async to support loading
skills from the filesystem (.continue/skills/, .claude/skills/,
~/.continue/skills/).

Generated with [Continue](https://continue.dev)

Co-Authored-By: Continue <noreply@continue.dev>
…u models in order

- Modified updateAnthropicModelInYaml() to create all three Claude 4-6 models
- Order: Opus (first/default), Sonnet (second), Haiku (third)
- Updated filtering logic to handle all three Claude model types
- Updated all test cases to verify the new three-model configuration
- This affects the fallback config created when ANTHROPIC_API_KEY is set

Generated with Continue (https://continue.dev)

Co-Authored-By: Continue <noreply@continue.dev>
Document the npm link workflow so agents remember to build and
link after finishing features, making cn immediately testable.

Generated with [Continue](https://continue.dev)

Co-Authored-By: Continue <noreply@continue.dev>
@sestinj sestinj requested a review from a team as a code owner March 2, 2026 01:25
@sestinj sestinj requested review from RomneyDa and removed request for a team March 2, 2026 01:25
@dosubot dosubot bot added the size:L This PR changes 100-499 lines, ignoring generated files. label Mar 2, 2026
… selector

Remove the annoying and poorly worded 'Dangerous commands will be blocked
regardless of your preference' message. Replace it with a helpful tip about
using Shift+Tab to switch to Auto mode (which never asks for permissions).

Also clean up all related dead code: hasDynamicEvaluation prop,
hasShownDangerousCommandWarning state, and unused ALL_BUILT_IN_TOOLS import.

Generated with [Continue](https://continue.dev)

Co-Authored-By: Continue <noreply@continue.dev>
@continue
Copy link
Contributor

continue bot commented Mar 2, 2026

Docs Review: No documentation updates needed.

This PR changes the internal default model configuration for the Anthropic API key flow—adding Opus, Sonnet, and Haiku instead of just Sonnet. The CLI documentation intentionally stays at a high level ("you can use an Anthropic API key directly") without specifying which exact models get auto-configured.

This is appropriate because:

  • Users see the configured models in their config.yaml after setup
  • Documenting specific model defaults would create maintenance burden
  • The change doesn't affect how users interact with the CLI or configure it

Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 issues found across 10 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="extensions/cli/src/ui/SlashCommandUI.tsx">

<violation number="1" location="extensions/cli/src/ui/SlashCommandUI.tsx:43">
P2: Async command loading can race with assistant changes or unmounts; the later-resolving promise will overwrite newer commands or trigger setState on an unmounted component. Add a cancellation/active flag in the effect so only the latest request updates state.</violation>
</file>

<file name="extensions/cli/src/ui/UserInput.tsx">

<violation number="1" location="extensions/cli/src/ui/UserInput.tsx:190">
P2: When `assistant`/`isRemoteMode` becomes false, the cached slash commands are never reset, so the UI can keep showing stale assistant/remote commands after switching modes. Reset the ref to the fallback list in the `else` branch.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

getAllSlashCommands(assistant || ({} as AssistantConfig), {
isRemoteMode,
});
}).then(setAllCommands);
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Async command loading can race with assistant changes or unmounts; the later-resolving promise will overwrite newer commands or trigger setState on an unmounted component. Add a cancellation/active flag in the effect so only the latest request updates state.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At extensions/cli/src/ui/SlashCommandUI.tsx, line 43:

<comment>Async command loading can race with assistant changes or unmounts; the later-resolving promise will overwrite newer commands or trigger setState on an unmounted component. Add a cancellation/active flag in the effect so only the latest request updates state.</comment>

<file context>
@@ -29,20 +29,19 @@ const SlashCommandUI: React.FC<SlashCommandUIProps> = ({
+      getAllSlashCommands(assistant || ({} as AssistantConfig), {
         isRemoteMode,
-      });
+      }).then(setAllCommands);
     }
-
</file context>
Fix with Cubic

Comment on lines +190 to +198
useEffect(() => {
if (assistant || isRemoteMode) {
return getAllSlashCommands(assistant || ({} as AssistantConfig), {
getAllSlashCommands(assistant || ({} as AssistantConfig), {
isRemoteMode,
}).then((commands) => {
slashCommandsRef.current = commands;
});
}
}, [assistant?.prompts, assistant?.rules, isRemoteMode]);
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: When assistant/isRemoteMode becomes false, the cached slash commands are never reset, so the UI can keep showing stale assistant/remote commands after switching modes. Reset the ref to the fallback list in the else branch.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At extensions/cli/src/ui/UserInput.tsx, line 190:

<comment>When `assistant`/`isRemoteMode` becomes false, the cached slash commands are never reset, so the UI can keep showing stale assistant/remote commands after switching modes. Reset the ref to the fallback list in the `else` branch.</comment>

<file context>
@@ -180,20 +180,24 @@ const UserInput: React.FC<UserInputProps> = ({
+    { name: "exit", description: "Exit the chat", category: "system" },
+  ]);
+
+  useEffect(() => {
     if (assistant || isRemoteMode) {
-      return getAllSlashCommands(assistant || ({} as AssistantConfig), {
</file context>
Suggested change
useEffect(() => {
if (assistant || isRemoteMode) {
return getAllSlashCommands(assistant || ({} as AssistantConfig), {
getAllSlashCommands(assistant || ({} as AssistantConfig), {
isRemoteMode,
}).then((commands) => {
slashCommandsRef.current = commands;
});
}
}, [assistant?.prompts, assistant?.rules, isRemoteMode]);
useEffect(() => {
if (assistant || isRemoteMode) {
getAllSlashCommands(assistant || ({} as AssistantConfig), {
isRemoteMode,
}).then((commands) => {
slashCommandsRef.current = commands;
});
return;
}
slashCommandsRef.current = [
{ name: "help", description: "Show help message", category: "system" },
{ name: "clear", description: "Clear the chat history", category: "system" },
{ name: "exit", description: "Exit the chat", category: "system" },
];
}, [assistant?.prompts, assistant?.rules, isRemoteMode]);
Fix with Cubic

sestinj and others added 8 commits March 1, 2026 17:43
Previously the TUI displayed the first 4 lines of Bash output with no
per-line character limit, so a single very long line could flood the
terminal. Now each displayed line is capped at 200 characters with a
trailing '...' when truncated.

Generated with [Continue](https://continue.dev)

Co-Authored-By: Continue <noreply@continue.dev>
User messages in the chat history now render with a white background
and black text instead of a dim blue dot + gray text. This makes them
stand out clearly from assistant responses and tool output.

Generated with [Continue](https://continue.dev)

Co-Authored-By: Continue <noreply@continue.dev>
User messages in the chat history now render with a white background
and black text spanning the full terminal width, instead of a dim blue
dot + gray text. Long lines are word-wrapped at word boundaries, and
every line is padded to the terminal width so the block is clean and
even.

Generated with [Continue](https://continue.dev)

Co-Authored-By: Continue <noreply@continue.dev>
…nvocation language

- Tool description now uses <available_skills> XML structure (from OpenCode) with
  BLOCKING REQUIREMENT language (from Claude Code) to ensure the model loads
  skills before responding
- Tool output wraps content in <skill name="..."> tags with skill directory
  context and <skill_files> listing for bundled resources
- Add optional when_to_use frontmatter field to SKILL.md for explicit trigger
  conditions (separates 'what it does' from 'when to invoke it')
- Update both core/ and extensions/cli/ implementations to match
- Update CLI tests for new output format

Generated with [Continue](https://continue.dev)

Co-Authored-By: Continue <noreply@continue.dev>
LLMs often start responses with leading newlines (e.g., '\n\nYou're right...'),
which displayed as blank lines above the bullet point in the assistant's message.

Two fixes:
1. MemoizedMessage: trimStart() on formatted content before passing to
   MarkdownRenderer for both regular assistant messages and tool call messages
2. processChunkContent: strip leading whitespace from the first chunk of a
   streaming response to prevent blank lines during live output

The raw content stored in chat history is preserved unchanged to avoid
affecting LLM context in multi-turn conversations.

Generated with [Continue](https://continue.dev)

Co-Authored-By: Continue <noreply@continue.dev>
Plan mode now follows a structured workflow:
1. Agent investigates with read-only tools
2. Agent creates a plan using the Checklist tool
3. Agent calls ExitPlanMode to present plan for user approval
4. On approval, switches to normal mode for implementation

Changes:
- Add ExitPlanMode tool (src/tools/planMode.ts) - only available in plan mode
- Enhance plan mode system message with structured workflow instructions
- Register ExitPlanMode in tool index, built-in tool lists, and permission policies
- Update existing system message tests to match new plan mode message
- Add comprehensive tests for ExitPlanMode tool (9 tests)

Generated with [Continue](https://continue.dev)

Co-Authored-By: Continue <noreply@continue.dev>
…ruse

- Plan mode no longer references or requires Checklist. The agent presents
  its plan in prose and calls ExitPlanMode when ready.
- Checklist tool description updated to discourage use for simple/single-step
  tasks. Instructs the agent to keep items high-level (3-7 items) and avoid
  granular sub-steps or implementation details.
- ExitPlanMode tool description simplified — no longer mentions Checklist.

Generated with [Continue](https://continue.dev)

Co-Authored-By: Continue <noreply@continue.dev>
@dosubot dosubot bot added size:XXL This PR changes 1000+ lines, ignoring generated files. and removed size:L This PR changes 100-499 lines, ignoring generated files. labels Mar 2, 2026
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

4 issues found across 33 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="extensions/cli/src/tools/skills.ts">

<violation number="1" location="extensions/cli/src/tools/skills.ts:127">
P2: Derive the skill directory using both path separators (or a path utility). The current substring("/") logic fails on Windows paths and produces an empty skill directory.</violation>
</file>

<file name="extensions/cli/src/ui/components/MemoizedMessage.tsx">

<violation number="1" location="extensions/cli/src/ui/components/MemoizedMessage.tsx:171">
P2: Guard against non-positive terminal widths; `contentWidth` can be <= 0 (e.g., non‑TTY), which makes the wrap loop non-terminating and can hang rendering. Clamp `contentWidth` to at least 1 (or return early) before the wrapping loop.</violation>
</file>

<file name="extensions/cli/src/tools/readFile.ts">

<violation number="1" location="extensions/cli/src/tools/readFile.ts:140">
P2: Reading an image returns before markFileAsRead, so the Edit tool will still reject the same file as “not read.” If the Read tool now supports images, it should still record the file in readFilesSet to keep the “read before edit” guard consistent.</violation>
</file>

<file name="extensions/cli/src/tools/types.ts">

<violation number="1" location="extensions/cli/src/tools/types.ts:72">
P2: `typeof null === "object"` is `true` in JavaScript. If a `null` value reaches this type guard at runtime (e.g., from an untyped boundary or unexpected tool result), accessing `result.type` will throw a `TypeError`. Add a null check for defensive safety.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

content.push(
`<skill_files>${skill.files.join(",")}</skill_files>`,
`<other_instructions>Use the read file tool to access skill files as needed.</other_instructions>`,
const skillDir = skill.path.substring(
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Derive the skill directory using both path separators (or a path utility). The current substring("/") logic fails on Windows paths and produces an empty skill directory.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At extensions/cli/src/tools/skills.ts, line 127:

<comment>Derive the skill directory using both path separators (or a path utility). The current substring("/") logic fails on Windows paths and produces an empty skill directory.</comment>

<file context>
@@ -63,20 +116,40 @@ ${skills.map((skill) => `\nname: ${skill.name}\ndescription: ${skill.description
-        content.push(
-          `<skill_files>${skill.files.join(",")}</skill_files>`,
-          `<other_instructions>Use the read file tool to access skill files as needed.</other_instructions>`,
+        const skillDir = skill.path.substring(
+          0,
+          skill.path.lastIndexOf("/"),
</file context>
Fix with Cubic

const text = formatMessageContentForDisplay(message.content);
const termWidth = process.stdout.columns || 80;
// 1 char padding on each side
const contentWidth = termWidth - 2;
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Guard against non-positive terminal widths; contentWidth can be <= 0 (e.g., non‑TTY), which makes the wrap loop non-terminating and can hang rendering. Clamp contentWidth to at least 1 (or return early) before the wrapping loop.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At extensions/cli/src/ui/components/MemoizedMessage.tsx, line 171:

<comment>Guard against non-positive terminal widths; `contentWidth` can be <= 0 (e.g., non‑TTY), which makes the wrap loop non-terminating and can hang rendering. Clamp `contentWidth` to at least 1 (or return early) before the wrapping loop.</comment>

<file context>
@@ -162,19 +164,49 @@ export const MemoizedMessage = memo<MemoizedMessageProps>(
+      const text = formatMessageContentForDisplay(message.content);
+      const termWidth = process.stdout.columns || 80;
+      // 1 char padding on each side
+      const contentWidth = termWidth - 2;
+      // Word-wrap then pad each line to fill the terminal width
+      const wrappedLines: string[] = [];
</file context>
Suggested change
const contentWidth = termWidth - 2;
const contentWidth = Math.max(termWidth - 2, 1);
Fix with Cubic

Comment on lines +140 to +142
if (isImageFile(filepath)) {
return readImageFile(realPath, filepath);
}
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Reading an image returns before markFileAsRead, so the Edit tool will still reject the same file as “not read.” If the Read tool now supports images, it should still record the file in readFilesSet to keep the “read before edit” guard consistent.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At extensions/cli/src/tools/readFile.ts, line 140:

<comment>Reading an image returns before markFileAsRead, so the Edit tool will still reject the same file as “not read.” If the Read tool now supports images, it should still record the file in readFilesSet to keep the “read before edit” guard consistent.</comment>

<file context>
@@ -81,6 +135,12 @@ export const readFileTool: Tool = {
       const realPath = fs.realpathSync(filepath);
+
+      // Handle image files
+      if (isImageFile(filepath)) {
+        return readImageFile(realPath, filepath);
+      }
</file context>
Suggested change
if (isImageFile(filepath)) {
return readImageFile(realPath, filepath);
}
if (isImageFile(filepath)) {
markFileAsRead(realPath);
return readImageFile(realPath, filepath);
}
Fix with Cubic

export function isMultipartToolResult(
result: ToolResult,
): result is MultipartToolResult {
return typeof result === "object" && result.type === "multipart";
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: typeof null === "object" is true in JavaScript. If a null value reaches this type guard at runtime (e.g., from an untyped boundary or unexpected tool result), accessing result.type will throw a TypeError. Add a null check for defensive safety.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At extensions/cli/src/tools/types.ts, line 72:

<comment>`typeof null === "object"` is `true` in JavaScript. If a `null` value reaches this type guard at runtime (e.g., from an untyped boundary or unexpected tool result), accessing `result.type` will throw a `TypeError`. Add a null check for defensive safety.</comment>

<file context>
@@ -41,13 +41,57 @@ export interface ToolRunContext {
+export function isMultipartToolResult(
+  result: ToolResult,
+): result is MultipartToolResult {
+  return typeof result === "object" && result.type === "multipart";
+}
+
</file context>
Suggested change
return typeof result === "object" && result.type === "multipart";
return typeof result === "object" && result !== null && result.type === "multipart";
Fix with Cubic

After compaction, the compacted history ended with an assistant message,
which caused Anthropic/Claude models to reject the request with an
'assistant message prefill' error. This happened because
compactChatHistory() produced [system, assistant_summary] and multiple
code paths in streamChatResponse.ts could send this directly to the API
without appending a user message first.

Fix: Add a user 'continue' message to the compacted history so it always
ends with a valid user turn: [system, assistant_summary, user_continue].

Generated with [Continue](https://continue.dev)

Co-Authored-By: Continue <noreply@continue.dev>
@sestinj sestinj marked this pull request as draft March 2, 2026 08:15
@sestinj sestinj marked this pull request as draft March 2, 2026 08:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL This PR changes 1000+ lines, ignoring generated files.

Projects

Status: Todo

Development

Successfully merging this pull request may close these issues.

1 participant