comfygit-studio 0.7.0__tar.gz → 0.7.1__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.
Files changed (17) hide show
  1. {comfygit_studio-0.7.0 → comfygit_studio-0.7.1}/PKG-INFO +2 -2
  2. {comfygit_studio-0.7.0 → comfygit_studio-0.7.1}/comfygit_studio/api_schema.py +26 -2
  3. {comfygit_studio-0.7.0 → comfygit_studio-0.7.1}/comfygit_studio/openapi/studio-contract-api.v1.json +213 -0
  4. {comfygit_studio-0.7.0 → comfygit_studio-0.7.1}/comfygit_studio/runtime.py +89 -15
  5. {comfygit_studio-0.7.0 → comfygit_studio-0.7.1}/pyproject.toml +2 -2
  6. {comfygit_studio-0.7.0 → comfygit_studio-0.7.1}/.gitignore +0 -0
  7. {comfygit_studio-0.7.0 → comfygit_studio-0.7.1}/README.md +0 -0
  8. {comfygit_studio-0.7.0 → comfygit_studio-0.7.1}/comfygit_studio/__init__.py +0 -0
  9. {comfygit_studio-0.7.0 → comfygit_studio-0.7.1}/comfygit_studio/embedded.py +0 -0
  10. {comfygit_studio-0.7.0 → comfygit_studio-0.7.1}/comfygit_studio/executor.py +0 -0
  11. {comfygit_studio-0.7.0 → comfygit_studio-0.7.1}/comfygit_studio/state.py +0 -0
  12. {comfygit_studio-0.7.0 → comfygit_studio-0.7.1}/comfygit_studio/static/assets/geist-cyrillic-wght-normal-CHSlOQsW.woff2 +0 -0
  13. {comfygit_studio-0.7.0 → comfygit_studio-0.7.1}/comfygit_studio/static/assets/geist-latin-ext-wght-normal-DMtmJ5ZE.woff2 +0 -0
  14. {comfygit_studio-0.7.0 → comfygit_studio-0.7.1}/comfygit_studio/static/assets/geist-latin-wght-normal-Dm3htQBi.woff2 +0 -0
  15. {comfygit_studio-0.7.0 → comfygit_studio-0.7.1}/comfygit_studio/static/assets/index-BDmIh9tA.css +0 -0
  16. {comfygit_studio-0.7.0 → comfygit_studio-0.7.1}/comfygit_studio/static/assets/index-ry5Db1EL.js +0 -0
  17. {comfygit_studio-0.7.0 → comfygit_studio-0.7.1}/comfygit_studio/static/index.html +0 -0
@@ -1,13 +1,13 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: comfygit-studio
3
- Version: 0.7.0
3
+ Version: 0.7.1
4
4
  Summary: ComfyGit Studio runtime - contract API and Studio serving adapters
5
5
  Project-URL: Documentation, https://docs.comfygit.org/
6
6
  Project-URL: Repository, https://github.com/comfygit-ai/comfygit
7
7
  Project-URL: Issues, https://github.com/comfygit-ai/comfygit/issues
8
8
  Requires-Python: >=3.10
9
9
  Requires-Dist: aiohttp>=3.14.3
10
- Requires-Dist: comfygit-core==0.7.0
10
+ Requires-Dist: comfygit-core==0.7.1
11
11
  Description-Content-Type: text/markdown
12
12
 
13
13
  # ComfyGit Studio Runtime
@@ -82,7 +82,7 @@ def _paths() -> dict[str, Any]:
82
82
  "get": {
83
83
  "summary": "List available workflow contracts",
84
84
  "operationId": "listContracts",
85
- "responses": _json_response("Contracts response", "ContractsResponse"),
85
+ "responses": {**_json_response("Contracts response", "ContractsResponse"), "503": _busy_response()},
86
86
  }
87
87
  },
88
88
  "/contracts/{workflow}/{contract}": {
@@ -93,7 +93,7 @@ def _paths() -> dict[str, Any]:
93
93
  {"$ref": "#/components/parameters/workflow"},
94
94
  {"$ref": "#/components/parameters/contract"},
95
95
  ],
96
- "responses": _json_response("Contract response", "ContractSummary"),
96
+ "responses": {**_json_response("Contract response", "ContractSummary"), "503": _busy_response()},
97
97
  }
98
98
  },
