feat: release 2.3.0 with workspace plugin extensions
This commit is contained in:
1 parent
8380057548
commit
bb873ec0e0
44 files changed
+1497
-48
No files matched your search
@@ -0,0 +1,162 @@
|
||||
"""Human-authored task metadata owned by the host, scoped to installed plugins."""
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
import time
|
||||
from functools import lru_cache
|
||||
|
||||
from apps.admin_console.services.automation_store import AutomationStore, identifier
|
||||
from apps.admin_console.database.repositories.session_repository import SessionRepository
|
||||
from artemis.plugins.models import PluginManifest
|
||||
from artemis.plugins.store import PluginStore
|
||||
|
||||
TERMINAL = {"completed", "success", "failed", "cancelled", "interrupted", "skipped"}
|
||||
|
||||
|
||||
@lru_cache(maxsize=1)
|
||||
def plugin_store() -> PluginStore:
|
||||
return PluginStore()
|
||||
|
||||
|
||||
def active_manifests() -> list[PluginManifest]:
|
||||
return [
|
||||
PluginManifest.model_validate(plugin["manifest"])
|
||||
for plugin in plugin_store().list()
|
||||
if plugin["enabled"] and plugin["manifest"].get("workspace")
|
||||
]
|
||||
|
||||
|
||||
def installation_signature() -> str:
|
||||
manifests = [manifest.model_dump(mode="json") for manifest in active_manifests()]
|
||||
return (
|
||||
hashlib.sha256(json.dumps(manifests, sort_keys=True).encode()).hexdigest()
|
||||
if manifests
|
||||
else ""
|
||||
)
|
||||
|
||||
|
||||
def task_context(session_id: str, store: AutomationStore) -> dict:
|
||||
identifier(session_id)
|
||||
session = SessionRepository(store.path).get_session_by_id(session_id) or {}
|
||||
job = store.job(session_id) or {}
|
||||
if not session and not job:
|
||||
raise ValueError("任务记录不存在")
|
||||
return {
|
||||
"session_id": session_id,
|
||||
"initial_prompt": session.get("initial_goal") or (job.get("case") or {}).get("goal", ""),
|
||||
"editable": (job.get("status") or session.get("status")) in TERMINAL,
|
||||
}
|
||||
|
||||
|
||||
def contributions(session_id: str, store: AutomationStore) -> list[dict]:
|
||||
record = store.get("task_extensions", session_id) or {}
|
||||
result = []
|
||||
for manifest in active_manifests():
|
||||
assert manifest.workspace is not None
|
||||
saved = record.get(manifest.id) or {}
|
||||
values = saved.get("values", {})
|
||||
result.append(
|
||||
{
|
||||
"plugin_id": manifest.id,
|
||||
"name": manifest.name,
|
||||
"version": manifest.version,
|
||||
"workspace": manifest.workspace.model_dump(),
|
||||
"revision": saved.get("revision", 0),
|
||||
"values": {
|
||||
field.id: values.get(field.id, "") for field in manifest.workspace.fields
|
||||
},
|
||||
"history": saved.get("history", []),
|
||||
}
|
||||
)
|
||||
return result
|
||||
|
||||
|
||||
def report_sections(session_id: str, store: AutomationStore) -> list[dict]:
|
||||
return [
|
||||
{
|
||||
"plugin_id": item["plugin_id"],
|
||||
"name": item["name"],
|
||||
"version": item["version"],
|
||||
"revision": item["revision"],
|
||||
"fields": [
|
||||
{"id": field["id"], "label": field["label"], "value": item["values"][field["id"]]}
|
||||
for field in item["workspace"]["fields"]
|
||||
if field["report"]
|
||||
],
|
||||
}
|
||||
for item in contributions(session_id, store)
|
||||
if any(field["report"] for field in item["workspace"]["fields"])
|
||||
]
|
||||
|
||||
|
||||
def save_values(
|
||||
store: AutomationStore,
|
||||
session_id: str,
|
||||
manifest: PluginManifest,
|
||||
values: dict[str, str],
|
||||
expected_revision: int,
|
||||
request_id: str,
|
||||
) -> None:
|
||||
"""CAS + request receipts prevent lost edits and repeated history entries."""
|
||||
identifier(session_id)
|
||||
assert manifest.workspace is not None
|
||||
fields = {field.id: field for field in manifest.workspace.fields}
|
||||
if values.keys() - fields.keys():
|
||||
raise ValueError("包含未声明的扩展字段")
|
||||
for key, value in values.items():
|
||||
field = fields[key]
|
||||
if len(value) > field.max_length or (
|
||||
field.kind == "choice" and value and value not in field.options
|
||||
):
|
||||
raise ValueError(f"字段 {field.label} 的内容不符合插件声明")
|
||||
now = time.time()
|
||||
with store.connection() as conn:
|
||||
conn.execute("BEGIN IMMEDIATE")
|
||||
row = conn.execute(
|
||||
"SELECT payload FROM automation_records WHERE kind='task_extensions' AND id=?",
|
||||
(session_id,),
|
||||
).fetchone()
|
||||
record = json.loads(row[0]) if row else {}
|
||||
saved = record.get(manifest.id, {"revision": 0, "values": {}, "history": []})
|
||||
for entry in saved["history"]:
|
||||
if entry["request_id"] == request_id:
|
||||
if entry["patch"] != values:
|
||||
raise ValueError("同一保存请求不能提交不同内容")
|
||||
return
|
||||
if saved["revision"] != expected_revision:
|
||||
raise ValueError("评审已在其他页面修改,请重新加载后保存")
|
||||
updated = {**saved["values"], **values}
|
||||
revision = saved["revision"] + 1
|
||||
saved["history"].append(
|
||||
{
|
||||
"revision": revision,
|
||||
"saved_at": now,
|
||||
"request_id": request_id,
|
||||
"patch": values,
|
||||
"values": updated,
|
||||
"plugin_version": manifest.version,
|
||||
"fields": [field.model_dump() for field in manifest.workspace.fields],
|
||||
}
|
||||
)
|
||||
record[manifest.id] = {**saved, "revision": revision, "values": updated}
|
||||
conn.execute(
|
||||
"INSERT INTO automation_records VALUES ('task_extensions',?,?,?) "
|
||||
"ON CONFLICT(kind,id) DO UPDATE SET payload=excluded.payload,updated_at=excluded.updated_at",
|
||||
(session_id, json.dumps(record, ensure_ascii=False), now),
|
||||
)
|
||||
job = conn.execute(
|
||||
"SELECT batch_id FROM automation_jobs WHERE session_id=?", (session_id,)
|
||||
).fetchone()
|
||||
if job and job[0]:
|
||||
batch = conn.execute(
|
||||
"SELECT payload FROM automation_records WHERE kind='batch' AND id=?", (job[0],)
|
||||
).fetchone()
|
||||
if not batch or json.loads(batch[0]).get("status") == "deleting":
|
||||
raise ValueError("批次正在删除,不能保存评审")
|
||||
payload = json.loads(batch[0])
|
||||
payload["report_ready"] = False
|
||||
conn.execute(
|
||||
"UPDATE automation_records SET payload=?,updated_at=? WHERE kind='batch' AND id=?",
|
||||
(json.dumps(payload, ensure_ascii=False), now, job[0]),
|
||||
)
|
||||
conn.commit()
|
||||
Reference in new issue
Block a user