feat: post-review backlog hardening (cancel/parser/stall/history/9502)
Five independent items from the post-review backlog. F1: Stop on an external agent task now aborts the running child via a per-task AbortController registry reachable from the cancel route, and finalizes the assistant message as cancelled (fixing two latent bugs — catch blocks left the message streaming, and warm success-paths wrote complete on an aborted turn); warm pools/worktrees are preserved and the native path is unchanged. F2/F3: prune the tool-call parser to its two load-bearing exports (unexport eight zero-caller symbols, add a gate test for the <invoke>-as-text fallback) and route placeholder-rejection logging through pino. F6: a 90s per-chunk stall-timeout wraps native inference's fullStream via AbortSignal.any so a hung stream finalizes the message instead of hanging — no retry (a pure classifyStreamError helper is added). F7: a read-only view_session_history MCP tool (newest-N, chronological). F9: retire the unused apps/coder/web :9502 fallback SPA, keeping every API/WS/health/MCP route. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -29,6 +29,17 @@ interface ProjectPathRow {
|
||||
path: string;
|
||||
}
|
||||
|
||||
interface MessageRow {
|
||||
id: string;
|
||||
session_id: string;
|
||||
chat_id: string | null;
|
||||
role: string;
|
||||
content: string;
|
||||
status: string;
|
||||
model: string | null;
|
||||
created_at: Date;
|
||||
}
|
||||
|
||||
function textResult(data: unknown) {
|
||||
return { content: [{ type: 'text' as const, text: JSON.stringify(data, null, 2) }] };
|
||||
}
|
||||
@@ -189,6 +200,56 @@ export async function startMcpServer(sql: Sql): Promise<void> {
|
||||
},
|
||||
);
|
||||
|
||||
// 6. boocoder.view_session_history
|
||||
server.tool(
|
||||
'boocoder.view_session_history',
|
||||
'Retrieve the most-recent N messages of a session chat transcript (role != system) from messages_with_parts, returned in chronological (oldest→newest) order',
|
||||
{
|
||||
session_id: z.string().describe('Session UUID'),
|
||||
chat_id: z.string().optional().describe('Optional chat UUID — narrows to one chat tab'),
|
||||
limit: z
|
||||
.number()
|
||||
.int()
|
||||
.min(1)
|
||||
.max(200)
|
||||
.optional()
|
||||
.describe('Max messages to return (default 50, max 200)'),
|
||||
},
|
||||
async (args) => {
|
||||
const effectiveLimit = Math.min(args.limit ?? 50, 200);
|
||||
let rows: MessageRow[];
|
||||
if (args.chat_id) {
|
||||
rows = await sql<MessageRow[]>`
|
||||
SELECT id, session_id, chat_id, role, content, status, model, created_at
|
||||
FROM (
|
||||
SELECT id, session_id, chat_id, role, content, status, model, created_at
|
||||
FROM messages_with_parts
|
||||
WHERE session_id = ${args.session_id}
|
||||
AND chat_id = ${args.chat_id}
|
||||
AND role != 'system'
|
||||
ORDER BY created_at DESC
|
||||
LIMIT ${effectiveLimit}
|
||||
) sub
|
||||
ORDER BY created_at ASC
|
||||
`;
|
||||
} else {
|
||||
rows = await sql<MessageRow[]>`
|
||||
SELECT id, session_id, chat_id, role, content, status, model, created_at
|
||||
FROM (
|
||||
SELECT id, session_id, chat_id, role, content, status, model, created_at
|
||||
FROM messages_with_parts
|
||||
WHERE session_id = ${args.session_id}
|
||||
AND role != 'system'
|
||||
ORDER BY created_at DESC
|
||||
LIMIT ${effectiveLimit}
|
||||
) sub
|
||||
ORDER BY created_at ASC
|
||||
`;
|
||||
}
|
||||
return textResult({ session_id: args.session_id, count: rows.length, messages: rows });
|
||||
},
|
||||
);
|
||||
|
||||
// Connect via stdio
|
||||
const transport = new StdioServerTransport();
|
||||
await server.connect(transport);
|
||||
|
||||
Reference in New Issue
Block a user