mirror of
https://github.com/MervinPraison/PraisonAIUI.git
synced 2026-11-07 05:56:17 -10:00
Compare commits
7
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
17cd4d9f86 | ||
|
|
43935e4e41 | ||
|
|
135b05435c | ||
|
|
be502d823f | ||
|
|
9be1266747 | ||
|
|
8ce13a9fec | ||
|
|
e6a346aa22 |
No files matched your search
@@ -3,7 +3,7 @@
|
||||
"stack": "claude",
|
||||
"profile": "sdk",
|
||||
"mode": "hub",
|
||||
"hubRef": "v1.1.7",
|
||||
"hubRef": "v1.1.8",
|
||||
"templateVersion": "1.1.7",
|
||||
"installedAt": "2026-09-07T10:02:11Z"
|
||||
}
|
||||
@@ -27,7 +27,7 @@ jobs:
|
||||
permissions:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
uses: MervinPraison/github-automation-template/.github/workflows/[email protected].7
|
||||
uses: MervinPraison/github-automation-template/.github/workflows/[email protected].8
|
||||
secrets: inherit
|
||||
with:
|
||||
hub_ref: v1.1.7
|
||||
hub_ref: v1.1.8
|
||||
@@ -16,8 +16,8 @@ permissions:
|
||||
|
||||
jobs:
|
||||
recover:
|
||||
uses: MervinPraison/github-automation-template/.github/workflows/[email protected].7
|
||||
uses: MervinPraison/github-automation-template/.github/workflows/[email protected].8
|
||||
secrets: inherit
|
||||
with:
|
||||
hub_ref: v1.1.7
|
||||
hub_ref: v1.1.8
|
||||
pr_number: ${{ github.event.inputs.pr_number || '' }}
|
||||
@@ -25,8 +25,8 @@ permissions:
|
||||
|
||||
jobs:
|
||||
detect-and-trigger:
|
||||
uses: MervinPraison/github-automation-template/.github/workflows/[email protected].7
|
||||
uses: MervinPraison/github-automation-template/.github/workflows/[email protected].8
|
||||
secrets: inherit
|
||||
with:
|
||||
hub_ref: v1.1.7
|
||||
hub_ref: v1.1.8
|
||||
pr_number: ${{ github.event.inputs.pr_number || '' }}
|
||||
@@ -43,9 +43,9 @@ jobs:
|
||||
id-token: write
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: MervinPraison/github-automation-template/.github/actions/[email protected].7
|
||||
- uses: MervinPraison/github-automation-template/.github/actions/[email protected].8
|
||||
with:
|
||||
hub_ref: v1.1.7
|
||||
hub_ref: v1.1.8
|
||||
- name: Generate GitHub App token
|
||||
id: app-token
|
||||
uses: actions/create-github-app-token@v1
|
||||
@@ -87,9 +87,9 @@ jobs:
|
||||
issues: write
|
||||
actions: read
|
||||
id-token: write
|
||||
uses: MervinPraison/github-automation-template/.github/workflows/[email protected].7
|
||||
uses: MervinPraison/github-automation-template/.github/workflows/[email protected].8
|
||||
secrets: inherit
|
||||
with:
|
||||
hub_ref: v1.1.7
|
||||
hub_ref: v1.1.8
|
||||
pr_number: ${{ github.event.inputs.pr_number || github.event.client_payload.pr_number || '' }}
|
||||
merge_method: ${{ github.event.inputs.merge_method || 'merge' }}
|
||||
@@ -18,7 +18,7 @@ jobs:
|
||||
issues: write
|
||||
actions: read
|
||||
id-token: write
|
||||
uses: MervinPraison/github-automation-template/.github/workflows/[email protected].7
|
||||
uses: MervinPraison/github-automation-template/.github/workflows/[email protected].8
|
||||
secrets: inherit
|
||||
with:
|
||||
hub_ref: v1.1.7
|
||||
hub_ref: v1.1.8
|
||||
@@ -25,9 +25,9 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: MervinPraison/github-automation-template/.github/actions/[email protected].7
|
||||
- uses: MervinPraison/github-automation-template/.github/actions/[email protected].8
|
||||
with:
|
||||
hub_ref: v1.1.7
|
||||
hub_ref: v1.1.8
|
||||
- name: Detect conflicting PRs and trigger Claude
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
|
||||
@@ -13,7 +13,7 @@ permissions:
|
||||
|
||||
jobs:
|
||||
sync:
|
||||
uses: MervinPraison/github-automation-template/.github/workflows/[email protected].7
|
||||
uses: MervinPraison/github-automation-template/.github/workflows/[email protected].8
|
||||
secrets: inherit
|
||||
with:
|
||||
hub_ref: v1.1.7
|
||||
hub_ref: v1.1.8
|
||||
@@ -17,6 +17,25 @@ def _tcp_open(host: str, port: int, timeout: float = 2.0) -> bool:
|
||||
return False
|
||||
|
||||
|
||||
_MCP_PROBE_TIMEOUT_SEC = 2.0
|
||||
|
||||
|
||||
def _is_slow_mcp_probe(detail: str) -> bool:
|
||||
lowered = detail.lower()
|
||||
return any(token in lowered for token in ("timed out", "timeout", "temporarily unavailable"))
|
||||
|
||||
|
||||
def _mcp_servers_probe(app_port: int) -> tuple[str, str]:
|
||||
"""Probe MCP list endpoint; slow stdio connect must not fail the whole doctor run."""
|
||||
status, detail = _http_status(
|
||||
f"http://127.0.0.1:{app_port}/api/mcp/servers",
|
||||
timeout=_MCP_PROBE_TIMEOUT_SEC,
|
||||
)
|
||||
if status == "fail" and _is_slow_mcp_probe(detail):
|
||||
return "warn", detail
|
||||
return status, detail
|
||||
|
||||
|
||||
def _http_status(url: str, timeout: float = 5.0) -> tuple[str, str]:
|
||||
try:
|
||||
req = Request(url, headers={"User-Agent": "praisonai-voice-doctor"})
|
||||
@@ -64,7 +83,7 @@ def run_voice_doctor(*, app_port: int = 8001, sidecar_port: int = 8002) -> dict[
|
||||
checks.append({"name": "Speech Engine config", "status": status, "detail": detail})
|
||||
status, detail = _http_status(f"http://127.0.0.1:{app_port}/api/voice/realtime/config")
|
||||
checks.append({"name": "GPT Realtime config", "status": status, "detail": detail})
|
||||
status, detail = _http_status(f"http://127.0.0.1:{app_port}/api/mcp/servers")
|
||||
status, detail = _mcp_servers_probe(app_port)
|
||||
checks.append({"name": "MCP servers API", "status": status, "detail": detail})
|
||||
else:
|
||||
checks.append(
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
[project]
|
||||
name = "aiui"
|
||||
version = "0.3.144"
|
||||
version = "0.3.145"
|
||||
description = "YAML-driven website generator - CLI and compiler"
|
||||
readme = "README.md"
|
||||
license = {text = "MIT"}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
"""Version information."""
|
||||
|
||||
__version__ = "0.3.144"
|
||||
__version__ = "0.3.145"
|
||||
@@ -652,13 +652,24 @@ class ChannelsFeature(BaseFeatureProtocol):
|
||||
if hasattr(bot, "_session") and hasattr(bot._session, "chat"):
|
||||
_original_chat = bot._session.chat
|
||||
|
||||
async def _wrapped_chat(agent: Any, user_id: str, text: str) -> str:
|
||||
async def _wrapped_chat(
|
||||
agent: Any,
|
||||
user_id: str,
|
||||
text: str,
|
||||
*args: Any,
|
||||
**kwargs: Any,
|
||||
) -> str:
|
||||
"""Wrap agent chat to broadcast responses AND intermediate
|
||||
tool/step events to the Chat UI.
|
||||
|
||||
Hooks into the agent's StreamEventEmitter (same pattern as
|
||||
PraisonAIProvider._run_direct_mode) to capture tool_call,
|
||||
reasoning, and content events during execution.
|
||||
|
||||
Any extra positional/keyword arguments (e.g. ``chat_id``,
|
||||
``platform``, ``thread_id``) are forwarded untouched to the
|
||||
wrapped session ``chat`` so per-conversation memory/session
|
||||
continuity is preserved instead of raising ``TypeError``.
|
||||
"""
|
||||
from .chat import _enrich_tool_payload, get_chat_manager
|
||||
|
||||
@@ -831,7 +842,7 @@ class ChannelsFeature(BaseFeatureProtocol):
|
||||
drain_task = asyncio.create_task(_drain_events())
|
||||
|
||||
try:
|
||||
response = await _original_chat(agent, user_id, text)
|
||||
response = await _original_chat(agent, user_id, text, *args, **kwargs)
|
||||
finally:
|
||||
# Signal drain to stop and clean up callback
|
||||
try:
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
"""Regression coverage for issue #287: the channels chat bridge dropped
|
||||
extra keyword arguments (e.g. ``chat_id``, ``platform``) because the
|
||||
``_wrapped_chat`` wrapper installed over ``bot._session.chat`` had a fixed
|
||||
``(agent, user_id, text)`` signature.
|
||||
|
||||
When the bot internals invoked the session with per-conversation kwargs the
|
||||
wrapper raised ``TypeError: _wrapped_chat() got an unexpected keyword
|
||||
argument 'chat_id'`` and the bot silently failed to reply. The wrapper must
|
||||
forward any extra positional/keyword arguments untouched to the original
|
||||
session ``chat`` so memory/session continuity is preserved.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
import pytest
|
||||
|
||||
from praisonaiui.features.channels import ChannelsFeature
|
||||
|
||||
|
||||
class _FakeSession:
|
||||
"""Minimal session whose ``chat`` records forwarded arguments."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.received_args: tuple = ()
|
||||
self.received_kwargs: dict = {}
|
||||
|
||||
async def chat(self, agent: Any, user_id: str, text: str, *args: Any, **kwargs: Any) -> str:
|
||||
self.received_args = args
|
||||
self.received_kwargs = dict(kwargs)
|
||||
return f"reply to {text}"
|
||||
|
||||
|
||||
class _FakeBot:
|
||||
def __init__(self) -> None:
|
||||
self._session = _FakeSession()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_wrapped_chat_forwards_kwargs() -> None:
|
||||
feature = ChannelsFeature()
|
||||
bot = _FakeBot()
|
||||
|
||||
feature._attach_chat_bridge("chan-1", bot, "telegram")
|
||||
|
||||
# The bridge replaced the session chat with the wrapper.
|
||||
assert bot._session.chat.__name__ == "_wrapped_chat"
|
||||
|
||||
reply = await bot._session.chat(
|
||||
object(),
|
||||
"user-42",
|
||||
"Hi",
|
||||
chat_id="987654321",
|
||||
platform="telegram",
|
||||
user_id_meta="987654321",
|
||||
)
|
||||
|
||||
assert reply == "reply to Hi"
|
||||
assert bot._session.received_kwargs["chat_id"] == "987654321"
|
||||
assert bot._session.received_kwargs["platform"] == "telegram"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_wrapped_chat_forwards_extra_positional_args() -> None:
|
||||
feature = ChannelsFeature()
|
||||
bot = _FakeBot()
|
||||
|
||||
feature._attach_chat_bridge("chan-2", bot, "slack")
|
||||
|
||||
reply = await bot._session.chat(object(), "user-7", "Hello", "extra")
|
||||
|
||||
assert reply == "reply to Hello"
|
||||
assert bot._session.received_args == ("extra",)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_wrapped_chat_still_works_without_kwargs() -> None:
|
||||
"""No-kwargs path (web UI / existing callers) must keep working."""
|
||||
feature = ChannelsFeature()
|
||||
bot = _FakeBot()
|
||||
|
||||
feature._attach_chat_bridge("chan-3", bot, "discord")
|
||||
|
||||
reply = await bot._session.chat(object(), "user-9", "Ping")
|
||||
|
||||
assert reply == "reply to Ping"
|
||||
assert bot._session.received_kwargs == {}
|
||||
@@ -478,6 +478,26 @@ def test_voice_doctor_report(monkeypatch):
|
||||
assert report["summary"]["failed"] >= 1
|
||||
|
||||
|
||||
def test_voice_doctor_mcp_probe_timeout_is_warn(monkeypatch):
|
||||
monkeypatch.setenv("OPENAI_API_KEY", "sk-test-key-12345678")
|
||||
|
||||
def fake_http_status(url: str, timeout: float = 5.0):
|
||||
if "/api/mcp/servers" in url:
|
||||
assert timeout == 2.0
|
||||
return "fail", "timed out"
|
||||
return "pass", "HTTP 200"
|
||||
|
||||
with _voice_modules() as imp:
|
||||
doctor = imp("integrations.voice.doctor")
|
||||
monkeypatch.setattr(doctor, "_tcp_open", lambda *args, **kwargs: True)
|
||||
monkeypatch.setattr(doctor, "_http_status", fake_http_status)
|
||||
report = doctor.run_voice_doctor(app_port=8001, sidecar_port=59998)
|
||||
|
||||
mcp = next(c for c in report["checks"] if c["name"] == "MCP servers API")
|
||||
assert mcp["status"] == "warn"
|
||||
assert mcp["detail"] == "timed out"
|
||||
|
||||
|
||||
def test_call_detail_ui_includes_analytics():
|
||||
with _voice_modules() as imp:
|
||||
ui = imp("integrations.voice.call_detail_ui")
|
||||
|
||||
Reference in new issue
Block a user