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:
@@ -1,6 +1,8 @@
|
||||
import type { FastifyInstance } from 'fastify';
|
||||
import { z } from 'zod';
|
||||
import type { Sql } from '../db.js';
|
||||
import { getPendingPermission, respondToPermission, cancelPendingPermission } from '../services/permission-waiter.js';
|
||||
import { getTaskCommands } from '../services/agent-commands-cache.js';
|
||||
|
||||
interface InferenceApi {
|
||||
cancel: (sessionId: string, chatId: string) => Promise<boolean>;
|
||||
@@ -11,6 +13,12 @@ const CreateBody = z.object({
|
||||
input: z.string().min(1).max(64_000),
|
||||
agent: z.string().max(100).optional(),
|
||||
model: z.string().max(200).optional(),
|
||||
mode_id: z.string().max(200).optional(),
|
||||
thinking_option_id: z.string().max(200).optional(),
|
||||
});
|
||||
|
||||
const PermissionBody = z.object({
|
||||
option_id: z.string().max(200).nullable(),
|
||||
});
|
||||
|
||||
const ListQuery = z.object({
|
||||
@@ -27,11 +35,11 @@ export function registerTaskRoutes(app: FastifyInstance, sql: Sql, inference: In
|
||||
return { error: 'invalid body', details: parsed.error.flatten() };
|
||||
}
|
||||
|
||||
const { project_id, input, agent, model } = parsed.data;
|
||||
const { project_id, input, agent, model, mode_id, thinking_option_id } = parsed.data;
|
||||
|
||||
const [task] = await sql<{ id: string; state: string }[]>`
|
||||
INSERT INTO tasks (project_id, input, agent, model)
|
||||
VALUES (${project_id}, ${input}, ${agent ?? null}, ${model ?? null})
|
||||
INSERT INTO tasks (project_id, input, agent, model, mode_id, thinking_option_id)
|
||||
VALUES (${project_id}, ${input}, ${agent ?? null}, ${model ?? null}, ${mode_id ?? null}, ${thinking_option_id ?? null})
|
||||
RETURNING id, state
|
||||
`;
|
||||
|
||||
@@ -111,13 +119,15 @@ export function registerTaskRoutes(app: FastifyInstance, sql: Sql, inference: In
|
||||
}
|
||||
|
||||
const task = rows[0]!;
|
||||
if (task.state !== 'pending' && task.state !== 'running') {
|
||||
if (task.state !== 'pending' && task.state !== 'running' && task.state !== 'blocked') {
|
||||
reply.code(409);
|
||||
return { error: `cannot cancel task in state '${task.state}'` };
|
||||
}
|
||||
|
||||
cancelPendingPermission(taskId);
|
||||
|
||||
// If running, try to cancel inference
|
||||
if (task.state === 'running' && task.session_id) {
|
||||
if ((task.state === 'running' || task.state === 'blocked') && task.session_id) {
|
||||
// Find active chat in the task's session
|
||||
const chats = await sql<{ id: string }[]>`
|
||||
SELECT id FROM chats WHERE session_id = ${task.session_id} AND status = 'open'
|
||||
@@ -130,9 +140,45 @@ export function registerTaskRoutes(app: FastifyInstance, sql: Sql, inference: In
|
||||
await sql`
|
||||
UPDATE tasks
|
||||
SET state = 'cancelled', ended_at = clock_timestamp()
|
||||
WHERE id = ${taskId} AND state IN ('pending', 'running')
|
||||
WHERE id = ${taskId} AND state IN ('pending', 'running', 'blocked')
|
||||
`;
|
||||
|
||||
return { cancelled: true };
|
||||
});
|
||||
|
||||
// GET /api/tasks/:id/permission — pending permission prompt (if any)
|
||||
app.get<{ Params: { id: string } }>('/api/tasks/:id/permission', async (req, reply) => {
|
||||
const prompt = getPendingPermission(req.params.id);
|
||||
if (!prompt) {
|
||||
reply.code(404);
|
||||
return { error: 'no pending permission' };
|
||||
}
|
||||
return prompt;
|
||||
});
|
||||
|
||||
// POST /api/tasks/:id/permission — respond to a pending permission prompt
|
||||
app.post<{ Params: { id: string } }>('/api/tasks/:id/permission', async (req, reply) => {
|
||||
const parsed = PermissionBody.safeParse(req.body);
|
||||
if (!parsed.success) {
|
||||
reply.code(400);
|
||||
return { error: 'invalid body', details: parsed.error.flatten() };
|
||||
}
|
||||
|
||||
const ok = respondToPermission(req.params.id, parsed.data.option_id);
|
||||
if (!ok) {
|
||||
reply.code(404);
|
||||
return { error: 'no pending permission' };
|
||||
}
|
||||
return { ok: true };
|
||||
});
|
||||
|
||||
// GET /api/tasks/:id/commands — cached ACP slash commands (if any)
|
||||
app.get<{ Params: { id: string } }>('/api/tasks/:id/commands', async (req, reply) => {
|
||||
const commands = getTaskCommands(req.params.id);
|
||||
if (!commands?.length) {
|
||||
reply.code(404);
|
||||
return { error: 'no commands cached' };
|
||||
}
|
||||
return { taskId: req.params.id, commands };
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user