webbee 0.1.19__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.
- {webbee-0.1.19 → webbee-0.2.1}/CHANGELOG.md +21 -0
- {webbee-0.1.19 → webbee-0.2.1}/PKG-INFO +1 -1
- {webbee-0.1.19 → webbee-0.2.1}/pyproject.toml +1 -1
- webbee-0.2.1/src/webbee/__init__.py +1 -0
- {webbee-0.1.19 → webbee-0.2.1}/src/webbee/cli.py +6 -1
- {webbee-0.1.19 → webbee-0.2.1}/src/webbee/render.py +3 -0
- {webbee-0.1.19 → webbee-0.2.1}/src/webbee/repl.py +2 -2
- {webbee-0.1.19 → webbee-0.2.1}/src/webbee/session.py +15 -1
- {webbee-0.1.19 → webbee-0.2.1}/tests/test_marathon.py +58 -8
- {webbee-0.1.19 → webbee-0.2.1}/tests/test_repl.py +22 -6
- webbee-0.1.19/src/webbee/__init__.py +0 -1
- {webbee-0.1.19 → webbee-0.2.1}/.github/workflows/publish.yml +0 -0
- {webbee-0.1.19 → webbee-0.2.1}/.gitignore +0 -0
- {webbee-0.1.19 → webbee-0.2.1}/LICENSE +0 -0
- {webbee-0.1.19 → webbee-0.2.1}/README.md +0 -0
- {webbee-0.1.19 → webbee-0.2.1}/install.sh +0 -0
- {webbee-0.1.19 → webbee-0.2.1}/src/webbee/account.py +0 -0
- {webbee-0.1.19 → webbee-0.2.1}/src/webbee/banner_art.py +0 -0
- {webbee-0.1.19 → webbee-0.2.1}/src/webbee/clipboard.py +0 -0
- {webbee-0.1.19 → webbee-0.2.1}/src/webbee/commands.py +0 -0
- {webbee-0.1.19 → webbee-0.2.1}/src/webbee/config.py +0 -0
- {webbee-0.1.19 → webbee-0.2.1}/src/webbee/details.py +0 -0
- {webbee-0.1.19 → webbee-0.2.1}/src/webbee/events.py +0 -0
- {webbee-0.1.19 → webbee-0.2.1}/src/webbee/frames.py +0 -0
- {webbee-0.1.19 → webbee-0.2.1}/src/webbee/intel/__init__.py +0 -0
- {webbee-0.1.19 → webbee-0.2.1}/src/webbee/intel/chunker.py +0 -0
- {webbee-0.1.19 → webbee-0.2.1}/src/webbee/intel/embed.py +0 -0
- {webbee-0.1.19 → webbee-0.2.1}/src/webbee/intel/graph.py +0 -0
- {webbee-0.1.19 → webbee-0.2.1}/src/webbee/intel/indexer.py +0 -0
- {webbee-0.1.19 → webbee-0.2.1}/src/webbee/intel/models.py +0 -0
- {webbee-0.1.19 → webbee-0.2.1}/src/webbee/intel/query.py +0 -0
- {webbee-0.1.19 → webbee-0.2.1}/src/webbee/intel/service.py +0 -0
- {webbee-0.1.19 → webbee-0.2.1}/src/webbee/intel/store.py +0 -0
- {webbee-0.1.19 → webbee-0.2.1}/src/webbee/intel/vectors.py +0 -0
- {webbee-0.1.19 → webbee-0.2.1}/src/webbee/intel/watch.py +0 -0
- {webbee-0.1.19 → webbee-0.2.1}/src/webbee/output_pane.py +0 -0
- {webbee-0.1.19 → webbee-0.2.1}/src/webbee/repo.py +0 -0
- {webbee-0.1.19 → webbee-0.2.1}/src/webbee/sessions.py +0 -0
- {webbee-0.1.19 → webbee-0.2.1}/src/webbee/stream.py +0 -0
- {webbee-0.1.19 → webbee-0.2.1}/src/webbee/tools.py +0 -0
- {webbee-0.1.19 → webbee-0.2.1}/src/webbee/tui.py +0 -0
- {webbee-0.1.19 → webbee-0.2.1}/src/webbee/update.py +0 -0
- {webbee-0.1.19 → webbee-0.2.1}/tests/__init__.py +0 -0
- {webbee-0.1.19 → webbee-0.2.1}/tests/test_account.py +0 -0
- {webbee-0.1.19 → webbee-0.2.1}/tests/test_cli.py +0 -0
- {webbee-0.1.19 → webbee-0.2.1}/tests/test_clipboard.py +0 -0
- {webbee-0.1.19 → webbee-0.2.1}/tests/test_commands.py +0 -0
- {webbee-0.1.19 → webbee-0.2.1}/tests/test_config.py +0 -0
- {webbee-0.1.19 → webbee-0.2.1}/tests/test_cpc_contract_stable.py +0 -0
- {webbee-0.1.19 → webbee-0.2.1}/tests/test_details.py +0 -0
- {webbee-0.1.19 → webbee-0.2.1}/tests/test_events.py +0 -0
- {webbee-0.1.19 → webbee-0.2.1}/tests/test_freeze_fix.py +0 -0
- {webbee-0.1.19 → webbee-0.2.1}/tests/test_intel_chunker.py +0 -0
- {webbee-0.1.19 → webbee-0.2.1}/tests/test_intel_embed.py +0 -0
- {webbee-0.1.19 → webbee-0.2.1}/tests/test_intel_graph.py +0 -0
- {webbee-0.1.19 → webbee-0.2.1}/tests/test_intel_indexer.py +0 -0
- {webbee-0.1.19 → webbee-0.2.1}/tests/test_intel_no_numpy.py +0 -0
- {webbee-0.1.19 → webbee-0.2.1}/tests/test_intel_query.py +0 -0
- {webbee-0.1.19 → webbee-0.2.1}/tests/test_intel_service.py +0 -0
- {webbee-0.1.19 → webbee-0.2.1}/tests/test_intel_store.py +0 -0
- {webbee-0.1.19 → webbee-0.2.1}/tests/test_intel_vectors.py +0 -0
- {webbee-0.1.19 → webbee-0.2.1}/tests/test_intel_watch.py +0 -0
- {webbee-0.1.19 → webbee-0.2.1}/tests/test_packaging.py +0 -0
- {webbee-0.1.19 → webbee-0.2.1}/tests/test_render.py +0 -0
- {webbee-0.1.19 → webbee-0.2.1}/tests/test_repo.py +0 -0
- {webbee-0.1.19 → webbee-0.2.1}/tests/test_session.py +0 -0
- {webbee-0.1.19 → webbee-0.2.1}/tests/test_sessions.py +0 -0
- {webbee-0.1.19 → webbee-0.2.1}/tests/test_stream.py +0 -0
- {webbee-0.1.19 → webbee-0.2.1}/tests/test_tools.py +0 -0
- {webbee-0.1.19 → webbee-0.2.1}/tests/test_tui.py +0 -0
- {webbee-0.1.19 → webbee-0.2.1}/tests/test_tui_hardening.py +0 -0
- {webbee-0.1.19 → webbee-0.2.1}/tests/test_update.py +0 -0
- {webbee-0.1.19 → webbee-0.2.1}/tests/test_version.py +0 -0
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.2.1
|
|
4
|
+
|
|
5
|
+
- **Fixes a marathon that could hang on "working" and quietly burn credits.** A
|
|
6
|
+
marathon works through milestones and streams a result at each one; the
|
|
7
|
+
terminal used to stop at the first milestone, abandoning the rest of the run —
|
|
8
|
+
the agent kept going server-side with nobody to run its tools, spinning until
|
|
9
|
+
your balance drained. The terminal now follows a marathon to the end: it keeps
|
|
10
|
+
working across milestones and stops only when the whole goal is done, it
|
|
11
|
+
pauses (low credits / awaiting approval), or you stop it. Requires the current
|
|
12
|
+
cloud; older milestones are unaffected.
|
|
13
|
+
|
|
14
|
+
## 0.2.0
|
|
15
|
+
|
|
16
|
+
- **Marathon is now the default.** A task you type runs to completion on its own
|
|
17
|
+
— it plans, edits, runs, and verifies across as many steps as it needs, and
|
|
18
|
+
only stops when the goal is done, you stop it, or your credits run low (it
|
|
19
|
+
pauses, it doesn't fail — top up and it resumes). No more fixed per-run step
|
|
20
|
+
limit on the default path.
|
|
21
|
+
- **`--once` (alias `--no-marathon`)** runs a single bounded coding turn instead,
|
|
22
|
+
for a quick one-shot.
|
|
23
|
+
|
|
3
24
|
## 0.1.14
|
|
4
25
|
|
|
5
26
|
- **Esc/Ctrl-C now actually stops the turn — server-side, not just locally.**
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.2.1"
|
|
@@ -15,6 +15,11 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
15
15
|
help="Launch a long-horizon marathon toward GOAL (runs autonomously "
|
|
16
16
|
"until the goal is met, verified by the project's own test command).",
|
|
17
17
|
)
|
|
18
|
+
p.add_argument(
|
|
19
|
+
"--once", "--no-marathon", dest="once", action="store_true",
|
|
20
|
+
help="Run a single bounded coding turn (stops at the step limit) instead "
|
|
21
|
+
"of the default self-driving marathon.",
|
|
22
|
+
)
|
|
18
23
|
sub = p.add_subparsers(dest="cmd")
|
|
19
24
|
sub.add_parser("login", help="Log in to your Imperal account in the browser")
|
|
20
25
|
sub.add_parser("logout", help="Log out and remove local credentials")
|
|
@@ -63,7 +68,7 @@ def main(argv=None) -> None:
|
|
|
63
68
|
from webbee.repl import run_repl
|
|
64
69
|
try:
|
|
65
70
|
_maybe_print_update_notice()
|
|
66
|
-
asyncio.run(run_repl(cfg, args.mode))
|
|
71
|
+
asyncio.run(run_repl(cfg, args.mode, once=args.once))
|
|
67
72
|
except KeyboardInterrupt:
|
|
68
73
|
# Ctrl-C during the update-check fetch, or at the read_line() prompt,
|
|
69
74
|
# unwinds here — exit clean, no traceback. (repl.py itself now cancels
|
|
@@ -140,6 +140,9 @@ class RichSink:
|
|
|
140
140
|
for label, value in rows:
|
|
141
141
|
self.console.print(Text.assemble((pad + label.ljust(14), "dim"), (value, "white")))
|
|
142
142
|
self.console.print()
|
|
143
|
+
tip = "Type a task and Webbee runs it to completion · pauses if credits run low · --once = single turn"
|
|
144
|
+
self.console.print(Text(tip.center(w), style="dim"))
|
|
145
|
+
self.console.print()
|
|
143
146
|
self.console.print(Text("/help · Ctrl-D to exit".center(w), style="dim"))
|
|
144
147
|
self.console.print()
|
|
145
148
|
self._nudge()
|
|
@@ -91,7 +91,7 @@ def _default_intel_factory(cfg, workspace: str):
|
|
|
91
91
|
return IntelService(root, compute_repo_key(root), cache_dir=cfg.cache_dir)
|
|
92
92
|
|
|
93
93
|
|
|
94
|
-
async def run_repl(cfg, mode: str = "default", *, sink=None, read_line=input,
|
|
94
|
+
async def run_repl(cfg, mode: str = "default", *, once: bool = False, sink=None, read_line=input,
|
|
95
95
|
agent_factory=None, auth=None, account_fetcher=None,
|
|
96
96
|
sessions_client=None, intel_factory=None) -> None:
|
|
97
97
|
"""Interactive coding REPL. Production (a real tty, no injected sink) runs
|
|
@@ -226,7 +226,7 @@ async def run_repl(cfg, mode: str = "default", *, sink=None, read_line=input,
|
|
|
226
226
|
_sink.user_echo(line)
|
|
227
227
|
_sink.begin_turn()
|
|
228
228
|
try:
|
|
229
|
-
text = await agent.run(line, _sink)
|
|
229
|
+
text = await agent.run(line, _sink, marathon=not once, goal=(line if not once else ""))
|
|
230
230
|
except (KeyboardInterrupt, asyncio.CancelledError):
|
|
231
231
|
_sink.abort()
|
|
232
232
|
_sink.note("Interrupted.")
|
|
@@ -219,7 +219,7 @@ class AgentSession:
|
|
|
219
219
|
# persistent stream (task_id absent on legacy kernels -> not filtered).
|
|
220
220
|
if _ftid and self._task_id and _ftid != self._task_id and ftype in (
|
|
221
221
|
"tool_request", "confirm_request", "final",
|
|
222
|
-
"panel_release_required"):
|
|
222
|
+
"marathon_complete", "panel_release_required"):
|
|
223
223
|
continue
|
|
224
224
|
|
|
225
225
|
if ftype == "tool_request":
|
|
@@ -281,6 +281,9 @@ class AgentSession:
|
|
|
281
281
|
int(frame.get("credits", 0) or 0),
|
|
282
282
|
)
|
|
283
283
|
|
|
284
|
+
elif ftype == "marathon_complete": # U4 — the whole GOAL is done: terminal
|
|
285
|
+
return frame.get("text", "")
|
|
286
|
+
|
|
284
287
|
elif ftype in _MARATHON_FACT_TYPES: # U4 — marathon plan/milestone/pause
|
|
285
288
|
# Facts-only; render ONE human-readable line. Guarded: a
|
|
286
289
|
# minimal sink (no `note`) simply drops the fact rather than
|
|
@@ -289,8 +292,19 @@ class AgentSession:
|
|
|
289
292
|
_note = getattr(sink, "note", None)
|
|
290
293
|
if _note is not None:
|
|
291
294
|
_note(marathon_note(frame))
|
|
295
|
+
if ftype == "marathon_paused":
|
|
296
|
+
# Parked (out-of-credits / consent / runaway) -> end the
|
|
297
|
+
# turn so the dock leaves "working"; the note shows why, and
|
|
298
|
+
# the run resumes on the user's next reply.
|
|
299
|
+
return ""
|
|
292
300
|
|
|
293
301
|
elif ftype == "final":
|
|
302
|
+
# In a MARATHON a `final` is a PER-MILESTONE result, NOT the end
|
|
303
|
+
# of the run -> keep streaming (the goal ends on marathon_complete
|
|
304
|
+
# / marathon_paused, or a user-stop `final` with stopped=true). A
|
|
305
|
+
# non-marathon coding turn's `final` is terminal (unchanged).
|
|
306
|
+
if marathon and not frame.get("stopped"):
|
|
307
|
+
continue
|
|
294
308
|
return frame.get("text", "")
|
|
295
309
|
|
|
296
310
|
return ""
|
|
@@ -85,9 +85,9 @@ def test_parser_marathon_defaults_none():
|
|
|
85
85
|
assert build_parser().parse_args([]).marathon is None
|
|
86
86
|
|
|
87
87
|
|
|
88
|
-
def _run_marathon_capture_post(monkeypatch, tmp_path, goal="build X"):
|
|
89
|
-
"""Drive AgentSession.run(
|
|
90
|
-
|
|
88
|
+
def _run_marathon_capture_post(monkeypatch, tmp_path, goal="build X", frames=None, marathon=True):
|
|
89
|
+
"""Drive AgentSession.run() with everything network-side faked; return the
|
|
90
|
+
JSON body POSTed to /v1/agent/sessions, the run() result, and the sink."""
|
|
91
91
|
import httpx
|
|
92
92
|
import imperal_mcp.client as ic
|
|
93
93
|
import webbee.session as S
|
|
@@ -113,11 +113,18 @@ def _run_marathon_capture_post(monkeypatch, tmp_path, goal="build X"):
|
|
|
113
113
|
|
|
114
114
|
monkeypatch.setattr(T, "LocalToolExecutor", _RecExecutor)
|
|
115
115
|
|
|
116
|
+
# Default protocol (U4 semantics): a marathon_plan FACT + a per-milestone
|
|
117
|
+
# `final` (NON-terminal) + the terminal marathon_complete. Tests override
|
|
118
|
+
# `frames` to exercise the terminal taxonomy.
|
|
119
|
+
_frames = frames if frames is not None else [
|
|
120
|
+
{"type": "marathon_plan", "task_id": "OURS", "milestone_count": 2, "goal": goal},
|
|
121
|
+
{"type": "final", "task_id": "OURS", "text": "milestone 1"},
|
|
122
|
+
{"type": "marathon_complete", "task_id": "OURS", "text": "done"},
|
|
123
|
+
]
|
|
124
|
+
|
|
116
125
|
async def _fake_stream(client, session_id, headers_provider, *, start_id="0-0"):
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
"goal": goal}
|
|
120
|
-
yield {"type": "final", "task_id": "OURS", "text": "done"}
|
|
126
|
+
for _fr in _frames:
|
|
127
|
+
yield _fr
|
|
121
128
|
|
|
122
129
|
monkeypatch.setattr(ST, "stream_frames", _fake_stream)
|
|
123
130
|
|
|
@@ -161,7 +168,7 @@ def _run_marathon_capture_post(monkeypatch, tmp_path, goal="build X"):
|
|
|
161
168
|
|
|
162
169
|
sink = NoteSink()
|
|
163
170
|
sess = S.AgentSession(cfg=_Cfg(), token_provider=token_provider, workspace_root=str(tmp_path))
|
|
164
|
-
result = asyncio.run(sess.run(goal, sink, marathon=
|
|
171
|
+
result = asyncio.run(sess.run(goal, sink, marathon=marathon, goal=(goal if marathon else "")))
|
|
165
172
|
return posted, result, sink
|
|
166
173
|
|
|
167
174
|
|
|
@@ -176,6 +183,49 @@ def test_marathon_run_sets_marathon_and_goal_and_verify_cmd(monkeypatch, tmp_pat
|
|
|
176
183
|
assert any("Marathon plan" in n for n in sink.notes)
|
|
177
184
|
|
|
178
185
|
|
|
186
|
+
def test_marathon_final_is_not_terminal_keeps_streaming(monkeypatch, tmp_path):
|
|
187
|
+
# A per-milestone `final` must NOT end a marathon run: with no terminal frame
|
|
188
|
+
# the stream simply exhausts and run() returns "" (it kept streaming past the
|
|
189
|
+
# milestone final, never bailed early — the phantom-hang root cause).
|
|
190
|
+
_, result, _ = _run_marathon_capture_post(monkeypatch, tmp_path, frames=[
|
|
191
|
+
{"type": "marathon_plan", "task_id": "OURS", "goal": "g"},
|
|
192
|
+
{"type": "final", "task_id": "OURS", "text": "milestone 1 done"},
|
|
193
|
+
])
|
|
194
|
+
assert result == ""
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def test_marathon_complete_is_terminal(monkeypatch, tmp_path):
|
|
198
|
+
_, result, _ = _run_marathon_capture_post(monkeypatch, tmp_path, frames=[
|
|
199
|
+
{"type": "final", "task_id": "OURS", "text": "milestone 1"},
|
|
200
|
+
{"type": "marathon_complete", "task_id": "OURS", "text": "goal done"},
|
|
201
|
+
])
|
|
202
|
+
assert result == "goal done"
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def test_marathon_paused_is_terminal_and_noted(monkeypatch, tmp_path):
|
|
206
|
+
_, result, sink = _run_marathon_capture_post(monkeypatch, tmp_path, frames=[
|
|
207
|
+
{"type": "marathon_paused", "task_id": "OURS", "reason": "out of credits"},
|
|
208
|
+
])
|
|
209
|
+
assert result == ""
|
|
210
|
+
assert any("paused" in n.lower() for n in sink.notes)
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def test_marathon_user_stop_final_is_terminal(monkeypatch, tmp_path):
|
|
214
|
+
# A user-stop (stopped=true) `final` ends the marathon immediately.
|
|
215
|
+
_, result, _ = _run_marathon_capture_post(monkeypatch, tmp_path, frames=[
|
|
216
|
+
{"type": "final", "task_id": "OURS", "text": "partial", "stopped": True},
|
|
217
|
+
])
|
|
218
|
+
assert result == "partial"
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def test_non_marathon_final_is_terminal(monkeypatch, tmp_path):
|
|
222
|
+
# Coding (non-marathon): a `final` is terminal (unchanged) -> returns its text.
|
|
223
|
+
_, result, _ = _run_marathon_capture_post(monkeypatch, tmp_path, marathon=False, frames=[
|
|
224
|
+
{"type": "final", "task_id": "OURS", "text": "answer"},
|
|
225
|
+
])
|
|
226
|
+
assert result == "answer"
|
|
227
|
+
|
|
228
|
+
|
|
179
229
|
def test_normal_run_omits_marathon_fields(monkeypatch, tmp_path):
|
|
180
230
|
# Coding path unchanged: no marathon/goal keys, no verify_cmd injected.
|
|
181
231
|
import httpx
|
|
@@ -30,9 +30,10 @@ class FakeSink:
|
|
|
30
30
|
|
|
31
31
|
|
|
32
32
|
class FakeAgent:
|
|
33
|
-
def __init__(self): self.tasks = []; self.mode = "default"
|
|
34
|
-
async def run(self, task, sink):
|
|
33
|
+
def __init__(self): self.tasks = []; self.mode = "default"; self.runs = []
|
|
34
|
+
async def run(self, task, sink, *, marathon=False, goal=""):
|
|
35
35
|
self.tasks.append(task)
|
|
36
|
+
self.runs.append({"task": task, "marathon": marathon, "goal": goal})
|
|
36
37
|
return f"answer:{task}"
|
|
37
38
|
|
|
38
39
|
|
|
@@ -95,7 +96,8 @@ def _run(**kw):
|
|
|
95
96
|
cfg = Config(api_url="http://x", panel_url="http://p")
|
|
96
97
|
sink = kw.pop("sink", FakeSink())
|
|
97
98
|
agent = kw.pop("agent", FakeAgent())
|
|
98
|
-
|
|
99
|
+
once = kw.pop("once", False)
|
|
100
|
+
asyncio.run(run_repl(cfg, "default", once=once, sink=sink, agent_factory=lambda c, tp, ws, m: agent,
|
|
99
101
|
read_line=kw.pop("read_line"), auth=kw.pop("auth", FakeAuth()),
|
|
100
102
|
account_fetcher=kw.pop("account_fetcher", _fake_account_fetcher),
|
|
101
103
|
sessions_client=kw.pop("sessions_client", FakeSessions()),
|
|
@@ -109,6 +111,20 @@ def test_task_is_sent_to_agent_and_answer_rendered():
|
|
|
109
111
|
assert sink.turns == ["answer:исправь баг"]
|
|
110
112
|
|
|
111
113
|
|
|
114
|
+
def test_interactive_default_is_marathon():
|
|
115
|
+
# Marathon is the default: a typed task self-drives (marathon=True) with the
|
|
116
|
+
# task carried as the goal.
|
|
117
|
+
sink, agent = _run(read_line=_lines("build a thing", "/exit"))
|
|
118
|
+
assert agent.runs and agent.runs[0]["marathon"] is True
|
|
119
|
+
assert agent.runs[0]["goal"] == "build a thing"
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def test_once_flag_uses_bounded_coding():
|
|
123
|
+
# --once opts back into the bounded, non-marathon coding turn.
|
|
124
|
+
sink, agent = _run(read_line=_lines("build a thing", "/exit"), once=True)
|
|
125
|
+
assert agent.runs and agent.runs[0]["marathon"] is False
|
|
126
|
+
|
|
127
|
+
|
|
112
128
|
def test_exit_command_stops_loop():
|
|
113
129
|
sink, agent = _run(read_line=_lines("/exit"))
|
|
114
130
|
assert agent.tasks == []
|
|
@@ -133,7 +149,7 @@ def test_logout_command_calls_auth():
|
|
|
133
149
|
|
|
134
150
|
def test_agent_error_is_swallowed_and_loop_continues():
|
|
135
151
|
class RaisingAgent(FakeAgent):
|
|
136
|
-
async def run(self, task, sink):
|
|
152
|
+
async def run(self, task, sink, *, marathon=False, goal=""):
|
|
137
153
|
self.tasks.append(task)
|
|
138
154
|
raise RuntimeError("boom")
|
|
139
155
|
|
|
@@ -204,7 +220,7 @@ def test_clear_command_clears_sink():
|
|
|
204
220
|
|
|
205
221
|
def test_ctrl_c_mid_turn_aborts_and_returns_to_prompt():
|
|
206
222
|
class InterruptingAgent(FakeAgent):
|
|
207
|
-
async def run(self, task, sink):
|
|
223
|
+
async def run(self, task, sink, *, marathon=False, goal=""):
|
|
208
224
|
self.tasks.append(task)
|
|
209
225
|
raise KeyboardInterrupt
|
|
210
226
|
|
|
@@ -309,7 +325,7 @@ class _SpyAgent:
|
|
|
309
325
|
self.mode = mode
|
|
310
326
|
self.steps = []
|
|
311
327
|
|
|
312
|
-
async def run(self, task, sink):
|
|
328
|
+
async def run(self, task, sink, *, marathon=False, goal=""):
|
|
313
329
|
return f"answer:{task}"
|
|
314
330
|
|
|
315
331
|
async def stop(self): ...
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.1.19"
|
|
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
|