feat: separate codex live and automation context
This commit is contained in:
@@ -8,6 +8,7 @@
|
||||
- 문서 정리나 Codex 작업 시 Git 원격 동기화, 브랜치 운영, 자동 merge 흐름은 기본 전제로 사용하지 않습니다.
|
||||
- `Codex Live`, 일반 채팅, 일반 작업메모 반영 요청은 **현재 프로젝트 루트의 로컬 `main` 작업본을 바로 수정**하는 방식으로 처리합니다.
|
||||
- 자동화 작업메모도 별도 브랜치 흐름을 이 문서에 고정하지 않고, 실제 운영 설정과 요청 문맥을 기준으로 처리합니다.
|
||||
- 자동화와 `Codex Live`는 별개로 취급하며, 자동화는 선택된 자동화 유형 context만 우선 참조합니다.
|
||||
- Git 관련 작업은 사용자가 명시적으로 요청할 때만 수행합니다.
|
||||
|
||||
## 1. 작업일지
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
Plan 자동화 기능의 데이터 구조, API 연동 방식, release 검수 연계 방식을 정리합니다.
|
||||
|
||||
현재 문서는 자동화 브랜치 전략 자체를 고정 규칙으로 설명하지 않습니다. 자동화 처리 방식은 실제 서버 설정, 요청 문맥, 현재 운영 규칙을 함께 확인해 판단합니다. 반면 `Codex Live`나 일반 수동 요청은 로컬 작업본 기준으로 처리합니다.
|
||||
현재 문서는 자동화 브랜치 전략 자체를 고정 규칙으로 설명하지 않습니다. 자동화 처리 방식은 실제 서버 설정, 요청 문맥, 현재 운영 규칙을 함께 확인해 판단합니다. 자동화와 `Codex Live`는 별개이며, 자동화 실행기는 선택된 자동화 유형의 context만 우선 참조합니다. 반면 `Codex Live`나 일반 수동 요청은 로컬 작업본 기준으로 처리합니다.
|
||||
|
||||
## 구현 위치
|
||||
|
||||
@@ -38,6 +38,12 @@ Plan 자동화 기능의 데이터 구조, API 연동 방식, release 검수 연
|
||||
|
||||
기존 저장값인 `plan_registration`, `general_development`는 서버에서 각각 `plan`, `auto_worker`로 정규화합니다.
|
||||
|
||||
자동화 context 해석 규칙:
|
||||
|
||||
- 자동화 실행 시 기본 문맥은 선택된 자동화 유형 description/context만 사용
|
||||
- `Codex Live` 채팅 문맥, 일반 채팅 문맥은 자동화 기본 context로 섞지 않음
|
||||
- 추가 지시가 필요하면 요청 본문에 명시적으로 적어 전달
|
||||
|
||||
## API 연동 방식
|
||||
|
||||
기본 API 베이스 URL 규칙:
|
||||
|
||||
@@ -59,6 +59,8 @@ npm run server-command:runner
|
||||
|
||||
현재 운영 기준에서는 `Codex Live`, 일반 채팅, 일반 작업메모 반영 요청은 **현재 프로젝트 루트의 로컬 작업본을 직접 기준으로 처리**합니다.
|
||||
|
||||
`Codex Live`와 `Plan` 자동화는 별개입니다. `Codex Live`는 채팅 유형과 현재 화면 문맥을 기준으로 동작하고, 자동화 유형 context를 기본 문맥으로 섞지 않습니다.
|
||||
|
||||
브라우저 기준 접속 확인, 화면 검증, 외부 도메인 테스트는 **`https://test.sm-home.cloud/`를 기본 작업 도메인으로 사용**합니다. 별도 요청이 없는 한 `sm-home.cloud`나 `rel.sm-home.cloud`는 기본 검증 대상으로 삼지 않습니다.
|
||||
|
||||
채팅에서 파일, 문서, 이미지, 코드 같은 리소스를 제공할 때의 기본 공개 경로는 `public/.codex_chat/<chat-session-id>/resource/...`입니다. Codex가 원본 파일 경로만 답해도 서버가 이 위치로 세션 전용 사본을 만들고, 채팅에는 공개 URL을 다시 적어 줍니다.
|
||||
@@ -71,6 +73,8 @@ npm run server-command:runner
|
||||
|
||||
현재 운영 기준에서 자동화 작업메모의 세부 Git 절차는 이 문서에 고정하지 않습니다. 상태 전이와 실제 처리 흐름은 worker 구현, 환경 변수, 현재 운영 정책을 함께 확인해야 합니다.
|
||||
|
||||
자동화 실행기는 선택된 자동화 유형의 description/context만 우선 참조합니다. `Codex Live`나 일반 채팅 문맥은 자동화 기본 context로 사용하지 않습니다.
|
||||
|
||||
안전 조건:
|
||||
|
||||
- Git worktree가 깨끗해야 동작
|
||||
|
||||
@@ -60,7 +60,8 @@ export const DEFAULT_AUTOMATION_TYPES: AutomationTypeRecord[] = [
|
||||
{
|
||||
id: 'auto_worker',
|
||||
name: 'autoWorker',
|
||||
description: '자동화 작업메모로 처리하며, 세부 절차는 현재 운영 설정을 따릅니다.',
|
||||
description:
|
||||
'자동화 작업메모로 처리합니다.\n\n## context 사용 규칙\n- 자동화 실행기는 선택된 자동화 유형의 context만 우선 참조합니다.\n- Codex Live 문맥이나 일반 채팅 문맥은 자동화 기본 context로 섞지 않습니다.\n\n## 처리 원칙\n- 세부 절차는 현재 운영 설정을 따릅니다.',
|
||||
behaviorType: 'auto_worker',
|
||||
enabled: true,
|
||||
updatedAt: '2026-04-23T00:00:00.000Z',
|
||||
|
||||
@@ -14,8 +14,9 @@ test('buildBoardPostPlanNote formats automation work memo with clear sections',
|
||||
'- 게시판 제목: 알림 개선',
|
||||
'- 메모 출처: board_posts 자동화 접수',
|
||||
'- 선택 자동화 유형: 자동화 메모',
|
||||
'- 자동화 처리 원칙: 아래 자동화 유형 context만 우선 참조하고, Codex Live 문맥은 섞지 않습니다.',
|
||||
'',
|
||||
'## 선택된 유형 context',
|
||||
'## 자동화 유형 context',
|
||||
'## 처리 기준\n- 선택된 유형의 context를 먼저 읽습니다.',
|
||||
'',
|
||||
'## 요청 본문',
|
||||
@@ -36,8 +37,9 @@ test('buildBoardPostPlanNote keeps context section even when automation type des
|
||||
'- 게시판 제목: 작업',
|
||||
'- 메모 출처: board_posts 자동화 접수',
|
||||
'- 선택 자동화 유형: 빈 context 유형',
|
||||
'- 자동화 처리 원칙: 아래 자동화 유형 context만 우선 참조하고, Codex Live 문맥은 섞지 않습니다.',
|
||||
'',
|
||||
'## 선택된 유형 context',
|
||||
'## 자동화 유형 context',
|
||||
'선택된 자동화 유형 context 없음',
|
||||
'',
|
||||
'## 요청 본문',
|
||||
|
||||
@@ -91,8 +91,9 @@ export function buildBoardPostPlanNote(title: string, content: string, automatio
|
||||
`- 게시판 제목: ${normalizedTitle}`,
|
||||
'- 메모 출처: board_posts 자동화 접수',
|
||||
normalizedAutomationTypeName ? `- 선택 자동화 유형: ${normalizedAutomationTypeName}` : null,
|
||||
'- 자동화 처리 원칙: 아래 자동화 유형 context만 우선 참조하고, Codex Live 문맥은 섞지 않습니다.',
|
||||
'',
|
||||
'## 선택된 유형 context',
|
||||
'## 자동화 유형 context',
|
||||
normalizedAutomationContext || '선택된 자동화 유형 context 없음',
|
||||
'',
|
||||
'## 요청 본문',
|
||||
|
||||
@@ -1432,6 +1432,7 @@ function buildAgenticCodexPrompt(
|
||||
|
||||
return [
|
||||
'당신은 이 저장소에서 Codex Live 요청을 처리하는 실제 Codex 실행기입니다.',
|
||||
'Codex Live는 Plan 자동화와 별개입니다. 자동화 유형 context를 Codex Live 기본 문맥으로 섞지 마세요.',
|
||||
`저장소 루트(main_project): ${repoPath}`,
|
||||
'반드시 저장소 루트의 AGENTS.md를 먼저 읽고 그 규칙을 따르세요.',
|
||||
'가능한 작업 범위:',
|
||||
@@ -1457,6 +1458,7 @@ function buildAgenticCodexPrompt(
|
||||
`- chatTypeLabel: ${context?.chatTypeLabel ?? '없음'}`,
|
||||
`- chatTypeDescription: ${context?.chatTypeDescription ?? '없음'}`,
|
||||
'- 답변 스타일과 기본 문맥은 반드시 채팅 유형 정보만 기준으로 적용하세요.',
|
||||
'- Plan 자동화용 자동화 유형 context가 있더라도 Codex Live에서는 별도 요청이 없는 한 참조하지 마세요.',
|
||||
'',
|
||||
'참고 화면 정보:',
|
||||
`- pageTitle: ${context?.pageTitle ?? '없음'}`,
|
||||
|
||||
@@ -3,12 +3,14 @@ import {
|
||||
BellOutlined,
|
||||
ClockCircleOutlined,
|
||||
CopyOutlined,
|
||||
DownOutlined,
|
||||
FileMarkdownOutlined,
|
||||
LoadingOutlined,
|
||||
MenuFoldOutlined,
|
||||
MenuUnfoldOutlined,
|
||||
ProfileOutlined,
|
||||
ReloadOutlined,
|
||||
RightOutlined,
|
||||
SettingOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import {
|
||||
@@ -883,6 +885,7 @@ export function MainHeader({
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
const [settingsOpen, setSettingsOpen] = useState(false);
|
||||
const [automationGroupExpanded, setAutomationGroupExpanded] = useState(false);
|
||||
const [settingsModalOpen, setSettingsModalOpen] = useState(false);
|
||||
const [activeSettingsModal, setActiveSettingsModal] = useState<SettingsModalKey>('appSettings');
|
||||
const [activeAppSettingsCategory, setActiveAppSettingsCategory] = useState<AppSettingsCategoryKey>('automation');
|
||||
@@ -958,6 +961,8 @@ export function MainHeader({
|
||||
const workServerPendingUpdateCount =
|
||||
workServerStatus && (workServerStatus.updateAvailable || workServerStatus.buildRequired) ? 1 : 0;
|
||||
const totalPendingUpdateCount = testServerPendingUpdateCount + prodServerPendingUpdateCount + workServerPendingUpdateCount;
|
||||
const totalAutomationShortcutCount =
|
||||
planShortcutCounts.working + planShortcutCounts.releasePendingMain + planShortcutCounts.automationFailed;
|
||||
const settingsStatusClassName =
|
||||
totalPendingUpdateCount >= 2
|
||||
? 'app-header__status-dot--inactive'
|
||||
@@ -2621,68 +2626,20 @@ export function MainHeader({
|
||||
const settingsMenu = (
|
||||
<div className="app-header__settings-menu">
|
||||
{hasAccess ? (
|
||||
<>
|
||||
<div className="app-header__settings-group">
|
||||
<button
|
||||
type="button"
|
||||
className="app-header__settings-item"
|
||||
aria-expanded={automationGroupExpanded}
|
||||
onClick={() => {
|
||||
setSettingsOpen(false);
|
||||
onOpenPlanQuickFilter('working');
|
||||
}}
|
||||
>
|
||||
<span className="app-header__settings-icon">
|
||||
<ProfileOutlined />
|
||||
<span
|
||||
className={`app-header__status-dot ${
|
||||
planShortcutCounts.working > 0
|
||||
? 'app-header__status-dot--active'
|
||||
: 'app-header__status-dot--inactive'
|
||||
}`}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</span>
|
||||
<span className="app-header__settings-label">
|
||||
작업중 항목
|
||||
<Text type="secondary"> {planShortcutCounts.working}건</Text>
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="app-header__settings-item"
|
||||
onClick={() => {
|
||||
setSettingsOpen(false);
|
||||
onOpenPlanQuickFilter('release-pending-main');
|
||||
}}
|
||||
>
|
||||
<span className="app-header__settings-icon">
|
||||
<ProfileOutlined />
|
||||
<span
|
||||
className={`app-header__status-dot ${
|
||||
planShortcutCounts.releasePendingMain > 0
|
||||
? 'app-header__status-dot--warning'
|
||||
: 'app-header__status-dot--inactive'
|
||||
}`}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</span>
|
||||
<span className="app-header__settings-label">
|
||||
release 상태 작업
|
||||
<Text type="secondary"> {planShortcutCounts.releasePendingMain}건</Text>
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="app-header__settings-item"
|
||||
onClick={() => {
|
||||
setSettingsOpen(false);
|
||||
onOpenPlanQuickFilter('automation-failed');
|
||||
setAutomationGroupExpanded((current) => !current);
|
||||
}}
|
||||
>
|
||||
<span className="app-header__settings-icon">
|
||||
<ReloadOutlined />
|
||||
<span
|
||||
className={`app-header__status-dot ${
|
||||
planShortcutCounts.automationFailed > 0
|
||||
totalAutomationShortcutCount > 0
|
||||
? 'app-header__status-dot--warning'
|
||||
: 'app-header__status-dot--inactive'
|
||||
}`}
|
||||
@@ -2690,11 +2647,93 @@ export function MainHeader({
|
||||
/>
|
||||
</span>
|
||||
<span className="app-header__settings-label">
|
||||
자동화 실패
|
||||
<Text type="secondary"> {planShortcutCounts.automationFailed}건</Text>
|
||||
자동화 건수
|
||||
<Text type="secondary"> {totalAutomationShortcutCount}건</Text>
|
||||
</span>
|
||||
<span className="app-header__settings-group-arrow" aria-hidden="true">
|
||||
{automationGroupExpanded ? <DownOutlined /> : <RightOutlined />}
|
||||
</span>
|
||||
</button>
|
||||
</>
|
||||
{automationGroupExpanded ? (
|
||||
<div className="app-header__settings-group-children">
|
||||
<button
|
||||
type="button"
|
||||
className="app-header__settings-item app-header__settings-item--nested"
|
||||
onClick={() => {
|
||||
setSettingsOpen(false);
|
||||
setAutomationGroupExpanded(false);
|
||||
onOpenPlanQuickFilter('working');
|
||||
}}
|
||||
>
|
||||
<span className="app-header__settings-icon">
|
||||
<ProfileOutlined />
|
||||
<span
|
||||
className={`app-header__status-dot ${
|
||||
planShortcutCounts.working > 0
|
||||
? 'app-header__status-dot--active'
|
||||
: 'app-header__status-dot--inactive'
|
||||
}`}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</span>
|
||||
<span className="app-header__settings-label">
|
||||
작업중 항목
|
||||
<Text type="secondary"> {planShortcutCounts.working}건</Text>
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="app-header__settings-item app-header__settings-item--nested"
|
||||
onClick={() => {
|
||||
setSettingsOpen(false);
|
||||
setAutomationGroupExpanded(false);
|
||||
onOpenPlanQuickFilter('release-pending-main');
|
||||
}}
|
||||
>
|
||||
<span className="app-header__settings-icon">
|
||||
<ProfileOutlined />
|
||||
<span
|
||||
className={`app-header__status-dot ${
|
||||
planShortcutCounts.releasePendingMain > 0
|
||||
? 'app-header__status-dot--warning'
|
||||
: 'app-header__status-dot--inactive'
|
||||
}`}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</span>
|
||||
<span className="app-header__settings-label">
|
||||
release 상태 작업
|
||||
<Text type="secondary"> {planShortcutCounts.releasePendingMain}건</Text>
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="app-header__settings-item app-header__settings-item--nested"
|
||||
onClick={() => {
|
||||
setSettingsOpen(false);
|
||||
setAutomationGroupExpanded(false);
|
||||
onOpenPlanQuickFilter('automation-failed');
|
||||
}}
|
||||
>
|
||||
<span className="app-header__settings-icon">
|
||||
<ReloadOutlined />
|
||||
<span
|
||||
className={`app-header__status-dot ${
|
||||
planShortcutCounts.automationFailed > 0
|
||||
? 'app-header__status-dot--warning'
|
||||
: 'app-header__status-dot--inactive'
|
||||
}`}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</span>
|
||||
<span className="app-header__settings-label">
|
||||
자동화 실패
|
||||
<Text type="secondary"> {planShortcutCounts.automationFailed}건</Text>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
) : null}
|
||||
<button
|
||||
type="button"
|
||||
|
||||
@@ -174,10 +174,23 @@
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.app-header__settings-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.app-header__settings-item:hover {
|
||||
background: #f3f7ff;
|
||||
}
|
||||
|
||||
.app-header__settings-item--nested {
|
||||
margin-left: 12px;
|
||||
min-width: 0;
|
||||
padding-left: 14px;
|
||||
background: #f8fbff;
|
||||
}
|
||||
|
||||
.app-header__settings-icon {
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
@@ -241,10 +254,23 @@
|
||||
}
|
||||
|
||||
.app-header__settings-label {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
flex: 1;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.app-header__settings-group-arrow {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 16px;
|
||||
color: #64748b;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.app-header__update-progress {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
Reference in New Issue
Block a user