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 e4cc67b63d
commit eddf7ea342
8 changed files with 559 additions and 0 deletions
+21
View File
@@ -26,6 +26,7 @@ import { getStartupWarnings } from './utils/startupWarnings.js';
import { getUserStartupWarnings } from './utils/userStartupWarnings.js';
import { ConsolePatcher } from './ui/utils/ConsolePatcher.js';
import { runNonInteractive } from './nonInteractiveCli.js';
import { startExternalListener } from './external-listener.js';
import {
cleanupCheckpoints,
registerCleanup,
@@ -322,6 +323,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());
}