mirror of
https://github.com/wu736139669/hapi.git
synced 2026-11-08 01:43:07 +00:00
revert(codex): remove Luna Reserve fallback (#1780)
Revert 8357da0a9d and its later shared-runtime integration.
Remove automatic model fallback, account usage polling, and the related UI, protocol fields, tests, and documentation without adding replacement quota handling.
Keep generic model pagination and method probing required by the current shared-session architecture. Cover idle sessions staying online without usage polling or agent-state churn.
This commit is contained in:
@@ -31,7 +31,10 @@ vi.mock('../utils/buildHapiMcpBridge', () => ({ buildHapiMcpBridge: async () =>
|
||||
}) }));
|
||||
|
||||
const cleanups: Array<() => Promise<void>> = [];
|
||||
afterEach(async () => { for (const cleanup of cleanups.splice(0)) await cleanup(); });
|
||||
afterEach(async () => {
|
||||
try { for (const cleanup of cleanups.splice(0)) await cleanup(); }
|
||||
finally { vi.useRealTimers(); }
|
||||
});
|
||||
|
||||
async function fixture() {
|
||||
const directory = await mkdtemp('/tmp/hapi-shared-root-');
|
||||
@@ -45,7 +48,7 @@ async function fixture() {
|
||||
updateAgentState: updateState, keepAlive() {},
|
||||
onUserMessage() {}, onCancelQueuedMessage() {}, onRetryQueuedMessage() {},
|
||||
onReconnect: (fn: (() => void) | null) => { reconnect = fn; },
|
||||
rpcHandlerManager: { registerHandler() {} }, sendSessionEvent() {}, sendAgentMessage() {},
|
||||
rpcHandlerManager: { registerHandler() {} }, sendSessionEvent() {}, sendAgentMessage() {}, emitSessionReady() {},
|
||||
sendSessionDeath() {}, async flush() {}, close() {}
|
||||
} as unknown as ApiSessionClient;
|
||||
const root = new SharedCodexRoot({ session, workingDirectory: directory } as SessionBootstrapResult, {
|
||||
@@ -66,6 +69,21 @@ async function fixture() {
|
||||
}
|
||||
|
||||
describe('shared steering availability', () => {
|
||||
it('keeps idle sessions online without polling usage or publishing agent-state updates', async () => {
|
||||
const f = await fixture();
|
||||
const requests = vi.spyOn(f.root.client, 'request');
|
||||
const heartbeat = vi.spyOn(f.root.session, 'keepAlive');
|
||||
const updates = f.updateState.mock.calls.length;
|
||||
vi.useFakeTimers();
|
||||
|
||||
await f.root.activate();
|
||||
await vi.advanceTimersByTimeAsync(5 * 60_000);
|
||||
|
||||
expect(heartbeat).toHaveBeenCalled();
|
||||
expect(requests).not.toHaveBeenCalled();
|
||||
expect(f.updateState).toHaveBeenCalledTimes(updates);
|
||||
});
|
||||
|
||||
it('publishes root turn transitions, ignores child turns, and clears on shutdown', async () => {
|
||||
const f = await fixture();
|
||||
expect(f.state().steeringActive).toBe(false);
|
||||
|
||||
@@ -12,7 +12,6 @@ import { CodexAppServerClient } from '../codexAppServerClient';
|
||||
import { buildHapiMcpBridge, type HapiMcpBridge } from '../utils/buildHapiMcpBridge';
|
||||
import { buildUserInputFromMessage } from '../utils/appServerConfig';
|
||||
import { resolveCodexPermissionModeConfig } from '../utils/permissionModeConfig';
|
||||
import { LunaReserve } from '../utils/lunaReserve';
|
||||
import { resolveCodexSlashCommand } from '../utils/slashCommands';
|
||||
import { parseReasoningEffortValue } from '../utils/reasoningEffort';
|
||||
import { SharedCodexPermissions } from './permissions';
|
||||
@@ -49,9 +48,7 @@ export class SharedCodexRoot {
|
||||
private projection!: SharedCodexProjection;
|
||||
private readonly children = new Map<string, SharedCodexProjection>();
|
||||
private readonly ancestry = new Map<string, string | null>();
|
||||
private readonly reserve: LunaReserve;
|
||||
private heartbeat?: ReturnType<typeof setInterval>;
|
||||
private usageTimer?: ReturnType<typeof setInterval>;
|
||||
private work: Promise<unknown> = Promise.resolve();
|
||||
private notifications = Promise.resolve();
|
||||
private currentTurn: string | undefined;
|
||||
@@ -75,9 +72,6 @@ export class SharedCodexRoot {
|
||||
constructor(readonly bootstrap: SessionBootstrapResult, private readonly host: RootHost) {
|
||||
this.session = bootstrap.session;
|
||||
this.client = new CodexAppServerClient({ endpoint: host.endpoint, token: host.token, cwd: bootstrap.workingDirectory });
|
||||
this.reserve = new LunaReserve(this.client,
|
||||
(model, effort, serviceTier) => { this.settings = { ...this.settings, model, modelReasoningEffort: effort, serviceTier }; },
|
||||
codexUsage => this.session.updateAgentState(state => ({ ...state, codexUsage })), message => this.notice(message));
|
||||
this.client.setNotificationHandler((method, params) => {
|
||||
if (method === 'serverRequest/resolved') {
|
||||
this.permissions?.resolved(string(record(params).threadId) ?? '', record(params).requestId); return;
|
||||
@@ -105,7 +99,6 @@ export class SharedCodexRoot {
|
||||
const text = formatMessageWithAttachments(message.content.text, message.content.attachments);
|
||||
const resolved = text.trim().startsWith('/') ? await this.queue.command(id, () => this.command(text)) : text;
|
||||
if (resolved === null) { this.session.emitMessagesConsumed([id], { clearQueuedThinkingGrace: true }); return; }
|
||||
await this.refreshUsage();
|
||||
await this.queue.enqueue(id, buildUserInputFromMessage(resolved), this.interrupted);
|
||||
}).catch(error => this.notice(`Message not confirmed: ${error instanceof Error ? error.message : error}. Inspect the queue before retrying.`));
|
||||
});
|
||||
@@ -186,7 +179,7 @@ export class SharedCodexRoot {
|
||||
[id, { ...request, completedAt: Date.now(), status: 'canceled' as const }])) }
|
||||
}));
|
||||
if (subscribe) response = record(await this.client.request('thread/resume', { threadId }));
|
||||
this.acceptSettings(response); this.acceptSettings(this.host.settingsFor(threadId) ?? {}); this.reserve.attach(threadId, response);
|
||||
this.acceptSettings(response); this.acceptSettings(this.host.settingsFor(threadId) ?? {});
|
||||
await this.projection.history(response.thread); await this.refresh(); await this.refreshChildren(true);
|
||||
}
|
||||
async activate(options: SharedLaunchOptions = {}): Promise<void> {
|
||||
@@ -195,8 +188,6 @@ export class SharedCodexRoot {
|
||||
if (this.stopping) throw new Error('Codex execution is stopping');
|
||||
this.registerControls(); this.ready();
|
||||
this.heartbeat = setInterval(() => this.alive(), 2_000); this.alive(); this.session.emitSessionReady();
|
||||
void this.reserve.initialize().catch(error => logger.debug('[Codex shared] usage initialization', error));
|
||||
this.usageTimer = setInterval(() => { void this.refreshUsage(); }, 60_000);
|
||||
}
|
||||
private publishSteering(): void {
|
||||
const active = Boolean(this.currentTurn) && !this.stopping && !this.closed && !this.reconnecting && this.client.isInitialized();
|
||||
@@ -220,7 +211,6 @@ export class SharedCodexRoot {
|
||||
...('serviceTier' in value ? { serviceTier: value.serviceTier === 'priority' ? 'fast' : 'standard' } : {}),
|
||||
...('collaborationMode' in value ? { collaborationMode: record(value.collaborationMode).mode === 'plan' ? 'plan' as const : 'default' as const } : {})
|
||||
};
|
||||
this.reserve.onSettings(this.threadId, value);
|
||||
for (const listener of this.settingsListeners) listener();
|
||||
}
|
||||
private async notification(method: string, params: unknown, modelAtReceipt?: string): Promise<void> {
|
||||
@@ -303,11 +293,6 @@ export class SharedCodexRoot {
|
||||
} catch (error) { logger.debug('[Codex shared] child history unavailable', { id, error }); }
|
||||
}
|
||||
}
|
||||
private refreshUsage(): Promise<void> {
|
||||
return this.reserve.refresh({ permissionMode: this.settings.permissionMode === 'yolo' ? 'yolo' : 'default',
|
||||
collaborationMode: this.settings.collaborationMode ?? 'default', model: this.settings.model ?? undefined },
|
||||
() => !this.closed && !this.currentTurn);
|
||||
}
|
||||
nativeQueueDeleted(nativeId: string): Promise<void> { return this.queue.deleted(nativeId); }
|
||||
replaySettings(): Array<{ method: string; params: unknown }> {
|
||||
return this.settingsNotification ? [{ method: 'thread/settings/updated', params: {
|
||||
@@ -482,9 +467,9 @@ export class SharedCodexRoot {
|
||||
return slash.kind === 'replace' ? slash.text : null;
|
||||
}
|
||||
stopAccepting(): void {
|
||||
this.stopping = true; this.ready(); clearInterval(this.usageTimer);
|
||||
this.stopping = true; this.ready();
|
||||
this.publishSteering();
|
||||
this.session.onReconnect(null); this.client.setTransportAbandonedHandler(null); this.reserve.dispose();
|
||||
this.session.onReconnect(null); this.client.setTransportAbandonedHandler(null);
|
||||
}
|
||||
async suspend(): Promise<void> {
|
||||
this.stopAccepting();
|
||||
|
||||
Reference in New Issue
Block a user