Add full shell, replaceable Omics adapter and bilingual interaction

Implement approved specification #32 and tickets #33-#37. Keep host authentication server-verified and pin session interaction language. Compile scoped base selectors for browser compatibility and retain full gutters during CSS pruning.
This commit is contained in:
Codex
2026-09-13 14:26:39 +02:00
parent 2d1b714ebe
commit d8a29bfbdd
207 changed files with 8570 additions and 2164 deletions
+50 -3
View File
@@ -144,6 +144,7 @@ def _list_sessions(sessions_root: Path) -> list[dict]:
"archived": m.archived,
"workspace_id": m.workspace_id,
"workspace_revision": m.workspace_revision,
"interaction_language": m.interaction_language,
})
out.sort(key=lambda r: r["created_at"], reverse=True)
return out
@@ -162,7 +163,8 @@ def list_cmd(
"updated_at": s.manifest.updated_at.isoformat() if s.manifest.updated_at else None,
"author": s.manifest.author, "name": s.manifest.name, "group": s.manifest.group,
"archived": s.manifest.archived, "workspace_id": s.manifest.workspace_id,
"workspace_revision": s.manifest.workspace_revision}
"workspace_revision": s.manifest.workspace_revision,
"interaction_language": s.manifest.interaction_language}
for s in session_repository(cfg).list()
]
if json_out:
@@ -175,6 +177,9 @@ def list_cmd(
@session_app.command("new")
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."
),
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)."),
thinking: str = typer.Option(None, "--thinking", help="Livello di thinking (es. medium)."),
@@ -188,10 +193,18 @@ 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
try:
language = validate_language_tag(
interaction_language if interaction_language is not None else getattr(cfg, "language", "en")
)
except ValueError as exc:
raise typer.BadParameter(str(exc), param_hint="--interaction-language") from None
manifest = new_session_manifest(question, cfg.database,
provider=provider, model=model, thinking=thinking,
workspace_id=workspace_id, workspace_revision=workspace_revision,
name=name or _extract_name(question))
name=name or _extract_name(question), interaction_language=language)
repository = session_repository(cfg)
repository.create(manifest)
repository.write_artifact(manifest.id, "question", render_question_md(question))
@@ -306,6 +319,7 @@ def show_cmd(
typer.echo(f"stato : {manifest.status}")
typer.echo(f"domanda : {manifest.question}")
typer.echo(f"target : {manifest.database} / {manifest.db_schema}")
typer.echo(f"interaction_language: {manifest.interaction_language or '(legacy: not pinned)'}")
typer.echo(f"decisioni: {len(decisions)}")
for d in decisions[-10:]:
typer.echo(f" [{d.seq}] {d.type}: {d.subject}" + (f" — {d.detail}" if d.detail else ""))
@@ -352,11 +366,44 @@ def fail_cmd(session_id: str = typer.Argument(...), config: Path = CONFIG_OPT) -
session_repository(cfg).save_manifest(snapshot.manifest)
typer.secho(f"OK: sessione {session_id} marcata failed.", fg=typer.colors.RED)
def ensure_session_language_or_exit(cfg, session_id: str):
from tht.session.store import SessionError
try:
return session_repository(cfg).ensure_interaction_language(
session_id, getattr(cfg, "language", "en")
).manifest
except (SessionError, ValueError) as exc:
typer.echo(f"ERROR: {exc}", err=True)
raise typer.Exit(code=1) from None
@session_app.command("ensure-interaction-language")
def ensure_interaction_language_cmd(
session_id: str = typer.Argument(...),
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."""
cfg = _load_config_or_exit(config)
manifest = ensure_session_language_or_exit(cfg, session_id)
if json_out:
payload = manifest.model_dump(by_alias=True, mode="json")
payload["workspace_language"] = getattr(cfg, "language", "en")
typer.echo(json.dumps(payload, ensure_ascii=False))
else:
typer.echo(manifest.interaction_language)
@session_app.command("reopen")
def reopen_cmd(session_id: str = typer.Argument(...), config: Path = CONFIG_OPT) -> None:
"""Riapre una sessione per una ripresa manuale."""
cfg = _load_config_or_exit(config)
load_session_or_exit(cfg, session_id)
manifest = load_session_or_exit(cfg, session_id)
if manifest.status == "finalized" or manifest.archived:
typer.echo("ERROR: Session is read-only (finalized or archived)", err=True)
raise typer.Exit(code=1)
ensure_session_language_or_exit(cfg, session_id)
snapshot = load_snapshot_or_exit(cfg, session_id)
snapshot.manifest.status = "open"
session_repository(cfg).save_manifest(snapshot.manifest)