99
99
  "/uploads/prepare": {
@@ -152,6 +152,7 @@ def _paths() -> dict[str, Any]:
152
152
  "400": _json_content("Invalid request", "RunResponse"),
153
153
  "413": _json_content("Request too large", "ErrorResponse"),
154
154
  "502": _json_content("Executor unavailable", "RunResponse"),
155
+ "503": _busy_response(),
155
156
  "504": _json_content("Run timed out", "RunResponse"),
156
157
  },
157
158
  }
@@ -289,6 +290,7 @@ def _schemas() -> dict[str, Any]:
289
290
  "type": "object",
290
291
  "required": ["workflow", "contract", "inputs", "outputs"],
291
292
  "properties": {
293
+ "manifest_revision": {"type": "string"},
292
294
  "workflow": {"type": "string"},
293
295
  "contract": {"type": "string"},
294
296
  "display_name": {"type": "string"},
@@ -303,6 +305,7 @@ def _schemas() -> dict[str, Any]:
303
305
  "required": ["environment", "contracts"],
304
306
  "properties": {
305
307
  "environment": {"type": "string"},
308
+ "manifest_revision": {"type": "string"},
306
309
  "contracts": {"type": "array", "items": {"$ref": "#/components/schemas/ContractSummary"}},
307
310
  },
308
311
  },
@@ -464,6 +467,9 @@ def _schemas() -> dict[str, Any]:
464
467
  "status": {"type": "string"},
465
468
  "run_id": {"type": "string"},
466
469
  "prompt_id": {"type": "string"},
470
+ "manifest_revision": {"type": "string"},
471
+ "prompt_digest": {"type": "string"},
472
+ "request_id": {"type": ["string", "null"]},
467
473
  "issues": {"type": "array", "items": {"$ref": "#/components/schemas/RunIssue"}},
468
474
  "outputs": {"type": "array", "items": {"$ref": "#/components/schemas/RunOutput"}},
469
475
  "output_slots": {"type": "array", "items": {"$ref": "#/components/schemas/RunOutputSlot"}},
@@ -538,6 +544,24 @@ def _parameters() -> dict[str, Any]:
538
544
  }
539
545
 
540
546
 
547
+ def _busy_response() -> dict[str, Any]:
548
+ return {
549
+ "description": "Environment mutation in progress; no run submitted. Retry GET after Retry-After. Do not replay uncertain POSTs.",
550
+ "headers": {"Retry-After": {"schema": {"type": "string"}}, "X-Request-ID": {"schema": {"type": "string"}}},
551
+ "content": {"application/json": {"schema": {
552
+ "type": "object", "required": ["error", "retryable", "request_id", "owner"],
553
+ "properties": {
554
+ "error": {"const": "environment_busy"}, "retryable": {"const": True},
555
+ "request_id": {"type": "string"}, "message": {"type": "string"},
556
+ "owner": {"type": "object", "properties": {
557
+ "pid": {"type": ["integer", "null"]}, "operation": {"type": ["string", "null"]},
558
+ "acquired_at": {"type": ["string", "null"]},
559
+ }},
560
+ },
561
+ }}},
562
+ }
563
+
564
+
541
565
  def _json_response(description: str, schema_name: str) -> dict[str, Any]:
542
566
  return {"200": _json_content(description, schema_name)}
543
567
 
@@ -242,6 +242,9 @@
242
242
  },
243
243
  "type": "array"
244
244
  },
245
+ "manifest_revision": {
246
+ "type": "string"
247
+ },
245
248
  "outputs": {
246
249
  "items": {
247
250
  "$ref": "#/components/schemas/ContractOutput"
@@ -270,6 +273,9 @@
270
273
  },
271
274
  "environment": {
272
275
  "type": "string"
276
+ },
277
+ "manifest_revision": {
278
+ "type": "string"
273
279
  }
274
280
  },
275
281
  "required": [
@@ -708,6 +714,9 @@
708
714
  },
709
715
  "type": "array"
710
716
  },
717
+ "manifest_revision": {
718
+ "type": "string"
719
+ },
711
720
  "message": {
712
721
  "type": "string"
713
722
  },
@@ -723,9 +732,18 @@
723
732
  },
724
733
  "type": "array"
725
734
  },
735
+ "prompt_digest": {
736
+ "type": "string"
737
+ },
726
738
  "prompt_id": {
727
739
  "type": "string"
728
740
  },
