@team-agent/installer 0.2.11 → 0.3.0
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.
- package/Cargo.lock +744 -0
- package/Cargo.toml +34 -0
- package/crates/team-agent/Cargo.toml +33 -0
- package/crates/team-agent/src/cli/adapters.rs +1343 -0
- package/crates/team-agent/src/cli/diagnose.rs +554 -0
- package/crates/team-agent/src/cli/emit.rs +1077 -0
- package/crates/team-agent/src/cli/helpers.rs +88 -0
- package/crates/team-agent/src/cli/leader.rs +216 -0
- package/crates/team-agent/src/cli/mod.rs +1141 -0
- package/crates/team-agent/src/cli/profile.rs +306 -0
- package/crates/team-agent/src/cli/send.rs +215 -0
- package/crates/team-agent/src/cli/status.rs +179 -0
- package/crates/team-agent/src/cli/status_port.rs +502 -0
- package/crates/team-agent/src/cli/tests/base.rs +616 -0
- package/crates/team-agent/src/cli/tests/compile.rs +96 -0
- package/crates/team-agent/src/cli/tests/divergence.rs +509 -0
- package/crates/team-agent/src/cli/tests/lane_c.rs +333 -0
- package/crates/team-agent/src/cli/tests/leader_watch.rs +395 -0
- package/crates/team-agent/src/cli/tests/main_preserved.rs +675 -0
- package/crates/team-agent/src/cli/tests/missing_subcommands.rs +390 -0
- package/crates/team-agent/src/cli/tests/mod.rs +97 -0
- package/crates/team-agent/src/cli/tests/peer_allow.rs +137 -0
- package/crates/team-agent/src/cli/tests/repair_state_byte_lock.rs +302 -0
- package/crates/team-agent/src/cli/tests/run_delegation.rs +305 -0
- package/crates/team-agent/src/cli/tests/status_send.rs +385 -0
- package/crates/team-agent/src/cli/tests/verb_profile.rs +182 -0
- package/crates/team-agent/src/cli/tests/verb_settle.rs +236 -0
- package/crates/team-agent/src/cli/tests/verb_validate.rs +184 -0
- package/crates/team-agent/src/cli/types.rs +605 -0
- package/crates/team-agent/src/compiler/tests.rs +701 -0
- package/crates/team-agent/src/compiler.rs +489 -0
- package/crates/team-agent/src/coordinator/backoff.rs +153 -0
- package/crates/team-agent/src/coordinator/health.rs +436 -0
- package/crates/team-agent/src/coordinator/mod.rs +80 -0
- package/crates/team-agent/src/coordinator/orphan.rs +179 -0
- package/crates/team-agent/src/coordinator/tests/abnormal.rs +255 -0
- package/crates/team-agent/src/coordinator/tests/basics.rs +262 -0
- package/crates/team-agent/src/coordinator/tests/daemon.rs +323 -0
- package/crates/team-agent/src/coordinator/tests/health_sync.rs +263 -0
- package/crates/team-agent/src/coordinator/tests/main_preserved.rs +136 -0
- package/crates/team-agent/src/coordinator/tests/mod.rs +310 -0
- package/crates/team-agent/src/coordinator/tests/spine.rs +261 -0
- package/crates/team-agent/src/coordinator/tests/takeover.rs +227 -0
- package/crates/team-agent/src/coordinator/tests/tick_core.rs +256 -0
- package/crates/team-agent/src/coordinator/tests/watch.rs +167 -0
- package/crates/team-agent/src/coordinator/tick.rs +2032 -0
- package/crates/team-agent/src/coordinator/types.rs +584 -0
- package/crates/team-agent/src/db/migration.rs +716 -0
- package/crates/team-agent/src/db/mod.rs +23 -0
- package/crates/team-agent/src/db/schema.rs +378 -0
- package/crates/team-agent/src/event_log.rs +375 -0
- package/crates/team-agent/src/fake_worker.rs +253 -0
- package/crates/team-agent/src/leader/helpers.rs +190 -0
- package/crates/team-agent/src/leader/inject.rs +33 -0
- package/crates/team-agent/src/leader/lease.rs +1063 -0
- package/crates/team-agent/src/leader/mod.rs +99 -0
- package/crates/team-agent/src/leader/owner_bind.rs +292 -0
- package/crates/team-agent/src/leader/rediscover/tests.rs +525 -0
- package/crates/team-agent/src/leader/rediscover.rs +1099 -0
- package/crates/team-agent/src/leader/start.rs +273 -0
- package/crates/team-agent/src/leader/takeover.rs +235 -0
- package/crates/team-agent/src/leader/tests/basics.rs +183 -0
- package/crates/team-agent/src/leader/tests/byte_findings.rs +234 -0
- package/crates/team-agent/src/leader/tests/identity.rs +206 -0
- package/crates/team-agent/src/leader/tests/idle.rs +271 -0
- package/crates/team-agent/src/leader/tests/lease_api.rs +225 -0
- package/crates/team-agent/src/leader/tests/lease_claim.rs +253 -0
- package/crates/team-agent/src/leader/tests/mod.rs +125 -0
- package/crates/team-agent/src/leader/tests/rediscover.rs +351 -0
- package/crates/team-agent/src/leader/tests/wake_start_owner.rs +204 -0
- package/crates/team-agent/src/leader/types.rs +487 -0
- package/crates/team-agent/src/lib.rs +85 -0
- package/crates/team-agent/src/lifecycle/display.rs +228 -0
- package/crates/team-agent/src/lifecycle/helpers.rs +112 -0
- package/crates/team-agent/src/lifecycle/launch/plan.rs +227 -0
- package/crates/team-agent/src/lifecycle/launch.rs +1833 -0
- package/crates/team-agent/src/lifecycle/mod.rs +62 -0
- package/crates/team-agent/src/lifecycle/restart/agent.rs +533 -0
- package/crates/team-agent/src/lifecycle/restart/common.rs +517 -0
- package/crates/team-agent/src/lifecycle/restart/orchestrator.rs +41 -0
- package/crates/team-agent/src/lifecycle/restart/rebuild.rs +268 -0
- package/crates/team-agent/src/lifecycle/restart/remove.rs +780 -0
- package/crates/team-agent/src/lifecycle/restart/selection.rs +208 -0
- package/crates/team-agent/src/lifecycle/restart/team_state.rs +242 -0
- package/crates/team-agent/src/lifecycle/restart.rs +76 -0
- package/crates/team-agent/src/lifecycle/tests/agent_ops.rs +455 -0
- package/crates/team-agent/src/lifecycle/tests/core.rs +989 -0
- package/crates/team-agent/src/lifecycle/tests/lane_ops.rs +583 -0
- package/crates/team-agent/src/lifecycle/tests/launch_spawn.rs +933 -0
- package/crates/team-agent/src/lifecycle/tests/main_preserved.rs +265 -0
- package/crates/team-agent/src/lifecycle/tests.rs +27 -0
- package/crates/team-agent/src/lifecycle/types.rs +685 -0
- package/crates/team-agent/src/main.rs +41 -0
- package/crates/team-agent/src/mcp_server/helpers.rs +228 -0
- package/crates/team-agent/src/mcp_server/mod.rs +183 -0
- package/crates/team-agent/src/mcp_server/normalize.rs +312 -0
- package/crates/team-agent/src/mcp_server/tests/golden.rs +283 -0
- package/crates/team-agent/src/mcp_server/tests/normalize.rs +244 -0
- package/crates/team-agent/src/mcp_server/tests/scoped.rs +189 -0
- package/crates/team-agent/src/mcp_server/tests/send.rs +222 -0
- package/crates/team-agent/src/mcp_server/tests/tools.rs +158 -0
- package/crates/team-agent/src/mcp_server/tests/wire.rs +159 -0
- package/crates/team-agent/src/mcp_server/tests.rs +38 -0
- package/crates/team-agent/src/mcp_server/tools.rs +603 -0
- package/crates/team-agent/src/mcp_server/types.rs +421 -0
- package/crates/team-agent/src/mcp_server/wire.rs +388 -0
- package/crates/team-agent/src/message_store.rs +767 -0
- package/crates/team-agent/src/messaging/activity.rs +433 -0
- package/crates/team-agent/src/messaging/delivery.rs +542 -0
- package/crates/team-agent/src/messaging/helpers.rs +209 -0
- package/crates/team-agent/src/messaging/leader_receiver.rs +340 -0
- package/crates/team-agent/src/messaging/mod.rs +147 -0
- package/crates/team-agent/src/messaging/peers.rs +32 -0
- package/crates/team-agent/src/messaging/results.rs +537 -0
- package/crates/team-agent/src/messaging/scheduler.rs +344 -0
- package/crates/team-agent/src/messaging/selftest.rs +100 -0
- package/crates/team-agent/src/messaging/send.rs +582 -0
- package/crates/team-agent/src/messaging/tests/basic.rs +357 -0
- package/crates/team-agent/src/messaging/tests/main_preserved.rs +122 -0
- package/crates/team-agent/src/messaging/tests/mod.rs +293 -0
- package/crates/team-agent/src/messaging/tests/runtime.rs +1422 -0
- package/crates/team-agent/src/messaging/tests/spine.rs +437 -0
- package/crates/team-agent/src/messaging/trust.rs +192 -0
- package/crates/team-agent/src/messaging/types.rs +355 -0
- package/crates/team-agent/src/messaging/watchers.rs +591 -0
- package/crates/team-agent/src/model/enums.rs +311 -0
- package/crates/team-agent/src/model/errors.rs +17 -0
- package/crates/team-agent/src/model/ids.rs +155 -0
- package/crates/team-agent/src/model/mod.rs +22 -0
- package/crates/team-agent/src/model/paths.rs +228 -0
- package/crates/team-agent/src/model/permissions.rs +567 -0
- package/crates/team-agent/src/model/routing.rs +340 -0
- package/crates/team-agent/src/model/spec.rs +680 -0
- package/crates/team-agent/src/model/task_graph.rs +380 -0
- package/crates/team-agent/src/model/testdata/fuzz.golden.yaml +43 -0
- package/crates/team-agent/src/model/testdata/fuzz.yaml +43 -0
- package/crates/team-agent/src/model/testdata/spec_invalid_a.yaml +207 -0
- package/crates/team-agent/src/model/testdata/team.spec.golden.yaml +206 -0
- package/crates/team-agent/src/model/testdata/team.spec.yaml +206 -0
- package/crates/team-agent/src/model/yaml/tests.rs +288 -0
- package/crates/team-agent/src/model/yaml.rs +800 -0
- package/crates/team-agent/src/packaging/install.rs +305 -0
- package/crates/team-agent/src/packaging/migrate.rs +30 -0
- package/crates/team-agent/src/packaging/mod.rs +82 -0
- package/crates/team-agent/src/packaging/repair.rs +24 -0
- package/crates/team-agent/src/packaging/tests.rs +829 -0
- package/crates/team-agent/src/packaging/types.rs +369 -0
- package/crates/team-agent/src/provider/adapter.rs +801 -0
- package/crates/team-agent/src/provider/approvals/mod.rs +2 -0
- package/crates/team-agent/src/provider/approvals/parsing.rs +452 -0
- package/crates/team-agent/src/provider/approvals/runtime_prompts.rs +163 -0
- package/crates/team-agent/src/provider/classify.rs +456 -0
- package/crates/team-agent/src/provider/faults.rs +136 -0
- package/crates/team-agent/src/provider/helpers.rs +41 -0
- package/crates/team-agent/src/provider/mod.rs +53 -0
- package/crates/team-agent/src/provider/startup_prompt.rs +423 -0
- package/crates/team-agent/src/provider/tests/adapter.rs +239 -0
- package/crates/team-agent/src/provider/tests/classify.rs +240 -0
- package/crates/team-agent/src/provider/tests/faults.rs +120 -0
- package/crates/team-agent/src/provider/tests/idle.rs +208 -0
- package/crates/team-agent/src/provider/tests/wire.rs +213 -0
- package/crates/team-agent/src/provider/tests.rs +31 -0
- package/crates/team-agent/src/provider/types.rs +424 -0
- package/crates/team-agent/src/state/identity.rs +656 -0
- package/crates/team-agent/src/state/mod.rs +58 -0
- package/crates/team-agent/src/state/owner_gate.rs +423 -0
- package/crates/team-agent/src/state/persist.rs +712 -0
- package/crates/team-agent/src/state/projection.rs +657 -0
- package/crates/team-agent/src/state/selector.rs +105 -0
- package/crates/team-agent/src/state/testdata/state-rich.canonical.json +133 -0
- package/crates/team-agent/src/tmux_backend/tests.rs +586 -0
- package/crates/team-agent/src/tmux_backend.rs +758 -0
- package/crates/team-agent/src/transport/test_support.rs +252 -0
- package/crates/team-agent/src/transport/tests/behavior.rs +327 -0
- package/crates/team-agent/src/transport/tests/mod.rs +199 -0
- package/crates/team-agent/src/transport/tests/wire.rs +527 -0
- package/crates/team-agent/src/transport.rs +774 -0
- package/npm/install.mjs +90 -106
- package/package.json +15 -13
- package/crates/team-agent-core/Cargo.toml +0 -12
- package/crates/team-agent-core/src/lib.rs +0 -332
- package/crates/team-agent-core/src/main.rs +0 -152
- package/pyproject.toml +0 -18
- package/scripts/install.py +0 -88
- package/scripts/run_regression_tests.py +0 -83
- package/src/team_agent/__init__.py +0 -3
- package/src/team_agent/__main__.py +0 -5
- package/src/team_agent/_legacy_pane_discovery.py +0 -186
- package/src/team_agent/abnormal_track.py +0 -253
- package/src/team_agent/approvals/__init__.py +0 -65
- package/src/team_agent/approvals/constants.py +0 -6
- package/src/team_agent/approvals/parsing.py +0 -176
- package/src/team_agent/approvals/runtime_prompts.py +0 -171
- package/src/team_agent/approvals/status.py +0 -176
- package/src/team_agent/cli/__init__.py +0 -137
- package/src/team_agent/cli/commands.py +0 -481
- package/src/team_agent/cli/e2e.py +0 -202
- package/src/team_agent/cli/helpers.py +0 -226
- package/src/team_agent/cli/parser.py +0 -540
- package/src/team_agent/compiler.py +0 -334
- package/src/team_agent/coordinator/__init__.py +0 -53
- package/src/team_agent/coordinator/__main__.py +0 -119
- package/src/team_agent/coordinator/lifecycle.py +0 -411
- package/src/team_agent/coordinator/metadata.py +0 -61
- package/src/team_agent/coordinator/paths.py +0 -17
- package/src/team_agent/diagnose/__init__.py +0 -48
- package/src/team_agent/diagnose/checks.py +0 -101
- package/src/team_agent/diagnose/comms.py +0 -213
- package/src/team_agent/diagnose/health.py +0 -241
- package/src/team_agent/diagnose/orphan_cleanup.py +0 -364
- package/src/team_agent/diagnose/preflight.py +0 -194
- package/src/team_agent/diagnose/quick_start.py +0 -324
- package/src/team_agent/display/__init__.py +0 -92
- package/src/team_agent/display/adaptive.py +0 -511
- package/src/team_agent/display/backend.py +0 -46
- package/src/team_agent/display/close.py +0 -154
- package/src/team_agent/display/ghostty.py +0 -77
- package/src/team_agent/display/rebuild.py +0 -102
- package/src/team_agent/display/tiling.py +0 -156
- package/src/team_agent/display/worker_window.py +0 -114
- package/src/team_agent/display/workspace.py +0 -382
- package/src/team_agent/errors.py +0 -10
- package/src/team_agent/events.py +0 -84
- package/src/team_agent/fake_worker.py +0 -80
- package/src/team_agent/idle_predicate.py +0 -218
- package/src/team_agent/idle_takeover.py +0 -59
- package/src/team_agent/idle_takeover_wiring.py +0 -114
- package/src/team_agent/launch/__init__.py +0 -41
- package/src/team_agent/launch/bootstrap.py +0 -85
- package/src/team_agent/launch/config.py +0 -106
- package/src/team_agent/launch/core.py +0 -301
- package/src/team_agent/launch/requirements.py +0 -57
- package/src/team_agent/leader/__init__.py +0 -926
- package/src/team_agent/leader_binding.py +0 -183
- package/src/team_agent/lifecycle/__init__.py +0 -5
- package/src/team_agent/lifecycle/agents.py +0 -278
- package/src/team_agent/lifecycle/operations.py +0 -411
- package/src/team_agent/lifecycle/paste_buffer_hygiene.py +0 -39
- package/src/team_agent/lifecycle/start.py +0 -363
- package/src/team_agent/mcp_server/__init__.py +0 -42
- package/src/team_agent/mcp_server/__main__.py +0 -7
- package/src/team_agent/mcp_server/contracts.py +0 -148
- package/src/team_agent/mcp_server/normalize.py +0 -257
- package/src/team_agent/mcp_server/server.py +0 -150
- package/src/team_agent/mcp_server/tools.py +0 -352
- package/src/team_agent/message_store/__init__.py +0 -23
- package/src/team_agent/message_store/agent_health.py +0 -113
- package/src/team_agent/message_store/core.py +0 -497
- package/src/team_agent/message_store/leader_notification_log.py +0 -198
- package/src/team_agent/message_store/result_watchers.py +0 -251
- package/src/team_agent/message_store/schema.py +0 -308
- package/src/team_agent/message_store/schema_migration.py +0 -448
- package/src/team_agent/messaging/__init__.py +0 -1
- package/src/team_agent/messaging/activity_detector.py +0 -262
- package/src/team_agent/messaging/delivery.py +0 -504
- package/src/team_agent/messaging/deps.py +0 -247
- package/src/team_agent/messaging/idle_alerts.py +0 -423
- package/src/team_agent/messaging/internal_delivery.py +0 -46
- package/src/team_agent/messaging/leader.py +0 -497
- package/src/team_agent/messaging/leader_api_errors.py +0 -216
- package/src/team_agent/messaging/leader_panes.py +0 -673
- package/src/team_agent/messaging/owner_bypass.py +0 -29
- package/src/team_agent/messaging/result_delivery.py +0 -539
- package/src/team_agent/messaging/results.py +0 -447
- package/src/team_agent/messaging/scheduler.py +0 -450
- package/src/team_agent/messaging/send.py +0 -532
- package/src/team_agent/messaging/session_drift.py +0 -94
- package/src/team_agent/messaging/tmux_io.py +0 -506
- package/src/team_agent/messaging/tmux_prompt.py +0 -338
- package/src/team_agent/messaging/trust_auto_answer.py +0 -52
- package/src/team_agent/orchestrator/__init__.py +0 -376
- package/src/team_agent/orchestrator/plan.py +0 -122
- package/src/team_agent/orchestrator/state.py +0 -128
- package/src/team_agent/paths.py +0 -45
- package/src/team_agent/permissions.py +0 -123
- package/src/team_agent/profiles/__init__.py +0 -82
- package/src/team_agent/profiles/constants.py +0 -19
- package/src/team_agent/profiles/core.py +0 -407
- package/src/team_agent/profiles/helpers.py +0 -69
- package/src/team_agent/profiles/provider_env.py +0 -188
- package/src/team_agent/profiles/smoke.py +0 -201
- package/src/team_agent/provider_cli/__init__.py +0 -43
- package/src/team_agent/provider_cli/adapter.py +0 -172
- package/src/team_agent/provider_cli/base.py +0 -48
- package/src/team_agent/provider_cli/claude.py +0 -503
- package/src/team_agent/provider_cli/codex.py +0 -336
- package/src/team_agent/provider_cli/copilot.py +0 -8
- package/src/team_agent/provider_cli/fake.py +0 -39
- package/src/team_agent/provider_cli/gemini.py +0 -95
- package/src/team_agent/provider_cli/opencode.py +0 -8
- package/src/team_agent/provider_cli/prompt.py +0 -62
- package/src/team_agent/provider_cli/registry.py +0 -18
- package/src/team_agent/provider_cli/unsupported.py +0 -32
- package/src/team_agent/provider_state/README.md +0 -78
- package/src/team_agent/provider_state/__init__.py +0 -91
- package/src/team_agent/provider_state/claude.py +0 -86
- package/src/team_agent/provider_state/codex.py +0 -84
- package/src/team_agent/provider_state/common.py +0 -207
- package/src/team_agent/provider_state/registry.py +0 -118
- package/src/team_agent/providers.py +0 -163
- package/src/team_agent/quality_gates.py +0 -104
- package/src/team_agent/restart/__init__.py +0 -34
- package/src/team_agent/restart/orchestration.py +0 -554
- package/src/team_agent/restart/selection.py +0 -89
- package/src/team_agent/restart/snapshot.py +0 -70
- package/src/team_agent/routing.py +0 -84
- package/src/team_agent/runtime.py +0 -1243
- package/src/team_agent/rust_core.py +0 -327
- package/src/team_agent/sessions/__init__.py +0 -25
- package/src/team_agent/sessions/capture.py +0 -144
- package/src/team_agent/sessions/inventory.py +0 -44
- package/src/team_agent/sessions/resume.py +0 -135
- package/src/team_agent/simple_yaml.py +0 -236
- package/src/team_agent/spec.py +0 -370
- package/src/team_agent/state.py +0 -693
- package/src/team_agent/status/__init__.py +0 -63
- package/src/team_agent/status/approvals.py +0 -52
- package/src/team_agent/status/compact.py +0 -158
- package/src/team_agent/status/constants.py +0 -18
- package/src/team_agent/status/inbox.py +0 -58
- package/src/team_agent/status/peek.py +0 -117
- package/src/team_agent/status/queries.py +0 -199
- package/src/team_agent/task_graph.py +0 -80
- package/src/team_agent/terminal.py +0 -57
- package/src/team_agent/wake.py +0 -58
- package/src/team_agent/watch/__init__.py +0 -145
|
@@ -1,364 +0,0 @@
|
|
|
1
|
-
"""Stage 14 (Gap 37a) — `team-agent doctor --cleanup-orphans` implementation.
|
|
2
|
-
|
|
3
|
-
Scans `ps` for processes matching `team_agent.coordinator --workspace <path>` and
|
|
4
|
-
classifies any whose workspace path no longer exists (or matches the test-tempdir
|
|
5
|
-
pattern) as an orphan. Dry-run by default; --confirm sends SIGTERM.
|
|
6
|
-
|
|
7
|
-
Mac mini 2026-05-26 evidence: 35 orphan coordinator processes alive simultaneously
|
|
8
|
-
pointing at /var/folders/.../T/team-agent-watcher-dedupe-* paths that had been removed
|
|
9
|
-
hours earlier. Each holds a long-lived Python interpreter + SQLite connection.
|
|
10
|
-
"""
|
|
11
|
-
from __future__ import annotations
|
|
12
|
-
|
|
13
|
-
import os
|
|
14
|
-
import re
|
|
15
|
-
import signal
|
|
16
|
-
import subprocess
|
|
17
|
-
import time
|
|
18
|
-
from datetime import datetime, timezone
|
|
19
|
-
from typing import Any
|
|
20
|
-
|
|
21
|
-
# Pattern: argv contains "team_agent.coordinator --workspace <path>" anywhere.
|
|
22
|
-
_COORDINATOR_ARGV_RE = re.compile(
|
|
23
|
-
r"team_agent\.coordinator(?:\.__main__)?(?:\s+|.*?)\s--workspace\s+(\S+)"
|
|
24
|
-
)
|
|
25
|
-
# Test-tempdir patterns that indicate the workspace is ephemeral and almost certainly orphan.
|
|
26
|
-
_EPHEMERAL_PATH_HINTS = (
|
|
27
|
-
"team-agent-watcher-dedupe-",
|
|
28
|
-
"team-agent-gap",
|
|
29
|
-
"team-agent-stage",
|
|
30
|
-
"team-agent-orchestrator-",
|
|
31
|
-
"team-agent-rm-",
|
|
32
|
-
"team-agent-claim-",
|
|
33
|
-
"team-agent-hotfix",
|
|
34
|
-
"team-agent-multi",
|
|
35
|
-
"team-agent-progress-",
|
|
36
|
-
"team-agent-fanout-",
|
|
37
|
-
"team-agent-in-flight-",
|
|
38
|
-
"team-agent-test-",
|
|
39
|
-
)
|
|
40
|
-
_SIGTERM_WAIT_SECONDS = 3.0
|
|
41
|
-
_SIGKILL_WAIT_SECONDS = 2.0
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
def find_coordinator_processes(*, runner=subprocess.run) -> list[dict[str, Any]]:
|
|
45
|
-
"""Return list of {pid, etime, cmdline, workspace} dicts for every running
|
|
46
|
-
team_agent.coordinator process visible to ps. workspace is None when the cmdline
|
|
47
|
-
doesn't parse — those are noted but not auto-classified as orphan."""
|
|
48
|
-
try:
|
|
49
|
-
proc = runner(
|
|
50
|
-
["ps", "-Awwo", "pid=,etime=,command="],
|
|
51
|
-
text=True,
|
|
52
|
-
capture_output=True,
|
|
53
|
-
timeout=5,
|
|
54
|
-
check=False,
|
|
55
|
-
)
|
|
56
|
-
except (subprocess.TimeoutExpired, FileNotFoundError, OSError):
|
|
57
|
-
return []
|
|
58
|
-
if proc.returncode != 0 or not proc.stdout:
|
|
59
|
-
return []
|
|
60
|
-
rows: list[dict[str, Any]] = []
|
|
61
|
-
for line in proc.stdout.splitlines():
|
|
62
|
-
parts = line.strip().split(None, 2)
|
|
63
|
-
if len(parts) < 3:
|
|
64
|
-
continue
|
|
65
|
-
pid_s, etime, cmdline = parts[0], parts[1], parts[2]
|
|
66
|
-
if "team_agent.coordinator" not in cmdline:
|
|
67
|
-
continue
|
|
68
|
-
if "ps -Awwo" in cmdline:
|
|
69
|
-
continue
|
|
70
|
-
try:
|
|
71
|
-
pid = int(pid_s)
|
|
72
|
-
except ValueError:
|
|
73
|
-
continue
|
|
74
|
-
if pid == os.getpid():
|
|
75
|
-
continue
|
|
76
|
-
match = _COORDINATOR_ARGV_RE.search(cmdline)
|
|
77
|
-
workspace = match.group(1) if match else None
|
|
78
|
-
rows.append({
|
|
79
|
-
"pid": pid,
|
|
80
|
-
"etime": etime,
|
|
81
|
-
"cmdline": cmdline,
|
|
82
|
-
"workspace": workspace,
|
|
83
|
-
})
|
|
84
|
-
return rows
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
def classify_orphan(entry: dict[str, Any]) -> tuple[bool, str]:
|
|
88
|
-
"""Return (is_orphan, reason). An entry is orphan when its workspace path no longer
|
|
89
|
-
exists on disk OR matches a known ephemeral-tempdir pattern (test workspaces should
|
|
90
|
-
NEVER spawn long-lived coordinators)."""
|
|
91
|
-
workspace = entry.get("workspace")
|
|
92
|
-
if not workspace:
|
|
93
|
-
return False, "cmdline_unparsed"
|
|
94
|
-
if not os.path.exists(workspace):
|
|
95
|
-
return True, "workspace_path_missing"
|
|
96
|
-
for hint in _EPHEMERAL_PATH_HINTS:
|
|
97
|
-
if hint in workspace:
|
|
98
|
-
return True, f"ephemeral_tempdir_pattern:{hint}"
|
|
99
|
-
return False, "workspace_alive"
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
def cleanup_orphan_coordinators(
|
|
103
|
-
*,
|
|
104
|
-
confirm: bool = False,
|
|
105
|
-
runner=subprocess.run,
|
|
106
|
-
killer=os.kill,
|
|
107
|
-
pg_killer=None,
|
|
108
|
-
pgid_getter=None,
|
|
109
|
-
sleeper=time.sleep,
|
|
110
|
-
sigterm_wait_seconds: float = _SIGTERM_WAIT_SECONDS,
|
|
111
|
-
sigkill_wait_seconds: float = _SIGKILL_WAIT_SECONDS,
|
|
112
|
-
) -> dict[str, Any]:
|
|
113
|
-
"""Scan for orphan coordinators. Without confirm: dry-run (just classify and report).
|
|
114
|
-
With confirm: SIGTERM each orphan, wait up to _SIGTERM_WAIT_SECONDS for graceful
|
|
115
|
-
exit; if still alive, escalate to SIGKILL and wait _SIGKILL_WAIT_SECONDS. Only
|
|
116
|
-
report status='failed' (with error='alive_after_sigkill') when the process
|
|
117
|
-
survives BOTH signals — that's extremely rare and almost always indicates a
|
|
118
|
-
zombie/uninterruptible-sleep kernel state.
|
|
119
|
-
|
|
120
|
-
Mac mini 2026-05-26 evidence: real orphan coordinators have been observed alive
|
|
121
|
-
40+ hours; many of them never exit on SIGTERM (signal handler suppressed during
|
|
122
|
-
long sqlite reads, or the python interpreter is hosting an async loop that
|
|
123
|
-
swallows the term signal). SIGKILL escalation is required for production.
|
|
124
|
-
|
|
125
|
-
pg_killer / pgid_getter default to os.killpg / os.getpgid; mock them in tests.
|
|
126
|
-
If pgid_getter succeeds AND returns a pgid > 1 AND the pgid != pid (i.e. the
|
|
127
|
-
process leads its own process group with children), we signal the WHOLE group;
|
|
128
|
-
otherwise we signal the pid directly. This catches orphan coordinators that
|
|
129
|
-
spawned subprocess.Popen children which would otherwise survive a pid-only
|
|
130
|
-
SIGTERM."""
|
|
131
|
-
now = datetime.now(timezone.utc).isoformat()
|
|
132
|
-
if pg_killer is None:
|
|
133
|
-
pg_killer = getattr(os, "killpg", None)
|
|
134
|
-
if pgid_getter is None:
|
|
135
|
-
pgid_getter = getattr(os, "getpgid", None)
|
|
136
|
-
entries = find_coordinator_processes(runner=runner)
|
|
137
|
-
classified: list[dict[str, Any]] = []
|
|
138
|
-
orphans: list[dict[str, Any]] = []
|
|
139
|
-
for entry in entries:
|
|
140
|
-
is_orphan, reason = classify_orphan(entry)
|
|
141
|
-
annotated = {**entry, "is_orphan": is_orphan, "reason": reason}
|
|
142
|
-
classified.append(annotated)
|
|
143
|
-
if is_orphan:
|
|
144
|
-
orphans.append(annotated)
|
|
145
|
-
if not confirm:
|
|
146
|
-
return {
|
|
147
|
-
"ok": True,
|
|
148
|
-
"scanned": len(classified),
|
|
149
|
-
"orphans": orphans,
|
|
150
|
-
"dry_run": True,
|
|
151
|
-
"scanned_at": now,
|
|
152
|
-
"action_required": "re-run with --confirm to send SIGTERM",
|
|
153
|
-
}
|
|
154
|
-
killed: list[dict[str, Any]] = []
|
|
155
|
-
failed: list[dict[str, Any]] = []
|
|
156
|
-
for entry in orphans:
|
|
157
|
-
outcome = _terminate_orphan(
|
|
158
|
-
entry["pid"], killer=killer, pg_killer=pg_killer,
|
|
159
|
-
pgid_getter=pgid_getter, sleeper=sleeper,
|
|
160
|
-
sigterm_wait_seconds=sigterm_wait_seconds,
|
|
161
|
-
sigkill_wait_seconds=sigkill_wait_seconds,
|
|
162
|
-
)
|
|
163
|
-
annotated = {**entry, **outcome}
|
|
164
|
-
if outcome.get("status") == "killed":
|
|
165
|
-
killed.append(annotated)
|
|
166
|
-
elif outcome.get("status") == "missing":
|
|
167
|
-
killed.append(annotated)
|
|
168
|
-
else:
|
|
169
|
-
failed.append(annotated)
|
|
170
|
-
return {
|
|
171
|
-
"ok": True,
|
|
172
|
-
"scanned": len(classified),
|
|
173
|
-
"orphans": orphans,
|
|
174
|
-
"killed": killed,
|
|
175
|
-
"failed": failed,
|
|
176
|
-
"dry_run": False,
|
|
177
|
-
"scanned_at": now,
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
def _terminate_orphan(
|
|
182
|
-
pid: int,
|
|
183
|
-
*,
|
|
184
|
-
killer,
|
|
185
|
-
pg_killer,
|
|
186
|
-
pgid_getter,
|
|
187
|
-
sleeper,
|
|
188
|
-
sigterm_wait_seconds: float = _SIGTERM_WAIT_SECONDS,
|
|
189
|
-
sigkill_wait_seconds: float = _SIGKILL_WAIT_SECONDS,
|
|
190
|
-
) -> dict[str, Any]:
|
|
191
|
-
"""SIGTERM → wait 3s → SIGKILL → wait 2s escalation. Returns one of:
|
|
192
|
-
{status: 'killed', sigkill_required: False, signaled: 'pid'|'pgid'}
|
|
193
|
-
{status: 'killed', sigkill_required: True, signaled: 'pid'|'pgid'}
|
|
194
|
-
{status: 'missing', error: '<exc>'} — process gone before SIGTERM
|
|
195
|
-
{status: 'failed', error: 'alive_after_sigkill'} — process survived both
|
|
196
|
-
{status: 'failed', error: '<exc>'} — permission denied / OS error
|
|
197
|
-
"""
|
|
198
|
-
pgid, pgid_error = _safe_getpgid(pid, pgid_getter)
|
|
199
|
-
use_group = bool(pg_killer and pgid is not None and pgid > 1 and pgid != pid)
|
|
200
|
-
signaled = "pgid" if use_group else "pid"
|
|
201
|
-
|
|
202
|
-
def send(sig: int) -> tuple[bool, str | None]:
|
|
203
|
-
try:
|
|
204
|
-
if use_group:
|
|
205
|
-
pg_killer(pgid, sig)
|
|
206
|
-
else:
|
|
207
|
-
killer(pid, sig)
|
|
208
|
-
except ProcessLookupError:
|
|
209
|
-
return False, "process_lookup_error"
|
|
210
|
-
except (PermissionError, OSError) as exc:
|
|
211
|
-
return False, str(exc)
|
|
212
|
-
return True, None
|
|
213
|
-
|
|
214
|
-
ok, err = send(signal.SIGTERM)
|
|
215
|
-
if not ok:
|
|
216
|
-
if err == "process_lookup_error":
|
|
217
|
-
return {"status": "missing", "signaled": signaled, "pgid": pgid}
|
|
218
|
-
return {"status": "failed", "error": err, "signaled": signaled, "pgid": pgid}
|
|
219
|
-
if _wait_for_exit(pid, sigterm_wait_seconds, killer=killer, sleeper=sleeper):
|
|
220
|
-
return {
|
|
221
|
-
"status": "killed",
|
|
222
|
-
"sigkill_required": False,
|
|
223
|
-
"signaled": signaled,
|
|
224
|
-
"pgid": pgid,
|
|
225
|
-
"pgid_error": pgid_error,
|
|
226
|
-
}
|
|
227
|
-
# SIGTERM did not work — escalate.
|
|
228
|
-
ok, err = send(signal.SIGKILL)
|
|
229
|
-
if not ok:
|
|
230
|
-
if err == "process_lookup_error":
|
|
231
|
-
# Race: died between checks.
|
|
232
|
-
return {
|
|
233
|
-
"status": "killed",
|
|
234
|
-
"sigkill_required": False,
|
|
235
|
-
"signaled": signaled,
|
|
236
|
-
"pgid": pgid,
|
|
237
|
-
"pgid_error": pgid_error,
|
|
238
|
-
}
|
|
239
|
-
return {
|
|
240
|
-
"status": "failed",
|
|
241
|
-
"error": err,
|
|
242
|
-
"signaled": signaled,
|
|
243
|
-
"pgid": pgid,
|
|
244
|
-
"sigkill_attempted": True,
|
|
245
|
-
}
|
|
246
|
-
if _wait_for_exit(pid, sigkill_wait_seconds, killer=killer, sleeper=sleeper):
|
|
247
|
-
return {
|
|
248
|
-
"status": "killed",
|
|
249
|
-
"sigkill_required": True,
|
|
250
|
-
"signaled": signaled,
|
|
251
|
-
"pgid": pgid,
|
|
252
|
-
"pgid_error": pgid_error,
|
|
253
|
-
}
|
|
254
|
-
return {
|
|
255
|
-
"status": "failed",
|
|
256
|
-
"error": "alive_after_sigkill",
|
|
257
|
-
"signaled": signaled,
|
|
258
|
-
"pgid": pgid,
|
|
259
|
-
"sigkill_required": True,
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
def _safe_getpgid(pid: int, pgid_getter) -> tuple[int | None, str | None]:
|
|
264
|
-
if pgid_getter is None:
|
|
265
|
-
return None, "getpgid_unavailable"
|
|
266
|
-
try:
|
|
267
|
-
return pgid_getter(pid), None
|
|
268
|
-
except (ProcessLookupError, PermissionError, OSError) as exc:
|
|
269
|
-
return None, str(exc)
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
def _wait_for_exit(pid: int, timeout: float, *, killer, sleeper) -> bool:
|
|
273
|
-
deadline = time.monotonic() + max(timeout, 0.0)
|
|
274
|
-
while time.monotonic() < deadline:
|
|
275
|
-
try:
|
|
276
|
-
killer(pid, 0)
|
|
277
|
-
except ProcessLookupError:
|
|
278
|
-
return True
|
|
279
|
-
except (PermissionError, OSError):
|
|
280
|
-
return True
|
|
281
|
-
sleeper(0.1)
|
|
282
|
-
# Final check after the deadline elapses.
|
|
283
|
-
try:
|
|
284
|
-
killer(pid, 0)
|
|
285
|
-
except ProcessLookupError:
|
|
286
|
-
return True
|
|
287
|
-
except (PermissionError, OSError):
|
|
288
|
-
return True
|
|
289
|
-
return False
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
def orphan_gate(
|
|
293
|
-
*,
|
|
294
|
-
fix: bool = False,
|
|
295
|
-
confirm: bool = False,
|
|
296
|
-
runner=subprocess.run,
|
|
297
|
-
killer=os.kill,
|
|
298
|
-
pg_killer=None,
|
|
299
|
-
pgid_getter=None,
|
|
300
|
-
sleeper=time.sleep,
|
|
301
|
-
sigterm_wait_seconds: float = _SIGTERM_WAIT_SECONDS,
|
|
302
|
-
sigkill_wait_seconds: float = _SIGKILL_WAIT_SECONDS,
|
|
303
|
-
) -> dict[str, Any]:
|
|
304
|
-
if fix and not confirm:
|
|
305
|
-
return {
|
|
306
|
-
"ok": False,
|
|
307
|
-
"gate": "orphans",
|
|
308
|
-
"status": "refused",
|
|
309
|
-
"reason": "fix_requires_confirm",
|
|
310
|
-
"action": "re-run with --gate orphans --fix --confirm",
|
|
311
|
-
}
|
|
312
|
-
result = cleanup_orphan_coordinators(
|
|
313
|
-
confirm=fix and confirm,
|
|
314
|
-
runner=runner,
|
|
315
|
-
killer=killer,
|
|
316
|
-
pg_killer=pg_killer,
|
|
317
|
-
pgid_getter=pgid_getter,
|
|
318
|
-
sleeper=sleeper,
|
|
319
|
-
sigterm_wait_seconds=sigterm_wait_seconds,
|
|
320
|
-
sigkill_wait_seconds=sigkill_wait_seconds,
|
|
321
|
-
)
|
|
322
|
-
orphans = result.get("orphans") or []
|
|
323
|
-
failed = result.get("failed") or []
|
|
324
|
-
passed = not orphans if not fix else not failed
|
|
325
|
-
envelope = {
|
|
326
|
-
**result,
|
|
327
|
-
"ok": passed,
|
|
328
|
-
"gate": "orphans",
|
|
329
|
-
"status": "passed" if passed else "failed",
|
|
330
|
-
"fix": bool(fix),
|
|
331
|
-
}
|
|
332
|
-
if not fix and orphans:
|
|
333
|
-
envelope["action_required"] = "re-run with --gate orphans --fix --confirm"
|
|
334
|
-
return envelope
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
def format_cleanup_orphans(result: dict[str, Any]) -> str:
|
|
338
|
-
lines = [
|
|
339
|
-
f"Coordinator orphan scan @ {result.get('scanned_at')}",
|
|
340
|
-
f" scanned: {result.get('scanned', 0)} coordinator processes",
|
|
341
|
-
f" orphans: {len(result.get('orphans') or [])}",
|
|
342
|
-
]
|
|
343
|
-
if result.get("dry_run"):
|
|
344
|
-
lines.append(" mode: DRY-RUN (no SIGTERM sent; re-run with --confirm)")
|
|
345
|
-
else:
|
|
346
|
-
killed_entries = result.get("killed") or []
|
|
347
|
-
escalated = sum(1 for k in killed_entries if k.get("sigkill_required"))
|
|
348
|
-
lines.append(f" killed: {len(killed_entries)} (sigkill_required: {escalated})")
|
|
349
|
-
lines.append(f" failed: {len(result.get('failed') or [])}")
|
|
350
|
-
for orphan in result.get("orphans") or []:
|
|
351
|
-
lines.append(
|
|
352
|
-
f" PID {orphan['pid']} etime={orphan['etime']} "
|
|
353
|
-
f"workspace={orphan.get('workspace') or '?'} reason={orphan.get('reason')}"
|
|
354
|
-
)
|
|
355
|
-
return "\n".join(lines)
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
__all__ = [
|
|
359
|
-
"cleanup_orphan_coordinators",
|
|
360
|
-
"classify_orphan",
|
|
361
|
-
"find_coordinator_processes",
|
|
362
|
-
"format_cleanup_orphans",
|
|
363
|
-
"orphan_gate",
|
|
364
|
-
]
|
|
@@ -1,194 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
import json
|
|
4
|
-
import time
|
|
5
|
-
from pathlib import Path
|
|
6
|
-
from typing import Any
|
|
7
|
-
|
|
8
|
-
from team_agent.diagnose.checks import (
|
|
9
|
-
compact_model_checks,
|
|
10
|
-
model_checks_for_agents,
|
|
11
|
-
profile_checks_for_agents,
|
|
12
|
-
profile_smoke_checks_for_agents,
|
|
13
|
-
)
|
|
14
|
-
from team_agent.events import EventLog
|
|
15
|
-
from team_agent.paths import logs_dir, team_workspace
|
|
16
|
-
from team_agent.profiles import compact_profile_check
|
|
17
|
-
from team_agent.rust_core import core_binary
|
|
18
|
-
from team_agent.simple_yaml import dumps
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
def preflight(team_dir: Path) -> dict[str, Any]:
|
|
22
|
-
from team_agent.compiler import compile_team
|
|
23
|
-
from team_agent.profiles import profile_dir
|
|
24
|
-
from team_agent.runtime import (
|
|
25
|
-
GHOSTTY_DISPLAY_BACKENDS,
|
|
26
|
-
_attach_team_profile_dirs,
|
|
27
|
-
_ghostty_command,
|
|
28
|
-
ensure_workspace_dirs,
|
|
29
|
-
shutil_which,
|
|
30
|
-
)
|
|
31
|
-
|
|
32
|
-
team_dir = team_dir.resolve()
|
|
33
|
-
workspace = team_workspace(team_dir)
|
|
34
|
-
ensure_workspace_dirs(workspace)
|
|
35
|
-
ensure_profiles_for_roles(team_dir)
|
|
36
|
-
event_log = EventLog(workspace)
|
|
37
|
-
checks: list[dict[str, Any]] = []
|
|
38
|
-
ok = True
|
|
39
|
-
spec = None
|
|
40
|
-
try:
|
|
41
|
-
compiled = compile_team(team_dir)
|
|
42
|
-
spec = compiled["spec"]
|
|
43
|
-
_attach_team_profile_dirs(spec, team_dir / "team.spec.yaml", workspace, team_dir)
|
|
44
|
-
checks.append({"name": "compile", "ok": True, "agents": [a["id"] for a in spec.get("agents", [])]})
|
|
45
|
-
except Exception as exc:
|
|
46
|
-
ok = False
|
|
47
|
-
checks.append({"name": "compile", "ok": False, "error": str(exc)})
|
|
48
|
-
tmux_path = shutil_which("tmux")
|
|
49
|
-
checks.append({"name": "tmux", "ok": bool(tmux_path), "path": tmux_path})
|
|
50
|
-
ok = ok and bool(tmux_path)
|
|
51
|
-
ghostty = _ghostty_command()
|
|
52
|
-
ghostty_check = {"name": "ghostty", "ok": bool(ghostty), "path": ghostty, "required": False}
|
|
53
|
-
if spec and spec.get("runtime", {}).get("display_backend") in GHOSTTY_DISPLAY_BACKENDS:
|
|
54
|
-
ghostty_check["required"] = True
|
|
55
|
-
ok = ok and bool(ghostty)
|
|
56
|
-
checks.append(ghostty_check)
|
|
57
|
-
if spec:
|
|
58
|
-
profile_checks = profile_checks_for_agents(workspace, spec.get("agents", []))
|
|
59
|
-
profile_failures = [item for item in profile_checks if item.get("ok") is False]
|
|
60
|
-
checks.append({"name": "profiles", "ok": not profile_failures, "checks": [compact_profile_check(item) for item in profile_checks]})
|
|
61
|
-
ok = ok and not profile_failures
|
|
62
|
-
smoke_checks = profile_smoke_checks_for_agents(workspace, spec.get("agents", []))
|
|
63
|
-
smoke_failures = [item for item in smoke_checks if item.get("ok") is False]
|
|
64
|
-
checks.append({"name": "profile_smoke", "ok": not smoke_failures, "checks": [compact_profile_check(item) for item in smoke_checks]})
|
|
65
|
-
ok = ok and not smoke_failures
|
|
66
|
-
model_checks = model_checks_for_agents(spec.get("agents", []), workspace)
|
|
67
|
-
model_failures = [item for item in model_checks if item.get("ok") is False]
|
|
68
|
-
checks.append({"name": "models", "ok": not model_failures, "checks": compact_model_checks(model_checks)})
|
|
69
|
-
ok = ok and not model_failures
|
|
70
|
-
core = core_binary()
|
|
71
|
-
checks.append(
|
|
72
|
-
{
|
|
73
|
-
"name": "rust_core",
|
|
74
|
-
"ok": True,
|
|
75
|
-
"required": False,
|
|
76
|
-
"available": bool(core),
|
|
77
|
-
"path": str(core) if core else None,
|
|
78
|
-
"status": "available" if core else "python_fallback",
|
|
79
|
-
}
|
|
80
|
-
)
|
|
81
|
-
checks.append({"name": "profile_dir", "ok": profile_dir(workspace).exists() or (team_dir / "profiles").exists()})
|
|
82
|
-
details_log = logs_dir(workspace) / f"preflight-{int(time.time())}.json"
|
|
83
|
-
details = {"team_dir": str(team_dir), "checks": checks}
|
|
84
|
-
details_log.write_text(json.dumps(details, indent=2, ensure_ascii=False), encoding="utf-8")
|
|
85
|
-
event_log.write("preflight.complete", ok=ok, details_log=str(details_log), checks=checks)
|
|
86
|
-
blockers = [] if ok else preflight_blockers(checks)
|
|
87
|
-
return {
|
|
88
|
-
"ok": ok,
|
|
89
|
-
"summary": "preflight passed" if ok else "preflight found blockers: " + "; ".join(blockers[:3]),
|
|
90
|
-
"next_actions": [f"team-agent start --team {team_dir} --yes --json"] if ok else preflight_next_actions(blockers),
|
|
91
|
-
"details_log": str(details_log),
|
|
92
|
-
"checks": checks,
|
|
93
|
-
"blockers": blockers,
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
def start(team_dir: Path, yes: bool = False) -> dict[str, Any]:
|
|
98
|
-
from team_agent.compiler import compile_team
|
|
99
|
-
from team_agent.runtime import launch
|
|
100
|
-
|
|
101
|
-
team_dir = team_dir.resolve()
|
|
102
|
-
workspace = team_workspace(team_dir)
|
|
103
|
-
spec_path = team_dir / "team.spec.yaml"
|
|
104
|
-
compiled = compile_team(team_dir, spec_path)
|
|
105
|
-
if compiled["spec"].get("context", {}).get("state_file") == "team_state.md":
|
|
106
|
-
state_file = str(team_dir.relative_to(workspace) / "team_state.md") if team_dir.is_relative_to(workspace) else "team_state.md"
|
|
107
|
-
compiled["spec"]["context"]["state_file"] = state_file
|
|
108
|
-
spec_path.write_text(dumps(compiled["spec"]), encoding="utf-8")
|
|
109
|
-
launched = launch(spec_path, auto_approve=yes)
|
|
110
|
-
details_log = logs_dir(workspace) / f"start-{int(time.time())}.json"
|
|
111
|
-
details_log.write_text(json.dumps({"compile": compiled, "launch": launched}, indent=2, ensure_ascii=False), encoding="utf-8")
|
|
112
|
-
return {
|
|
113
|
-
"ok": bool(launched.get("ok")),
|
|
114
|
-
"summary": f"compiled {team_dir} and launched {len(launched.get('agents', []))} agents",
|
|
115
|
-
"next_actions": ["team-agent wait-ready --workspace . --timeout 120 --json"],
|
|
116
|
-
"details_log": str(details_log),
|
|
117
|
-
"spec": str(spec_path),
|
|
118
|
-
"launch": launched,
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
def preflight_blockers(checks: list[dict[str, Any]]) -> list[str]:
|
|
123
|
-
blockers: list[str] = []
|
|
124
|
-
for check in checks:
|
|
125
|
-
if check.get("ok", True):
|
|
126
|
-
continue
|
|
127
|
-
name = check.get("name") or "check"
|
|
128
|
-
if name == "compile":
|
|
129
|
-
blockers.append(f"compile: {check.get('error')}")
|
|
130
|
-
continue
|
|
131
|
-
for item in check.get("checks", []) or []:
|
|
132
|
-
agent = item.get("agent_id") or item.get("profile") or "-"
|
|
133
|
-
reason = item.get("reason") or item.get("status") or "failed"
|
|
134
|
-
detail = f"{name}: {agent} {reason}"
|
|
135
|
-
if item.get("endpoint"):
|
|
136
|
-
detail += f" endpoint={item['endpoint']}"
|
|
137
|
-
if item.get("proxy_configured"):
|
|
138
|
-
detail += f" proxy={item.get('proxy_url') or item.get('proxy_scheme')}"
|
|
139
|
-
if item.get("proxy_source"):
|
|
140
|
-
detail += f" proxy_source={item['proxy_source']}"
|
|
141
|
-
if item.get("proxy_mode"):
|
|
142
|
-
detail += f" proxy_mode={item['proxy_mode']}"
|
|
143
|
-
if item.get("missing_required"):
|
|
144
|
-
detail += " missing=" + ",".join(item["missing_required"])
|
|
145
|
-
if item.get("effective_model"):
|
|
146
|
-
detail += f" model={item['effective_model']}"
|
|
147
|
-
if item.get("suggestion"):
|
|
148
|
-
detail += f" suggestion={item['suggestion']}"
|
|
149
|
-
blockers.append(detail)
|
|
150
|
-
if not check.get("checks"):
|
|
151
|
-
blockers.append(f"{name}: failed")
|
|
152
|
-
return blockers or ["unknown preflight blocker"]
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
def preflight_next_actions(blockers: list[str]) -> list[str]:
|
|
156
|
-
actions = ["Fix failed checks, then rerun preflight."]
|
|
157
|
-
if any("proxy_connectivity_failed" in item for item in blockers):
|
|
158
|
-
actions.insert(0, "Allow the profile BASE_URL through the configured proxy, or disable the proxy for Team Agent startup.")
|
|
159
|
-
if any("proxy_source=ambient" in item for item in blockers):
|
|
160
|
-
actions.insert(0, "Current environment proxy is being used for this compatible_api worker; either fix that proxy for BASE_URL, set HTTPS_PROXY/HTTP_PROXY in the profile, or set PROXY_MODE=direct in the profile to bypass proxy for this worker.")
|
|
161
|
-
if any("missing=" in item or "profile_required_values_missing" in item for item in blockers):
|
|
162
|
-
actions.insert(
|
|
163
|
-
0,
|
|
164
|
-
"Ask the human user to fill the local profile file; agents must inspect only with `team-agent profile show <name> --workspace . --json` or the returned --team variant and must not read .team/*/profiles/*.env.",
|
|
165
|
-
)
|
|
166
|
-
if any("model_mismatch" in item or "does not match profile MODEL" in item for item in blockers):
|
|
167
|
-
actions.insert(0, "Keep the model in the profile MODEL field or make the role model exactly match it.")
|
|
168
|
-
return actions
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
def ensure_profiles_for_roles(team_dir: Path) -> None:
|
|
172
|
-
from team_agent.compiler import _read_front_matter
|
|
173
|
-
from team_agent.profiles import ensure_profile_secret_boundary, ensure_profile_secret_boundary_dir, init_profile
|
|
174
|
-
|
|
175
|
-
workspace = team_workspace(team_dir)
|
|
176
|
-
profiles_dir = team_dir / "profiles"
|
|
177
|
-
profiles_dir.mkdir(parents=True, exist_ok=True)
|
|
178
|
-
ensure_profile_secret_boundary(workspace)
|
|
179
|
-
ensure_profile_secret_boundary_dir(profiles_dir)
|
|
180
|
-
for role_doc in sorted((team_dir / "agents").glob("*.md")):
|
|
181
|
-
meta, _ = _read_front_matter(role_doc)
|
|
182
|
-
profile = meta.get("profile")
|
|
183
|
-
auth_mode = meta.get("auth_mode") or "subscription"
|
|
184
|
-
if not profile:
|
|
185
|
-
continue
|
|
186
|
-
if not (profiles_dir / f"{profile}.env").exists() and not (profiles_dir / f"{profile}.example.env").exists():
|
|
187
|
-
init_profile(workspace, str(profile), str(auth_mode))
|
|
188
|
-
if auth_mode == "subscription":
|
|
189
|
-
body = f"AUTH_MODE=subscription\nPROFILE_NAME={profile}\n"
|
|
190
|
-
elif auth_mode == "official_api":
|
|
191
|
-
body = f"AUTH_MODE=official_api\nPROFILE_NAME={profile}\nAPI_KEY=\nMODEL=\n"
|
|
192
|
-
else:
|
|
193
|
-
body = f"AUTH_MODE={auth_mode}\nPROFILE_NAME={profile}\nBASE_URL=\nAPI_KEY=\nMODEL=\n"
|
|
194
|
-
(profiles_dir / f"{profile}.example.env").write_text(body, encoding="utf-8")
|