@team-agent/installer 0.2.8 → 0.2.9

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@team-agent/installer",
3
- "version": "0.2.8",
3
+ "version": "0.2.9",
4
4
  "description": "npx installer for Team Agent",
5
5
  "keywords": [
6
6
  "codex",
@@ -245,34 +245,51 @@ def adaptive_blocked(
245
245
  return displays
246
246
 
247
247
 
248
- def close_adaptive_display(state: dict[str, Any], event_log: EventLog) -> None:
248
+ def close_adaptive_display(state: dict[str, Any], event_log: EventLog) -> dict[str, Any]:
249
249
  displays = [
250
250
  (agent_id, agent_state.get("display") or {})
251
251
  for agent_id, agent_state in state.get("agents", {}).items()
252
252
  if (agent_state.get("display") or {}).get("backend") == "adaptive"
253
253
  ]
254
254
  if not displays:
255
- return
255
+ return {"windows": [], "linked_sessions": [], "orphans_detected": {}}
256
256
  killed_windows: list[str] = []
257
257
  linked_sessions: list[str] = []
258
+ session_name = str(state.get("session_name") or "")
259
+ leader_session = _adaptive_leader_session(state, displays)
260
+ needs_named_fallback = False
258
261
  for _agent_id, display in displays:
259
262
  linked = display.get("linked_session")
260
263
  if linked:
261
264
  linked_sessions.append(str(linked))
265
+ if not linked or not display.get("leader_session") or not (display.get("workspace_window") or display.get("window")):
266
+ needs_named_fallback = True
262
267
  seen_targets: set[str] = set()
263
268
  for _agent_id, display in displays:
264
- leader_session = str(display.get("leader_session") or "")
269
+ display_leader_session = str(display.get("leader_session") or "")
265
270
  window_name = str(display.get("workspace_window") or display.get("window") or "")
266
- if not leader_session or not window_name:
271
+ if not display_leader_session or not window_name:
267
272
  continue
268
- target = f"{leader_session}:{window_name}"
273
+ target = f"{display_leader_session}:{window_name}"
269
274
  if target in seen_targets:
270
275
  continue
271
276
  seen_targets.add(target)
272
277
  if kill_adaptive_window(target):
273
278
  killed_windows.append(target)
274
- linked_closed = kill_ghostty_workspace_linked_sessions(linked_sessions)
275
- event_log.write("display.adaptive_closed", windows=killed_windows, linked_sessions=linked_closed)
279
+ removed_orphans: dict[str, list[str]] = {}
280
+ if needs_named_fallback and leader_session and session_name:
281
+ named_windows = close_adaptive_windows(leader_session, session_name, event_log)
282
+ killed_windows.extend(named_windows)
283
+ linked_closed = kill_ghostty_workspace_linked_sessions(linked_sessions)
284
+ named_closed, named_failed = _kill_adaptive_named_display_sessions(session_name, [agent_id for agent_id, _display in displays])
285
+ linked_closed.extend(named_closed)
286
+ removed_orphans = _adaptive_orphan_summary(named_closed, named_windows)
287
+ else:
288
+ named_failed = []
289
+ linked_closed = kill_ghostty_workspace_linked_sessions(linked_sessions)
290
+ orphans = _adaptive_orphans(session_name, leader_session, [agent_id for agent_id, _display in displays], named_failed) if needs_named_fallback else {}
291
+ event_log.write("display.adaptive_closed", windows=killed_windows, linked_sessions=linked_closed, orphans_detected=orphans, orphans_removed=removed_orphans)
292
+ return {"windows": killed_windows, "linked_sessions": linked_closed, "orphans_detected": orphans, "orphans_removed": removed_orphans}
276
293
 
277
294
 
278
295
  def close_adaptive_windows(leader_session: str, session_name: str, event_log: EventLog | None = None) -> list[str]:
@@ -293,6 +310,75 @@ def close_adaptive_windows(leader_session: str, session_name: str, event_log: Ev
293
310
  return killed
294
311
 
295
312
 
313
+ def _adaptive_leader_session(state: dict[str, Any], displays: list[tuple[str, dict[str, Any]]]) -> str:
314
+ for _agent_id, display in displays:
315
+ if display.get("leader_session"):
316
+ return str(display["leader_session"])
317
+ receiver = state.get("leader_receiver") if isinstance(state.get("leader_receiver"), dict) else {}
318
+ return str(receiver.get("session_name") or "")
319
+
320
+
321
+ def _adaptive_named_display_sessions(session_name: str, agent_ids: list[str], fallback_exact: bool = True) -> list[str]:
322
+ from team_agent.runtime import run_cmd
323
+ if not session_name or not agent_ids:
324
+ return []
325
+ exact = [ghostty_display_session_name(session_name, agent_id) for agent_id in agent_ids]
326
+ proc = run_cmd(["tmux", "list-sessions", "-F", "#{session_name}"], timeout=10)
327
+ if proc.returncode != 0:
328
+ return exact if fallback_exact else []
329
+ prefixes = [ghostty_display_session_name(session_name, agent_id).rsplit("__", 1)[0] + "__" for agent_id in agent_ids]
330
+ matched = [name for name in proc.stdout.splitlines() if any(name.startswith(prefix) for prefix in prefixes)]
331
+ return matched or (exact if fallback_exact else [])
332
+
333
+
334
+ def _kill_adaptive_named_display_sessions(session_name: str, agent_ids: list[str]) -> tuple[list[str], list[str]]:
335
+ from team_agent.runtime import run_cmd
336
+ killed: list[str] = []
337
+ failed: list[str] = []
338
+ for display_session in _adaptive_named_display_sessions(session_name, agent_ids):
339
+ proc = run_cmd(["tmux", "kill-session", "-t", display_session], timeout=10)
340
+ if proc.returncode == 0:
341
+ killed.append(display_session)
342
+ else:
343
+ failed.append(display_session)
344
+ return killed, failed
345
+
346
+
347
+ def _adaptive_orphans(session_name: str, leader_session: str, agent_ids: list[str], failed_sessions: list[str]) -> dict[str, list[str]]:
348
+ display_sessions = sorted(set([*_adaptive_named_display_sessions(session_name, agent_ids, fallback_exact=False), *failed_sessions]))
349
+ windows: list[str] = []
350
+ if leader_session and session_name:
351
+ windows = _adaptive_window_orphans(leader_session, session_name)
352
+ if not display_sessions and not windows:
353
+ return {}
354
+ return {
355
+ "adaptive_display_sessions": sorted(set(display_sessions)),
356
+ "adaptive_overview_windows": sorted(set(windows)),
357
+ }
358
+
359
+
360
+ def _adaptive_orphan_summary(display_sessions: list[str], windows: list[str]) -> dict[str, list[str]]:
361
+ if not display_sessions and not windows:
362
+ return {}
363
+ return {
364
+ "adaptive_display_sessions": sorted(set(display_sessions)),
365
+ "adaptive_overview_windows": sorted(set(windows)),
366
+ }
367
+
368
+
369
+ def _adaptive_window_orphans(leader_session: str, session_name: str) -> list[str]:
370
+ from team_agent.runtime import run_cmd
371
+ prefix = f"team-agent:{session_name}:overview"
372
+ proc = run_cmd(["tmux", "list-windows", "-t", leader_session, "-F", "#{window_name}"], timeout=10)
373
+ if proc.returncode != 0:
374
+ return []
375
+ return [
376
+ f"{leader_session}:{window_name}"
377
+ for window_name in proc.stdout.splitlines()
378
+ if window_name == prefix or window_name.startswith(f"{prefix}-")
379
+ ]
380
+
381
+
296
382
  def kill_adaptive_window(target: str) -> bool:
297
383
  from team_agent.runtime import run_cmd
298
384
  proc = run_cmd(["tmux", "kill-window", "-t", target], timeout=10)
@@ -8,9 +8,10 @@ from team_agent.display.ghostty import ghostty_pids_by_title
8
8
  from team_agent.display.workspace import kill_ghostty_workspace_linked_sessions
9
9
 
10
10
 
11
- def close_team_display_backends(state: dict[str, Any], event_log: EventLog) -> None:
12
- close_adaptive_display(state, event_log)
11
+ def close_team_display_backends(state: dict[str, Any], event_log: EventLog) -> dict[str, Any]:
12
+ result = close_adaptive_display(state, event_log)
13
13
  close_ghostty_workspace(state, event_log)
14
+ return result
14
15
 
15
16
 
16
17
  def close_ghostty_display(
@@ -527,7 +527,7 @@ def shutdown(workspace: Path, keep_logs: bool = True, team: str | None = None) -
527
527
  if proc.returncode == 0:
528
528
  log_path.write_text(proc.stdout, encoding="utf-8")
529
529
  captured.append(str(log_path))
530
- _close_team_display_backends(state, event_log)
530
+ display_cleanup = _close_team_display_backends(state, event_log)
531
531
  for agent_id, agent_state in state.get("agents", {}).items():
532
532
  _close_ghostty_display(agent_id, agent_state, event_log)
533
533
  closed_displays.add(agent_id)
@@ -541,7 +541,7 @@ def shutdown(workspace: Path, keep_logs: bool = True, team: str | None = None) -
541
541
  event_log.write("shutdown.kill_session", session=session_name, keep_logs=keep_logs, captured=captured)
542
542
  else:
543
543
  event_log.write("shutdown.idempotent", session=session_name, reason="session missing")
544
- _close_team_display_backends(state, event_log)
544
+ display_cleanup = _close_team_display_backends(state, event_log)
545
545
  for agent_id, agent_state in state.get("agents", {}).items():
546
546
  if agent_id not in closed_displays:
547
547
  _close_ghostty_display(agent_id, agent_state, event_log)
@@ -573,7 +573,7 @@ def shutdown(workspace: Path, keep_logs: bool = True, team: str | None = None) -
573
573
  archive_path, teams_remaining, new_active = _commit_shutdown_cleanup(
574
574
  workspace, str(resolved_team_id or ""), session_name, event_log
575
575
  )
576
- return {
576
+ result = {
577
577
  "ok": True,
578
578
  "session_name": session_name,
579
579
  "team": resolved_team_id,
@@ -584,6 +584,24 @@ def shutdown(workspace: Path, keep_logs: bool = True, team: str | None = None) -
584
584
  "new_active_team_key": new_active,
585
585
  "cleanup_mode": "synchronous_committed",
586
586
  }
587
+ removed_orphans = (display_cleanup or {}).get("orphans_removed") or {}
588
+ remaining_orphans = (display_cleanup or {}).get("orphans_detected") or {}
589
+ if removed_orphans:
590
+ result["orphans_detected"] = removed_orphans
591
+ result["warnings"] = ["Adaptive display tmux objects were found and removed during shutdown cleanup."]
592
+ if remaining_orphans:
593
+ result["cleanup_mode"] = "synchronous_with_orphans"
594
+ result["orphans_detected"] = remaining_orphans
595
+ result["warning"] = "Adaptive display tmux objects remain after shutdown cleanup."
596
+ event_log.write(
597
+ "shutdown.orphans_detected",
598
+ warning=result["warning"],
599
+ message=result["warning"],
600
+ orphans_detected=remaining_orphans,
601
+ adaptive_display_sessions=remaining_orphans.get("adaptive_display_sessions", []),
602
+ adaptive_overview_windows=remaining_orphans.get("adaptive_overview_windows", []),
603
+ )
604
+ return result
587
605
 
588
606
 
589
607
  def _commit_shutdown_cleanup(