@team-agent/installer 0.1.11 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (113) 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/src/team_agent/approvals/__init__.py +65 -0
  5. package/src/team_agent/approvals/constants.py +6 -0
  6. package/src/team_agent/approvals/parsing.py +176 -0
  7. package/src/team_agent/approvals/runtime_prompts.py +171 -0
  8. package/src/team_agent/approvals/status.py +165 -0
  9. package/src/team_agent/cli/__init__.py +137 -0
  10. package/src/team_agent/cli/commands.py +339 -0
  11. package/src/team_agent/cli/e2e.py +202 -0
  12. package/src/team_agent/cli/helpers.py +137 -0
  13. package/src/team_agent/cli/parser.py +477 -0
  14. package/src/team_agent/compiler.py +98 -33
  15. package/src/team_agent/coordinator/__init__.py +53 -0
  16. package/src/team_agent/{coordinator.py → coordinator/__main__.py} +3 -1
  17. package/src/team_agent/coordinator/lifecycle.py +334 -0
  18. package/src/team_agent/coordinator/metadata.py +61 -0
  19. package/src/team_agent/coordinator/paths.py +17 -0
  20. package/src/team_agent/diagnose/__init__.py +48 -0
  21. package/src/team_agent/diagnose/checks.py +101 -0
  22. package/src/team_agent/diagnose/health.py +241 -0
  23. package/src/team_agent/diagnose/preflight.py +194 -0
  24. package/src/team_agent/diagnose/quick_start.py +233 -0
  25. package/src/team_agent/display/__init__.py +61 -0
  26. package/src/team_agent/display/close.py +147 -0
  27. package/src/team_agent/display/ghostty.py +77 -0
  28. package/src/team_agent/display/worker_window.py +110 -0
  29. package/src/team_agent/display/workspace.py +473 -0
  30. package/src/team_agent/launch/__init__.py +41 -0
  31. package/src/team_agent/launch/bootstrap.py +85 -0
  32. package/src/team_agent/launch/config.py +106 -0
  33. package/src/team_agent/launch/core.py +291 -0
  34. package/src/team_agent/launch/requirements.py +57 -0
  35. package/src/team_agent/leader/__init__.py +320 -0
  36. package/src/team_agent/lifecycle/__init__.py +5 -0
  37. package/src/team_agent/lifecycle/agents.py +226 -0
  38. package/src/team_agent/lifecycle/operations.py +321 -0
  39. package/src/team_agent/lifecycle/paste_buffer_hygiene.py +39 -0
  40. package/src/team_agent/lifecycle/start.py +363 -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 +138 -0
  55. package/src/team_agent/messaging/deps.py +263 -0
  56. package/src/team_agent/messaging/idle_alerts.py +323 -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/owner_bypass.py +29 -0
  61. package/src/team_agent/messaging/result_delivery.py +300 -0
  62. package/src/team_agent/messaging/results.py +456 -0
  63. package/src/team_agent/messaging/scheduler.py +428 -0
  64. package/src/team_agent/messaging/send.py +500 -0
  65. package/src/team_agent/messaging/session_drift.py +94 -0
  66. package/src/team_agent/messaging/tmux_io.py +337 -0
  67. package/src/team_agent/messaging/tmux_prompt.py +229 -0
  68. package/src/team_agent/orchestrator/__init__.py +376 -0
  69. package/src/team_agent/orchestrator/plan.py +122 -0
  70. package/src/team_agent/orchestrator/state.py +128 -0
  71. package/src/team_agent/profiles/__init__.py +82 -0
  72. package/src/team_agent/profiles/constants.py +19 -0
  73. package/src/team_agent/profiles/core.py +407 -0
  74. package/src/team_agent/profiles/helpers.py +69 -0
  75. package/src/team_agent/profiles/provider_env.py +188 -0
  76. package/src/team_agent/profiles/smoke.py +201 -0
  77. package/src/team_agent/provider_cli/__init__.py +43 -0
  78. package/src/team_agent/provider_cli/adapter.py +167 -0
  79. package/src/team_agent/provider_cli/base.py +48 -0
  80. package/src/team_agent/provider_cli/claude.py +457 -0
  81. package/src/team_agent/provider_cli/codex.py +319 -0
  82. package/src/team_agent/provider_cli/copilot.py +8 -0
  83. package/src/team_agent/provider_cli/fake.py +39 -0
  84. package/src/team_agent/provider_cli/gemini.py +95 -0
  85. package/src/team_agent/provider_cli/opencode.py +8 -0
  86. package/src/team_agent/provider_cli/prompt.py +62 -0
  87. package/src/team_agent/provider_cli/registry.py +18 -0
  88. package/src/team_agent/provider_cli/unsupported.py +32 -0
  89. package/src/team_agent/providers.py +67 -949
  90. package/src/team_agent/quality_gates.py +104 -0
  91. package/src/team_agent/restart/__init__.py +34 -0
  92. package/src/team_agent/restart/orchestration.py +328 -0
  93. package/src/team_agent/restart/selection.py +89 -0
  94. package/src/team_agent/restart/snapshot.py +70 -0
  95. package/src/team_agent/runtime.py +809 -5892
  96. package/src/team_agent/rust_core.py +22 -5
  97. package/src/team_agent/sessions/__init__.py +25 -0
  98. package/src/team_agent/sessions/capture.py +93 -0
  99. package/src/team_agent/sessions/inventory.py +44 -0
  100. package/src/team_agent/sessions/resume.py +135 -0
  101. package/src/team_agent/spec.py +3 -1
  102. package/src/team_agent/state.py +218 -4
  103. package/src/team_agent/status/__init__.py +63 -0
  104. package/src/team_agent/status/approvals.py +52 -0
  105. package/src/team_agent/status/compact.py +158 -0
  106. package/src/team_agent/status/constants.py +18 -0
  107. package/src/team_agent/status/inbox.py +28 -0
  108. package/src/team_agent/status/peek.py +117 -0
  109. package/src/team_agent/status/queries.py +168 -0
  110. package/src/team_agent/terminal.py +57 -0
  111. package/src/team_agent/cli.py +0 -858
  112. package/src/team_agent/mcp_server.py +0 -579
  113. package/src/team_agent/profiles.py +0 -882
