v2.2-paseo-providers: Paseo provider stack + v2.2.1 pane-scoped chat fixes

Ship Paseo-equivalent provider snapshot, AgentComposerBar, ACP dispatch
rewrite with streaming/persist, permission prompts, and agent commands.
Follow-up: pane-scoped chat resolution, CoderMessageList tool timeline,
WS user-delta replace, and inference orphan tool_call stripping.
Archive openspec v2-2; update CHANGELOG and CURRENT.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-05-26 15:18:31 +00:00
parent 04673eaf59
commit 93d3f86c2b
96 changed files with 6694 additions and 1329 deletions

View File

@@ -23,7 +23,8 @@ import { FileMentionPopover } from '@/components/FileMentionPopover';
import { DropOverlay } from '@/components/DropOverlay';
import { AgentPicker } from '@/components/AgentPicker';
import { ContextBar } from '@/components/ContextBar';
import { SkillSlashCommand } from '@/components/SkillSlashCommand';
import { SlashCommandPicker } from '@/components/SlashCommandPicker';
import { isSlashCommandToken, parseSlashInput, slashQuery } from '@/lib/slash-command';
import { api } from '@/api/client';
import type { Message } from '@/api/types';
import { sessionEvents } from '@/hooks/sessionEvents';
@@ -87,10 +88,8 @@ export function ChatInput({ disabled, projectId, agentId, onAgentChange, session
// Batch 9.6: slash-command dropdown. Opens when `/` is the first char of
// the input and stays open while the input is `/<word>` with no whitespace.
// Disabled entirely when the caller doesn't pass onSlashCommand.
// v1.12 CP7.5: anchorRect was a snapshot taken at open time. SkillSlashCommand
// now reads the live textarea rect via inputRef (textareaRef below) so it can
// recompute on visualViewport changes (iOS keyboard open/close), so the
// anchorRect field is no longer needed in this state.
// SlashCommandPicker reads the live textarea rect via inputRef (textareaRef below)
// so it can recompute on visualViewport changes (iOS keyboard open/close).
const [slashState, setSlashState] = useState<{
query: string;
} | null>(null);
@@ -168,13 +167,11 @@ export function ChatInput({ disabled, projectId, agentId, onAgentChange, session
// input parses to a known skill. Falls through to onSend for unknown
// slash names (literal text) or when slash dispatch isn't wired.
if (onSlashCommand && attachments.length === 0 && text.startsWith('/')) {
const match = text.match(/^\/(\S+)\s*([\s\S]*)$/);
if (match && skillsLookup.has(match[1]!)) {
const skillName = match[1]!;
const args = (match[2] ?? '').trim();
const parsed = parseSlashInput(text);
if (parsed && skillsLookup.has(parsed.cmdName)) {
setBusy(true);
try {
await onSlashCommand(skillName, args);
await onSlashCommand(parsed.cmdName, parsed.args);
setValue('');
setAttachments([]);
setSlashState(null);
@@ -268,8 +265,8 @@ export function ChatInput({ disabled, projectId, agentId, onAgentChange, session
// slash-prefixed token with no whitespace (i.e. user is still typing the
// skill name). Hand off to args mode the moment a space appears or the
// slash leaves position 0.
if (onSlashCommand && /^\/[^\s]*$/.test(newValue)) {
const query = newValue.slice(1);
if (onSlashCommand && isSlashCommandToken(newValue)) {
const query = slashQuery(newValue);
if (!slashState) {
setSlashState({ query });
} else if (slashState.query !== query) {
@@ -496,7 +493,7 @@ export function ChatInput({ disabled, projectId, agentId, onAgentChange, session
function onKeyDown(e: KeyboardEvent<HTMLTextAreaElement>) {
if (mentionState?.open) return;
// SkillSlashCommand owns Arrow/Enter/Tab/Esc via a document listener; let
// SlashCommandPicker owns Arrow/Enter/Tab/Esc via a document listener; let
// it consume them so the textarea doesn't also submit on Enter.
if (slashState) return;
// IME safety: never act on Enter while an IME composition is in flight
@@ -658,12 +655,13 @@ export function ChatInput({ disabled, projectId, agentId, onAgentChange, session
/>
)}
{slashState && (
<SkillSlashCommand
<SlashCommandPicker
query={slashState.query}
skills={skills}
items={skills}
inputRef={textareaRef}
onSelect={handleSlashSelect}
onClose={() => setSlashState(null)}
emptyLabel="No skills available"
/>
)}
</div>