88from pathlib import Path
99from typing import Any , Dict , List , Optional
1010
11- from .storage import ScriptStore , TrajectoryStore , SessionStore , DFUStore , SessionRuntimeStore
11+ from .storage import ScriptStore , TrajectoryStore , SessionStore , DFUStore , SessionRuntimeStore , ScriptBindingStore
1212
1313_RUNTIME_UNSET = object ()
1414
@@ -23,7 +23,7 @@ def _json_deserializer(s: Optional[str]) -> Any:
2323 return json .loads (s )
2424
2525
26- class SQLiteStorage (ScriptStore , TrajectoryStore , SessionStore , DFUStore , SessionRuntimeStore ):
26+ class SQLiteStorage (ScriptStore , TrajectoryStore , SessionStore , DFUStore , SessionRuntimeStore , ScriptBindingStore ):
2727 """Single SQLite-backed storage for scripts, trajectories, and sessions."""
2828
2929 def __init__ (self , db_path : str = "zerotoken.db" ):
@@ -110,6 +110,16 @@ def _ensure_tables(self) -> None:
110110 updated_at TEXT NOT NULL
111111 )
112112 """ )
113+ cur .execute ("""
114+ CREATE TABLE IF NOT EXISTS script_bindings (
115+ binding_key TEXT PRIMARY KEY,
116+ script_task_id TEXT NOT NULL,
117+ description TEXT,
118+ default_vars_json TEXT,
119+ created_at TEXT NOT NULL,
120+ updated_at TEXT NOT NULL
121+ )
122+ """ )
113123 self .conn .commit ()
114124
115125 # --- ScriptStore ---
@@ -147,6 +157,78 @@ def script_save(
147157 )
148158 self .conn .commit ()
149159
160+ # --- ScriptBindingStore ---
161+ def script_binding_set (
162+ self ,
163+ binding_key : str ,
164+ * ,
165+ script_task_id : str ,
166+ description : str = "" ,
167+ default_vars : Optional [Dict [str , Any ]] = None ,
168+ ) -> None :
169+ now = datetime .utcnow ().isoformat () + "Z"
170+ cur = self .conn .cursor ()
171+ cur .execute (
172+ """
173+ INSERT INTO script_bindings (binding_key, script_task_id, description, default_vars_json, created_at, updated_at)
174+ VALUES (?, ?, ?, ?, ?, ?)
175+ ON CONFLICT(binding_key) DO UPDATE SET
176+ script_task_id = excluded.script_task_id,
177+ description = excluded.description,
178+ default_vars_json = excluded.default_vars_json,
179+ updated_at = excluded.updated_at
180+ """ ,
181+ (
182+ binding_key ,
183+ script_task_id ,
184+ description ,
185+ _json_serializer (default_vars or {}),
186+ now ,
187+ now ,
188+ ),
189+ )
190+ self .conn .commit ()
191+
192+ def script_binding_get (self , binding_key : str ) -> Optional [Dict [str , Any ]]:
193+ cur = self .conn .cursor ()
194+ cur .execute (
195+ "SELECT binding_key, script_task_id, description, default_vars_json, created_at, updated_at FROM script_bindings WHERE binding_key = ?" ,
196+ (binding_key ,),
197+ )
198+ row = cur .fetchone ()
199+ if row is None :
200+ return None
201+ return {
202+ "binding_key" : row ["binding_key" ],
203+ "script_task_id" : row ["script_task_id" ],
204+ "description" : row ["description" ] or "" ,
205+ "default_vars" : _json_deserializer (row ["default_vars_json" ]) or {},
206+ "created_at" : row ["created_at" ],
207+ "updated_at" : row ["updated_at" ],
208+ }
209+
210+ def script_binding_list (self , limit : int = 100 ) -> List [Dict [str , Any ]]:
211+ cur = self .conn .cursor ()
212+ cur .execute (
213+ "SELECT binding_key, script_task_id, description, updated_at FROM script_bindings ORDER BY updated_at DESC LIMIT ?" ,
214+ (limit ,),
215+ )
216+ return [
217+ {
218+ "binding_key" : r ["binding_key" ],
219+ "script_task_id" : r ["script_task_id" ],
220+ "description" : r ["description" ] or "" ,
221+ "updated_at" : r ["updated_at" ],
222+ }
223+ for r in cur .fetchall ()
224+ ]
225+
226+ def script_binding_delete (self , binding_key : str ) -> bool :
227+ cur = self .conn .cursor ()
228+ cur .execute ("DELETE FROM script_bindings WHERE binding_key = ?" , (binding_key ,))
229+ self .conn .commit ()
230+ return cur .rowcount > 0
231+
150232 def script_load (self , task_id : str ) -> Optional [Dict [str , Any ]]:
151233 cur = self .conn .cursor ()
152234 cur .execute (
0 commit comments