style: format files after pre-commit prettier run
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -353,14 +353,9 @@ args = [${JSON.stringify(mementoSseUrl)}, "--header", ${JSON.stringify(`Authoriz
|
|||||||
env.ANTHROPIC_API_KEY =
|
env.ANTHROPIC_API_KEY =
|
||||||
envVars.ANTHROPIC_API_KEY || process.env.ANTHROPIC_API_KEY || '';
|
envVars.ANTHROPIC_API_KEY || process.env.ANTHROPIC_API_KEY || '';
|
||||||
}
|
}
|
||||||
if (
|
if (envVars.ANTHROPIC_AUTH_TOKEN || process.env.ANTHROPIC_AUTH_TOKEN) {
|
||||||
envVars.ANTHROPIC_AUTH_TOKEN ||
|
|
||||||
process.env.ANTHROPIC_AUTH_TOKEN
|
|
||||||
) {
|
|
||||||
env.ANTHROPIC_AUTH_TOKEN =
|
env.ANTHROPIC_AUTH_TOKEN =
|
||||||
envVars.ANTHROPIC_AUTH_TOKEN ||
|
envVars.ANTHROPIC_AUTH_TOKEN || process.env.ANTHROPIC_AUTH_TOKEN || '';
|
||||||
process.env.ANTHROPIC_AUTH_TOKEN ||
|
|
||||||
'';
|
|
||||||
}
|
}
|
||||||
if (envVars.ANTHROPIC_BASE_URL || process.env.ANTHROPIC_BASE_URL) {
|
if (envVars.ANTHROPIC_BASE_URL || process.env.ANTHROPIC_BASE_URL) {
|
||||||
env.ANTHROPIC_BASE_URL =
|
env.ANTHROPIC_BASE_URL =
|
||||||
|
|||||||
28
src/db.ts
28
src/db.ts
@@ -648,7 +648,9 @@ export function getMessagesSince(
|
|||||||
return rows.map(normalizeMessageRow);
|
return rows.map(normalizeMessageRow);
|
||||||
}
|
}
|
||||||
|
|
||||||
function normalizeSeqCursor(cursor: string | number | null | undefined): number {
|
function normalizeSeqCursor(
|
||||||
|
cursor: string | number | null | undefined,
|
||||||
|
): number {
|
||||||
if (typeof cursor === 'number') {
|
if (typeof cursor === 'number') {
|
||||||
return Number.isFinite(cursor) && cursor > 0 ? cursor : 0;
|
return Number.isFinite(cursor) && cursor > 0 ? cursor : 0;
|
||||||
}
|
}
|
||||||
@@ -662,21 +664,23 @@ export function getLatestMessageSeqAtOrBefore(
|
|||||||
chatJid?: string,
|
chatJid?: string,
|
||||||
): number {
|
): number {
|
||||||
if (!timestamp) return 0;
|
if (!timestamp) return 0;
|
||||||
const row = (chatJid
|
const row = (
|
||||||
? db
|
chatJid
|
||||||
.prepare(
|
? db
|
||||||
`SELECT COALESCE(MAX(seq), 0) AS maxSeq
|
.prepare(
|
||||||
|
`SELECT COALESCE(MAX(seq), 0) AS maxSeq
|
||||||
FROM messages
|
FROM messages
|
||||||
WHERE chat_jid = ? AND timestamp <= ?`,
|
WHERE chat_jid = ? AND timestamp <= ?`,
|
||||||
)
|
)
|
||||||
.get(chatJid, timestamp)
|
.get(chatJid, timestamp)
|
||||||
: db
|
: db
|
||||||
.prepare(
|
.prepare(
|
||||||
`SELECT COALESCE(MAX(seq), 0) AS maxSeq
|
`SELECT COALESCE(MAX(seq), 0) AS maxSeq
|
||||||
FROM messages
|
FROM messages
|
||||||
WHERE timestamp <= ?`,
|
WHERE timestamp <= ?`,
|
||||||
)
|
)
|
||||||
.get(timestamp)) as { maxSeq: number | null };
|
.get(timestamp)
|
||||||
|
) as { maxSeq: number | null };
|
||||||
return row.maxSeq ?? 0;
|
return row.maxSeq ?? 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
39
src/index.ts
39
src/index.ts
@@ -222,9 +222,12 @@ function loadState(): void {
|
|||||||
lastTimestamp = normalizeStoredSeqCursor(
|
lastTimestamp = normalizeStoredSeqCursor(
|
||||||
getRouterState('last_seq') || getRouterState('last_timestamp'),
|
getRouterState('last_seq') || getRouterState('last_timestamp'),
|
||||||
);
|
);
|
||||||
const agentTs = getRouterState('last_agent_seq') || getRouterState('last_agent_timestamp');
|
const agentTs =
|
||||||
|
getRouterState('last_agent_seq') || getRouterState('last_agent_timestamp');
|
||||||
try {
|
try {
|
||||||
const parsed = agentTs ? (JSON.parse(agentTs) as Record<string, string>) : {};
|
const parsed = agentTs
|
||||||
|
? (JSON.parse(agentTs) as Record<string, string>)
|
||||||
|
: {};
|
||||||
lastAgentTimestamp = Object.fromEntries(
|
lastAgentTimestamp = Object.fromEntries(
|
||||||
Object.entries(parsed).map(([chatJid, cursor]) => [
|
Object.entries(parsed).map(([chatJid, cursor]) => [
|
||||||
chatJid,
|
chatJid,
|
||||||
@@ -843,7 +846,11 @@ async function processGroupMessages(chatJid: string): Promise<boolean> {
|
|||||||
queue.setActivityTouch(chatJid, null);
|
queue.setActivityTouch(chatJid, null);
|
||||||
|
|
||||||
if (hadError) {
|
if (hadError) {
|
||||||
if (finalOutputSentToUser || progressOutputSentToUser || producedFinalText) {
|
if (
|
||||||
|
finalOutputSentToUser ||
|
||||||
|
progressOutputSentToUser ||
|
||||||
|
producedFinalText
|
||||||
|
) {
|
||||||
logger.warn(
|
logger.warn(
|
||||||
{ group: group.name },
|
{ group: group.name },
|
||||||
'Agent error after output was produced, skipping cursor rollback to prevent duplicates',
|
'Agent error after output was produced, skipping cursor rollback to prevent duplicates',
|
||||||
@@ -913,8 +920,7 @@ async function runAgent(
|
|||||||
'settings.json',
|
'settings.json',
|
||||||
);
|
);
|
||||||
const groupHasOverride = hasGroupProviderOverride(settingsPath);
|
const groupHasOverride = hasGroupProviderOverride(settingsPath);
|
||||||
const canFallback =
|
const canFallback = isClaudeCode && isFallbackEnabled() && !groupHasOverride;
|
||||||
isClaudeCode && isFallbackEnabled() && !groupHasOverride;
|
|
||||||
|
|
||||||
const agentInput = {
|
const agentInput = {
|
||||||
prompt,
|
prompt,
|
||||||
@@ -925,7 +931,9 @@ async function runAgent(
|
|||||||
assistantName: ASSISTANT_NAME,
|
assistantName: ASSISTANT_NAME,
|
||||||
};
|
};
|
||||||
|
|
||||||
const runAttempt = async (provider: string): Promise<{
|
const runAttempt = async (
|
||||||
|
provider: string,
|
||||||
|
): Promise<{
|
||||||
output?: AgentOutput;
|
output?: AgentOutput;
|
||||||
error?: unknown;
|
error?: unknown;
|
||||||
sawOutput: boolean;
|
sawOutput: boolean;
|
||||||
@@ -1124,7 +1132,10 @@ async function runAgent(
|
|||||||
|
|
||||||
const output = primaryAttempt.output;
|
const output = primaryAttempt.output;
|
||||||
if (!output) {
|
if (!output) {
|
||||||
logger.error({ group: group.name, chatJid, provider }, 'Agent produced no output object');
|
logger.error(
|
||||||
|
{ group: group.name, chatJid, provider },
|
||||||
|
'Agent produced no output object',
|
||||||
|
);
|
||||||
return 'error';
|
return 'error';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1750,7 +1761,9 @@ async function buildUsageContent(): Promise<string> {
|
|||||||
lines.push('_조회 불가_');
|
lines.push('_조회 불가_');
|
||||||
}
|
}
|
||||||
if (shouldFetchClaudeUsage && usageApiPollingDisabled) {
|
if (shouldFetchClaudeUsage && usageApiPollingDisabled) {
|
||||||
lines.push('_* Claude 사용량 조회는 반복된 429로 이번 프로세스에서 일시 중지_');
|
lines.push(
|
||||||
|
'_* Claude 사용량 조회는 반복된 429로 이번 프로세스에서 일시 중지_',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (claudeUsageIsCached) {
|
if (claudeUsageIsCached) {
|
||||||
lines.push('_* Claude 사용량은 작업 중일 때는 캐시값 유지_');
|
lines.push('_* Claude 사용량은 작업 중일 때는 캐시값 유지_');
|
||||||
@@ -2081,9 +2094,7 @@ async function announceRestartRecovery(
|
|||||||
const explicitContext = consumeRestartContext();
|
const explicitContext = consumeRestartContext();
|
||||||
const dedupeSince = new Date(processStartedAtMs - 60_000).toISOString();
|
const dedupeSince = new Date(processStartedAtMs - 60_000).toISOString();
|
||||||
if (explicitContext) {
|
if (explicitContext) {
|
||||||
if (
|
if (hasRecentRestartAnnouncement(explicitContext.chatJid, dedupeSince)) {
|
||||||
hasRecentRestartAnnouncement(explicitContext.chatJid, dedupeSince)
|
|
||||||
) {
|
|
||||||
logger.info(
|
logger.info(
|
||||||
{ chatJid: explicitContext.chatJid },
|
{ chatJid: explicitContext.chatJid },
|
||||||
'Skipped duplicate restart recovery announcement',
|
'Skipped duplicate restart recovery announcement',
|
||||||
@@ -2173,7 +2184,11 @@ async function main(): Promise<void> {
|
|||||||
);
|
);
|
||||||
if (writtenPaths.length > 0) {
|
if (writtenPaths.length > 0) {
|
||||||
logger.info(
|
logger.info(
|
||||||
{ signal, interruptedGroupCount: interruptedGroups.length, writtenPaths },
|
{
|
||||||
|
signal,
|
||||||
|
interruptedGroupCount: interruptedGroups.length,
|
||||||
|
writtenPaths,
|
||||||
|
},
|
||||||
'Stored shutdown restart context for interrupted groups',
|
'Stored shutdown restart context for interrupted groups',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -95,9 +95,7 @@ describe('createMessageRuntime', () => {
|
|||||||
ANTHROPIC_AUTH_TOKEN: 'test-kimi-key',
|
ANTHROPIC_AUTH_TOKEN: 'test-kimi-key',
|
||||||
ANTHROPIC_MODEL: 'kimi-k2.5',
|
ANTHROPIC_MODEL: 'kimi-k2.5',
|
||||||
});
|
});
|
||||||
vi.mocked(providerFallback.hasGroupProviderOverride).mockReturnValue(
|
vi.mocked(providerFallback.hasGroupProviderOverride).mockReturnValue(false);
|
||||||
false,
|
|
||||||
);
|
|
||||||
vi.mocked(providerFallback.isFallbackEnabled).mockReturnValue(true);
|
vi.mocked(providerFallback.isFallbackEnabled).mockReturnValue(true);
|
||||||
vi.mocked(providerFallback.detectFallbackTrigger).mockReturnValue({
|
vi.mocked(providerFallback.detectFallbackTrigger).mockReturnValue({
|
||||||
shouldFallback: false,
|
shouldFallback: false,
|
||||||
|
|||||||
@@ -151,7 +151,9 @@ export function createMessageRuntime(deps: MessageRuntimeDeps): {
|
|||||||
assistantName: deps.assistantName,
|
assistantName: deps.assistantName,
|
||||||
};
|
};
|
||||||
|
|
||||||
const runAttempt = async (provider: string): Promise<{
|
const runAttempt = async (
|
||||||
|
provider: string,
|
||||||
|
): Promise<{
|
||||||
output?: AgentOutput;
|
output?: AgentOutput;
|
||||||
error?: unknown;
|
error?: unknown;
|
||||||
sawOutput: boolean;
|
sawOutput: boolean;
|
||||||
@@ -212,7 +214,13 @@ export function createMessageRuntime(deps: MessageRuntimeDeps): {
|
|||||||
|
|
||||||
if (provider !== 'claude') {
|
if (provider !== 'claude') {
|
||||||
logger.info(
|
logger.info(
|
||||||
{ chatJid, group: group.name, groupFolder: group.folder, runId, provider },
|
{
|
||||||
|
chatJid,
|
||||||
|
group: group.name,
|
||||||
|
groupFolder: group.folder,
|
||||||
|
runId,
|
||||||
|
provider,
|
||||||
|
},
|
||||||
`Claude provider in cooldown, routing request to ${provider}`,
|
`Claude provider in cooldown, routing request to ${provider}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -238,7 +246,12 @@ export function createMessageRuntime(deps: MessageRuntimeDeps): {
|
|||||||
sessionId: persistSessionIds ? sessionId : undefined,
|
sessionId: persistSessionIds ? sessionId : undefined,
|
||||||
},
|
},
|
||||||
(proc, processName) =>
|
(proc, processName) =>
|
||||||
deps.queue.registerProcess(chatJid, proc, processName, group.folder),
|
deps.queue.registerProcess(
|
||||||
|
chatJid,
|
||||||
|
proc,
|
||||||
|
processName,
|
||||||
|
group.folder,
|
||||||
|
),
|
||||||
wrappedOnOutput,
|
wrappedOnOutput,
|
||||||
provider === 'claude' ? undefined : getFallbackEnvOverrides(),
|
provider === 'claude' ? undefined : getFallbackEnvOverrides(),
|
||||||
);
|
);
|
||||||
@@ -368,7 +381,13 @@ export function createMessageRuntime(deps: MessageRuntimeDeps): {
|
|||||||
const output = primaryAttempt.output;
|
const output = primaryAttempt.output;
|
||||||
if (!output) {
|
if (!output) {
|
||||||
logger.error(
|
logger.error(
|
||||||
{ chatJid, group: group.name, groupFolder: group.folder, runId, provider },
|
{
|
||||||
|
chatJid,
|
||||||
|
group: group.name,
|
||||||
|
groupFolder: group.folder,
|
||||||
|
runId,
|
||||||
|
provider,
|
||||||
|
},
|
||||||
'Agent produced no output object',
|
'Agent produced no output object',
|
||||||
);
|
);
|
||||||
return 'error';
|
return 'error';
|
||||||
@@ -831,7 +850,13 @@ export function createMessageRuntime(deps: MessageRuntimeDeps): {
|
|||||||
progressMessageId = await channel.sendAndTrack(chatJid, rendered);
|
progressMessageId = await channel.sendAndTrack(chatJid, rendered);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.warn(
|
logger.warn(
|
||||||
{ chatJid, group: group.name, groupFolder: group.folder, runId, err },
|
{
|
||||||
|
chatJid,
|
||||||
|
group: group.name,
|
||||||
|
groupFolder: group.folder,
|
||||||
|
runId,
|
||||||
|
err,
|
||||||
|
},
|
||||||
'Failed to send tracked progress message',
|
'Failed to send tracked progress message',
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -63,8 +63,7 @@ function loadConfig(): FallbackConfig {
|
|||||||
'FALLBACK_COOLDOWN_MS',
|
'FALLBACK_COOLDOWN_MS',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const baseUrl =
|
const baseUrl = process.env.FALLBACK_BASE_URL || env.FALLBACK_BASE_URL || '';
|
||||||
process.env.FALLBACK_BASE_URL || env.FALLBACK_BASE_URL || '';
|
|
||||||
const authToken =
|
const authToken =
|
||||||
process.env.FALLBACK_AUTH_TOKEN || env.FALLBACK_AUTH_TOKEN || '';
|
process.env.FALLBACK_AUTH_TOKEN || env.FALLBACK_AUTH_TOKEN || '';
|
||||||
const model = process.env.FALLBACK_MODEL || env.FALLBACK_MODEL || '';
|
const model = process.env.FALLBACK_MODEL || env.FALLBACK_MODEL || '';
|
||||||
|
|||||||
@@ -159,8 +159,7 @@ export function writeShutdownRestartContext(
|
|||||||
|
|
||||||
const payload: RestartContext = {
|
const payload: RestartContext = {
|
||||||
chatJid: existing?.chatJid || mainChatJid,
|
chatJid: existing?.chatJid || mainChatJid,
|
||||||
summary:
|
summary: existing?.summary || '서비스 재시작으로 진행 중인 작업이 중단됨',
|
||||||
existing?.summary || '서비스 재시작으로 진행 중인 작업이 중단됨',
|
|
||||||
verify: existing?.verify ?? [],
|
verify: existing?.verify ?? [],
|
||||||
writtenAt: new Date().toISOString(),
|
writtenAt: new Date().toISOString(),
|
||||||
source: existing?.source || 'shutdown-snapshot',
|
source: existing?.source || 'shutdown-snapshot',
|
||||||
@@ -181,7 +180,9 @@ export function buildInterruptedRestartAnnouncement(
|
|||||||
const lines = ['서비스 재시작으로 이전 작업이 중단됐습니다.'];
|
const lines = ['서비스 재시작으로 이전 작업이 중단됐습니다.'];
|
||||||
lines.push(`- 직전 상태: ${interrupted.status}`);
|
lines.push(`- 직전 상태: ${interrupted.status}`);
|
||||||
if (interrupted.elapsedMs !== null) {
|
if (interrupted.elapsedMs !== null) {
|
||||||
lines.push(`- 직전 실행 시간: ${Math.round(interrupted.elapsedMs / 1000)}초`);
|
lines.push(
|
||||||
|
`- 직전 실행 시간: ${Math.round(interrupted.elapsedMs / 1000)}초`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (interrupted.pendingMessages) {
|
if (interrupted.pendingMessages) {
|
||||||
lines.push('- 미처리 메시지가 남아 있었음');
|
lines.push('- 미처리 메시지가 남아 있었음');
|
||||||
@@ -216,9 +217,7 @@ export function consumeRestartContext(): RestartContext | null {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function buildRestartAnnouncement(
|
export function buildRestartAnnouncement(context: RestartContext): string {
|
||||||
context: RestartContext,
|
|
||||||
): string {
|
|
||||||
const lines = ['재시작 완료.', `- 변경: ${context.summary}`];
|
const lines = ['재시작 완료.', `- 변경: ${context.summary}`];
|
||||||
if (context.interruptedGroups && context.interruptedGroups.length > 0) {
|
if (context.interruptedGroups && context.interruptedGroups.length > 0) {
|
||||||
lines.push(`- 중단 작업 감지: ${context.interruptedGroups.length}개`);
|
lines.push(`- 중단 작업 감지: ${context.interruptedGroups.length}개`);
|
||||||
|
|||||||
@@ -119,7 +119,9 @@ function formatTimeLabel(timestampIso: string): string {
|
|||||||
.replace(/시 (\d{2})$/, '분 $1초');
|
.replace(/시 (\d{2})$/, '분 $1초');
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatWatchIntervalLabel(task: Pick<ScheduledTask, 'schedule_type' | 'schedule_value'>): string | null {
|
function formatWatchIntervalLabel(
|
||||||
|
task: Pick<ScheduledTask, 'schedule_type' | 'schedule_value'>,
|
||||||
|
): string | null {
|
||||||
if (task.schedule_type !== 'interval') return null;
|
if (task.schedule_type !== 'interval') return null;
|
||||||
const ms = parseInt(task.schedule_value, 10);
|
const ms = parseInt(task.schedule_value, 10);
|
||||||
if (!Number.isFinite(ms) || ms <= 0) return null;
|
if (!Number.isFinite(ms) || ms <= 0) return null;
|
||||||
|
|||||||
Reference in New Issue
Block a user