agentlink-cli 0.1.9__tar.gz → 0.2.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.
- {agentlink_cli-0.1.9 → agentlink_cli-0.2.1}/PKG-INFO +1 -1
- {agentlink_cli-0.1.9 → agentlink_cli-0.2.1}/connector/local/common.py +1 -3
- {agentlink_cli-0.1.9 → agentlink_cli-0.2.1}/connector/runtime.py +26 -2
- {agentlink_cli-0.1.9 → agentlink_cli-0.2.1}/connector/version.py +1 -1
- {agentlink_cli-0.1.9 → agentlink_cli-0.2.1}/pyproject.toml +1 -1
- {agentlink_cli-0.1.9 → agentlink_cli-0.2.1}/tests/test_connector_runtime.py +38 -1
- {agentlink_cli-0.1.9 → agentlink_cli-0.2.1}/tests/test_version.py +1 -1
- {agentlink_cli-0.1.9 → agentlink_cli-0.2.1}/uv.lock +1 -1
- {agentlink_cli-0.1.9 → agentlink_cli-0.2.1}/.gitignore +0 -0
- {agentlink_cli-0.1.9 → agentlink_cli-0.2.1}/README.md +0 -0
- {agentlink_cli-0.1.9 → agentlink_cli-0.2.1}/connector/__init__.py +0 -0
- {agentlink_cli-0.1.9 → agentlink_cli-0.2.1}/connector/acp/__init__.py +0 -0
- {agentlink_cli-0.1.9 → agentlink_cli-0.2.1}/connector/acp/adapter.py +0 -0
- {agentlink_cli-0.1.9 → agentlink_cli-0.2.1}/connector/acp/config_options.py +0 -0
- {agentlink_cli-0.1.9 → agentlink_cli-0.2.1}/connector/acp/discovery.py +0 -0
- {agentlink_cli-0.1.9 → agentlink_cli-0.2.1}/connector/acp/manifest.py +0 -0
- {agentlink_cli-0.1.9 → agentlink_cli-0.2.1}/connector/acp/manifests/__init__.py +0 -0
- {agentlink_cli-0.1.9 → agentlink_cli-0.2.1}/connector/acp/manifests/codebuddy.json +0 -0
- {agentlink_cli-0.1.9 → agentlink_cli-0.2.1}/connector/acp/manifests/cursor.json +0 -0
- {agentlink_cli-0.1.9 → agentlink_cli-0.2.1}/connector/acp/manifests/gemini.json +0 -0
- {agentlink_cli-0.1.9 → agentlink_cli-0.2.1}/connector/acp/manifests/grok_build.json +0 -0
- {agentlink_cli-0.1.9 → agentlink_cli-0.2.1}/connector/acp/reducer.py +0 -0
- {agentlink_cli-0.1.9 → agentlink_cli-0.2.1}/connector/acp/rpc.py +0 -0
- {agentlink_cli-0.1.9 → agentlink_cli-0.2.1}/connector/adapter.py +0 -0
- {agentlink_cli-0.1.9 → agentlink_cli-0.2.1}/connector/attachments.py +0 -0
- {agentlink_cli-0.1.9 → agentlink_cli-0.2.1}/connector/capabilities.py +0 -0
- {agentlink_cli-0.1.9 → agentlink_cli-0.2.1}/connector/claude/__init__.py +0 -0
- {agentlink_cli-0.1.9 → agentlink_cli-0.2.1}/connector/claude/history_adapter.py +0 -0
- {agentlink_cli-0.1.9 → agentlink_cli-0.2.1}/connector/claude/normalized.py +0 -0
- {agentlink_cli-0.1.9 → agentlink_cli-0.2.1}/connector/claude/normalizers.py +0 -0
- {agentlink_cli-0.1.9 → agentlink_cli-0.2.1}/connector/claude/path_utils.py +0 -0
- {agentlink_cli-0.1.9 → agentlink_cli-0.2.1}/connector/claude/preferences.py +0 -0
- {agentlink_cli-0.1.9 → agentlink_cli-0.2.1}/connector/claude/sdk_adapter.py +0 -0
- {agentlink_cli-0.1.9 → agentlink_cli-0.2.1}/connector/claude/timeline_identity.py +0 -0
- {agentlink_cli-0.1.9 → agentlink_cli-0.2.1}/connector/claude/timeline_reducer.py +0 -0
- {agentlink_cli-0.1.9 → agentlink_cli-0.2.1}/connector/claude/trust.py +0 -0
- {agentlink_cli-0.1.9 → agentlink_cli-0.2.1}/connector/cli.py +0 -0
- {agentlink_cli-0.1.9 → agentlink_cli-0.2.1}/connector/codex/__init__.py +0 -0
- {agentlink_cli-0.1.9 → agentlink_cli-0.2.1}/connector/codex/adapter.py +0 -0
- {agentlink_cli-0.1.9 → agentlink_cli-0.2.1}/connector/codex/collaboration.py +0 -0
- {agentlink_cli-0.1.9 → agentlink_cli-0.2.1}/connector/codex/history.py +0 -0
- {agentlink_cli-0.1.9 → agentlink_cli-0.2.1}/connector/codex/reducer.py +0 -0
- {agentlink_cli-0.1.9 → agentlink_cli-0.2.1}/connector/codex/rpc.py +0 -0
- {agentlink_cli-0.1.9 → agentlink_cli-0.2.1}/connector/control.py +0 -0
- {agentlink_cli-0.1.9 → agentlink_cli-0.2.1}/connector/json_rpc.py +0 -0
- {agentlink_cli-0.1.9 → agentlink_cli-0.2.1}/connector/launch.py +0 -0
- {agentlink_cli-0.1.9 → agentlink_cli-0.2.1}/connector/local/__init__.py +0 -0
- {agentlink_cli-0.1.9 → agentlink_cli-0.2.1}/connector/local/file_ops.py +0 -0
- {agentlink_cli-0.1.9 → agentlink_cli-0.2.1}/connector/local/ops.py +0 -0
- {agentlink_cli-0.1.9 → agentlink_cli-0.2.1}/connector/local/shell.py +0 -0
- {agentlink_cli-0.1.9 → agentlink_cli-0.2.1}/connector/local/terminal.py +0 -0
- {agentlink_cli-0.1.9 → agentlink_cli-0.2.1}/connector/local_ops.py +0 -0
- {agentlink_cli-0.1.9 → agentlink_cli-0.2.1}/connector/local_runtime.py +0 -0
- {agentlink_cli-0.1.9 → agentlink_cli-0.2.1}/connector/logging.py +0 -0
- {agentlink_cli-0.1.9 → agentlink_cli-0.2.1}/connector/perf.py +0 -0
- {agentlink_cli-0.1.9 → agentlink_cli-0.2.1}/connector/protocol.py +0 -0
- {agentlink_cli-0.1.9 → agentlink_cli-0.2.1}/connector/registry.py +0 -0
- {agentlink_cli-0.1.9 → agentlink_cli-0.2.1}/connector/sync_state.py +0 -0
- {agentlink_cli-0.1.9 → agentlink_cli-0.2.1}/connector/time.py +0 -0
- {agentlink_cli-0.1.9 → agentlink_cli-0.2.1}/run.sh +0 -0
- {agentlink_cli-0.1.9 → agentlink_cli-0.2.1}/tests/__init__.py +0 -0
- {agentlink_cli-0.1.9 → agentlink_cli-0.2.1}/tests/acp/__init__.py +0 -0
- {agentlink_cli-0.1.9 → agentlink_cli-0.2.1}/tests/acp/fake_agent/__init__.py +0 -0
- {agentlink_cli-0.1.9 → agentlink_cli-0.2.1}/tests/acp/fake_agent/__main__.py +0 -0
- {agentlink_cli-0.1.9 → agentlink_cli-0.2.1}/tests/acp/test_acp_adapter_fake.py +0 -0
- {agentlink_cli-0.1.9 → agentlink_cli-0.2.1}/tests/acp/test_acp_reducer_golden.py +0 -0
- {agentlink_cli-0.1.9 → agentlink_cli-0.2.1}/tests/acp/test_config_options.py +0 -0
- {agentlink_cli-0.1.9 → agentlink_cli-0.2.1}/tests/contract/__init__.py +0 -0
- {agentlink_cli-0.1.9 → agentlink_cli-0.2.1}/tests/contract/helpers.py +0 -0
- {agentlink_cli-0.1.9 → agentlink_cli-0.2.1}/tests/test_claude_history_adapter.py +0 -0
- {agentlink_cli-0.1.9 → agentlink_cli-0.2.1}/tests/test_claude_preferences.py +0 -0
- {agentlink_cli-0.1.9 → agentlink_cli-0.2.1}/tests/test_claude_sdk_adapter.py +0 -0
- {agentlink_cli-0.1.9 → agentlink_cli-0.2.1}/tests/test_claude_timeline_parity.py +0 -0
- {agentlink_cli-0.1.9 → agentlink_cli-0.2.1}/tests/test_claude_trust.py +0 -0
- {agentlink_cli-0.1.9 → agentlink_cli-0.2.1}/tests/test_codex_adapter.py +0 -0
- {agentlink_cli-0.1.9 → agentlink_cli-0.2.1}/tests/test_connector_capabilities.py +0 -0
- {agentlink_cli-0.1.9 → agentlink_cli-0.2.1}/tests/test_connector_cli.py +0 -0
- {agentlink_cli-0.1.9 → agentlink_cli-0.2.1}/tests/test_connector_control.py +0 -0
- {agentlink_cli-0.1.9 → agentlink_cli-0.2.1}/tests/test_connector_json_rpc.py +0 -0
- {agentlink_cli-0.1.9 → agentlink_cli-0.2.1}/tests/test_connector_logging.py +0 -0
- {agentlink_cli-0.1.9 → agentlink_cli-0.2.1}/tests/test_launch_command.py +0 -0
- {agentlink_cli-0.1.9 → agentlink_cli-0.2.1}/tests/test_local_runtime.py +0 -0
- {agentlink_cli-0.1.9 → agentlink_cli-0.2.1}/tests/test_perf.py +0 -0
- {agentlink_cli-0.1.9 → agentlink_cli-0.2.1}/tests/test_terminal_backend.py +0 -0
|
@@ -40,10 +40,8 @@ def workspace_root(params: dict[str, Any]) -> Path:
|
|
|
40
40
|
|
|
41
41
|
|
|
42
42
|
def resolve_path(root: Path, raw_path: str) -> Path:
|
|
43
|
-
if raw_path.startswith("~"):
|
|
44
|
-
raise WorkspaceBoundaryError("path must stay within the workspace")
|
|
45
43
|
root = root.resolve(strict=False)
|
|
46
|
-
path = Path(raw_path)
|
|
44
|
+
path = Path(raw_path).expanduser()
|
|
47
45
|
if not path.is_absolute():
|
|
48
46
|
path = root / path
|
|
49
47
|
resolved = path.resolve(strict=False)
|
|
@@ -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":
|
|
@@ -658,6 +660,8 @@ class BackendRpcClient:
|
|
|
658
660
|
Exception: the first assistant text delta for a turn bypasses the
|
|
659
661
|
window so TTFB is not inflated by FLUSH_WINDOW_SECONDS.
|
|
660
662
|
"""
|
|
663
|
+
if method == "session.updated" and isinstance(params, dict) and params.get("runtime") == "codex":
|
|
664
|
+
params = {**params, "runtimeLaunch": self._codex_launch_snapshot()}
|
|
661
665
|
notification = {"method": method, "params": params}
|
|
662
666
|
if self._outbox is not None:
|
|
663
667
|
await asyncio.to_thread(self._outbox.add, method, params)
|
|
@@ -971,9 +975,12 @@ class BackendRpcClient:
|
|
|
971
975
|
self._codex_launch_spec = {
|
|
972
976
|
"mode": mode,
|
|
973
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 {}),
|
|
974
981
|
}
|
|
975
982
|
self._blocked_runtimes.discard("codex")
|
|
976
|
-
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"}}
|
|
977
984
|
self._record_runtime_report("codex", report)
|
|
978
985
|
except Exception:
|
|
979
986
|
codex.rpc = old_rpc
|
|
@@ -989,7 +996,11 @@ class BackendRpcClient:
|
|
|
989
996
|
async def _apply_codex_launch_spec(self, spec: dict[str, Any]) -> None:
|
|
990
997
|
mode = str(spec.get("mode") or "auto")
|
|
991
998
|
command = spec.get("command") if isinstance(spec.get("command"), str) else None
|
|
992
|
-
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
|
+
}
|
|
993
1004
|
if normalized == self._codex_launch_spec and "codex" not in self._blocked_runtimes:
|
|
994
1005
|
return
|
|
995
1006
|
await self._configure_codex_launch({"runtime": "codex", **normalized})
|
|
@@ -1006,6 +1017,7 @@ class BackendRpcClient:
|
|
|
1006
1017
|
self._codex_launch_spec = {
|
|
1007
1018
|
"mode": str(spec.get("mode") or "auto"),
|
|
1008
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)}),
|
|
1009
1021
|
}
|
|
1010
1022
|
if self._codex_launch_spec.get("mode") == "command":
|
|
1011
1023
|
self._blocked_runtimes.add("codex")
|
|
@@ -1161,8 +1173,20 @@ class BackendRpcClient:
|
|
|
1161
1173
|
method = notification.get("method")
|
|
1162
1174
|
params = notification.get("params")
|
|
1163
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()}
|
|
1164
1178
|
await self.ingest_notifications([{"method": method, "params": params}])
|
|
1165
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
|
+
|
|
1166
1190
|
async def enqueue_backend_notifications(self, notifications: list[dict[str, Any]]) -> None:
|
|
1167
1191
|
for notification in notifications:
|
|
1168
1192
|
if not isinstance(notification, dict):
|
|
@@ -324,6 +324,39 @@ def test_connector_rejects_workspace_symlink_escape(tmp_path) -> None:
|
|
|
324
324
|
asyncio.run(exercise())
|
|
325
325
|
|
|
326
326
|
|
|
327
|
+
def test_connector_accepts_home_shorthand_within_home(tmp_path, monkeypatch) -> None:
|
|
328
|
+
async def exercise() -> None:
|
|
329
|
+
home = tmp_path / "home"
|
|
330
|
+
child = home / "project"
|
|
331
|
+
child.mkdir(parents=True)
|
|
332
|
+
(child / "README.md").write_text("home workspace", encoding="utf-8")
|
|
333
|
+
monkeypatch.setenv("HOME", str(home))
|
|
334
|
+
monkeypatch.setenv("USERPROFILE", str(home))
|
|
335
|
+
|
|
336
|
+
client = BackendRpcClient(
|
|
337
|
+
ConnectorConfig("http://127.0.0.1:8000", "conn_1", "secret"),
|
|
338
|
+
adapters={"codex": FakeAdapter()},
|
|
339
|
+
)
|
|
340
|
+
home_result = await client.dispatch("fs.readDir", {"root": "~", "path": "~"})
|
|
341
|
+
assert home_result["path"] == str(home)
|
|
342
|
+
assert [entry["name"] for entry in home_result["entries"]] == ["project"]
|
|
343
|
+
|
|
344
|
+
child_result = await client.dispatch(
|
|
345
|
+
"fs.readDir", {"root": "~", "path": "~/project"}
|
|
346
|
+
)
|
|
347
|
+
assert child_result["path"] == str(child)
|
|
348
|
+
assert [entry["name"] for entry in child_result["entries"]] == ["README.md"]
|
|
349
|
+
|
|
350
|
+
workspace = tmp_path / "workspace"
|
|
351
|
+
workspace.mkdir()
|
|
352
|
+
with pytest.raises(WorkspaceBoundaryError):
|
|
353
|
+
await client.dispatch(
|
|
354
|
+
"fs.readDir", {"root": str(workspace), "path": "~"}
|
|
355
|
+
)
|
|
356
|
+
|
|
357
|
+
asyncio.run(exercise())
|
|
358
|
+
|
|
359
|
+
|
|
327
360
|
def test_notification_outbox_survives_reopen(tmp_path) -> None:
|
|
328
361
|
path = tmp_path / "state.sqlite3"
|
|
329
362
|
outbox = SqliteNotificationOutbox(
|
|
@@ -551,7 +584,11 @@ async def _exercise_runtime() -> None:
|
|
|
551
584
|
"id": "rpc_1",
|
|
552
585
|
"type": "response",
|
|
553
586
|
"ok": True,
|
|
554
|
-
"result": {
|
|
587
|
+
"result": {
|
|
588
|
+
"sessionId": "sess_1",
|
|
589
|
+
"externalSessionId": "thr_1",
|
|
590
|
+
"runtimeLaunch": {"mode": "auto", "source": "auto"},
|
|
591
|
+
},
|
|
555
592
|
}
|
|
556
593
|
|
|
557
594
|
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
|
|
File without changes
|