@team-agent/installer 0.1.10 → 0.2.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/crates/team-agent-core/src/lib.rs +50 -5
- package/package.json +1 -1
- package/schemas/team.schema.json +1 -0
- package/skills/team-agent/SKILL.md +1 -1
- package/src/team_agent/approvals/__init__.py +65 -0
- package/src/team_agent/approvals/constants.py +6 -0
- package/src/team_agent/approvals/parsing.py +176 -0
- package/src/team_agent/approvals/runtime_prompts.py +171 -0
- package/src/team_agent/approvals/status.py +165 -0
- package/src/team_agent/cli/__init__.py +135 -0
- package/src/team_agent/cli/commands.py +335 -0
- package/src/team_agent/cli/e2e.py +202 -0
- package/src/team_agent/cli/helpers.py +137 -0
- package/src/team_agent/cli/parser.py +470 -0
- package/src/team_agent/compiler.py +98 -33
- package/src/team_agent/coordinator/__init__.py +53 -0
- package/src/team_agent/{coordinator.py → coordinator/__main__.py} +3 -1
- package/src/team_agent/coordinator/lifecycle.py +319 -0
- package/src/team_agent/coordinator/metadata.py +61 -0
- package/src/team_agent/coordinator/paths.py +17 -0
- package/src/team_agent/diagnose/__init__.py +48 -0
- package/src/team_agent/diagnose/checks.py +101 -0
- package/src/team_agent/diagnose/health.py +241 -0
- package/src/team_agent/diagnose/preflight.py +194 -0
- package/src/team_agent/diagnose/quick_start.py +233 -0
- package/src/team_agent/display/__init__.py +61 -0
- package/src/team_agent/display/close.py +147 -0
- package/src/team_agent/display/ghostty.py +77 -0
- package/src/team_agent/display/worker_window.py +110 -0
- package/src/team_agent/display/workspace.py +473 -0
- package/src/team_agent/launch/__init__.py +41 -0
- package/src/team_agent/launch/bootstrap.py +85 -0
- package/src/team_agent/launch/config.py +106 -0
- package/src/team_agent/launch/core.py +291 -0
- package/src/team_agent/launch/requirements.py +57 -0
- package/src/team_agent/leader/__init__.py +320 -0
- package/src/team_agent/lifecycle/__init__.py +5 -0
- package/src/team_agent/lifecycle/agents.py +226 -0
- package/src/team_agent/lifecycle/operations.py +321 -0
- package/src/team_agent/lifecycle/start.py +360 -0
- package/src/team_agent/mcp_server/__init__.py +42 -0
- package/src/team_agent/mcp_server/__main__.py +7 -0
- package/src/team_agent/mcp_server/contracts.py +148 -0
- package/src/team_agent/mcp_server/normalize.py +257 -0
- package/src/team_agent/mcp_server/server.py +150 -0
- package/src/team_agent/mcp_server/tools.py +205 -0
- package/src/team_agent/message_store/__init__.py +23 -0
- package/src/team_agent/message_store/agent_health.py +109 -0
- package/src/team_agent/{message_store.py → message_store/core.py} +188 -245
- package/src/team_agent/message_store/result_watchers.py +102 -0
- package/src/team_agent/message_store/schema.py +266 -0
- package/src/team_agent/messaging/__init__.py +1 -0
- package/src/team_agent/messaging/activity_detector.py +190 -0
- package/src/team_agent/messaging/delivery.py +128 -0
- package/src/team_agent/messaging/deps.py +263 -0
- package/src/team_agent/messaging/idle_alerts.py +217 -0
- package/src/team_agent/messaging/internal_delivery.py +46 -0
- package/src/team_agent/messaging/leader.py +317 -0
- package/src/team_agent/messaging/leader_panes.py +343 -0
- package/src/team_agent/messaging/result_delivery.py +300 -0
- package/src/team_agent/messaging/results.py +456 -0
- package/src/team_agent/messaging/scheduler.py +418 -0
- package/src/team_agent/messaging/send.py +493 -0
- package/src/team_agent/messaging/tmux_io.py +337 -0
- package/src/team_agent/messaging/tmux_prompt.py +229 -0
- package/src/team_agent/orchestrator/__init__.py +376 -0
- package/src/team_agent/orchestrator/plan.py +122 -0
- package/src/team_agent/orchestrator/state.py +128 -0
- package/src/team_agent/profiles/__init__.py +82 -0
- package/src/team_agent/profiles/constants.py +19 -0
- package/src/team_agent/profiles/core.py +407 -0
- package/src/team_agent/profiles/helpers.py +69 -0
- package/src/team_agent/profiles/provider_env.py +188 -0
- package/src/team_agent/profiles/smoke.py +201 -0
- package/src/team_agent/provider_cli/__init__.py +43 -0
- package/src/team_agent/provider_cli/adapter.py +167 -0
- package/src/team_agent/provider_cli/base.py +48 -0
- package/src/team_agent/provider_cli/claude.py +457 -0
- package/src/team_agent/provider_cli/codex.py +319 -0
- package/src/team_agent/provider_cli/copilot.py +8 -0
- package/src/team_agent/provider_cli/fake.py +39 -0
- package/src/team_agent/provider_cli/gemini.py +95 -0
- package/src/team_agent/provider_cli/opencode.py +8 -0
- package/src/team_agent/provider_cli/prompt.py +62 -0
- package/src/team_agent/provider_cli/registry.py +18 -0
- package/src/team_agent/provider_cli/unsupported.py +32 -0
- package/src/team_agent/providers.py +67 -949
- package/src/team_agent/quality_gates.py +104 -0
- package/src/team_agent/restart/__init__.py +34 -0
- package/src/team_agent/restart/orchestration.py +328 -0
- package/src/team_agent/restart/selection.py +89 -0
- package/src/team_agent/restart/snapshot.py +70 -0
- package/src/team_agent/runtime.py +802 -5740
- package/src/team_agent/rust_core.py +22 -5
- package/src/team_agent/sessions/__init__.py +25 -0
- package/src/team_agent/sessions/capture.py +93 -0
- package/src/team_agent/sessions/inventory.py +44 -0
- package/src/team_agent/sessions/resume.py +135 -0
- package/src/team_agent/spec.py +3 -1
- package/src/team_agent/state.py +204 -4
- package/src/team_agent/status/__init__.py +63 -0
- package/src/team_agent/status/approvals.py +52 -0
- package/src/team_agent/status/compact.py +158 -0
- package/src/team_agent/status/constants.py +18 -0
- package/src/team_agent/status/inbox.py +28 -0
- package/src/team_agent/status/peek.py +117 -0
- package/src/team_agent/status/queries.py +168 -0
- package/src/team_agent/terminal.py +57 -0
- package/src/team_agent/cli.py +0 -857
- package/src/team_agent/mcp_server.py +0 -579
- package/src/team_agent/profiles.py +0 -882
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import copy
|
|
4
|
+
import json
|
|
5
|
+
import os
|
|
6
|
+
import re
|
|
7
|
+
import subprocess
|
|
8
|
+
import time
|
|
9
|
+
from datetime import datetime, timedelta, timezone
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
from team_agent import runtime as _runtime
|
|
13
|
+
from team_agent.errors import RuntimeError, ValidationError
|
|
14
|
+
from team_agent.events import EventLog
|
|
15
|
+
from team_agent.message_store import MessageStore
|
|
16
|
+
from team_agent.paths import runtime_dir
|
|
17
|
+
from team_agent.permissions import missing_tools
|
|
18
|
+
from team_agent.routing import route_task
|
|
19
|
+
from team_agent.spec import load_spec, validate_result_envelope
|
|
20
|
+
from team_agent.state import (
|
|
21
|
+
ambiguous_team_target_result,
|
|
22
|
+
check_team_owner,
|
|
23
|
+
load_runtime_state,
|
|
24
|
+
save_runtime_state,
|
|
25
|
+
save_team_scoped_state,
|
|
26
|
+
select_runtime_state,
|
|
27
|
+
team_state_key,
|
|
28
|
+
write_team_state,
|
|
29
|
+
)
|
|
30
|
+
from team_agent.task_graph import update_task_status
|
|
31
|
+
|
|
32
|
+
# Explicit runtime dependency surface for messaging extraction. Wrappers keep
|
|
33
|
+
# runtime monkeypatch points stable while avoiding module-wide globals sync.
|
|
34
|
+
_RUNTIME_CONSTANTS = (
|
|
35
|
+
"DELIVERY_CAPTURE_LINES",
|
|
36
|
+
"PASTED_CONTENT_PROMPT_RE",
|
|
37
|
+
"TMUX_PANE_FORMAT",
|
|
38
|
+
"TMUX_PASTE_BYTES_PER_SECOND",
|
|
39
|
+
"TMUX_PASTE_MAX_READY_TIMEOUT",
|
|
40
|
+
"TMUX_PASTE_MIN_READY_TIMEOUT",
|
|
41
|
+
"TMUX_STDIN_BUFFER_THRESHOLD",
|
|
42
|
+
"TMUX_SUBMIT_BYTES_PER_SECOND",
|
|
43
|
+
"TMUX_SUBMIT_MAX_SETTLE_TIMEOUT",
|
|
44
|
+
"TMUX_SUBMIT_MIN_SETTLE_TIMEOUT",
|
|
45
|
+
)
|
|
46
|
+
_RUNTIME_PATCH_POINTS = (
|
|
47
|
+
"_capture_has_pasted_content_prompt",
|
|
48
|
+
"_capture_missing_sessions",
|
|
49
|
+
"_capture_tmux_pane_text",
|
|
50
|
+
"_choose_leader_submit_key",
|
|
51
|
+
"_current_task_for_agent",
|
|
52
|
+
"_deliver_pending_message",
|
|
53
|
+
"_deliver_pending_messages",
|
|
54
|
+
"_find_agent",
|
|
55
|
+
"_find_task",
|
|
56
|
+
"_find_task_or_none",
|
|
57
|
+
"_format_team_agent_message",
|
|
58
|
+
"_handle_provider_runtime_prompts",
|
|
59
|
+
"_handle_provider_startup_prompts",
|
|
60
|
+
"_infer_active_tmux_pane",
|
|
61
|
+
"_infer_workspace_tmux_pane",
|
|
62
|
+
"_is_leader_sender",
|
|
63
|
+
"_is_leader_target",
|
|
64
|
+
"_is_message_scoped_result",
|
|
65
|
+
"_is_runtime_team_agent",
|
|
66
|
+
"_leader_id",
|
|
67
|
+
"_leader_receiver_is_direct",
|
|
68
|
+
"_message_by_id",
|
|
69
|
+
"_message_payload",
|
|
70
|
+
"_mirror_peer_message_to_leader",
|
|
71
|
+
"_notify_leader_of_report_result",
|
|
72
|
+
"_rediscover_leader_receiver",
|
|
73
|
+
"_refresh_agent_runtime_statuses",
|
|
74
|
+
"_result_status_to_task_status",
|
|
75
|
+
"_runtime_lock",
|
|
76
|
+
"_runtime_team_agent_ids",
|
|
77
|
+
"_send_to_leader_receiver",
|
|
78
|
+
"_submit_worker_prompt",
|
|
79
|
+
"_tmux_current_client_pane_info",
|
|
80
|
+
"_tmux_inject_text",
|
|
81
|
+
"_tmux_list_panes",
|
|
82
|
+
"_tmux_load_buffer_stdin",
|
|
83
|
+
"_tmux_pane_info",
|
|
84
|
+
"_tmux_paste_ready_timeout",
|
|
85
|
+
"_tmux_set_buffer_text",
|
|
86
|
+
"_tmux_submit_settle_timeout",
|
|
87
|
+
"_tmux_window_exists",
|
|
88
|
+
"_validate_leader_receiver",
|
|
89
|
+
"_wait_for_message_ready",
|
|
90
|
+
"_wait_for_worker_message_ready",
|
|
91
|
+
"core_list_targets",
|
|
92
|
+
"core_render_message",
|
|
93
|
+
"run_cmd",
|
|
94
|
+
"send_message",
|
|
95
|
+
"start_coordinator",
|
|
96
|
+
)
|
|
97
|
+
for _name in (*_RUNTIME_CONSTANTS, *_RUNTIME_PATCH_POINTS):
|
|
98
|
+
if not hasattr(_runtime, _name):
|
|
99
|
+
raise ImportError(f"team_agent.runtime missing messaging dependency: {_name}")
|
|
100
|
+
|
|
101
|
+
DELIVERY_CAPTURE_LINES = _runtime.DELIVERY_CAPTURE_LINES
|
|
102
|
+
PASTED_CONTENT_PROMPT_RE = _runtime.PASTED_CONTENT_PROMPT_RE
|
|
103
|
+
TMUX_PANE_FORMAT = _runtime.TMUX_PANE_FORMAT
|
|
104
|
+
TMUX_PASTE_BYTES_PER_SECOND = _runtime.TMUX_PASTE_BYTES_PER_SECOND
|
|
105
|
+
TMUX_PASTE_MAX_READY_TIMEOUT = _runtime.TMUX_PASTE_MAX_READY_TIMEOUT
|
|
106
|
+
TMUX_PASTE_MIN_READY_TIMEOUT = _runtime.TMUX_PASTE_MIN_READY_TIMEOUT
|
|
107
|
+
TMUX_STDIN_BUFFER_THRESHOLD = _runtime.TMUX_STDIN_BUFFER_THRESHOLD
|
|
108
|
+
TMUX_SUBMIT_BYTES_PER_SECOND = _runtime.TMUX_SUBMIT_BYTES_PER_SECOND
|
|
109
|
+
TMUX_SUBMIT_MAX_SETTLE_TIMEOUT = _runtime.TMUX_SUBMIT_MAX_SETTLE_TIMEOUT
|
|
110
|
+
TMUX_SUBMIT_MIN_SETTLE_TIMEOUT = _runtime.TMUX_SUBMIT_MIN_SETTLE_TIMEOUT
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def _runtime_symbol(name: str) -> Any:
|
|
114
|
+
return getattr(_runtime, name)
|
|
115
|
+
|
|
116
|
+
def _capture_has_pasted_content_prompt(*args: Any, **kwargs: Any) -> Any:
|
|
117
|
+
return _runtime_symbol("_capture_has_pasted_content_prompt")(*args, **kwargs)
|
|
118
|
+
|
|
119
|
+
def _capture_missing_sessions(*args: Any, **kwargs: Any) -> Any:
|
|
120
|
+
return _runtime_symbol("_capture_missing_sessions")(*args, **kwargs)
|
|
121
|
+
|
|
122
|
+
def _capture_tmux_pane_text(*args: Any, **kwargs: Any) -> Any:
|
|
123
|
+
return _runtime_symbol("_capture_tmux_pane_text")(*args, **kwargs)
|
|
124
|
+
|
|
125
|
+
def _choose_leader_submit_key(*args: Any, **kwargs: Any) -> Any:
|
|
126
|
+
return _runtime_symbol("_choose_leader_submit_key")(*args, **kwargs)
|
|
127
|
+
|
|
128
|
+
def _current_task_for_agent(*args: Any, **kwargs: Any) -> Any:
|
|
129
|
+
return _runtime_symbol("_current_task_for_agent")(*args, **kwargs)
|
|
130
|
+
|
|
131
|
+
def _deliver_pending_message(*args: Any, **kwargs: Any) -> Any:
|
|
132
|
+
return _runtime_symbol("_deliver_pending_message")(*args, **kwargs)
|
|
133
|
+
|
|
134
|
+
def _deliver_pending_messages(*args: Any, **kwargs: Any) -> Any:
|
|
135
|
+
return _runtime_symbol("_deliver_pending_messages")(*args, **kwargs)
|
|
136
|
+
|
|
137
|
+
def _find_agent(*args: Any, **kwargs: Any) -> Any:
|
|
138
|
+
return _runtime_symbol("_find_agent")(*args, **kwargs)
|
|
139
|
+
|
|
140
|
+
def _find_task(*args: Any, **kwargs: Any) -> Any:
|
|
141
|
+
return _runtime_symbol("_find_task")(*args, **kwargs)
|
|
142
|
+
|
|
143
|
+
def _find_task_or_none(*args: Any, **kwargs: Any) -> Any:
|
|
144
|
+
return _runtime_symbol("_find_task_or_none")(*args, **kwargs)
|
|
145
|
+
|
|
146
|
+
def _format_team_agent_message(*args: Any, **kwargs: Any) -> Any:
|
|
147
|
+
return _runtime_symbol("_format_team_agent_message")(*args, **kwargs)
|
|
148
|
+
|
|
149
|
+
def _handle_provider_runtime_prompts(*args: Any, **kwargs: Any) -> Any:
|
|
150
|
+
return _runtime_symbol("_handle_provider_runtime_prompts")(*args, **kwargs)
|
|
151
|
+
|
|
152
|
+
def _handle_provider_startup_prompts(*args: Any, **kwargs: Any) -> Any:
|
|
153
|
+
return _runtime_symbol("_handle_provider_startup_prompts")(*args, **kwargs)
|
|
154
|
+
|
|
155
|
+
def _is_leader_sender(*args: Any, **kwargs: Any) -> Any:
|
|
156
|
+
return _runtime_symbol("_is_leader_sender")(*args, **kwargs)
|
|
157
|
+
|
|
158
|
+
def _is_leader_target(*args: Any, **kwargs: Any) -> Any:
|
|
159
|
+
return _runtime_symbol("_is_leader_target")(*args, **kwargs)
|
|
160
|
+
|
|
161
|
+
def _is_message_scoped_result(*args: Any, **kwargs: Any) -> Any:
|
|
162
|
+
return _runtime_symbol("_is_message_scoped_result")(*args, **kwargs)
|
|
163
|
+
|
|
164
|
+
def _is_runtime_team_agent(*args: Any, **kwargs: Any) -> Any:
|
|
165
|
+
return _runtime_symbol("_is_runtime_team_agent")(*args, **kwargs)
|
|
166
|
+
|
|
167
|
+
def _leader_id(*args: Any, **kwargs: Any) -> Any:
|
|
168
|
+
return _runtime_symbol("_leader_id")(*args, **kwargs)
|
|
169
|
+
|
|
170
|
+
def _leader_receiver_is_direct(*args: Any, **kwargs: Any) -> Any:
|
|
171
|
+
return _runtime_symbol("_leader_receiver_is_direct")(*args, **kwargs)
|
|
172
|
+
|
|
173
|
+
def _message_by_id(*args: Any, **kwargs: Any) -> Any:
|
|
174
|
+
return _runtime_symbol("_message_by_id")(*args, **kwargs)
|
|
175
|
+
|
|
176
|
+
def _message_payload(*args: Any, **kwargs: Any) -> Any:
|
|
177
|
+
return _runtime_symbol("_message_payload")(*args, **kwargs)
|
|
178
|
+
|
|
179
|
+
def _mirror_peer_message_to_leader(*args: Any, **kwargs: Any) -> Any:
|
|
180
|
+
return _runtime_symbol("_mirror_peer_message_to_leader")(*args, **kwargs)
|
|
181
|
+
|
|
182
|
+
def _notify_leader_of_report_result(*args: Any, **kwargs: Any) -> Any:
|
|
183
|
+
return _runtime_symbol("_notify_leader_of_report_result")(*args, **kwargs)
|
|
184
|
+
|
|
185
|
+
def _rediscover_leader_receiver(*args: Any, **kwargs: Any) -> Any:
|
|
186
|
+
return _runtime_symbol("_rediscover_leader_receiver")(*args, **kwargs)
|
|
187
|
+
|
|
188
|
+
def _refresh_agent_runtime_statuses(*args: Any, **kwargs: Any) -> Any:
|
|
189
|
+
return _runtime_symbol("_refresh_agent_runtime_statuses")(*args, **kwargs)
|
|
190
|
+
|
|
191
|
+
def _result_status_to_task_status(*args: Any, **kwargs: Any) -> Any:
|
|
192
|
+
return _runtime_symbol("_result_status_to_task_status")(*args, **kwargs)
|
|
193
|
+
|
|
194
|
+
def _runtime_lock(*args: Any, **kwargs: Any) -> Any:
|
|
195
|
+
return _runtime_symbol("_runtime_lock")(*args, **kwargs)
|
|
196
|
+
|
|
197
|
+
def _runtime_team_agent_ids(*args: Any, **kwargs: Any) -> Any:
|
|
198
|
+
return _runtime_symbol("_runtime_team_agent_ids")(*args, **kwargs)
|
|
199
|
+
|
|
200
|
+
def _send_to_leader_receiver(*args: Any, **kwargs: Any) -> Any:
|
|
201
|
+
return _runtime_symbol("_send_to_leader_receiver")(*args, **kwargs)
|
|
202
|
+
|
|
203
|
+
def _submit_worker_prompt(*args: Any, **kwargs: Any) -> Any:
|
|
204
|
+
return _runtime_symbol("_submit_worker_prompt")(*args, **kwargs)
|
|
205
|
+
|
|
206
|
+
def _tmux_inject_text(*args: Any, **kwargs: Any) -> Any:
|
|
207
|
+
return _runtime_symbol("_tmux_inject_text")(*args, **kwargs)
|
|
208
|
+
|
|
209
|
+
def _tmux_current_client_pane_info(*args: Any, **kwargs: Any) -> Any:
|
|
210
|
+
return _runtime_symbol("_tmux_current_client_pane_info")(*args, **kwargs)
|
|
211
|
+
|
|
212
|
+
def _tmux_list_panes(*args: Any, **kwargs: Any) -> Any:
|
|
213
|
+
return _runtime_symbol("_tmux_list_panes")(*args, **kwargs)
|
|
214
|
+
|
|
215
|
+
def _infer_active_tmux_pane(*args: Any, **kwargs: Any) -> Any:
|
|
216
|
+
return _runtime_symbol("_infer_active_tmux_pane")(*args, **kwargs)
|
|
217
|
+
|
|
218
|
+
def _tmux_pane_info(*args: Any, **kwargs: Any) -> Any:
|
|
219
|
+
return _runtime_symbol("_tmux_pane_info")(*args, **kwargs)
|
|
220
|
+
|
|
221
|
+
def _infer_workspace_tmux_pane(*args: Any, **kwargs: Any) -> Any:
|
|
222
|
+
return _runtime_symbol("_infer_workspace_tmux_pane")(*args, **kwargs)
|
|
223
|
+
|
|
224
|
+
def _tmux_load_buffer_stdin(*args: Any, **kwargs: Any) -> Any:
|
|
225
|
+
return _runtime_symbol("_tmux_load_buffer_stdin")(*args, **kwargs)
|
|
226
|
+
|
|
227
|
+
def _tmux_paste_ready_timeout(*args: Any, **kwargs: Any) -> Any:
|
|
228
|
+
return _runtime_symbol("_tmux_paste_ready_timeout")(*args, **kwargs)
|
|
229
|
+
|
|
230
|
+
def _tmux_set_buffer_text(*args: Any, **kwargs: Any) -> Any:
|
|
231
|
+
return _runtime_symbol("_tmux_set_buffer_text")(*args, **kwargs)
|
|
232
|
+
|
|
233
|
+
def _tmux_submit_settle_timeout(*args: Any, **kwargs: Any) -> Any:
|
|
234
|
+
return _runtime_symbol("_tmux_submit_settle_timeout")(*args, **kwargs)
|
|
235
|
+
|
|
236
|
+
def _tmux_window_exists(*args: Any, **kwargs: Any) -> Any:
|
|
237
|
+
return _runtime_symbol("_tmux_window_exists")(*args, **kwargs)
|
|
238
|
+
|
|
239
|
+
def _validate_leader_receiver(*args: Any, **kwargs: Any) -> Any:
|
|
240
|
+
return _runtime_symbol("_validate_leader_receiver")(*args, **kwargs)
|
|
241
|
+
|
|
242
|
+
def _wait_for_message_ready(*args: Any, **kwargs: Any) -> Any:
|
|
243
|
+
return _runtime_symbol("_wait_for_message_ready")(*args, **kwargs)
|
|
244
|
+
|
|
245
|
+
def _wait_for_worker_message_ready(*args: Any, **kwargs: Any) -> Any:
|
|
246
|
+
return _runtime_symbol("_wait_for_worker_message_ready")(*args, **kwargs)
|
|
247
|
+
|
|
248
|
+
def run_cmd(*args: Any, **kwargs: Any) -> Any:
|
|
249
|
+
return _runtime_symbol("run_cmd")(*args, **kwargs)
|
|
250
|
+
|
|
251
|
+
def core_list_targets(*args: Any, **kwargs: Any) -> Any:
|
|
252
|
+
return _runtime_symbol("core_list_targets")(*args, **kwargs)
|
|
253
|
+
|
|
254
|
+
def core_render_message(*args: Any, **kwargs: Any) -> Any:
|
|
255
|
+
return _runtime_symbol("core_render_message")(*args, **kwargs)
|
|
256
|
+
|
|
257
|
+
def send_message(*args: Any, **kwargs: Any) -> Any:
|
|
258
|
+
return _runtime_symbol("send_message")(*args, **kwargs)
|
|
259
|
+
|
|
260
|
+
def start_coordinator(*args: Any, **kwargs: Any) -> Any:
|
|
261
|
+
return _runtime_symbol("start_coordinator")(*args, **kwargs)
|
|
262
|
+
|
|
263
|
+
__all__ = ['DELIVERY_CAPTURE_LINES', 'EventLog', 'MessageStore', 'PASTED_CONTENT_PROMPT_RE', 'RuntimeError', 'TMUX_PANE_FORMAT', 'TMUX_PASTE_BYTES_PER_SECOND', 'TMUX_PASTE_MAX_READY_TIMEOUT', 'TMUX_PASTE_MIN_READY_TIMEOUT', 'TMUX_STDIN_BUFFER_THRESHOLD', 'TMUX_SUBMIT_BYTES_PER_SECOND', 'TMUX_SUBMIT_MAX_SETTLE_TIMEOUT', 'TMUX_SUBMIT_MIN_SETTLE_TIMEOUT', 'ValidationError', '_capture_has_pasted_content_prompt', '_capture_missing_sessions', '_capture_tmux_pane_text', '_choose_leader_submit_key', '_current_task_for_agent', '_deliver_pending_message', '_deliver_pending_messages', '_find_agent', '_find_task', '_find_task_or_none', '_format_team_agent_message', '_handle_provider_runtime_prompts', '_handle_provider_startup_prompts', '_infer_active_tmux_pane', '_infer_workspace_tmux_pane', '_is_leader_sender', '_is_leader_target', '_is_message_scoped_result', '_is_runtime_team_agent', '_leader_id', '_leader_receiver_is_direct', '_message_by_id', '_message_payload', '_mirror_peer_message_to_leader', '_notify_leader_of_report_result', '_rediscover_leader_receiver', '_refresh_agent_runtime_statuses', '_result_status_to_task_status', '_runtime_lock', '_runtime_team_agent_ids', '_send_to_leader_receiver', '_submit_worker_prompt', '_tmux_current_client_pane_info', '_tmux_inject_text', '_tmux_list_panes', '_tmux_load_buffer_stdin', '_tmux_pane_info', '_tmux_paste_ready_timeout', '_tmux_set_buffer_text', '_tmux_submit_settle_timeout', '_tmux_window_exists', '_validate_leader_receiver', '_wait_for_message_ready', '_wait_for_worker_message_ready', 'ambiguous_team_target_result', 'check_team_owner', 'copy', 'core_list_targets', 'core_render_message', 'datetime', 'json', 'load_runtime_state', 'load_spec', 'missing_tools', 'os', 're', 'route_task', 'run_cmd', 'runtime_dir', 'save_runtime_state', 'save_team_scoped_state', 'select_runtime_state', 'send_message', 'start_coordinator', 'subprocess', 'team_state_key', 'time', 'timedelta', 'timezone', 'update_task_status', 'validate_result_envelope', 'write_team_state']
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from datetime import datetime, timezone
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from team_agent.events import EventLog
|
|
8
|
+
from team_agent.message_store import MessageStore
|
|
9
|
+
from team_agent.messaging.deps import load_spec, save_runtime_state, team_state_key
|
|
10
|
+
from team_agent.messaging.internal_delivery import deliver_stored_message
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
_UNDELIVERED_MESSAGE_STATUSES = {
|
|
14
|
+
"pending",
|
|
15
|
+
"accepted",
|
|
16
|
+
"queued_until_idle",
|
|
17
|
+
"queued_until_start",
|
|
18
|
+
"queued_stopped",
|
|
19
|
+
"queued_pane_missing",
|
|
20
|
+
"failed",
|
|
21
|
+
"delivery_blocked",
|
|
22
|
+
"injected_unverified",
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _team_undelivered_obligations(
|
|
27
|
+
state: dict[str, Any],
|
|
28
|
+
store: MessageStore,
|
|
29
|
+
owner_team_id: str,
|
|
30
|
+
active_task_statuses: set[str],
|
|
31
|
+
) -> list[dict[str, Any]]:
|
|
32
|
+
obligations: list[dict[str, Any]] = []
|
|
33
|
+
for message in store.messages(owner_team_id=owner_team_id):
|
|
34
|
+
if message.get("status") in _UNDELIVERED_MESSAGE_STATUSES:
|
|
35
|
+
obligations.append(
|
|
36
|
+
{
|
|
37
|
+
"kind": "undelivered_message",
|
|
38
|
+
"message_id": message.get("message_id"),
|
|
39
|
+
"recipient": message.get("recipient"),
|
|
40
|
+
"status": message.get("status"),
|
|
41
|
+
}
|
|
42
|
+
)
|
|
43
|
+
for watcher in store.retryable_result_watchers():
|
|
44
|
+
if watcher.get("status") in {"pending", "notify_failed"}:
|
|
45
|
+
obligations.append(
|
|
46
|
+
{
|
|
47
|
+
"kind": "pending_result_watcher",
|
|
48
|
+
"watcher_id": watcher.get("watcher_id"),
|
|
49
|
+
"task_id": watcher.get("task_id"),
|
|
50
|
+
"agent_id": watcher.get("agent_id"),
|
|
51
|
+
}
|
|
52
|
+
)
|
|
53
|
+
for task in state.get("tasks", []):
|
|
54
|
+
if task.get("status", "pending") in active_task_statuses and task.get("assignee"):
|
|
55
|
+
obligations.append(
|
|
56
|
+
{
|
|
57
|
+
"kind": "active_task",
|
|
58
|
+
"task_id": task.get("id"),
|
|
59
|
+
"assignee": task.get("assignee"),
|
|
60
|
+
"status": task.get("status"),
|
|
61
|
+
}
|
|
62
|
+
)
|
|
63
|
+
return obligations
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _all_workers_idle(
|
|
67
|
+
state: dict[str, Any],
|
|
68
|
+
store: MessageStore,
|
|
69
|
+
owner_team_id: str,
|
|
70
|
+
) -> tuple[bool, list[str]]:
|
|
71
|
+
health = store.agent_health(owner_team_id=owner_team_id)
|
|
72
|
+
worker_ids = list(state.get("agents", {}).keys()) or list(health.keys())
|
|
73
|
+
if not worker_ids:
|
|
74
|
+
return False, []
|
|
75
|
+
idle: list[str] = []
|
|
76
|
+
for agent_id in worker_ids:
|
|
77
|
+
row = health.get(agent_id) or {}
|
|
78
|
+
status = str(row.get("status") or "").lower()
|
|
79
|
+
if status != "idle":
|
|
80
|
+
return False, []
|
|
81
|
+
idle.append(agent_id)
|
|
82
|
+
return True, idle
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _register_unified_alert(
|
|
86
|
+
state: dict[str, Any],
|
|
87
|
+
owner_team_id: str,
|
|
88
|
+
agent_id: str,
|
|
89
|
+
alert_type: str,
|
|
90
|
+
snapshot: dict[str, Any],
|
|
91
|
+
suppressed_by: str,
|
|
92
|
+
now: datetime,
|
|
93
|
+
) -> dict[str, Any]:
|
|
94
|
+
coordinator = state.setdefault("coordinator", {})
|
|
95
|
+
suppressed = coordinator.setdefault("suppressed_idle_alerts", {})
|
|
96
|
+
team_suppressions = suppressed.setdefault(owner_team_id, {})
|
|
97
|
+
agent_suppressions = team_suppressions.setdefault(agent_id, {})
|
|
98
|
+
entry = {
|
|
99
|
+
"suppressed_at": now.isoformat(),
|
|
100
|
+
"suppressed_by": suppressed_by,
|
|
101
|
+
"snapshot": snapshot,
|
|
102
|
+
}
|
|
103
|
+
agent_suppressions[alert_type] = entry
|
|
104
|
+
return entry
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def detect_idle_fallbacks(
|
|
108
|
+
workspace: Path,
|
|
109
|
+
state: dict[str, Any],
|
|
110
|
+
store: MessageStore,
|
|
111
|
+
event_log: EventLog,
|
|
112
|
+
now: datetime | None = None,
|
|
113
|
+
) -> list[dict[str, Any]]:
|
|
114
|
+
from team_agent.messaging.scheduler import (
|
|
115
|
+
_ACTIVE_TASK_STATUSES,
|
|
116
|
+
_active_alert_suppression,
|
|
117
|
+
_agent_alert_snapshot,
|
|
118
|
+
)
|
|
119
|
+
now = now or datetime.now(timezone.utc)
|
|
120
|
+
owner_team_id = team_state_key(state)
|
|
121
|
+
obligations = _team_undelivered_obligations(state, store, owner_team_id, _ACTIVE_TASK_STATUSES)
|
|
122
|
+
if not obligations:
|
|
123
|
+
return []
|
|
124
|
+
all_idle, idle_workers = _all_workers_idle(state, store, owner_team_id)
|
|
125
|
+
if not all_idle:
|
|
126
|
+
return []
|
|
127
|
+
spec_path = Path(state.get("spec_path", workspace / "team.spec.yaml"))
|
|
128
|
+
spec = load_spec(spec_path) if spec_path.exists() else {}
|
|
129
|
+
leader_id = state.get("leader", {}).get("id") or spec.get("leader", {}).get("id") or "leader"
|
|
130
|
+
alerts: list[dict[str, Any]] = []
|
|
131
|
+
for agent_id in idle_workers:
|
|
132
|
+
suppression = _active_alert_suppression(state, store, event_log, agent_id, "idle_fallback")
|
|
133
|
+
if suppression:
|
|
134
|
+
continue
|
|
135
|
+
snapshot = _agent_alert_snapshot(state, store, agent_id, owner_team_id)
|
|
136
|
+
_register_unified_alert(state, owner_team_id, agent_id, "idle_fallback", snapshot, "coordinator", now)
|
|
137
|
+
alerts.append({"agent_id": agent_id, "alert_type": "idle_fallback", "obligations": obligations})
|
|
138
|
+
if not alerts:
|
|
139
|
+
return []
|
|
140
|
+
save_runtime_state(workspace, state)
|
|
141
|
+
content = (
|
|
142
|
+
"There is still unfinished work. Continue coordinating, deliver a result, "
|
|
143
|
+
"or acknowledge that this idle state is intentional via team-agent acknowledge-idle."
|
|
144
|
+
)
|
|
145
|
+
try:
|
|
146
|
+
deliver_stored_message(
|
|
147
|
+
workspace,
|
|
148
|
+
leader_id,
|
|
149
|
+
content,
|
|
150
|
+
sender="coordinator",
|
|
151
|
+
requires_ack=False,
|
|
152
|
+
wait_visible=False,
|
|
153
|
+
team=owner_team_id,
|
|
154
|
+
)
|
|
155
|
+
except Exception as exc:
|
|
156
|
+
event_log.write("coordinator.idle_fallback_push_failed", error=str(exc), team=owner_team_id)
|
|
157
|
+
event_log.write(
|
|
158
|
+
"coordinator.idle_fallback",
|
|
159
|
+
team=owner_team_id,
|
|
160
|
+
idle_workers=idle_workers,
|
|
161
|
+
obligation_count=len(obligations),
|
|
162
|
+
alert_count=len(alerts),
|
|
163
|
+
)
|
|
164
|
+
return alerts
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def detect_cross_worker_deadlocks(
|
|
168
|
+
workspace: Path,
|
|
169
|
+
state: dict[str, Any],
|
|
170
|
+
store: MessageStore,
|
|
171
|
+
event_log: EventLog,
|
|
172
|
+
now: datetime | None = None,
|
|
173
|
+
) -> list[dict[str, Any]]:
|
|
174
|
+
from team_agent.messaging.scheduler import (
|
|
175
|
+
_active_alert_suppression,
|
|
176
|
+
_agent_alert_snapshot,
|
|
177
|
+
)
|
|
178
|
+
now = now or datetime.now(timezone.utc)
|
|
179
|
+
owner_team_id = team_state_key(state)
|
|
180
|
+
health = store.agent_health(owner_team_id=owner_team_id)
|
|
181
|
+
candidate_recipients: dict[str, list[dict[str, Any]]] = {}
|
|
182
|
+
for message in store.messages(owner_team_id=owner_team_id):
|
|
183
|
+
if message.get("status") not in _UNDELIVERED_MESSAGE_STATUSES:
|
|
184
|
+
continue
|
|
185
|
+
recipient = message.get("recipient")
|
|
186
|
+
if not recipient:
|
|
187
|
+
continue
|
|
188
|
+
candidate_recipients.setdefault(str(recipient), []).append(message)
|
|
189
|
+
alerts: list[dict[str, Any]] = []
|
|
190
|
+
for agent_id, messages in candidate_recipients.items():
|
|
191
|
+
row = health.get(agent_id) or {}
|
|
192
|
+
status = str(row.get("status") or "").lower()
|
|
193
|
+
if status != "idle":
|
|
194
|
+
continue
|
|
195
|
+
suppression = _active_alert_suppression(state, store, event_log, agent_id, "cross_worker_deadlock")
|
|
196
|
+
if suppression:
|
|
197
|
+
continue
|
|
198
|
+
snapshot = _agent_alert_snapshot(state, store, agent_id, owner_team_id)
|
|
199
|
+
snapshot["pending_message_ids"] = sorted(str(m.get("message_id")) for m in messages)
|
|
200
|
+
_register_unified_alert(state, owner_team_id, agent_id, "cross_worker_deadlock", snapshot, "coordinator", now)
|
|
201
|
+
alerts.append(
|
|
202
|
+
{
|
|
203
|
+
"agent_id": agent_id,
|
|
204
|
+
"alert_type": "cross_worker_deadlock",
|
|
205
|
+
"pending_messages": snapshot["pending_message_ids"],
|
|
206
|
+
}
|
|
207
|
+
)
|
|
208
|
+
if not alerts:
|
|
209
|
+
return []
|
|
210
|
+
save_runtime_state(workspace, state)
|
|
211
|
+
event_log.write(
|
|
212
|
+
"coordinator.cross_worker_deadlock",
|
|
213
|
+
team=owner_team_id,
|
|
214
|
+
agent_ids=[alert["agent_id"] for alert in alerts],
|
|
215
|
+
alert_count=len(alerts),
|
|
216
|
+
)
|
|
217
|
+
return alerts
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from team_agent.messaging.deps import (
|
|
7
|
+
EventLog,
|
|
8
|
+
_runtime_lock,
|
|
9
|
+
load_spec,
|
|
10
|
+
select_runtime_state,
|
|
11
|
+
team_state_key,
|
|
12
|
+
)
|
|
13
|
+
from team_agent.messaging.send import _send_single_message_unlocked
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def deliver_stored_message(
|
|
17
|
+
workspace: Path,
|
|
18
|
+
target: str | None,
|
|
19
|
+
content: str,
|
|
20
|
+
*,
|
|
21
|
+
task_id: str | None = None,
|
|
22
|
+
sender: str = "coordinator",
|
|
23
|
+
requires_ack: bool = False,
|
|
24
|
+
wait_visible: bool = False,
|
|
25
|
+
timeout: float = 30.0,
|
|
26
|
+
team: str | None = None,
|
|
27
|
+
) -> dict[str, Any]:
|
|
28
|
+
with _runtime_lock(workspace, "send"):
|
|
29
|
+
state = select_runtime_state(workspace, team)
|
|
30
|
+
spec_path = Path(state.get("spec_path", workspace / "team.spec.yaml"))
|
|
31
|
+
spec = load_spec(spec_path)
|
|
32
|
+
return _send_single_message_unlocked(
|
|
33
|
+
workspace,
|
|
34
|
+
state,
|
|
35
|
+
spec,
|
|
36
|
+
EventLog(workspace),
|
|
37
|
+
target,
|
|
38
|
+
content,
|
|
39
|
+
task_id=task_id,
|
|
40
|
+
sender=sender,
|
|
41
|
+
requires_ack=requires_ack,
|
|
42
|
+
wait_visible=wait_visible,
|
|
43
|
+
timeout=timeout,
|
|
44
|
+
route_task_id=False,
|
|
45
|
+
owner_team_id=team_state_key(state),
|
|
46
|
+
)
|