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>
82 lines
2.3 KiB
TypeScript
82 lines
2.3 KiB
TypeScript
import type { Sql } from '../db.js';
|
|
|
|
interface WorkspacePaneRow {
|
|
id: string;
|
|
kind: string;
|
|
chatId?: string;
|
|
chatIds?: string[];
|
|
activeChatIdx?: number;
|
|
}
|
|
|
|
function chatNameForKind(kind: string): string {
|
|
if (kind === 'coder' || kind === 'agent') return 'BooCoder';
|
|
if (kind === 'terminal') return 'Terminal';
|
|
return 'Chat';
|
|
}
|
|
|
|
function activeChatIdForPane(pane: WorkspacePaneRow): string | undefined {
|
|
const chatIds = pane.chatIds ?? [];
|
|
const idx = pane.activeChatIdx ?? 0;
|
|
if (idx >= 0 && idx < chatIds.length) return chatIds[idx];
|
|
return pane.chatId;
|
|
}
|
|
|
|
/** Resolve the active chat for a workspace pane; auto-seed when empty. */
|
|
export async function resolveChatId(
|
|
sql: Sql,
|
|
sessionId: string,
|
|
paneId: string,
|
|
): Promise<string | null> {
|
|
return sql.begin(async (tx) => {
|
|
const sessionRows = await tx<{ workspace_panes: WorkspacePaneRow[] }[]>`
|
|
SELECT workspace_panes FROM sessions WHERE id = ${sessionId} FOR UPDATE
|
|
`;
|
|
if (sessionRows.length === 0) return null;
|
|
|
|
const panes = sessionRows[0]!.workspace_panes ?? [];
|
|
const paneIdx = panes.findIndex((p) => p.id === paneId);
|
|
if (paneIdx < 0) return null;
|
|
|
|
const pane = panes[paneIdx]!;
|
|
const existingChatId = activeChatIdForPane(pane);
|
|
if (existingChatId) {
|
|
const chatRows = await tx<{ id: string }[]>`
|
|
SELECT id FROM chats
|
|
WHERE id = ${existingChatId}
|
|
AND session_id = ${sessionId}
|
|
AND status = 'open'
|
|
`;
|
|
if (chatRows.length > 0) return existingChatId;
|
|
}
|
|
|
|
const [newChat] = await tx<{ id: string }[]>`
|
|
INSERT INTO chats (session_id, name, status)
|
|
VALUES (${sessionId}, ${chatNameForKind(pane.kind)}, 'open')
|
|
RETURNING id
|
|
`;
|
|
if (!newChat) return null;
|
|
|
|
const nextChatIds = [...(pane.chatIds ?? []), newChat.id];
|
|
const nextActiveIdx = nextChatIds.length - 1;
|
|
const nextPanes = panes.map((p, i) =>
|
|
i === paneIdx
|
|
? {
|
|
...p,
|
|
chatIds: nextChatIds,
|
|
activeChatIdx: nextActiveIdx,
|
|
chatId: newChat.id,
|
|
}
|
|
: p,
|
|
);
|
|
|
|
await tx`
|
|
UPDATE sessions
|
|
SET workspace_panes = ${tx.json(nextPanes as never)},
|
|
updated_at = clock_timestamp()
|
|
WHERE id = ${sessionId}
|
|
`;
|
|
|
|
return newChat.id;
|
|
});
|
|
}
|