741
+ "request_id": {
742
+ "type": [
743
+ "string",
744
+ "null"
745
+ ]
746
+ },
729
747
  "run_id": {
730
748
  "type": "string"
731
749
  },
@@ -860,6 +878,71 @@
860
878
  }
861
879
  },
862
880
  "description": "Contracts response"
881
+ },
882
+ "503": {
883
+ "content": {
884
+ "application/json": {
885
+ "schema": {
886
+ "properties": {
887
+ "error": {
888
+ "const": "environment_busy"
889
+ },
890
+ "message": {
891
+ "type": "string"
892
+ },
893
+ "owner": {
894
+ "properties": {
895
+ "acquired_at": {
896
+ "type": [
897
+ "string",
898
+ "null"
899
+ ]
900
+ },
901
+ "operation": {
902
+ "type": [
903
+ "string",
904
+ "null"
905
+ ]
906
+ },
907
+ "pid": {
908
+ "type": [
909
+ "integer",
910
+ "null"
911
+ ]
912
+ }
913
+ },
914
+ "type": "object"
915
+ },
916
+ "request_id": {
917
+ "type": "string"
918
+ },
919
+ "retryable": {
920
+ "const": true
921
+ }
922
+ },
923
+ "required": [
924
+ "error",
925
+ "retryable",
926
+ "request_id",
927
+ "owner"
928
+ ],
929
+ "type": "object"
930
+ }
931
+ }
932
+ },
933
+ "description": "Environment mutation in progress; no run submitted. Retry GET after Retry-After. Do not replay uncertain POSTs.",
934
+ "headers": {
935
+ "Retry-After": {
936
+ "schema": {
937
+ "type": "string"
938
+ }
939
+ },
940
+ "X-Request-ID": {
941
+ "schema": {
942
+ "type": "string"
943
+ }
944
+ }
945
+ }
863
946
  }
864
947
  },
865
948
  "summary": "List available workflow contracts"
@@ -886,6 +969,71 @@
886
969
  }
887
970
  },
888
971
  "description": "Contract response"
972
+ },
973
+ "503": {
974
+ "content": {
975
+ "application/json": {
976
+ "schema": {
977
+ "properties": {
978
+ "error": {
979
+ "const": "environment_busy"
980
+ },
981
+ "message": {
982
+ "type": "string"
983
+ },
984
+ "owner": {
985
+ "properties": {
986
+ "acquired_at": {
987
+ "type": [
988
+ "string",
989
+ "null"
990
+ ]
991
+ },
992
+ "operation": {
993
+ "type": [
994
+ "string",
995
+ "null"
996
+ ]
997
+ },
998
+ "pid": {
999
+ "type": [
1000
+ "integer",
1001
+ "null"
1002
+ ]
1003
+ }
1004
+ },
1005
+ "type": "object"
1006
+ },
1007
+ "request_id": {
1008
+ "type": "string"
1009
+ },
1010
+ "retryable": {
1011
+ "const": true
1012
+ }
1013
+ },
1014
+ "required": [
1015
+ "error",
1016
+ "retryable",
1017
+ "request_id",
1018
+ "owner"
1019
+ ],
1020
+ "type": "object"
1021
+ }
1022
+ }
1023
+ },
1024
+ "description": "Environment mutation in progress; no run submitted. Retry GET after Retry-After. Do not replay uncertain POSTs.",
1025
+ "headers": {
1026
+ "Retry-After": {
1027
+ "schema": {
1028
+ "type": "string"
1029
+ }
1030
+ },
1031
+ "X-Request-ID": {
1032
+ "schema": {
1033
+ "type": "string"
1034
+ }
1035
+ }
1036
+ }
889
1037
  }
890
1038
  },
891
1039
  "summary": "Return one workflow contract"
@@ -953,6 +1101,71 @@
953
1101
  },
954
1102
  "description": "Executor unavailable"
955
1103
  },
