feat: add chat replay verification

This commit is contained in:
thanhnv
2026-07-08 22:45:12 +09:00
parent 49d0363c6a
commit 36af576f15
15 changed files with 350 additions and 14 deletions
@@ -1,4 +1,4 @@
import { Body, Controller, Get, Headers, Inject, Post } from '@nestjs/common';
import { Body, Controller, Get, Headers, Inject, Post, Query } from '@nestjs/common';
import { ok } from '../common/api-response.js';
import { actorFromHeaders } from '../common/auth-context.js';
import { ChatAskInput, ChatService } from './chat.service.js';
@@ -17,6 +17,11 @@ export class ChatController {
return ok(this.svc.verifyAudit());
}
@Get('replay')
replay(@Query('chatId') chatId?: string, @Query('turnId') turnId?: string) {
return ok(this.svc.replay(chatId || '', turnId || ''));
}
@Get('actions')
actions(@Headers() headers: Record<string, string | string[] | undefined>) {
return ok(this.svc.listActions(actorFromHeaders(headers)));
@@ -22,6 +22,7 @@ const HARNESS_BIN = join(APP_ROOT, 'packages', 'casan-harness', 'scripts', 'bash
const CHAT_CLI = join(HARNESS_BIN, 'chat-turn.py');
const OPERATOR_CLI = join(HARNESS_BIN, 'chat-operator.py');
const AGENT_CLI = join(HARNESS_BIN, 'chat-agent-resolver.py');
const REPLAY_CLI = join(HARNESS_BIN, 'chat-replay.py');
const RBAC_CLI = join(HARNESS_BIN, 'rbac-check.py');
function runPython(script: string, args: string[]): CommandResult {
@@ -93,6 +94,16 @@ export class ChatService {
return { ok: res.status === 0, output: res.stdout || res.stderr };
}
replay(chatId = '', turnId = '') {
const args = ['replay'];
if (chatId) args.push('--chat-id', chatId);
if (turnId) args.push('--turn-id', turnId);
const res = runPython(REPLAY_CLI, args);
const parsed = parseJson<Record<string, any>>(res.stdout);
if (parsed) return { ok: res.status === 0, ...parsed };
throw new InternalServerErrorException(res.stderr || res.stdout || 'CHAT_REPLAY_FAILED');
}
listActions(actor: SettingsActor) {
this.requireRead(actor);
const res = runPython(OPERATOR_CLI, ['list-actions']);
@@ -89,6 +89,10 @@ test('chat ask executes registered operator action through action-gate', () => {
assert.equal(res.loop_run.side_effect_released, true);
assert.equal(res.loop_run.trace_verify.ok, true);
assert.equal(res.loop_run.replay.ok, true);
const replay = svc.replay('operator-chat') as any;
assert.equal(replay.ok, true);
assert.equal(replay.decision, 'MATCH');
assert.equal(replay.loop_replayed, 1);
assert.equal(res.audit_verify.ok, true);
});
});