fix(chat): support Hermes clarify batches
Build / build (push) Canceled after 0s
Playwright / e2e (push) Canceled after 0s

This commit is contained in:
2026-11-09 09:14:42 +08:00
parent 96aac671b3
commit f935385356
10 changed files with 233 additions and 10 deletions
@@ -0,0 +1,41 @@
"""Adapt Hermes batch clarify callbacks to the bridge's per-question UI."""
from __future__ import annotations
import queue
import uuid
def make_clarify_callback(pool, session_id, legacy_callback):
def callback(questions, choices=None):
# Older Hermes versions call callback(question, choices) and expect text.
if not isinstance(questions, list):
return legacy_callback(questions, choices)
answers = {}
for index, item in enumerate(questions):
if not isinstance(item, dict):
raise ValueError('invalid clarify question')
question_id = str(item.get('qid') or f'q{index}')
clarify_id = uuid.uuid4().hex
response_queue = queue.Queue(maxsize=1)
with pool._lock:
pool._clarify_requests[clarify_id] = response_queue
try:
pool._append_event(session_id, {
'event': 'clarify.requested',
'clarify_id': clarify_id,
'question': str(item.get('question') or ''),
'choices': item.get('choices') or None,
'multi_select': bool(item.get('multi_select')),
'timeout_ms': 300_000,
})
try:
response = response_queue.get(timeout=300)
except queue.Empty:
return {'answers': answers, 'outcome': 'timed_out'}
answers[question_id] = response or None
finally:
with pool._lock:
pool._clarify_requests.pop(clarify_id, None)
return {'answers': answers, 'outcome': 'submitted'}
return callback
@@ -0,0 +1,25 @@
#!/usr/bin/env python3
"""Launch the read-only vendored bridge with local compatibility adapters."""
from pathlib import Path
import sys
compat_dir = Path(__file__).resolve().parent
sys.path.insert(0, str(compat_dir))
sys.path.insert(0, str(compat_dir.parent / 'python'))
import hermes_bridge as bridge
from clarify_compat import make_clarify_callback
original_callback = bridge.AgentPool._clarify_callback
def clarify_callback(self, session_id):
return make_clarify_callback(self, session_id, original_callback(self, session_id))
bridge.AgentPool._clarify_callback = clarify_callback
# Workers must launch this same compatibility entrypoint, not the vendored one.
bridge._transport.__file__ = __file__
if __name__ == '__main__':
raise SystemExit(bridge.main())
@@ -332,11 +332,11 @@ export function buildAgentBridgeLaunchArgs(command: BridgeCommand, script: strin
function bridgeScriptPath(): string {
const candidates = [
// Built server: dist/server/index.js -> dist/server/agent-bridge/python/hermes_bridge.py
resolve(__dirname, 'agent-bridge', 'python', 'hermes_bridge.py'),
// Built server: dist/server/index.js -> dist/server/agent-bridge/compat/hermes_bridge.py
resolve(__dirname, 'agent-bridge', 'compat', 'hermes_bridge.py'),
// ts-node/dev source tree.
resolve(__dirname, 'services/hermes/agent-bridge/python/hermes_bridge.py'),
resolve(process.cwd(), 'packages/server/src/services/hermes/agent-bridge/python/hermes_bridge.py'),
resolve(__dirname, 'services/hermes/agent-bridge/compat/hermes_bridge.py'),
resolve(process.cwd(), 'packages/server/src/services/hermes/agent-bridge/compat/hermes_bridge.py'),
]
const found = candidates.find(candidate => existsSync(candidate))
if (!found) {
@@ -1202,6 +1202,7 @@ async function applyBridgeChunkAsync(
clarify_id: ev.clarify_id,
question: ev.question,
choices: Array.isArray(ev.choices) ? ev.choices : null,
multi_select: Boolean(ev.multi_select),
timeout_ms: ev.timeout_ms,
}
replaceState(sessionMap, sessionId, 'clarify.requested', payload)