feat: update codex live automation and plan flows

This commit is contained in:
2026-04-24 08:06:36 +09:00
parent 916107dbe5
commit f2d6310efa
47 changed files with 2767 additions and 507 deletions

View File

@@ -1,6 +1,12 @@
import { z } from 'zod';
import { getEnv } from '../config/env.js';
import { db } from '../db/client.js';
import {
normalizeLegacyAutomationBehaviorType,
resolveAutomationType,
resolveStoredAutomationTypeId,
type AutomationBehaviorType,
} from './automation-type-config-service.js';
import { shouldTriggerRetryFromActionNote } from './plan-retry-policy.js';
export const PLAN_TABLE = 'plan_items';
@@ -53,6 +59,7 @@ type PlanRowOptions = {
maskNote?: boolean;
noteMasked?: boolean;
releaseReviewNote?: string;
exposeConfiguredAutomationType?: boolean;
};
export const statusSchema = z.enum(planStatuses);
@@ -62,32 +69,15 @@ export const setupSchema = z.object({
});
function resolvePlanAutomationTypeAlias(value: unknown) {
if (typeof value !== 'string') {
return value;
}
const normalizedValue = value.trim();
if (normalizedValue === 'plan_registration') {
return 'plan';
}
if (normalizedValue === 'general_development') {
return 'auto_worker';
}
return normalizedValue;
return normalizeLegacyAutomationBehaviorType(value);
}
export const planAutomationTypeSchema = z.preprocess(
resolvePlanAutomationTypeAlias,
z.enum(planAutomationTypes),
);
export const planAutomationTypeSchema = z.preprocess(resolvePlanAutomationTypeAlias, z.string().trim().min(1).max(120));
export const createPlanSchema = z.object({
workId: z.string().trim().optional().default('작업ID'),
note: z.string().default(''),
automationType: z.preprocess(resolvePlanAutomationTypeAlias, z.enum(planAutomationTypes).default('none')),
automationType: z.preprocess(resolvePlanAutomationTypeAlias, z.string().trim().min(1).max(120).default('none')),
releaseTarget: z.string().trim().min(1).default('release'),
jangsingProcessingRequired: z.boolean().default(true),
autoDeployToMain: z.boolean().default(true),
@@ -98,7 +88,7 @@ export const createPlanSchema = z.object({
export const updatePlanSchema = z.object({
workId: z.string().trim().optional(),
note: z.string().optional(),
automationType: z.preprocess(resolvePlanAutomationTypeAlias, z.enum(planAutomationTypes).optional()),
automationType: z.preprocess(resolvePlanAutomationTypeAlias, z.string().trim().min(1).max(120).optional()),
releaseTarget: z.string().trim().min(1).optional(),
jangsingProcessingRequired: z.boolean().optional(),
autoDeployToMain: z.boolean().optional(),
@@ -135,7 +125,7 @@ export const listPlanQuerySchema = z.object({
});
export type PlanStatus = (typeof planStatuses)[number];
export type PlanAutomationType = (typeof planAutomationTypes)[number];
export type PlanAutomationType = string;
export type PlanWorkerStatus = (typeof planWorkerStatuses)[number];
export type PlanReleaseReviewStatus = (typeof planReleaseReviewStatuses)[number];
@@ -191,8 +181,8 @@ function normalizePlanWorkId(value?: string | null) {
export function normalizePlanAutomationType(value: unknown): PlanAutomationType {
const normalizedValue = resolvePlanAutomationTypeAlias(value);
return planAutomationTypes.includes(normalizedValue as PlanAutomationType)
? (normalizedValue as PlanAutomationType)
return planAutomationTypes.includes(normalizedValue as AutomationBehaviorType)
? (normalizedValue as AutomationBehaviorType)
: 'none';
}
@@ -281,7 +271,8 @@ export function mapPlanRow(
id: row.id,
workId: row.work_id,
note: options?.maskNote ? maskPlanNote(row.note) : row.note,
automationType: normalizePlanAutomationType(row.automation_type),
automationType: options?.exposeConfiguredAutomationType ? resolveStoredAutomationTypeId(row) : normalizePlanAutomationType(row.automation_type),
automationBehaviorType: normalizePlanAutomationType(row.automation_type),
releaseReviewNote: options?.releaseReviewNote ?? '',
noteMasked: Boolean(options?.noteMasked),
status: row.status,
@@ -825,6 +816,9 @@ async function syncPlanColumns() {
await ensureColumn('automation_type', (table) => {
table.string('automation_type', 40).notNullable().defaultTo('none');
});
await ensureColumn('automation_type_id', (table) => {
table.string('automation_type_id', 120).nullable();
});
await ensureColumn('auto_deploy_to_main', (table) => {
table.boolean('auto_deploy_to_main').notNullable().defaultTo(true);
});
@@ -871,6 +865,11 @@ async function syncPlanColumns() {
await db(PLAN_TABLE)
.where({ automation_type: 'general_development' })
.update({ automation_type: 'auto_worker' });
await db(PLAN_TABLE)
.whereNull('automation_type_id')
.update({
automation_type_id: db.raw('automation_type'),
});
}
async function dropPlanWorkIdUniqueConstraint() {
@@ -1090,13 +1089,15 @@ export async function ensurePlanTable() {
export async function createPlanItem(payload: z.infer<typeof createPlanSchema>) {
await ensurePlanTable();
const workId = normalizePlanWorkId(payload.workId);
const automationType = await resolveAutomationType(payload.automationType);
const rows = await db(PLAN_TABLE)
.insert({
work_id: workId,
note: payload.note,
status: '등록',
automation_type: normalizePlanAutomationType(payload.automationType),
automation_type: automationType.behaviorType,
automation_type_id: automationType.id,
release_target: payload.releaseTarget,
jangsing_processing_required: payload.jangsingProcessingRequired,
auto_deploy_to_main: payload.autoDeployToMain,
@@ -1114,13 +1115,15 @@ export async function createPlanItem(payload: z.infer<typeof createPlanSchema>)
export async function createCompletedPlanExecutionLogItem(payload: z.infer<typeof createPlanSchema>) {
await ensurePlanTable();
const workId = normalizePlanWorkId(payload.workId);
const automationType = await resolveAutomationType(payload.automationType);
const rows = await db(PLAN_TABLE)
.insert({
work_id: workId,
note: payload.note,
status: '완료',
automation_type: normalizePlanAutomationType(payload.automationType),
automation_type: automationType.behaviorType,
automation_type_id: automationType.id,
release_target: payload.releaseTarget,
jangsing_processing_required: payload.jangsingProcessingRequired,
auto_deploy_to_main: payload.autoDeployToMain,
@@ -1159,11 +1162,12 @@ export async function upsertAutoPlanItem(args: {
.first();
if (!existingRow) {
const automationType = await resolveAutomationType(args.automationType);
return {
row: await createPlanItem({
workId,
note: args.note,
automationType: normalizePlanAutomationType(args.automationType),
automationType: automationType.id,
releaseTarget: args.releaseTarget,
jangsingProcessingRequired: args.jangsingProcessingRequired,
autoDeployToMain: args.autoDeployToMain,
@@ -1175,7 +1179,7 @@ export async function upsertAutoPlanItem(args: {
}
const nextReleaseTarget = args.releaseTarget || existingRow.release_target || 'release';
const nextAutomationType = normalizePlanAutomationType(args.automationType ?? existingRow.automation_type);
const nextAutomationType = await resolveAutomationType(args.automationType ?? existingRow.automation_type_id ?? existingRow.automation_type);
const nextJangsingProcessingRequired = args.jangsingProcessingRequired;
const nextAutoDeployToMain = args.autoDeployToMain;
const nextNote = args.note;
@@ -1185,7 +1189,7 @@ export async function upsertAutoPlanItem(args: {
: existingRow.normal_processing_level === '상';
const hasPayloadChange =
existingRow.note !== nextNote ||
normalizePlanAutomationType(existingRow.automation_type) !== nextAutomationType ||
String(existingRow.automation_type_id ?? existingRow.automation_type ?? 'none') !== nextAutomationType.id ||
(existingRow.release_target ?? 'release') !== nextReleaseTarget ||
Boolean(existingRow.auto_deploy_to_main ?? true) !== nextAutoDeployToMain ||
Boolean(currentJangsingProcessingRequired) !== nextJangsingProcessingRequired;
@@ -1202,7 +1206,8 @@ export async function upsertAutoPlanItem(args: {
.where({ id: existingRow.id })
.update({
note: nextNote,
automation_type: nextAutomationType,
automation_type: nextAutomationType.behaviorType,
automation_type_id: nextAutomationType.id,
release_target: nextReleaseTarget,
jangsing_processing_required: nextJangsingProcessingRequired,
auto_deploy_to_main: nextAutoDeployToMain,
@@ -1223,7 +1228,8 @@ export async function upsertAutoPlanItem(args: {
note: nextNote,
status: '등록',
assigned_branch: null,
automation_type: nextAutomationType,
automation_type: nextAutomationType.behaviorType,
automation_type_id: nextAutomationType.id,
release_target: nextReleaseTarget,
jangsing_processing_required: nextJangsingProcessingRequired,
auto_deploy_to_main: nextAutoDeployToMain,
@@ -1262,7 +1268,9 @@ export async function updatePlanItem(id: number, payload: z.infer<typeof updateP
const nextWorkId =
payload.workId === undefined ? currentRow.work_id : normalizePlanWorkId(payload.workId);
const nextAutomationType = payload.automationType ?? normalizePlanAutomationType(currentRow.automation_type);
const nextAutomationType = await resolveAutomationType(
payload.automationType ?? currentRow.automation_type_id ?? currentRow.automation_type,
);
const nextReleaseTarget = payload.releaseTarget ?? currentRow.release_target ?? 'release';
const nextJangsingProcessingRequired =
payload.jangsingProcessingRequired ??
@@ -1276,7 +1284,7 @@ export async function updatePlanItem(id: number, payload: z.infer<typeof updateP
const isOnlyJangsingUpdate =
nextWorkId === currentRow.work_id &&
nextAutomationType === normalizePlanAutomationType(currentRow.automation_type) &&
nextAutomationType.id === String(currentRow.automation_type_id ?? currentRow.automation_type ?? 'none') &&
nextReleaseTarget === (currentRow.release_target ?? 'release') &&
nextAutoDeployToMain === (currentRow.auto_deploy_to_main ?? true) &&
nextRepeatRequestEnabled === (currentRow.repeat_request_enabled ?? false) &&
@@ -1297,7 +1305,8 @@ export async function updatePlanItem(id: number, payload: z.infer<typeof updateP
work_id: nextWorkId,
note: nextNote,
status: currentRow.status,
automation_type: nextAutomationType,
automation_type: nextAutomationType.behaviorType,
automation_type_id: nextAutomationType.id,
release_target: nextReleaseTarget,
jangsing_processing_required: nextJangsingProcessingRequired,
auto_deploy_to_main: nextAutoDeployToMain,
@@ -2261,6 +2270,7 @@ export async function listPlanReleaseReviewBoardItems(options?: Pick<PlanRowOpti
...(issueSummaryMap.get(planItemId) ?? { issueTags: [], hasOpenIssues: false }),
maskNote: options?.maskNote,
noteMasked: options?.maskNote,
exposeConfiguredAutomationType: true,
});
const latestSourceWork = latestSourceWorkMap.get(planItemId) ?? null;
const reviewRow = reviewRowMap.get(planItemId);
@@ -2818,6 +2828,7 @@ export async function listPlanItems(status?: PlanStatus, options?: Pick<PlanRowO
maskNote: options?.maskNote,
noteMasked: options?.maskNote,
releaseReviewNote: releaseReviewNoteMap.get(Number(row.id)) ?? '',
exposeConfiguredAutomationType: true,
}),
);
}
@@ -2843,6 +2854,7 @@ export async function getPlanItemById(id: number, options?: Pick<PlanRowOptions,
maskNote: options?.maskNote,
noteMasked: options?.maskNote,
releaseReviewNote: releaseReviewNoteMap.get(id) ?? '',
exposeConfiguredAutomationType: true,
});
}