agentlink-cli 0.1.7__tar.gz → 0.1.8__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.
- {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/PKG-INFO +1 -1
- {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/connector/acp/adapter.py +41 -35
- {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/connector/acp/discovery.py +19 -8
- {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/connector/acp/manifest.py +1 -2
- {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/connector/acp/rpc.py +4 -5
- {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/connector/adapter.py +0 -1
- {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/connector/attachments.py +0 -1
- {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/connector/capabilities.py +9 -9
- {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/connector/claude/history_adapter.py +3 -4
- {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/connector/claude/path_utils.py +1 -1
- {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/connector/claude/sdk_adapter.py +4 -4
- {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/connector/claude/timeline_reducer.py +2 -2
- {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/connector/codex/adapter.py +10 -10
- {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/connector/codex/collaboration.py +0 -1
- {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/connector/codex/history.py +1 -2
- {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/connector/codex/reducer.py +1 -1
- {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/connector/codex/rpc.py +1 -1
- {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/connector/control.py +3 -3
- {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/connector/json_rpc.py +1 -2
- {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/connector/launch.py +1 -1
- {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/connector/local/common.py +2 -2
- {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/connector/local/file_ops.py +1 -1
- {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/connector/local/shell.py +2 -2
- {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/connector/local/terminal.py +15 -14
- {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/connector/local_runtime.py +1 -1
- {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/connector/logging.py +1 -2
- {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/connector/perf.py +1 -1
- {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/connector/registry.py +0 -1
- {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/connector/runtime.py +35 -33
- {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/connector/version.py +1 -1
- {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/pyproject.toml +1 -1
- {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/tests/contract/helpers.py +0 -1
- {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/tests/test_claude_history_adapter.py +6 -6
- {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/tests/test_claude_sdk_adapter.py +2 -2
- {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/tests/test_claude_timeline_parity.py +4 -1
- {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/tests/test_codex_adapter.py +10 -10
- {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/tests/test_connector_capabilities.py +6 -7
- {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/tests/test_connector_cli.py +7 -7
- {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/tests/test_connector_control.py +2 -2
- {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/tests/test_connector_runtime.py +10 -11
- {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/tests/test_version.py +1 -1
- {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/uv.lock +1 -1
- {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/.gitignore +0 -0
- {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/README.md +0 -0
- {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/connector/__init__.py +0 -0
- {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/connector/acp/__init__.py +0 -0
- {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/connector/acp/config_options.py +0 -0
- {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/connector/acp/manifests/__init__.py +0 -0
- {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/connector/acp/manifests/codebuddy.json +0 -0
- {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/connector/acp/manifests/cursor.json +0 -0
- {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/connector/acp/manifests/gemini.json +0 -0
- {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/connector/acp/manifests/grok_build.json +0 -0
- {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/connector/acp/reducer.py +0 -0
- {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/connector/claude/__init__.py +0 -0
- {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/connector/claude/normalized.py +0 -0
- {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/connector/claude/normalizers.py +0 -0
- {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/connector/claude/preferences.py +0 -0
- {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/connector/claude/timeline_identity.py +0 -0
- {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/connector/claude/trust.py +0 -0
- {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/connector/cli.py +0 -0
- {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/connector/codex/__init__.py +0 -0
- {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/connector/local/__init__.py +0 -0
- {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/connector/local/ops.py +0 -0
- {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/connector/local_ops.py +0 -0
- {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/connector/protocol.py +0 -0
- {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/connector/sync_state.py +0 -0
- {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/connector/time.py +0 -0
- {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/run.sh +0 -0
- {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/tests/__init__.py +0 -0
- {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/tests/acp/__init__.py +0 -0
- {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/tests/acp/fake_agent/__init__.py +0 -0
- {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/tests/acp/fake_agent/__main__.py +0 -0
- {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/tests/acp/test_acp_adapter_fake.py +0 -0
- {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/tests/acp/test_acp_reducer_golden.py +0 -0
- {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/tests/acp/test_config_options.py +0 -0
- {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/tests/contract/__init__.py +0 -0
- {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/tests/test_claude_preferences.py +0 -0
- {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/tests/test_claude_trust.py +0 -0
- {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/tests/test_connector_json_rpc.py +0 -0
- {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/tests/test_connector_logging.py +0 -0
- {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/tests/test_launch_command.py +0 -0
- {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/tests/test_local_runtime.py +0 -0
- {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/tests/test_perf.py +0 -0
- {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/tests/test_terminal_backend.py +0 -0
|
@@ -125,7 +125,7 @@ class AcpAdapter:
|
|
|
125
125
|
},
|
|
126
126
|
timeout=_SESSION_NEW_TIMEOUT_S,
|
|
127
127
|
)
|
|
128
|
-
except (AcpJsonRpcError, TimeoutError
|
|
128
|
+
except (AcpJsonRpcError, TimeoutError) as exc:
|
|
129
129
|
stderr = client.stderr_excerpt if client else ""
|
|
130
130
|
detail = str(exc)
|
|
131
131
|
if _is_authish_error(detail):
|
|
@@ -257,7 +257,7 @@ class AcpAdapter:
|
|
|
257
257
|
if isinstance(item, dict) and item.get("sessionId")
|
|
258
258
|
]
|
|
259
259
|
return {"threads": threads[:limit], "skippedThreads": [], "backendNotifications": []}
|
|
260
|
-
except Exception:
|
|
260
|
+
except Exception: # noqa: BLE001
|
|
261
261
|
logger.exception("{} session/list failed", self.runtime)
|
|
262
262
|
return {"threads": [], "skippedThreads": [], "backendNotifications": []}
|
|
263
263
|
|
|
@@ -305,7 +305,7 @@ class AcpAdapter:
|
|
|
305
305
|
client = await self._ensure_client()
|
|
306
306
|
await client.notify("session/cancel", {"sessionId": runtime.external_session_id})
|
|
307
307
|
return {"interrupted": True}
|
|
308
|
-
except Exception as exc:
|
|
308
|
+
except Exception as exc: # noqa: BLE001
|
|
309
309
|
logger.exception("{} session/cancel failed", self.runtime)
|
|
310
310
|
return {"interrupted": False, "reason": str(exc)}
|
|
311
311
|
|
|
@@ -382,7 +382,7 @@ class AcpAdapter:
|
|
|
382
382
|
},
|
|
383
383
|
timeout=max_turn_s,
|
|
384
384
|
)
|
|
385
|
-
except AcpJsonRpcError
|
|
385
|
+
except AcpJsonRpcError:
|
|
386
386
|
if runtime.interrupted:
|
|
387
387
|
outcome = "cancelled"
|
|
388
388
|
end = runtime.reducer.turn_end(
|
|
@@ -394,7 +394,7 @@ class AcpAdapter:
|
|
|
394
394
|
)
|
|
395
395
|
await self._emit_reduction(end)
|
|
396
396
|
return
|
|
397
|
-
raise
|
|
397
|
+
raise
|
|
398
398
|
stop_reason = _optional_string(result.get("stopReason") or result.get("stop_reason"))
|
|
399
399
|
if runtime.interrupted:
|
|
400
400
|
outcome = "cancelled"
|
|
@@ -409,7 +409,7 @@ class AcpAdapter:
|
|
|
409
409
|
except asyncio.CancelledError:
|
|
410
410
|
outcome = "cancelled"
|
|
411
411
|
raise
|
|
412
|
-
except Exception as exc:
|
|
412
|
+
except Exception as exc: # noqa: BLE001
|
|
413
413
|
outcome = "cancelled" if runtime.interrupted else "failed"
|
|
414
414
|
logger.exception(
|
|
415
415
|
"ACP turn failed runtime={} session_id={} turn_id={}",
|
|
@@ -477,7 +477,7 @@ class AcpAdapter:
|
|
|
477
477
|
data, original_name, detected_type = await self.attachment_downloader(
|
|
478
478
|
runtime.session_id, file_id
|
|
479
479
|
)
|
|
480
|
-
except Exception:
|
|
480
|
+
except Exception: # noqa: BLE001
|
|
481
481
|
logger.exception("attachment download failed file_id={}", file_id)
|
|
482
482
|
blocks.append({"type": "text", "text": f"\n\nAttached file: {name}"})
|
|
483
483
|
continue
|
|
@@ -542,7 +542,7 @@ class AcpAdapter:
|
|
|
542
542
|
},
|
|
543
543
|
timeout=45.0,
|
|
544
544
|
)
|
|
545
|
-
except (AcpJsonRpcError, TimeoutError
|
|
545
|
+
except (AcpJsonRpcError, TimeoutError) as exc:
|
|
546
546
|
stderr = client.stderr_excerpt
|
|
547
547
|
await self._shutdown_client_unlocked()
|
|
548
548
|
detail = str(exc)
|
|
@@ -575,7 +575,7 @@ class AcpAdapter:
|
|
|
575
575
|
"""Best-effort pre-spawn of the ACP process so first session is faster."""
|
|
576
576
|
try:
|
|
577
577
|
await self._ensure_client()
|
|
578
|
-
except Exception as exc:
|
|
578
|
+
except Exception as exc: # noqa: BLE001
|
|
579
579
|
logger.info("{} warm_start skipped: {}", self.runtime, exc)
|
|
580
580
|
|
|
581
581
|
async def _shutdown_client(self) -> None:
|
|
@@ -589,7 +589,7 @@ class AcpAdapter:
|
|
|
589
589
|
if client is not None:
|
|
590
590
|
try:
|
|
591
591
|
await client.close()
|
|
592
|
-
except Exception:
|
|
592
|
+
except Exception: # noqa: BLE001
|
|
593
593
|
logger.exception("{} failed to close ACP client", self.runtime)
|
|
594
594
|
|
|
595
595
|
async def _on_client_exit(self) -> None:
|
|
@@ -613,7 +613,7 @@ class AcpAdapter:
|
|
|
613
613
|
interrupted=True,
|
|
614
614
|
)
|
|
615
615
|
await self._emit_reduction(end)
|
|
616
|
-
except Exception:
|
|
616
|
+
except Exception: # noqa: BLE001
|
|
617
617
|
logger.exception("failed to finalize turn after process exit")
|
|
618
618
|
await self._emit(
|
|
619
619
|
"runtime.error",
|
|
@@ -793,7 +793,7 @@ class AcpAdapter:
|
|
|
793
793
|
)
|
|
794
794
|
used_method = mid
|
|
795
795
|
break
|
|
796
|
-
except Exception as exc:
|
|
796
|
+
except Exception as exc: # noqa: BLE001
|
|
797
797
|
last_error = str(exc)
|
|
798
798
|
logger.warning(
|
|
799
799
|
"{} interactive authenticate method={} failed: {}",
|
|
@@ -886,8 +886,8 @@ class AcpAdapter:
|
|
|
886
886
|
"sourceObservedAt": utc_now(),
|
|
887
887
|
},
|
|
888
888
|
)
|
|
889
|
-
except Exception:
|
|
890
|
-
|
|
889
|
+
except Exception: # noqa: BLE001
|
|
890
|
+
logger.exception("{} failed to emit required authentication status", self.runtime)
|
|
891
891
|
return detail
|
|
892
892
|
|
|
893
893
|
async def _probe_auth_and_options(self, client: AcpJsonRpcClient) -> dict[str, Any]:
|
|
@@ -946,7 +946,14 @@ class AcpAdapter:
|
|
|
946
946
|
else:
|
|
947
947
|
await client.request(method, {"sessionId": session_id}, timeout=3.0)
|
|
948
948
|
break
|
|
949
|
-
except Exception:
|
|
949
|
+
except Exception as exc: # noqa: BLE001
|
|
950
|
+
logger.debug(
|
|
951
|
+
"{} best-effort session cleanup failed method={} session_id={} error={}",
|
|
952
|
+
self.runtime,
|
|
953
|
+
method,
|
|
954
|
+
session_id,
|
|
955
|
+
exc,
|
|
956
|
+
)
|
|
950
957
|
continue
|
|
951
958
|
return probe
|
|
952
959
|
|
|
@@ -997,7 +1004,7 @@ class AcpAdapter:
|
|
|
997
1004
|
)
|
|
998
1005
|
logger.info("{} authenticated via method={}", self.runtime, mid)
|
|
999
1006
|
return True
|
|
1000
|
-
except Exception as exc:
|
|
1007
|
+
except Exception as exc: # noqa: BLE001
|
|
1001
1008
|
logger.debug(
|
|
1002
1009
|
"{} authenticate method={} failed: {}",
|
|
1003
1010
|
self.runtime,
|
|
@@ -1132,23 +1139,24 @@ class AcpAdapter:
|
|
|
1132
1139
|
if isinstance(session_id, str):
|
|
1133
1140
|
session_runtime = self._sessions.get(session_id)
|
|
1134
1141
|
timer = session_runtime.turn_timer if session_runtime is not None else None
|
|
1135
|
-
if
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
)
|
|
1145
|
-
)
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1142
|
+
if (
|
|
1143
|
+
timer is not None
|
|
1144
|
+
and item_type not in {"turn.start", "turn.end"}
|
|
1145
|
+
and item_type == "message"
|
|
1146
|
+
and isinstance(item, dict)
|
|
1147
|
+
and item.get("role") == "assistant"
|
|
1148
|
+
and isinstance(item.get("content"), dict)
|
|
1149
|
+
and (
|
|
1150
|
+
(isinstance(item["content"].get("text"), str) and item["content"]["text"].strip())
|
|
1151
|
+
or (isinstance(item["content"].get("rawText"), str) and item["content"]["rawText"].strip())
|
|
1152
|
+
)
|
|
1153
|
+
):
|
|
1154
|
+
timer.mark_first_timeline(
|
|
1155
|
+
runtime=self.runtime,
|
|
1156
|
+
session_id=session_id,
|
|
1157
|
+
turn_id=item.get("turnId"),
|
|
1158
|
+
stage_alias="adapter.first_assistant_token",
|
|
1159
|
+
)
|
|
1152
1160
|
await self._emit("timeline.itemUpsert", {"sessionId": item["sessionId"], "item": item})
|
|
1153
1161
|
if reduced.approval:
|
|
1154
1162
|
await self._emit("approval.requested", reduced.approval)
|
|
@@ -1217,5 +1225,3 @@ def _max_turn_seconds(manifest: AgentManifest) -> float | None:
|
|
|
1217
1225
|
|
|
1218
1226
|
def _connector_version() -> str:
|
|
1219
1227
|
return connector_version()
|
|
1220
|
-
|
|
1221
|
-
|
|
@@ -19,7 +19,6 @@ from connector.acp.rpc import AcpJsonRpcClient, AcpJsonRpcError
|
|
|
19
19
|
from connector.launch import LaunchTarget, launch_target, path_exists_for_launch
|
|
20
20
|
from connector.logging import logger
|
|
21
21
|
|
|
22
|
-
|
|
23
22
|
_COMMAND_CHECK_TIMEOUT_S = 6.0
|
|
24
23
|
# Full auth/model probe is expensive (spawn + session/new). Discovery stays light.
|
|
25
24
|
_LIGHT_INIT_TIMEOUT_S = 10.0
|
|
@@ -164,7 +163,7 @@ async def probe_acp_agent(manifest: AgentManifest, target: LaunchTarget) -> dict
|
|
|
164
163
|
if isinstance(session_id, str) and session_id:
|
|
165
164
|
await _best_effort_close_session(client, session_id)
|
|
166
165
|
return probe
|
|
167
|
-
except Exception as exc:
|
|
166
|
+
except Exception as exc: # noqa: BLE001
|
|
168
167
|
logger.warning("ACP probe failed runtime={} error={}", manifest.id, exc)
|
|
169
168
|
probe["authStatus"] = "unknown"
|
|
170
169
|
probe["probeError"] = str(exc)
|
|
@@ -174,8 +173,8 @@ async def probe_acp_agent(manifest: AgentManifest, target: LaunchTarget) -> dict
|
|
|
174
173
|
finally:
|
|
175
174
|
try:
|
|
176
175
|
await client.close()
|
|
177
|
-
except Exception:
|
|
178
|
-
|
|
176
|
+
except Exception: # noqa: BLE001
|
|
177
|
+
logger.exception("ACP probe client close failed runtime={}", manifest.id)
|
|
179
178
|
|
|
180
179
|
|
|
181
180
|
def _looks_like_auth_error(message: str) -> bool:
|
|
@@ -217,7 +216,7 @@ async def _probe_session_new(
|
|
|
217
216
|
return None, True
|
|
218
217
|
logger.debug("ACP probe session/new failed non-auth: {}", exc)
|
|
219
218
|
return None, False
|
|
220
|
-
except Exception as exc:
|
|
219
|
+
except Exception as exc: # noqa: BLE001
|
|
221
220
|
message = str(exc)
|
|
222
221
|
if "timeout" in message.lower() or _looks_like_auth_error(message):
|
|
223
222
|
return None, True
|
|
@@ -233,7 +232,13 @@ async def _best_effort_close_session(client: AcpJsonRpcClient, session_id: str)
|
|
|
233
232
|
else:
|
|
234
233
|
await client.request(method, {"sessionId": session_id}, timeout=2.0)
|
|
235
234
|
break
|
|
236
|
-
except Exception:
|
|
235
|
+
except Exception as exc: # noqa: BLE001
|
|
236
|
+
logger.debug(
|
|
237
|
+
"ACP best-effort session cleanup failed method={} session_id={} error={}",
|
|
238
|
+
method,
|
|
239
|
+
session_id,
|
|
240
|
+
exc,
|
|
241
|
+
)
|
|
237
242
|
continue
|
|
238
243
|
|
|
239
244
|
|
|
@@ -268,7 +273,13 @@ async def _try_authenticate(
|
|
|
268
273
|
)
|
|
269
274
|
logger.info("ACP discovery authenticated runtime={} method={}", manifest.id, mid)
|
|
270
275
|
return
|
|
271
|
-
except Exception:
|
|
276
|
+
except Exception as exc: # noqa: BLE001
|
|
277
|
+
logger.debug(
|
|
278
|
+
"ACP headless authentication failed runtime={} method={} error={}",
|
|
279
|
+
manifest.id,
|
|
280
|
+
mid,
|
|
281
|
+
exc,
|
|
282
|
+
)
|
|
272
283
|
continue
|
|
273
284
|
|
|
274
285
|
|
|
@@ -368,7 +379,7 @@ async def _run_command(command: list[str]) -> dict[str, Any]:
|
|
|
368
379
|
return {"status": "ok", "stdout": (stdout or stderr)[:200]}
|
|
369
380
|
except FileNotFoundError:
|
|
370
381
|
return {"status": "missing", "reason": "file not found"}
|
|
371
|
-
except Exception as exc:
|
|
382
|
+
except Exception as exc: # noqa: BLE001
|
|
372
383
|
logger.exception("ACP version check failed command={}", command)
|
|
373
384
|
return {"status": "failed", "reason": str(exc)}
|
|
374
385
|
|
|
@@ -6,7 +6,6 @@ from importlib import resources
|
|
|
6
6
|
from pathlib import Path
|
|
7
7
|
from typing import Any, Literal
|
|
8
8
|
|
|
9
|
-
|
|
10
9
|
ProcessModel = Literal["shared", "per_session"]
|
|
11
10
|
CapabilityLevel = Literal["required", "optional", "unsupported"]
|
|
12
11
|
|
|
@@ -86,7 +85,7 @@ def manifest_from_dict(raw: dict[str, Any]) -> AgentManifest:
|
|
|
86
85
|
def load_manifest_file(path: str | Path) -> AgentManifest:
|
|
87
86
|
data = json.loads(Path(path).read_text(encoding="utf-8"))
|
|
88
87
|
if not isinstance(data, dict):
|
|
89
|
-
raise
|
|
88
|
+
raise TypeError(f"manifest file must be a JSON object: {path}")
|
|
90
89
|
return manifest_from_dict(data)
|
|
91
90
|
|
|
92
91
|
|
|
@@ -8,7 +8,6 @@ from typing import Any
|
|
|
8
8
|
|
|
9
9
|
from connector.logging import logger
|
|
10
10
|
|
|
11
|
-
|
|
12
11
|
NotificationHandler = Callable[[dict[str, Any]], Awaitable[None]]
|
|
13
12
|
ServerRequestHandler = Callable[[str | int, str, dict[str, Any]], Awaitable[dict[str, Any] | None]]
|
|
14
13
|
ExitHandler = Callable[[], Awaitable[None]]
|
|
@@ -232,7 +231,7 @@ class AcpJsonRpcClient:
|
|
|
232
231
|
if handler is not None and not self._closed:
|
|
233
232
|
try:
|
|
234
233
|
await handler()
|
|
235
|
-
except Exception:
|
|
234
|
+
except Exception: # noqa: BLE001
|
|
236
235
|
logger.exception("ACP exit handler failed")
|
|
237
236
|
|
|
238
237
|
async def _read_stderr(self) -> None:
|
|
@@ -293,16 +292,16 @@ class AcpJsonRpcClient:
|
|
|
293
292
|
if handler is None:
|
|
294
293
|
try:
|
|
295
294
|
await self.respond_error(request_id, code=-32601, message=f"Method not found: {method}")
|
|
296
|
-
except Exception:
|
|
295
|
+
except Exception: # noqa: BLE001
|
|
297
296
|
logger.exception("ACP respond_error failed method={}", method)
|
|
298
297
|
return
|
|
299
298
|
try:
|
|
300
299
|
result = await handler(request_id, method, params)
|
|
301
300
|
if result is not None:
|
|
302
301
|
await self.respond(request_id, result)
|
|
303
|
-
except Exception as exc:
|
|
302
|
+
except Exception as exc: # noqa: BLE001
|
|
304
303
|
logger.exception("ACP server request handler failed method={}", method)
|
|
305
304
|
try:
|
|
306
305
|
await self.respond_error(request_id, code=-32000, message=str(exc))
|
|
307
|
-
except Exception:
|
|
306
|
+
except Exception: # noqa: BLE001
|
|
308
307
|
logger.exception("ACP respond_error failed method={}", method)
|
|
@@ -264,7 +264,7 @@ async def _check_codex_candidate(candidate: dict[str, str] | LaunchTarget | Laun
|
|
|
264
264
|
_read_codex_collaboration_mode_options(client),
|
|
265
265
|
timeout=_CODEX_CHECK_TIMEOUT_S,
|
|
266
266
|
)
|
|
267
|
-
except Exception as exc:
|
|
267
|
+
except Exception as exc: # noqa: BLE001
|
|
268
268
|
stage = "model-list" if "model_options" in locals() or _is_model_list_error(exc) else "app-server"
|
|
269
269
|
return {
|
|
270
270
|
**base,
|
|
@@ -276,8 +276,8 @@ async def _check_codex_candidate(candidate: dict[str, str] | LaunchTarget | Laun
|
|
|
276
276
|
finally:
|
|
277
277
|
try:
|
|
278
278
|
await client.close()
|
|
279
|
-
except Exception:
|
|
280
|
-
|
|
279
|
+
except Exception: # noqa: BLE001
|
|
280
|
+
logger.exception("closing Codex capability probe client failed")
|
|
281
281
|
|
|
282
282
|
return {
|
|
283
283
|
**base,
|
|
@@ -398,7 +398,7 @@ async def _read_codex_model_options(client: JsonRpcStdioClient) -> list[dict[str
|
|
|
398
398
|
|
|
399
399
|
def _codex_model_page(result: Any) -> tuple[list[Any], str | None]:
|
|
400
400
|
if not isinstance(result, dict):
|
|
401
|
-
raise RuntimeError("model/list returned a non-object response")
|
|
401
|
+
raise RuntimeError("model/list returned a non-object response") # noqa: TRY004
|
|
402
402
|
raw_models: Any = result.get("models")
|
|
403
403
|
if raw_models is None:
|
|
404
404
|
raw_models = result.get("items")
|
|
@@ -407,7 +407,7 @@ def _codex_model_page(result: Any) -> tuple[list[Any], str | None]:
|
|
|
407
407
|
if isinstance(raw_models, dict):
|
|
408
408
|
raw_models = raw_models.get("items") or raw_models.get("models")
|
|
409
409
|
if not isinstance(raw_models, list):
|
|
410
|
-
raise RuntimeError("model/list response has no models list")
|
|
410
|
+
raise RuntimeError("model/list response has no models list") # noqa: TRY004
|
|
411
411
|
raw_cursor = (
|
|
412
412
|
result.get("nextCursor")
|
|
413
413
|
or result.get("nextPageToken")
|
|
@@ -463,7 +463,7 @@ def _is_model_list_error(exc: BaseException) -> bool:
|
|
|
463
463
|
async def _read_codex_collaboration_mode_options(client: JsonRpcStdioClient) -> list[dict[str, Any]]:
|
|
464
464
|
try:
|
|
465
465
|
result = await client.request("collaborationMode/list", {})
|
|
466
|
-
except Exception:
|
|
466
|
+
except Exception: # noqa: BLE001
|
|
467
467
|
return []
|
|
468
468
|
return normalize_collaboration_modes(result)
|
|
469
469
|
|
|
@@ -498,7 +498,7 @@ def _check_claude_history() -> dict[str, Any]:
|
|
|
498
498
|
api = "list_sessions"
|
|
499
499
|
try:
|
|
500
500
|
sessions = _list_claude_sdk_sessions()
|
|
501
|
-
except Exception as exc:
|
|
501
|
+
except Exception as exc: # noqa: BLE001
|
|
502
502
|
return {
|
|
503
503
|
"status": "unavailable",
|
|
504
504
|
"source": source,
|
|
@@ -515,7 +515,7 @@ def _check_claude_history() -> dict[str, Any]:
|
|
|
515
515
|
|
|
516
516
|
def _list_claude_sdk_sessions() -> list[Any]:
|
|
517
517
|
sdk = importlib.import_module("claude_agent_sdk")
|
|
518
|
-
list_sessions =
|
|
518
|
+
list_sessions = sdk.list_sessions
|
|
519
519
|
return list(list_sessions())
|
|
520
520
|
|
|
521
521
|
|
|
@@ -546,7 +546,7 @@ async def _run_version(command: list[str], *, start_new_session: bool = False) -
|
|
|
546
546
|
if proc is not None and proc.returncode is None:
|
|
547
547
|
await _terminate_process(proc, process_group=start_new_session)
|
|
548
548
|
raise
|
|
549
|
-
except Exception as exc:
|
|
549
|
+
except Exception as exc: # noqa: BLE001
|
|
550
550
|
if proc is not None and proc.returncode is None:
|
|
551
551
|
await _terminate_process(proc, process_group=start_new_session)
|
|
552
552
|
return {"status": "failed", "reason": _exception_reason(exc)}
|
|
@@ -6,12 +6,11 @@ from dataclasses import dataclass, field
|
|
|
6
6
|
from datetime import UTC, datetime
|
|
7
7
|
from typing import Any
|
|
8
8
|
|
|
9
|
-
from connector.logging import logger
|
|
10
|
-
|
|
11
9
|
from connector.claude.normalizers import ClaudeTranscriptNormalizer
|
|
12
10
|
from connector.claude.path_utils import stable_claude_session_id
|
|
13
11
|
from connector.claude.timeline_identity import content_hash
|
|
14
12
|
from connector.claude.timeline_reducer import ClaudeTimelineReducer
|
|
13
|
+
from connector.logging import logger
|
|
15
14
|
from connector.sync_state import SyncStateStore
|
|
16
15
|
from connector.time import utc_now
|
|
17
16
|
|
|
@@ -503,7 +502,7 @@ def _turn_boundary_item(
|
|
|
503
502
|
def _list_sessions(sdk: Any, *, limit: int) -> list[Any]:
|
|
504
503
|
list_sessions = getattr(sdk, "list_sessions", None)
|
|
505
504
|
if not callable(list_sessions):
|
|
506
|
-
raise RuntimeError("claude-agent-sdk does not expose list_sessions()")
|
|
505
|
+
raise RuntimeError("claude-agent-sdk does not expose list_sessions()") # noqa: TRY004
|
|
507
506
|
return list(list_sessions(limit=limit))
|
|
508
507
|
|
|
509
508
|
|
|
@@ -520,7 +519,7 @@ def _get_session_info(sdk: Any, session_id: str, *, directory: str | None) -> An
|
|
|
520
519
|
def _get_session_messages(sdk: Any, session_id: str, *, directory: str | None) -> list[Any]:
|
|
521
520
|
get_session_messages = getattr(sdk, "get_session_messages", None)
|
|
522
521
|
if not callable(get_session_messages):
|
|
523
|
-
raise RuntimeError("claude-agent-sdk does not expose get_session_messages()")
|
|
522
|
+
raise RuntimeError("claude-agent-sdk does not expose get_session_messages()") # noqa: TRY004
|
|
524
523
|
try:
|
|
525
524
|
return list(get_session_messages(session_id, directory=directory))
|
|
526
525
|
except TypeError:
|
|
@@ -9,5 +9,5 @@ def stable_claude_session_id(connector_id: str, claude_uuid: str) -> str:
|
|
|
9
9
|
Mirrors connector.codex.adapter.stable_session_id so the backend never
|
|
10
10
|
sees two ids referring to the same upstream session.
|
|
11
11
|
"""
|
|
12
|
-
digest = hashlib.sha256(f"{connector_id}:claude:{claude_uuid}".encode(
|
|
12
|
+
digest = hashlib.sha256(f"{connector_id}:claude:{claude_uuid}".encode()).hexdigest()[:24]
|
|
13
13
|
return f"sess_claude_{digest}"
|
|
@@ -262,7 +262,7 @@ class ClaudeSdkAdapter:
|
|
|
262
262
|
stream_finished = True
|
|
263
263
|
except asyncio.CancelledError:
|
|
264
264
|
raise
|
|
265
|
-
except Exception as exc:
|
|
265
|
+
except Exception as exc: # noqa: BLE001
|
|
266
266
|
stderr = _stderr_excerpt(runtime.stderr_lines)
|
|
267
267
|
logger.exception(
|
|
268
268
|
"claude sdk turn failed session_id={} turn_id={} cwd={} external_session_id={} "
|
|
@@ -533,7 +533,7 @@ class ClaudeSdkAdapter:
|
|
|
533
533
|
external_session_id=runtime.external_session_id,
|
|
534
534
|
cwd=runtime.cwd,
|
|
535
535
|
)
|
|
536
|
-
except Exception:
|
|
536
|
+
except Exception: # noqa: BLE001
|
|
537
537
|
logger.exception(
|
|
538
538
|
"claude sdk history consumed marker failed session_id={} external_session_id={}",
|
|
539
539
|
runtime.session_id,
|
|
@@ -553,7 +553,7 @@ class ClaudeSdkAdapter:
|
|
|
553
553
|
"pendingClientMessages": _pending_client_messages(runtime),
|
|
554
554
|
}
|
|
555
555
|
)
|
|
556
|
-
except Exception:
|
|
556
|
+
except Exception: # noqa: BLE001
|
|
557
557
|
logger.exception(
|
|
558
558
|
"claude sdk history snapshot failed session_id={} external_session_id={}",
|
|
559
559
|
runtime.session_id,
|
|
@@ -702,7 +702,7 @@ class ClaudeSdkAdapter:
|
|
|
702
702
|
data, original_name, media_type = await self.attachment_downloader(
|
|
703
703
|
session_id, file_id
|
|
704
704
|
)
|
|
705
|
-
except Exception as exc:
|
|
705
|
+
except Exception as exc: # noqa: BLE001
|
|
706
706
|
logger.exception("Claude attachment download failed file_id={}", file_id)
|
|
707
707
|
blocks.append({"type": "text", "text": f"\n\n[Failed to load attachment {file_id}: {exc}]"})
|
|
708
708
|
continue
|
|
@@ -183,7 +183,7 @@ def _content_score(content: Any) -> int:
|
|
|
183
183
|
|
|
184
184
|
def _tool_call_content(event: NormalizedClaudeEvent) -> dict[str, Any]:
|
|
185
185
|
tool_name = event.toolName or "tool"
|
|
186
|
-
tool_input = event.toolInput
|
|
186
|
+
tool_input = event.toolInput
|
|
187
187
|
common = _tool_common(event)
|
|
188
188
|
kind = _tool_kind(tool_name)
|
|
189
189
|
if kind == "command":
|
|
@@ -235,7 +235,7 @@ def _tool_call_content(event: NormalizedClaudeEvent) -> dict[str, Any]:
|
|
|
235
235
|
"kind": "tool",
|
|
236
236
|
"name": tool_name,
|
|
237
237
|
"tool": tool_name,
|
|
238
|
-
"arguments": tool_input
|
|
238
|
+
"arguments": tool_input,
|
|
239
239
|
})
|
|
240
240
|
|
|
241
241
|
|
|
@@ -309,7 +309,7 @@ class CodexAdapter:
|
|
|
309
309
|
EXISTING_SYNC_CHANGED_THREAD_TIMEOUT_SECONDS,
|
|
310
310
|
)
|
|
311
311
|
continue
|
|
312
|
-
except Exception as exc:
|
|
312
|
+
except Exception as exc: # noqa: BLE001
|
|
313
313
|
reason = _unresumable_thread_failure_reason(str(exc))
|
|
314
314
|
if reason is not None:
|
|
315
315
|
logger.info(
|
|
@@ -456,7 +456,7 @@ class CodexAdapter:
|
|
|
456
456
|
self._collaboration_modes = normalize_collaboration_modes(
|
|
457
457
|
await self.rpc.request("collaborationMode/list", {})
|
|
458
458
|
)
|
|
459
|
-
except Exception:
|
|
459
|
+
except Exception: # noqa: BLE001
|
|
460
460
|
self._collaboration_modes = []
|
|
461
461
|
collaboration = (
|
|
462
462
|
collaboration_mode_payload(
|
|
@@ -644,7 +644,7 @@ class CodexAdapter:
|
|
|
644
644
|
phase="completed" if command == "status" else "accepted",
|
|
645
645
|
data=data,
|
|
646
646
|
)
|
|
647
|
-
except Exception as exc:
|
|
647
|
+
except Exception as exc: # noqa: BLE001
|
|
648
648
|
failed_items = self.reducer.command_items(
|
|
649
649
|
session_id=session_id,
|
|
650
650
|
thread_id=thread_id,
|
|
@@ -672,7 +672,7 @@ class CodexAdapter:
|
|
|
672
672
|
try:
|
|
673
673
|
result = await self.rpc.request(method, params)
|
|
674
674
|
return result if isinstance(result, dict) else None
|
|
675
|
-
except Exception as exc:
|
|
675
|
+
except Exception as exc: # noqa: BLE001
|
|
676
676
|
logger.debug("optional codex command read failed method={} error={}", method, exc)
|
|
677
677
|
return None
|
|
678
678
|
|
|
@@ -709,7 +709,7 @@ class CodexAdapter:
|
|
|
709
709
|
data, original_name, media_type = await self.attachment_downloader(
|
|
710
710
|
session_id, file_id
|
|
711
711
|
)
|
|
712
|
-
except Exception as exc:
|
|
712
|
+
except Exception as exc: # noqa: BLE001
|
|
713
713
|
logger.exception("attachment download failed file_id={}", file_id)
|
|
714
714
|
text += f"\n\n[Failed to load attachment {file_id}: {exc}]"
|
|
715
715
|
continue
|
|
@@ -783,7 +783,7 @@ class CodexAdapter:
|
|
|
783
783
|
raise ValueError("requestId is required to respond to Codex user input")
|
|
784
784
|
answers = params.get("answers")
|
|
785
785
|
if not isinstance(answers, dict):
|
|
786
|
-
raise
|
|
786
|
+
raise TypeError("answers are required to respond to Codex user input")
|
|
787
787
|
await self.rpc.respond(request_id, {"answers": answers})
|
|
788
788
|
logger.info(
|
|
789
789
|
"codex user input resolved request_id={} approval_id={} question_count={}",
|
|
@@ -919,8 +919,8 @@ class CodexAdapter:
|
|
|
919
919
|
("thread/loaded/list", None),
|
|
920
920
|
):
|
|
921
921
|
try:
|
|
922
|
-
|
|
923
|
-
except Exception as exc: # pragma: no cover - defensive against version drift
|
|
922
|
+
await self.rpc.request(method, params)
|
|
923
|
+
except Exception as exc: # pragma: no cover - defensive against version drift # noqa: BLE001
|
|
924
924
|
logger.debug("codex bootstrap read failed method={} error={}", method, exc)
|
|
925
925
|
|
|
926
926
|
async def _reduce_current_timeline(
|
|
@@ -979,7 +979,7 @@ class CodexAdapter:
|
|
|
979
979
|
)
|
|
980
980
|
except asyncio.CancelledError:
|
|
981
981
|
raise
|
|
982
|
-
except Exception:
|
|
982
|
+
except Exception: # noqa: BLE001
|
|
983
983
|
logger.exception("codex delayed thread snapshot sync failed thread_id={}", thread_id)
|
|
984
984
|
|
|
985
985
|
|
|
@@ -1028,7 +1028,7 @@ def _completed_item_from_message(message: dict[str, Any]) -> dict[str, Any] | No
|
|
|
1028
1028
|
|
|
1029
1029
|
|
|
1030
1030
|
def stable_session_id(connector_id: str, thread_id: str) -> str:
|
|
1031
|
-
digest = hashlib.sha256(f"{connector_id}:codex:{thread_id}".encode(
|
|
1031
|
+
digest = hashlib.sha256(f"{connector_id}:codex:{thread_id}".encode()).hexdigest()[:24]
|
|
1032
1032
|
return f"sess_codex_{digest}"
|
|
1033
1033
|
|
|
1034
1034
|
|
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
import json
|
|
4
3
|
import hashlib
|
|
4
|
+
import json
|
|
5
5
|
from dataclasses import dataclass
|
|
6
6
|
from pathlib import Path
|
|
7
7
|
from typing import Any
|
|
8
8
|
|
|
9
9
|
from connector.logging import logger
|
|
10
10
|
|
|
11
|
-
|
|
12
11
|
CODEX_HISTORY_ITEM_TYPES = {
|
|
13
12
|
"message",
|
|
14
13
|
"reasoning",
|
|
@@ -1321,7 +1321,7 @@ def _changes_from_patch(patch: str) -> list[dict[str, Any]]:
|
|
|
1321
1321
|
current: dict[str, Any] | None = None
|
|
1322
1322
|
diff_lines: list[str] = []
|
|
1323
1323
|
for line in patch.splitlines():
|
|
1324
|
-
if line.startswith("*** Add File: "
|
|
1324
|
+
if line.startswith(("*** Add File: ", "*** Update File: ", "*** Delete File: ")):
|
|
1325
1325
|
if current is not None:
|
|
1326
1326
|
current["diff"] = "\n".join(diff_lines)
|
|
1327
1327
|
changes.append(current)
|
|
@@ -176,7 +176,7 @@ class JsonRpcStdioClient:
|
|
|
176
176
|
completed.result()
|
|
177
177
|
except asyncio.CancelledError:
|
|
178
178
|
return
|
|
179
|
-
except Exception:
|
|
179
|
+
except Exception: # noqa: BLE001
|
|
180
180
|
logger.exception("codex app-server {} reader stopped unexpectedly", name)
|
|
181
181
|
|
|
182
182
|
task.add_done_callback(done)
|