baserun-cli 0.1.4__tar.gz → 0.1.5__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.
- {baserun_cli-0.1.4 → baserun_cli-0.1.5}/PKG-INFO +1 -1
- {baserun_cli-0.1.4 → baserun_cli-0.1.5}/baserun_cli/channel.py +35 -44
- {baserun_cli-0.1.4 → baserun_cli-0.1.5}/baserun_cli/runner.py +9 -7
- {baserun_cli-0.1.4 → baserun_cli-0.1.5}/baserun_cli.egg-info/PKG-INFO +1 -1
- {baserun_cli-0.1.4 → baserun_cli-0.1.5}/pyproject.toml +1 -1
- {baserun_cli-0.1.4 → baserun_cli-0.1.5}/README.md +0 -0
- {baserun_cli-0.1.4 → baserun_cli-0.1.5}/baserun_cli/__init__.py +0 -0
- {baserun_cli-0.1.4 → baserun_cli-0.1.5}/baserun_cli/_vendored/__init__.py +0 -0
- {baserun_cli-0.1.4 → baserun_cli-0.1.5}/baserun_cli/_vendored/base.py +0 -0
- {baserun_cli-0.1.4 → baserun_cli-0.1.5}/baserun_cli/_vendored/cli.py +0 -0
- {baserun_cli-0.1.4 → baserun_cli-0.1.5}/baserun_cli/_vendored/parsers/__init__.py +0 -0
- {baserun_cli-0.1.4 → baserun_cli-0.1.5}/baserun_cli/_vendored/parsers/base.py +0 -0
- {baserun_cli-0.1.4 → baserun_cli-0.1.5}/baserun_cli/_vendored/parsers/bash_agent.py +0 -0
- {baserun_cli-0.1.4 → baserun_cli-0.1.5}/baserun_cli/_vendored/parsers/claude.py +0 -0
- {baserun_cli-0.1.4 → baserun_cli-0.1.5}/baserun_cli/_vendored/parsers/codex.py +0 -0
- {baserun_cli-0.1.4 → baserun_cli-0.1.5}/baserun_cli/main.py +0 -0
- {baserun_cli-0.1.4 → baserun_cli-0.1.5}/baserun_cli.egg-info/SOURCES.txt +0 -0
- {baserun_cli-0.1.4 → baserun_cli-0.1.5}/baserun_cli.egg-info/dependency_links.txt +0 -0
- {baserun_cli-0.1.4 → baserun_cli-0.1.5}/baserun_cli.egg-info/entry_points.txt +0 -0
- {baserun_cli-0.1.4 → baserun_cli-0.1.5}/baserun_cli.egg-info/requires.txt +0 -0
- {baserun_cli-0.1.4 → baserun_cli-0.1.5}/baserun_cli.egg-info/top_level.txt +0 -0
- {baserun_cli-0.1.4 → baserun_cli-0.1.5}/setup.cfg +0 -0
|
@@ -82,47 +82,47 @@ class ChannelClient:
|
|
|
82
82
|
}
|
|
83
83
|
|
|
84
84
|
async def ensure_run_pub(self, run_id: str) -> None:
|
|
85
|
-
"""
|
|
85
|
+
"""Pre-connect the WS publisher for a run (fire-and-forget).
|
|
86
86
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
publish as the authoritative, acknowledged path.
|
|
90
|
-
"""
|
|
91
|
-
return
|
|
92
|
-
|
|
93
|
-
async def publish_event(self, run_id: str, payload: dict[str, Any]) -> bool:
|
|
94
|
-
"""Publish a run event via HTTP and require an acknowledged response.
|
|
95
|
-
|
|
96
|
-
We intentionally avoid the WebSocket publisher for run events: send()
|
|
97
|
-
only confirms that bytes entered the local socket buffer, not that nchan
|
|
98
|
-
accepted and buffered the event. HTTP gives per-event success/failure.
|
|
87
|
+
Called at run start so the WS handshake completes before the first
|
|
88
|
+
event arrives. If this fails, publish_event will retry on demand.
|
|
99
89
|
"""
|
|
90
|
+
if run_id in self._run_pubs:
|
|
91
|
+
return
|
|
100
92
|
try:
|
|
101
|
-
await self.
|
|
102
|
-
|
|
93
|
+
ws = await self._open_run_pub(run_id)
|
|
94
|
+
self._run_pubs[run_id] = ws
|
|
103
95
|
except Exception as e:
|
|
104
|
-
log.
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
96
|
+
log.debug("pre-connect ws pub for run %s failed (will retry on first event): %s", run_id, e)
|
|
97
|
+
|
|
98
|
+
async def publish_event(self, run_id: str, payload: dict[str, Any]) -> bool:
|
|
99
|
+
"""Publish a run event via WebSocket (ordered streaming path)."""
|
|
100
|
+
data = json.dumps(payload, ensure_ascii=False)
|
|
101
|
+
is_terminal = (payload.get("data", {}).get("finished") is True)
|
|
102
|
+
max_retries = 4 if is_terminal else 2
|
|
111
103
|
|
|
112
|
-
async def publish_event_reliably(self, run_id: str, payload: dict[str, Any]) -> bool:
|
|
113
|
-
"""Publish a critical run event with HTTP retries."""
|
|
114
|
-
max_retries = 5
|
|
115
104
|
for attempt in range(max_retries):
|
|
116
|
-
|
|
105
|
+
ws = self._run_pubs.get(run_id)
|
|
106
|
+
if ws is None:
|
|
107
|
+
try:
|
|
108
|
+
ws = await self._open_run_pub(run_id)
|
|
109
|
+
self._run_pubs[run_id] = ws
|
|
110
|
+
except Exception as e:
|
|
111
|
+
log.warning("ws pub connect for run %s failed (attempt %d): %s", run_id, attempt + 1, e)
|
|
112
|
+
if attempt < max_retries - 1:
|
|
113
|
+
await asyncio.sleep(1.0)
|
|
114
|
+
continue
|
|
115
|
+
try:
|
|
116
|
+
await ws.send(data)
|
|
117
117
|
return True
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
118
|
+
except Exception as e:
|
|
119
|
+
log.warning("ws publish to run %s failed (attempt %d): %s", run_id, attempt + 1, e)
|
|
120
|
+
self._run_pubs.pop(run_id, None)
|
|
121
|
+
if attempt < max_retries - 1:
|
|
122
|
+
await asyncio.sleep(1.0)
|
|
123
|
+
|
|
124
|
+
log.error("publish failed for run %s seq=%s after %d attempts",
|
|
125
|
+
run_id, payload.get("data", {}).get("seq"), max_retries)
|
|
126
126
|
return False
|
|
127
127
|
|
|
128
128
|
async def _open_run_pub(self, run_id: str) -> Any:
|
|
@@ -136,12 +136,6 @@ class ChannelClient:
|
|
|
136
136
|
log.info("opening ws publisher for run %s", run_id)
|
|
137
137
|
return await connect(url, ping_interval=30, open_timeout=30)
|
|
138
138
|
|
|
139
|
-
async def _http_publish(self, run_id: str, payload: dict[str, Any]) -> None:
|
|
140
|
-
"""HTTP fallback for publishing events (legacy, not used in normal flow)."""
|
|
141
|
-
url = f"{self._publish_base.rstrip('/')}/internal/run/{run_id}/publish"
|
|
142
|
-
resp = await self._http.post(url, content=json.dumps(payload, ensure_ascii=False))
|
|
143
|
-
resp.raise_for_status()
|
|
144
|
-
|
|
145
139
|
async def close_run_pub(self, run_id: str) -> None:
|
|
146
140
|
"""Close the WS publisher for a run."""
|
|
147
141
|
ws = self._run_pubs.pop(run_id, None)
|
|
@@ -228,10 +222,7 @@ class ChannelClient:
|
|
|
228
222
|
replay_ok = True
|
|
229
223
|
for ev in missing_events:
|
|
230
224
|
data = ev.get("data", {})
|
|
231
|
-
|
|
232
|
-
ok = await self.publish_event_reliably(run_id, ev)
|
|
233
|
-
else:
|
|
234
|
-
ok = await self.publish_event(run_id, ev)
|
|
225
|
+
ok = await self.publish_event(run_id, ev)
|
|
235
226
|
if not ok:
|
|
236
227
|
replay_ok = False
|
|
237
228
|
log.error("verify: replay still failed for run %s seq=%s", run_id, data.get("seq"))
|
|
@@ -163,6 +163,11 @@ class TaskRunner:
|
|
|
163
163
|
connector = get_connector(connector_type, config)
|
|
164
164
|
log.info("run %s mode=%s connector=%s session_id=%s", run_id, mode, connector.spec.bin, agent_session_id or "new")
|
|
165
165
|
|
|
166
|
+
# Open the run publisher before connector starts. Otherwise a short task
|
|
167
|
+
# can finish before the run channel/subscriber is ready, leaving the
|
|
168
|
+
# server in RUNNING even though the client process completed locally.
|
|
169
|
+
await self.channel.ensure_run_pub(run_id)
|
|
170
|
+
|
|
166
171
|
events_iter = self._select_events(connector, mode, prompt, agent_session_id)
|
|
167
172
|
|
|
168
173
|
# local persistence: raw event log for debugging + retry
|
|
@@ -199,11 +204,8 @@ class TaskRunner:
|
|
|
199
204
|
|
|
200
205
|
log.debug("run %s event seq=%d kind=%s", run_id, seq, kind)
|
|
201
206
|
|
|
202
|
-
# publish
|
|
203
|
-
|
|
204
|
-
await self.channel.publish_event_reliably(run_id, payload)
|
|
205
|
-
else:
|
|
206
|
-
await self.channel.publish_event(run_id, payload)
|
|
207
|
+
# publish via WebSocket only
|
|
208
|
+
await self.channel.publish_event(run_id, payload)
|
|
207
209
|
|
|
208
210
|
if is_terminal:
|
|
209
211
|
saw_terminal = True
|
|
@@ -224,7 +226,7 @@ class TaskRunner:
|
|
|
224
226
|
}
|
|
225
227
|
log_file.write(json.dumps(payload["data"], ensure_ascii=False) + "\n")
|
|
226
228
|
log_file.flush()
|
|
227
|
-
await self.channel.
|
|
229
|
+
await self.channel.publish_event(run_id, payload)
|
|
228
230
|
log.info("run %s synthesized terminal (seq=%d)", run_id, seq)
|
|
229
231
|
|
|
230
232
|
# verify-and-replay:对比 nchan 实际状态,精确补发缺失事件
|
|
@@ -268,7 +270,7 @@ class TaskRunner:
|
|
|
268
270
|
log_file.write(json.dumps(error_payload["data"], ensure_ascii=False) + "\n")
|
|
269
271
|
except Exception:
|
|
270
272
|
pass
|
|
271
|
-
ok = await self.channel.
|
|
273
|
+
ok = await self.channel.publish_event(run_id, error_payload)
|
|
272
274
|
return ok, "error", seq
|
|
273
275
|
|
|
274
276
|
finally:
|
|
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
|