chore: test deploy snapshot

This commit is contained in:
2026-05-27 10:43:01 +09:00
parent c1d0f4c1db
commit 4c4b3c8d2c
78 changed files with 10392 additions and 2301 deletions

View File

@@ -18,7 +18,6 @@ import {
ensureChatSessionResourceDirectories,
getActiveChatService,
getChatRuntimeController,
shouldAutoCompleteReplyParentVerification,
} from '../services/chat-service.js';
import { rollbackChatRuntimeRequest } from '../services/chat-runtime-rollback-service.js';
import {
@@ -43,10 +42,12 @@ import {
listChatConversationDetailPage,
listChatConversations,
markChatConversationRequestManualCompletion,
ChatConversationManualCompletionBlockedError,
markChatConversationResponsesRead,
persistChatConversationPromptSelection,
upsertChatConversationRequest,
updateChatConversationContext,
hasPendingAttentionVerificationRequest,
} from '../services/chat-room-service.js';
import { chatRuntimeService } from '../services/chat-runtime-service.js';
import { resolveMainProjectRoot } from '../services/main-project-root-service.js';
@@ -123,6 +124,10 @@ async function findExistingActivePromptFollowupRequest(
) ?? null;
}
export function resolvePromptFollowupMode(mode?: 'queue' | 'direct' | null) {
return mode === 'direct' ? 'direct' : 'queue';
}
function encodeBase64Url(value: string) {
return Buffer.from(value, 'utf-8').toString('base64url');
}
@@ -223,19 +228,6 @@ function resolveChatShareTokenSettingSnapshot(tokenPayload: ChatShareTokenPayloa
};
}
export function shouldAutoCompleteShareReplyParentVerification(request: {
responseMessageId?: number | null;
responseText?: string | null;
manualVerificationCompletedAt?: string | null;
} | null | undefined) {
return shouldAutoCompleteReplyParentVerification({
requestOrigin: 'composer',
responseMessageId: request?.responseMessageId ?? null,
responseText: request?.responseText ?? '',
manualVerificationCompletedAt: request?.manualVerificationCompletedAt ?? null,
});
}
function createManagedChatShareTokenId() {
return `chat_share_${randomUUID().replace(/-/g, '').slice(0, 20)}`;
}
@@ -802,36 +794,40 @@ function hasUnresolvedPromptPart(message: ListedChatConversationMessage) {
function hasPendingPromptRequest(
request: ListedChatConversationRequest,
relatedMessages: ListedChatConversationMessage[],
promptSubmittedCount = 0,
) {
if (request.manualPromptCompletedAt) {
return false;
}
return relatedMessages.some(
(message) => (message.author === 'codex' || message.author === 'system') && hasUnresolvedPromptPart(message),
);
}
const unresolvedPromptCount = relatedMessages.reduce((count, message) => {
if (message.author !== 'codex' && message.author !== 'system') {
return count;
}
function hasVerificationTargetMessage(message: ListedChatConversationMessage) {
if (message.author !== 'codex' && message.author !== 'system') {
const promptParts = (message.parts ?? []).filter(
(
part: NonNullable<ListedChatConversationMessage['parts']>[number],
): part is Extract<NonNullable<ListedChatConversationMessage['parts']>[number], { type: 'prompt' }> => part.type === 'prompt',
);
return count + promptParts.filter(
(part: Extract<NonNullable<ListedChatConversationMessage['parts']>[number], { type: 'prompt' }>) => !isPromptPartResolved(part),
).length;
}, 0);
if (unresolvedPromptCount === 0) {
return false;
}
const text = String(message.text ?? '').trim();
if (!text) {
return false;
}
if (text.length > 720) {
return true;
}
return /```diff[\s\S]*?```|\[\[preview:|\[\[link-card:|\[\[prompt:/i.test(text);
return unresolvedPromptCount > Math.max(0, promptSubmittedCount);
}
function hasVerificationTargetRequest(relatedMessages: ListedChatConversationMessage[]) {
return relatedMessages.some((message) => hasVerificationTargetMessage(message));
function hasVerificationTargetRequest(
request: ListedChatConversationRequest,
relatedMessages: ListedChatConversationMessage[],
) {
return hasPendingAttentionVerificationRequest(request, relatedMessages);
}
function buildChildRequestCountMap(requests: ListedChatConversationRequest[]) {
@@ -850,6 +846,30 @@ function buildChildRequestCountMap(requests: ListedChatConversationRequest[]) {
return nextMap;
}
function buildPromptFollowupCountMap(requests: ListedChatConversationRequest[]) {
const nextMap = new Map<string, number>();
requests.forEach((request) => {
if (request.requestOrigin !== 'prompt') {
return;
}
const parentRequestId = request.parentRequestId?.trim() || '';
if (!parentRequestId) {
return;
}
nextMap.set(parentRequestId, (nextMap.get(parentRequestId) ?? 0) + 1);
});
return nextMap;
}
function isPromptFollowupRoomRequest(request: ListedChatConversationRequest) {
return request.requestOrigin === 'prompt';
}
function isRequestInFlight(status: ListedChatConversationRequest['status']) {
return status === 'accepted' || status === 'queued' || status === 'started';
}
@@ -858,20 +878,29 @@ function isPendingCompletionRoomRequest(
request: ListedChatConversationRequest,
relatedMessages: ListedChatConversationMessage[],
childRequestCountByParentId?: Map<string, number>,
promptFollowupCountByParentId?: Map<string, number>,
) {
if (isPromptFollowupRoomRequest(request)) {
return false;
}
if (isRequestInFlight(request.status)) {
return true;
}
if (hasPendingPromptRequest(request, relatedMessages)) {
if (hasPendingPromptRequest(request, relatedMessages, promptFollowupCountByParentId?.get(request.requestId.trim()) ?? 0)) {
return true;
}
if (request.manualPromptCompletedAt) {
return false;
}
if ((childRequestCountByParentId?.get(request.requestId.trim()) ?? 0) > 0) {
return false;
}
if (!hasVerificationTargetRequest(relatedMessages)) {
if (!hasVerificationTargetRequest(request, relatedMessages)) {
return false;
}
@@ -884,6 +913,7 @@ function buildRoomRequestCounts(
) {
const requestMessagesById = new Map<string, ListedChatConversationMessage[]>();
const childRequestCountByParentId = buildChildRequestCountMap(requests);
const promptFollowupCountByParentId = buildPromptFollowupCountMap(requests);
messages.forEach((message) => {
const requestId = message.clientRequestId?.trim() || '';
@@ -897,12 +927,15 @@ function buildRoomRequestCounts(
requestMessagesById.set(requestId, current);
});
const processingCount = requests.filter((request) => isRequestInFlight(request.status)).length;
const processingCount = requests.filter(
(request) => !isPromptFollowupRoomRequest(request) && isRequestInFlight(request.status),
).length;
const unansweredCount = requests.filter((request) =>
isPendingCompletionRoomRequest(
request,
requestMessagesById.get(request.requestId.trim()) ?? [],
childRequestCountByParentId,
promptFollowupCountByParentId,
),
).length;
@@ -937,8 +970,10 @@ function buildManagedSharePlaceholderRequest(tokenPayload: ChatShareTokenPayload
requestOrigin: 'composer',
sharedResourceTokenId: tokenPayload.managedResourceTokenId?.trim() || null,
parentRequestId: null,
promptContextRef: null,
status: 'completed',
statusMessage: '공유 채팅방 시작 요청을 복원했습니다.',
retryCount: 0,
userMessageId: null,
userText: '',
responseMessageId: null,
@@ -1554,6 +1589,9 @@ export async function registerChatRoutes(app: FastifyInstance) {
const payload = z.object({
accessPin: z.string().regex(/^\d{4}$/u).optional().nullable(),
accessPinPromptTtlMinutes: z.number().int().min(0).max(7 * 24 * 60).optional().nullable(),
chatTypeId: z.string().trim().min(1).max(120).optional().nullable(),
chatTypeLabel: z.string().trim().min(1).max(200).optional().nullable(),
notifyOffline: z.boolean().optional().nullable(),
}).parse(request.body ?? {});
const managedContext = await resolveManagedChatShareContext(params.token);
const tokenPayload = resolveChatSharePayloadFromManagedResource(managedContext.managedResource) ?? parseChatShareToken(params.token);
@@ -1615,10 +1653,35 @@ export async function registerChatRoutes(app: FastifyInstance) {
});
}
let updatedConversation = await getChatConversation(tokenPayload.sessionId, getRequestClientId(request));
if (payload.chatTypeId || payload.notifyOffline != null) {
updatedConversation = await updateChatConversationContext(tokenPayload.sessionId, {
clientId: getRequestClientId(request),
chatTypeId: payload.chatTypeId?.trim() || undefined,
lastChatTypeId: payload.chatTypeId?.trim() || undefined,
contextLabel: payload.chatTypeLabel?.trim() || undefined,
contextDescription: payload.chatTypeId ? null : undefined,
notifyOffline: payload.notifyOffline ?? undefined,
});
}
return {
ok: true,
hasAccessPin: saved.token.hasAccessPin,
accessPinPromptTtlMinutes: saved.token.accessPinPromptTtlMinutes,
conversation: updatedConversation
? {
sessionId: updatedConversation.sessionId,
title: updatedConversation.title,
requestBadgeLabel: updatedConversation.requestBadgeLabel ?? null,
chatTypeId: updatedConversation.chatTypeId ?? null,
lastChatTypeId: updatedConversation.lastChatTypeId ?? null,
contextLabel: updatedConversation.contextLabel ?? null,
contextDescription: updatedConversation.contextDescription ?? null,
notifyOffline: updatedConversation.notifyOffline,
}
: null,
};
});
@@ -1866,6 +1929,11 @@ export async function registerChatRoutes(app: FastifyInstance) {
sessionId: shareSnapshot.conversation?.sessionId ?? tokenPayload.sessionId,
title: shareSnapshot.conversation?.title ?? '공유 채팅',
requestBadgeLabel: shareSnapshot.conversation?.requestBadgeLabel ?? null,
chatTypeId: shareSnapshot.conversation?.chatTypeId ?? null,
lastChatTypeId: shareSnapshot.conversation?.lastChatTypeId ?? null,
contextLabel: shareSnapshot.conversation?.contextLabel ?? null,
contextDescription: shareSnapshot.conversation?.contextDescription ?? null,
notifyOffline: shareSnapshot.conversation?.notifyOffline === true,
},
rootRequestId: shareSnapshot.rootRequestId,
targetRequest: shareSnapshot.targetRequest,
@@ -1955,6 +2023,7 @@ export async function registerChatRoutes(app: FastifyInstance) {
}).parse(request.params ?? {});
const payload = z.object({
text: z.string().trim().min(1).max(20000),
mode: z.enum(['queue', 'direct']).optional(),
parentRequestId: z.string().trim().min(1).max(120).optional().nullable(),
}).parse(request.body ?? {});
const managedContext = await resolveManagedChatShareContext(params.token);
@@ -2012,11 +2081,13 @@ export async function registerChatRoutes(app: FastifyInstance) {
}
const requestedParentRequestId = payload.parentRequestId?.trim() || '';
const resolvedParentRequestId = resolveRecoveredShareParentRequestId(
shareSnapshot,
requestedParentRequestId,
[shareSnapshot.targetRequest.requestId],
);
const resolvedParentRequestId = requestedParentRequestId
? resolveRecoveredShareParentRequestId(
shareSnapshot,
requestedParentRequestId,
[shareSnapshot.targetRequest.requestId],
)
: null;
if (
resolvedParentRequestId
@@ -2028,7 +2099,7 @@ export async function registerChatRoutes(app: FastifyInstance) {
}
const queuedRequestId = await getActiveChatService()?.submitExternalMessage(tokenPayload.sessionId, payload.text, {
mode: 'direct',
mode: payload.mode === 'direct' ? 'direct' : 'queue',
requestOrigin: 'composer',
sharedResourceTokenId: managedContext.managedResource?.token.id ?? tokenPayload.managedResourceTokenId ?? null,
parentRequestId: resolvedParentRequestId,
@@ -2041,22 +2112,6 @@ export async function registerChatRoutes(app: FastifyInstance) {
});
}
const parentRequest = resolvedParentRequestId
? shareSnapshot.requests.find((request) => request.requestId.trim() === resolvedParentRequestId) ?? null
: null;
if (resolvedParentRequestId && shouldAutoCompleteShareReplyParentVerification(parentRequest)) {
const updatedParentRequest = await markChatConversationRequestManualCompletion(
tokenPayload.sessionId,
resolvedParentRequestId,
'verification',
);
if (updatedParentRequest) {
getActiveChatService()?.broadcastRequestUpdate(tokenPayload.sessionId, updatedParentRequest);
}
}
if (managedContext.managedResource) {
await recordSharedResourceTokenUsage(managedContext.managedResource.token.id, {
actorLabel: 'share-viewer',
@@ -2179,6 +2234,7 @@ export async function registerChatRoutes(app: FastifyInstance) {
summaryText: z.string().max(10000).optional().nullable(),
attachments: z.array(chatComposerAttachmentSchema).max(20).optional(),
followupText: z.string().trim().min(1).max(20000),
mode: z.enum(['queue', 'direct']).optional(),
contextRef: chatPromptContextRefSchema,
}).parse(request.body ?? {});
const managedContext = await resolveManagedChatShareContext(params.token);
@@ -2261,7 +2317,7 @@ export async function registerChatRoutes(app: FastifyInstance) {
);
const queuedRequestId = existingPromptRequest?.requestId ?? await getActiveChatService()?.submitExternalMessage(tokenPayload.sessionId, payload.followupText, {
mode: 'direct',
mode: resolvePromptFollowupMode(payload.mode),
requestOrigin: 'prompt',
sharedResourceTokenId: managedContext.managedResource?.token.id ?? tokenPayload.managedResourceTokenId ?? null,
parentRequestId: normalizedParentRequestId,
@@ -2363,11 +2419,23 @@ export async function registerChatRoutes(app: FastifyInstance) {
});
}
const item = await markChatConversationRequestManualCompletion(
tokenPayload.sessionId,
normalizedParentRequestId,
payload.type,
);
let item = null;
try {
item = await markChatConversationRequestManualCompletion(
tokenPayload.sessionId,
normalizedParentRequestId,
payload.type,
);
} catch (error) {
if (error instanceof ChatConversationManualCompletionBlockedError) {
return reply.code(409).send({
message: error.message,
});
}
throw error;
}
if (!item) {
return reply.code(404).send({
@@ -2587,6 +2655,7 @@ export async function registerChatRoutes(app: FastifyInstance) {
}
const queuedRequestId = await getActiveChatService()?.submitExternalMessage(tokenPayload.sessionId, normalizedUserText, {
requestId: normalizedParentRequestId,
mode: 'direct',
requestOrigin: targetRequest.requestOrigin === 'prompt' ? 'prompt' : 'composer',
sharedResourceTokenId: managedContext.managedResource?.token.id ?? tokenPayload.managedResourceTokenId ?? null,
@@ -2928,11 +2997,23 @@ export async function registerChatRoutes(app: FastifyInstance) {
type: z.enum(['prompt', 'verification']),
}).parse(request.body ?? {});
const item = await markChatConversationRequestManualCompletion(
params.sessionId,
params.requestId,
payload.type,
);
let item = null;
try {
item = await markChatConversationRequestManualCompletion(
params.sessionId,
params.requestId,
payload.type,
);
} catch (error) {
if (error instanceof ChatConversationManualCompletionBlockedError) {
return reply.code(409).send({
message: error.message,
});
}
throw error;
}
if (!item) {
return reply.code(404).send({
@@ -3044,7 +3125,7 @@ export async function registerChatRoutes(app: FastifyInstance) {
);
const queuedRequestId = existingPromptRequest?.requestId ?? await getActiveChatService()?.submitExternalMessage(params.sessionId, payload.followupText, {
mode: payload.mode === 'direct' ? 'direct' : 'queue',
mode: resolvePromptFollowupMode(payload.mode),
requestOrigin: 'prompt',
parentRequestId: params.requestId,
promptContextRef: payload.contextRef ?? null,