chore: test deploy snapshot
This commit is contained in:
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user