fix(ai): persist context requests and enforce agent execution order

This commit is contained in:
2026-10-15 14:28:22 +09:00
parent 5290c61178
commit 6463db4159
2 changed files with 36 additions and 0 deletions
+26
View File
@@ -218,4 +218,30 @@ describe('AgentService', () => {
}),
]);
});
it('persists a response requesting source or logs even without commands or changes', () => {
const service = Object.create(AgentService.prototype) as unknown as {
parseSyncedMessage: (value: Record<string, unknown>, index: number) => {
plan: { requestedModules: string[]; requestedLogs: string[] };
};
};
const message = service.parseSyncedMessage({
id: 'agent-plan-context',
role: 'assistant',
content: '차량 설정 파일을 확인하겠습니다.',
plan: {
summary: '차량 설정 파일을 확인하겠습니다.',
explanation: '조향 방식 설정을 확인하려면 차량 소스가 필요합니다.',
requestedModules: ['NAV2-MKMini'],
requestedLogs: ['NAV2-Control'],
actionPlan: ['차량 설정 확인'],
commands: [],
changes: [],
createModule: null,
},
}, 0);
expect(message.plan.requestedModules).toEqual(['NAV2-MKMini']);
expect(message.plan.requestedLogs).toEqual(['NAV2-Control']);
});
});
+10
View File
@@ -311,6 +311,11 @@ export class AgentService {
let plan: AgentPlan | null = null;
if (rawPlan) {
const moduleNames = new Set<string>();
for (const field of ['requestedModules', 'requestedLogs']) {
for (const name of Array.isArray(rawPlan[field]) ? rawPlan[field] : []) {
if (typeof name === 'string') moduleNames.add(name.trim());
}
}
for (const command of Array.isArray(rawPlan.commands)
? rawPlan.commands
: []) {
@@ -744,7 +749,12 @@ export class AgentService {
'학생이 정보 확인이나 설명만 요청하면 제공된 입력만 근거로 답하고 changes는 빈 배열, createModule은 null로 반환한다.',
'작업이 여러 단계라면 actionPlan에 학생이 이해할 수 있는 짧은 단계 목록을 순서대로 작성한다.',
'빌드, 테스트, 진단 명령 실행이 필요하면 commands에 현재 단계에서 실행할 다음 승인 명령만 넣는다. 명령은 해당 모듈 루트에서 실행된다.',
'파일 변경과 commands를 함께 반환하면 모든 명령은 파일 변경 적용 후 실행할 검증 명령이어야 하며, actionPlan에도 파일 변경을 명령보다 먼저 적는다.',
'파일 변경 전에 실행해야 하는 사전 진단 명령이라면 changes를 비우고 명령만 먼저 반환한다. 실행 결과가 다음 요청에 제공되면 그때 변경안을 제안한다.',
'commands는 읽기, 빌드, 테스트, 실행 진단에 필요한 최소 명령만 제안하고 sudo, 시스템 설정 변경, 삭제, 재부팅, 자격증명 접근 명령은 절대 제안하지 않는다.',
'ros2 run 또는 ros2 launch는 대상 모듈의 state가 running일 때만 제안한다. 이 명령은 해당 모듈 컨테이너 안에서 최대 20초간 관찰 실행된다.',
'ros2 실행이 필요한 대상 모듈의 state가 stopped이면 commands에 command가 정확히 "lms module start"인 모듈 시작 승인 액션 하나만 반환한다. 학생이 승인하면 LMS가 모듈을 시작하고 결과를 다음 요청에 제공한다.',
'모듈 시작 액션이 승인되기 전에는 ros2 run 또는 ros2 launch를 함께 제안하지 않는다. ros2 topic pub, service call, action send_goal 같은 ROS2 제어 명령은 제안하지 않는다.',
'명령에는 cd를 포함하지 말고 source 연결은 &&만 사용한다. 한 명령은 한 줄로 작성한다.',
'기존 모듈 변경은 전달받은 moduleName만 사용한다. 새 모듈 요청일 때만 createModule을 채운다.',
'launchFile은 모듈 루트 기준 상대 경로다. 파일 path도 각 모듈 루트 기준 상대 경로다.',