1104
+ "503": {
1105
+ "content": {
1106
+ "application/json": {
1107
+ "schema": {
1108
+ "properties": {
1109
+ "error": {
1110
+ "const": "environment_busy"
1111
+ },
1112
+ "message": {
1113
+ "type": "string"
1114
+ },
1115
+ "owner": {
1116
+ "properties": {
1117
+ "acquired_at": {
1118
+ "type": [
1119
+ "string",
1120
+ "null"
1121
+ ]
1122
+ },
1123
+ "operation": {
1124
+ "type": [
1125
+ "string",
1126
+ "null"
1127
+ ]
1128
+ },
1129
+ "pid": {
1130
+ "type": [
1131
+ "integer",
1132
+ "null"
1133
+ ]
1134
+ }
1135
+ },
1136
+ "type": "object"
1137
+ },
1138
+ "request_id": {
1139
+ "type": "string"
1140
+ },
1141
+ "retryable": {
1142
+ "const": true
1143
+ }
1144
+ },
1145
+ "required": [
1146
+ "error",
1147
+ "retryable",
1148
+ "request_id",
1149
+ "owner"
1150
+ ],
1151
+ "type": "object"
1152
+ }
1153
+ }
1154
+ },
1155
+ "description": "Environment mutation in progress; no run submitted. Retry GET after Retry-After. Do not replay uncertain POSTs.",
1156
+ "headers": {
1157
+ "Retry-After": {
1158
+ "schema": {
1159
+ "type": "string"
1160
+ }
1161
+ },
1162
+ "X-Request-ID": {
1163
+ "schema": {
1164
+ "type": "string"
1165
+ }
1166
+ }
1167
+ }
1168
+ },
956
1169
  "504": {
957
1170
  "content": {
958
1171
  "application/json": {
@@ -5,11 +5,13 @@ from __future__ import annotations
5
5
  import asyncio
6
6
  import hashlib
7
7
  import json
8
+ import logging
8
9
  import secrets
9
10
  import subprocess
10
11
  import uuid
11
- from collections.abc import Callable, Mapping
12
+ from collections.abc import Awaitable, Callable, Mapping
12
13
  from dataclasses import asdict, dataclass
14
+ from functools import wraps
13
15
  from importlib import resources
14
16
  from pathlib import Path
15
17
  from typing import Any, cast
@@ -18,7 +20,11 @@ from urllib.parse import quote
18
20
  import aiohttp
19
21
  from aiohttp import web
20
22
  from comfygit_core import Environment
21
- from comfygit_core.models import NamedWorkflowContract
23
+ from comfygit_core.models import (
24
+ CDEnvironmentBusyError,
25
+ EnvironmentManifestSnapshot,
26
+ NamedWorkflowContract,
27
+ )
22
28
  from comfygit_core.workflow import build_manifest_contract_prompt
23
29
 
24
30
  from .api_schema import studio_contract_api_openapi
@@ -92,6 +98,43 @@ UPLOAD_MIME_TYPE_ALIASES = {
92
98
  }
93
99
 
94
100
 
101
+ logger = logging.getLogger(__name__)
102
+
103
+
104
+ class _ContractPreparationBusy(CDEnvironmentBusyError):
105
+ """Known contention before any executor call; safe to report as unsubmitted."""
106
+
107
+
108
+ def _contract_request(
109
+ handler: Callable[[web.Request], Awaitable[web.Response]],
110
+ ) -> Callable[[web.Request], Awaitable[web.Response]]:
111
+ """Correlate contract requests and expose pre-submission lock contention."""
112
+ @wraps(handler)
113
+ async def wrapped(request: web.Request) -> web.Response:
114
+ request_id = uuid.uuid4().hex
115
+ request["contract_request_id"] = request_id
116
+ try:
117
+ response = await handler(request)
118
+ except CDEnvironmentBusyError as exc:
119
+ owner = asdict(exc.owner)
120
+ logger.warning("Environment busy request_id=%s method=%s route=%s owner=%s",
121
+ request_id, request.method, request.path, owner)
122
+ response = web.json_response({
123
+ "error": "environment_busy", "retryable": True,
124
+ "message": "Environment is being updated; retry after the operation completes.",
125
+ "request_id": request_id, "owner": owner,
126
+ }, status=503, headers={"Retry-After": "1"})
127
+ except web.HTTPException:
128
+ raise
129
+ except Exception:
130
+ logger.exception("Contract request failed request_id=%s route=%s", request_id, request.path)
131
+ response = web.json_response({"error": "internal_error", "request_id": request_id,
132
+ "message": "Contract request failed; inspect the server log."}, status=500)
133
+ response.headers["X-Request-ID"] = request_id
134
+ return response
135
+ return wrapped
136
+
137
+
95
138
  @dataclass(frozen=True)
96
139
  class ServeConfig:
97
140
  """Configuration for the local ComfyGit serve adapter."""
@@ -959,10 +1002,12 @@ async def proxy_artifact_handler(request: web.Request) -> web.StreamResponse:
959
1002
  return web.Response(body=output_response.body, status=output_response.status, headers=headers)
960
1003
 
961
1004
 
1005
+ @_contract_request
962
1006
  async def contracts_handler(request: web.Request) -> web.Response:
963
1007
  return web.json_response(_contracts_payload(_state(request)))
964
1008
 
965
1009
 
1010
+ @_contract_request
966
1011
  async def single_contract_handler(request: web.Request) -> web.Response:
967
1012
  try:
968
1013
  payload = _single_contract_payload(
@@ -1491,6 +1536,7 @@ def _localize_worker_callback_outputs(
1491
1536
  return output_payloads
1492
1537
 
1493
1538
 
1539
+ @_contract_request
1494
1540
  async def run_contract_handler(request: web.Request) -> web.Response:
1495
1541
  body: dict[str, Any] = {}
1496
1542
  session = _serve_session(request)
@@ -1502,9 +1548,12 @@ async def run_contract_handler(request: web.Request) -> web.Response:
1502
1548
  request.match_info["workflow"],
1503
1549
  request.match_info["contract"],
1504
1550
  body,
1551
+ request_id=request["contract_request_id"],
1505
1552
  )
1506
1553
  status = 400 if payload.get("status") == "invalid_request" else 200
1507
1554
  return _json_response_for_session(payload, session, status=status, request=request)
1555
+ except _ContractPreparationBusy:
1556
+ raise # Preparation did not submit a run; the outer handler returns 503.
1508
1557
  except web.HTTPRequestEntityTooLarge:
1509
1558
  state = _state(request)
1510
1559
  max_mib = _max_request_bytes(state) // (1024 * 1024)
@@ -1552,7 +1601,9 @@ async def run_contract_handler(request: web.Request) -> web.Response:
1552
1601
  payload.update(_record_failed_run(_state(request), session, request.match_info["workflow"], request.match_info["contract"], body, payload))
1553
1602
  return _json_response_for_session(payload, session, status=400)
1554
1603
  except Exception as exc:
1555
- payload = {"error": "internal_error", "message": str(exc)}
1604
+ request_id = request["contract_request_id"]
1605
+ logger.exception("Contract request failed request_id=%s route=%s", request_id, request.path)
1606
+ payload = {"error": "internal_error", "message": str(exc), "request_id": request_id}
1556
1607
  payload.update(_record_failed_run(_state(request), session, request.match_info["workflow"], request.match_info["contract"], body, payload))
1557
1608
  return _json_response_for_session(payload, session, status=500)
1558
1609
 
@@ -2241,6 +2292,7 @@ def _contracts_payload(state: ServeState) -> dict[str, Any]:
2241
2292
  contracts.append(_contract_payload(workflow_name, contract_name, contract))
2242
2293
  return {
2243
2294
  "environment": state.env.name,
2295
+ "manifest_revision": manifest.revision,
2244
2296
  "contracts": contracts,
2245
2297
  }
2246
2298
 
@@ -2257,7 +2309,7 @@ def _single_contract_payload(
2257
2309
  contract = workflow.execution_contract.contracts.get(contract_name)
2258
2310
  if contract is None:
2259
2311
  raise ValueError(f"Workflow '{workflow_name}' does not declare contract '{contract_name}'.")
2260
- return _contract_payload(workflow_name, contract_name, contract)
2312
+ return {**_contract_payload(workflow_name, contract_name, contract), "manifest_revision": manifest.revision}
2261
2313
 
2262
2314
 
2263
2315
  def _contract_payload(
@@ -2281,6 +2333,8 @@ async def _run_contract(
2281
2333
  workflow_name: str,
2282
2334
  contract_name: str,
2283
2335
  body: dict[str, Any],
2336
+ *,
2337
+ request_id: str | None = None,
2284
2338
  ) -> dict[str, Any]:
2285
2339
  if "inputs" in body:
2286
2340
  inputs = body["inputs"]
@@ -2293,16 +2347,19 @@ async def _run_contract(
2293
2347
  timeout_seconds = float(body.get("timeout_seconds", state.config.run_timeout_seconds))
2294
2348
  poll_interval_seconds = float(body.get("poll_interval_seconds", 1))
2295
2349
 
2296
- manifest = state.manifest_snapshot()
2297
- prepared_contract = await _prepare_contract_run_inputs(state, workflow_name, contract_name, inputs)
2298
- inputs = prepared_contract.inputs
2299
- build_result = build_manifest_contract_prompt(
2300
- manifest,
2301
- state.env.cec_path,
2302
- workflow_name,
2303
- inputs,
2304
- contract_name=contract_name,
2305
- )
2350
+ # One synchronous shared read protects both manifest and captured prompt.
2351
+ # No environment lock is held across any network call or GPU execution.
2352
+ try:
2353
+ with state.env.read_manifest_snapshot() as manifest:
2354
+ prepared_contract = _resolve_contract_run_inputs(state, manifest, workflow_name, contract_name, inputs)
2355
+ inputs = prepared_contract.inputs
2356
+ build_result = build_manifest_contract_prompt(
2357
+ manifest, state.env.cec_path, workflow_name, inputs, contract_name=contract_name,
2358
+ )
2359
+ manifest_revision = manifest.revision
2360
+ prompt_digest = hashlib.sha256(json.dumps(build_result.prompt, sort_keys=True).encode()).hexdigest()
2361
+ except CDEnvironmentBusyError as exc:
2362
+ raise _ContractPreparationBusy(exc.lock_path, exc.owner) from exc
2306
2363
  if build_result.has_errors:
2307
2364
  payload: dict[str, Any] = {
2308
2365
  "status": "invalid_request",
@@ -2318,8 +2375,13 @@ async def _run_contract(
2318
2375
  callback_token = _callback_auth_token(state) if callback_url else None
2319
2376
 
2320
2377
  async def record_submitted(prompt_id: str) -> None:
2378
+ logger.info("Contract submitted request_id=%s run_id=%s prompt_id=%s manifest_revision=%s prompt_digest=%s",
2379
+ request_id, run_id, prompt_id, manifest_revision, prompt_digest)
2321
2380
  response = {
2322
2381
  "status": "submitted",
2382
+ "manifest_revision": manifest_revision,
2383
+ "prompt_digest": prompt_digest,
2384
+ "request_id": request_id,
2323
2385
  "run_id": run_id,
2324
2386
  "prompt_id": prompt_id,
2325
2387
  "issues": [asdict(issue) for issue in build_result.issues],
@@ -2356,6 +2418,9 @@ async def _run_contract(
2356
2418
 
2357
2419
  response: dict[str, Any] = {
2358
2420
  "status": execution.status,
2421
+ "manifest_revision": manifest_revision,
2422
+ "prompt_digest": prompt_digest,
2423
+ "request_id": request_id,
2359
2424
  "run_id": run_id,
2360
2425
  "prompt_id": execution.prompt_id,
2361
2426
  "issues": [asdict(issue) for issue in build_result.issues],
@@ -3139,7 +3204,16 @@ async def _prepare_contract_run_inputs(
3139
3204
  ) -> PreparedContractInputs:
3140
3205
  """Resolve uploaded media refs before building the ComfyUI prompt."""
3141
3206
 
3142
- manifest = state.manifest_snapshot()
3207
+ return _resolve_contract_run_inputs(state, state.manifest_snapshot(), workflow_name, contract_name, inputs)
3208
+
3209
+
3210
+ def _resolve_contract_run_inputs(
3211
+ state: ServeState,
3212
+ manifest: EnvironmentManifestSnapshot,
3213
+ workflow_name: str,
3214
+ contract_name: str,
3215
+ inputs: dict[str, Any],
3216
+ ) -> PreparedContractInputs:
3143
3217
  workflow = manifest.workflows.get(workflow_name)
3144
3218
  execution_contract = getattr(workflow, "execution_contract", None) if workflow else None
3145
3219
  contract = execution_contract.contracts.get(contract_name) if execution_contract else None
@@ -1,12 +1,12 @@
1
1
  [project]
2
2
  name = "comfygit-studio"
3
- version = "0.7.0"
3
+ version = "0.7.1"
4
4
  description = "ComfyGit Studio runtime - contract API and Studio serving adapters"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.10"
7
7
  dependencies = [
8
8
  "aiohttp>=3.14.3",
9
- "comfygit-core==0.7.0",
9
+ "comfygit-core==0.7.1",
10
10
  ]
11
11
 
12
12
  [project.urls]