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 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("? / show ID", "Show session details & messages")
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: