zwarm 3.2.0__py3-none-any.whl → 3.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/interactive.py +2 -2
- zwarm/cli/main.py +75 -77
- zwarm/cli/pilot.py +3 -1
- zwarm/core/config.py +24 -9
- zwarm/core/test_config.py +2 -3
- zwarm/orchestrator.py +8 -44
- zwarm/sessions/manager.py +210 -90
- zwarm/tools/delegation.py +6 -1
- zwarm-3.3.0.dist-info/METADATA +396 -0
- {zwarm-3.2.0.dist-info → zwarm-3.3.0.dist-info}/RECORD +12 -19
- zwarm/adapters/__init__.py +0 -21
- zwarm/adapters/base.py +0 -109
- zwarm/adapters/claude_code.py +0 -357
- zwarm/adapters/codex_mcp.py +0 -1262
- zwarm/adapters/registry.py +0 -69
- zwarm/adapters/test_codex_mcp.py +0 -274
- zwarm/adapters/test_registry.py +0 -68
- zwarm-3.2.0.dist-info/METADATA +0 -310
- {zwarm-3.2.0.dist-info → zwarm-3.3.0.dist-info}/WHEEL +0 -0
- {zwarm-3.2.0.dist-info → zwarm-3.3.0.dist-info}/entry_points.txt +0 -0
zwarm/cli/interactive.py
CHANGED
|
@@ -157,7 +157,7 @@ def cmd_help():
|
|
|
157
157
|
table.add_column("Description")
|
|
158
158
|
|
|
159
159
|
table.add_row("[bold]Session Lifecycle[/]", "")
|
|
160
|
-
table.add_row('spawn "task" [--dir PATH]', "Start new session")
|
|
160
|
+
table.add_row('spawn "task" [--dir PATH] [--model M]', "Start new session")
|
|
161
161
|
table.add_row('c ID "message"', "Continue conversation")
|
|
162
162
|
table.add_row("kill ID | all", "Stop session(s)")
|
|
163
163
|
table.add_row("rm ID | all", "Delete session(s)")
|
|
@@ -694,7 +694,7 @@ def run_interactive(
|
|
|
694
694
|
|
|
695
695
|
elif cmd == "spawn":
|
|
696
696
|
if not args:
|
|
697
|
-
console.print(" [red]Usage:[/] spawn \"task\" [--dir PATH]")
|
|
697
|
+
console.print(" [red]Usage:[/] spawn \"task\" [--dir PATH] [--search]")
|
|
698
698
|
else:
|
|
699
699
|
# Parse spawn args
|
|
700
700
|
task_parts = []
|
zwarm/cli/main.py
CHANGED
|
@@ -122,16 +122,6 @@ Manage zwarm configurations.
|
|
|
122
122
|
app.add_typer(configs_app, name="configs")
|
|
123
123
|
|
|
124
124
|
|
|
125
|
-
class AdapterType(str, Enum):
|
|
126
|
-
codex_mcp = "codex_mcp"
|
|
127
|
-
claude_code = "claude_code"
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
class ModeType(str, Enum):
|
|
131
|
-
sync = "sync"
|
|
132
|
-
async_ = "async"
|
|
133
|
-
|
|
134
|
-
|
|
135
125
|
@app.command()
|
|
136
126
|
def orchestrate(
|
|
137
127
|
task: Annotated[Optional[str], typer.Option("--task", "-t", help="The task to accomplish")] = None,
|
|
@@ -384,78 +374,68 @@ def pilot(
|
|
|
384
374
|
@app.command()
|
|
385
375
|
def exec(
|
|
386
376
|
task: Annotated[str, typer.Option("--task", "-t", help="Task to execute")],
|
|
387
|
-
adapter: Annotated[AdapterType, typer.Option("--adapter", "-a", help="Executor adapter")] = AdapterType.codex_mcp,
|
|
388
|
-
mode: Annotated[ModeType, typer.Option("--mode", "-m", help="Execution mode")] = ModeType.sync,
|
|
389
377
|
working_dir: Annotated[Path, typer.Option("--working-dir", "-w", help="Working directory")] = Path("."),
|
|
390
378
|
model: Annotated[Optional[str], typer.Option("--model", help="Model override")] = None,
|
|
379
|
+
wait: Annotated[bool, typer.Option("--wait", help="Wait for completion and show result")] = False,
|
|
391
380
|
):
|
|
392
381
|
"""
|
|
393
|
-
Run a single
|
|
382
|
+
Run a single Codex session directly (for testing).
|
|
394
383
|
|
|
395
|
-
|
|
384
|
+
Spawns a session using CodexSessionManager - same as interactive/pilot.
|
|
385
|
+
Web search is always enabled via .codex/config.toml (set up by `zwarm init`).
|
|
396
386
|
|
|
397
387
|
[bold]Examples:[/]
|
|
398
|
-
[dim]#
|
|
399
|
-
$ zwarm exec --task "What is 2+2?"
|
|
388
|
+
[dim]# Quick test[/]
|
|
389
|
+
$ zwarm exec --task "What is 2+2?" --wait
|
|
400
390
|
|
|
401
|
-
[dim]#
|
|
402
|
-
$ zwarm exec
|
|
391
|
+
[dim]# Run in background[/]
|
|
392
|
+
$ zwarm exec --task "Build feature"
|
|
403
393
|
|
|
404
|
-
[dim]#
|
|
405
|
-
$ zwarm exec --task "
|
|
394
|
+
[dim]# Web search is always available[/]
|
|
395
|
+
$ zwarm exec --task "Find latest FastAPI docs" --wait
|
|
406
396
|
"""
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
console.print(f"[bold]Running executor directly...[/]")
|
|
410
|
-
console.print(f" Adapter: [cyan]{adapter.value}[/]")
|
|
411
|
-
console.print(f" Mode: {mode.value}")
|
|
412
|
-
console.print(f" Task: {task}")
|
|
413
|
-
|
|
414
|
-
# Use isolated codex config if available
|
|
415
|
-
config_path = working_dir / ".zwarm" / "codex.toml"
|
|
416
|
-
if not config_path.exists():
|
|
417
|
-
config_path = None
|
|
418
|
-
|
|
419
|
-
try:
|
|
420
|
-
executor = get_adapter(adapter.value, model=model, config_path=config_path)
|
|
421
|
-
except ValueError as e:
|
|
422
|
-
console.print(f"[red]Error:[/] {e}")
|
|
423
|
-
sys.exit(1)
|
|
424
|
-
|
|
425
|
-
async def run():
|
|
426
|
-
try:
|
|
427
|
-
session = await executor.start_session(
|
|
428
|
-
task=task,
|
|
429
|
-
working_dir=working_dir.absolute(),
|
|
430
|
-
mode=mode.value,
|
|
431
|
-
model=model,
|
|
432
|
-
)
|
|
433
|
-
|
|
434
|
-
console.print(f"\n[green]Session started:[/] {session.id[:8]}")
|
|
435
|
-
|
|
436
|
-
if mode == ModeType.sync:
|
|
437
|
-
response = session.messages[-1].content if session.messages else "(no response)"
|
|
438
|
-
console.print(f"\n[bold]Response:[/]\n{response}")
|
|
397
|
+
import time
|
|
398
|
+
from zwarm.sessions import CodexSessionManager, SessionStatus
|
|
439
399
|
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
if user_input.lower() == "exit" or not user_input:
|
|
445
|
-
break
|
|
400
|
+
console.print(f"[bold]Running Codex session...[/]")
|
|
401
|
+
console.print(f" Task: {task[:60]}{'...' if len(task) > 60 else ''}")
|
|
402
|
+
if model:
|
|
403
|
+
console.print(f" Model: {model}")
|
|
446
404
|
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
except KeyboardInterrupt:
|
|
450
|
-
break
|
|
451
|
-
else:
|
|
452
|
-
console.print("[dim]Async mode - session running in background.[/]")
|
|
453
|
-
console.print("Use 'zwarm status' to check progress.")
|
|
405
|
+
manager = CodexSessionManager(working_dir / ".zwarm")
|
|
406
|
+
effective_model = model or "gpt-5.1-codex-mini"
|
|
454
407
|
|
|
455
|
-
|
|
456
|
-
|
|
408
|
+
session = manager.start_session(
|
|
409
|
+
task=task,
|
|
410
|
+
working_dir=working_dir.absolute(),
|
|
411
|
+
model=effective_model,
|
|
412
|
+
)
|
|
457
413
|
|
|
458
|
-
|
|
414
|
+
console.print(f"\n[green]Session started:[/] {session.short_id}")
|
|
415
|
+
|
|
416
|
+
if wait:
|
|
417
|
+
console.print("[dim]Waiting for completion...[/]")
|
|
418
|
+
while True:
|
|
419
|
+
time.sleep(2)
|
|
420
|
+
session = manager.get_session(session.id)
|
|
421
|
+
if session.status != SessionStatus.RUNNING:
|
|
422
|
+
break
|
|
423
|
+
|
|
424
|
+
if session.status == SessionStatus.COMPLETED:
|
|
425
|
+
console.print(f"\n[green]✓ Completed[/]")
|
|
426
|
+
# Show last assistant message
|
|
427
|
+
for msg in reversed(session.messages):
|
|
428
|
+
if msg.role == "assistant":
|
|
429
|
+
console.print(f"\n[bold]Response:[/]\n{msg.content}")
|
|
430
|
+
break
|
|
431
|
+
else:
|
|
432
|
+
console.print(f"\n[red]Status:[/] {session.status.value}")
|
|
433
|
+
if session.error:
|
|
434
|
+
console.print(f"[red]Error:[/] {session.error}")
|
|
435
|
+
else:
|
|
436
|
+
console.print("[dim]Running in background. Check with:[/]")
|
|
437
|
+
console.print(f" zwarm sessions")
|
|
438
|
+
console.print(f" zwarm session show {session.short_id}")
|
|
459
439
|
|
|
460
440
|
|
|
461
441
|
@app.command()
|
|
@@ -740,12 +720,12 @@ def init(
|
|
|
740
720
|
[bold]Creates:[/]
|
|
741
721
|
[cyan].zwarm/[/] State directory for sessions and events
|
|
742
722
|
[cyan].zwarm/config.toml[/] Runtime settings (weave, adapter, watchers)
|
|
743
|
-
[cyan].zwarm/codex.toml[/] Codex CLI settings (model,
|
|
723
|
+
[cyan].zwarm/codex.toml[/] Codex CLI settings (model, web search, etc.)
|
|
744
724
|
[cyan]zwarm.yaml[/] Project config (optional, with --with-project)
|
|
745
725
|
|
|
746
726
|
[bold]Configuration relationship:[/]
|
|
747
727
|
config.toml → Controls zwarm itself (tracing, which watchers run)
|
|
748
|
-
codex.toml →
|
|
728
|
+
codex.toml → Codex settings, parsed by zwarm and passed via -c overrides
|
|
749
729
|
zwarm.yaml → Project-specific context injected into orchestrator
|
|
750
730
|
|
|
751
731
|
[bold]Examples:[/]
|
|
@@ -939,7 +919,7 @@ def init(
|
|
|
939
919
|
# Explain config files
|
|
940
920
|
console.print("[bold]Configuration files:[/]")
|
|
941
921
|
console.print(" [cyan].zwarm/config.toml[/] - Runtime settings (Weave tracing, watchers)")
|
|
942
|
-
console.print(" [cyan].zwarm/codex.toml[/] - Codex CLI settings (model,
|
|
922
|
+
console.print(" [cyan].zwarm/codex.toml[/] - Codex CLI settings (model, web search, sandbox)")
|
|
943
923
|
if create_project_config:
|
|
944
924
|
console.print(" [cyan]zwarm.yaml[/] - Project context and constraints")
|
|
945
925
|
console.print()
|
|
@@ -982,6 +962,7 @@ def _generate_config_toml(
|
|
|
982
962
|
"[executor]",
|
|
983
963
|
f'adapter = "{adapter}"',
|
|
984
964
|
"# model = \"\" # Optional model override",
|
|
965
|
+
"# web_search = false # Enable web search for delegated sessions",
|
|
985
966
|
"",
|
|
986
967
|
"[watchers]",
|
|
987
968
|
f"enabled = {watchers}",
|
|
@@ -1006,22 +987,35 @@ def _generate_codex_toml(
|
|
|
1006
987
|
"""
|
|
1007
988
|
Generate codex.toml for isolated codex configuration.
|
|
1008
989
|
|
|
1009
|
-
This file is
|
|
1010
|
-
|
|
990
|
+
This file is parsed by zwarm and settings are passed to codex via -c overrides.
|
|
991
|
+
Each .zwarm directory has its own codex config, independent of ~/.codex/config.toml.
|
|
1011
992
|
"""
|
|
1012
993
|
lines = [
|
|
1013
994
|
"# Codex configuration for zwarm",
|
|
1014
|
-
"#
|
|
995
|
+
"# zwarm parses this file and passes settings to codex via -c overrides",
|
|
996
|
+
"# Each .zwarm dir has its own config, independent of ~/.codex/config.toml",
|
|
1015
997
|
"# Generated by 'zwarm init'",
|
|
1016
998
|
"",
|
|
1017
999
|
"# Model settings",
|
|
1018
1000
|
f'model = "{model}"',
|
|
1019
1001
|
f'model_reasoning_effort = "{reasoning_effort}" # low | medium | high',
|
|
1020
1002
|
"",
|
|
1021
|
-
"#
|
|
1022
|
-
"#
|
|
1003
|
+
"# DANGER MODE - bypasses all safety controls",
|
|
1004
|
+
"# Set to true to use --dangerously-bypass-approvals-and-sandbox",
|
|
1005
|
+
"full_danger = true",
|
|
1006
|
+
"",
|
|
1007
|
+
"# Web search - enables web_search tool for agents",
|
|
1008
|
+
"[features]",
|
|
1009
|
+
"web_search_request = true",
|
|
1023
1010
|
"",
|
|
1024
|
-
"#
|
|
1011
|
+
"# Sandbox settings - network access required for web search",
|
|
1012
|
+
"[sandbox_workspace_write]",
|
|
1013
|
+
"network_access = true",
|
|
1014
|
+
"",
|
|
1015
|
+
"# Approval policy - 'never' means no human approval needed",
|
|
1016
|
+
"# approval_policy = \"never\"",
|
|
1017
|
+
"",
|
|
1018
|
+
"# You can add any codex config key here",
|
|
1025
1019
|
"# See: https://github.com/openai/codex#configuration",
|
|
1026
1020
|
"",
|
|
1027
1021
|
]
|
|
@@ -1534,6 +1528,7 @@ def session_start(
|
|
|
1534
1528
|
Start a new Codex session in the background.
|
|
1535
1529
|
|
|
1536
1530
|
The session runs independently and you can check on it later.
|
|
1531
|
+
Web search is always enabled via .codex/config.toml (set up by `zwarm init`).
|
|
1537
1532
|
|
|
1538
1533
|
[bold]Examples:[/]
|
|
1539
1534
|
[dim]# Simple task[/]
|
|
@@ -1541,6 +1536,9 @@ def session_start(
|
|
|
1541
1536
|
|
|
1542
1537
|
[dim]# With specific model[/]
|
|
1543
1538
|
$ zwarm session start "Refactor the API" --model gpt-5.1-codex-max
|
|
1539
|
+
|
|
1540
|
+
[dim]# Web search is always available[/]
|
|
1541
|
+
$ zwarm session start "Research latest OAuth2 best practices"
|
|
1544
1542
|
"""
|
|
1545
1543
|
from zwarm.sessions import CodexSessionManager
|
|
1546
1544
|
|
zwarm/cli/pilot.py
CHANGED
|
@@ -186,10 +186,12 @@ def build_pilot_orchestrator(
|
|
|
186
186
|
lm_class = lm_map.get(lm_choice, GPT5LargeVerbose)
|
|
187
187
|
lm = lm_class()
|
|
188
188
|
|
|
189
|
-
# Load configuration
|
|
189
|
+
# Load configuration from working_dir (not cwd!)
|
|
190
|
+
# This ensures config.toml and .env are loaded from the project being worked on
|
|
190
191
|
config = load_config(
|
|
191
192
|
config_path=config_path,
|
|
192
193
|
overrides=overrides,
|
|
194
|
+
working_dir=working_dir,
|
|
193
195
|
)
|
|
194
196
|
|
|
195
197
|
# Resolve working directory
|
zwarm/core/config.py
CHANGED
|
@@ -37,6 +37,7 @@ class ExecutorConfig:
|
|
|
37
37
|
sandbox: str = "workspace-write" # read-only | workspace-write | danger-full-access
|
|
38
38
|
timeout: int = 3600
|
|
39
39
|
reasoning_effort: str | None = "high" # low | medium | high (default to high for compatibility)
|
|
40
|
+
# Note: web_search is always enabled via .codex/config.toml (set up by `zwarm init`)
|
|
40
41
|
|
|
41
42
|
|
|
42
43
|
@dataclass
|
|
@@ -60,7 +61,6 @@ class OrchestratorConfig:
|
|
|
60
61
|
tools: list[str] = field(default_factory=lambda: ["delegate", "converse", "check_session", "end_session", "bash"])
|
|
61
62
|
max_steps: int = 50
|
|
62
63
|
parallel_delegations: int = 4
|
|
63
|
-
sync_first: bool = True # prefer sync mode by default
|
|
64
64
|
compaction: CompactionConfig = field(default_factory=CompactionConfig)
|
|
65
65
|
|
|
66
66
|
# Directory restrictions for agent delegations
|
|
@@ -173,7 +173,6 @@ class ZwarmConfig:
|
|
|
173
173
|
"tools": self.orchestrator.tools,
|
|
174
174
|
"max_steps": self.orchestrator.max_steps,
|
|
175
175
|
"parallel_delegations": self.orchestrator.parallel_delegations,
|
|
176
|
-
"sync_first": self.orchestrator.sync_first,
|
|
177
176
|
"compaction": {
|
|
178
177
|
"enabled": self.orchestrator.compaction.enabled,
|
|
179
178
|
"max_tokens": self.orchestrator.compaction.max_tokens,
|
|
@@ -195,15 +194,16 @@ class ZwarmConfig:
|
|
|
195
194
|
}
|
|
196
195
|
|
|
197
196
|
|
|
198
|
-
def load_env(path: Path | None = None) -> None:
|
|
197
|
+
def load_env(path: Path | None = None, base_dir: Path | None = None) -> None:
|
|
199
198
|
"""Load .env file if it exists."""
|
|
200
199
|
if path is None:
|
|
201
|
-
|
|
200
|
+
base = base_dir or Path.cwd()
|
|
201
|
+
path = base / ".env"
|
|
202
202
|
if path.exists():
|
|
203
203
|
load_dotenv(path)
|
|
204
204
|
|
|
205
205
|
|
|
206
|
-
def load_toml_config(path: Path | None = None) -> dict[str, Any]:
|
|
206
|
+
def load_toml_config(path: Path | None = None, base_dir: Path | None = None) -> dict[str, Any]:
|
|
207
207
|
"""
|
|
208
208
|
Load config.toml file.
|
|
209
209
|
|
|
@@ -211,11 +211,16 @@ def load_toml_config(path: Path | None = None) -> dict[str, Any]:
|
|
|
211
211
|
1. Explicit path (if provided)
|
|
212
212
|
2. .zwarm/config.toml (new standard location)
|
|
213
213
|
3. config.toml (legacy location for backwards compat)
|
|
214
|
+
|
|
215
|
+
Args:
|
|
216
|
+
path: Explicit path to config.toml
|
|
217
|
+
base_dir: Base directory to search in (defaults to cwd)
|
|
214
218
|
"""
|
|
215
219
|
if path is None:
|
|
220
|
+
base = base_dir or Path.cwd()
|
|
216
221
|
# Try new location first
|
|
217
|
-
new_path =
|
|
218
|
-
legacy_path =
|
|
222
|
+
new_path = base / ".zwarm" / "config.toml"
|
|
223
|
+
legacy_path = base / "config.toml"
|
|
219
224
|
if new_path.exists():
|
|
220
225
|
path = new_path
|
|
221
226
|
elif legacy_path.exists():
|
|
@@ -306,6 +311,7 @@ def load_config(
|
|
|
306
311
|
toml_path: Path | None = None,
|
|
307
312
|
env_path: Path | None = None,
|
|
308
313
|
overrides: list[str] | None = None,
|
|
314
|
+
working_dir: Path | None = None,
|
|
309
315
|
) -> ZwarmConfig:
|
|
310
316
|
"""
|
|
311
317
|
Load configuration with full precedence chain:
|
|
@@ -314,15 +320,24 @@ def load_config(
|
|
|
314
320
|
3. YAML config file (if provided)
|
|
315
321
|
4. CLI overrides (--set key=value)
|
|
316
322
|
5. Environment variables (for secrets)
|
|
323
|
+
|
|
324
|
+
Args:
|
|
325
|
+
config_path: Path to YAML config file
|
|
326
|
+
toml_path: Explicit path to config.toml
|
|
327
|
+
env_path: Explicit path to .env file
|
|
328
|
+
overrides: CLI overrides (--set key=value)
|
|
329
|
+
working_dir: Working directory to search for config files (defaults to cwd).
|
|
330
|
+
This is important when using --working-dir flag to ensure
|
|
331
|
+
config is loaded from the project directory, not invoke directory.
|
|
317
332
|
"""
|
|
318
333
|
# Load .env first (for secrets)
|
|
319
|
-
load_env(env_path)
|
|
334
|
+
load_env(env_path, base_dir=working_dir)
|
|
320
335
|
|
|
321
336
|
# Start with defaults
|
|
322
337
|
config_dict: dict[str, Any] = {}
|
|
323
338
|
|
|
324
339
|
# Layer in config.toml
|
|
325
|
-
toml_config = load_toml_config(toml_path)
|
|
340
|
+
toml_config = load_toml_config(toml_path, base_dir=working_dir)
|
|
326
341
|
if toml_config:
|
|
327
342
|
config_dict = deep_merge(config_dict, toml_config)
|
|
328
343
|
|
zwarm/core/test_config.py
CHANGED
|
@@ -20,7 +20,6 @@ def test_default_config():
|
|
|
20
20
|
assert config.executor.adapter == "codex_mcp"
|
|
21
21
|
assert config.executor.sandbox == "workspace-write"
|
|
22
22
|
assert config.orchestrator.lm == "gpt-5-mini"
|
|
23
|
-
assert config.orchestrator.sync_first is True
|
|
24
23
|
assert config.state_dir == ".zwarm"
|
|
25
24
|
|
|
26
25
|
|
|
@@ -68,8 +67,8 @@ def test_apply_overrides():
|
|
|
68
67
|
assert result["executor"]["adapter"] == "claude_code"
|
|
69
68
|
|
|
70
69
|
# Override with boolean
|
|
71
|
-
result = apply_overrides(config, ["
|
|
72
|
-
assert result["
|
|
70
|
+
result = apply_overrides(config, ["executor.web_search=true"])
|
|
71
|
+
assert result["executor"]["web_search"] is True
|
|
73
72
|
|
|
74
73
|
# Create new nested path
|
|
75
74
|
result = apply_overrides(config, ["weave.project=my-project"])
|
zwarm/orchestrator.py
CHANGED
|
@@ -23,7 +23,6 @@ from wbal.helper import TOOL_CALL_TYPE, format_openai_tool_response
|
|
|
23
23
|
from wbal.lm import LM as wbalLMGeneric
|
|
24
24
|
from wbal.lm import GPT5LargeVerbose
|
|
25
25
|
|
|
26
|
-
from zwarm.adapters import ExecutorAdapter, get_adapter
|
|
27
26
|
from zwarm.core.compact import compact_messages, should_compact
|
|
28
27
|
from zwarm.core.config import ZwarmConfig, load_config
|
|
29
28
|
from zwarm.core.environment import OrchestratorEnv
|
|
@@ -72,7 +71,6 @@ class Orchestrator(YamlAgent):
|
|
|
72
71
|
# State management
|
|
73
72
|
_state: StateManager = PrivateAttr()
|
|
74
73
|
_sessions: dict[str, ConversationSession] = PrivateAttr(default_factory=dict)
|
|
75
|
-
_adapters: dict[str, ExecutorAdapter] = PrivateAttr(default_factory=dict)
|
|
76
74
|
_watcher_manager: WatcherManager | None = PrivateAttr(default=None)
|
|
77
75
|
_resumed: bool = PrivateAttr(default=False)
|
|
78
76
|
_total_tokens: int = PrivateAttr(default=0) # Cumulative orchestrator tokens
|
|
@@ -85,7 +83,7 @@ class Orchestrator(YamlAgent):
|
|
|
85
83
|
)
|
|
86
84
|
|
|
87
85
|
def model_post_init(self, __context: Any) -> None:
|
|
88
|
-
"""Initialize state
|
|
86
|
+
"""Initialize state after model creation."""
|
|
89
87
|
super().model_post_init(__context)
|
|
90
88
|
|
|
91
89
|
# Initialize state manager with instance isolation
|
|
@@ -151,41 +149,6 @@ class Orchestrator(YamlAgent):
|
|
|
151
149
|
"""Access state manager."""
|
|
152
150
|
return self._state
|
|
153
151
|
|
|
154
|
-
def _get_adapter(self, name: str) -> ExecutorAdapter:
|
|
155
|
-
"""Get or create an adapter by name using the adapter registry."""
|
|
156
|
-
if name not in self._adapters:
|
|
157
|
-
# Get model from config (adapters have their own defaults if None)
|
|
158
|
-
model = self.config.executor.model
|
|
159
|
-
|
|
160
|
-
# Use isolated codex config if available
|
|
161
|
-
config_path = self.working_dir / self.config.state_dir / "codex.toml"
|
|
162
|
-
if not config_path.exists():
|
|
163
|
-
config_path = None # Fallback to adapter defaults
|
|
164
|
-
|
|
165
|
-
self._adapters[name] = get_adapter(
|
|
166
|
-
name, model=model, config_path=config_path
|
|
167
|
-
)
|
|
168
|
-
return self._adapters[name]
|
|
169
|
-
|
|
170
|
-
def get_executor_usage(self) -> dict[str, int]:
|
|
171
|
-
"""Get aggregated token usage across all executors."""
|
|
172
|
-
total = {
|
|
173
|
-
"input_tokens": 0,
|
|
174
|
-
"output_tokens": 0,
|
|
175
|
-
"total_tokens": 0,
|
|
176
|
-
}
|
|
177
|
-
for adapter in self._adapters.values():
|
|
178
|
-
if hasattr(adapter, "total_usage"):
|
|
179
|
-
usage = adapter.total_usage
|
|
180
|
-
for key in total:
|
|
181
|
-
total[key] += usage.get(key, 0)
|
|
182
|
-
return total
|
|
183
|
-
|
|
184
|
-
@property
|
|
185
|
-
def executor_usage(self) -> dict[str, int]:
|
|
186
|
-
"""Aggregated executor token usage (for Weave tracking)."""
|
|
187
|
-
return self.get_executor_usage()
|
|
188
|
-
|
|
189
152
|
def save_state(self) -> None:
|
|
190
153
|
"""Save orchestrator state for resume."""
|
|
191
154
|
self._state.save_orchestrator_messages(self.messages)
|
|
@@ -599,8 +562,7 @@ Review what was accomplished in the previous session and delegate new tasks as n
|
|
|
599
562
|
|
|
600
563
|
async def cleanup(self) -> None:
|
|
601
564
|
"""Clean up resources."""
|
|
602
|
-
|
|
603
|
-
await adapter.cleanup()
|
|
565
|
+
pass # Session cleanup handled by CodexSessionManager
|
|
604
566
|
|
|
605
567
|
|
|
606
568
|
def build_orchestrator(
|
|
@@ -631,15 +593,17 @@ def build_orchestrator(
|
|
|
631
593
|
"""
|
|
632
594
|
from uuid import uuid4
|
|
633
595
|
|
|
634
|
-
#
|
|
596
|
+
# Resolve working directory first (needed for config loading)
|
|
597
|
+
working_dir = working_dir or Path.cwd()
|
|
598
|
+
|
|
599
|
+
# Load configuration from working_dir (not cwd!)
|
|
600
|
+
# This ensures config.toml and .env are loaded from the project being worked on
|
|
635
601
|
config = load_config(
|
|
636
602
|
config_path=config_path,
|
|
637
603
|
overrides=overrides,
|
|
604
|
+
working_dir=working_dir,
|
|
638
605
|
)
|
|
639
606
|
|
|
640
|
-
# Resolve working directory
|
|
641
|
-
working_dir = working_dir or Path.cwd()
|
|
642
|
-
|
|
643
607
|
# Generate instance ID if not provided (enables isolation by default for new runs)
|
|
644
608
|
# For resume, instance_id should be provided explicitly
|
|
645
609
|
if instance_id is None and not resume:
|