mirror of
https://github.com/wu736139669/hapi.git
synced 2026-11-08 01:43:07 +00:00
feat: support Codex goal slash command
This commit is contained in:
@@ -16,6 +16,33 @@ describe('AppServerEventConverter', () => {
|
||||
expect(events).toEqual([{ type: 'thread_started', thread_id: 'thread-2' }]);
|
||||
});
|
||||
|
||||
it('maps thread goal updates and clears', () => {
|
||||
const converter = new AppServerEventConverter();
|
||||
const goal = {
|
||||
threadId: 'thread-1',
|
||||
objective: 'ship goal support',
|
||||
status: 'active'
|
||||
};
|
||||
|
||||
expect(converter.handleNotification('thread/goal/updated', {
|
||||
threadId: 'thread-1',
|
||||
turnId: 'turn-1',
|
||||
goal
|
||||
})).toEqual([{
|
||||
type: 'thread_goal_updated',
|
||||
thread_id: 'thread-1',
|
||||
turn_id: 'turn-1',
|
||||
goal
|
||||
}]);
|
||||
|
||||
expect(converter.handleNotification('thread/goal/cleared', {
|
||||
threadId: 'thread-1'
|
||||
})).toEqual([{
|
||||
type: 'thread_goal_cleared',
|
||||
thread_id: 'thread-1'
|
||||
}]);
|
||||
});
|
||||
|
||||
it('maps thread systemError to a task failure', () => {
|
||||
const converter = new AppServerEventConverter();
|
||||
const events = converter.handleNotification('thread/status/changed', {
|
||||
|
||||
@@ -546,6 +546,34 @@ export class AppServerEventConverter {
|
||||
return events;
|
||||
}
|
||||
|
||||
if (method === 'thread/goal/updated') {
|
||||
const goal = asRecord(paramsRecord.goal);
|
||||
const threadId = asString(paramsRecord.threadId ?? paramsRecord.thread_id ?? goal?.threadId ?? goal?.thread_id);
|
||||
if (!threadId || !goal) {
|
||||
return events;
|
||||
}
|
||||
const turnId = asString(paramsRecord.turnId ?? paramsRecord.turn_id);
|
||||
events.push({
|
||||
type: 'thread_goal_updated',
|
||||
thread_id: threadId,
|
||||
...(turnId ? { turn_id: turnId } : {}),
|
||||
goal
|
||||
});
|
||||
return events;
|
||||
}
|
||||
|
||||
if (method === 'thread/goal/cleared') {
|
||||
const threadId = asString(paramsRecord.threadId ?? paramsRecord.thread_id ?? eventScope.thread_id);
|
||||
if (!threadId) {
|
||||
return events;
|
||||
}
|
||||
events.push({
|
||||
type: 'thread_goal_cleared',
|
||||
thread_id: threadId
|
||||
});
|
||||
return events;
|
||||
}
|
||||
|
||||
if (method === 'thread/started' || method === 'thread/resumed') {
|
||||
const thread = asRecord(paramsRecord.thread) ?? paramsRecord;
|
||||
const threadId = asString(thread.threadId ?? thread.thread_id ?? thread.id);
|
||||
|
||||
@@ -46,6 +46,37 @@ describe('resolveCodexSlashCommand', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('resolves Codex goal commands for native handling', () => {
|
||||
expect(resolveCodexSlashCommand('/goal', state)).toEqual({
|
||||
kind: 'goal',
|
||||
action: 'show'
|
||||
});
|
||||
expect(resolveCodexSlashCommand('/goal improve benchmark coverage', state)).toEqual({
|
||||
kind: 'goal',
|
||||
action: 'set',
|
||||
objective: 'improve benchmark coverage'
|
||||
});
|
||||
expect(resolveCodexSlashCommand('/goal pause', state)).toEqual({
|
||||
kind: 'goal',
|
||||
action: 'pause'
|
||||
});
|
||||
expect(resolveCodexSlashCommand('/goal resume', state)).toEqual({
|
||||
kind: 'goal',
|
||||
action: 'resume'
|
||||
});
|
||||
expect(resolveCodexSlashCommand('/goal clear', state)).toEqual({
|
||||
kind: 'goal',
|
||||
action: 'clear'
|
||||
});
|
||||
});
|
||||
|
||||
it('rejects oversized Codex goal objectives', () => {
|
||||
expect(resolveCodexSlashCommand(`/goal ${'x'.repeat(4001)}`, state)).toEqual({
|
||||
kind: 'handled',
|
||||
message: 'Goal objective must be at most 4000 characters.'
|
||||
});
|
||||
});
|
||||
|
||||
it('expands custom Codex prompt commands', () => {
|
||||
expect(resolveCodexSlashCommand('/review src/index.ts', {
|
||||
...state,
|
||||
|
||||
@@ -5,6 +5,7 @@ import type { EnhancedMode } from '../loop';
|
||||
import type { SlashCommand } from '@/modules/common/slashCommands';
|
||||
|
||||
const REASONING_EFFORTS = new Set<ReasoningEffort>(['none', 'minimal', 'low', 'medium', 'high', 'xhigh']);
|
||||
export const MAX_CODEX_GOAL_OBJECTIVE_CHARS = 4_000;
|
||||
|
||||
const UNSUPPORTED_CODEX_BUILTIN_COMMANDS = new Set([
|
||||
'compat',
|
||||
@@ -43,6 +44,12 @@ export type CodexSlashResolution =
|
||||
model?: string | null;
|
||||
modelReasoningEffort?: ReasoningEffort | null;
|
||||
};
|
||||
}
|
||||
| {
|
||||
kind: 'goal';
|
||||
action: 'show' | 'set' | 'pause' | 'resume' | 'clear';
|
||||
objective?: string;
|
||||
message?: string;
|
||||
};
|
||||
|
||||
export function resolveCodexSlashCommand(
|
||||
@@ -97,6 +104,40 @@ export function resolveCodexSlashCommand(
|
||||
};
|
||||
}
|
||||
|
||||
if (command === 'goal') {
|
||||
const lowerRest = rest.toLowerCase();
|
||||
if (!rest) {
|
||||
return { kind: 'goal', action: 'show' };
|
||||
}
|
||||
if (lowerRest === 'clear') {
|
||||
return { kind: 'goal', action: 'clear' };
|
||||
}
|
||||
if (lowerRest === 'pause') {
|
||||
return { kind: 'goal', action: 'pause' };
|
||||
}
|
||||
if (lowerRest === 'resume') {
|
||||
return { kind: 'goal', action: 'resume' };
|
||||
}
|
||||
const objective = rest.trim();
|
||||
if (!objective) {
|
||||
return {
|
||||
kind: 'handled',
|
||||
message: 'Goal objective must not be empty.'
|
||||
};
|
||||
}
|
||||
if ([...objective].length > MAX_CODEX_GOAL_OBJECTIVE_CHARS) {
|
||||
return {
|
||||
kind: 'handled',
|
||||
message: `Goal objective must be at most ${MAX_CODEX_GOAL_OBJECTIVE_CHARS} characters.`
|
||||
};
|
||||
}
|
||||
return {
|
||||
kind: 'goal',
|
||||
action: 'set',
|
||||
objective
|
||||
};
|
||||
}
|
||||
|
||||
if (command === 'default' || command === 'execute') {
|
||||
return {
|
||||
kind: 'handled',
|
||||
@@ -178,6 +219,8 @@ export function resolveCodexSlashCommand(
|
||||
'Supported Codex slash commands:',
|
||||
'/plan [prompt] — enable plan mode, optionally send prompt',
|
||||
'/plan off — return to default mode',
|
||||
'/goal [objective] — set or view the persistent goal',
|
||||
'/goal pause|resume|clear — update the current goal',
|
||||
'/clear — reset current Codex thread context',
|
||||
'/compact — compact current Codex thread context',
|
||||
'/status — show current Codex session config',
|
||||
|
||||
Reference in New Issue
Block a user