116 lines
3.8 KiB
Python
116 lines
3.8 KiB
Python
"""Trusted bridge from Agent PreToolUse package validation to native SubagentStart.
|
|
|
|
Native SubagentStart payloads may omit the spawning prompt. PreToolUse therefore appends a
|
|
pending exact package binding; SubagentStart claims the oldest unclaimed binding for the same
|
|
concrete agent type. The log is append-only and protected by guard_tools.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import os
|
|
import uuid
|
|
from datetime import datetime, timezone
|
|
|
|
import _workspace as W
|
|
|
|
|
|
def _path() -> str:
|
|
return os.path.join(W.state_dir(), "spawn-bindings.jsonl")
|
|
|
|
|
|
def _rows() -> list[dict]:
|
|
path = _path()
|
|
if not os.path.exists(path):
|
|
return []
|
|
rows = []
|
|
for line in open(path, encoding="utf-8"):
|
|
try:
|
|
value = json.loads(line)
|
|
if isinstance(value, dict):
|
|
rows.append(value)
|
|
except Exception:
|
|
continue
|
|
return rows
|
|
|
|
|
|
def _append(record: dict) -> None:
|
|
path = _path()
|
|
os.makedirs(os.path.dirname(path), exist_ok=True)
|
|
with open(path, "a", encoding="utf-8") as handle:
|
|
try:
|
|
import fcntl
|
|
fcntl.flock(handle.fileno(), fcntl.LOCK_EX)
|
|
except Exception:
|
|
pass
|
|
handle.write(json.dumps(record, ensure_ascii=False) + "\n")
|
|
handle.flush()
|
|
os.fsync(handle.fileno())
|
|
|
|
|
|
def record_pending(
|
|
agent_type: str,
|
|
package_path: str,
|
|
package_sha256: str,
|
|
session_id: str | None = None,
|
|
) -> str | None:
|
|
try:
|
|
binding_id = "spb-" + uuid.uuid4().hex
|
|
_append({
|
|
"event-type": "spawn-binding-pending",
|
|
"binding-id": binding_id,
|
|
"agent-type": str(agent_type).lower(),
|
|
"context-package": package_path,
|
|
"context-package-sha256": package_sha256,
|
|
"session-id": session_id,
|
|
"at": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"),
|
|
})
|
|
return binding_id
|
|
except Exception:
|
|
return None
|
|
|
|
|
|
def claim_pending(agent_type: str, agent_id: str, session_id: str | None = None) -> dict | None:
|
|
try:
|
|
path = _path()
|
|
os.makedirs(os.path.dirname(path), exist_ok=True)
|
|
with open(path, "a+", encoding="utf-8") as handle:
|
|
try:
|
|
import fcntl
|
|
fcntl.flock(handle.fileno(), fcntl.LOCK_EX)
|
|
except Exception:
|
|
pass
|
|
handle.seek(0)
|
|
rows = []
|
|
for line in handle:
|
|
try:
|
|
value = json.loads(line)
|
|
if isinstance(value, dict):
|
|
rows.append(value)
|
|
except Exception:
|
|
continue
|
|
consumed = {row.get("binding-id") for row in rows
|
|
if row.get("event-type") == "spawn-binding-claimed"}
|
|
pending = [row for row in rows
|
|
if row.get("event-type") == "spawn-binding-pending"
|
|
and row.get("agent-type") == str(agent_type).lower()
|
|
and row.get("binding-id") not in consumed
|
|
and (not session_id or not row.get("session-id")
|
|
or row.get("session-id") == session_id)]
|
|
if not pending:
|
|
return None
|
|
record = pending[0]
|
|
claimed = {
|
|
"event-type": "spawn-binding-claimed",
|
|
"binding-id": record["binding-id"],
|
|
"agent-id": agent_id,
|
|
"session-id": session_id,
|
|
"at": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"),
|
|
}
|
|
handle.seek(0, os.SEEK_END)
|
|
handle.write(json.dumps(claimed, ensure_ascii=False) + "\n")
|
|
handle.flush()
|
|
os.fsync(handle.fileno())
|
|
return record
|
|
except Exception:
|
|
return None
|