collab-runtime 0.6.1__tar.gz → 0.6.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {collab_runtime-0.6.1/collab_runtime.egg-info → collab_runtime-0.6.2}/PKG-INFO +1 -1
- {collab_runtime-0.6.1 → collab_runtime-0.6.2}/collab/lock_client.py +39 -7
- {collab_runtime-0.6.1 → collab_runtime-0.6.2/collab_runtime.egg-info}/PKG-INFO +1 -1
- {collab_runtime-0.6.1 → collab_runtime-0.6.2}/pyproject.toml +1 -1
- {collab_runtime-0.6.1 → collab_runtime-0.6.2}/LICENSE +0 -0
- {collab_runtime-0.6.1 → collab_runtime-0.6.2}/README.md +0 -0
- {collab_runtime-0.6.1 → collab_runtime-0.6.2}/collab/__init__.py +0 -0
- {collab_runtime-0.6.1 → collab_runtime-0.6.2}/collab/__main__.py +0 -0
- {collab_runtime-0.6.1 → collab_runtime-0.6.2}/collab/agent_hooks.py +0 -0
- {collab_runtime-0.6.1 → collab_runtime-0.6.2}/collab/agent_identity.py +0 -0
- {collab_runtime-0.6.1 → collab_runtime-0.6.2}/collab/dashboard/dashboard-format.js +0 -0
- {collab_runtime-0.6.1 → collab_runtime-0.6.2}/collab/dashboard/index.html +0 -0
- {collab_runtime-0.6.1 → collab_runtime-0.6.2}/collab/dashboard_server.py +0 -0
- {collab_runtime-0.6.1 → collab_runtime-0.6.2}/collab/errors.py +0 -0
- {collab_runtime-0.6.1 → collab_runtime-0.6.2}/collab/githooks.py +0 -0
- {collab_runtime-0.6.1 → collab_runtime-0.6.2}/collab/hook_templates/commit-msg +0 -0
- {collab_runtime-0.6.1 → collab_runtime-0.6.2}/collab/hook_templates/post-commit +0 -0
- {collab_runtime-0.6.1 → collab_runtime-0.6.2}/collab/hook_templates/pre-commit +0 -0
- {collab_runtime-0.6.1 → collab_runtime-0.6.2}/collab/hook_templates/pre-push +0 -0
- {collab_runtime-0.6.1 → collab_runtime-0.6.2}/collab/live_locks_watcher.py +0 -0
- {collab_runtime-0.6.1 → collab_runtime-0.6.2}/collab/logging_config.py +0 -0
- {collab_runtime-0.6.1 → collab_runtime-0.6.2}/collab/main.py +0 -0
- {collab_runtime-0.6.1 → collab_runtime-0.6.2}/collab/platform_probe.py +0 -0
- {collab_runtime-0.6.1 → collab_runtime-0.6.2}/collab/safe_subprocess.py +0 -0
- {collab_runtime-0.6.1 → collab_runtime-0.6.2}/collab/subprocess_bridge.py +0 -0
- {collab_runtime-0.6.1 → collab_runtime-0.6.2}/collab_runtime.egg-info/SOURCES.txt +0 -0
- {collab_runtime-0.6.1 → collab_runtime-0.6.2}/collab_runtime.egg-info/dependency_links.txt +0 -0
- {collab_runtime-0.6.1 → collab_runtime-0.6.2}/collab_runtime.egg-info/entry_points.txt +0 -0
- {collab_runtime-0.6.1 → collab_runtime-0.6.2}/collab_runtime.egg-info/requires.txt +0 -0
- {collab_runtime-0.6.1 → collab_runtime-0.6.2}/collab_runtime.egg-info/top_level.txt +0 -0
- {collab_runtime-0.6.1 → collab_runtime-0.6.2}/docs/pypi/README.md +0 -0
- {collab_runtime-0.6.1 → collab_runtime-0.6.2}/setup.cfg +0 -0
|
@@ -1118,6 +1118,11 @@ class LockClient:
|
|
|
1118
1118
|
def release(self, file_path: str) -> Tuple[bool, str]:
|
|
1119
1119
|
"""Release a lock on file_path owned by this developer.
|
|
1120
1120
|
|
|
1121
|
+
When called by a **human** (``agent_id`` is ``None``), any lock owned by this
|
|
1122
|
+
``developer_id`` is released regardless of which agent claimed it — the human is
|
|
1123
|
+
in charge of all their agents. When called by an **agent** (``agent_id`` is
|
|
1124
|
+
set), only locks claimed by that specific agent identity are released.
|
|
1125
|
+
|
|
1121
1126
|
Returns (success: bool, message: str).
|
|
1122
1127
|
"""
|
|
1123
1128
|
# If ephemeral, nothing was persisted so there's nothing to delete.
|
|
@@ -1127,16 +1132,45 @@ class LockClient:
|
|
|
1127
1132
|
)
|
|
1128
1133
|
return True, "ephemeral-released"
|
|
1129
1134
|
|
|
1135
|
+
norm = self._normalize_file_path(file_path)
|
|
1130
1136
|
client = self._require_client()
|
|
1137
|
+
|
|
1138
|
+
# Pre-check: verify a lock row exists for this file *and* belongs to
|
|
1139
|
+
# this developer before attempting the DELETE. PostgREST returns
|
|
1140
|
+
# 204 No Content even when zero rows are deleted, so without this
|
|
1141
|
+
# guard the CLI would falsely report "✓ released" for locks that
|
|
1142
|
+
# belong to another developer or do not exist at all.
|
|
1143
|
+
try:
|
|
1144
|
+
check_res = _retry_on_network_error(
|
|
1145
|
+
lambda: client.table("file_locks")
|
|
1146
|
+
.select("developer_id")
|
|
1147
|
+
.eq("file_path", norm)
|
|
1148
|
+
.execute()
|
|
1149
|
+
)
|
|
1150
|
+
except Exception as e:
|
|
1151
|
+
return False, f"API Error: {e}"
|
|
1152
|
+
_st, rows, _err = self._parse_response(check_res)
|
|
1153
|
+
if not rows or not isinstance(rows, list) or len(rows) == 0:
|
|
1154
|
+
return False, f"No lock found for: {file_path}"
|
|
1155
|
+
lock_owner = rows[0].get("developer_id")
|
|
1156
|
+
if lock_owner != self.developer_id:
|
|
1157
|
+
return False, (
|
|
1158
|
+
f"Permission denied: {file_path} is locked by @{lock_owner or '?'}. "
|
|
1159
|
+
"Use `collab force-release` if you have admin credentials."
|
|
1160
|
+
)
|
|
1161
|
+
|
|
1131
1162
|
try:
|
|
1132
|
-
norm = self._normalize_file_path(file_path)
|
|
1133
1163
|
delete_query = (
|
|
1134
1164
|
client.table("file_locks")
|
|
1135
1165
|
.delete()
|
|
1136
1166
|
.eq("file_path", norm)
|
|
1137
1167
|
.eq("developer_id", self.developer_id)
|
|
1138
1168
|
)
|
|
1139
|
-
|
|
1169
|
+
# Human (agent_id is None): developer-scoped — release any agent's
|
|
1170
|
+
# lock. Agent (agent_id is set): identity-scoped — only release
|
|
1171
|
+
# this specific agent's lock.
|
|
1172
|
+
if self.agent_id is not None:
|
|
1173
|
+
delete_query = delete_query.eq("agent_id", self.agent_id)
|
|
1140
1174
|
res = _retry_on_network_error(lambda: delete_query.execute())
|
|
1141
1175
|
except Exception as e:
|
|
1142
1176
|
return False, f"API Error: {e}"
|
|
@@ -1145,9 +1179,7 @@ class LockClient:
|
|
|
1145
1179
|
if error:
|
|
1146
1180
|
return False, f"API Error: {error}"
|
|
1147
1181
|
if status in (200, 204) or data is not None:
|
|
1148
|
-
logger.info(
|
|
1149
|
-
"🔓 [RELEASED] %s — lock released", self._normalize_file_path(file_path)
|
|
1150
|
-
)
|
|
1182
|
+
logger.info("🔓 [RELEASED] %s — lock released", norm)
|
|
1151
1183
|
return True, "released"
|
|
1152
1184
|
return False, "No lock released (not owner or lock does not exist)"
|
|
1153
1185
|
|
|
@@ -2008,7 +2040,7 @@ class LockClient:
|
|
|
2008
2040
|
try:
|
|
2009
2041
|
pid = self._read_pid(strict=True)
|
|
2010
2042
|
except PidParseError as exc:
|
|
2011
|
-
print(f"
|
|
2043
|
+
print(f"ℹ️ Lock watcher status unavailable: {exc.message}")
|
|
2012
2044
|
return False
|
|
2013
2045
|
local_only_mode = bool(getattr(self, "local_only", False))
|
|
2014
2046
|
if pid and self._is_process_alive(pid):
|
|
@@ -2137,7 +2169,7 @@ class LockClient:
|
|
|
2137
2169
|
return True
|
|
2138
2170
|
except (ValueError, OSError):
|
|
2139
2171
|
pass
|
|
2140
|
-
print("
|
|
2172
|
+
print("ℹ️ Lock watcher is not running.")
|
|
2141
2173
|
return False
|
|
2142
2174
|
|
|
2143
2175
|
def cleanup_orphaned_processes(self) -> None:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|