feat: chat + optmz control panel
This commit is contained in:
@@ -27,6 +27,15 @@ export class ChatController {
|
||||
return ok(this.svc.verifyAudit());
|
||||
}
|
||||
|
||||
@Get('history')
|
||||
history(
|
||||
@Headers() headers: Record<string, string | string[] | undefined>,
|
||||
@Query('chatId') chatId?: string,
|
||||
@Query('limit') limit?: string,
|
||||
) {
|
||||
return ok(this.svc.history(actorFromHeaders(headers), chatId || '', Number(limit ?? 50)));
|
||||
}
|
||||
|
||||
@Get('replay')
|
||||
replay(@Query('chatId') chatId?: string, @Query('turnId') turnId?: string, @Query('tenant') tenant?: string) {
|
||||
return ok(this.svc.replay(chatId || '', turnId || '', tenant || ''));
|
||||
|
||||
@@ -95,6 +95,17 @@ export class ChatService {
|
||||
return { ok: res.status === 0, output: res.stdout || res.stderr };
|
||||
}
|
||||
|
||||
history(actor: SettingsActor, chatId = '', limit = 50) {
|
||||
this.requireRead(actor);
|
||||
const safeLimit = Math.max(1, Math.min(Number.isFinite(limit) ? Math.trunc(limit) : 50, 100));
|
||||
const args = ['history', '--actor', actor.actor, '--tenant', actor.tenant, '--limit', String(safeLimit)];
|
||||
if (chatId) args.push('--chat-id', chatId);
|
||||
const res = runPython(CHAT_CLI, args);
|
||||
const parsed = parseJson<Record<string, unknown>>(res.stdout);
|
||||
if (res.status === 0 && parsed?.ok === true) return parsed;
|
||||
throw new InternalServerErrorException(res.stderr || res.stdout || 'CHAT_HISTORY_FAILED');
|
||||
}
|
||||
|
||||
/**
|
||||
* Item 3: streaming read-only/analysis turns. The harness emits two NDJSON
|
||||
* phases — an UNCERTIFIED deterministic draft, then the certified final. We
|
||||
|
||||
@@ -47,6 +47,23 @@ test('chat ask returns certified read-only answer with evidence sources', () =>
|
||||
});
|
||||
});
|
||||
|
||||
test('chat history returns only integrity-checked previews for the requesting actor', () => {
|
||||
withTempChatState(() => {
|
||||
const svc = new ChatService();
|
||||
svc.ask({ message: 'Summarize Plan 18 MVP-0 status', chatId: 'history-chat' }, viewer);
|
||||
svc.ask({ message: 'Summarize Plan 18 MVP-2 status', chatId: 'history-chat' }, viewer);
|
||||
svc.ask({ message: 'Summarize Plan 18 MVP-0 status', chatId: 'other-actor' }, { ...viewer, actor: 'someone-else' });
|
||||
|
||||
const history = svc.history(viewer, 'history-chat') as any;
|
||||
assert.equal(history.ok, true);
|
||||
assert.equal(history.turns.length, 2);
|
||||
assert.equal(history.turns[0].chat_id, 'history-chat');
|
||||
assert.ok(history.turns.every((turn: any) => turn.prompt_preview && turn.audit_hash));
|
||||
assert.equal(history.conversations.length, 1);
|
||||
assert.equal(history.conversations[0].chat_id, 'history-chat');
|
||||
});
|
||||
});
|
||||
|
||||
test('chat ask denies prompt injection and returns governed block response', () => {
|
||||
withTempChatState(() => {
|
||||
const svc = new ChatService();
|
||||
|
||||
Reference in New Issue
Block a user