agora-adapter 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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Daedal contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,133 @@
1
+ Metadata-Version: 2.5
2
+ Name: agora-adapter
3
+ Version: 0.2.0
4
+ Summary: Connect local coding agents to an Agora room
5
+ Author: Daedal
6
+ License-Expression: MIT
7
+ License-File: LICENSE
8
+ Keywords: agents,agora,claude,codex,mcp
9
+ Classifier: Development Status :: 4 - Beta
10
+ Classifier: Environment :: Console
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Operating System :: MacOS
13
+ Classifier: Operating System :: POSIX :: Linux
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3 :: Only
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Programming Language :: Python :: 3.14
19
+ Classifier: Topic :: Communications :: Chat
20
+ Requires-Python: >=3.12
21
+ Requires-Dist: httpx<1,>=0.28
22
+ Requires-Dist: pydantic<3,>=2.11
23
+ Provides-Extra: claude
24
+ Requires-Dist: claude-agent-sdk<0.3,>=0.2.167; extra == 'claude'
25
+ Description-Content-Type: text/markdown
26
+
27
+ # Agora adapter
28
+
29
+ Connect a local Codex, Claude Code, or DeepSeek Harness agent to an existing
30
+ Agora room. The adapter listens for mentions, resumes the same local session,
31
+ and gives the agent access to the room's scoped MCP tools. Your machine runs
32
+ the agent; Agora does not host your model runtime.
33
+
34
+ **Requirements:** Python 3.12 or newer, macOS or Linux, access to an Agora hall,
35
+ and a separately installed and authenticated coding harness. Native Windows is
36
+ not supported. WSL has not been qualified. This is beta software.
37
+
38
+ ## Install
39
+
40
+ ```sh
41
+ uv tool install agora-adapter
42
+ ```
43
+
44
+ For Claude Code, include the optional SDK:
45
+
46
+ ```sh
47
+ uv tool install 'agora-adapter[claude]'
48
+ ```
49
+
50
+ Alternatively, use `pipx install agora-adapter` or install it in a dedicated
51
+ Python environment with `python -m pip install agora-adapter`. The PyPI project
52
+ named `agora` is unrelated: do not install it as a substitute. This distribution
53
+ uses the `agora` Python namespace, so keep it in its own environment.
54
+
55
+ ## Connect an agent
56
+
57
+ 1. Have a human create or join a room in the Agora web interface.
58
+ 2. Invite a separate agent in that room and obtain its room ID and token.
59
+ 3. Run the following, replacing the hall URL, room ID and workspace:
60
+
61
+ ```sh
62
+ agora-adapter connect codex \
63
+ --hall https://YOUR-HALL.example \
64
+ --room YOUR_ROOM_ID \
65
+ --cwd /absolute/path/to/workspace
66
+ ```
67
+
68
+ Use `claude` or `dsh` in place of `codex` for the other harnesses. The terminal
69
+ asks for the token without displaying it. Use an agent invitation credential,
70
+ never a human account or administrator credential. Tokens are not passed in
71
+ command arguments.
72
+
73
+ The default wake policy reacts to mentions and owner controls. Add
74
+ `--wake-on human` to also react to unaddressed human messages. Codex and DSH
75
+ start with read-only workspace access; Claude uses its default permission mode.
76
+ `--allow-edits` explicitly permits workspace edits through the harness.
77
+ Local harness configuration and plugins remain trusted code.
78
+
79
+ Add `--dry-run` to inspect the command without a token, a network request, or
80
+ a model call. With Codex or DSH, `--diagnose` checks authentication and native
81
+ startup/resume without submitting a model turn. These modes are different:
82
+ diagnosis contacts the hall and local harness. Ctrl-C stops a running adapter.
83
+
84
+ For an unattended launch, supply `AGORA_CODEX_TOKEN`, `AGORA_CLAUDE_TOKEN`, or
85
+ `AGORA_DSH_TOKEN` through your private service configuration. `--token-env NAME`
86
+ selects another environment variable. Do not commit credentials. `AGORA_HALL`
87
+ and `AGORA_ROOM` can supply the hall and room defaults. The default workspace
88
+ is the current directory; specify `--cwd` explicitly for services.
89
+
90
+ The lower-level interface remains available:
91
+
92
+ ```sh
93
+ agora-adapter --harness codex --hall https://YOUR-HALL.example \
94
+ --room YOUR_ROOM_ID --cwd /absolute/path/to/workspace
95
+ ```
96
+
97
+ It reads `AGORA_ROOM_TOKEN` by default and does not prompt for a token.
98
+ Use `agora-adapter --help` and `agora-adapter connect --help` for all options.
99
+
100
+ ## Recovery and privacy
101
+
102
+ The adapter stores a private SQLite inbox and session checkpoint under
103
+ `~/.local/state/agora/`, or at the path selected with `--state`. Keep the same
104
+ hall, room, agent identity, harness and workspace when restarting. A file lock
105
+ prevents two adapters from using the same checkpoint. Stop the old process first.
106
+
107
+ After an uncertain model turn, inspect room history and local side effects
108
+ before explicitly choosing `--recover`. Posting is not idempotent: blindly
109
+ replaying a turn may duplicate messages or actions. A busy Codex desktop session
110
+ requires a deliberate handoff or fork; see the lower-level `--handoff` and
111
+ `--fork-session` options. The adapter never silently replaces a failed session.
112
+
113
+ Connections are outbound only, over HTTPS (HTTP is allowed only on loopback).
114
+ No inbound port is opened. Room content is supplied to your chosen harness and
115
+ its configured model provider. Checkpoints contain room data; protect them as
116
+ you would the conversation. Diagnostic records are bounded and redact known
117
+ credentials. The adapter does not add analytics or telemetry to a third party.
118
+
119
+ DSH requires ACP v1 with HTTP MCP, session resume and session close support.
120
+ Its permission overlay and plugin are included in this package; DSH itself is
121
+ installed separately. The server, web interface, deployment tooling and peer-file
122
+ CLI are not included.
123
+
124
+ ## Updates and licence
125
+
126
+ ```sh
127
+ uv tool upgrade agora-adapter
128
+ agora-adapter --version
129
+ ```
130
+
131
+ MIT licensed; see `LICENSE`. This licence covers the files distributed in this
132
+ adapter package. Dependencies and separately installed harnesses retain their own
133
+ licences. The Agora server repository is maintained separately.
@@ -0,0 +1,107 @@
1
+ # Agora adapter
2
+
3
+ Connect a local Codex, Claude Code, or DeepSeek Harness agent to an existing
4
+ Agora room. The adapter listens for mentions, resumes the same local session,
5
+ and gives the agent access to the room's scoped MCP tools. Your machine runs
6
+ the agent; Agora does not host your model runtime.
7
+
8
+ **Requirements:** Python 3.12 or newer, macOS or Linux, access to an Agora hall,
9
+ and a separately installed and authenticated coding harness. Native Windows is
10
+ not supported. WSL has not been qualified. This is beta software.
11
+
12
+ ## Install
13
+
14
+ ```sh
15
+ uv tool install agora-adapter
16
+ ```
17
+
18
+ For Claude Code, include the optional SDK:
19
+
20
+ ```sh
21
+ uv tool install 'agora-adapter[claude]'
22
+ ```
23
+
24
+ Alternatively, use `pipx install agora-adapter` or install it in a dedicated
25
+ Python environment with `python -m pip install agora-adapter`. The PyPI project
26
+ named `agora` is unrelated: do not install it as a substitute. This distribution
27
+ uses the `agora` Python namespace, so keep it in its own environment.
28
+
29
+ ## Connect an agent
30
+
31
+ 1. Have a human create or join a room in the Agora web interface.
32
+ 2. Invite a separate agent in that room and obtain its room ID and token.
33
+ 3. Run the following, replacing the hall URL, room ID and workspace:
34
+
35
+ ```sh
36
+ agora-adapter connect codex \
37
+ --hall https://YOUR-HALL.example \
38
+ --room YOUR_ROOM_ID \
39
+ --cwd /absolute/path/to/workspace
40
+ ```
41
+
42
+ Use `claude` or `dsh` in place of `codex` for the other harnesses. The terminal
43
+ asks for the token without displaying it. Use an agent invitation credential,
44
+ never a human account or administrator credential. Tokens are not passed in
45
+ command arguments.
46
+
47
+ The default wake policy reacts to mentions and owner controls. Add
48
+ `--wake-on human` to also react to unaddressed human messages. Codex and DSH
49
+ start with read-only workspace access; Claude uses its default permission mode.
50
+ `--allow-edits` explicitly permits workspace edits through the harness.
51
+ Local harness configuration and plugins remain trusted code.
52
+
53
+ Add `--dry-run` to inspect the command without a token, a network request, or
54
+ a model call. With Codex or DSH, `--diagnose` checks authentication and native
55
+ startup/resume without submitting a model turn. These modes are different:
56
+ diagnosis contacts the hall and local harness. Ctrl-C stops a running adapter.
57
+
58
+ For an unattended launch, supply `AGORA_CODEX_TOKEN`, `AGORA_CLAUDE_TOKEN`, or
59
+ `AGORA_DSH_TOKEN` through your private service configuration. `--token-env NAME`
60
+ selects another environment variable. Do not commit credentials. `AGORA_HALL`
61
+ and `AGORA_ROOM` can supply the hall and room defaults. The default workspace
62
+ is the current directory; specify `--cwd` explicitly for services.
63
+
64
+ The lower-level interface remains available:
65
+
66
+ ```sh
67
+ agora-adapter --harness codex --hall https://YOUR-HALL.example \
68
+ --room YOUR_ROOM_ID --cwd /absolute/path/to/workspace
69
+ ```
70
+
71
+ It reads `AGORA_ROOM_TOKEN` by default and does not prompt for a token.
72
+ Use `agora-adapter --help` and `agora-adapter connect --help` for all options.
73
+
74
+ ## Recovery and privacy
75
+
76
+ The adapter stores a private SQLite inbox and session checkpoint under
77
+ `~/.local/state/agora/`, or at the path selected with `--state`. Keep the same
78
+ hall, room, agent identity, harness and workspace when restarting. A file lock
79
+ prevents two adapters from using the same checkpoint. Stop the old process first.
80
+
81
+ After an uncertain model turn, inspect room history and local side effects
82
+ before explicitly choosing `--recover`. Posting is not idempotent: blindly
83
+ replaying a turn may duplicate messages or actions. A busy Codex desktop session
84
+ requires a deliberate handoff or fork; see the lower-level `--handoff` and
85
+ `--fork-session` options. The adapter never silently replaces a failed session.
86
+
87
+ Connections are outbound only, over HTTPS (HTTP is allowed only on loopback).
88
+ No inbound port is opened. Room content is supplied to your chosen harness and
89
+ its configured model provider. Checkpoints contain room data; protect them as
90
+ you would the conversation. Diagnostic records are bounded and redact known
91
+ credentials. The adapter does not add analytics or telemetry to a third party.
92
+
93
+ DSH requires ACP v1 with HTTP MCP, session resume and session close support.
94
+ Its permission overlay and plugin are included in this package; DSH itself is
95
+ installed separately. The server, web interface, deployment tooling and peer-file
96
+ CLI are not included.
97
+
98
+ ## Updates and licence
99
+
100
+ ```sh
101
+ uv tool upgrade agora-adapter
102
+ agora-adapter --version
103
+ ```
104
+
105
+ MIT licensed; see `LICENSE`. This licence covers the files distributed in this
106
+ adapter package. Dependencies and separately installed harnesses retain their own
107
+ licences. The Agora server repository is maintained separately.
@@ -0,0 +1,43 @@
1
+ [project]
2
+ name = "agora-adapter"
3
+ dynamic = ["version"]
4
+ description = "Connect local coding agents to an Agora room"
5
+ readme = "README.md"
6
+ requires-python = ">=3.12"
7
+ license = "MIT"
8
+ license-files = ["LICENSE"]
9
+ authors = [{name = "Daedal"}]
10
+ keywords = ["agora", "agents", "codex", "claude", "mcp"]
11
+ classifiers = [
12
+ "Development Status :: 4 - Beta",
13
+ "Environment :: Console",
14
+ "Intended Audience :: Developers",
15
+ "Operating System :: MacOS",
16
+ "Operating System :: POSIX :: Linux",
17
+ "Programming Language :: Python :: 3",
18
+ "Programming Language :: Python :: 3 :: Only",
19
+ "Programming Language :: Python :: 3.12",
20
+ "Programming Language :: Python :: 3.13",
21
+ "Programming Language :: Python :: 3.14",
22
+ "Topic :: Communications :: Chat",
23
+ ]
24
+ dependencies = ["httpx>=0.28,<1", "pydantic>=2.11,<3"]
25
+
26
+ [project.optional-dependencies]
27
+ claude = ["claude-agent-sdk>=0.2.167,<0.3"]
28
+
29
+ [project.scripts]
30
+ agora-adapter = "agora.adapter.cli:main"
31
+
32
+ [build-system]
33
+ requires = ["hatchling==1.32.4"]
34
+ build-backend = "hatchling.build"
35
+
36
+ [tool.hatch.version]
37
+ path = "src/agora/adapter/__init__.py"
38
+
39
+ [tool.hatch.build.targets.wheel]
40
+ packages = ["src/agora"]
41
+
42
+ [tool.hatch.build.targets.sdist]
43
+ only-include = ["src/agora", "README.md", "LICENSE", "pyproject.toml"]
@@ -0,0 +1 @@
1
+ """Agora's thin agent adapters. PocketBase owns storage, identity, and realtime."""
@@ -0,0 +1,3 @@
1
+ """Outbound-only local harness supervisor; the room relay remains the data plane."""
2
+
3
+ __version__ = "0.2.0"
@@ -0,0 +1,3 @@
1
+ from agora.adapter.cli import main
2
+
3
+ main()
@@ -0,0 +1,261 @@
1
+ """Run one room-scoped agent on the operator's machine."""
2
+
3
+ import argparse
4
+ import asyncio
5
+ import contextlib
6
+ import hashlib
7
+ import json
8
+ import logging
9
+ import os
10
+ import signal
11
+ import sqlite3
12
+ import sys
13
+ import time
14
+ from pathlib import Path
15
+
16
+ import httpx
17
+
18
+ from agora.adapter import __version__
19
+ from agora.adapter.client import AccessLost, Hall
20
+ from agora.adapter.diagnostics import Diagnostics
21
+ from agora.adapter.dsh import DSH
22
+ from agora.adapter.harness import Claude, Codex, HarnessError
23
+ from agora.adapter.state import State
24
+ from agora.adapter.supervisor import Supervisor
25
+
26
+
27
+ def parser():
28
+ result = argparse.ArgumentParser(
29
+ description=__doc__,
30
+ epilog="For a hidden token prompt, use: agora-adapter connect --help",
31
+ )
32
+ result.add_argument("--version", action="version", version="%(prog)s " + __version__)
33
+ result.add_argument("--hall", help="Hall origin, e.g. https://hall.example.com")
34
+ result.add_argument("--room", help="15-character room ID")
35
+ result.add_argument("--harness", choices=("claude", "codex", "dsh"))
36
+ result.add_argument("--cwd", type=Path, help="Local workspace exposed to the harness")
37
+ result.add_argument(
38
+ "--token-env",
39
+ default="AGORA_ROOM_TOKEN",
40
+ help="Environment variable holding the agent token",
41
+ )
42
+ result.add_argument("--state", type=Path, help="Private SQLite checkpoint file")
43
+ result.add_argument("--wake-on", choices=("mentions", "human", "controls"), default="mentions")
44
+ result.add_argument(
45
+ "--allow-edits",
46
+ action="store_true",
47
+ help="Allow harness-native workspace editing; never bypass approvals",
48
+ )
49
+ result.add_argument("--model", help="Optional harness model override")
50
+ result.add_argument(
51
+ "--inspect", action="store_true", help="Read local --state without starting a harness"
52
+ )
53
+ result.add_argument(
54
+ "--diagnostics",
55
+ action="store_true",
56
+ help="Include the last private diagnostic records with --inspect",
57
+ )
58
+ result.add_argument(
59
+ "--diagnose",
60
+ action="store_true",
61
+ help="Check Codex/DSH startup/resume without submitting a model turn",
62
+ )
63
+ result.add_argument(
64
+ "--handoff",
65
+ action="store_true",
66
+ help="Wait for the desktop Codex turn to finish before taking over",
67
+ )
68
+ result.add_argument(
69
+ "--handoff-timeout", type=float, default=600, help="Maximum handoff wait in seconds"
70
+ )
71
+ result.add_argument(
72
+ "--fork-session",
73
+ action="store_true",
74
+ help="Explicitly fork existing history into an adapter-owned Codex session",
75
+ )
76
+ result.add_argument(
77
+ "--recover",
78
+ action="store_true",
79
+ help="Replay an uncertain turn after inspecting its side effects",
80
+ )
81
+ return result
82
+
83
+
84
+ def inspect_state(path, *, diagnostics=False):
85
+ with sqlite3.connect(f"file:{path.resolve()}?mode=ro", uri=True) as db:
86
+ meta = {key: json.loads(value) for key, value in db.execute("SELECT key, value FROM meta")}
87
+ result = {key: meta.get(key) for key in ("scope", "session", "attempt", "last_failure")}
88
+ result["inbox"] = [
89
+ dict(zip(("source", "id", "status"), row, strict=True))
90
+ for row in db.execute("SELECT source, id, status FROM inbox WHERE status != 'done'")
91
+ ]
92
+ journal = Path(str(path) + ".diagnostics.jsonl")
93
+ result["diagnostics_path"] = str(journal)
94
+ if diagnostics and journal.exists():
95
+ # The journal is bounded and redacted at write time; no room transcripts are logged.
96
+ result["diagnostics"] = [
97
+ json.loads(line) for line in journal.read_text().splitlines()[-10:]
98
+ ]
99
+ print(json.dumps(result, indent=2))
100
+
101
+
102
+ async def run(args):
103
+ token = os.environ.get(args.token_env, "")
104
+ if not token or any(char.isspace() for char in token):
105
+ raise ValueError("Set the room token in the selected environment variable.")
106
+ cwd = args.cwd.resolve(strict=True)
107
+ if not cwd.is_dir():
108
+ raise ValueError("The workspace must be a directory.")
109
+ hall = Hall(args.hall, token, args.room)
110
+ state = harness = None
111
+ bootstrap = args.state or Path.home() / ".local/state/agora/startup"
112
+ diagnostics = Diagnostics(Path(str(bootstrap) + ".diagnostics.jsonl"), secrets=(token,))
113
+ try:
114
+ await hall.authenticate()
115
+ mcp = await hall.discovery()
116
+ scope = {
117
+ "hall": hall.url,
118
+ "room": hall.room,
119
+ "agent": hall.person["id"],
120
+ "harness": args.harness,
121
+ "cwd": str(cwd),
122
+ }
123
+ key = hashlib.sha256(repr(sorted(scope.items())).encode()).hexdigest()[:24]
124
+ path = args.state or Path.home() / ".local/state/agora" / (key + ".sqlite3")
125
+ diagnostics = Diagnostics(Path(str(path) + ".diagnostics.jsonl"), secrets=(token,))
126
+ state = State(path, scope, args.recover, hold_uncertain=True)
127
+ if state.recovery:
128
+ diagnostics.record("recovery", **state.recovery)
129
+ harness = {"claude": Claude, "codex": Codex, "dsh": DSH}[args.harness](
130
+ cwd=cwd,
131
+ mcp=mcp,
132
+ token=token,
133
+ allow_edits=args.allow_edits,
134
+ model=args.model,
135
+ )
136
+ harness.diagnostics = diagnostics
137
+ supervisor = Supervisor(hall, state, harness, wake_on=args.wake_on)
138
+ task = asyncio.current_task()
139
+ loop = asyncio.get_running_loop()
140
+ for sig in (signal.SIGTERM, signal.SIGINT):
141
+ loop.add_signal_handler(sig, task.cancel)
142
+ logging.info(
143
+ "Listening for room activity. Harness: %s. Checkpoints: %s", args.harness, path
144
+ )
145
+ try:
146
+ session = state.get("session")
147
+ if args.fork_session and session:
148
+ # Reject a blind replay before creating a replacement session.
149
+ if state.unfinished():
150
+ raise HarnessError("Inspect uncertain work and use --recover before forking.")
151
+ session = await harness.fork_session(session, lambda sid: state.set("session", sid))
152
+ state.set("last_failure", None)
153
+ supervisor.retries = 0
154
+ logging.info("Adapter-owned Codex session: %s", session)
155
+ if args.handoff and session:
156
+ logging.info(
157
+ "Handoff waiting for the current Codex turn; do not send more desktop turns."
158
+ )
159
+ await hall.activity("error")
160
+ waiter = asyncio.create_task(harness.wait_for_idle(session, args.handoff_timeout))
161
+ try:
162
+ while not waiter.done():
163
+ try:
164
+ await asyncio.wait_for(asyncio.shield(waiter), 15)
165
+ except TimeoutError:
166
+ if waiter.done():
167
+ raise
168
+ await hall.activity("error")
169
+ await waiter
170
+ finally:
171
+ waiter.cancel()
172
+ with contextlib.suppress(asyncio.CancelledError):
173
+ await waiter
174
+ if args.diagnose:
175
+ if args.harness == "dsh":
176
+ await harness.prepare(session, lambda sid: state.set("session", sid))
177
+ else:
178
+ await harness.start()
179
+ if session:
180
+ await harness.check_idle(session)
181
+ await harness.request("thread/resume", harness.thread_params(session))
182
+ logging.info(
183
+ "%s startup/resume succeeded. No model turn was submitted.", args.harness
184
+ )
185
+ return
186
+ await supervisor.run()
187
+ finally:
188
+ for sig in (signal.SIGTERM, signal.SIGINT):
189
+ loop.remove_signal_handler(sig)
190
+ except Exception as exc:
191
+ if diagnostics:
192
+ incident = diagnostics.record(
193
+ "adapter_failure", exception=type(exc).__name__, message=str(exc)
194
+ )
195
+ logging.error("Adapter failure %s. Diagnostics: %s", incident, diagnostics.path)
196
+ if state and not state.get("last_failure"):
197
+ state.set(
198
+ "last_failure",
199
+ {
200
+ "incident": incident,
201
+ "message": str(exc)
202
+ if isinstance(exc, HarnessError)
203
+ else "Adapter startup or lifecycle failed.",
204
+ "blocked": True,
205
+ },
206
+ )
207
+ # Without the state lock, another adapter may own this identity. Do not
208
+ # overwrite its activity merely because this process failed to start.
209
+ if state:
210
+ try:
211
+ await hall.activity("error")
212
+ except (AccessLost, httpx.HTTPError):
213
+ pass
214
+ raise
215
+ finally:
216
+ if harness:
217
+ await harness.close()
218
+ if state:
219
+ state.close()
220
+ await hall.close()
221
+
222
+
223
+ def main():
224
+ if sys.argv[1:2] == ["connect"]:
225
+ from agora.adapter.launcher import main as connect
226
+
227
+ connect(sys.argv[2:])
228
+ return
229
+ logging.Formatter.converter = time.gmtime
230
+ logging.basicConfig(
231
+ level=logging.INFO,
232
+ format="%(asctime)sZ %(levelname)s %(message)s",
233
+ datefmt="%Y-%m-%dT%H:%M:%S",
234
+ )
235
+ logging.getLogger("httpx").setLevel(logging.WARNING)
236
+ argument_parser = parser()
237
+ args = argument_parser.parse_args()
238
+ if args.inspect:
239
+ if not args.state:
240
+ argument_parser.error("--inspect requires --state")
241
+ inspect_state(args.state, diagnostics=args.diagnostics)
242
+ return
243
+ if not all((args.hall, args.room, args.harness, args.cwd)):
244
+ argument_parser.error("--hall, --room, --harness and --cwd are required")
245
+ if (args.handoff or args.fork_session) and args.harness != "codex":
246
+ argument_parser.error("--handoff and --fork-session require --harness codex")
247
+ if args.diagnose and args.harness not in ("codex", "dsh"):
248
+ argument_parser.error("--diagnose requires --harness codex or dsh")
249
+ if args.handoff_timeout <= 0:
250
+ argument_parser.error("--handoff-timeout must be positive")
251
+ try:
252
+ asyncio.run(run(args))
253
+ except (KeyboardInterrupt, asyncio.CancelledError):
254
+ pass
255
+ except (ValueError, AccessLost, HarnessError) as exc:
256
+ raise SystemExit(str(exc)) from None
257
+ except Exception:
258
+ # Provider and transport exceptions may contain prompts, URLs, or credentials.
259
+ raise SystemExit(
260
+ "Adapter failed; inspect local state and harness logs before restarting."
261
+ ) from None