feat(cli): add A2A HTTP listener for external message injection in Forever Mode

Embed a JSON-RPC 2.0 HTTP server that bridges A2A protocol messages into
the interactive session. Starts automatically in Forever Mode, binds to
127.0.0.1 on a configurable port (sisyphusMode.a2aPort), and writes a
port discovery file to ~/.gemini/sessions/.

Supported methods: message/send (blocking), tasks/get, responses/poll,
and GET /.well-known/agent-card.json.

- Add ExternalMessage and A2AListenerStarted app events
- Track streaming state transitions to capture agent responses
- Display A2A port in StatusDisplay when active
This commit is contained in:
Sandy Tao
2026-03-09 19:31:57 -07:00
parent 5194cef9c1
commit c83d368e2c
9 changed files with 561 additions and 0 deletions
+22
View File
@@ -10,6 +10,8 @@ import { basename } from 'node:path';
import { AppContainer } from './ui/AppContainer.js';
import { ConsolePatcher } from './ui/utils/ConsolePatcher.js';
import { registerCleanup, setupTtyCheck } from './utils/cleanup.js';
import { startExternalListener } from './external-listener.js';
import { appEvents, AppEvent } from './utils/events.js';
import {
type StartupWarning,
type Config,
@@ -191,6 +193,26 @@ export async function startInteractiveUI(
registerCleanup(() => instance.unmount());
// Auto-start A2A HTTP listener in Forever Mode
if (config.getIsForeverMode()) {
const sisyphusMode = config.getSisyphusMode();
const a2aPort = sisyphusMode.a2aPort ?? 0;
try {
const listener = await startExternalListener({ port: a2aPort });
registerCleanup(listener.cleanup);
appEvents.emit(AppEvent.A2AListenerStarted, listener.port);
coreEvents.emitFeedback(
'info',
`A2A endpoint listening on port ${listener.port}`,
);
} catch (err) {
coreEvents.emitFeedback(
'warning',
`Failed to start A2A listener: ${err instanceof Error ? err.message : String(err)}`,
);
}
}
registerCleanup(setupTtyCheck());
}