agentlink-cli 0.1.8__tar.gz → 0.2.0__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.8 → agentlink_cli-0.2.0}/PKG-INFO +1 -1
- {agentlink_cli-0.1.8 → agentlink_cli-0.2.0}/connector/codex/adapter.py +11 -0
- {agentlink_cli-0.1.8 → agentlink_cli-0.2.0}/connector/runtime.py +36 -3
- {agentlink_cli-0.1.8 → agentlink_cli-0.2.0}/connector/version.py +1 -1
- {agentlink_cli-0.1.8 → agentlink_cli-0.2.0}/pyproject.toml +1 -1
- {agentlink_cli-0.1.8 → agentlink_cli-0.2.0}/tests/test_codex_adapter.py +60 -0
- {agentlink_cli-0.1.8 → agentlink_cli-0.2.0}/tests/test_connector_runtime.py +69 -1
- {agentlink_cli-0.1.8 → agentlink_cli-0.2.0}/tests/test_version.py +1 -1
- {agentlink_cli-0.1.8 → agentlink_cli-0.2.0}/uv.lock +1 -1
- {agentlink_cli-0.1.8 → agentlink_cli-0.2.0}/.gitignore +0 -0
- {agentlink_cli-0.1.8 → agentlink_cli-0.2.0}/README.md +0 -0
- {agentlink_cli-0.1.8 → agentlink_cli-0.2.0}/connector/__init__.py +0 -0
- {agentlink_cli-0.1.8 → agentlink_cli-0.2.0}/connector/acp/__init__.py +0 -0
- {agentlink_cli-0.1.8 → agentlink_cli-0.2.0}/connector/acp/adapter.py +0 -0
- {agentlink_cli-0.1.8 → agentlink_cli-0.2.0}/connector/acp/config_options.py +0 -0
- {agentlink_cli-0.1.8 → agentlink_cli-0.2.0}/connector/acp/discovery.py +0 -0
- {agentlink_cli-0.1.8 → agentlink_cli-0.2.0}/connector/acp/manifest.py +0 -0
- {agentlink_cli-0.1.8 → agentlink_cli-0.2.0}/connector/acp/manifests/__init__.py +0 -0
- {agentlink_cli-0.1.8 → agentlink_cli-0.2.0}/connector/acp/manifests/codebuddy.json +0 -0
- {agentlink_cli-0.1.8 → agentlink_cli-0.2.0}/connector/acp/manifests/cursor.json +0 -0
- {agentlink_cli-0.1.8 → agentlink_cli-0.2.0}/connector/acp/manifests/gemini.json +0 -0
- {agentlink_cli-0.1.8 → agentlink_cli-0.2.0}/connector/acp/manifests/grok_build.json +0 -0
- {agentlink_cli-0.1.8 → agentlink_cli-0.2.0}/connector/acp/reducer.py +0 -0
- {agentlink_cli-0.1.8 → agentlink_cli-0.2.0}/connector/acp/rpc.py +0 -0
- {agentlink_cli-0.1.8 → agentlink_cli-0.2.0}/connector/adapter.py +0 -0
- {agentlink_cli-0.1.8 → agentlink_cli-0.2.0}/connector/attachments.py +0 -0
- {agentlink_cli-0.1.8 → agentlink_cli-0.2.0}/connector/capabilities.py +0 -0
- {agentlink_cli-0.1.8 → agentlink_cli-0.2.0}/connector/claude/__init__.py +0 -0
- {agentlink_cli-0.1.8 → agentlink_cli-0.2.0}/connector/claude/history_adapter.py +0 -0
- {agentlink_cli-0.1.8 → agentlink_cli-0.2.0}/connector/claude/normalized.py +0 -0
- {agentlink_cli-0.1.8 → agentlink_cli-0.2.0}/connector/claude/normalizers.py +0 -0
- {agentlink_cli-0.1.8 → agentlink_cli-0.2.0}/connector/claude/path_utils.py +0 -0
- {agentlink_cli-0.1.8 → agentlink_cli-0.2.0}/connector/claude/preferences.py +0 -0
- {agentlink_cli-0.1.8 → agentlink_cli-0.2.0}/connector/claude/sdk_adapter.py +0 -0
- {agentlink_cli-0.1.8 → agentlink_cli-0.2.0}/connector/claude/timeline_identity.py +0 -0
- {agentlink_cli-0.1.8 → agentlink_cli-0.2.0}/connector/claude/timeline_reducer.py +0 -0
- {agentlink_cli-0.1.8 → agentlink_cli-0.2.0}/connector/claude/trust.py +0 -0
- {agentlink_cli-0.1.8 → agentlink_cli-0.2.0}/connector/cli.py +0 -0
- {agentlink_cli-0.1.8 → agentlink_cli-0.2.0}/connector/codex/__init__.py +0 -0
- {agentlink_cli-0.1.8 → agentlink_cli-0.2.0}/connector/codex/collaboration.py +0 -0
- {agentlink_cli-0.1.8 → agentlink_cli-0.2.0}/connector/codex/history.py +0 -0
- {agentlink_cli-0.1.8 → agentlink_cli-0.2.0}/connector/codex/reducer.py +0 -0
- {agentlink_cli-0.1.8 → agentlink_cli-0.2.0}/connector/codex/rpc.py +0 -0
- {agentlink_cli-0.1.8 → agentlink_cli-0.2.0}/connector/control.py +0 -0
- {agentlink_cli-0.1.8 → agentlink_cli-0.2.0}/connector/json_rpc.py +0 -0
- {agentlink_cli-0.1.8 → agentlink_cli-0.2.0}/connector/launch.py +0 -0
- {agentlink_cli-0.1.8 → agentlink_cli-0.2.0}/connector/local/__init__.py +0 -0
- {agentlink_cli-0.1.8 → agentlink_cli-0.2.0}/connector/local/common.py +0 -0
- {agentlink_cli-0.1.8 → agentlink_cli-0.2.0}/connector/local/file_ops.py +0 -0
- {agentlink_cli-0.1.8 → agentlink_cli-0.2.0}/connector/local/ops.py +0 -0
- {agentlink_cli-0.1.8 → agentlink_cli-0.2.0}/connector/local/shell.py +0 -0
- {agentlink_cli-0.1.8 → agentlink_cli-0.2.0}/connector/local/terminal.py +0 -0
- {agentlink_cli-0.1.8 → agentlink_cli-0.2.0}/connector/local_ops.py +0 -0
- {agentlink_cli-0.1.8 → agentlink_cli-0.2.0}/connector/local_runtime.py +0 -0
- {agentlink_cli-0.1.8 → agentlink_cli-0.2.0}/connector/logging.py +0 -0
- {agentlink_cli-0.1.8 → agentlink_cli-0.2.0}/connector/perf.py +0 -0
- {agentlink_cli-0.1.8 → agentlink_cli-0.2.0}/connector/protocol.py +0 -0
- {agentlink_cli-0.1.8 → agentlink_cli-0.2.0}/connector/registry.py +0 -0
- {agentlink_cli-0.1.8 → agentlink_cli-0.2.0}/connector/sync_state.py +0 -0
- {agentlink_cli-0.1.8 → agentlink_cli-0.2.0}/connector/time.py +0 -0
- {agentlink_cli-0.1.8 → agentlink_cli-0.2.0}/run.sh +0 -0
- {agentlink_cli-0.1.8 → agentlink_cli-0.2.0}/tests/__init__.py +0 -0
- {agentlink_cli-0.1.8 → agentlink_cli-0.2.0}/tests/acp/__init__.py +0 -0
- {agentlink_cli-0.1.8 → agentlink_cli-0.2.0}/tests/acp/fake_agent/__init__.py +0 -0
- {agentlink_cli-0.1.8 → agentlink_cli-0.2.0}/tests/acp/fake_agent/__main__.py +0 -0
- {agentlink_cli-0.1.8 → agentlink_cli-0.2.0}/tests/acp/test_acp_adapter_fake.py +0 -0
- {agentlink_cli-0.1.8 → agentlink_cli-0.2.0}/tests/acp/test_acp_reducer_golden.py +0 -0
- {agentlink_cli-0.1.8 → agentlink_cli-0.2.0}/tests/acp/test_config_options.py +0 -0
- {agentlink_cli-0.1.8 → agentlink_cli-0.2.0}/tests/contract/__init__.py +0 -0
- {agentlink_cli-0.1.8 → agentlink_cli-0.2.0}/tests/contract/helpers.py +0 -0
- {agentlink_cli-0.1.8 → agentlink_cli-0.2.0}/tests/test_claude_history_adapter.py +0 -0
- {agentlink_cli-0.1.8 → agentlink_cli-0.2.0}/tests/test_claude_preferences.py +0 -0
- {agentlink_cli-0.1.8 → agentlink_cli-0.2.0}/tests/test_claude_sdk_adapter.py +0 -0
- {agentlink_cli-0.1.8 → agentlink_cli-0.2.0}/tests/test_claude_timeline_parity.py +0 -0
- {agentlink_cli-0.1.8 → agentlink_cli-0.2.0}/tests/test_claude_trust.py +0 -0
- {agentlink_cli-0.1.8 → agentlink_cli-0.2.0}/tests/test_connector_capabilities.py +0 -0
- {agentlink_cli-0.1.8 → agentlink_cli-0.2.0}/tests/test_connector_cli.py +0 -0
- {agentlink_cli-0.1.8 → agentlink_cli-0.2.0}/tests/test_connector_control.py +0 -0
- {agentlink_cli-0.1.8 → agentlink_cli-0.2.0}/tests/test_connector_json_rpc.py +0 -0
- {agentlink_cli-0.1.8 → agentlink_cli-0.2.0}/tests/test_connector_logging.py +0 -0
- {agentlink_cli-0.1.8 → agentlink_cli-0.2.0}/tests/test_launch_command.py +0 -0
- {agentlink_cli-0.1.8 → agentlink_cli-0.2.0}/tests/test_local_runtime.py +0 -0
- {agentlink_cli-0.1.8 → agentlink_cli-0.2.0}/tests/test_perf.py +0 -0
- {agentlink_cli-0.1.8 → agentlink_cli-0.2.0}/tests/test_terminal_backend.py +0 -0
|
@@ -585,6 +585,17 @@ class CodexAdapter:
|
|
|
585
585
|
raw_args=raw_args,
|
|
586
586
|
phase="started",
|
|
587
587
|
)
|
|
588
|
+
if self.notification_sink is not None:
|
|
589
|
+
for notification in _item_notifications(session_id, started_items):
|
|
590
|
+
try:
|
|
591
|
+
await self.notification_sink(notification["method"], notification["params"])
|
|
592
|
+
except Exception as exc: # noqa: BLE001
|
|
593
|
+
logger.warning(
|
|
594
|
+
"codex command started notification enqueue failed session_id={} command={} error={}",
|
|
595
|
+
session_id,
|
|
596
|
+
command,
|
|
597
|
+
exc,
|
|
598
|
+
)
|
|
588
599
|
try:
|
|
589
600
|
if command == "compact":
|
|
590
601
|
await self.rpc.request("thread/compact/start", {"threadId": thread_id})
|
|
@@ -480,6 +480,8 @@ class BackendRpcClient:
|
|
|
480
480
|
if method == "session.create":
|
|
481
481
|
adapter = self._resolve_adapter(params)
|
|
482
482
|
result = await adapter.create_session({**params, "connectorId": self.config.connector_id})
|
|
483
|
+
if isinstance(result, dict) and adapter is self.adapters.get("codex"):
|
|
484
|
+
result.setdefault("runtimeLaunch", self._codex_launch_snapshot())
|
|
483
485
|
await self._send_backend_notifications(result)
|
|
484
486
|
return _strip_backend_notifications(result)
|
|
485
487
|
if method == "session.sync":
|
|
@@ -497,7 +499,16 @@ class BackendRpcClient:
|
|
|
497
499
|
if execute_command is None:
|
|
498
500
|
raise ValueError("command_unavailable")
|
|
499
501
|
result = await execute_command({**params, "connectorId": self.config.connector_id})
|
|
500
|
-
|
|
502
|
+
notifications = result.get("backendNotifications", [])
|
|
503
|
+
if isinstance(notifications, list):
|
|
504
|
+
try:
|
|
505
|
+
await self.enqueue_backend_notifications(notifications)
|
|
506
|
+
except Exception: # noqa: BLE001
|
|
507
|
+
logger.exception(
|
|
508
|
+
"command timeline notification enqueue failed session_id={} command={}",
|
|
509
|
+
params.get("sessionId"),
|
|
510
|
+
params.get("command"),
|
|
511
|
+
)
|
|
501
512
|
return _strip_backend_notifications(result)
|
|
502
513
|
if method == "turn.interrupt":
|
|
503
514
|
return await self._resolve_adapter(params).interrupt_turn(params)
|
|
@@ -649,6 +660,8 @@ class BackendRpcClient:
|
|
|
649
660
|
Exception: the first assistant text delta for a turn bypasses the
|
|
650
661
|
window so TTFB is not inflated by FLUSH_WINDOW_SECONDS.
|
|
651
662
|
"""
|
|
663
|
+
if method == "session.updated" and isinstance(params, dict) and params.get("runtime") == "codex":
|
|
664
|
+
params = {**params, "runtimeLaunch": self._codex_launch_snapshot()}
|
|
652
665
|
notification = {"method": method, "params": params}
|
|
653
666
|
if self._outbox is not None:
|
|
654
667
|
await asyncio.to_thread(self._outbox.add, method, params)
|
|
@@ -962,9 +975,12 @@ class BackendRpcClient:
|
|
|
962
975
|
self._codex_launch_spec = {
|
|
963
976
|
"mode": mode,
|
|
964
977
|
**({"command": launch.raw} if mode == "command" else {}),
|
|
978
|
+
**({"source": str(params["source"])} if params.get("source") in {"builtin", "preset", "custom", "auto"} else {}),
|
|
979
|
+
**({"name": str(params["name"])} if isinstance(params.get("name"), str) and params["name"].strip() else {}),
|
|
980
|
+
**({"presetId": str(params["presetId"])} if isinstance(params.get("presetId"), str) and params["presetId"].strip() else {}),
|
|
965
981
|
}
|
|
966
982
|
self._blocked_runtimes.discard("codex")
|
|
967
|
-
report["launch"] = launch.report(mode=mode)
|
|
983
|
+
report["launch"] = {**launch.report(mode=mode), **{k: v for k, v in self._codex_launch_spec.items() if k != "mode"}}
|
|
968
984
|
self._record_runtime_report("codex", report)
|
|
969
985
|
except Exception:
|
|
970
986
|
codex.rpc = old_rpc
|
|
@@ -980,7 +996,11 @@ class BackendRpcClient:
|
|
|
980
996
|
async def _apply_codex_launch_spec(self, spec: dict[str, Any]) -> None:
|
|
981
997
|
mode = str(spec.get("mode") or "auto")
|
|
982
998
|
command = spec.get("command") if isinstance(spec.get("command"), str) else None
|
|
983
|
-
normalized = {
|
|
999
|
+
normalized = {
|
|
1000
|
+
"mode": mode,
|
|
1001
|
+
**({"command": command} if command else {}),
|
|
1002
|
+
**({key: spec[key] for key in ("source", "name", "presetId") if isinstance(spec.get(key), str)}),
|
|
1003
|
+
}
|
|
984
1004
|
if normalized == self._codex_launch_spec and "codex" not in self._blocked_runtimes:
|
|
985
1005
|
return
|
|
986
1006
|
await self._configure_codex_launch({"runtime": "codex", **normalized})
|
|
@@ -997,6 +1017,7 @@ class BackendRpcClient:
|
|
|
997
1017
|
self._codex_launch_spec = {
|
|
998
1018
|
"mode": str(spec.get("mode") or "auto"),
|
|
999
1019
|
**({"command": spec["command"]} if isinstance(spec.get("command"), str) else {}),
|
|
1020
|
+
**({key: spec[key] for key in ("source", "name", "presetId") if isinstance(spec.get(key), str)}),
|
|
1000
1021
|
}
|
|
1001
1022
|
if self._codex_launch_spec.get("mode") == "command":
|
|
1002
1023
|
self._blocked_runtimes.add("codex")
|
|
@@ -1152,8 +1173,20 @@ class BackendRpcClient:
|
|
|
1152
1173
|
method = notification.get("method")
|
|
1153
1174
|
params = notification.get("params")
|
|
1154
1175
|
if isinstance(method, str) and isinstance(params, dict):
|
|
1176
|
+
if method == "session.updated" and params.get("runtime") == "codex":
|
|
1177
|
+
params = {**params, "runtimeLaunch": self._codex_launch_snapshot()}
|
|
1155
1178
|
await self.ingest_notifications([{"method": method, "params": params}])
|
|
1156
1179
|
|
|
1180
|
+
def _codex_launch_snapshot(self) -> dict[str, Any]:
|
|
1181
|
+
spec = self._codex_launch_spec
|
|
1182
|
+
mode = "command" if spec.get("mode") == "command" else "auto"
|
|
1183
|
+
snapshot: dict[str, Any] = {"mode": mode, "source": spec.get("source") or ("auto" if mode == "auto" else "custom")}
|
|
1184
|
+
for key in ("source", "name", "presetId", "command"):
|
|
1185
|
+
value = spec.get(key)
|
|
1186
|
+
if isinstance(value, str) and value:
|
|
1187
|
+
snapshot[key] = value
|
|
1188
|
+
return snapshot
|
|
1189
|
+
|
|
1157
1190
|
async def enqueue_backend_notifications(self, notifications: list[dict[str, Any]]) -> None:
|
|
1158
1191
|
for notification in notifications:
|
|
1159
1192
|
if not isinstance(notification, dict):
|
|
@@ -1149,6 +1149,66 @@ def test_codex_commands_use_native_app_server_methods() -> None:
|
|
|
1149
1149
|
asyncio.run(exercise())
|
|
1150
1150
|
|
|
1151
1151
|
|
|
1152
|
+
def test_review_command_publishes_started_items_before_native_review_events() -> None:
|
|
1153
|
+
async def exercise() -> None:
|
|
1154
|
+
published: list[tuple[str, dict[str, Any]]] = []
|
|
1155
|
+
|
|
1156
|
+
class ReviewRpc(FakeCodexRpc):
|
|
1157
|
+
async def request(self, method: str, params: dict[str, Any] | None = None) -> dict[str, Any]:
|
|
1158
|
+
if method == "review/start":
|
|
1159
|
+
assert [entry[1]["item"]["content"].get("phase") for entry in published] == [None, "started"]
|
|
1160
|
+
return {"turn": {"id": "turn_review"}}
|
|
1161
|
+
return await super().request(method, params)
|
|
1162
|
+
|
|
1163
|
+
async def sink(method: str, params: dict[str, Any]) -> None:
|
|
1164
|
+
published.append((method, params))
|
|
1165
|
+
|
|
1166
|
+
adapter = CodexAdapter(rpc=ReviewRpc(), notification_sink=sink) # type: ignore[arg-type]
|
|
1167
|
+
result = await adapter.execute_command(
|
|
1168
|
+
{
|
|
1169
|
+
"sessionId": "sess_1",
|
|
1170
|
+
"externalSessionId": "thr_1",
|
|
1171
|
+
"command": "review",
|
|
1172
|
+
"clientCommandId": "cmd_review",
|
|
1173
|
+
}
|
|
1174
|
+
)
|
|
1175
|
+
|
|
1176
|
+
started_user = published[0][1]["item"]
|
|
1177
|
+
started_command = published[1][1]["item"]
|
|
1178
|
+
completed_user = result["backendNotifications"][0]["params"]["item"]
|
|
1179
|
+
accepted_command = result["backendNotifications"][1]["params"]["item"]
|
|
1180
|
+
assert started_user["id"] == completed_user["id"]
|
|
1181
|
+
assert started_command["id"] == accepted_command["id"]
|
|
1182
|
+
assert accepted_command["content"]["phase"] == "accepted"
|
|
1183
|
+
assert accepted_command["content"]["data"]["turnId"] == "turn_review"
|
|
1184
|
+
|
|
1185
|
+
asyncio.run(exercise())
|
|
1186
|
+
|
|
1187
|
+
|
|
1188
|
+
def test_review_command_continues_when_started_notification_enqueue_fails() -> None:
|
|
1189
|
+
async def exercise() -> None:
|
|
1190
|
+
rpc = FakeCodexRpc()
|
|
1191
|
+
|
|
1192
|
+
async def failing_sink(_method: str, _params: dict[str, Any]) -> None:
|
|
1193
|
+
raise OSError("temporary ingest failure")
|
|
1194
|
+
|
|
1195
|
+
adapter = CodexAdapter(rpc=rpc, notification_sink=failing_sink) # type: ignore[arg-type]
|
|
1196
|
+
result = await adapter.execute_command(
|
|
1197
|
+
{
|
|
1198
|
+
"sessionId": "sess_1",
|
|
1199
|
+
"externalSessionId": "thr_1",
|
|
1200
|
+
"command": "review",
|
|
1201
|
+
"clientCommandId": "cmd_review",
|
|
1202
|
+
}
|
|
1203
|
+
)
|
|
1204
|
+
|
|
1205
|
+
assert result["status"] == "accepted"
|
|
1206
|
+
assert any(method == "review/start" for method, _params in rpc.requests)
|
|
1207
|
+
assert result["backendNotifications"][1]["params"]["item"]["content"]["phase"] == "accepted"
|
|
1208
|
+
|
|
1209
|
+
asyncio.run(exercise())
|
|
1210
|
+
|
|
1211
|
+
|
|
1152
1212
|
def test_status_command_returns_thread_and_cached_token_usage() -> None:
|
|
1153
1213
|
async def exercise() -> None:
|
|
1154
1214
|
rpc = FakeCodexRpc()
|
|
@@ -142,6 +142,70 @@ def test_connector_runtime_dispatches_request_and_forwards_notifications() -> No
|
|
|
142
142
|
asyncio.run(_exercise_runtime())
|
|
143
143
|
|
|
144
144
|
|
|
145
|
+
def test_connector_runtime_queues_command_notifications_without_synchronous_ingest() -> None:
|
|
146
|
+
async def exercise() -> None:
|
|
147
|
+
class CommandAdapter(FakeAdapter):
|
|
148
|
+
async def execute_command(self, params: dict[str, Any]) -> dict[str, Any]:
|
|
149
|
+
self.calls.append(("command.execute", params))
|
|
150
|
+
return {
|
|
151
|
+
"status": "accepted",
|
|
152
|
+
"backendNotifications": [
|
|
153
|
+
{"method": "timeline.itemUpsert", "params": {"sessionId": "sess_1", "item": {"id": "cmd"}}},
|
|
154
|
+
{"method": "timeline.itemUpsert", "params": {"sessionId": "sess_1", "item": {"id": "result"}}},
|
|
155
|
+
],
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
adapter = CommandAdapter()
|
|
159
|
+
client = BackendRpcClient(
|
|
160
|
+
ConnectorConfig(
|
|
161
|
+
server_url="http://127.0.0.1:8000",
|
|
162
|
+
connector_id="conn_1",
|
|
163
|
+
connector_token="token",
|
|
164
|
+
sync_existing_on_connect=False,
|
|
165
|
+
),
|
|
166
|
+
adapter=adapter, # type: ignore[arg-type]
|
|
167
|
+
)
|
|
168
|
+
queued: list[tuple[str, dict[str, Any]]] = []
|
|
169
|
+
|
|
170
|
+
async def queue(method: str, params: dict[str, Any]) -> None:
|
|
171
|
+
queued.append((method, params))
|
|
172
|
+
|
|
173
|
+
async def fail_sync_ingest(_notifications: list[dict[str, Any]]) -> None:
|
|
174
|
+
raise AssertionError("command notifications must not use synchronous ingest")
|
|
175
|
+
|
|
176
|
+
client.send_backend_notification = queue # type: ignore[method-assign]
|
|
177
|
+
client.ingest_notifications = fail_sync_ingest # type: ignore[method-assign]
|
|
178
|
+
|
|
179
|
+
result = await client.dispatch(
|
|
180
|
+
"command.execute",
|
|
181
|
+
{
|
|
182
|
+
"runtime": "codex",
|
|
183
|
+
"sessionId": "sess_1",
|
|
184
|
+
"externalSessionId": "thr_1",
|
|
185
|
+
"command": "review",
|
|
186
|
+
},
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
assert result == {"status": "accepted"}
|
|
190
|
+
assert [params["item"]["id"] for _method, params in queued] == ["cmd", "result"]
|
|
191
|
+
|
|
192
|
+
async def fail_queue(_method: str, _params: dict[str, Any]) -> None:
|
|
193
|
+
raise OSError("outbox unavailable")
|
|
194
|
+
|
|
195
|
+
client.send_backend_notification = fail_queue # type: ignore[method-assign]
|
|
196
|
+
assert await client.dispatch(
|
|
197
|
+
"command.execute",
|
|
198
|
+
{
|
|
199
|
+
"runtime": "codex",
|
|
200
|
+
"sessionId": "sess_1",
|
|
201
|
+
"externalSessionId": "thr_1",
|
|
202
|
+
"command": "review",
|
|
203
|
+
},
|
|
204
|
+
) == {"status": "accepted"}
|
|
205
|
+
|
|
206
|
+
asyncio.run(exercise())
|
|
207
|
+
|
|
208
|
+
|
|
145
209
|
def test_connector_config_saves_and_loads_local_json(tmp_path) -> None:
|
|
146
210
|
path = tmp_path / "connector.json"
|
|
147
211
|
config = ConnectorConfig(
|
|
@@ -487,7 +551,11 @@ async def _exercise_runtime() -> None:
|
|
|
487
551
|
"id": "rpc_1",
|
|
488
552
|
"type": "response",
|
|
489
553
|
"ok": True,
|
|
490
|
-
"result": {
|
|
554
|
+
"result": {
|
|
555
|
+
"sessionId": "sess_1",
|
|
556
|
+
"externalSessionId": "thr_1",
|
|
557
|
+
"runtimeLaunch": {"mode": "auto", "source": "auto"},
|
|
558
|
+
},
|
|
491
559
|
}
|
|
492
560
|
|
|
493
561
|
await client.handle_message(
|
|
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
|
|
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
|
|
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
|