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.
Files changed (84) hide show
  1. {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/PKG-INFO +1 -1
  2. {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/connector/acp/adapter.py +41 -35
  3. {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/connector/acp/discovery.py +19 -8
  4. {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/connector/acp/manifest.py +1 -2
  5. {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/connector/acp/rpc.py +4 -5
  6. {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/connector/adapter.py +0 -1
  7. {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/connector/attachments.py +0 -1
  8. {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/connector/capabilities.py +9 -9
  9. {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/connector/claude/history_adapter.py +3 -4
  10. {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/connector/claude/path_utils.py +1 -1
  11. {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/connector/claude/sdk_adapter.py +4 -4
  12. {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/connector/claude/timeline_reducer.py +2 -2
  13. {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/connector/codex/adapter.py +10 -10
  14. {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/connector/codex/collaboration.py +0 -1
  15. {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/connector/codex/history.py +1 -2
  16. {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/connector/codex/reducer.py +1 -1
  17. {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/connector/codex/rpc.py +1 -1
  18. {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/connector/control.py +3 -3
  19. {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/connector/json_rpc.py +1 -2
  20. {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/connector/launch.py +1 -1
  21. {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/connector/local/common.py +2 -2
  22. {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/connector/local/file_ops.py +1 -1
  23. {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/connector/local/shell.py +2 -2
  24. {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/connector/local/terminal.py +15 -14
  25. {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/connector/local_runtime.py +1 -1
  26. {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/connector/logging.py +1 -2
  27. {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/connector/perf.py +1 -1
  28. {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/connector/registry.py +0 -1
  29. {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/connector/runtime.py +35 -33
  30. {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/connector/version.py +1 -1
  31. {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/pyproject.toml +1 -1
  32. {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/tests/contract/helpers.py +0 -1
  33. {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/tests/test_claude_history_adapter.py +6 -6
  34. {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/tests/test_claude_sdk_adapter.py +2 -2
  35. {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/tests/test_claude_timeline_parity.py +4 -1
  36. {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/tests/test_codex_adapter.py +10 -10
  37. {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/tests/test_connector_capabilities.py +6 -7
  38. {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/tests/test_connector_cli.py +7 -7
  39. {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/tests/test_connector_control.py +2 -2
  40. {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/tests/test_connector_runtime.py +10 -11
  41. {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/tests/test_version.py +1 -1
  42. {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/uv.lock +1 -1
  43. {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/.gitignore +0 -0
  44. {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/README.md +0 -0
  45. {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/connector/__init__.py +0 -0
  46. {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/connector/acp/__init__.py +0 -0
  47. {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/connector/acp/config_options.py +0 -0
  48. {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/connector/acp/manifests/__init__.py +0 -0
  49. {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/connector/acp/manifests/codebuddy.json +0 -0
  50. {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/connector/acp/manifests/cursor.json +0 -0
  51. {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/connector/acp/manifests/gemini.json +0 -0
  52. {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/connector/acp/manifests/grok_build.json +0 -0
  53. {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/connector/acp/reducer.py +0 -0
  54. {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/connector/claude/__init__.py +0 -0
  55. {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/connector/claude/normalized.py +0 -0
  56. {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/connector/claude/normalizers.py +0 -0
  57. {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/connector/claude/preferences.py +0 -0
  58. {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/connector/claude/timeline_identity.py +0 -0
  59. {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/connector/claude/trust.py +0 -0
  60. {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/connector/cli.py +0 -0
  61. {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/connector/codex/__init__.py +0 -0
  62. {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/connector/local/__init__.py +0 -0
  63. {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/connector/local/ops.py +0 -0
  64. {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/connector/local_ops.py +0 -0
  65. {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/connector/protocol.py +0 -0
  66. {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/connector/sync_state.py +0 -0
  67. {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/connector/time.py +0 -0
  68. {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/run.sh +0 -0
  69. {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/tests/__init__.py +0 -0
  70. {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/tests/acp/__init__.py +0 -0
  71. {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/tests/acp/fake_agent/__init__.py +0 -0
  72. {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/tests/acp/fake_agent/__main__.py +0 -0
  73. {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/tests/acp/test_acp_adapter_fake.py +0 -0
  74. {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/tests/acp/test_acp_reducer_golden.py +0 -0
  75. {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/tests/acp/test_config_options.py +0 -0
  76. {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/tests/contract/__init__.py +0 -0
  77. {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/tests/test_claude_preferences.py +0 -0
  78. {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/tests/test_claude_trust.py +0 -0
  79. {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/tests/test_connector_json_rpc.py +0 -0
  80. {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/tests/test_connector_logging.py +0 -0
  81. {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/tests/test_launch_command.py +0 -0
  82. {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/tests/test_local_runtime.py +0 -0
  83. {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/tests/test_perf.py +0 -0
  84. {agentlink_cli-0.1.7 → agentlink_cli-0.1.8}/tests/test_terminal_backend.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: agentlink-cli
3
- Version: 0.1.7
3
+ Version: 0.1.8
4
4
  Summary: Local runtime connector for AgentLink
5
5
  Requires-Python: >=3.12
6
6
  Requires-Dist: claude-agent-sdk
@@ -125,7 +125,7 @@ class AcpAdapter:
125
125
  },
126
126
  timeout=_SESSION_NEW_TIMEOUT_S,
127
127
  )
128
- except (AcpJsonRpcError, TimeoutError, asyncio.TimeoutError) as exc:
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 as exc:
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 exc
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, asyncio.TimeoutError) as exc:
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
- pass
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 timer is not None and item_type not in {"turn.start", "turn.end"}:
1136
- if (
1137
- item_type == "message"
1138
- and isinstance(item, dict)
1139
- and item.get("role") == "assistant"
1140
- and isinstance(item.get("content"), dict)
1141
- and (
1142
- (isinstance(item["content"].get("text"), str) and item["content"]["text"].strip())
1143
- or (isinstance(item["content"].get("rawText"), str) and item["content"]["rawText"].strip())
1144
- )
1145
- ):
1146
- timer.mark_first_timeline(
1147
- runtime=self.runtime,
1148
- session_id=session_id,
1149
- turn_id=item.get("turnId") if isinstance(item, dict) else None,
1150
- stage_alias="adapter.first_assistant_token",
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
- pass
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 ValueError(f"manifest file must be a JSON object: {path}")
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)
@@ -3,7 +3,6 @@ from __future__ import annotations
3
3
  from collections.abc import Awaitable, Callable
4
4
  from typing import Any, Protocol, runtime_checkable
5
5
 
6
-
7
6
  NotificationSink = Callable[[str, dict[str, Any]], Awaitable[None]] | None
8
7
 
9
8
 
@@ -3,7 +3,6 @@ from __future__ import annotations
3
3
  import re
4
4
  from pathlib import Path
5
5
 
6
-
7
6
  ATTACHMENTS_ROOT_ENV = "AGENT_CONNECTOR_ATTACHMENTS_ROOT"
8
7
  DEFAULT_ATTACHMENTS_DIR = ".agent-link/attachments"
9
8
  _SAFE_FILENAME_RE = re.compile(r"[^\w.\-+]+")
@@ -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
- pass
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 = getattr(sdk, "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("utf-8")).hexdigest()[:24]
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 if isinstance(event.toolInput, dict) else 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 if isinstance(tool_input, dict) else 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 ValueError("answers are required to respond to Codex user input")
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
- result = await self.rpc.request(method, params)
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("utf-8")).hexdigest()[:24]
1031
+ digest = hashlib.sha256(f"{connector_id}:codex:{thread_id}".encode()).hexdigest()[:24]
1032
1032
  return f"sess_codex_{digest}"
1033
1033
 
1034
1034
 
@@ -2,7 +2,6 @@ from __future__ import annotations
2
2
 
3
3
  from typing import Any
4
4
 
5
-
6
5
  PLAN_MODE_DEVELOPER_INSTRUCTIONS = """# Plan Mode (Conversational)
7
6
 
8
7
  Work with the user to produce a decision-complete implementation plan before any implementation.
@@ -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: ") or line.startswith("*** Update File: ") or line.startswith("*** Delete 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)