Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 75 additions & 0 deletions apps/api/src/handlers/tasks/__tests__/getDiffRiskHints.test.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

76 changes: 76 additions & 0 deletions apps/api/src/handlers/tasks/getDiffRiskHints.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import type { Context } from 'hono';
import { z } from 'zod';

import {
DIFF_RISK_HINTS_MAX_DIFF_CHARS,
screenDiffRiskHints,
} from '@roomote/cloud-agents/server';
import { db, eq, tasks, taskRuns } from '@roomote/db/server';

import type { Variables } from '../../types';
import type { McpAuth } from '../mcp/middleware';
import { isRunTokenContext } from '../mcp/proxy-utils';
import { logHandlerError } from '../utils';

const bodySchema = z.object({
diff: z.string().min(1).max(DIFF_RISK_HINTS_MAX_DIFF_CHARS),
});

/**
* Risk hints for a task's own diff: the pull request review pre-screen, run
* before the agent ships. The sandbox sends the diff; the judgment model key
* stays here.
*/
export async function getDiffRiskHints(
c: Context<{ Variables: Variables & { mcpAuth: McpAuth } }>,
): Promise<Response> {
const auth = c.get('mcpAuth').authContext;

if (!isRunTokenContext(auth)) {
return c.json({ error: 'Diff risk hints require a task run token' }, 403);
}

const runId = Number(c.req.param('runId'));

if (!Number.isInteger(runId) || runId <= 0) {
return c.json({ error: 'Invalid task run id' }, 400);
}

if (auth.runId !== runId) {
return c.json(
{ error: 'Task run token does not match requested task run' },
403,
);
}

const parsed = bodySchema.safeParse(await c.req.json().catch(() => null));

if (!parsed.success) {
return c.json(
{
error: `Invalid request: send the diff as \`diff\` (at most ${DIFF_RISK_HINTS_MAX_DIFF_CHARS} characters).`,
},
400,
);
}

try {
const [row] = await db
.select({ title: tasks.title })
.from(taskRuns)
.innerJoin(tasks, eq(tasks.id, taskRuns.taskId))
.where(eq(taskRuns.id, runId))
.limit(1);

if (!row) {
return c.json({ error: 'Task run not found' }, 404);
}

return c.json(
await screenDiffRiskHints({ title: row.title, diff: parsed.data.diff }),
);
} catch (error) {
logHandlerError('getDiffRiskHints', error);
return c.json({ error: 'Failed to screen the diff' }, 500);
}
}
2 changes: 2 additions & 0 deletions apps/api/src/handlers/tasks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { manageSourceControl } from './manageSourceControl';
import { updateTaskModelSelection } from './updateModelSelection';
import { listTaskModels } from './listModels';
import { saveTaskMemory } from './saveTaskMemory';
import { getDiffRiskHints } from './getDiffRiskHints';
import { recordAutomationResult } from './recordAutomationResult';
import { updatePersonalization } from './updatePersonalization';

Expand All @@ -43,4 +44,5 @@ tasksRouter.post('/:taskId/task_suggestions', submitTaskSuggestions);
tasksRouter.post('/:taskId/automation_result', recordAutomationResult);
tasksRouter.post('/:taskId/mcp_recommendations', submitMcpRecommendations);
tasksRouter.post('/runs/:runId/memory', saveTaskMemory);
tasksRouter.post('/runs/:runId/diff_risk_hints', getDiffRiskHints);
tasksRouter.post('/runs/:runId/personalization', updatePersonalization);
3 changes: 3 additions & 0 deletions apps/docs/models.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -355,6 +355,9 @@ When a judgment model is on, Roomote asks it first for:
- choosing the forum tag when Roomote opens a thread in a Discord forum channel
- whether a settled session or task turn holds something worth saving to
[Memory](/memory) that the agent did not record itself
- ranking a task's own changed hunks by defect risk during its self-review,
before it pushes or opens a pull request (the same pre-screen pull request
reviews use), as advisory hints the agent re-reads

Roomote acts on the judgment model only when it is confident. When it is
unsure, unavailable, or returns an error, Roomote keeps the behavior it has
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading