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

@@ -29,6 +29,7 @@ const CHAT_SESSION_ID_KEY = 'main-chat-panel:session-id';
const CHAT_LAST_EVENT_ID_STORAGE_PREFIX = 'main-chat-panel:last-event-id:';
const CHAT_NOTIFY_OFFLINE_STORAGE_PREFIX = 'main-chat-panel:notify-offline:';
const CHAT_SESSION_LAST_TYPE_STORAGE_PREFIX = 'main-chat-panel:last-chat-type:';
const CHAT_SHARE_ACCESS_PIN_STORAGE_KEY = 'main-chat-panel:share-access-pins';
const CHAT_INTRO_MESSAGE =
'요청은 기본적으로 순차 처리됩니다. 급한 요청만 즉시 실행을 사용하세요. 여러 Codex를 추가한 즉시 실행은 병렬로 처리됩니다.';
const CHAT_ACTIVITY_MESSAGE_PREFIX = '[[activity-log]]';
@@ -69,6 +70,83 @@ function normalizeOptionalText(value: string | null | undefined) {
return normalized || null;
}
function canUseLocalStorage() {
return typeof window !== 'undefined' && typeof window.localStorage !== 'undefined';
}
function readStoredChatShareAccessPins() {
if (!canUseLocalStorage()) {
return {} as Record<string, { pin: string; expiresAtMs: number | null }>;
}
try {
const rawValue = window.localStorage.getItem(CHAT_SHARE_ACCESS_PIN_STORAGE_KEY);
if (!rawValue) {
return {} as Record<string, { pin: string; expiresAtMs: number | null }>;
}
const parsed = JSON.parse(rawValue) as Record<string, { pin?: unknown; expiresAtMs?: unknown }>;
const nowMs = Date.now();
const nextEntries = Object.entries(parsed).flatMap(([token, value]) => {
const normalizedToken = normalizeRequiredText(token);
const normalizedPin = normalizeRequiredText(typeof value?.pin === 'string' ? value.pin : '');
const expiresAtMs = Number.isFinite(value?.expiresAtMs) ? Number(value.expiresAtMs) : null;
if (!normalizedToken || !normalizedPin) {
return [];
}
if (expiresAtMs != null && expiresAtMs <= nowMs) {
return [];
}
return [[normalizedToken, { pin: normalizedPin, expiresAtMs }] as const];
});
return Object.fromEntries(nextEntries);
} catch {
return {} as Record<string, { pin: string; expiresAtMs: number | null }>;
}
}
function writeStoredChatShareAccessPins(entries: Record<string, { pin: string; expiresAtMs: number | null }>) {
if (!canUseLocalStorage()) {
return;
}
try {
const normalizedEntries = Object.entries(entries).flatMap(([token, value]) => {
const normalizedToken = normalizeRequiredText(token);
const normalizedPin = normalizeRequiredText(value?.pin);
const expiresAtMs = Number.isFinite(value?.expiresAtMs) ? Number(value.expiresAtMs) : null;
if (!normalizedToken || !normalizedPin) {
return [];
}
return [[normalizedToken, { pin: normalizedPin, expiresAtMs }] as const];
});
if (normalizedEntries.length === 0) {
window.localStorage.removeItem(CHAT_SHARE_ACCESS_PIN_STORAGE_KEY);
return;
}
window.localStorage.setItem(
CHAT_SHARE_ACCESS_PIN_STORAGE_KEY,
JSON.stringify(Object.fromEntries(normalizedEntries)),
);
} catch {
// Ignore storage failures in restricted runtimes.
}
}
function removeStoredChatShareAccessPin(token: string) {
const nextEntries = readStoredChatShareAccessPins();
delete nextEntries[token];
writeStoredChatShareAccessPins(nextEntries);
}
export function getStoredChatShareAccessPin(token?: string | null) {
const normalizedToken = normalizeRequiredText(token);
@@ -79,11 +157,19 @@ export function getStoredChatShareAccessPin(token?: string | null) {
const stored = chatShareAccessPinMemory.get(normalizedToken);
if (!stored) {
return '';
const persisted = readStoredChatShareAccessPins()[normalizedToken];
if (!persisted) {
return '';
}
chatShareAccessPinMemory.set(normalizedToken, persisted);
return persisted.pin.trim();
}
if (stored.expiresAtMs != null && stored.expiresAtMs <= Date.now()) {
chatShareAccessPinMemory.delete(normalizedToken);
removeStoredChatShareAccessPin(normalizedToken);
return '';
}
@@ -100,11 +186,19 @@ export function getStoredChatShareAccessPinExpiryMs(token?: string | null) {
const stored = chatShareAccessPinMemory.get(normalizedToken);
if (!stored) {
return null;
const persisted = readStoredChatShareAccessPins()[normalizedToken];
if (!persisted) {
return null;
}
chatShareAccessPinMemory.set(normalizedToken, persisted);
return persisted.expiresAtMs;
}
if (stored.expiresAtMs != null && stored.expiresAtMs <= Date.now()) {
chatShareAccessPinMemory.delete(normalizedToken);
removeStoredChatShareAccessPin(normalizedToken);
return null;
}
@@ -131,18 +225,23 @@ export function setStoredChatShareAccessPin(
const expiresAt = normalizeOptionalText(options?.expiresAt);
const expiresAtMs = expiresAt ? Date.parse(expiresAt) : Number.NaN;
const ttlMinutes = Number.isFinite(options?.ttlMinutes) ? Math.max(0, Number(options?.ttlMinutes)) : 0;
chatShareAccessPinMemory.set(normalizedToken, {
const nextEntry = {
pin: normalizedPin,
expiresAtMs: Number.isFinite(expiresAtMs)
? expiresAtMs
: ttlMinutes > 0
? Date.now() + ttlMinutes * 60 * 1000
: null,
});
};
chatShareAccessPinMemory.set(normalizedToken, nextEntry);
const nextEntries = readStoredChatShareAccessPins();
nextEntries[normalizedToken] = nextEntry;
writeStoredChatShareAccessPins(nextEntries);
return;
}
chatShareAccessPinMemory.delete(normalizedToken);
removeStoredChatShareAccessPin(normalizedToken);
}
function extractChatShareTokenFromPath(path: string) {
@@ -433,7 +532,7 @@ function mergeConversationSummaries(
roomScope: preferred.roomScope ?? fallback.roomScope ?? null,
notifyOffline: preferred.notifyOffline ?? fallback.notifyOffline,
hasUnreadResponse: resolveConversationUnreadMergeState(existing, incoming),
hasPendingAttention: preferred.hasPendingAttention === true || fallback.hasPendingAttention === true,
hasPendingAttention: preferred.hasPendingAttention === true,
currentRequestId: preferred.currentRequestId?.trim() || fallback.currentRequestId?.trim() || null,
currentJobStatus: preferred.currentJobStatus ?? fallback.currentJobStatus,
currentJobMessage: preferred.currentJobMessage?.trim() || fallback.currentJobMessage?.trim() || null,
@@ -572,6 +671,15 @@ function normalizeChatConversationRequest(item: ChatConversationRequest): ChatCo
totalTokens: Math.max(0, Math.round(Number(item.usageSnapshot.totalTokens ?? 0) || 0)),
}
: null;
const promptContextRef =
item.promptContextRef?.key === 'prompt_parent_question' && normalizeRequiredText(item.promptContextRef.promptTitle)
? {
key: 'prompt_parent_question' as const,
promptTitle: normalizeRequiredText(item.promptContextRef.promptTitle),
promptDescription: normalizeOptionalText(item.promptContextRef.promptDescription),
parentQuestionText: normalizeOptionalText(item.promptContextRef.parentQuestionText),
}
: null;
return {
...item,
@@ -581,7 +689,9 @@ function normalizeChatConversationRequest(item: ChatConversationRequest): ChatCo
chatTypeId: normalizeOptionalText(item.chatTypeId),
chatTypeLabel: normalizeRequiredText(item.chatTypeLabel),
parentRequestId: normalizeOptionalText(item.parentRequestId),
promptContextRef,
statusMessage: normalizeOptionalText(item.statusMessage),
retryCount: Number.isFinite(Number(item.retryCount)) ? Math.max(0, Math.round(Number(item.retryCount))) : 0,
userText: normalizeRequiredText(item.userText),
responseText: normalizeRequiredText(item.responseText),
usageSnapshot,
@@ -1519,13 +1629,13 @@ async function requestChatApi<T>(
sharePin?: string | null;
},
): Promise<T> {
const allowUnauthenticated = options?.allowUnauthenticated === true;
const headers = appendClientIdHeader(init?.headers);
const accessToken = getRegisteredAccessToken();
const method = init?.method?.toUpperCase() ?? 'GET';
const controller = new AbortController();
const timeoutMs = Number.isFinite(options?.timeoutMs) ? Math.max(1000, Number(options?.timeoutMs)) : 8000;
const timeoutId = window.setTimeout(() => controller.abort(), timeoutMs);
const allowUnauthenticated = options?.allowUnauthenticated === true;
if (!allowUnauthenticated && !hasRegisteredAccessTokenAccess()) {
window.clearTimeout(timeoutId);
@@ -2311,6 +2421,11 @@ export type ChatShareSnapshot = {
sessionId: string;
title: string;
requestBadgeLabel?: string | null;
chatTypeId?: string | null;
lastChatTypeId?: string | null;
contextLabel?: string | null;
contextDescription?: string | null;
notifyOffline?: boolean;
};
rootRequestId: string;
targetRequest: ChatConversationRequest;
@@ -2447,20 +2562,40 @@ export async function createManagedChatShareRoom(payload: ManagedChatShareRoomDr
} satisfies ManagedChatShareRoom;
}
export async function saveChatShareRoomAccessPin(
export async function saveChatShareRoomSettings(
token: string,
input: {
accessPin?: string | null;
accessPinPromptTtlMinutes?: number | null;
chatTypeId?: string | null;
chatTypeLabel?: string | null;
notifyOffline?: boolean | null;
},
) {
const response = await requestChatApi<{ ok: boolean; hasAccessPin: boolean; accessPinPromptTtlMinutes?: number | null }>(
const response = await requestChatApi<{
ok: boolean;
hasAccessPin: boolean;
accessPinPromptTtlMinutes?: number | null;
conversation?: {
sessionId?: string | null;
title?: string | null;
requestBadgeLabel?: string | null;
chatTypeId?: string | null;
lastChatTypeId?: string | null;
contextLabel?: string | null;
contextDescription?: string | null;
notifyOffline?: boolean | null;
} | null;
}>(
`/shares/${encodeURIComponent(token)}/room-settings`,
{
method: 'POST',
body: JSON.stringify({
accessPin: input.accessPin,
accessPinPromptTtlMinutes: input.accessPinPromptTtlMinutes,
chatTypeId: input.chatTypeId,
chatTypeLabel: input.chatTypeLabel,
notifyOffline: input.notifyOffline,
}),
},
{
@@ -2474,6 +2609,18 @@ export async function saveChatShareRoomAccessPin(
Number.isFinite(response.accessPinPromptTtlMinutes) && Number(response.accessPinPromptTtlMinutes) >= 0
? Math.max(0, Number(response.accessPinPromptTtlMinutes))
: 0,
conversation: response.conversation
? {
sessionId: normalizeOptionalText(response.conversation.sessionId),
title: normalizeOptionalText(response.conversation.title),
requestBadgeLabel: normalizeOptionalText(response.conversation.requestBadgeLabel),
chatTypeId: normalizeOptionalText(response.conversation.chatTypeId),
lastChatTypeId: normalizeOptionalText(response.conversation.lastChatTypeId),
contextLabel: normalizeOptionalText(response.conversation.contextLabel),
contextDescription: normalizeOptionalText(response.conversation.contextDescription),
notifyOffline: response.conversation.notifyOffline === true,
}
: null,
};
}
@@ -2543,7 +2690,14 @@ export async function fetchChatShareSnapshot(token: string, options?: { sharePin
)
: [],
},
conversation: response.conversation,
conversation: {
...response.conversation,
chatTypeId: normalizeOptionalText(response.conversation?.chatTypeId),
lastChatTypeId: normalizeOptionalText(response.conversation?.lastChatTypeId),
contextLabel: normalizeOptionalText(response.conversation?.contextLabel),
contextDescription: normalizeOptionalText(response.conversation?.contextDescription),
notifyOffline: response.conversation?.notifyOffline === true,
},
rootRequestId: response.rootRequestId,
targetRequest: normalizeChatConversationRequest(response.targetRequest),
requests: Array.isArray(response.requests) ? response.requests.map((item) => normalizeChatConversationRequest(item)) : [],
@@ -2566,6 +2720,7 @@ export async function submitChatShareMessage(
token: string,
text: string,
options?: {
mode?: 'queue' | 'direct';
parentRequestId?: string | null;
},
) {
@@ -2575,6 +2730,7 @@ export async function submitChatShareMessage(
method: 'POST',
body: JSON.stringify({
text,
mode: options?.mode === 'direct' ? 'direct' : 'queue',
parentRequestId: options?.parentRequestId?.trim() || undefined,
}),
},
@@ -2604,6 +2760,7 @@ export async function submitChatSharePrompt(
summaryText?: string | null;
attachments?: ChatComposerAttachment[];
followupText: string;
mode?: 'queue' | 'direct';
contextRef?: ChatPromptContextRef | null;
},
) {