|
|
@@ -31,6 +31,7 @@ TOOL_CALL_EVENT = "tool_call"
|
|
|
TOOL_RESULT_EVENT = "tool_result"
|
|
|
CHAT_MESSAGE_EVENT = "chat_message"
|
|
|
CHAT_CAPTURE_EVENT = "chat_capture"
|
|
|
+TRIAGE_CHAT_EVENT = "triage_chat"
|
|
|
CLEARED_EVENT = "cleared"
|
|
|
TRIAGE_EVENT = "triage"
|
|
|
|
|
|
@@ -124,6 +125,7 @@ class AssistantService:
|
|
|
self._pending_chat: list[str] = []
|
|
|
self.triage_history: list[str] = []
|
|
|
self.chat_capture = True
|
|
|
+ self.triage_chat = False
|
|
|
|
|
|
# Speaker roster cache (display-name join only; the daemon owns identity).
|
|
|
self._speakers: dict[str, dict] = {}
|
|
|
@@ -329,6 +331,7 @@ class AssistantService:
|
|
|
"session_dir": self._session_dir,
|
|
|
"chat_turns": len(self.chat_history),
|
|
|
"chat_capture": self.chat_capture,
|
|
|
+ "triage_chat": self.triage_chat,
|
|
|
"inference_depth": self._inference.depth(),
|
|
|
"inference_in_flight": self._inference.in_flight(),
|
|
|
"triage_state": triage_state,
|
|
|
@@ -498,6 +501,16 @@ class AssistantService:
|
|
|
self._emit(CHAT_CAPTURE_EVENT, on=on)
|
|
|
return True
|
|
|
|
|
|
+ def set_triage_chat(self, on: bool) -> bool:
|
|
|
+ """Gate whether chat turns are fed into the triage window and brief."""
|
|
|
+ on = bool(on)
|
|
|
+ with self._lock:
|
|
|
+ if on == self.triage_chat:
|
|
|
+ return True
|
|
|
+ self.triage_chat = on
|
|
|
+ self._emit(TRIAGE_CHAT_EVENT, on=on)
|
|
|
+ return True
|
|
|
+
|
|
|
def submit_chat_text(self, text: str) -> bool:
|
|
|
"""Send one typed chat turn immediately, bypassing the turn-end gate."""
|
|
|
text = (text or "").strip()
|
|
|
@@ -924,6 +937,8 @@ class AssistantService:
|
|
|
self._debug_emit("queue-full", job="chat")
|
|
|
|
|
|
def _run_chat(self, text: str) -> None:
|
|
|
+ if self.triage_chat and not self.chat_capture:
|
|
|
+ self._capture_chat_turn("You", text)
|
|
|
self._emit(CHAT_MESSAGE_EVENT, role="user", text=text)
|
|
|
chat_fn = self.chat_fn
|
|
|
if chat_fn is None:
|
|
|
@@ -937,7 +952,25 @@ class AssistantService:
|
|
|
self._debug_emit("llm-timeout", job="chat")
|
|
|
return
|
|
|
if reply:
|
|
|
+ if self.triage_chat:
|
|
|
+ self._capture_chat_turn("Assistant", reply)
|
|
|
self._emit(CHAT_MESSAGE_EVENT, role="assistant", text=reply)
|
|
|
+ if self.triage_chat:
|
|
|
+ self._triage_check(force=True)
|
|
|
+
|
|
|
+ def _capture_chat_turn(self, label: str, text: str) -> None:
|
|
|
+ """Append one labeled chat turn to the triage window and brief deltas."""
|
|
|
+ if self._triage is None:
|
|
|
+ return
|
|
|
+ text = (text or "").strip()
|
|
|
+ if not text:
|
|
|
+ return
|
|
|
+ line = f"{label}: {text}"
|
|
|
+ with self._lock:
|
|
|
+ self.triage_history.append(line)
|
|
|
+ del self.triage_history[:-_TRIAGE_HISTORY_MAX]
|
|
|
+ self._pending_brief.append(line)
|
|
|
+ self._cap_pending_brief_locked()
|
|
|
|
|
|
def _periodic_s(self) -> float:
|
|
|
"""Accumulating-speech cadence; <= 0 disables the periodic trigger."""
|
|
|
@@ -948,6 +981,12 @@ class AssistantService:
|
|
|
|
|
|
def _maybe_triage(self) -> None:
|
|
|
"""Classify recent utterances on a settled turn, or periodically while speech grows."""
|
|
|
+ self._triage_check(force=False)
|
|
|
+
|
|
|
+ def _triage_check(self, force: bool) -> None:
|
|
|
+ """Submit the retained window to the Laya lane when a trigger fires."""
|
|
|
+ if self._triage is None or self._laya_lane is None:
|
|
|
+ return
|
|
|
questions = self._triage.questions
|
|
|
if not questions:
|
|
|
return
|
|
|
@@ -962,14 +1001,14 @@ class AssistantService:
|
|
|
and self._triage_next_due is not None
|
|
|
and now >= self._triage_next_due
|
|
|
)
|
|
|
- if not settled and not periodic_due:
|
|
|
+ if not force and not settled and not periodic_due:
|
|
|
return
|
|
|
window = tuple(self.triage_history[-int(self.cfg.triage.window):])
|
|
|
if not window or window == self._last_triage_window:
|
|
|
return
|
|
|
generation = self._triage_generation
|
|
|
self._last_triage_window = window
|
|
|
- trigger = "settled" if settled else "periodic"
|
|
|
+ trigger = "settled" if settled else ("periodic" if periodic_due else "chat")
|
|
|
state = self._triage.build_state(window)
|
|
|
if not state:
|
|
|
self._debug_emit("triage-skip", reason="empty_state", generation=generation)
|