mirror of
https://github.com/wu736139669/hapi.git
synced 2026-11-08 01:43:07 +00:00
fix(codex): ignore subagents in transcript fallback
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
export function isCodexSubagentSource(value: unknown): boolean {
|
||||
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
||||
return false;
|
||||
}
|
||||
return Object.prototype.hasOwnProperty.call(value, 'subagent');
|
||||
}
|
||||
@@ -129,6 +129,42 @@ describe('codexTranscriptLocator', () => {
|
||||
expect(new Set(ambiguous[0])).toEqual(new Set([first, second]));
|
||||
});
|
||||
|
||||
it('ignores a review subagent and waits for the top-level review transcript', async () => {
|
||||
const located: string[] = [];
|
||||
const ambiguous: string[][] = [];
|
||||
locator = createCodexTranscriptLocator({
|
||||
cwd: '/tmp/project',
|
||||
startupTimestampMs: Date.now(),
|
||||
intervalMs: 25,
|
||||
settlementMs: 50,
|
||||
onLocated: (result) => located.push(result.transcriptPath),
|
||||
onAmbiguous: (paths) => ambiguous.push(paths)
|
||||
});
|
||||
await locator.ready;
|
||||
const reviewSubagent = await createTranscript(
|
||||
'thread-review-subagent',
|
||||
'/tmp/project',
|
||||
{ subagent: 'review' }
|
||||
);
|
||||
const userEvent = (message: string) => `${JSON.stringify({
|
||||
timestamp: new Date().toISOString(),
|
||||
type: 'event_msg',
|
||||
payload: { type: 'user_message', message }
|
||||
})}\n`;
|
||||
|
||||
await appendFile(reviewSubagent, userEvent('review instructions'));
|
||||
await wait(100);
|
||||
expect(located).toEqual([]);
|
||||
expect(ambiguous).toEqual([]);
|
||||
|
||||
const primary = await createTranscript('thread-review-primary', '/tmp/project', 'cli');
|
||||
await appendFile(primary, userEvent('/review'));
|
||||
await wait(150);
|
||||
|
||||
expect(located).toEqual([primary]);
|
||||
expect(ambiguous).toEqual([]);
|
||||
});
|
||||
|
||||
it('rejects candidates whose activity arrives in adjacent polling cycles', async () => {
|
||||
const located: string[] = [];
|
||||
const ambiguous: string[][] = [];
|
||||
@@ -208,9 +244,13 @@ describe('codexTranscriptLocator', () => {
|
||||
expect(located).toEqual([]);
|
||||
});
|
||||
|
||||
it('polls only the exact resume transcript once it is found', async () => {
|
||||
it('polls only the exact resume transcript once it is found, including a subagent', async () => {
|
||||
const unrelated = await createTranscript('thread-unrelated', '/tmp/project');
|
||||
const target = await createTranscript('thread-resume', '/tmp/original-project');
|
||||
const target = await createTranscript(
|
||||
'thread-resume',
|
||||
'/tmp/original-project',
|
||||
{ subagent: 'review' }
|
||||
);
|
||||
await appendFile(unrelated, `${JSON.stringify({
|
||||
timestamp: new Date().toISOString(),
|
||||
type: 'event_msg',
|
||||
@@ -239,11 +279,15 @@ describe('codexTranscriptLocator', () => {
|
||||
expect(ambiguous).toEqual([]);
|
||||
});
|
||||
|
||||
async function createTranscript(sessionId: string, cwd: string): Promise<string> {
|
||||
async function createTranscript(sessionId: string, cwd: string, source?: unknown): Promise<string> {
|
||||
const transcriptPath = join(sessionDirectory, `rollout-${sessionId}.jsonl`);
|
||||
await writeFile(transcriptPath, `${JSON.stringify({
|
||||
type: 'session_meta',
|
||||
payload: { id: sessionId, cwd }
|
||||
payload: {
|
||||
id: sessionId,
|
||||
cwd,
|
||||
...(source === undefined ? {} : { source })
|
||||
}
|
||||
})}\n`);
|
||||
return transcriptPath;
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import { join, resolve } from 'node:path';
|
||||
import { open, readdir, stat } from 'node:fs/promises';
|
||||
import { logger } from '@/ui/logger';
|
||||
import { convertCodexEvent, type CodexSessionEvent } from './codexEventConverter';
|
||||
import { isCodexSubagentSource } from './codexSessionMetadata';
|
||||
|
||||
export type LocatedCodexTranscript = {
|
||||
sessionId: string;
|
||||
@@ -21,6 +22,7 @@ type TranscriptState = {
|
||||
ino: number;
|
||||
sessionId: string | null;
|
||||
cwd: string | null;
|
||||
isSubagent: boolean;
|
||||
};
|
||||
|
||||
type CodexTranscriptLocatorOptions = {
|
||||
@@ -170,7 +172,8 @@ class CodexTranscriptLocatorImpl {
|
||||
mtimeMs: 0,
|
||||
ino: fileStats.ino,
|
||||
sessionId: null,
|
||||
cwd: null
|
||||
cwd: null,
|
||||
isSubagent: false
|
||||
};
|
||||
|
||||
const replaced = previous && previous.ino !== fileStats.ino;
|
||||
@@ -186,7 +189,8 @@ class CodexTranscriptLocatorImpl {
|
||||
mtimeMs: 0,
|
||||
ino: fileStats.ino,
|
||||
sessionId: null,
|
||||
cwd: null
|
||||
cwd: null,
|
||||
isSubagent: false
|
||||
};
|
||||
} else if (previous
|
||||
&& fileStats.size === previous.size
|
||||
@@ -238,6 +242,9 @@ class CodexTranscriptLocatorImpl {
|
||||
state.sessionId = asString(metadata?.id) ?? state.sessionId;
|
||||
const eventCwd = asString(metadata?.cwd);
|
||||
state.cwd = eventCwd ? normalizePath(eventCwd) : state.cwd;
|
||||
if (metadata && Object.prototype.hasOwnProperty.call(metadata, 'source')) {
|
||||
state.isSubagent = isCodexSubagentSource(metadata.source);
|
||||
}
|
||||
}
|
||||
|
||||
if (convertCodexEvent(event)?.userActivity) {
|
||||
@@ -263,8 +270,10 @@ class CodexTranscriptLocatorImpl {
|
||||
if (state.sessionId !== this.resumeSessionId) {
|
||||
return null;
|
||||
}
|
||||
} else if (state.cwd !== this.targetCwd) {
|
||||
return null;
|
||||
} else {
|
||||
if (state.isSubagent || state.cwd !== this.targetCwd) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return { sessionId: state.sessionId, transcriptPath };
|
||||
|
||||
Reference in New Issue
Block a user