@@ -0,0 +1,137 @@
1
+ from __future__ import annotations
2
+
3
+ from team_agent.cli.parser import (
4
+ SEND_ORDER_HINT,
5
+ TeamAgentArgumentParser,
6
+ _run_leader_passthrough,
7
+ add_json,
8
+ main,
9
+ )
10
+ from team_agent.cli.helpers import (
11
+ _cli_error_payload,
12
+ _emit_cli_error,
13
+ _leader_launcher_args,
14
+ _provider_args,
15
+ _tmux_session_conflict_action,
16
+ _tmux_session_conflict_name,
17
+ _tmux_session_conflict_next_action,
18
+ _workspace_from_args,
19
+ emit,
20
+ )
21
+ from team_agent.cli.commands import (
22
+ cmd_quick_start,
23
+ cmd_codex,
24
+ cmd_claude,
25
+ cmd_init,
26
+ cmd_validate,
27
+ cmd_compile,
28
+ _profile_scope,
29
+ cmd_profile_init,
30
+ cmd_profile_doctor,
31
+ cmd_profile_show,
32
+ cmd_launch,
33
+ cmd_preflight,
34
+ cmd_start,
35
+ cmd_wait_ready,
36
+ cmd_settle,
37
+ cmd_status,
38
+ cmd_approvals,
39
+ cmd_peek,
40
+ cmd_inbox,
41
+ cmd_sessions,
42
+ cmd_attach_leader,
43
+ cmd_send,
44
+ cmd_collect,
45
+ cmd_diagnose,
46
+ cmd_repair_state,
47
+ cmd_validate_result,
48
+ cmd_doctor,
49
+ cmd_shutdown,
50
+ cmd_restart,
51
+ cmd_start_agent,
52
+ cmd_stop_agent,
53
+ cmd_reset_agent,
54
+ cmd_add_agent,
55
+ cmd_fork_agent,
56
+ cmd_remove_agent,
57
+ cmd_stuck_list,
58
+ cmd_stuck_cancel,
59
+ cmd_acknowledge_idle,
60
+ cmd_allow_peer_talk,
61
+ cmd_advanced,
62
+ cmd_install_skill,
63
+ _skill_dest_dir,
64
+ _install_skill_to,
65
+
66
+ )
67
+ from team_agent.cli.e2e import (
68
+ _fake_spec,
69
+ _run_fake_e2e,
70
+ _run_real_launch_smoke,
71
+ cmd_e2e,
72
+ )
73
+
74
+ __all__ = [
75
+ 'SEND_ORDER_HINT',
76
+ 'TeamAgentArgumentParser',
77
+ 'main',
78
+ 'add_json',
79
+ '_run_leader_passthrough',
80
+ 'emit',
81
+ '_workspace_from_args',
82
+ '_emit_cli_error',
83
+ '_cli_error_payload',
84
+ '_tmux_session_conflict_name',
85
+ '_tmux_session_conflict_next_action',
86
+ '_tmux_session_conflict_action',
87
+ '_provider_args',
88
+ '_leader_launcher_args',
89
+ 'cmd_quick_start',
90
+ 'cmd_codex',
91
+ 'cmd_claude',
92
+ 'cmd_init',
93
+ 'cmd_validate',
94
+ 'cmd_compile',
95
+ '_profile_scope',
96
+ 'cmd_profile_init',
97
+ 'cmd_profile_doctor',
98
+ 'cmd_profile_show',
99
+ 'cmd_launch',
100
+ 'cmd_preflight',
101
+ 'cmd_start',
102
+ 'cmd_wait_ready',
103
+ 'cmd_settle',
104
+ 'cmd_status',
105
+ 'cmd_approvals',
106
+ 'cmd_peek',
107
+ 'cmd_inbox',
108
+ 'cmd_sessions',
109
+ 'cmd_attach_leader',
110
+ 'cmd_send',
111
+ 'cmd_collect',
112
+ 'cmd_diagnose',
113
+ 'cmd_repair_state',
114
+ 'cmd_validate_result',
115
+ 'cmd_doctor',
116
+ 'cmd_shutdown',
117
+ 'cmd_restart',
118
+ 'cmd_start_agent',
119
+ 'cmd_stop_agent',
120
+ 'cmd_reset_agent',
121
+ 'cmd_add_agent',
122
+ 'cmd_fork_agent',
123
+ 'cmd_remove_agent',
124
+ 'cmd_stuck_list',
125
+ 'cmd_stuck_cancel',
126
+ 'cmd_acknowledge_idle',
127
+ 'cmd_allow_peer_talk',
128
+ 'cmd_advanced',
129
+ 'cmd_install_skill',
130
+ '_skill_dest_dir',
131
+ '_install_skill_to',
132
+ 'cmd_e2e',
133
+ '_run_fake_e2e',
134
+ '_run_real_launch_smoke',
135
+ '_fake_spec',
136
+
137
+ ]
@@ -0,0 +1,339 @@
1
+ from __future__ import annotations
2
+
3
+ import argparse
4
+ import json
5
+ import shutil
6
+ import sys
7
+ from pathlib import Path
8
+ from typing import Any
9
+
10
+ from team_agent import compiler, profiles, runtime
11
+ from team_agent.errors import TeamAgentError
12
+ from team_agent.paths import repo_root, team_workspace
13
+ from team_agent.spec import validate_result_envelope
14
+
15
+ from team_agent.cli.helpers import _provider_args
16
+
17
+
18
+ def cmd_quick_start(args: argparse.Namespace) -> dict[str, Any]:
19
+ result = runtime.quick_start(Path(args.agents_dir), name=args.name, yes=args.yes, fresh=args.fresh, team_id=args.team_id)
20
+ if args.json or not result.get("ok"):
21
+ return result
22
+ return result["summary"]
23
+
24
+
25
+ def cmd_codex(args: argparse.Namespace) -> None:
26
+ runtime.start_leader("codex", _provider_args(args.provider_args), Path.cwd().resolve())
27
+
28
+
29
+ def cmd_claude(args: argparse.Namespace) -> None:
30
+ runtime.start_leader("claude_code", _provider_args(args.provider_args), Path.cwd().resolve())
31
+
32
+
33
+ def cmd_init(args: argparse.Namespace) -> dict[str, Any]:
34
+ paths = runtime.init_workspace(Path(args.workspace).resolve(), force=args.force)
35
+ return {"ok": True, "spec": str(paths["spec"]), "state": str(paths["state"])}
36
+
37
+
38
+ def cmd_validate(args: argparse.Namespace) -> dict[str, Any]:
39
+ return runtime.validate_file(Path(args.spec).resolve())
40
+
41
+
42
+ def cmd_compile(args: argparse.Namespace) -> dict[str, Any]:
43
+ result = compiler.compile_team(Path(args.team).resolve(), Path(args.out).resolve())
44
+ return {"ok": True, "team_dir": result["team_dir"], "out": result["out"], "agents": [a["id"] for a in result["spec"]["agents"]]}
45
+
46
+
47
+ def _profile_scope(args: argparse.Namespace) -> tuple[Path, Path | None]:
48
+ team = getattr(args, "team", None)
49
+ if team:
50
+ team_dir = Path(team).resolve()
51
+ return team_workspace(team_dir), team_dir / "profiles"
52
+ return Path(args.workspace).resolve(), None
53
+
54
+
55
+ def cmd_profile_init(args: argparse.Namespace) -> dict[str, Any]:
56
+ workspace, profiles_dir = _profile_scope(args)
57
+ return profiles.init_profile(workspace, args.name, args.auth_mode, profiles_dir=profiles_dir)
58
+
59
+
60
+ def cmd_profile_doctor(args: argparse.Namespace) -> dict[str, Any]:
61
+ workspace, profiles_dir = _profile_scope(args)
62
+ return profiles.doctor_profile(workspace, args.name, profiles_dir=profiles_dir)
63
+
64
+
65
+ def cmd_profile_show(args: argparse.Namespace) -> dict[str, Any]:
66
+ workspace, profiles_dir = _profile_scope(args)
67
+ return profiles.show_profile(workspace, args.name, profiles_dir=profiles_dir)
68
+
69
+
70
+ def cmd_launch(args: argparse.Namespace) -> dict[str, Any]:
71
+ return runtime.launch(Path(args.spec).resolve(), dry_run=args.dry_run, auto_approve=args.yes)
72
+
73
+
74
+ def cmd_preflight(args: argparse.Namespace) -> dict[str, Any]:
75
+ return runtime.preflight(Path(args.team).resolve())
76
+
77
+
78
+ def cmd_start(args: argparse.Namespace) -> dict[str, Any]:
79
+ return runtime.start(Path(args.team).resolve(), yes=args.yes)
80
+
81
+
82
+ def cmd_wait_ready(args: argparse.Namespace) -> dict[str, Any]:
83
+ return runtime.wait_ready(Path(args.workspace).resolve(), timeout=args.timeout)
84
+
85
+
86
+ def cmd_settle(args: argparse.Namespace) -> dict[str, Any]:
87
+ return runtime.settle(Path(args.workspace).resolve())
88
+
89
+
90
+ def cmd_status(args: argparse.Namespace) -> dict[str, Any]:
91
+ if args.json:
92
+ return runtime.status(Path(args.workspace).resolve(), as_json=True, compact=not args.detail)
93
+ return runtime.format_status(Path(args.workspace).resolve(), args.agent)
94
+
95
+
96
+ def cmd_approvals(args: argparse.Namespace) -> dict[str, Any]:
97
+ if args.json:
98
+ return runtime.approvals(Path(args.workspace).resolve(), agent_id=args.agent)
99
+ return runtime.format_approvals(Path(args.workspace).resolve(), agent_id=args.agent)
100
+
101
+
102
+ def cmd_peek(args: argparse.Namespace) -> dict[str, Any]:
103
+ if not args.allow_raw_screen:
104
+ raise TeamAgentError(
105
+ "raw worker terminal inspection requires explicit user authorization and --allow-raw-screen; "
106
+ "normal operation must use status, approvals, inbox, collect, or event logs"
107
+ )
108
+ result = runtime.peek(
109
+ Path(args.workspace).resolve(),
110
+ args.agent,
111
+ head=args.head,
112
+ tail=args.tail,
113
+ search=args.search,
114
+ context=args.context,
115
+ )
116
+ if args.json:
117
+ return result
118
+ return result["text"]
119
+
120
+
121
+ def cmd_inbox(args: argparse.Namespace) -> dict[str, Any]:
122
+ if args.json:
123
+ return runtime.inbox(Path(args.workspace).resolve(), args.agent, limit=args.limit)
124
+ return runtime.format_inbox(Path(args.workspace).resolve(), args.agent, limit=args.limit)
125
+
126
+
127
+ def cmd_sessions(args: argparse.Namespace) -> dict[str, Any]:
128
+ return runtime.sessions(Path(args.workspace).resolve())
129
+
130
+
131
+ def cmd_attach_leader(args: argparse.Namespace) -> dict[str, Any]:
132
+ return runtime.attach_leader(Path(args.workspace).resolve(), pane=args.pane, provider=args.provider)
133
+
134
+
135
+ def cmd_takeover(args: argparse.Namespace) -> dict[str, Any]:
136
+ return runtime.takeover(Path(args.workspace).resolve(), team=args.team, confirm=args.confirm)
137
+
138
+
139
+ def cmd_send(args: argparse.Namespace) -> dict[str, Any]:
140
+ target = _send_target(args)
141
+ return runtime.send_message(
142
+ Path(args.workspace).resolve(),
143
+ target,
144
+ " ".join(args.message),
145
+ task_id=args.task,
146
+ sender=args.sender,
147
+ requires_ack=not args.no_ack,
148
+ confirm_human=args.confirm_human,
149
+ wait_visible=not args.no_wait,
150
+ timeout=args.timeout,
151
+ watch_result=args.watch_result,
152
+ team=args.team,
153
+ )
154
+
155
+
156
+ def _send_target(args: argparse.Namespace) -> str | list[str] | None:
157
+ if getattr(args, "targets", None):
158
+ return [item.strip() for item in args.targets.split(",") if item.strip()]
159
+ return args.target
160
+
161
+
162
+ def cmd_collect(args: argparse.Namespace) -> dict[str, Any]:
163
+ result_file = Path(args.result_file).resolve() if args.result_file else None
164
+ return runtime.collect(Path(args.workspace).resolve(), result_file=result_file)
165
+
166
+
167
+ def cmd_diagnose(args: argparse.Namespace) -> dict[str, Any]:
168
+ return runtime.diagnose(Path(args.workspace).resolve())
169
+
170
+
171
+ def cmd_repair_state(args: argparse.Namespace) -> dict[str, Any]:
172
+ return runtime.repair_state(
173
+ Path(args.workspace).resolve(),
174
+ task_id=args.task,
175
+ assignee=args.assignee,
176
+ status_value=args.status,
177
+ summary=args.summary,
178
+ )
179
+
180
+
181
+ def cmd_validate_result(args: argparse.Namespace) -> dict[str, Any]:
182
+ if args.file:
183
+ raw = Path(args.file).read_text(encoding="utf-8")
184
+ elif args.result:
185
+ raw = args.result
186
+ else:
187
+ raw = sys.stdin.read()
188
+ envelope = json.loads(raw)
189
+ validate_result_envelope(envelope)
190
+ return {"ok": True, "task_id": envelope["task_id"], "agent_id": envelope["agent_id"], "status": envelope["status"]}
191
+
192
+
193
+ def cmd_doctor(args: argparse.Namespace) -> dict[str, Any]:
194
+ spec = Path(args.spec).resolve() if args.spec else None
195
+ return runtime.doctor(spec)
196
+
197
+
198
+ def cmd_shutdown(args: argparse.Namespace) -> dict[str, Any]:
199
+ return runtime.shutdown(Path(args.workspace).resolve(), keep_logs=args.keep_logs, team=args.team)
200
+
201
+
202
+ def cmd_restart(args: argparse.Namespace) -> dict[str, Any]:
203
+ return runtime.restart(Path(args.workspace).resolve(), allow_fresh=args.allow_fresh, team=args.team)
204
+
205
+
206
+ def cmd_start_agent(args: argparse.Namespace) -> dict[str, Any]:
207
+ return runtime.start_agent(
208
+ Path(args.workspace).resolve(),
209
+ args.agent,
210
+ force=args.force,
211
+ open_display=not args.no_display,
212
+ allow_fresh=args.allow_fresh,
213
+ team=args.team,
214
+ )
215
+
216
+
217
+ def cmd_stop_agent(args: argparse.Namespace) -> dict[str, Any]:
218
+ return runtime.stop_agent(Path(args.workspace).resolve(), args.agent, team=args.team)
219
+
220
+
221
+ def cmd_reset_agent(args: argparse.Namespace) -> dict[str, Any]:
222
+ return runtime.reset_agent(
223
+ Path(args.workspace).resolve(),
224
+ args.agent,
225
+ discard_session=args.discard_session,
226
+ open_display=not args.no_display,
227
+ team=args.team,
228
+ )
229
+
230
+
231
+ def cmd_add_agent(args: argparse.Namespace) -> dict[str, Any]:
232
+ return runtime.add_agent(
233
+ Path(args.workspace).resolve(),
234
+ args.agent,
235
+ role_file_path=args.role_file,
236
+ open_display=not args.no_display,
237
+ team=args.team,
238
+ )
239
+
240
+
241
+ def cmd_fork_agent(args: argparse.Namespace) -> dict[str, Any]:
242
+ return runtime.fork_agent(
243
+ Path(args.workspace).resolve(),
244
+ args.source_agent,
245
+ as_agent_id=args.as_agent,
246
+ label=args.label,
247
+ open_display=not args.no_display,
248
+ team=args.team,
249
+ )
250
+
251
+
252
+ def cmd_remove_agent(args: argparse.Namespace) -> dict[str, Any]:
253
+ return runtime.remove_agent(
254
+ Path(args.workspace).resolve(),
255
+ args.agent,
256
+ from_spec=args.from_spec,
257
+ confirm=args.confirm,
258
+ force=args.force,
259
+ team=args.team,
260
+ )
261
+
262
+
263
+ def cmd_stuck_list(args: argparse.Namespace) -> dict[str, Any]:
264
+ return runtime.stuck_list(Path(args.workspace).resolve())
265
+
266
+
267
+ def cmd_stuck_cancel(args: argparse.Namespace) -> dict[str, Any]:
268
+ return runtime.stuck_cancel(
269
+ Path(args.workspace).resolve(),
270
+ args.agent,
271
+ alert_type=args.alert_type,
272
+ suppressed_by="leader",
273
+ )
274
+
275
+
276
+ def cmd_acknowledge_idle(args: argparse.Namespace) -> dict[str, Any]:
277
+ return runtime.acknowledge_idle(Path(args.workspace).resolve(), team=args.team)
278
+
279
+
280
+ def cmd_allow_peer_talk(args: argparse.Namespace) -> dict[str, Any]:
281
+ return runtime.allow_peer_talk(Path(args.workspace).resolve(), args.agent_a, args.agent_b)
282
+
283
+
284
+ def cmd_run_overnight(args: argparse.Namespace) -> dict[str, Any]:
285
+ from team_agent import orchestrator
286
+ workspace = Path(args.workspace).resolve()
287
+ if args.status:
288
+ return orchestrator.plan_status(workspace, plan_id=args.plan_id)
289
+ if args.halt:
290
+ if not args.plan_id:
291
+ raise TeamAgentError("--halt requires --plan-id")
292
+ return orchestrator.halt_plan(workspace, args.plan_id, reason=args.reason)
293
+ if not args.plan:
294
+ raise TeamAgentError("--plan PATH is required unless --status or --halt is used")
295
+ return orchestrator.start_plan(workspace, Path(args.plan).resolve(), start=not args.no_start)
296
+
297
+
298
+ def cmd_advanced(args: argparse.Namespace) -> str:
299
+ return "\n".join(
300
+ [
301
+ "Low-level commands:",
302
+ " init validate compile profile launch preflight start wait-ready settle",
303
+ " sessions attach-leader collect diagnose repair-state validate-result",
304
+ " install-skill e2e",
305
+ ]
306
+ )
307
+
308
+
309
+ def cmd_install_skill(args: argparse.Namespace) -> dict[str, Any]:
310
+ source = repo_root() / "skills" / "team-agent"
311
+ if args.dest and args.target == "all":
312
+ raise TeamAgentError("--dest cannot be combined with --target all")
313
+ if args.dest:
314
+ dest_dir = Path(args.dest).expanduser().resolve()
315
+ return _install_skill_to(source, dest_dir, args.dry_run)
316
+ if args.target == "all":
317
+ results = [
318
+ _install_skill_to(source, _skill_dest_dir("codex"), args.dry_run),
319
+ _install_skill_to(source, _skill_dest_dir("claude"), args.dry_run),
320
+ ]
321
+ return {"ok": all(item["ok"] for item in results), "targets": results}
322
+ return _install_skill_to(source, _skill_dest_dir(args.target), args.dry_run)
323
+
324
+
325
+ def _skill_dest_dir(target: str) -> Path:
326
+ if target == "claude":
327
+ dest_dir = Path.home() / ".claude" / "skills" / "team-agent"
328
+ else:
329
+ dest_dir = Path.home() / ".codex" / "skills" / "team-agent"
330
+ return dest_dir
331
+
332
+
333
+ def _install_skill_to(source: Path, dest_dir: Path, dry_run: bool) -> dict[str, Any]:
334
+ dest = dest_dir / "SKILL.md"
335
+ if dry_run:
336
+ return {"ok": True, "source": str(source / "SKILL.md"), "dest": str(dest), "dry_run": True}
337
+ dest_dir.mkdir(parents=True, exist_ok=True)
338
+ shutil.copytree(source, dest_dir, dirs_exist_ok=True)
339
+ return {"ok": True, "source": str(source / "SKILL.md"), "dest": str(dest)}
@@ -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
+ }