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

@@ -37,6 +37,34 @@ export interface OpenAiMessage {
// omit it and exercise the byte-stability surface directly through
// buildSystemPromptWithFingerprint. The observer Map in system-prompt.ts
// updates regardless of whether log is passed.
function toolResultIdsFollowing(history: Message[], assistantIdx: number): Set<string> {
const ids = new Set<string>();
for (let j = assistantIdx + 1; j < history.length; j++) {
const row = history[j]!;
if (row.role === 'user' || row.role === 'assistant') break;
if (row.role === 'tool' && row.tool_results?.tool_call_id) {
ids.add(row.tool_results.tool_call_id);
}
}
return ids;
}
function findAssistantOwnerForToolCall(history: Message[], toolIdx: number, callId: string): number | null {
for (let k = toolIdx - 1; k >= 0; k--) {
const row = history[k]!;
if (row.role === 'user') break;
if (row.role === 'assistant' && row.tool_calls?.some((tc) => tc.id === callId)) return k;
}
return null;
}
function assistantToolCallsArePayloadComplete(history: Message[], assistantIdx: number): boolean {
const assistant = history[assistantIdx]!;
if (!assistant.tool_calls?.length) return false;
const fulfilled = toolResultIdsFollowing(history, assistantIdx);
return assistant.tool_calls.every((tc) => fulfilled.has(tc.id));
}
export async function buildMessagesPayload(
session: Session,
project: Project,
@@ -97,6 +125,10 @@ export async function buildMessagesPayload(
if (m.role === 'tool') {
const tr = m.tool_results;
if (!tr) continue;
const ownerIdx = findAssistantOwnerForToolCall(history, i, tr.tool_call_id);
if (ownerIdx == null || !assistantToolCallsArePayloadComplete(history, ownerIdx)) {
continue;
}
const outputText = tr.error
? `error: ${tr.error}`
: typeof tr.output === 'string'
@@ -115,11 +147,15 @@ export async function buildMessagesPayload(
content: m.content && m.content.length > 0 ? m.content : null,
};
if (m.tool_calls && m.tool_calls.length > 0) {
msg.tool_calls = m.tool_calls.map((tc) => ({
id: tc.id,
type: 'function' as const,
function: { name: tc.name, arguments: JSON.stringify(tc.args) },
}));
if (assistantToolCallsArePayloadComplete(history, i)) {
msg.tool_calls = m.tool_calls.map((tc) => ({
id: tc.id,
type: 'function' as const,
function: { name: tc.name, arguments: JSON.stringify(tc.args) },
}));
}
// Orphaned tool_calls (no matching tool rows) are stripped so the
// upstream API does not reject the payload on the next user turn.
}
// v1.13.1-C: collapse reasoning_parts into a single string. The view
// returns them ordered by sequence; multiple reasoning parts on one
@@ -127,6 +163,11 @@ export async function buildMessagesPayload(
if (m.reasoning_parts && m.reasoning_parts.length > 0) {
msg.reasoning = m.reasoning_parts.map((p) => p.text ?? '').join('');
}
const hasPayload =
(msg.content != null && msg.content.trim().length > 0) ||
(msg.tool_calls != null && msg.tool_calls.length > 0) ||
(msg.reasoning != null && msg.reasoning.length > 0);
if (!hasPayload) continue;
out.push(msg);
continue;
}