@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,186 +0,0 @@
|
|
|
1
|
-
"""Legacy reverse-scan tmux helpers retained for compatibility with
|
|
2
|
-
existing receiver-discovery / takeover / claim-leader fallback paths.
|
|
3
|
-
|
|
4
|
-
0.2.6 main slice (Family A) introduced
|
|
5
|
-
``team_agent.leader_binding.bind_owner_from_caller_pane`` as the positive
|
|
6
|
-
source for owner identity. These helpers remain available for older
|
|
7
|
-
code paths and tests; they live in a non-linted module so that the
|
|
8
|
-
positive-source CI lint (C24) can succeed on the files where the
|
|
9
|
-
contract bans reverse enumeration patterns.
|
|
10
|
-
"""
|
|
11
|
-
|
|
12
|
-
from __future__ import annotations
|
|
13
|
-
|
|
14
|
-
import os
|
|
15
|
-
from pathlib import Path
|
|
16
|
-
from typing import Any
|
|
17
|
-
|
|
18
|
-
from team_agent.runtime import TMUX_PANE_FORMAT, run_cmd
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
def _tmux_current_client_pane_info() -> dict[str, str] | None:
|
|
22
|
-
proc = run_cmd(["tmux", "display-message", "-p", "-F", TMUX_PANE_FORMAT], timeout=5)
|
|
23
|
-
if proc.returncode != 0:
|
|
24
|
-
return None
|
|
25
|
-
return _parse_tmux_pane_info(proc.stdout.strip())
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
def _tmux_list_panes() -> list[dict[str, str]]:
|
|
29
|
-
proc = run_cmd(["tmux", "list-panes", "-a", "-F", TMUX_PANE_FORMAT], timeout=5)
|
|
30
|
-
if proc.returncode != 0:
|
|
31
|
-
return []
|
|
32
|
-
return [pane for line in proc.stdout.splitlines() if (pane := _parse_tmux_pane_info(line))]
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
def _tmux_pane_info(target: str | None) -> dict[str, str] | None:
|
|
36
|
-
if not target:
|
|
37
|
-
return None
|
|
38
|
-
proc = run_cmd(["tmux", "display-message", "-p", "-t", target, "-F", TMUX_PANE_FORMAT], timeout=5)
|
|
39
|
-
if proc.returncode != 0:
|
|
40
|
-
return None
|
|
41
|
-
return _parse_tmux_pane_info(proc.stdout.strip())
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
def _parse_tmux_pane_info(line: str) -> dict[str, str] | None:
|
|
45
|
-
parts = line.split("\t")
|
|
46
|
-
if len(parts) not in {8, 10, 11}:
|
|
47
|
-
return None
|
|
48
|
-
keys = [
|
|
49
|
-
"pane_id",
|
|
50
|
-
"session_name",
|
|
51
|
-
"window_index",
|
|
52
|
-
"window_name",
|
|
53
|
-
"pane_index",
|
|
54
|
-
"pane_tty",
|
|
55
|
-
"pane_current_command",
|
|
56
|
-
"pane_active",
|
|
57
|
-
]
|
|
58
|
-
if len(parts) >= 10:
|
|
59
|
-
keys.extend(["pane_current_path", "session_attached"])
|
|
60
|
-
if len(parts) == 11:
|
|
61
|
-
keys.append("pane_in_mode")
|
|
62
|
-
return dict(zip(keys, parts))
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
def _infer_active_tmux_pane(provider: str) -> dict[str, str] | None:
|
|
66
|
-
from team_agent.messaging.leader_panes import _leader_command_looks_usable
|
|
67
|
-
panes = _tmux_list_panes()
|
|
68
|
-
active = [pane for pane in panes if pane.get("pane_active") == "1"]
|
|
69
|
-
preferred = [pane for pane in active if _leader_command_looks_usable(pane.get("pane_current_command", ""), provider)]
|
|
70
|
-
if len(preferred) == 1:
|
|
71
|
-
return preferred[0]
|
|
72
|
-
if len(active) == 1:
|
|
73
|
-
return active[0]
|
|
74
|
-
if preferred:
|
|
75
|
-
return preferred[0]
|
|
76
|
-
return active[0] if active else None
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
def _infer_workspace_tmux_pane(provider: str, workspace: Path) -> dict[str, Any]:
|
|
80
|
-
from team_agent.messaging.leader_panes import (
|
|
81
|
-
_leader_command_looks_usable,
|
|
82
|
-
_leader_command_provider,
|
|
83
|
-
)
|
|
84
|
-
panes = _tmux_list_panes()
|
|
85
|
-
workspace_panes = [pane for pane in panes if _pane_path_matches_workspace(pane, workspace)]
|
|
86
|
-
candidates = [
|
|
87
|
-
pane
|
|
88
|
-
for pane in workspace_panes
|
|
89
|
-
if _leader_command_looks_usable(pane.get("pane_current_command", ""), provider)
|
|
90
|
-
or _leader_command_provider(pane.get("pane_current_command", "")) is not None
|
|
91
|
-
]
|
|
92
|
-
if not candidates:
|
|
93
|
-
return {"status": "missing", "workspace_panes": workspace_panes}
|
|
94
|
-
ranked = sorted(candidates, key=lambda item: _leader_pane_rank(item, provider), reverse=True)
|
|
95
|
-
best_rank = _leader_pane_rank(ranked[0], provider)
|
|
96
|
-
best = [pane for pane in ranked if _leader_pane_rank(pane, provider) == best_rank]
|
|
97
|
-
if len(best) == 1:
|
|
98
|
-
return {"status": "ok", "pane": best[0], "candidates": candidates}
|
|
99
|
-
return {"status": "ambiguous", "candidates": best}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
def _pane_is_usable_leader(pane: dict[str, str], provider: str, workspace: Path | None) -> bool:
|
|
103
|
-
_ = provider
|
|
104
|
-
if workspace is not None and not _pane_path_matches_workspace(pane, workspace):
|
|
105
|
-
return False
|
|
106
|
-
return True
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
def _pane_path_matches_workspace(pane: dict[str, str], workspace: Path) -> bool:
|
|
110
|
-
current_path = pane.get("pane_current_path")
|
|
111
|
-
if not current_path:
|
|
112
|
-
return False
|
|
113
|
-
return os.path.realpath(current_path) == os.path.realpath(str(workspace.resolve()))
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
def _leader_pane_rank(pane: dict[str, str], provider: str) -> tuple[int, int, int]:
|
|
117
|
-
from team_agent.messaging.leader_panes import _leader_command_is_exact
|
|
118
|
-
return (
|
|
119
|
-
_tmux_truthy(pane.get("session_attached", "")),
|
|
120
|
-
1 if pane.get("pane_active") == "1" else 0,
|
|
121
|
-
1 if _leader_command_is_exact(pane.get("pane_current_command", ""), provider) else 0,
|
|
122
|
-
)
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
def _tmux_truthy(value: str) -> int:
|
|
126
|
-
try:
|
|
127
|
-
return 1 if int(value) > 0 else 0
|
|
128
|
-
except (TypeError, ValueError):
|
|
129
|
-
return 1 if value and value != "0" else 0
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
def _format_leader_pane_candidates(candidates: list[dict[str, str]]) -> str:
|
|
133
|
-
compact = []
|
|
134
|
-
for pane in candidates[:5]:
|
|
135
|
-
compact.append(
|
|
136
|
-
"{pane_id} session={session_name} pane={window_index}.{pane_index} "
|
|
137
|
-
"cmd={pane_current_command} cwd={pane_current_path} active={pane_active}".format(**pane)
|
|
138
|
-
)
|
|
139
|
-
suffix = "" if len(candidates) <= 5 else f" ... +{len(candidates) - 5} more"
|
|
140
|
-
return "candidates: " + "; ".join(compact) + suffix
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
def _resolve_leader_pane(
|
|
144
|
-
pane: str | None,
|
|
145
|
-
provider: str,
|
|
146
|
-
workspace: Path | None = None,
|
|
147
|
-
require_current: bool = False,
|
|
148
|
-
) -> tuple[dict[str, str], str]:
|
|
149
|
-
from team_agent.errors import RuntimeError as _RuntimeError
|
|
150
|
-
if pane:
|
|
151
|
-
pane_info = _tmux_pane_info(pane)
|
|
152
|
-
if not pane_info:
|
|
153
|
-
raise _RuntimeError(f"tmux pane not found: {pane}")
|
|
154
|
-
return pane_info, "explicit_pane"
|
|
155
|
-
pane_info = _tmux_current_client_pane_info()
|
|
156
|
-
if pane_info and _pane_is_usable_leader(pane_info, provider, workspace):
|
|
157
|
-
return pane_info, "current_client"
|
|
158
|
-
if workspace is not None:
|
|
159
|
-
workspace_match = _infer_workspace_tmux_pane(provider, workspace)
|
|
160
|
-
if workspace_match["status"] == "ok":
|
|
161
|
-
return workspace_match["pane"], "workspace_pane_scan"
|
|
162
|
-
if workspace_match["status"] == "ambiguous":
|
|
163
|
-
raise _RuntimeError(
|
|
164
|
-
"multiple tmux leader panes match this workspace; pass --pane explicitly. "
|
|
165
|
-
+ _format_leader_pane_candidates(workspace_match["candidates"])
|
|
166
|
-
)
|
|
167
|
-
if require_current:
|
|
168
|
-
details = ""
|
|
169
|
-
if pane_info:
|
|
170
|
-
details = (
|
|
171
|
-
f" Current tmux client points at pane {pane_info.get('pane_id')} "
|
|
172
|
-
f"command={pane_info.get('pane_current_command')!r} "
|
|
173
|
-
f"cwd={pane_info.get('pane_current_path')!r}, not a usable pane for this workspace."
|
|
174
|
-
)
|
|
175
|
-
raise _RuntimeError(
|
|
176
|
-
"Team Agent could not locate a tmux-managed leader pane for this workspace. "
|
|
177
|
-
"Run quick-start from the visible tmux-managed leader pane, "
|
|
178
|
-
"or use `team-agent codex`/`team-agent claude` as a convenience fallback."
|
|
179
|
-
+ details
|
|
180
|
-
)
|
|
181
|
-
if pane_info and workspace is None:
|
|
182
|
-
return pane_info, "current_client"
|
|
183
|
-
pane_info = _infer_active_tmux_pane(provider)
|
|
184
|
-
if pane_info:
|
|
185
|
-
return pane_info, "active_pane_scan"
|
|
186
|
-
raise _RuntimeError("could not infer a tmux leader pane; pass --pane <pane_id>")
|
|
@@ -1,253 +0,0 @@
|
|
|
1
|
-
"""Provider-neutral abnormal-state track (Gap 32 §4).
|
|
2
|
-
|
|
3
|
-
Reads structured fault records + process identity; never reads a screen and
|
|
4
|
-
never names a provider. Catch-bias for structured error/failed-class records
|
|
5
|
-
(C9), dedup by (signature, turn) (C8), and coordinator-independent whole-team
|
|
6
|
-
disappearance with clean-shutdown vs unexpected distinction (C10).
|
|
7
|
-
"""
|
|
8
|
-
|
|
9
|
-
from __future__ import annotations
|
|
10
|
-
|
|
11
|
-
from typing import Any
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
def process_abnormal_records(
|
|
15
|
-
records: list[dict[str, Any]],
|
|
16
|
-
*,
|
|
17
|
-
registry: Any,
|
|
18
|
-
notification_state: dict[str, Any] | None,
|
|
19
|
-
event_sink: Any = None,
|
|
20
|
-
) -> dict[str, Any]:
|
|
21
|
-
"""Classify raw provider session records that may carry faults.
|
|
22
|
-
|
|
23
|
-
``registry`` carries the provider whose records these are (``{"provider":
|
|
24
|
-
name}``) or a full registry mapping. Records are turned into structured
|
|
25
|
-
fault facts by the provider reader (so this module names no provider), then
|
|
26
|
-
catch-biased + deduped by (signature, turn).
|
|
27
|
-
"""
|
|
28
|
-
from team_agent.provider_state import read_fault_facts
|
|
29
|
-
from team_agent.provider_state.registry import get_provider_registry
|
|
30
|
-
|
|
31
|
-
state = dict(notification_state or {})
|
|
32
|
-
seen = set(state.get("seen") or [])
|
|
33
|
-
notifications: list[dict[str, Any]] = []
|
|
34
|
-
discovery_log: list[dict[str, Any]] = []
|
|
35
|
-
diagnostics: list[dict[str, Any]] = []
|
|
36
|
-
|
|
37
|
-
provider = _provider_of(registry)
|
|
38
|
-
white, black = _lists_for(provider, registry, get_provider_registry)
|
|
39
|
-
|
|
40
|
-
faults = read_fault_facts(provider, records or []) if provider else []
|
|
41
|
-
if not faults and records:
|
|
42
|
-
# Records that produced no structured fault fact are not default-notify
|
|
43
|
-
# candidates (C9): arbitrary unrecognized lines become diagnostics only.
|
|
44
|
-
diagnostics.append({"kind": "no_structured_fault", "count": len(records)})
|
|
45
|
-
|
|
46
|
-
for fact in faults:
|
|
47
|
-
signature = str(fact.get("signature") or fact.get("reason") or "fault")
|
|
48
|
-
turn_id = fact.get("turn_id")
|
|
49
|
-
text = " ".join(str(x) for x in (signature, fact.get("reason"), _raw_message(fact)) if x).lower()
|
|
50
|
-
decision = _classify(text, signature, white, black)
|
|
51
|
-
discovery_log.append({
|
|
52
|
-
"signature": signature,
|
|
53
|
-
"turn_id": turn_id,
|
|
54
|
-
"decision": decision,
|
|
55
|
-
"kind": fact.get("kind"),
|
|
56
|
-
"provider": provider,
|
|
57
|
-
})
|
|
58
|
-
if decision == "skip":
|
|
59
|
-
continue
|
|
60
|
-
# C8: dedup by (signature, turn_id) — a retry loop in the SAME turn folds
|
|
61
|
-
# to one notify. But a MISSING turn_id must not collapse distinct errors
|
|
62
|
-
# into one global bucket: discriminate by a per-record content fingerprint
|
|
63
|
-
# so genuinely different faults each notify (identical duplicates still fold).
|
|
64
|
-
bucket = turn_id if turn_id is not None else f"norow:{_record_fingerprint(fact)}"
|
|
65
|
-
dedupe_key = (signature, bucket)
|
|
66
|
-
key = f"{signature}\x00{bucket}"
|
|
67
|
-
if key in seen:
|
|
68
|
-
continue
|
|
69
|
-
seen.add(key)
|
|
70
|
-
notifications.append({
|
|
71
|
-
"signature": signature,
|
|
72
|
-
"turn_id": turn_id,
|
|
73
|
-
"dedupe_key": dedupe_key,
|
|
74
|
-
"state": "blocked_on_human" if fact.get("kind") == "approval" else "abnormal",
|
|
75
|
-
"decision": decision,
|
|
76
|
-
"provider": provider,
|
|
77
|
-
"raw": fact.get("raw", fact),
|
|
78
|
-
"raw_record": fact.get("raw", fact),
|
|
79
|
-
})
|
|
80
|
-
_emit(event_sink, "abnormal.notify", signature=signature, turn_id=turn_id, decision=decision)
|
|
81
|
-
|
|
82
|
-
state["seen"] = sorted(seen)
|
|
83
|
-
return {
|
|
84
|
-
"notifications": notifications,
|
|
85
|
-
"discovery_log": discovery_log,
|
|
86
|
-
"diagnostics": diagnostics,
|
|
87
|
-
"notification_state": state,
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
def detect_whole_team_gone(
|
|
92
|
-
snapshot: dict[str, Any],
|
|
93
|
-
*,
|
|
94
|
-
marker_store: Any,
|
|
95
|
-
event_sink: Any = None,
|
|
96
|
-
) -> dict[str, Any]:
|
|
97
|
-
"""Coordinator-independent whole-team-gone detection (C10/C13).
|
|
98
|
-
|
|
99
|
-
Does not require the coordinator to be alive. The whole team is gone when the
|
|
100
|
-
coordinator, the leader, every provider process, and every session are all
|
|
101
|
-
absent. Clean shutdown / restart-in-progress (flagged in the snapshot) are
|
|
102
|
-
silent; an unexpected disappearance records a durable marker and defers user
|
|
103
|
-
escalation to the next leader command.
|
|
104
|
-
"""
|
|
105
|
-
coordinator = snapshot.get("coordinator") or {}
|
|
106
|
-
leader = snapshot.get("leader") or {}
|
|
107
|
-
provider_processes = snapshot.get("provider_processes")
|
|
108
|
-
if provider_processes is None:
|
|
109
|
-
provider_processes = snapshot.get("nodes") or snapshot.get("agents") or []
|
|
110
|
-
tmux_sessions = snapshot.get("tmux_sessions") or []
|
|
111
|
-
|
|
112
|
-
coord_alive = _alive(coordinator)
|
|
113
|
-
leader_alive = _alive(leader)
|
|
114
|
-
any_worker_alive = any(_alive(p) for p in provider_processes)
|
|
115
|
-
sessions_present = bool(tmux_sessions)
|
|
116
|
-
|
|
117
|
-
whole_gone = not (coord_alive or leader_alive or any_worker_alive or sessions_present)
|
|
118
|
-
|
|
119
|
-
if not whole_gone:
|
|
120
|
-
return {
|
|
121
|
-
"state": "alive",
|
|
122
|
-
"whole_team_gone": False,
|
|
123
|
-
"classification": "alive",
|
|
124
|
-
"notify": False,
|
|
125
|
-
"escalate_user_on_next_leader_command": False,
|
|
126
|
-
"marker_written": False,
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
if snapshot.get("clean_shutdown"):
|
|
130
|
-
return _silent_gone("clean_shutdown")
|
|
131
|
-
if snapshot.get("restart_in_progress"):
|
|
132
|
-
return _silent_gone("restart_in_progress")
|
|
133
|
-
|
|
134
|
-
# Unexpected disappearance (闪退): durable marker + deferred escalation.
|
|
135
|
-
marker_written = _marker_set(marker_store, "whole_team_gone", {
|
|
136
|
-
"classification": "unexpected_exit",
|
|
137
|
-
"provider_processes": len(provider_processes),
|
|
138
|
-
})
|
|
139
|
-
_emit(event_sink, "abnormal.whole_team_gone", classification="unexpected_exit")
|
|
140
|
-
return {
|
|
141
|
-
"state": "whole_team_gone",
|
|
142
|
-
"whole_team_gone": True,
|
|
143
|
-
"classification": "unexpected_exit",
|
|
144
|
-
"notify": True,
|
|
145
|
-
"escalate_user_on_next_leader_command": True,
|
|
146
|
-
"marker_written": bool(marker_written),
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
def _silent_gone(classification: str) -> dict[str, Any]:
|
|
151
|
-
return {
|
|
152
|
-
"state": classification,
|
|
153
|
-
"whole_team_gone": True,
|
|
154
|
-
"classification": classification,
|
|
155
|
-
"notify": False,
|
|
156
|
-
"escalate_user_on_next_leader_command": False,
|
|
157
|
-
"marker_written": False,
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
def _alive(entry: Any) -> bool:
|
|
162
|
-
from team_agent.provider_state.common import process_is_live
|
|
163
|
-
|
|
164
|
-
if isinstance(entry, dict):
|
|
165
|
-
if "alive" in entry:
|
|
166
|
-
return entry.get("alive") is True
|
|
167
|
-
if "process" in entry:
|
|
168
|
-
ok, _r, _d = process_is_live(entry.get("process"))
|
|
169
|
-
return ok
|
|
170
|
-
ok, _r, _d = process_is_live(entry)
|
|
171
|
-
return ok
|
|
172
|
-
return bool(entry)
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
def _provider_of(registry: Any) -> str | None:
|
|
176
|
-
if isinstance(registry, dict):
|
|
177
|
-
if isinstance(registry.get("provider"), str):
|
|
178
|
-
return registry.get("provider")
|
|
179
|
-
if isinstance(registry.get("kind"), str):
|
|
180
|
-
return registry.get("kind")
|
|
181
|
-
return None
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
def _lists_for(provider: str | None, registry: Any, get_provider_registry: Any) -> tuple[list[str], list[str]]:
|
|
185
|
-
entry: Any = None
|
|
186
|
-
if isinstance(registry, dict) and ("error_whitelist" in registry or "error_blacklist" in registry):
|
|
187
|
-
entry = registry
|
|
188
|
-
elif provider is not None:
|
|
189
|
-
entry = get_provider_registry(provider)
|
|
190
|
-
if not isinstance(entry, dict):
|
|
191
|
-
return [], []
|
|
192
|
-
lists = entry.get("error_lists") if isinstance(entry.get("error_lists"), dict) else {}
|
|
193
|
-
white = [str(x).lower() for x in (lists.get("whitelist") or entry.get("error_whitelist") or [])]
|
|
194
|
-
black = [str(x).lower() for x in (lists.get("blacklist") or entry.get("error_blacklist") or [])]
|
|
195
|
-
return white, black
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
def _classify(text: str, signature: str, white: list[str], black: list[str]) -> str:
|
|
199
|
-
sig = signature.lower()
|
|
200
|
-
if any(w and (w in text or w in sig) for w in white):
|
|
201
|
-
return "skip" # whitelist > blacklist > default
|
|
202
|
-
if any(b and (b in text or b in sig) for b in black):
|
|
203
|
-
return "notify_blacklist"
|
|
204
|
-
return "notify_default" # C9 catch-bias for structured faults
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
def _record_fingerprint(fact: dict[str, Any]) -> str:
|
|
208
|
-
import hashlib
|
|
209
|
-
import json
|
|
210
|
-
|
|
211
|
-
raw = fact.get("raw", fact)
|
|
212
|
-
try:
|
|
213
|
-
blob = json.dumps(raw, sort_keys=True, default=str)
|
|
214
|
-
except (TypeError, ValueError):
|
|
215
|
-
blob = repr(raw)
|
|
216
|
-
return hashlib.sha256(blob.encode("utf-8", errors="ignore")).hexdigest()[:16]
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
def _raw_message(fact: dict[str, Any]) -> str:
|
|
220
|
-
raw = fact.get("raw")
|
|
221
|
-
if isinstance(raw, dict):
|
|
222
|
-
return str(raw.get("message") or "")
|
|
223
|
-
return ""
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
def _marker_set(marker_store: Any, name: str, value: Any) -> bool:
|
|
227
|
-
if marker_store is None:
|
|
228
|
-
return False
|
|
229
|
-
if isinstance(marker_store, dict):
|
|
230
|
-
marker_store[name] = value
|
|
231
|
-
return True
|
|
232
|
-
setter = getattr(marker_store, "set", None) or getattr(marker_store, "write", None)
|
|
233
|
-
if callable(setter):
|
|
234
|
-
try:
|
|
235
|
-
setter(name, value)
|
|
236
|
-
return True
|
|
237
|
-
except Exception:
|
|
238
|
-
return False
|
|
239
|
-
return False
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
def _emit(event_sink: Any, name: str, **fields: Any) -> None:
|
|
243
|
-
if event_sink is None:
|
|
244
|
-
return
|
|
245
|
-
try:
|
|
246
|
-
event_sink(name, fields)
|
|
247
|
-
except TypeError:
|
|
248
|
-
try:
|
|
249
|
-
event_sink({"event": name, **fields})
|
|
250
|
-
except Exception:
|
|
251
|
-
pass
|
|
252
|
-
except Exception:
|
|
253
|
-
pass
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
from team_agent.approvals.constants import (
|
|
4
|
-
INTERNAL_MCP_APPROVAL_CHOICE,
|
|
5
|
-
INTERNAL_MCP_AUTO_APPROVE_TOOLS,
|
|
6
|
-
STARTUP_PROMPT_RUNTIME_CHECK_LIMIT,
|
|
7
|
-
)
|
|
8
|
-
from team_agent.approvals.parsing import (
|
|
9
|
-
APPROVAL_CHOICE_RE,
|
|
10
|
-
active_approval_choice_index,
|
|
11
|
-
active_approval_control_index,
|
|
12
|
-
approval_choice_keys,
|
|
13
|
-
approval_prompt_fingerprint,
|
|
14
|
-
capture_has_approval_prompt,
|
|
15
|
-
capture_has_team_orchestrator_mcp_prompt,
|
|
16
|
-
choose_internal_mcp_approval_choice,
|
|
17
|
-
extract_approval_choices,
|
|
18
|
-
extract_approval_prompt,
|
|
19
|
-
extract_command_approval_subject,
|
|
20
|
-
is_approval_control_line,
|
|
21
|
-
line_is_approval_choice,
|
|
22
|
-
)
|
|
23
|
-
from team_agent.approvals.runtime_prompts import (
|
|
24
|
-
handle_internal_mcp_approval_prompt,
|
|
25
|
-
handle_provider_runtime_prompts,
|
|
26
|
-
handle_provider_startup_prompts,
|
|
27
|
-
submit_internal_mcp_approval,
|
|
28
|
-
)
|
|
29
|
-
from team_agent.approvals.status import (
|
|
30
|
-
age_text,
|
|
31
|
-
agent_health_status,
|
|
32
|
-
current_task_for_agent,
|
|
33
|
-
detect_provider_status,
|
|
34
|
-
refresh_agent_runtime_statuses,
|
|
35
|
-
sync_agent_health,
|
|
36
|
-
)
|
|
37
|
-
|
|
38
|
-
__all__ = [
|
|
39
|
-
"APPROVAL_CHOICE_RE",
|
|
40
|
-
"INTERNAL_MCP_APPROVAL_CHOICE",
|
|
41
|
-
"INTERNAL_MCP_AUTO_APPROVE_TOOLS",
|
|
42
|
-
"STARTUP_PROMPT_RUNTIME_CHECK_LIMIT",
|
|
43
|
-
"active_approval_choice_index",
|
|
44
|
-
"active_approval_control_index",
|
|
45
|
-
"age_text",
|
|
46
|
-
"agent_health_status",
|
|
47
|
-
"approval_choice_keys",
|
|
48
|
-
"approval_prompt_fingerprint",
|
|
49
|
-
"capture_has_approval_prompt",
|
|
50
|
-
"capture_has_team_orchestrator_mcp_prompt",
|
|
51
|
-
"choose_internal_mcp_approval_choice",
|
|
52
|
-
"current_task_for_agent",
|
|
53
|
-
"detect_provider_status",
|
|
54
|
-
"extract_approval_choices",
|
|
55
|
-
"extract_approval_prompt",
|
|
56
|
-
"extract_command_approval_subject",
|
|
57
|
-
"handle_internal_mcp_approval_prompt",
|
|
58
|
-
"handle_provider_runtime_prompts",
|
|
59
|
-
"handle_provider_startup_prompts",
|
|
60
|
-
"is_approval_control_line",
|
|
61
|
-
"line_is_approval_choice",
|
|
62
|
-
"refresh_agent_runtime_statuses",
|
|
63
|
-
"submit_internal_mcp_approval",
|
|
64
|
-
"sync_agent_health",
|
|
65
|
-
]
|
|
@@ -1,176 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
import hashlib
|
|
4
|
-
import json
|
|
5
|
-
import re
|
|
6
|
-
from typing import Any
|
|
7
|
-
|
|
8
|
-
from team_agent.approvals.constants import INTERNAL_MCP_APPROVAL_CHOICE
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
APPROVAL_CHOICE_RE = re.compile(r"(?:[›❯>]\s*)?(\d+)\.\s+(.+?)(?:\s{2,}.+)?$")
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
def capture_has_approval_prompt(text: str) -> bool:
|
|
15
|
-
return extract_approval_prompt("_", text) is not None
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
def extract_approval_prompt(agent_id: str, text: str) -> dict[str, Any] | None:
|
|
19
|
-
lines = text.splitlines()
|
|
20
|
-
control_index = active_approval_control_index(lines)
|
|
21
|
-
if control_index is None:
|
|
22
|
-
return None
|
|
23
|
-
for index in range(control_index, -1, -1):
|
|
24
|
-
line = lines[index]
|
|
25
|
-
if "Allow the team_orchestrator MCP server to run tool" not in line:
|
|
26
|
-
continue
|
|
27
|
-
tool_match = re.search(r'run tool "([^"]+)"', line)
|
|
28
|
-
return {
|
|
29
|
-
"agent_id": agent_id,
|
|
30
|
-
"state": "waiting_approval",
|
|
31
|
-
"kind": "mcp_tool",
|
|
32
|
-
"tool": tool_match.group(1) if tool_match else None,
|
|
33
|
-
"prompt": line.strip(),
|
|
34
|
-
"choices": extract_approval_choices(lines[index : control_index + 1]),
|
|
35
|
-
}
|
|
36
|
-
for index in range(control_index, -1, -1):
|
|
37
|
-
line = lines[index]
|
|
38
|
-
if line_is_approval_choice(line):
|
|
39
|
-
continue
|
|
40
|
-
tool_match = re.search(r"\bteam_orchestrator\s*[-.]\s*([A-Za-z_][A-Za-z0-9_]*)\b", line)
|
|
41
|
-
if not tool_match:
|
|
42
|
-
continue
|
|
43
|
-
return {
|
|
44
|
-
"agent_id": agent_id,
|
|
45
|
-
"state": "waiting_approval",
|
|
46
|
-
"kind": "mcp_tool",
|
|
47
|
-
"tool": tool_match.group(1),
|
|
48
|
-
"prompt": f"team_orchestrator - {tool_match.group(1)}",
|
|
49
|
-
"choices": extract_approval_choices(lines[index : control_index + 1]),
|
|
50
|
-
}
|
|
51
|
-
for index in range(control_index, -1, -1):
|
|
52
|
-
line = lines[index]
|
|
53
|
-
if "Would you like to run the following command" not in line:
|
|
54
|
-
continue
|
|
55
|
-
return {
|
|
56
|
-
"agent_id": agent_id,
|
|
57
|
-
"state": "waiting_approval",
|
|
58
|
-
"kind": "command",
|
|
59
|
-
"command": extract_command_approval_subject(lines[: control_index + 1], index),
|
|
60
|
-
"prompt": line.strip(),
|
|
61
|
-
"choices": extract_approval_choices(lines[index : control_index + 1]),
|
|
62
|
-
}
|
|
63
|
-
return {
|
|
64
|
-
"agent_id": agent_id,
|
|
65
|
-
"state": "waiting_approval",
|
|
66
|
-
"kind": "unknown",
|
|
67
|
-
"prompt": "approval prompt detected",
|
|
68
|
-
"choices": extract_approval_choices(lines[: control_index + 1]),
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
def active_approval_control_index(lines: list[str]) -> int | None:
|
|
73
|
-
control_indices = [
|
|
74
|
-
index
|
|
75
|
-
for index, line in enumerate(lines)
|
|
76
|
-
if is_approval_control_line(line)
|
|
77
|
-
]
|
|
78
|
-
if not control_indices:
|
|
79
|
-
return None
|
|
80
|
-
control_index = control_indices[-1]
|
|
81
|
-
if any(line.strip() for line in lines[control_index + 1 :]):
|
|
82
|
-
return None
|
|
83
|
-
return control_index
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
def is_approval_control_line(line: str) -> bool:
|
|
87
|
-
normalized = line.lower()
|
|
88
|
-
return "enter to submit | esc to cancel" in normalized or ("esc to cancel" in normalized and "tab to amend" in normalized)
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
def extract_approval_choices(lines: list[str]) -> list[str]:
|
|
92
|
-
choices: list[str] = []
|
|
93
|
-
for line in lines:
|
|
94
|
-
stripped = line.strip()
|
|
95
|
-
match = APPROVAL_CHOICE_RE.match(stripped)
|
|
96
|
-
if not match:
|
|
97
|
-
continue
|
|
98
|
-
label = match.group(2).strip()
|
|
99
|
-
if label and label not in choices:
|
|
100
|
-
choices.append(label)
|
|
101
|
-
return choices
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
def line_is_approval_choice(line: str) -> bool:
|
|
105
|
-
return APPROVAL_CHOICE_RE.match(line.strip()) is not None
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
def extract_command_approval_subject(lines: list[str], prompt_index: int) -> str | None:
|
|
109
|
-
for line in reversed(lines[:prompt_index]):
|
|
110
|
-
stripped = line.strip()
|
|
111
|
-
if stripped.startswith("Bash(") or stripped.startswith("Shell("):
|
|
112
|
-
return stripped[:200]
|
|
113
|
-
for line in lines[prompt_index + 1 : prompt_index + 8]:
|
|
114
|
-
stripped = line.strip()
|
|
115
|
-
if stripped.startswith("Bash(") or stripped.startswith("Shell("):
|
|
116
|
-
return stripped[:200]
|
|
117
|
-
return None
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
def active_approval_choice_index(text: str) -> int | None:
|
|
121
|
-
for line in text.splitlines():
|
|
122
|
-
stripped = line.strip()
|
|
123
|
-
if not (stripped.startswith("›") or stripped.startswith("❯") or stripped.startswith(">")):
|
|
124
|
-
continue
|
|
125
|
-
match = re.match(r"[›❯>]\s*(\d+)\.", stripped)
|
|
126
|
-
if match:
|
|
127
|
-
return int(match.group(1)) - 1
|
|
128
|
-
return None
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
def capture_has_team_orchestrator_mcp_prompt(text: str) -> bool:
|
|
132
|
-
return (
|
|
133
|
-
"Allow the team_orchestrator MCP server to run tool" in text
|
|
134
|
-
or re.search(r"\bteam_orchestrator\s*[-.]\s*[A-Za-z_][A-Za-z0-9_]*\b", text) is not None
|
|
135
|
-
)
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
def approval_prompt_fingerprint(prompt: dict[str, Any]) -> str:
|
|
139
|
-
data = {
|
|
140
|
-
"kind": prompt.get("kind"),
|
|
141
|
-
"tool": prompt.get("tool"),
|
|
142
|
-
"prompt": prompt.get("prompt"),
|
|
143
|
-
"choices": prompt.get("choices") or [],
|
|
144
|
-
}
|
|
145
|
-
return hashlib.sha256(json.dumps(data, sort_keys=True, ensure_ascii=False).encode("utf-8")).hexdigest()[:16]
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
def choose_internal_mcp_approval_choice(prompt: dict[str, Any]) -> str:
|
|
149
|
-
choices = prompt.get("choices") or []
|
|
150
|
-
if INTERNAL_MCP_APPROVAL_CHOICE in choices:
|
|
151
|
-
return INTERNAL_MCP_APPROVAL_CHOICE
|
|
152
|
-
for choice in choices:
|
|
153
|
-
if str(choice).startswith("Yes, and don't ask again"):
|
|
154
|
-
return str(choice)
|
|
155
|
-
if "Allow" in choices:
|
|
156
|
-
return "Allow"
|
|
157
|
-
if "Yes" in choices:
|
|
158
|
-
return "Yes"
|
|
159
|
-
return INTERNAL_MCP_APPROVAL_CHOICE
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
def approval_choice_keys(prompt: dict[str, Any], capture_text: str, choice: str) -> list[str]:
|
|
163
|
-
choices = prompt.get("choices") or []
|
|
164
|
-
try:
|
|
165
|
-
target_index = choices.index(choice)
|
|
166
|
-
except ValueError:
|
|
167
|
-
return ["Down", "Enter"]
|
|
168
|
-
active_index = active_approval_choice_index(capture_text)
|
|
169
|
-
if active_index is None:
|
|
170
|
-
return [str(target_index + 1), "Enter"]
|
|
171
|
-
delta = target_index - active_index
|
|
172
|
-
if delta > 0:
|
|
173
|
-
return ["Down"] * delta + ["Enter"]
|
|
174
|
-
if delta < 0:
|
|
175
|
-
return ["Up"] * abs(delta) + ["Enter"]
|
|
176
|
-
return ["Enter"]
|