fix: use resolved ipc dirs for recovery flow

This commit is contained in:
Eyejoker
2026-03-23 07:18:47 +09:00
parent 9def49f7fb
commit f5eae07657
6 changed files with 35 additions and 13 deletions

View File

@@ -53,7 +53,9 @@ describe('group folder validation', () => {
it('throws for unsafe folder names', () => { it('throws for unsafe folder names', () => {
expect(() => resolveGroupFolderPath('../../etc')).toThrow(); expect(() => resolveGroupFolderPath('../../etc')).toThrow();
expect(() => resolveGroupIpcPath('/tmp')).toThrow(); expect(() => resolveGroupIpcPath('/tmp')).toThrow();
expect(() => resolveTaskRuntimeIpcPath('family-chat', '../../etc')).toThrow(); expect(() =>
resolveTaskRuntimeIpcPath('family-chat', '../../etc'),
).toThrow();
expect(() => resolveTaskSessionsPath('family-chat', '/tmp')).toThrow(); expect(() => resolveTaskSessionsPath('family-chat', '/tmp')).toThrow();
}); });
}); });

View File

@@ -29,7 +29,11 @@ function assertValidRuntimeSegment(segment: string, label: string): void {
if (!RUNTIME_SEGMENT_PATTERN.test(segment)) { if (!RUNTIME_SEGMENT_PATTERN.test(segment)) {
throw new Error(`Invalid ${label} "${segment}"`); throw new Error(`Invalid ${label} "${segment}"`);
} }
if (segment.includes('/') || segment.includes('\\') || segment.includes('..')) { if (
segment.includes('/') ||
segment.includes('\\') ||
segment.includes('..')
) {
throw new Error(`Invalid ${label} "${segment}"`); throw new Error(`Invalid ${label} "${segment}"`);
} }
} }
@@ -76,7 +80,10 @@ export function resolveTaskRuntimeIpcPath(
return ipcPath; return ipcPath;
} }
export function resolveTaskSessionsPath(folder: string, taskId: string): string { export function resolveTaskSessionsPath(
folder: string,
taskId: string,
): string {
assertValidGroupFolder(folder); assertValidGroupFolder(folder);
assertValidRuntimeSegment(taskId, 'task ID'); assertValidRuntimeSegment(taskId, 'task ID');
const sessionsBaseDir = path.resolve(DATA_DIR, 'sessions'); const sessionsBaseDir = path.resolve(DATA_DIR, 'sessions');

View File

@@ -77,10 +77,9 @@ describe('GroupQueue', () => {
await vi.advanceTimersByTimeAsync(10); await vi.advanceTimersByTimeAsync(10);
expect(queue.sendMessage('group1@g.us', '후속 메시지')).toBe(true); expect(queue.sendMessage('group1@g.us', '후속 메시지')).toBe(true);
expect(fs.mkdirSync).toHaveBeenCalledWith( expect(fs.mkdirSync).toHaveBeenCalledWith(`${ipcDir}/input`, {
`${ipcDir}/input`, recursive: true,
{ recursive: true }, });
);
expect(fs.writeFileSync).toHaveBeenCalled(); expect(fs.writeFileSync).toHaveBeenCalled();
expect(fs.renameSync).toHaveBeenCalled(); expect(fs.renameSync).toHaveBeenCalled();

View File

@@ -39,7 +39,7 @@ import {
} from './db.js'; } from './db.js';
import { composeDashboardContent } from './dashboard-render.js'; import { composeDashboardContent } from './dashboard-render.js';
import { GroupQueue } from './group-queue.js'; import { GroupQueue } from './group-queue.js';
import { resolveGroupFolderPath } from './group-folder.js'; import { resolveGroupFolderPath, resolveGroupIpcPath } from './group-folder.js';
import { startIpcWatcher } from './ipc.js'; import { startIpcWatcher } from './ipc.js';
import { findChannel, formatOutbound } from './router.js'; import { findChannel, formatOutbound } from './router.js';
import { import {
@@ -428,7 +428,10 @@ async function main(): Promise<void> {
restartContext, restartContext,
registeredGroups, registeredGroups,
)) { )) {
queue.enqueueMessageCheck(candidate.chatJid, candidate.groupFolder); queue.enqueueMessageCheck(
candidate.chatJid,
resolveGroupIpcPath(candidate.groupFolder),
);
logger.info( logger.info(
{ {
chatJid: candidate.chatJid, chatJid: candidate.chatJid,

View File

@@ -533,7 +533,10 @@ export function createMessageRuntime(deps: MessageRuntimeDeps): {
continue; continue;
} }
deps.queue.enqueueMessageCheck(chatJid, resolveGroupIpcPath(group.folder)); deps.queue.enqueueMessageCheck(
chatJid,
resolveGroupIpcPath(group.folder),
);
} }
} }
} catch (err) { } catch (err) {
@@ -555,7 +558,10 @@ export function createMessageRuntime(deps: MessageRuntimeDeps): {
{ chatJid, group: group.name, workItemId: openWorkItem.id }, { chatJid, group: group.name, workItemId: openWorkItem.id },
'Recovery: found open work item awaiting delivery', 'Recovery: found open work item awaiting delivery',
); );
deps.queue.enqueueMessageCheck(chatJid, resolveGroupIpcPath(group.folder)); deps.queue.enqueueMessageCheck(
chatJid,
resolveGroupIpcPath(group.folder),
);
continue; continue;
} }
@@ -576,7 +582,10 @@ export function createMessageRuntime(deps: MessageRuntimeDeps): {
{ group: group.name, pendingCount: pending.length }, { group: group.name, pendingCount: pending.length },
'Recovery: found unprocessed messages', 'Recovery: found unprocessed messages',
); );
deps.queue.enqueueMessageCheck(chatJid, resolveGroupIpcPath(group.folder)); deps.queue.enqueueMessageCheck(
chatJid,
resolveGroupIpcPath(group.folder),
);
} else if (rawPending.length > 0) { } else if (rawPending.length > 0) {
const endSeq = rawPending[rawPending.length - 1].seq; const endSeq = rawPending[rawPending.length - 1].seq;
if (endSeq != null) { if (endSeq != null) {

View File

@@ -10,7 +10,9 @@ const { runAgentProcessMock, writeTasksSnapshotMock } = vi.hoisted(() => ({
vi.mock('./agent-runner.js', async () => { vi.mock('./agent-runner.js', async () => {
const actual = const actual =
await vi.importActual<typeof import('./agent-runner.js')>('./agent-runner.js'); await vi.importActual<typeof import('./agent-runner.js')>(
'./agent-runner.js',
);
return { return {
...actual, ...actual,
runAgentProcess: runAgentProcessMock, runAgentProcess: runAgentProcessMock,