@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.
Files changed (111) hide show
  1. package/crates/team-agent-core/src/lib.rs +50 -5
  2. package/package.json +1 -1
  3. package/schemas/team.schema.json +1 -0
  4. package/skills/team-agent/SKILL.md +1 -1
  5. package/src/team_agent/approvals/__init__.py +65 -0
  6. package/src/team_agent/approvals/constants.py +6 -0
  7. package/src/team_agent/approvals/parsing.py +176 -0
  8. package/src/team_agent/approvals/runtime_prompts.py +171 -0
  9. package/src/team_agent/approvals/status.py +165 -0
  10. package/src/team_agent/cli/__init__.py +135 -0
  11. package/src/team_agent/cli/commands.py +335 -0
  12. package/src/team_agent/cli/e2e.py +202 -0
  13. package/src/team_agent/cli/helpers.py +137 -0
  14. package/src/team_agent/cli/parser.py +470 -0
  15. package/src/team_agent/compiler.py +98 -33
  16. package/src/team_agent/coordinator/__init__.py +53 -0
  17. package/src/team_agent/{coordinator.py → coordinator/__main__.py} +3 -1
  18. package/src/team_agent/coordinator/lifecycle.py +319 -0
  19. package/src/team_agent/coordinator/metadata.py +61 -0
  20. package/src/team_agent/coordinator/paths.py +17 -0
  21. package/src/team_agent/diagnose/__init__.py +48 -0
  22. package/src/team_agent/diagnose/checks.py +101 -0
  23. package/src/team_agent/diagnose/health.py +241 -0
  24. package/src/team_agent/diagnose/preflight.py +194 -0
  25. package/src/team_agent/diagnose/quick_start.py +233 -0
  26. package/src/team_agent/display/__init__.py +61 -0
  27. package/src/team_agent/display/close.py +147 -0
  28. package/src/team_agent/display/ghostty.py +77 -0
  29. package/src/team_agent/display/worker_window.py +110 -0
  30. package/src/team_agent/display/workspace.py +473 -0
  31. package/src/team_agent/launch/__init__.py +41 -0
  32. package/src/team_agent/launch/bootstrap.py +85 -0
  33. package/src/team_agent/launch/config.py +106 -0
  34. package/src/team_agent/launch/core.py +291 -0
  35. package/src/team_agent/launch/requirements.py +57 -0
  36. package/src/team_agent/leader/__init__.py +320 -0
  37. package/src/team_agent/lifecycle/__init__.py +5 -0
  38. package/src/team_agent/lifecycle/agents.py +226 -0
  39. package/src/team_agent/lifecycle/operations.py +321 -0
  40. package/src/team_agent/lifecycle/start.py +360 -0
  41. package/src/team_agent/mcp_server/__init__.py +42 -0
  42. package/src/team_agent/mcp_server/__main__.py +7 -0
  43. package/src/team_agent/mcp_server/contracts.py +148 -0
  44. package/src/team_agent/mcp_server/normalize.py +257 -0
  45. package/src/team_agent/mcp_server/server.py +150 -0
  46. package/src/team_agent/mcp_server/tools.py +205 -0
  47. package/src/team_agent/message_store/__init__.py +23 -0
  48. package/src/team_agent/message_store/agent_health.py +109 -0
  49. package/src/team_agent/{message_store.py → message_store/core.py} +188 -245
  50. package/src/team_agent/message_store/result_watchers.py +102 -0
  51. package/src/team_agent/message_store/schema.py +266 -0
  52. package/src/team_agent/messaging/__init__.py +1 -0
  53. package/src/team_agent/messaging/activity_detector.py +190 -0
  54. package/src/team_agent/messaging/delivery.py +128 -0
  55. package/src/team_agent/messaging/deps.py +263 -0
  56. package/src/team_agent/messaging/idle_alerts.py +217 -0
  57. package/src/team_agent/messaging/internal_delivery.py +46 -0
  58. package/src/team_agent/messaging/leader.py +317 -0
  59. package/src/team_agent/messaging/leader_panes.py +343 -0
  60. package/src/team_agent/messaging/result_delivery.py +300 -0
  61. package/src/team_agent/messaging/results.py +456 -0
  62. package/src/team_agent/messaging/scheduler.py +418 -0
  63. package/src/team_agent/messaging/send.py +493 -0
  64. package/src/team_agent/messaging/tmux_io.py +337 -0
  65. package/src/team_agent/messaging/tmux_prompt.py +229 -0
  66. package/src/team_agent/orchestrator/__init__.py +376 -0
  67. package/src/team_agent/orchestrator/plan.py +122 -0
  68. package/src/team_agent/orchestrator/state.py +128 -0
  69. package/src/team_agent/profiles/__init__.py +82 -0
  70. package/src/team_agent/profiles/constants.py +19 -0
  71. package/src/team_agent/profiles/core.py +407 -0
  72. package/src/team_agent/profiles/helpers.py +69 -0
  73. package/src/team_agent/profiles/provider_env.py +188 -0
  74. package/src/team_agent/profiles/smoke.py +201 -0
  75. package/src/team_agent/provider_cli/__init__.py +43 -0
  76. package/src/team_agent/provider_cli/adapter.py +167 -0
  77. package/src/team_agent/provider_cli/base.py +48 -0
  78. package/src/team_agent/provider_cli/claude.py +457 -0
  79. package/src/team_agent/provider_cli/codex.py +319 -0
  80. package/src/team_agent/provider_cli/copilot.py +8 -0
  81. package/src/team_agent/provider_cli/fake.py +39 -0
  82. package/src/team_agent/provider_cli/gemini.py +95 -0
  83. package/src/team_agent/provider_cli/opencode.py +8 -0
  84. package/src/team_agent/provider_cli/prompt.py +62 -0
  85. package/src/team_agent/provider_cli/registry.py +18 -0
  86. package/src/team_agent/provider_cli/unsupported.py +32 -0
  87. package/src/team_agent/providers.py +67 -949
  88. package/src/team_agent/quality_gates.py +104 -0
  89. package/src/team_agent/restart/__init__.py +34 -0
  90. package/src/team_agent/restart/orchestration.py +328 -0
  91. package/src/team_agent/restart/selection.py +89 -0
  92. package/src/team_agent/restart/snapshot.py +70 -0
  93. package/src/team_agent/runtime.py +802 -5740
  94. package/src/team_agent/rust_core.py +22 -5
  95. package/src/team_agent/sessions/__init__.py +25 -0
  96. package/src/team_agent/sessions/capture.py +93 -0
  97. package/src/team_agent/sessions/inventory.py +44 -0
  98. package/src/team_agent/sessions/resume.py +135 -0
  99. package/src/team_agent/spec.py +3 -1
  100. package/src/team_agent/state.py +204 -4
  101. package/src/team_agent/status/__init__.py +63 -0
  102. package/src/team_agent/status/approvals.py +52 -0
  103. package/src/team_agent/status/compact.py +158 -0
  104. package/src/team_agent/status/constants.py +18 -0
  105. package/src/team_agent/status/inbox.py +28 -0
  106. package/src/team_agent/status/peek.py +117 -0
  107. package/src/team_agent/status/queries.py +168 -0
  108. package/src/team_agent/terminal.py +57 -0
  109. package/src/team_agent/cli.py +0 -857
  110. package/src/team_agent/mcp_server.py +0 -579
  111. package/src/team_agent/profiles.py +0 -882
