@team-agent/installer 0.2.2 → 0.2.3
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/package.json +1 -1
- package/schemas/team.schema.json +6 -0
- package/src/team_agent/approvals/runtime_prompts.py +1 -1
- package/src/team_agent/cli/commands.py +104 -3
- package/src/team_agent/cli/parser.py +10 -1
- package/src/team_agent/coordinator/lifecycle.py +3 -0
- package/src/team_agent/diagnose/orphan_cleanup.py +199 -28
- package/src/team_agent/launch/core.py +2 -1
- package/src/team_agent/lifecycle/operations.py +1 -0
- package/src/team_agent/lifecycle/start.py +1 -1
- package/src/team_agent/message_store/core.py +8 -7
- package/src/team_agent/message_store/schema.py +8 -2
- package/src/team_agent/messaging/delivery.py +293 -1
- package/src/team_agent/messaging/leader.py +13 -4
- package/src/team_agent/messaging/leader_api_errors.py +216 -0
- package/src/team_agent/messaging/leader_panes.py +200 -0
- package/src/team_agent/messaging/scheduler.py +12 -0
- package/src/team_agent/messaging/send.py +21 -26
- package/src/team_agent/messaging/tmux_io.py +153 -23
- package/src/team_agent/messaging/tmux_prompt.py +87 -0
- package/src/team_agent/messaging/trust_auto_answer.py +44 -0
- package/src/team_agent/restart/orchestration.py +207 -4
- package/src/team_agent/runtime.py +3 -3
- package/src/team_agent/sessions/capture.py +65 -15
- package/src/team_agent/spec.py +59 -0
- package/src/team_agent/status/queries.py +32 -1
- package/src/team_agent/watch/__init__.py +145 -0
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import time
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Any, Callable
|
|
8
|
+
|
|
9
|
+
from team_agent.message_store import MessageStore
|
|
10
|
+
from team_agent.paths import logs_dir, runtime_dir
|
|
11
|
+
from team_agent.status.queries import result_summary_from_row
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass
|
|
15
|
+
class WatchCursor:
|
|
16
|
+
event_offset: int = 0
|
|
17
|
+
seen_result_ids: set[str] = field(default_factory=set)
|
|
18
|
+
initialized: bool = False
|
|
19
|
+
archive_signature: tuple[int, int] | None = None
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
ROTATION_MARKER = "[watch] log rotated; archived segment events.jsonl.1 not replayed — historical replay deferred to a future --replay flag"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def run_watch(
|
|
26
|
+
workspace: Path,
|
|
27
|
+
*,
|
|
28
|
+
team: str | None = None,
|
|
29
|
+
interval: float = 0.5,
|
|
30
|
+
output: Callable[[str], None] = print,
|
|
31
|
+
sleep: Callable[[float], None] = time.sleep,
|
|
32
|
+
) -> None:
|
|
33
|
+
cursor = WatchCursor()
|
|
34
|
+
while True:
|
|
35
|
+
for line in collect_watch_lines(workspace, cursor, team=team):
|
|
36
|
+
output(line)
|
|
37
|
+
sleep(interval)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def collect_watch_lines(workspace: Path, cursor: WatchCursor, *, team: str | None = None) -> list[str]:
|
|
41
|
+
lines = _collect_event_lines(workspace, cursor, team=team)
|
|
42
|
+
lines.extend(_collect_result_lines(workspace, cursor, team=team))
|
|
43
|
+
return lines
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def render_event_line(event: dict[str, Any]) -> str | None:
|
|
47
|
+
kind = event.get("event")
|
|
48
|
+
if kind == "result_received":
|
|
49
|
+
return _result_line(event.get("agent_id"), event.get("summary"))
|
|
50
|
+
if kind in {"leader_receiver.injected", "leader_receiver.submitted"}:
|
|
51
|
+
return f"leader_receiver.injected: {_message_snippet(event)} -> {_recipient(event)}"
|
|
52
|
+
if kind == "send.failed":
|
|
53
|
+
return f"send.failed: {_recipient(event)} reason={_clean(event.get('reason') or event.get('error') or '-')}"
|
|
54
|
+
if kind == "leader_receiver.rebind_required":
|
|
55
|
+
pane = event.get("old_pane_id") or event.get("pane_id") or event.get("target") or "-"
|
|
56
|
+
reason = event.get("reason") or event.get("rediscovery_status") or "-"
|
|
57
|
+
return f"leader_receiver.rebind_required: pane={pane} reason={_clean(reason)}"
|
|
58
|
+
if kind == "leader.api_error":
|
|
59
|
+
error_class = event.get("error_class") or "Unknown"
|
|
60
|
+
provider = event.get("provider") or "-"
|
|
61
|
+
snippet = _clean(event.get("matched_pattern_snippet") or event.get("snippet") or "-")
|
|
62
|
+
return f"leader.api_error: {error_class} provider={provider} snippet={snippet}"
|
|
63
|
+
return None
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _collect_event_lines(workspace: Path, cursor: WatchCursor, *, team: str | None = None) -> list[str]:
|
|
67
|
+
path = logs_dir(workspace) / "events.jsonl"
|
|
68
|
+
archive_signature = _archive_signature(path.with_name("events.jsonl.1"))
|
|
69
|
+
lines: list[str] = []
|
|
70
|
+
if not cursor.initialized:
|
|
71
|
+
cursor.archive_signature = archive_signature
|
|
72
|
+
cursor.initialized = True
|
|
73
|
+
elif archive_signature and archive_signature != cursor.archive_signature:
|
|
74
|
+
lines.append(ROTATION_MARKER)
|
|
75
|
+
cursor.archive_signature = archive_signature
|
|
76
|
+
cursor.event_offset = 0
|
|
77
|
+
if not path.exists():
|
|
78
|
+
return lines
|
|
79
|
+
size = path.stat().st_size
|
|
80
|
+
if cursor.event_offset > size:
|
|
81
|
+
if ROTATION_MARKER not in lines:
|
|
82
|
+
lines.append(ROTATION_MARKER)
|
|
83
|
+
cursor.event_offset = 0
|
|
84
|
+
with path.open("r", encoding="utf-8") as handle:
|
|
85
|
+
handle.seek(cursor.event_offset)
|
|
86
|
+
for raw in handle:
|
|
87
|
+
try:
|
|
88
|
+
event = json.loads(raw)
|
|
89
|
+
except json.JSONDecodeError:
|
|
90
|
+
continue
|
|
91
|
+
if team and _event_team_id(event) != team:
|
|
92
|
+
continue
|
|
93
|
+
rendered = render_event_line(event)
|
|
94
|
+
if rendered:
|
|
95
|
+
lines.append(rendered)
|
|
96
|
+
cursor.event_offset = handle.tell()
|
|
97
|
+
return lines
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def _collect_result_lines(workspace: Path, cursor: WatchCursor, *, team: str | None = None) -> list[str]:
|
|
101
|
+
if not (runtime_dir(workspace) / "team.db").exists():
|
|
102
|
+
return []
|
|
103
|
+
store = MessageStore(workspace)
|
|
104
|
+
lines: list[str] = []
|
|
105
|
+
for row in store.latest_results(limit=20, owner_team_id=team):
|
|
106
|
+
result_id = str(row.get("result_id") or "")
|
|
107
|
+
if not result_id or result_id in cursor.seen_result_ids:
|
|
108
|
+
continue
|
|
109
|
+
cursor.seen_result_ids.add(result_id)
|
|
110
|
+
summary = result_summary_from_row(row) or {}
|
|
111
|
+
lines.append(_result_line(summary.get("agent_id"), summary.get("summary")))
|
|
112
|
+
return lines
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def _result_line(agent_id: Any, summary: Any) -> str:
|
|
116
|
+
return f"result_received: {agent_id or '-'} -> {_clean(summary or '-')[:80]}"
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def _message_snippet(event: dict[str, Any]) -> str:
|
|
120
|
+
message_id = str(event.get("message_id") or event.get("msg_id") or "-")
|
|
121
|
+
return message_id[:12] if message_id != "-" else "-"
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def _recipient(event: dict[str, Any]) -> str:
|
|
125
|
+
return str(event.get("recipient") or event.get("to") or event.get("target") or "-")
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def _clean(value: Any) -> str:
|
|
129
|
+
return " ".join(str(value).split())
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def _event_team_id(event: dict[str, Any]) -> str | None:
|
|
133
|
+
value = event.get("team_id") or event.get("owner_team_id") or event.get("team")
|
|
134
|
+
return str(value) if value else None
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def _archive_signature(path: Path) -> tuple[int, int] | None:
|
|
138
|
+
try:
|
|
139
|
+
stat = path.stat()
|
|
140
|
+
except FileNotFoundError:
|
|
141
|
+
return None
|
|
142
|
+
return (stat.st_size, stat.st_mtime_ns)
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
__all__ = ["ROTATION_MARKER", "WatchCursor", "collect_watch_lines", "render_event_line", "run_watch"]
|