zwarm 2.3__py3-none-any.whl → 3.0__py3-none-any.whl
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.
- zwarm/cli/main.py +210 -1
- zwarm/cli/pilot.py +1000 -0
- zwarm/core/environment.py +51 -32
- zwarm/orchestrator.py +8 -3
- zwarm/prompts/__init__.py +3 -0
- zwarm/prompts/orchestrator.py +36 -29
- zwarm/prompts/pilot.py +147 -0
- zwarm/sessions/manager.py +112 -0
- zwarm/tools/delegation.py +151 -28
- zwarm/watchers/__init__.py +5 -0
- zwarm/watchers/llm_watcher.py +319 -0
- {zwarm-2.3.dist-info → zwarm-3.0.dist-info}/METADATA +1 -1
- {zwarm-2.3.dist-info → zwarm-3.0.dist-info}/RECORD +15 -12
- {zwarm-2.3.dist-info → zwarm-3.0.dist-info}/WHEEL +0 -0
- {zwarm-2.3.dist-info → zwarm-3.0.dist-info}/entry_points.txt +0 -0
zwarm/cli/main.py
CHANGED
|
@@ -79,6 +79,7 @@ app = typer.Typer(
|
|
|
79
79
|
[cyan]init[/] Initialize zwarm (creates .zwarm/ with config)
|
|
80
80
|
[cyan]reset[/] Reset state and optionally config files
|
|
81
81
|
[cyan]orchestrate[/] Start orchestrator to delegate tasks to executors
|
|
82
|
+
[cyan]pilot[/] Conversational orchestrator REPL (interactive)
|
|
82
83
|
[cyan]exec[/] Run a single executor directly (for testing)
|
|
83
84
|
[cyan]status[/] Show current state (sessions, tasks, events)
|
|
84
85
|
[cyan]history[/] Show event history log
|
|
@@ -274,6 +275,112 @@ def orchestrate(
|
|
|
274
275
|
sys.exit(1)
|
|
275
276
|
|
|
276
277
|
|
|
278
|
+
class PilotLM(str, Enum):
|
|
279
|
+
"""LM options for pilot mode."""
|
|
280
|
+
gpt5_mini = "gpt5-mini" # GPT5MiniTester - fast, cheap, good for testing
|
|
281
|
+
gpt5 = "gpt5" # GPT5Large - standard
|
|
282
|
+
gpt5_verbose = "gpt5-verbose" # GPT5LargeVerbose - with extended thinking
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
@app.command()
|
|
286
|
+
def pilot(
|
|
287
|
+
task: Annotated[Optional[str], typer.Option("--task", "-t", help="Initial task (optional)")] = None,
|
|
288
|
+
task_file: Annotated[Optional[Path], typer.Option("--task-file", "-f", help="Read task from file")] = None,
|
|
289
|
+
config: Annotated[Optional[Path], typer.Option("--config", "-c", help="Path to config YAML")] = None,
|
|
290
|
+
overrides: Annotated[Optional[list[str]], typer.Option("--set", help="Override config (key=value)")] = None,
|
|
291
|
+
working_dir: Annotated[Path, typer.Option("--working-dir", "-w", help="Working directory")] = Path("."),
|
|
292
|
+
instance: Annotated[Optional[str], typer.Option("--instance", "-i", help="Instance ID (for isolation)")] = None,
|
|
293
|
+
instance_name: Annotated[Optional[str], typer.Option("--name", "-n", help="Human-readable instance name")] = None,
|
|
294
|
+
model: Annotated[PilotLM, typer.Option("--model", "-m", help="LM to use")] = PilotLM.gpt5_verbose,
|
|
295
|
+
):
|
|
296
|
+
"""
|
|
297
|
+
Interactive conversational orchestrator REPL.
|
|
298
|
+
|
|
299
|
+
Like 'orchestrate' but conversational: give instructions, watch the
|
|
300
|
+
orchestrator work, course-correct in real-time, time-travel to checkpoints.
|
|
301
|
+
|
|
302
|
+
[bold]Features:[/]
|
|
303
|
+
- Streaming display of orchestrator thinking and tool calls
|
|
304
|
+
- Turn-by-turn execution with checkpoints
|
|
305
|
+
- Time travel (:goto T1) to return to previous states
|
|
306
|
+
- Session visibility (:sessions) and state inspection (:state)
|
|
307
|
+
|
|
308
|
+
[bold]Commands:[/]
|
|
309
|
+
:help Show help
|
|
310
|
+
:history [N|all] Show turn checkpoints
|
|
311
|
+
:goto <turn|root> Time travel (e.g., :goto T1)
|
|
312
|
+
:state Show orchestrator state
|
|
313
|
+
:sessions Show active executor sessions
|
|
314
|
+
:reasoning on|off Toggle reasoning display
|
|
315
|
+
:quit Exit
|
|
316
|
+
|
|
317
|
+
[bold]LM Options:[/]
|
|
318
|
+
gpt5-mini GPT5MiniTester - fast/cheap, good for testing
|
|
319
|
+
gpt5 GPT5Large - standard model
|
|
320
|
+
gpt5-verbose GPT5LargeVerbose - with extended thinking (default)
|
|
321
|
+
|
|
322
|
+
[bold]Examples:[/]
|
|
323
|
+
[dim]# Start fresh, give instructions interactively[/]
|
|
324
|
+
$ zwarm pilot
|
|
325
|
+
|
|
326
|
+
[dim]# Start with an initial task[/]
|
|
327
|
+
$ zwarm pilot --task "Build user authentication"
|
|
328
|
+
|
|
329
|
+
[dim]# Use faster model for testing[/]
|
|
330
|
+
$ zwarm pilot --model gpt5-mini
|
|
331
|
+
|
|
332
|
+
[dim]# Named instance[/]
|
|
333
|
+
$ zwarm pilot --name my-feature
|
|
334
|
+
"""
|
|
335
|
+
from zwarm.cli.pilot import run_pilot, build_pilot_orchestrator
|
|
336
|
+
|
|
337
|
+
# Resolve task (optional for pilot)
|
|
338
|
+
resolved_task = _resolve_task(task, task_file)
|
|
339
|
+
|
|
340
|
+
console.print(f"[bold]Starting pilot session...[/]")
|
|
341
|
+
console.print(f" Working dir: {working_dir.absolute()}")
|
|
342
|
+
console.print(f" Model: {model.value}")
|
|
343
|
+
if resolved_task:
|
|
344
|
+
console.print(f" Initial task: {resolved_task[:60]}...")
|
|
345
|
+
if instance:
|
|
346
|
+
console.print(f" Instance: {instance}" + (f" ({instance_name})" if instance_name else ""))
|
|
347
|
+
console.print()
|
|
348
|
+
|
|
349
|
+
orchestrator = None
|
|
350
|
+
try:
|
|
351
|
+
orchestrator = build_pilot_orchestrator(
|
|
352
|
+
config_path=config,
|
|
353
|
+
working_dir=working_dir.absolute(),
|
|
354
|
+
overrides=list(overrides or []),
|
|
355
|
+
instance_id=instance,
|
|
356
|
+
instance_name=instance_name,
|
|
357
|
+
lm_choice=model.value,
|
|
358
|
+
)
|
|
359
|
+
|
|
360
|
+
# Show instance ID if auto-generated
|
|
361
|
+
if orchestrator.instance_id and not instance:
|
|
362
|
+
console.print(f" [dim]Instance: {orchestrator.instance_id[:8]}[/]")
|
|
363
|
+
|
|
364
|
+
# Run the pilot REPL
|
|
365
|
+
run_pilot(orchestrator, initial_task=resolved_task)
|
|
366
|
+
|
|
367
|
+
# Save state on exit
|
|
368
|
+
orchestrator.save_state()
|
|
369
|
+
console.print("\n[dim]State saved.[/]")
|
|
370
|
+
|
|
371
|
+
except KeyboardInterrupt:
|
|
372
|
+
console.print("\n\n[yellow]Interrupted.[/]")
|
|
373
|
+
if orchestrator:
|
|
374
|
+
orchestrator.save_state()
|
|
375
|
+
console.print("[dim]State saved.[/]")
|
|
376
|
+
sys.exit(1)
|
|
377
|
+
except Exception as e:
|
|
378
|
+
console.print(f"\n[red]Error:[/] {e}")
|
|
379
|
+
import traceback
|
|
380
|
+
traceback.print_exc()
|
|
381
|
+
sys.exit(1)
|
|
382
|
+
|
|
383
|
+
|
|
277
384
|
@app.command()
|
|
278
385
|
def exec(
|
|
279
386
|
task: Annotated[str, typer.Option("--task", "-t", help="Task to execute")],
|
|
@@ -1151,6 +1258,7 @@ def interactive(
|
|
|
1151
1258
|
[cyan]ls[/] / [cyan]list[/] Dashboard of all sessions
|
|
1152
1259
|
[cyan]?[/] ID Quick peek (status + latest message)
|
|
1153
1260
|
[cyan]show[/] ID Full session details & history
|
|
1261
|
+
[cyan]traj[/] ID Show trajectory (all steps taken)
|
|
1154
1262
|
[cyan]c[/] / [cyan]continue[/] ID "msg" Continue a sync conversation
|
|
1155
1263
|
[cyan]kill[/] ID Stop a session (keeps in history)
|
|
1156
1264
|
[cyan]rm[/] ID Delete session entirely
|
|
@@ -1230,11 +1338,14 @@ def interactive(
|
|
|
1230
1338
|
help_table.add_row(" --async", "Background mode (don't wait)")
|
|
1231
1339
|
help_table.add_row("", "")
|
|
1232
1340
|
help_table.add_row("ls / list", "Dashboard of all sessions")
|
|
1233
|
-
help_table.add_row("? /
|
|
1341
|
+
help_table.add_row("? ID / peek ID", "Quick peek (status + latest message)")
|
|
1342
|
+
help_table.add_row("show ID", "Full session details & messages")
|
|
1343
|
+
help_table.add_row("traj ID [--full]", "Show trajectory (all steps taken)")
|
|
1234
1344
|
help_table.add_row('c ID "msg"', "Continue conversation (wait for response)")
|
|
1235
1345
|
help_table.add_row('ca ID "msg"', "Continue async (fire-and-forget)")
|
|
1236
1346
|
help_table.add_row("check ID", "Check session status")
|
|
1237
1347
|
help_table.add_row("kill ID", "Stop a running session")
|
|
1348
|
+
help_table.add_row("rm ID", "Delete session entirely")
|
|
1238
1349
|
help_table.add_row("killall", "Stop all running sessions")
|
|
1239
1350
|
help_table.add_row("clean", "Remove old completed sessions")
|
|
1240
1351
|
help_table.add_row("q / quit", "Exit")
|
|
@@ -1619,6 +1730,93 @@ def interactive(
|
|
|
1619
1730
|
if session.error:
|
|
1620
1731
|
console.print(f"[red]Error:[/] {session.error}")
|
|
1621
1732
|
|
|
1733
|
+
def do_trajectory(session_id: str, full: bool = False):
|
|
1734
|
+
"""Show the full trajectory of a session - all steps in order."""
|
|
1735
|
+
from zwarm.sessions import CodexSessionManager
|
|
1736
|
+
|
|
1737
|
+
manager = CodexSessionManager(default_dir / ".zwarm")
|
|
1738
|
+
session = manager.get_session(session_id)
|
|
1739
|
+
|
|
1740
|
+
if not session:
|
|
1741
|
+
console.print(f" [red]Session not found:[/] {session_id}")
|
|
1742
|
+
return
|
|
1743
|
+
|
|
1744
|
+
trajectory = manager.get_trajectory(session_id, full=full)
|
|
1745
|
+
|
|
1746
|
+
if not trajectory:
|
|
1747
|
+
console.print("[dim]No trajectory data available.[/]")
|
|
1748
|
+
return
|
|
1749
|
+
|
|
1750
|
+
mode = "[bold](full)[/] " if full else ""
|
|
1751
|
+
console.print(f"\n[bold cyan]Trajectory: {session.short_id}[/] {mode}({len(trajectory)} steps)")
|
|
1752
|
+
console.print(f"[dim]Task: {session.task[:60]}{'...' if len(session.task) > 60 else ''}[/]")
|
|
1753
|
+
console.print()
|
|
1754
|
+
|
|
1755
|
+
# Display each step
|
|
1756
|
+
for step in trajectory:
|
|
1757
|
+
turn = step.get("turn", 1)
|
|
1758
|
+
step_num = step.get("step", 0)
|
|
1759
|
+
step_type = step.get("type", "unknown")
|
|
1760
|
+
|
|
1761
|
+
prefix = f"[dim]T{turn}.{step_num:02d}[/]"
|
|
1762
|
+
|
|
1763
|
+
if step_type == "reasoning":
|
|
1764
|
+
if full and step.get("full_text"):
|
|
1765
|
+
console.print(f"{prefix} [yellow]thinking:[/]")
|
|
1766
|
+
console.print(f" {step['full_text']}")
|
|
1767
|
+
else:
|
|
1768
|
+
summary = step.get("summary", "")
|
|
1769
|
+
console.print(f"{prefix} [yellow]thinking:[/] {summary}")
|
|
1770
|
+
|
|
1771
|
+
elif step_type == "command":
|
|
1772
|
+
cmd = step.get("command", "")
|
|
1773
|
+
output = step.get("output", "")
|
|
1774
|
+
exit_code = step.get("exit_code", "?")
|
|
1775
|
+
# Show command
|
|
1776
|
+
console.print(f"{prefix} [cyan]$ {cmd}[/]")
|
|
1777
|
+
if output:
|
|
1778
|
+
if full:
|
|
1779
|
+
# Show all output
|
|
1780
|
+
for line in output.split("\n"):
|
|
1781
|
+
console.print(f" [dim]{line}[/]")
|
|
1782
|
+
else:
|
|
1783
|
+
# Indent output, max 5 lines
|
|
1784
|
+
for line in output.split("\n")[:5]:
|
|
1785
|
+
console.print(f" [dim]{line}[/]")
|
|
1786
|
+
if output.count("\n") > 5:
|
|
1787
|
+
console.print(f" [dim]... ({output.count(chr(10))} lines)[/]")
|
|
1788
|
+
if exit_code != 0 and exit_code is not None:
|
|
1789
|
+
console.print(f" [red]exit: {exit_code}[/]")
|
|
1790
|
+
|
|
1791
|
+
elif step_type == "tool_call":
|
|
1792
|
+
tool = step.get("tool", "unknown")
|
|
1793
|
+
if full and step.get("full_args"):
|
|
1794
|
+
import json
|
|
1795
|
+
console.print(f"{prefix} [magenta]tool:[/] {tool}")
|
|
1796
|
+
console.print(f" {json.dumps(step['full_args'], indent=2)}")
|
|
1797
|
+
else:
|
|
1798
|
+
args = step.get("args_preview", "")
|
|
1799
|
+
console.print(f"{prefix} [magenta]tool:[/] {tool}({args})")
|
|
1800
|
+
|
|
1801
|
+
elif step_type == "tool_output":
|
|
1802
|
+
output = step.get("output", "")
|
|
1803
|
+
if not full:
|
|
1804
|
+
output = output[:100]
|
|
1805
|
+
console.print(f"{prefix} [dim]→ {output}[/]")
|
|
1806
|
+
|
|
1807
|
+
elif step_type == "message":
|
|
1808
|
+
if full and step.get("full_text"):
|
|
1809
|
+
console.print(f"{prefix} [green]response:[/]")
|
|
1810
|
+
console.print(f" {step['full_text']}")
|
|
1811
|
+
else:
|
|
1812
|
+
summary = step.get("summary", "")
|
|
1813
|
+
full_len = step.get("full_length", 0)
|
|
1814
|
+
console.print(f"{prefix} [green]response:[/] {summary}")
|
|
1815
|
+
if full_len > 200:
|
|
1816
|
+
console.print(f" [dim]({full_len} chars total)[/]")
|
|
1817
|
+
|
|
1818
|
+
console.print()
|
|
1819
|
+
|
|
1622
1820
|
def do_continue(session_id: str, message: str, wait: bool = True):
|
|
1623
1821
|
"""
|
|
1624
1822
|
Continue a conversation using CodexSessionManager.inject_message().
|
|
@@ -1872,6 +2070,17 @@ def interactive(
|
|
|
1872
2070
|
else:
|
|
1873
2071
|
do_show(args[0])
|
|
1874
2072
|
|
|
2073
|
+
elif cmd in ("traj", "trajectory"):
|
|
2074
|
+
if not args:
|
|
2075
|
+
console.print(" [red]Usage:[/] traj SESSION_ID [--full]")
|
|
2076
|
+
else:
|
|
2077
|
+
full_mode = "--full" in args
|
|
2078
|
+
session_arg = [a for a in args if a != "--full"]
|
|
2079
|
+
if session_arg:
|
|
2080
|
+
do_trajectory(session_arg[0], full=full_mode)
|
|
2081
|
+
else:
|
|
2082
|
+
console.print(" [red]Usage:[/] traj SESSION_ID [--full]")
|
|
2083
|
+
|
|
1875
2084
|
elif cmd in ("c", "continue"):
|
|
1876
2085
|
# Sync continue - waits for response
|
|
1877
2086
|
if len(args) < 2:
|