@@ -0,0 +1,202 @@
1
+ from __future__ import annotations
2
+
3
+ import argparse
4
+ import tempfile
5
+ from pathlib import Path
6
+ from typing import Any
7
+
8
+ from team_agent import runtime
9
+ from team_agent.simple_yaml import dumps
10
+
11
+
12
+ def cmd_e2e(args: argparse.Namespace) -> dict[str, Any]:
13
+ providers = [item.strip() for item in args.providers.split(",") if item.strip()]
14
+ workspace = Path(args.workspace).resolve() if args.workspace else Path(tempfile.mkdtemp(prefix="team-agent-e2e-"))
15
+ workspace.mkdir(parents=True, exist_ok=True)
16
+ results: dict[str, Any] = {"workspace": str(workspace), "providers": {}, "ok": True}
17
+ if "fake" in providers:
18
+ spec_path = workspace / "team.spec.yaml"
19
+ spec_path.write_text(dumps(_fake_spec(workspace)), encoding="utf-8")
20
+ results["providers"]["fake"] = _run_fake_e2e(spec_path, workspace)
21
+ results["ok"] = results["ok"] and results["providers"]["fake"]["ok"]
22
+ for provider in [p for p in providers if p != "fake"]:
23
+ from team_agent.providers import get_adapter
24
+
25
+ adapter = get_adapter(provider)
26
+ installed = adapter.is_installed()
27
+ if not installed:
28
+ provider_result = {
29
+ "ok": False,
30
+ "skipped": True,
31
+ "reason": f"{adapter.command_name} not installed",
32
+ "version": None,
33
+ }
34
+ elif not args.real:
35
+ provider_result = {
36
+ "ok": False,
37
+ "skipped": True,
38
+ "reason": "real provider launch disabled; rerun with --real on an authenticated machine",
39
+ "version": adapter.version(),
40
+ }
41
+ else:
42
+ provider_result = _run_real_launch_smoke(provider, workspace)
43
+ results["providers"][provider] = provider_result
44
+ results["ok"] = results["ok"] and provider_result["ok"]
45
+ return results
46
+
47
+
48
+ def _run_fake_e2e(spec_path: Path, workspace: Path) -> dict[str, Any]:
49
+ launched = runtime.launch(spec_path, auto_approve=True)
50
+ sent = runtime.send_message(workspace, None, "implement fake task", task_id="task_impl", requires_ack=True)
51
+ import time
52
+
53
+ time.sleep(1.0)
54
+ collected = runtime.collect(workspace)
55
+ stopped = runtime.shutdown(workspace)
56
+ return {"ok": bool(launched["ok"] and sent["ok"] and collected["collected"] and stopped["ok"]), "launch": launched, "send": sent, "collect": collected, "shutdown": stopped}
57
+
58
+
59
+ def _run_real_launch_smoke(provider: str, workspace: Path) -> dict[str, Any]:
60
+ spec_path = workspace / f"team.{provider}.spec.yaml"
61
+ spec = _fake_spec(workspace)
62
+ spec["team"]["name"] = f"real-{provider}-smoke"
63
+ spec["leader"]["provider"] = provider
64
+ spec["agents"][0]["provider"] = provider
65
+ spec["agents"][0]["id"] = f"{provider}_smoke"
66
+ spec["agents"][0]["tools"] = ["fs_read", "fs_list", "git_diff", "mcp_team", "provider_builtin"]
67
+ spec["agents"][0]["role"] = "reviewer"
68
+ spec["agents"][0]["system_prompt"]["inline"] = (
69
+ "Real provider smoke. Do not edit files or run shell. "
70
+ "Do not call team-agent launch and do not create a nested Team Agent team. "
71
+ "When asked, call team_orchestrator.report_result exactly once with result_envelope_v1."
72
+ )
73
+ spec["routing"]["rules"][0]["assign_to"] = spec["agents"][0]["id"]
74
+ spec["runtime"]["session_name"] = f"team-agent-real-{provider}"
75
+ spec["runtime"]["startup_order"] = [spec["agents"][0]["id"]]
76
+ spec["tasks"][0]["id"] = f"task_real_{provider}_callback"
77
+ spec["tasks"][0]["title"] = f"Real {provider} callback smoke"
78
+ spec["tasks"][0]["assignee"] = spec["agents"][0]["id"]
79
+ spec["tasks"][0]["requires_tools"] = ["fs_read", "git_diff"]
80
+ spec["tasks"][0]["type"] = "review"
81
+ spec_path.write_text(dumps(spec), encoding="utf-8")
82
+ launched = runtime.launch(spec_path, auto_approve=True)
83
+ import time
84
+
85
+ time.sleep(10.0 if provider == "codex" else 3.0)
86
+ collected = None
87
+ sent = None
88
+ if provider == "codex":
89
+ task_id = spec["tasks"][0]["id"]
90
+ agent_id = spec["agents"][0]["id"]
91
+ message = (
92
+ "Do not call team-agent launch and do not create a nested Team Agent team. "
93
+ "Do not edit files or run shell. "
94
+ "Call team_orchestrator.report_result with envelope "
95
+ f'{{"schema_version":"result_envelope_v1","task_id":"{task_id}",'
96
+ f'"agent_id":"{agent_id}","status":"success","summary":"ok",'
97
+ '"changes":[],"tests":[{"command":"real-codex-callback-smoke","status":"passed"}],'
98
+ '"risks":[],"artifacts":[],"next_actions":[]}. Do not edit files or run shell.'
99
+ )
100
+ sent = runtime.send_message(workspace, agent_id, message, task_id=task_id, requires_ack=True)
101
+ for _ in range(24):
102
+ time.sleep(5.0)
103
+ result = runtime.collect(workspace)
104
+ if result["collected"]:
105
+ collected = result
106
+ break
107
+ status = runtime.status(workspace, as_json=True)
108
+ stopped = runtime.shutdown(workspace)
109
+ agent_id = spec["agents"][0]["id"]
110
+ agent_status = status["agents"].get(agent_id, {})
111
+ callback_ok = provider != "codex" or bool(collected and collected["collected"])
112
+ return {
113
+ "ok": bool(launched["ok"] and stopped["ok"] and agent_status.get("tmux_window_present") and callback_ok),
114
+ "launch": launched,
115
+ "send": sent,
116
+ "collect": collected,
117
+ "status": status,
118
+ "shutdown": stopped,
119
+ }
120
+
121
+
122
+ def _fake_spec(workspace: Path) -> dict[str, Any]:
123
+ return {
124
+ "version": 1,
125
+ "team": {
126
+ "name": "fake-e2e",
127
+ "mode": "supervisor_worker",
128
+ "objective": "Exercise fake provider orchestration.",
129
+ "workspace": str(workspace),
130
+ },
131
+ "leader": {
132
+ "id": "leader",
133
+ "role": "leader",
134
+ "provider": "fake",
135
+ "model": None,
136
+ "tools": ["fs_read", "fs_list", "mcp_team"],
137
+ "context_policy": {
138
+ "keep_user_thread": True,
139
+ "receive_worker_outputs": "structured_only",
140
+ "max_worker_result_tokens": 2000,
141
+ },
142
+ },
143
+ "agents": [
144
+ {
145
+ "id": "fake_impl",
146
+ "role": "implementation_engineer",
147
+ "provider": "fake",
148
+ "model": None,
149
+ "working_directory": str(workspace),
150
+ "system_prompt": {"inline": "Handle fake implementation tasks.", "file": None},
151
+ "tools": ["fs_read", "fs_write", "fs_list", "execute_bash", "git_diff", "mcp_team", "provider_builtin"],
152
+ "permission_mode": "restricted",
153
+ "preferred_for": ["implementation"],
154
+ "avoid_for": [],
155
+ "output_contract": {"format": "result_envelope_v1", "required_fields": ["task_id", "status", "summary", "artifacts"]},
156
+ }
157
+ ],
158
+ "routing": {
159
+ "default_assignee": "leader",
160
+ "rules": [{"id": "implementation-to-fake", "match": {"type": ["implementation"]}, "assign_to": "fake_impl", "priority": 10}],
161
+ },
162
+ "communication": {
163
+ "protocol": "mcp_inbox",
164
+ "topology": "leader_centered",
165
+ "worker_to_worker": True,
166
+ "ack_timeout_sec": 2,
167
+ "result_format": "result_envelope_v1",
168
+ "message_store": {"sqlite": ".team/runtime/team.db", "mirror_files": ".team/messages"},
169
+ },
170
+ "runtime": {
171
+ "backend": "tmux",
172
+ "display_backend": "none",
173
+ "session_name": "team-agent-fake-e2e",
174
+ "auto_launch": True,
175
+ "require_user_approval_before_launch": False,
176
+ "max_active_agents": 1,
177
+ "startup_order": ["fake_impl"],
178
+ },
179
+ "context": {
180
+ "state_file": "team_state.md",
181
+ "artifact_dir": ".team/artifacts",
182
+ "log_dir": ".team/logs",
183
+ "summarization": {
184
+ "worker_full_logs": "retain_outside_leader_context",
185
+ "state_update": "after_each_result",
186
+ },
187
+ },
188
+ "tasks": [
189
+ {
190
+ "id": "task_impl",
191
+ "title": "Fake implementation",
192
+ "type": "implementation",
193
+ "assignee": None,
194
+ "deps": [],
195
+ "acceptance": ["fake result collected"],
196
+ "status": "pending",
197
+ "requires_tools": ["fs_write", "execute_bash"],
198
+ "files": ["src/example.py"],
199
+ "risk": "low",
200
+ }
201
+ ],
202
+ }
@@ -0,0 +1,137 @@
1
+ from __future__ import annotations
2
+
3
+ import argparse
4
+ import json
5
+ import sys
6
+ import time
7
+ import traceback
8
+ from pathlib import Path
9
+ from typing import Any
10
+
11
+
12
+ def emit(result: Any, as_json: bool) -> None:
13
+ if as_json:
14
+ print(json.dumps(result, indent=2, ensure_ascii=False, sort_keys=True))
15
+ return
16
+ if isinstance(result, dict):
17
+ for key, value in result.items():
18
+ if isinstance(value, (dict, list)):
19
+ print(f"{key}: {json.dumps(value, ensure_ascii=False)}")
20
+ else:
21
+ print(f"{key}: {value}")
22
+ else:
23
+ print(result)
24
+
25
+
26
+ def _workspace_from_args(args: argparse.Namespace) -> Path:
27
+ return Path(getattr(args, "workspace", ".")).resolve()
28
+
29
+
30
+ def _emit_cli_error(exc: Exception, args: argparse.Namespace) -> None:
31
+ workspace = _workspace_from_args(args)
32
+ log_dir = workspace / ".team" / "logs"
33
+ try:
34
+ log_dir.mkdir(parents=True, exist_ok=True)
35
+ except OSError:
36
+ log_dir = Path.cwd()
37
+ log_path = log_dir / f"cli-error-{int(time.time())}.log"
38
+ log_path.write_text("".join(traceback.format_exception(type(exc), exc, exc.__traceback__)), encoding="utf-8")
39
+ payload = _cli_error_payload(exc, args, log_path)
40
+ if getattr(args, "json", False):
41
+ print(json.dumps(payload, ensure_ascii=False))
42
+ return
43
+ print(f"error: {payload['error']}", file=sys.stderr)
44
+ print(f"action: {payload['action']}", file=sys.stderr)
45
+ print(f"log: {payload['log']}", file=sys.stderr)
46
+
47
+
48
+ def _cli_error_payload(exc: Exception, args: argparse.Namespace, log_path: Path) -> dict[str, Any]:
49
+ error = str(exc)
50
+ payload = {
51
+ "ok": False,
52
+ "error": error,
53
+ "action": "run `team-agent doctor` or inspect the log path shown here",
54
+ "log": str(log_path),
55
+ }
56
+ session_name = _tmux_session_conflict_name(error)
57
+ if session_name:
58
+ payload.update(
59
+ {
60
+ "reason": "tmux_session_name_conflict",
61
+ "session_name": session_name,
62
+ "action": _tmux_session_conflict_action(session_name, getattr(args, "command", "")),
63
+ "next_actions": [_tmux_session_conflict_next_action(getattr(args, "command", ""))],
64
+ }
65
+ )
66
+ return payload
67
+
68
+
69
+ def _tmux_session_conflict_name(error: str) -> str | None:
70
+ marker = "tmux session already exists:"
71
+ if marker not in error:
72
+ return None
73
+ name = error.split(marker, 1)[1].strip()
74
+ name = name.split(";", 1)[0].splitlines()[0].strip()
75
+ if ". Startup" in name:
76
+ name = name.split(". Startup", 1)[0].strip()
77
+ name = name.rstrip(".").strip()
78
+ return name or None
79
+
80
+
81
+ def _tmux_session_conflict_next_action(command: str) -> str:
82
+ if command == "quick-start":
83
+ return "Change `name:` in TEAM.md and run `team-agent quick-start` again."
84
+ return "Use a different team name or runtime.session_name before starting again."
85
+
86
+
87
+ def _tmux_session_conflict_action(session_name: str, command: str) -> str:
88
+ if command == "quick-start":
89
+ return (
90
+ f"tmux session `{session_name}` already exists. It may be an active team. "
91
+ "Do not terminate existing tmux sessions from quick-start; "
92
+ "change `name:` in TEAM.md and run quick-start again."
93
+ )
94
+ return (
95
+ f"tmux session `{session_name}` already exists. It may be an active team. "
96
+ "Do not terminate existing tmux sessions from startup; "
97
+ "use a different team name or runtime.session_name and start again."
98
+ )
99
+
100
+
101
+ def _provider_args(values: list[str]) -> list[str]:
102
+ if values and values[0] == "--":
103
+ return values[1:]
104
+ return values
105
+
106
+
107
+ def _leader_launcher_args(values: list[str]) -> dict[str, Any]:
108
+ provider_args: list[str] = []
109
+ attach_existing = False
110
+ confirm_attach = False
111
+ attach_session: str | None = None
112
+ index = 0
113
+ while index < len(values):
114
+ value = values[index]
115
+ if value == "--":
116
+ provider_args.extend(values[index:])
117
+ break
118
+ if value in {"--attach", "--attach-existing"}:
119
+ attach_existing = True
120
+ elif value == "--confirm":
121
+ confirm_attach = True
122
+ elif value == "--attach-session":
123
+ index += 1
124
+ if index >= len(values):
125
+ raise RuntimeError("--attach-session requires a tmux session name")
126
+ attach_session = values[index]
127
+ elif value.startswith("--attach-session="):
128
+ attach_session = value.split("=", 1)[1]
129
+ else:
130
+ provider_args.append(value)
131
+ index += 1
132
+ return {
133
+ "provider_args": provider_args,
134
+ "attach_existing": attach_existing,
135
+ "confirm_attach": confirm_attach,
136
+ "attach_session": attach_session,
137
+ }