Fix new-question landing and question-language HITL
Publish documentation / publish (push) Successful in 34s

Reset the activity panel when starting a new question so the landing navigation is restored. Detect and persist the original question language, pass it through runtime and widget descriptors, and scope HITL controls to that language.

Validated with gate, session, backend and frontend tests, TypeScript checks, Ruff and strict docs build. Rebuilt and restarted local core/frontend; both healthy and serving HTTP successfully.
This commit is contained in:
Codex
2026-09-21 19:47:22 +02:00
parent 23e52c80de
commit bd416f7327
22 changed files with 232 additions and 43 deletions
+7 -5
View File
@@ -178,7 +178,8 @@ def list_cmd(
def new_cmd(
question: str = typer.Argument(..., help="La domanda in linguaggio naturale."),
interaction_language: str = typer.Option(
None, "--interaction-language", help="BCP-47 interaction language; defaults to workspace language."
None, "--interaction-language",
help="BCP-47 fallback for ambiguous questions; otherwise use the question's language.",
),
provider: str = typer.Option(None, "--provider", help="Provider LLM (es. zai, anthropic)."),
model: str = typer.Option(None, "--model", help="Modello LLM (es. glm-5.2)."),
@@ -193,10 +194,11 @@ def new_cmd(
from tht.session.store import _extract_name, new_session_manifest, render_question_md
cfg = _load_config_or_exit(config)
from tht.session.language import validate_language_tag
from tht.session.language import question_interaction_language
try:
language = validate_language_tag(
language = question_interaction_language(
question,
interaction_language if interaction_language is not None else getattr(cfg, "language", "en")
)
except ValueError as exc:
@@ -209,7 +211,7 @@ def new_cmd(
repository.create(manifest)
repository.write_artifact(manifest.id, "question", render_question_md(question))
if json_out:
typer.echo(json.dumps({"id": manifest.id}, ensure_ascii=False))
typer.echo(json.dumps({"id": manifest.id, "interaction_language": language}, ensure_ascii=False))
return
typer.secho(f"OK: sessione creata ({manifest.id})", fg=typer.colors.GREEN)
typer.echo(manifest.id)
@@ -384,7 +386,7 @@ def ensure_interaction_language_cmd(
json_out: bool = typer.Option(False, "--json", help="Emit pristine JSON."),
config: Path = CONFIG_OPT,
) -> None:
"""Pin a missing legacy language from the workspace; never override a saved value."""
"""Pin a missing language from the question; never override a saved value."""
cfg = _load_config_or_exit(config)
manifest = ensure_session_language_or_exit(cfg, session_id)
if json_out:
+37
View File
@@ -1,5 +1,9 @@
"""Session language policy, independent of workspace document content."""
import re
from functools import lru_cache
from langdetect import DetectorFactory, LangDetectException
from langdetect.detector_factory import PROFILES_DIRECTORY
_LANGUAGE_TAG = re.compile(
r"(?P<language>[A-Za-z]{2,8})(?:-(?P<script>[A-Za-z]{4}))?"
@@ -21,3 +25,36 @@ def validate_language_tag(value: str) -> str:
if match["region"]:
parts.append(match["region"].upper())
return "-".join(parts) + match["rest"].lower()
@lru_cache(maxsize=1)
def _question_detector_factory() -> DetectorFactory:
factory = DetectorFactory()
factory.seed = 0
factory.load_profile(PROFILES_DIRECTORY)
return factory
def question_interaction_language(question: str, fallback: str) -> str:
"""Pin confidently detected prose language; short/ambiguous/code input uses the hint."""
fallback = validate_language_tag(fallback)
prose = re.sub(r"```[\s\S]*?```|`[^`]*`|\b\w*_\w*\b", " ", question)
if re.match(
r"\s*(?:SELECT\s+.+\s+FROM\b|WITH\s+\w+\s+AS\s*\(|INSERT\s+INTO\b|"
r"UPDATE\s+\w+\s+SET\b|DELETE\s+FROM\b|CREATE\s+TABLE\b)",
prose, re.IGNORECASE | re.DOTALL,
):
return fallback
if sum(character.isalpha() for character in prose) < 12:
return fallback
detector = _question_detector_factory().create()
detector.append(prose)
try:
candidates = detector.get_probabilities()
except LangDetectException:
return fallback
if not candidates or candidates[0].prob < 0.90:
return fallback
detected = validate_language_tag(candidates[0].lang)
# Preserve a regional preference when it agrees with the detected language.
return fallback if detected.split("-")[0] == fallback.split("-")[0] else detected
+2 -2
View File
@@ -151,13 +151,13 @@ def new_session_manifest(
def pin_interaction_language(manifest: SessionManifest, workspace_language: str) -> bool:
"""Apply legacy compatibility once, while the repository holds its writer lock."""
from tht.session.language import validate_language_tag
from tht.session.language import question_interaction_language
if manifest.interaction_language is not None:
return False
if manifest.status == "finalized" or manifest.archived:
raise SessionError("Session is read-only (finalized or archived)")
manifest.interaction_language = validate_language_tag(workspace_language)
manifest.interaction_language = question_interaction_language(manifest.question, workspace_language)
manifest.updated_at = datetime.now(UTC)
manifest.updated_by = current_author()
return True