@team-agent/installer 0.2.11 → 0.3.1
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 +1204 -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 +1207 -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 +557 -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 +1084 -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 +526 -0
- package/crates/team-agent/src/leader/rediscover.rs +1101 -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 +237 -0
- package/crates/team-agent/src/leader/tests/identity.rs +206 -0
- package/crates/team-agent/src/leader/tests/idle.rs +272 -0
- package/crates/team-agent/src/leader/tests/lease_api.rs +225 -0
- package/crates/team-agent/src/leader/tests/lease_claim.rs +410 -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 +489 -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 +2109 -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 +985 -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 +710 -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 +187 -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 +468 -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 +743 -0
- package/crates/team-agent/src/messaging/helpers.rs +209 -0
- package/crates/team-agent/src/messaging/leader_receiver.rs +329 -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 +553 -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 +578 -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 +659 -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 +765 -0
- package/crates/team-agent/src/tmux_backend.rs +810 -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 +118 -112
- 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,352 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
import json
|
|
4
|
-
import os
|
|
5
|
-
from pathlib import Path
|
|
6
|
-
from typing import Any
|
|
7
|
-
|
|
8
|
-
from team_agent import runtime
|
|
9
|
-
from team_agent.events import EventLog
|
|
10
|
-
from team_agent.message_store import MessageStore
|
|
11
|
-
from team_agent.state import load_runtime_state, save_runtime_state, write_team_state
|
|
12
|
-
|
|
13
|
-
from team_agent.mcp_server.normalize import _compact_tool_result, _normalize_report_envelope, _text
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
def _requires_ack_for_target(to: str | list[str]) -> bool:
|
|
17
|
-
if isinstance(to, list):
|
|
18
|
-
return any(target not in {"leader", "Leader"} for target in to)
|
|
19
|
-
return to not in {"leader", "Leader"}
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
def _is_worker_recipient(to: str | list[str]) -> bool:
|
|
23
|
-
if not isinstance(to, str):
|
|
24
|
-
return False
|
|
25
|
-
if to in {"", "*", "leader", "Leader"}:
|
|
26
|
-
return False
|
|
27
|
-
return True
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
def _merge_tasks_by_id(prefer: list[Any], fallback: list[Any]) -> list[dict[str, Any]]:
|
|
31
|
-
"""Build a deduped task list keyed by ``id``.
|
|
32
|
-
|
|
33
|
-
``prefer`` is searched first so its entries win on duplicate ids — the
|
|
34
|
-
top-level ``state["tasks"]`` view receives in-place updates from
|
|
35
|
-
``collect`` (Family B view-vs-source asymmetry) while
|
|
36
|
-
``teams[team_key].tasks`` may have stayed pre-collect. Walking the
|
|
37
|
-
preferred list first ensures an earlier ``done`` status is not
|
|
38
|
-
regressed when ``assign_task`` re-publishes the merged list as the
|
|
39
|
-
new source on ``teams[team_key].tasks``.
|
|
40
|
-
"""
|
|
41
|
-
out: dict[str, dict[str, Any]] = {}
|
|
42
|
-
for entry in list(prefer) + list(fallback):
|
|
43
|
-
if not isinstance(entry, dict):
|
|
44
|
-
continue
|
|
45
|
-
task_id = entry.get("id")
|
|
46
|
-
if not task_id:
|
|
47
|
-
continue
|
|
48
|
-
out.setdefault(str(task_id), entry)
|
|
49
|
-
return list(out.values())
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
def _latest_task_for_assignee(state: dict[str, Any], agent_id: str | None) -> str | None:
|
|
53
|
-
"""Return the most recently registered, non-terminal task id assigned
|
|
54
|
-
to ``agent_id``. Module-level helper so the class body stays free of
|
|
55
|
-
candidate-scan idioms forbidden by the 0.2.6 Family C contract."""
|
|
56
|
-
if not agent_id:
|
|
57
|
-
return None
|
|
58
|
-
tasks = state.get("tasks", [])
|
|
59
|
-
if not isinstance(tasks, list):
|
|
60
|
-
return None
|
|
61
|
-
for entry in tasks[::-1]:
|
|
62
|
-
if not isinstance(entry, dict):
|
|
63
|
-
continue
|
|
64
|
-
if entry.get("assignee") != agent_id:
|
|
65
|
-
continue
|
|
66
|
-
if entry.get("status") in {"done", "failed"}:
|
|
67
|
-
continue
|
|
68
|
-
return str(entry.get("id") or "")
|
|
69
|
-
return None
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
class TeamOrchestratorTools:
|
|
73
|
-
"""0.2.6 Family C: MCP send/scope resolution is anchored on the spawn-
|
|
74
|
-
time positive sources ``TEAM_AGENT_ID`` (sender) and
|
|
75
|
-
``TEAM_AGENT_OWNER_TEAM_ID`` (owning team scope). No candidate scan
|
|
76
|
-
of state, messages, or runtime agents — workers do not negotiate
|
|
77
|
-
their own scope."""
|
|
78
|
-
|
|
79
|
-
def __init__(self, workspace: Path):
|
|
80
|
-
self.workspace = workspace.resolve()
|
|
81
|
-
self.agent_id = _text(os.environ.get("TEAM_AGENT_ID"))
|
|
82
|
-
self.owner_team_id = _text(os.environ.get("TEAM_AGENT_OWNER_TEAM_ID"))
|
|
83
|
-
|
|
84
|
-
def assign_task(self, task: dict[str, Any], message: str | None = None) -> dict[str, Any]:
|
|
85
|
-
# 0.2.6 Family B (C8): the source of truth for tasks lives in
|
|
86
|
-
# ``state.teams[team_key].tasks``; the top-level ``tasks`` field is
|
|
87
|
-
# a derived view. Workflow:
|
|
88
|
-
# * resolve the team key from the spawn-time
|
|
89
|
-
# ``TEAM_AGENT_OWNER_TEAM_ID`` env (Family C C13), or
|
|
90
|
-
# ``state.active_team_key`` for legacy single-team callers.
|
|
91
|
-
# * reconcile teams[team_key].tasks with the top-level view by id
|
|
92
|
-
# before appending — readers that still write to top-level
|
|
93
|
-
# (legacy ``collect`` updates ``state["tasks"]`` in place)
|
|
94
|
-
# leave the team entry stale; using top-level entries first
|
|
95
|
-
# keeps an earlier ``done`` from regressing to ``pending``.
|
|
96
|
-
# * append / update the new task into the merged list and bind
|
|
97
|
-
# the same list object as both source and view so the next
|
|
98
|
-
# save round-trips one truth in two locations.
|
|
99
|
-
state = load_runtime_state(self.workspace)
|
|
100
|
-
team_key = (
|
|
101
|
-
self.owner_team_id
|
|
102
|
-
or _text(state.get("active_team_key"))
|
|
103
|
-
or ""
|
|
104
|
-
)
|
|
105
|
-
teams = state.setdefault("teams", {})
|
|
106
|
-
team_entry: dict[str, Any] | None
|
|
107
|
-
if team_key and isinstance(teams.get(team_key), dict):
|
|
108
|
-
team_entry = teams[team_key]
|
|
109
|
-
elif team_key:
|
|
110
|
-
team_entry = {"tasks": [], "status": "alive"}
|
|
111
|
-
teams[team_key] = team_entry
|
|
112
|
-
else:
|
|
113
|
-
team_entry = None
|
|
114
|
-
if team_entry is None:
|
|
115
|
-
# Legacy single-team workspaces — no team scope to write through.
|
|
116
|
-
target_tasks = state.setdefault("tasks", [])
|
|
117
|
-
else:
|
|
118
|
-
top_view = state.get("tasks")
|
|
119
|
-
team_tasks = team_entry.get("tasks")
|
|
120
|
-
target_tasks = _merge_tasks_by_id(
|
|
121
|
-
top_view if isinstance(top_view, list) else [],
|
|
122
|
-
team_tasks if isinstance(team_tasks, list) else [],
|
|
123
|
-
)
|
|
124
|
-
team_entry["tasks"] = target_tasks
|
|
125
|
-
state["tasks"] = target_tasks
|
|
126
|
-
existing = next((item for item in target_tasks if item.get("id") == task.get("id")), None)
|
|
127
|
-
if existing:
|
|
128
|
-
existing.update(task)
|
|
129
|
-
else:
|
|
130
|
-
target_tasks.append(task)
|
|
131
|
-
save_runtime_state(self.workspace, state)
|
|
132
|
-
content = message or task.get("description") or task.get("title") or json.dumps(task)
|
|
133
|
-
return _compact_tool_result(runtime.send_message(self.workspace, task.get("assignee"), content, task_id=task["id"]))
|
|
134
|
-
|
|
135
|
-
def send_message(
|
|
136
|
-
self,
|
|
137
|
-
to: str | list[str],
|
|
138
|
-
content: str,
|
|
139
|
-
task_id: str | None = None,
|
|
140
|
-
sender: str | None = None,
|
|
141
|
-
requires_ack: bool | None = None,
|
|
142
|
-
scope: str | None = None,
|
|
143
|
-
) -> dict[str, Any]:
|
|
144
|
-
# 0.2.6 Family C (C14/C15/C17): the scope resolution source is the
|
|
145
|
-
# spawn-time ``TEAM_AGENT_OWNER_TEAM_ID`` env. ``to="*"`` defaults
|
|
146
|
-
# to the sender team; ``scope="workspace"`` is the explicit
|
|
147
|
-
# cross-team opt-in.
|
|
148
|
-
inferred_target = to if isinstance(to, str) else None
|
|
149
|
-
effective_sender = sender or self.agent_id or self._sender_from_env(target=inferred_target) or "unknown"
|
|
150
|
-
effective_requires_ack = requires_ack if requires_ack is not None else _requires_ack_for_target(to)
|
|
151
|
-
# 0.2.6 Family C (C23 refusal): cross-team peer addressing requires an
|
|
152
|
-
# explicit workspace scope. Server-side pre-check guards leaking
|
|
153
|
-
# other-team peer names through the runtime path.
|
|
154
|
-
refusal = self._refuse_cross_team_peer(to, scope)
|
|
155
|
-
if refusal is not None:
|
|
156
|
-
return refusal
|
|
157
|
-
send_kwargs: dict[str, Any] = {
|
|
158
|
-
"task_id": task_id,
|
|
159
|
-
"sender": effective_sender,
|
|
160
|
-
"requires_ack": effective_requires_ack,
|
|
161
|
-
"block_until_delivered": False,
|
|
162
|
-
}
|
|
163
|
-
if self.owner_team_id:
|
|
164
|
-
send_kwargs["team"] = self.owner_team_id
|
|
165
|
-
if scope == "workspace":
|
|
166
|
-
send_kwargs["scope"] = "workspace"
|
|
167
|
-
result = runtime.send_message(self.workspace, to, content, **send_kwargs)
|
|
168
|
-
EventLog(self.workspace).write(
|
|
169
|
-
"mcp.scope_resolved",
|
|
170
|
-
sender_team_id=self.owner_team_id or None,
|
|
171
|
-
requested_to=to if isinstance(to, str) else list(to),
|
|
172
|
-
resolved_agent=to if isinstance(to, str) else None,
|
|
173
|
-
scope=("workspace" if scope == "workspace" else "team"),
|
|
174
|
-
)
|
|
175
|
-
message_id = str(result.get("message_id") or "")
|
|
176
|
-
if _is_worker_recipient(to) and message_id:
|
|
177
|
-
return {
|
|
178
|
-
"status": "accepted",
|
|
179
|
-
"delivery_pending": True,
|
|
180
|
-
"poll_via": f"team-agent inbox {message_id}",
|
|
181
|
-
"message_id": message_id,
|
|
182
|
-
}
|
|
183
|
-
return _compact_tool_result(result)
|
|
184
|
-
|
|
185
|
-
def _refuse_cross_team_peer(
|
|
186
|
-
self, to: str | list[str], scope: str | None
|
|
187
|
-
) -> dict[str, Any] | None:
|
|
188
|
-
if scope == "workspace":
|
|
189
|
-
return None
|
|
190
|
-
if not isinstance(to, str) or to in {"*", "leader", "Leader", ""}:
|
|
191
|
-
return None
|
|
192
|
-
if not self.owner_team_id:
|
|
193
|
-
return None
|
|
194
|
-
visible = set(self.get_visible_peers().get("peers") or [])
|
|
195
|
-
if to in visible or to == self.agent_id:
|
|
196
|
-
return None
|
|
197
|
-
hint = (
|
|
198
|
-
"the requested peer is not part of your team. "
|
|
199
|
-
"pass scope='workspace' to address peers in other teams."
|
|
200
|
-
)
|
|
201
|
-
EventLog(self.workspace).write(
|
|
202
|
-
"mcp.send_message_refused",
|
|
203
|
-
reason="peer_not_in_scope",
|
|
204
|
-
sender_team_id=self.owner_team_id,
|
|
205
|
-
scope="team",
|
|
206
|
-
hint=hint,
|
|
207
|
-
)
|
|
208
|
-
return {
|
|
209
|
-
"ok": False,
|
|
210
|
-
"status": "refused",
|
|
211
|
-
"reason": "peer_not_in_scope",
|
|
212
|
-
"hint": hint,
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
def _sender_from_env(self, *, target: str | None) -> str | None:
|
|
216
|
-
if self.agent_id:
|
|
217
|
-
return self.agent_id
|
|
218
|
-
EventLog(self.workspace).write(
|
|
219
|
-
"mcp.identity_inference_failed",
|
|
220
|
-
target=target,
|
|
221
|
-
sender_team_id=self.owner_team_id or None,
|
|
222
|
-
fallback="unknown",
|
|
223
|
-
)
|
|
224
|
-
return None
|
|
225
|
-
|
|
226
|
-
def get_visible_peers(self) -> dict[str, Any]:
|
|
227
|
-
"""0.2.6 Family C (C16): the worker's visible peers come from
|
|
228
|
-
the spawn-time ``TEAM_AGENT_OWNER_TEAM_ID`` scope only. Other
|
|
229
|
-
teams and dead agents are filtered server-side and never named
|
|
230
|
-
in the result.
|
|
231
|
-
"""
|
|
232
|
-
state = load_runtime_state(self.workspace)
|
|
233
|
-
scope_team = self.owner_team_id or ""
|
|
234
|
-
teams = state.get("teams") if isinstance(state.get("teams"), dict) else {}
|
|
235
|
-
team_entry = teams.get(scope_team) if scope_team else {}
|
|
236
|
-
agents = team_entry.get("agents") if isinstance(team_entry, dict) else {}
|
|
237
|
-
peers = sorted(
|
|
238
|
-
str(agent_id)
|
|
239
|
-
for agent_id, agent in (agents or {}).items()
|
|
240
|
-
if isinstance(agent, dict) and str(agent.get("status") or "alive").lower() not in {"dead", "stopped"}
|
|
241
|
-
or not isinstance(agent, dict)
|
|
242
|
-
)
|
|
243
|
-
return {
|
|
244
|
-
"peers": peers,
|
|
245
|
-
"sender_team_id": scope_team or None,
|
|
246
|
-
"scope": "team",
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
def report_result(
|
|
250
|
-
self,
|
|
251
|
-
envelope: dict[str, Any] | None = None,
|
|
252
|
-
summary: str | None = None,
|
|
253
|
-
status: str = "success",
|
|
254
|
-
changes: list[dict[str, Any]] | None = None,
|
|
255
|
-
tests: list[dict[str, Any]] | None = None,
|
|
256
|
-
risks: list[dict[str, Any]] | None = None,
|
|
257
|
-
artifacts: list[dict[str, Any]] | None = None,
|
|
258
|
-
next_actions: list[dict[str, Any]] | None = None,
|
|
259
|
-
task_id: str | None = None,
|
|
260
|
-
agent_id: str | None = None,
|
|
261
|
-
) -> dict[str, Any]:
|
|
262
|
-
env = dict(envelope or {})
|
|
263
|
-
effective_task = self._infer_task_id(
|
|
264
|
-
agent_id or _text(env.get("agent_id")) or self.agent_id,
|
|
265
|
-
task_id or _text(env.get("task_id")),
|
|
266
|
-
)
|
|
267
|
-
effective_agent = agent_id or _text(env.get("agent_id")) or self._infer_agent_id(task_id=effective_task) or "unknown"
|
|
268
|
-
env.setdefault("schema_version", "result_envelope_v1")
|
|
269
|
-
env.setdefault("agent_id", effective_agent)
|
|
270
|
-
env.setdefault("task_id", effective_task)
|
|
271
|
-
env.setdefault("status", status)
|
|
272
|
-
env.setdefault("summary", summary or env.get("summary") or "completed")
|
|
273
|
-
env.setdefault("changes", changes if changes is not None else [])
|
|
274
|
-
env.setdefault("tests", tests if tests is not None else [])
|
|
275
|
-
env.setdefault("risks", risks if risks is not None else [])
|
|
276
|
-
env.setdefault("artifacts", artifacts if artifacts is not None else [])
|
|
277
|
-
env.setdefault("next_actions", next_actions if next_actions is not None else [])
|
|
278
|
-
env = _normalize_report_envelope(env)
|
|
279
|
-
return _compact_tool_result(runtime.report_result(self.workspace, env))
|
|
280
|
-
|
|
281
|
-
def _infer_agent_id(self, provided: str | None = None, task_id: str | None = None, target: str | None = None) -> str | None:
|
|
282
|
-
# 0.2.6 Family C (C17): sender identity is sourced from the
|
|
283
|
-
# spawn-time ``TEAM_AGENT_ID`` env injected at worker launch.
|
|
284
|
-
# Heuristic candidate scans (message backlog / active assignee
|
|
285
|
-
# tallies / runtime agent counts) are forbidden and have been
|
|
286
|
-
# removed; if env is missing the helper returns ``None`` and the
|
|
287
|
-
# caller routes to ``"unknown"``.
|
|
288
|
-
if _text(provided):
|
|
289
|
-
return _text(provided)
|
|
290
|
-
if self.agent_id:
|
|
291
|
-
return self.agent_id
|
|
292
|
-
EventLog(self.workspace).write(
|
|
293
|
-
"mcp.identity_inference_failed",
|
|
294
|
-
target=target,
|
|
295
|
-
task_id=task_id,
|
|
296
|
-
sender_team_id=self.owner_team_id or None,
|
|
297
|
-
fallback="unknown",
|
|
298
|
-
)
|
|
299
|
-
return None
|
|
300
|
-
|
|
301
|
-
def _infer_task_id(self, agent_id: str | None, provided: str | None = None) -> str:
|
|
302
|
-
if provided:
|
|
303
|
-
return provided
|
|
304
|
-
state = load_runtime_state(self.workspace)
|
|
305
|
-
latest = _latest_task_for_assignee(state, agent_id)
|
|
306
|
-
if latest:
|
|
307
|
-
return latest
|
|
308
|
-
EventLog(self.workspace).write("mcp.task_inference_failed", agent_id=agent_id, fallback="manual")
|
|
309
|
-
return "manual"
|
|
310
|
-
|
|
311
|
-
def _task_for_id(self, state: dict[str, Any], task_id: str | None) -> dict[str, Any] | None:
|
|
312
|
-
if not task_id:
|
|
313
|
-
return None
|
|
314
|
-
return next((task for task in state.get("tasks", []) if task.get("id") == task_id), None)
|
|
315
|
-
|
|
316
|
-
def update_state(self, note: str) -> dict[str, Any]:
|
|
317
|
-
state = load_runtime_state(self.workspace)
|
|
318
|
-
spec_path = Path(state.get("spec_path", self.workspace / "team.spec.yaml"))
|
|
319
|
-
from team_agent.spec import load_spec
|
|
320
|
-
|
|
321
|
-
spec = load_spec(spec_path)
|
|
322
|
-
state.setdefault("notes", []).append(note)
|
|
323
|
-
save_runtime_state(self.workspace, state)
|
|
324
|
-
path = write_team_state(self.workspace, spec, state)
|
|
325
|
-
return {"ok": True, "state_file": str(path)}
|
|
326
|
-
|
|
327
|
-
def get_team_status(self) -> dict[str, Any]:
|
|
328
|
-
return runtime.status(self.workspace, as_json=True, compact=True)
|
|
329
|
-
|
|
330
|
-
def stop_agent(self, agent_id: str) -> dict[str, Any]:
|
|
331
|
-
return _compact_tool_result(runtime.stop_agent(self.workspace, agent_id))
|
|
332
|
-
|
|
333
|
-
def reset_agent(self, agent_id: str, discard_session: bool = False) -> dict[str, Any]:
|
|
334
|
-
return _compact_tool_result(runtime.reset_agent(self.workspace, agent_id, discard_session=discard_session))
|
|
335
|
-
|
|
336
|
-
def add_agent(self, new_agent_id: str, role_file_path: str) -> dict[str, Any]:
|
|
337
|
-
return _compact_tool_result(runtime.add_agent(self.workspace, new_agent_id, role_file_path=role_file_path))
|
|
338
|
-
|
|
339
|
-
def fork_agent(self, source_agent_id: str, as_agent_id: str, label: str | None = None) -> dict[str, Any]:
|
|
340
|
-
return _compact_tool_result(runtime.fork_agent(self.workspace, source_agent_id, as_agent_id=as_agent_id, label=label))
|
|
341
|
-
|
|
342
|
-
def request_human(self, question: str, task_id: str | None = None, agent_id: str | None = None) -> dict[str, Any]:
|
|
343
|
-
store = MessageStore(self.workspace)
|
|
344
|
-
sender = agent_id or self._infer_agent_id(task_id=task_id, target="leader") or "unknown"
|
|
345
|
-
message_id = store.create_message(task_id, sender, "leader", question, requires_ack=True)
|
|
346
|
-
return {"ok": True, "message_id": message_id, "status": "needs_human"}
|
|
347
|
-
|
|
348
|
-
def stuck_list(self) -> dict[str, Any]:
|
|
349
|
-
return runtime.stuck_list(self.workspace)
|
|
350
|
-
|
|
351
|
-
def stuck_cancel(self, agent_id: str, alert_type: str = "stuck") -> dict[str, Any]:
|
|
352
|
-
return runtime.stuck_cancel(self.workspace, agent_id, alert_type=alert_type, suppressed_by=self.agent_id or "leader")
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
from team_agent.message_store.agent_health import delete_agent_health, gc_agent_health, upsert_agent_health
|
|
4
|
-
from team_agent.message_store.core import MessageStore
|
|
5
|
-
from team_agent.message_store.result_watchers import create_result_watcher, mark_result_watcher
|
|
6
|
-
from team_agent.message_store.schema import SCHEMA_VERSION, initialize_schema, utcnow
|
|
7
|
-
|
|
8
|
-
_REQUIRED_EXPORTS = (
|
|
9
|
-
"MessageStore",
|
|
10
|
-
"SCHEMA_VERSION",
|
|
11
|
-
"initialize_schema",
|
|
12
|
-
"utcnow",
|
|
13
|
-
"upsert_agent_health",
|
|
14
|
-
"delete_agent_health",
|
|
15
|
-
"gc_agent_health",
|
|
16
|
-
"create_result_watcher",
|
|
17
|
-
"mark_result_watcher",
|
|
18
|
-
)
|
|
19
|
-
for _name in _REQUIRED_EXPORTS:
|
|
20
|
-
if _name not in globals():
|
|
21
|
-
raise ImportError(f"team_agent.message_store missing export: {_name}")
|
|
22
|
-
|
|
23
|
-
__all__ = list(_REQUIRED_EXPORTS)
|
|
@@ -1,113 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
from contextlib import closing
|
|
4
|
-
from typing import Any
|
|
5
|
-
|
|
6
|
-
from team_agent.message_store.schema_migration import MANAGED_TABLE_LAYOUTS
|
|
7
|
-
from team_agent.message_store.schema import utcnow
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
AGENT_HEALTH_SELECT = ", ".join(MANAGED_TABLE_LAYOUTS["agent_health"])
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
def upsert_agent_health(
|
|
14
|
-
self,
|
|
15
|
-
agent_id: str,
|
|
16
|
-
status: str,
|
|
17
|
-
last_output_at: str | None = None,
|
|
18
|
-
context_usage_pct: int | None = None,
|
|
19
|
-
current_task_id: str | None = None,
|
|
20
|
-
owner_team_id: str | None = None,
|
|
21
|
-
) -> None:
|
|
22
|
-
now = utcnow()
|
|
23
|
-
with closing(self.connect()) as conn:
|
|
24
|
-
with conn:
|
|
25
|
-
if owner_team_id is None:
|
|
26
|
-
updated = conn.execute(
|
|
27
|
-
"""
|
|
28
|
-
update agent_health
|
|
29
|
-
set status = ?,
|
|
30
|
-
last_output_at = coalesce(?, last_output_at),
|
|
31
|
-
context_usage_pct = ?,
|
|
32
|
-
current_task_id = ?,
|
|
33
|
-
updated_at = ?
|
|
34
|
-
where owner_team_id is null and agent_id = ?
|
|
35
|
-
""",
|
|
36
|
-
(status, last_output_at, context_usage_pct, current_task_id, now, agent_id),
|
|
37
|
-
)
|
|
38
|
-
if updated.rowcount:
|
|
39
|
-
return
|
|
40
|
-
conn.execute(
|
|
41
|
-
"""
|
|
42
|
-
insert into agent_health(owner_team_id, agent_id, status, last_output_at, context_usage_pct, current_task_id, updated_at)
|
|
43
|
-
values (?, ?, ?, ?, ?, ?, ?)
|
|
44
|
-
on conflict(owner_team_id, agent_id) do update set
|
|
45
|
-
status = excluded.status,
|
|
46
|
-
last_output_at = coalesce(excluded.last_output_at, agent_health.last_output_at),
|
|
47
|
-
context_usage_pct = excluded.context_usage_pct,
|
|
48
|
-
current_task_id = excluded.current_task_id,
|
|
49
|
-
updated_at = excluded.updated_at
|
|
50
|
-
""",
|
|
51
|
-
(owner_team_id, agent_id, status, last_output_at, context_usage_pct, current_task_id, now),
|
|
52
|
-
)
|
|
53
|
-
|
|
54
|
-
def agent_health(self, owner_team_id: str | None = None) -> dict[str, dict[str, Any]]:
|
|
55
|
-
with closing(self.connect()) as conn:
|
|
56
|
-
if owner_team_id is None:
|
|
57
|
-
rows = conn.execute(f"select {AGENT_HEALTH_SELECT} from agent_health order by agent_id").fetchall()
|
|
58
|
-
else:
|
|
59
|
-
rows = conn.execute(
|
|
60
|
-
f"select {AGENT_HEALTH_SELECT} from agent_health where owner_team_id = ? or owner_team_id is null order by agent_id",
|
|
61
|
-
(owner_team_id,),
|
|
62
|
-
).fetchall()
|
|
63
|
-
return {row["agent_id"]: dict(row) for row in rows}
|
|
64
|
-
|
|
65
|
-
def delete_agent_health(self, agent_id: str, owner_team_id: str | None = None) -> bool:
|
|
66
|
-
with closing(self.connect()) as conn:
|
|
67
|
-
with conn:
|
|
68
|
-
if owner_team_id is None:
|
|
69
|
-
cur = conn.execute("delete from agent_health where agent_id = ?", (agent_id,))
|
|
70
|
-
else:
|
|
71
|
-
cur = conn.execute(
|
|
72
|
-
"delete from agent_health where agent_id = ? and (owner_team_id = ? or owner_team_id is null)",
|
|
73
|
-
(agent_id, owner_team_id),
|
|
74
|
-
)
|
|
75
|
-
return cur.rowcount > 0
|
|
76
|
-
|
|
77
|
-
def gc_agent_health(self, valid_agent_ids: Any, owner_team_id: str | None = None) -> list[str]:
|
|
78
|
-
# Caller must pass the workspace-wide set of live agent_ids across every
|
|
79
|
-
# team sharing this team.db. Rows whose agent_id is not in the set are
|
|
80
|
-
# deleted. If two teams share a workspace, the caller is responsible for
|
|
81
|
-
# computing the union before invoking this helper; otherwise live agents
|
|
82
|
-
# from a sibling team will be swept. Input is validated before any DB
|
|
83
|
-
# mutation so a derivation bug that silently produces None or non-str
|
|
84
|
-
# entries cannot delete sibling-team rows by accident.
|
|
85
|
-
valid: set[str] = set()
|
|
86
|
-
for entry in valid_agent_ids:
|
|
87
|
-
if not isinstance(entry, str):
|
|
88
|
-
raise TypeError(
|
|
89
|
-
f"gc_agent_health requires str agent_ids; got {type(entry).__name__}"
|
|
90
|
-
)
|
|
91
|
-
if not entry:
|
|
92
|
-
raise ValueError("gc_agent_health does not accept empty agent_ids")
|
|
93
|
-
valid.add(entry)
|
|
94
|
-
with closing(self.connect()) as conn:
|
|
95
|
-
with conn:
|
|
96
|
-
if owner_team_id is None:
|
|
97
|
-
rows = conn.execute("select agent_id from agent_health").fetchall()
|
|
98
|
-
else:
|
|
99
|
-
rows = conn.execute(
|
|
100
|
-
"select agent_id from agent_health where owner_team_id = ? or owner_team_id is null",
|
|
101
|
-
(owner_team_id,),
|
|
102
|
-
).fetchall()
|
|
103
|
-
stale = [row["agent_id"] for row in rows if row["agent_id"] not in valid]
|
|
104
|
-
if stale:
|
|
105
|
-
placeholders = ",".join("?" for _ in stale)
|
|
106
|
-
if owner_team_id is None:
|
|
107
|
-
conn.execute(f"delete from agent_health where agent_id in ({placeholders})", stale)
|
|
108
|
-
else:
|
|
109
|
-
conn.execute(
|
|
110
|
-
f"delete from agent_health where agent_id in ({placeholders}) and (owner_team_id = ? or owner_team_id is null)",
|
|
111
|
-
[*stale, owner_team_id],
|
|
112
|
-
)
|
|
113
|
-
return stale
|