Seed Assistent personas from a shelf folder instead of yaml prompts.
Push assistent-personas/ JSON overlays without wiping VM clones or exact.controls. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+1
-1
@@ -944,7 +944,7 @@ def seed_extensions_cmd() -> None:
|
||||
|
||||
@app.command("seed-personas")
|
||||
def seed_personas_cmd() -> None:
|
||||
"""Push assistent-personas.yaml → VM Assistent overlay (без полного up)."""
|
||||
"""Push assistent-personas/ → VM Assistent overlay (без полного up)."""
|
||||
try:
|
||||
from gpu_rent.provision import seed_assistent_personas
|
||||
|
||||
|
||||
@@ -48,11 +48,22 @@ def models_manifest_path() -> Path:
|
||||
return app_root() / "models.yaml"
|
||||
|
||||
|
||||
def assistent_personas_dir() -> Path:
|
||||
"""Working overlay source on the laptop (gitignored)."""
|
||||
return app_root() / "assistent-personas"
|
||||
|
||||
|
||||
def assistent_personas_example_dir() -> Path:
|
||||
return app_root() / "assistent-personas.example"
|
||||
|
||||
|
||||
def assistent_personas_manifest_path() -> Path:
|
||||
"""Deprecated yaml path — kept for migration warning only."""
|
||||
return app_root() / "assistent-personas.yaml"
|
||||
|
||||
|
||||
def assistent_personas_example_path() -> Path:
|
||||
"""Deprecated yaml example — prefer assistent_personas_example_dir()."""
|
||||
return app_root() / "assistent-personas.example.yaml"
|
||||
|
||||
|
||||
|
||||
+74
-50
@@ -752,71 +752,95 @@ def _overlay_num_ctx(cfg: Config, host: str) -> int | None:
|
||||
|
||||
|
||||
def seed_assistent_personas(cfg: Config, host: str, log: Log) -> None:
|
||||
"""Push local assistent-personas.yaml → VM Assistent/personas/<id>/ overlay.
|
||||
"""Push local assistent-personas/ folder → VM Assistent/ overlay.
|
||||
|
||||
Laptop yaml stays the editor; on VM we write persona.json + extra.md and
|
||||
overlay assistant.json (default_persona, optional num_ctx). Does not clobber
|
||||
voice/likes/dislikes/rules already on disk. No longer writes legacy
|
||||
personas.json / personas.yaml dumps.
|
||||
Copies personas/<id>/*.json (+ optional extra.md) and sparse _base/assistant.json
|
||||
(default_persona + optional num_ctx). Does not delete overlay personas missing
|
||||
from the laptop (VM-created clones stay). Does not clobber exact.controls.
|
||||
Legacy assistent-personas.yaml is no longer read.
|
||||
"""
|
||||
from gpu_rent.paths import assistent_personas_example_path, assistent_personas_manifest_path
|
||||
from gpu_rent.paths import (
|
||||
assistent_personas_dir,
|
||||
assistent_personas_example_dir,
|
||||
assistent_personas_manifest_path,
|
||||
)
|
||||
|
||||
local = Path(
|
||||
(getattr(cfg, "assistent_personas_manifest", None) or assistent_personas_manifest_path())
|
||||
(getattr(cfg, "assistent_personas_dir", None) or assistent_personas_dir())
|
||||
)
|
||||
if not local.is_file():
|
||||
example = assistent_personas_example_path()
|
||||
if example.is_file():
|
||||
if not local.is_dir():
|
||||
example = assistent_personas_example_dir()
|
||||
if example.is_dir():
|
||||
local = example
|
||||
else:
|
||||
log("assistent-personas: нет yaml — skip")
|
||||
legacy = assistent_personas_manifest_path()
|
||||
if legacy.is_file():
|
||||
log(
|
||||
"assistent-personas: yaml больше не читается — "
|
||||
"скопируй полки в assistent-personas/ (см. assistent-personas.example/)"
|
||||
)
|
||||
else:
|
||||
log("assistent-personas: нет папки — skip (bundled из расширения)")
|
||||
return
|
||||
text = local.read_text(encoding="utf-8")
|
||||
if not text.strip():
|
||||
log("assistent-personas: пустой файл — skip")
|
||||
return
|
||||
try:
|
||||
import yaml
|
||||
|
||||
data = yaml.safe_load(text) or {}
|
||||
except Exception as exc:
|
||||
log(f"assistent-personas: yaml parse — {exc}")
|
||||
return
|
||||
if not isinstance(data, dict):
|
||||
log("assistent-personas: корень должен быть mapping — skip")
|
||||
return
|
||||
|
||||
remote_dir = f"{DATA}/Assistent"
|
||||
run_ssh(cfg, host, f"mkdir -p {shlex.quote(remote_dir)}/personas {shlex.quote(remote_dir)}/_base", check=False)
|
||||
run_ssh(
|
||||
cfg,
|
||||
host,
|
||||
f"mkdir -p {shlex.quote(remote_dir)}/personas {shlex.quote(remote_dir)}/_base",
|
||||
check=False,
|
||||
)
|
||||
|
||||
default_id = _safe_persona_id(data.get("default")) or "neutral"
|
||||
personas = data.get("personas") or []
|
||||
default_id = "neutral"
|
||||
base_asst = local / "_base" / "assistant.json"
|
||||
if base_asst.is_file():
|
||||
try:
|
||||
data = json.loads(base_asst.read_text(encoding="utf-8"))
|
||||
if isinstance(data, dict):
|
||||
default_id = _safe_persona_id(data.get("default_persona")) or default_id
|
||||
except (json.JSONDecodeError, OSError) as exc:
|
||||
log(f"assistent-personas: _base/assistant.json — {exc}")
|
||||
|
||||
personas_root = local / "personas"
|
||||
written = 0
|
||||
if isinstance(personas, list):
|
||||
for row in personas:
|
||||
if not isinstance(row, dict):
|
||||
if personas_root.is_dir():
|
||||
for pdir in sorted(personas_root.iterdir()):
|
||||
if not pdir.is_dir():
|
||||
continue
|
||||
pid = _safe_persona_id(row.get("id"))
|
||||
pid = _safe_persona_id(pdir.name)
|
||||
if not pid:
|
||||
continue
|
||||
title = str(row.get("title") or pid).strip() or pid
|
||||
prompt = str(row.get("prompt") or "").strip()
|
||||
pdir = f"{remote_dir}/personas/{pid}"
|
||||
run_ssh(cfg, host, f"mkdir -p {shlex.quote(pdir)}", check=False)
|
||||
meta = {"title": title, "tagline": title, "accent": "#8b949e"}
|
||||
put_text(
|
||||
cfg,
|
||||
host,
|
||||
f"{pdir}/persona.json",
|
||||
json.dumps(meta, ensure_ascii=False, indent=2) + "\n",
|
||||
)
|
||||
if prompt:
|
||||
put_text(
|
||||
cfg,
|
||||
host,
|
||||
f"{pdir}/extra.md",
|
||||
prompt if prompt.endswith("\n") else prompt + "\n",
|
||||
)
|
||||
remote_p = f"{remote_dir}/personas/{pid}"
|
||||
run_ssh(cfg, host, f"mkdir -p {shlex.quote(remote_p)}", check=False)
|
||||
for f in sorted(pdir.iterdir()):
|
||||
if not f.is_file():
|
||||
continue
|
||||
name = f.name
|
||||
if name.endswith(".json") or name == "extra.md":
|
||||
if ".." in name or "/" in name or "\\" in name:
|
||||
continue
|
||||
# Never overwrite exact.json controls from laptop if remote has
|
||||
# user-tuned values — merge only when seeding a missing exact,
|
||||
# or push non-controls shelves always. For simplicity: push all
|
||||
# shelves except skip exact.json when remote already has it
|
||||
# (preserves preference_bias). First-time still seeds exact.
|
||||
remote_f = f"{remote_p}/{name}"
|
||||
if name == "exact.json":
|
||||
exists = run_ssh(
|
||||
cfg,
|
||||
host,
|
||||
f"test -f {shlex.quote(remote_f)} && echo yes || true",
|
||||
check=False,
|
||||
timeout=15,
|
||||
).strip()
|
||||
if exists == "yes":
|
||||
continue
|
||||
put_text(
|
||||
cfg,
|
||||
host,
|
||||
remote_f,
|
||||
f.read_text(encoding="utf-8"),
|
||||
)
|
||||
written += 1
|
||||
|
||||
assistant_overlay: dict = {"default_persona": default_id}
|
||||
|
||||
@@ -16,8 +16,8 @@ from gpu_rent.llm_runtime import (
|
||||
)
|
||||
from gpu_rent.paths import (
|
||||
app_root,
|
||||
assistent_personas_example_path,
|
||||
assistent_personas_manifest_path,
|
||||
assistent_personas_dir,
|
||||
assistent_personas_example_dir,
|
||||
env_path,
|
||||
extensions_manifest_path,
|
||||
models_manifest_path,
|
||||
@@ -42,6 +42,17 @@ def _copy_if_missing(src: Path, dst: Path, label: str, log: Log) -> None:
|
||||
log(f"нет example для {label}: {src}")
|
||||
|
||||
|
||||
def _copy_tree_if_missing(src: Path, dst: Path, label: str, log: Log) -> None:
|
||||
if dst.exists():
|
||||
log(f"есть {label}")
|
||||
return
|
||||
if src.is_dir():
|
||||
shutil.copytree(src, dst)
|
||||
log(f"создал {label} из example")
|
||||
else:
|
||||
log(f"нет example для {label}: {src}")
|
||||
|
||||
|
||||
def run_setup(
|
||||
*,
|
||||
llm: str | None = None,
|
||||
@@ -56,10 +67,10 @@ def run_setup(
|
||||
|
||||
_copy_if_missing(root / "env.example", env_path(), ".env", log)
|
||||
_copy_if_missing(root / "models.example.yaml", models_manifest_path(), "models.yaml", log)
|
||||
_copy_if_missing(
|
||||
assistent_personas_example_path(),
|
||||
assistent_personas_manifest_path(),
|
||||
"assistent-personas.yaml",
|
||||
_copy_tree_if_missing(
|
||||
assistent_personas_example_dir(),
|
||||
assistent_personas_dir(),
|
||||
"assistent-personas/",
|
||||
log,
|
||||
)
|
||||
_copy_if_missing(
|
||||
|
||||
Reference in New Issue
Block a user