@team-agent/installer 0.2.11 → 0.3.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/Cargo.lock +744 -0
- package/Cargo.toml +34 -0
- package/crates/team-agent/Cargo.toml +33 -0
- package/crates/team-agent/src/cli/adapters.rs +1343 -0
- package/crates/team-agent/src/cli/diagnose.rs +554 -0
- package/crates/team-agent/src/cli/emit.rs +1077 -0
- package/crates/team-agent/src/cli/helpers.rs +88 -0
- package/crates/team-agent/src/cli/leader.rs +216 -0
- package/crates/team-agent/src/cli/mod.rs +1141 -0
- package/crates/team-agent/src/cli/profile.rs +306 -0
- package/crates/team-agent/src/cli/send.rs +215 -0
- package/crates/team-agent/src/cli/status.rs +179 -0
- package/crates/team-agent/src/cli/status_port.rs +502 -0
- package/crates/team-agent/src/cli/tests/base.rs +616 -0
- package/crates/team-agent/src/cli/tests/compile.rs +96 -0
- package/crates/team-agent/src/cli/tests/divergence.rs +509 -0
- package/crates/team-agent/src/cli/tests/lane_c.rs +333 -0
- package/crates/team-agent/src/cli/tests/leader_watch.rs +395 -0
- package/crates/team-agent/src/cli/tests/main_preserved.rs +675 -0
- package/crates/team-agent/src/cli/tests/missing_subcommands.rs +390 -0
- package/crates/team-agent/src/cli/tests/mod.rs +97 -0
- package/crates/team-agent/src/cli/tests/peer_allow.rs +137 -0
- package/crates/team-agent/src/cli/tests/repair_state_byte_lock.rs +302 -0
- package/crates/team-agent/src/cli/tests/run_delegation.rs +305 -0
- package/crates/team-agent/src/cli/tests/status_send.rs +385 -0
- package/crates/team-agent/src/cli/tests/verb_profile.rs +182 -0
- package/crates/team-agent/src/cli/tests/verb_settle.rs +236 -0
- package/crates/team-agent/src/cli/tests/verb_validate.rs +184 -0
- package/crates/team-agent/src/cli/types.rs +605 -0
- package/crates/team-agent/src/compiler/tests.rs +701 -0
- package/crates/team-agent/src/compiler.rs +489 -0
- package/crates/team-agent/src/coordinator/backoff.rs +153 -0
- package/crates/team-agent/src/coordinator/health.rs +436 -0
- package/crates/team-agent/src/coordinator/mod.rs +80 -0
- package/crates/team-agent/src/coordinator/orphan.rs +179 -0
- package/crates/team-agent/src/coordinator/tests/abnormal.rs +255 -0
- package/crates/team-agent/src/coordinator/tests/basics.rs +262 -0
- package/crates/team-agent/src/coordinator/tests/daemon.rs +323 -0
- package/crates/team-agent/src/coordinator/tests/health_sync.rs +263 -0
- package/crates/team-agent/src/coordinator/tests/main_preserved.rs +136 -0
- package/crates/team-agent/src/coordinator/tests/mod.rs +310 -0
- package/crates/team-agent/src/coordinator/tests/spine.rs +261 -0
- package/crates/team-agent/src/coordinator/tests/takeover.rs +227 -0
- package/crates/team-agent/src/coordinator/tests/tick_core.rs +256 -0
- package/crates/team-agent/src/coordinator/tests/watch.rs +167 -0
- package/crates/team-agent/src/coordinator/tick.rs +2032 -0
- package/crates/team-agent/src/coordinator/types.rs +584 -0
- package/crates/team-agent/src/db/migration.rs +716 -0
- package/crates/team-agent/src/db/mod.rs +23 -0
- package/crates/team-agent/src/db/schema.rs +378 -0
- package/crates/team-agent/src/event_log.rs +375 -0
- package/crates/team-agent/src/fake_worker.rs +253 -0
- package/crates/team-agent/src/leader/helpers.rs +190 -0
- package/crates/team-agent/src/leader/inject.rs +33 -0
- package/crates/team-agent/src/leader/lease.rs +1063 -0
- package/crates/team-agent/src/leader/mod.rs +99 -0
- package/crates/team-agent/src/leader/owner_bind.rs +292 -0
- package/crates/team-agent/src/leader/rediscover/tests.rs +525 -0
- package/crates/team-agent/src/leader/rediscover.rs +1099 -0
- package/crates/team-agent/src/leader/start.rs +273 -0
- package/crates/team-agent/src/leader/takeover.rs +235 -0
- package/crates/team-agent/src/leader/tests/basics.rs +183 -0
- package/crates/team-agent/src/leader/tests/byte_findings.rs +234 -0
- package/crates/team-agent/src/leader/tests/identity.rs +206 -0
- package/crates/team-agent/src/leader/tests/idle.rs +271 -0
- package/crates/team-agent/src/leader/tests/lease_api.rs +225 -0
- package/crates/team-agent/src/leader/tests/lease_claim.rs +253 -0
- package/crates/team-agent/src/leader/tests/mod.rs +125 -0
- package/crates/team-agent/src/leader/tests/rediscover.rs +351 -0
- package/crates/team-agent/src/leader/tests/wake_start_owner.rs +204 -0
- package/crates/team-agent/src/leader/types.rs +487 -0
- package/crates/team-agent/src/lib.rs +85 -0
- package/crates/team-agent/src/lifecycle/display.rs +228 -0
- package/crates/team-agent/src/lifecycle/helpers.rs +112 -0
- package/crates/team-agent/src/lifecycle/launch/plan.rs +227 -0
- package/crates/team-agent/src/lifecycle/launch.rs +1833 -0
- package/crates/team-agent/src/lifecycle/mod.rs +62 -0
- package/crates/team-agent/src/lifecycle/restart/agent.rs +533 -0
- package/crates/team-agent/src/lifecycle/restart/common.rs +517 -0
- package/crates/team-agent/src/lifecycle/restart/orchestrator.rs +41 -0
- package/crates/team-agent/src/lifecycle/restart/rebuild.rs +268 -0
- package/crates/team-agent/src/lifecycle/restart/remove.rs +780 -0
- package/crates/team-agent/src/lifecycle/restart/selection.rs +208 -0
- package/crates/team-agent/src/lifecycle/restart/team_state.rs +242 -0
- package/crates/team-agent/src/lifecycle/restart.rs +76 -0
- package/crates/team-agent/src/lifecycle/tests/agent_ops.rs +455 -0
- package/crates/team-agent/src/lifecycle/tests/core.rs +989 -0
- package/crates/team-agent/src/lifecycle/tests/lane_ops.rs +583 -0
- package/crates/team-agent/src/lifecycle/tests/launch_spawn.rs +933 -0
- package/crates/team-agent/src/lifecycle/tests/main_preserved.rs +265 -0
- package/crates/team-agent/src/lifecycle/tests.rs +27 -0
- package/crates/team-agent/src/lifecycle/types.rs +685 -0
- package/crates/team-agent/src/main.rs +41 -0
- package/crates/team-agent/src/mcp_server/helpers.rs +228 -0
- package/crates/team-agent/src/mcp_server/mod.rs +183 -0
- package/crates/team-agent/src/mcp_server/normalize.rs +312 -0
- package/crates/team-agent/src/mcp_server/tests/golden.rs +283 -0
- package/crates/team-agent/src/mcp_server/tests/normalize.rs +244 -0
- package/crates/team-agent/src/mcp_server/tests/scoped.rs +189 -0
- package/crates/team-agent/src/mcp_server/tests/send.rs +222 -0
- package/crates/team-agent/src/mcp_server/tests/tools.rs +158 -0
- package/crates/team-agent/src/mcp_server/tests/wire.rs +159 -0
- package/crates/team-agent/src/mcp_server/tests.rs +38 -0
- package/crates/team-agent/src/mcp_server/tools.rs +603 -0
- package/crates/team-agent/src/mcp_server/types.rs +421 -0
- package/crates/team-agent/src/mcp_server/wire.rs +388 -0
- package/crates/team-agent/src/message_store.rs +767 -0
- package/crates/team-agent/src/messaging/activity.rs +433 -0
- package/crates/team-agent/src/messaging/delivery.rs +542 -0
- package/crates/team-agent/src/messaging/helpers.rs +209 -0
- package/crates/team-agent/src/messaging/leader_receiver.rs +340 -0
- package/crates/team-agent/src/messaging/mod.rs +147 -0
- package/crates/team-agent/src/messaging/peers.rs +32 -0
- package/crates/team-agent/src/messaging/results.rs +537 -0
- package/crates/team-agent/src/messaging/scheduler.rs +344 -0
- package/crates/team-agent/src/messaging/selftest.rs +100 -0
- package/crates/team-agent/src/messaging/send.rs +582 -0
- package/crates/team-agent/src/messaging/tests/basic.rs +357 -0
- package/crates/team-agent/src/messaging/tests/main_preserved.rs +122 -0
- package/crates/team-agent/src/messaging/tests/mod.rs +293 -0
- package/crates/team-agent/src/messaging/tests/runtime.rs +1422 -0
- package/crates/team-agent/src/messaging/tests/spine.rs +437 -0
- package/crates/team-agent/src/messaging/trust.rs +192 -0
- package/crates/team-agent/src/messaging/types.rs +355 -0
- package/crates/team-agent/src/messaging/watchers.rs +591 -0
- package/crates/team-agent/src/model/enums.rs +311 -0
- package/crates/team-agent/src/model/errors.rs +17 -0
- package/crates/team-agent/src/model/ids.rs +155 -0
- package/crates/team-agent/src/model/mod.rs +22 -0
- package/crates/team-agent/src/model/paths.rs +228 -0
- package/crates/team-agent/src/model/permissions.rs +567 -0
- package/crates/team-agent/src/model/routing.rs +340 -0
- package/crates/team-agent/src/model/spec.rs +680 -0
- package/crates/team-agent/src/model/task_graph.rs +380 -0
- package/crates/team-agent/src/model/testdata/fuzz.golden.yaml +43 -0
- package/crates/team-agent/src/model/testdata/fuzz.yaml +43 -0
- package/crates/team-agent/src/model/testdata/spec_invalid_a.yaml +207 -0
- package/crates/team-agent/src/model/testdata/team.spec.golden.yaml +206 -0
- package/crates/team-agent/src/model/testdata/team.spec.yaml +206 -0
- package/crates/team-agent/src/model/yaml/tests.rs +288 -0
- package/crates/team-agent/src/model/yaml.rs +800 -0
- package/crates/team-agent/src/packaging/install.rs +305 -0
- package/crates/team-agent/src/packaging/migrate.rs +30 -0
- package/crates/team-agent/src/packaging/mod.rs +82 -0
- package/crates/team-agent/src/packaging/repair.rs +24 -0
- package/crates/team-agent/src/packaging/tests.rs +829 -0
- package/crates/team-agent/src/packaging/types.rs +369 -0
- package/crates/team-agent/src/provider/adapter.rs +801 -0
- package/crates/team-agent/src/provider/approvals/mod.rs +2 -0
- package/crates/team-agent/src/provider/approvals/parsing.rs +452 -0
- package/crates/team-agent/src/provider/approvals/runtime_prompts.rs +163 -0
- package/crates/team-agent/src/provider/classify.rs +456 -0
- package/crates/team-agent/src/provider/faults.rs +136 -0
- package/crates/team-agent/src/provider/helpers.rs +41 -0
- package/crates/team-agent/src/provider/mod.rs +53 -0
- package/crates/team-agent/src/provider/startup_prompt.rs +423 -0
- package/crates/team-agent/src/provider/tests/adapter.rs +239 -0
- package/crates/team-agent/src/provider/tests/classify.rs +240 -0
- package/crates/team-agent/src/provider/tests/faults.rs +120 -0
- package/crates/team-agent/src/provider/tests/idle.rs +208 -0
- package/crates/team-agent/src/provider/tests/wire.rs +213 -0
- package/crates/team-agent/src/provider/tests.rs +31 -0
- package/crates/team-agent/src/provider/types.rs +424 -0
- package/crates/team-agent/src/state/identity.rs +656 -0
- package/crates/team-agent/src/state/mod.rs +58 -0
- package/crates/team-agent/src/state/owner_gate.rs +423 -0
- package/crates/team-agent/src/state/persist.rs +712 -0
- package/crates/team-agent/src/state/projection.rs +657 -0
- package/crates/team-agent/src/state/selector.rs +105 -0
- package/crates/team-agent/src/state/testdata/state-rich.canonical.json +133 -0
- package/crates/team-agent/src/tmux_backend/tests.rs +586 -0
- package/crates/team-agent/src/tmux_backend.rs +758 -0
- package/crates/team-agent/src/transport/test_support.rs +252 -0
- package/crates/team-agent/src/transport/tests/behavior.rs +327 -0
- package/crates/team-agent/src/transport/tests/mod.rs +199 -0
- package/crates/team-agent/src/transport/tests/wire.rs +527 -0
- package/crates/team-agent/src/transport.rs +774 -0
- package/npm/install.mjs +90 -106
- package/package.json +15 -13
- package/crates/team-agent-core/Cargo.toml +0 -12
- package/crates/team-agent-core/src/lib.rs +0 -332
- package/crates/team-agent-core/src/main.rs +0 -152
- package/pyproject.toml +0 -18
- package/scripts/install.py +0 -88
- package/scripts/run_regression_tests.py +0 -83
- package/src/team_agent/__init__.py +0 -3
- package/src/team_agent/__main__.py +0 -5
- package/src/team_agent/_legacy_pane_discovery.py +0 -186
- package/src/team_agent/abnormal_track.py +0 -253
- package/src/team_agent/approvals/__init__.py +0 -65
- package/src/team_agent/approvals/constants.py +0 -6
- package/src/team_agent/approvals/parsing.py +0 -176
- package/src/team_agent/approvals/runtime_prompts.py +0 -171
- package/src/team_agent/approvals/status.py +0 -176
- package/src/team_agent/cli/__init__.py +0 -137
- package/src/team_agent/cli/commands.py +0 -481
- package/src/team_agent/cli/e2e.py +0 -202
- package/src/team_agent/cli/helpers.py +0 -226
- package/src/team_agent/cli/parser.py +0 -540
- package/src/team_agent/compiler.py +0 -334
- package/src/team_agent/coordinator/__init__.py +0 -53
- package/src/team_agent/coordinator/__main__.py +0 -119
- package/src/team_agent/coordinator/lifecycle.py +0 -411
- package/src/team_agent/coordinator/metadata.py +0 -61
- package/src/team_agent/coordinator/paths.py +0 -17
- package/src/team_agent/diagnose/__init__.py +0 -48
- package/src/team_agent/diagnose/checks.py +0 -101
- package/src/team_agent/diagnose/comms.py +0 -213
- package/src/team_agent/diagnose/health.py +0 -241
- package/src/team_agent/diagnose/orphan_cleanup.py +0 -364
- package/src/team_agent/diagnose/preflight.py +0 -194
- package/src/team_agent/diagnose/quick_start.py +0 -324
- package/src/team_agent/display/__init__.py +0 -92
- package/src/team_agent/display/adaptive.py +0 -511
- package/src/team_agent/display/backend.py +0 -46
- package/src/team_agent/display/close.py +0 -154
- package/src/team_agent/display/ghostty.py +0 -77
- package/src/team_agent/display/rebuild.py +0 -102
- package/src/team_agent/display/tiling.py +0 -156
- package/src/team_agent/display/worker_window.py +0 -114
- package/src/team_agent/display/workspace.py +0 -382
- package/src/team_agent/errors.py +0 -10
- package/src/team_agent/events.py +0 -84
- package/src/team_agent/fake_worker.py +0 -80
- package/src/team_agent/idle_predicate.py +0 -218
- package/src/team_agent/idle_takeover.py +0 -59
- package/src/team_agent/idle_takeover_wiring.py +0 -114
- package/src/team_agent/launch/__init__.py +0 -41
- package/src/team_agent/launch/bootstrap.py +0 -85
- package/src/team_agent/launch/config.py +0 -106
- package/src/team_agent/launch/core.py +0 -301
- package/src/team_agent/launch/requirements.py +0 -57
- package/src/team_agent/leader/__init__.py +0 -926
- package/src/team_agent/leader_binding.py +0 -183
- package/src/team_agent/lifecycle/__init__.py +0 -5
- package/src/team_agent/lifecycle/agents.py +0 -278
- package/src/team_agent/lifecycle/operations.py +0 -411
- package/src/team_agent/lifecycle/paste_buffer_hygiene.py +0 -39
- package/src/team_agent/lifecycle/start.py +0 -363
- package/src/team_agent/mcp_server/__init__.py +0 -42
- package/src/team_agent/mcp_server/__main__.py +0 -7
- package/src/team_agent/mcp_server/contracts.py +0 -148
- package/src/team_agent/mcp_server/normalize.py +0 -257
- package/src/team_agent/mcp_server/server.py +0 -150
- package/src/team_agent/mcp_server/tools.py +0 -352
- package/src/team_agent/message_store/__init__.py +0 -23
- package/src/team_agent/message_store/agent_health.py +0 -113
- package/src/team_agent/message_store/core.py +0 -497
- package/src/team_agent/message_store/leader_notification_log.py +0 -198
- package/src/team_agent/message_store/result_watchers.py +0 -251
- package/src/team_agent/message_store/schema.py +0 -308
- package/src/team_agent/message_store/schema_migration.py +0 -448
- package/src/team_agent/messaging/__init__.py +0 -1
- package/src/team_agent/messaging/activity_detector.py +0 -262
- package/src/team_agent/messaging/delivery.py +0 -504
- package/src/team_agent/messaging/deps.py +0 -247
- package/src/team_agent/messaging/idle_alerts.py +0 -423
- package/src/team_agent/messaging/internal_delivery.py +0 -46
- package/src/team_agent/messaging/leader.py +0 -497
- package/src/team_agent/messaging/leader_api_errors.py +0 -216
- package/src/team_agent/messaging/leader_panes.py +0 -673
- package/src/team_agent/messaging/owner_bypass.py +0 -29
- package/src/team_agent/messaging/result_delivery.py +0 -539
- package/src/team_agent/messaging/results.py +0 -447
- package/src/team_agent/messaging/scheduler.py +0 -450
- package/src/team_agent/messaging/send.py +0 -532
- package/src/team_agent/messaging/session_drift.py +0 -94
- package/src/team_agent/messaging/tmux_io.py +0 -506
- package/src/team_agent/messaging/tmux_prompt.py +0 -338
- package/src/team_agent/messaging/trust_auto_answer.py +0 -52
- package/src/team_agent/orchestrator/__init__.py +0 -376
- package/src/team_agent/orchestrator/plan.py +0 -122
- package/src/team_agent/orchestrator/state.py +0 -128
- package/src/team_agent/paths.py +0 -45
- package/src/team_agent/permissions.py +0 -123
- package/src/team_agent/profiles/__init__.py +0 -82
- package/src/team_agent/profiles/constants.py +0 -19
- package/src/team_agent/profiles/core.py +0 -407
- package/src/team_agent/profiles/helpers.py +0 -69
- package/src/team_agent/profiles/provider_env.py +0 -188
- package/src/team_agent/profiles/smoke.py +0 -201
- package/src/team_agent/provider_cli/__init__.py +0 -43
- package/src/team_agent/provider_cli/adapter.py +0 -172
- package/src/team_agent/provider_cli/base.py +0 -48
- package/src/team_agent/provider_cli/claude.py +0 -503
- package/src/team_agent/provider_cli/codex.py +0 -336
- package/src/team_agent/provider_cli/copilot.py +0 -8
- package/src/team_agent/provider_cli/fake.py +0 -39
- package/src/team_agent/provider_cli/gemini.py +0 -95
- package/src/team_agent/provider_cli/opencode.py +0 -8
- package/src/team_agent/provider_cli/prompt.py +0 -62
- package/src/team_agent/provider_cli/registry.py +0 -18
- package/src/team_agent/provider_cli/unsupported.py +0 -32
- package/src/team_agent/provider_state/README.md +0 -78
- package/src/team_agent/provider_state/__init__.py +0 -91
- package/src/team_agent/provider_state/claude.py +0 -86
- package/src/team_agent/provider_state/codex.py +0 -84
- package/src/team_agent/provider_state/common.py +0 -207
- package/src/team_agent/provider_state/registry.py +0 -118
- package/src/team_agent/providers.py +0 -163
- package/src/team_agent/quality_gates.py +0 -104
- package/src/team_agent/restart/__init__.py +0 -34
- package/src/team_agent/restart/orchestration.py +0 -554
- package/src/team_agent/restart/selection.py +0 -89
- package/src/team_agent/restart/snapshot.py +0 -70
- package/src/team_agent/routing.py +0 -84
- package/src/team_agent/runtime.py +0 -1243
- package/src/team_agent/rust_core.py +0 -327
- package/src/team_agent/sessions/__init__.py +0 -25
- package/src/team_agent/sessions/capture.py +0 -144
- package/src/team_agent/sessions/inventory.py +0 -44
- package/src/team_agent/sessions/resume.py +0 -135
- package/src/team_agent/simple_yaml.py +0 -236
- package/src/team_agent/spec.py +0 -370
- package/src/team_agent/state.py +0 -693
- package/src/team_agent/status/__init__.py +0 -63
- package/src/team_agent/status/approvals.py +0 -52
- package/src/team_agent/status/compact.py +0 -158
- package/src/team_agent/status/constants.py +0 -18
- package/src/team_agent/status/inbox.py +0 -58
- package/src/team_agent/status/peek.py +0 -117
- package/src/team_agent/status/queries.py +0 -199
- package/src/team_agent/task_graph.py +0 -80
- package/src/team_agent/terminal.py +0 -57
- package/src/team_agent/wake.py +0 -58
- package/src/team_agent/watch/__init__.py +0 -145
|
@@ -1,327 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
import json
|
|
4
|
-
import platform
|
|
5
|
-
import re
|
|
6
|
-
import shutil
|
|
7
|
-
import subprocess
|
|
8
|
-
from pathlib import Path
|
|
9
|
-
from typing import Any
|
|
10
|
-
|
|
11
|
-
from team_agent.paths import repo_root
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
_LEADER_ENV_KEYS = (
|
|
15
|
-
"TEAM_AGENT_LEADER_SESSION_UUID",
|
|
16
|
-
"TEAM_AGENT_LEADER_PANE_ID",
|
|
17
|
-
"TEAM_AGENT_LEADER_PROVIDER",
|
|
18
|
-
"TEAM_AGENT_MACHINE_FINGERPRINT",
|
|
19
|
-
"TEAM_AGENT_LEADER_SESSION_UUID_OVERRIDE",
|
|
20
|
-
)
|
|
21
|
-
_LEADER_SHAPED_COMMANDS = {"codex", "claude", "claude.exe", "node", "nodejs"}
|
|
22
|
-
_PANE_ENV_SCAN_TIMEOUT_SECONDS = 2.0
|
|
23
|
-
_run_subprocess = subprocess.run # test-injectable indirection
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
def core_binary() -> Path | None:
|
|
27
|
-
configured = shutil.which("team-agent-core")
|
|
28
|
-
if configured:
|
|
29
|
-
return Path(configured)
|
|
30
|
-
local = repo_root() / "crates" / "team-agent-core" / "target" / "debug" / "team-agent-core"
|
|
31
|
-
if local.exists():
|
|
32
|
-
return local
|
|
33
|
-
return None
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
def call_core(command: str, payload: dict[str, Any] | str | None = None) -> dict[str, Any]:
|
|
37
|
-
binary = core_binary()
|
|
38
|
-
if not binary:
|
|
39
|
-
return {"ok": False, "error": "team-agent-core binary not found", "fallback": True}
|
|
40
|
-
raw = json.dumps(payload, ensure_ascii=False) if isinstance(payload, dict) else (payload or "")
|
|
41
|
-
proc = subprocess.run(
|
|
42
|
-
[str(binary), command, "--json"],
|
|
43
|
-
input=raw,
|
|
44
|
-
text=True,
|
|
45
|
-
capture_output=True,
|
|
46
|
-
timeout=10,
|
|
47
|
-
check=False,
|
|
48
|
-
)
|
|
49
|
-
try:
|
|
50
|
-
result = json.loads(proc.stdout or "{}")
|
|
51
|
-
except json.JSONDecodeError:
|
|
52
|
-
result = {"ok": False, "error": proc.stdout.strip() or proc.stderr.strip()}
|
|
53
|
-
if proc.returncode != 0:
|
|
54
|
-
result.setdefault("ok", False)
|
|
55
|
-
result.setdefault("error", proc.stderr.strip() or "team-agent-core failed")
|
|
56
|
-
result["engine"] = "rust" if result.get("ok") else "rust_failed"
|
|
57
|
-
return result
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
def render_message(payload: dict[str, Any]) -> dict[str, Any]:
|
|
61
|
-
result = call_core("render-message", payload)
|
|
62
|
-
if result.get("ok"):
|
|
63
|
-
return result
|
|
64
|
-
sender = payload.get("from") or payload.get("sender") or "unknown"
|
|
65
|
-
task_id = payload.get("task_id")
|
|
66
|
-
content = payload.get("content") or ""
|
|
67
|
-
token = payload.get("message_id") or "missing"
|
|
68
|
-
header = f"Team Agent message from {sender}"
|
|
69
|
-
if task_id:
|
|
70
|
-
header += f" for {task_id}"
|
|
71
|
-
return {
|
|
72
|
-
"ok": True,
|
|
73
|
-
"text": f"{header}:\n\n{content}\n\n[team-agent-token:{token}]",
|
|
74
|
-
"token": token,
|
|
75
|
-
"engine": "python_fallback",
|
|
76
|
-
"fallback_reason": result.get("error"),
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
def redact_text(text: str) -> dict[str, Any]:
|
|
81
|
-
result = call_core("redact", {"text": text})
|
|
82
|
-
if result.get("ok"):
|
|
83
|
-
return result
|
|
84
|
-
redacted = []
|
|
85
|
-
for chunk in text.split():
|
|
86
|
-
lower = chunk.lower()
|
|
87
|
-
if (
|
|
88
|
-
"api_key" in lower
|
|
89
|
-
or "apikey" in lower
|
|
90
|
-
or "token=" in lower
|
|
91
|
-
or "secret" in lower
|
|
92
|
-
or lower == "bearer"
|
|
93
|
-
or chunk.startswith("sk-")
|
|
94
|
-
or _looks_base64_secret(chunk)
|
|
95
|
-
):
|
|
96
|
-
redacted.append("[REDACTED]")
|
|
97
|
-
else:
|
|
98
|
-
redacted.append(chunk)
|
|
99
|
-
return {"ok": True, "text": " ".join(redacted), "engine": "python_fallback", "fallback_reason": result.get("error")}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
def validate_profile_metadata(profile: dict[str, Any]) -> dict[str, Any]:
|
|
103
|
-
result = call_core("validate-profile", profile)
|
|
104
|
-
if result.get("ok") or result.get("errors"):
|
|
105
|
-
return result
|
|
106
|
-
errors: list[str] = []
|
|
107
|
-
if profile.get("auth_mode") not in {"subscription", "official_api", "compatible_api"}:
|
|
108
|
-
errors.append("auth_mode must be subscription, official_api, or compatible_api")
|
|
109
|
-
for field in ["provider", "model", "profile"]:
|
|
110
|
-
if not profile.get(field):
|
|
111
|
-
errors.append(f"{field} must not be empty")
|
|
112
|
-
if contains_inline_secret(str(profile.get(field) or "")):
|
|
113
|
-
errors.append("profile metadata contains a probable inline secret")
|
|
114
|
-
return {"ok": not errors, "errors": errors, "engine": "python_fallback", "fallback_reason": result.get("error")}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
def list_targets() -> dict[str, Any]:
|
|
118
|
-
result = call_core("list-targets")
|
|
119
|
-
if result.get("ok"):
|
|
120
|
-
return result
|
|
121
|
-
proc = _run_subprocess(
|
|
122
|
-
[
|
|
123
|
-
"tmux",
|
|
124
|
-
"list-panes",
|
|
125
|
-
"-a",
|
|
126
|
-
"-F",
|
|
127
|
-
"#{pane_id}\t#{session_name}\t#{window_index}\t#{window_name}\t#{pane_index}\t#{pane_tty}\t#{pane_current_command}\t#{pane_active}\t#{pane_pid}",
|
|
128
|
-
],
|
|
129
|
-
text=True,
|
|
130
|
-
capture_output=True,
|
|
131
|
-
timeout=5,
|
|
132
|
-
check=False,
|
|
133
|
-
)
|
|
134
|
-
if proc.returncode != 0:
|
|
135
|
-
return {"ok": False, "error": proc.stderr.strip() or "tmux list-panes failed", "engine": "python_fallback"}
|
|
136
|
-
targets = []
|
|
137
|
-
for line in proc.stdout.splitlines():
|
|
138
|
-
parts = line.split("\t")
|
|
139
|
-
if len(parts) not in {8, 9}:
|
|
140
|
-
continue
|
|
141
|
-
target = {
|
|
142
|
-
"pane_id": parts[0],
|
|
143
|
-
"session_name": parts[1],
|
|
144
|
-
"window_index": parts[2],
|
|
145
|
-
"window_name": parts[3],
|
|
146
|
-
"pane_index": parts[4],
|
|
147
|
-
"pane_tty": parts[5],
|
|
148
|
-
"pane_current_command": parts[6],
|
|
149
|
-
"pane_active": parts[7] == "1",
|
|
150
|
-
}
|
|
151
|
-
pane_pid = parts[8].strip() if len(parts) == 9 else ""
|
|
152
|
-
if pane_pid:
|
|
153
|
-
target["pane_pid"] = pane_pid
|
|
154
|
-
target["fingerprint"] = f"{target['session_name']}|{target['window_index']}|{target['pane_index']}|{target['pane_tty']}"
|
|
155
|
-
_attach_leader_env(target)
|
|
156
|
-
targets.append(target)
|
|
157
|
-
return {"ok": True, "targets": targets, "engine": "python_fallback", "fallback_reason": result.get("error")}
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
def _attach_leader_env(target: dict[str, Any]) -> None:
|
|
161
|
-
pane_pid = str(target.get("pane_pid") or "").strip()
|
|
162
|
-
if not pane_pid:
|
|
163
|
-
target["leader_env"] = None
|
|
164
|
-
return
|
|
165
|
-
env = _read_process_env(pane_pid)
|
|
166
|
-
if env is None:
|
|
167
|
-
target["leader_env"] = None
|
|
168
|
-
return
|
|
169
|
-
leader_env = {key: env[key] for key in _LEADER_ENV_KEYS if key in env}
|
|
170
|
-
if "TEAM_AGENT_LEADER_SESSION_UUID" not in leader_env:
|
|
171
|
-
for child_pid in _walk_leader_shaped_children(pane_pid):
|
|
172
|
-
child_env = _read_process_env(child_pid)
|
|
173
|
-
if child_env is None:
|
|
174
|
-
continue
|
|
175
|
-
for key in _LEADER_ENV_KEYS:
|
|
176
|
-
if key not in leader_env and key in child_env:
|
|
177
|
-
leader_env[key] = child_env[key]
|
|
178
|
-
if "TEAM_AGENT_LEADER_SESSION_UUID" in leader_env:
|
|
179
|
-
break
|
|
180
|
-
target["leader_env"] = leader_env
|
|
181
|
-
uuid_value = leader_env.get("TEAM_AGENT_LEADER_SESSION_UUID")
|
|
182
|
-
if uuid_value:
|
|
183
|
-
target["leader_session_uuid"] = uuid_value
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
def _read_process_env(pid: str) -> dict[str, str] | None:
|
|
187
|
-
if platform.system() == "Linux":
|
|
188
|
-
return _read_proc_environ(pid)
|
|
189
|
-
return _read_ps_eww_env(pid)
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
def _read_proc_environ(pid: str) -> dict[str, str] | None:
|
|
193
|
-
path = Path(f"/proc/{pid}/environ")
|
|
194
|
-
try:
|
|
195
|
-
raw = path.read_bytes()
|
|
196
|
-
except (FileNotFoundError, PermissionError, OSError):
|
|
197
|
-
return None
|
|
198
|
-
env: dict[str, str] = {}
|
|
199
|
-
for token in raw.split(b"\x00"):
|
|
200
|
-
if not token or b"=" not in token:
|
|
201
|
-
continue
|
|
202
|
-
try:
|
|
203
|
-
text = token.decode("utf-8", errors="replace")
|
|
204
|
-
except Exception:
|
|
205
|
-
continue
|
|
206
|
-
key, _, value = text.partition("=")
|
|
207
|
-
env[key] = value
|
|
208
|
-
return env
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
def _read_ps_eww_env(pid: str) -> dict[str, str] | None:
|
|
212
|
-
try:
|
|
213
|
-
proc = _run_subprocess(
|
|
214
|
-
["ps", "-E", "-ww", "-p", str(pid)],
|
|
215
|
-
text=True,
|
|
216
|
-
capture_output=True,
|
|
217
|
-
timeout=_PANE_ENV_SCAN_TIMEOUT_SECONDS,
|
|
218
|
-
check=False,
|
|
219
|
-
)
|
|
220
|
-
except (subprocess.TimeoutExpired, FileNotFoundError, OSError):
|
|
221
|
-
return None
|
|
222
|
-
if proc.returncode != 0 or not proc.stdout:
|
|
223
|
-
return None
|
|
224
|
-
return _parse_ps_eww_output(proc.stdout, pid)
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
def _parse_ps_eww_output(text: str, pid: str) -> dict[str, str]:
|
|
228
|
-
env: dict[str, str] = {}
|
|
229
|
-
lines = text.splitlines()
|
|
230
|
-
if len(lines) < 2:
|
|
231
|
-
return env
|
|
232
|
-
target_row = None
|
|
233
|
-
for line in lines[1:]:
|
|
234
|
-
stripped = line.lstrip()
|
|
235
|
-
if stripped.split(" ", 1)[0] == str(pid):
|
|
236
|
-
target_row = stripped
|
|
237
|
-
break
|
|
238
|
-
if target_row is None:
|
|
239
|
-
# Spark MEDIUM #2 (da436a3): never fall back to lines[1] — that row may belong to
|
|
240
|
-
# an unrelated process and would leak its env (incl. another team's
|
|
241
|
-
# TEAM_AGENT_LEADER_SESSION_UUID) into this pane's leader_env, corrupting rediscovery.
|
|
242
|
-
return env
|
|
243
|
-
for token in target_row.split():
|
|
244
|
-
if "=" not in token:
|
|
245
|
-
continue
|
|
246
|
-
key, _, value = token.partition("=")
|
|
247
|
-
if not key or " " in key:
|
|
248
|
-
continue
|
|
249
|
-
if not (key[0].isalpha() or key[0] == "_"):
|
|
250
|
-
continue
|
|
251
|
-
if not all(ch.isalnum() or ch == "_" for ch in key):
|
|
252
|
-
continue
|
|
253
|
-
env[key] = value
|
|
254
|
-
return env
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
def _walk_leader_shaped_children(parent_pid: str) -> list[str]:
|
|
258
|
-
try:
|
|
259
|
-
proc = _run_subprocess(
|
|
260
|
-
["ps", "-o", "pid=,ppid=,comm="],
|
|
261
|
-
text=True,
|
|
262
|
-
capture_output=True,
|
|
263
|
-
timeout=_PANE_ENV_SCAN_TIMEOUT_SECONDS,
|
|
264
|
-
check=False,
|
|
265
|
-
)
|
|
266
|
-
except (subprocess.TimeoutExpired, FileNotFoundError, OSError):
|
|
267
|
-
return []
|
|
268
|
-
if proc.returncode != 0 or not proc.stdout:
|
|
269
|
-
return []
|
|
270
|
-
return _select_leader_shaped_descendants(proc.stdout, parent_pid)
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
def _select_leader_shaped_descendants(ps_output: str, parent_pid: str) -> list[str]:
|
|
274
|
-
rows: list[tuple[str, str, str]] = []
|
|
275
|
-
for line in ps_output.splitlines():
|
|
276
|
-
parts = line.split()
|
|
277
|
-
if len(parts) < 3:
|
|
278
|
-
continue
|
|
279
|
-
pid, ppid, command = parts[0], parts[1], " ".join(parts[2:])
|
|
280
|
-
rows.append((pid, ppid, Path(command).name))
|
|
281
|
-
descendants: set[str] = set()
|
|
282
|
-
frontier = {str(parent_pid)}
|
|
283
|
-
while frontier:
|
|
284
|
-
next_frontier: set[str] = set()
|
|
285
|
-
for pid, ppid, _ in rows:
|
|
286
|
-
if ppid in frontier and pid not in descendants:
|
|
287
|
-
descendants.add(pid)
|
|
288
|
-
next_frontier.add(pid)
|
|
289
|
-
frontier = next_frontier
|
|
290
|
-
return [
|
|
291
|
-
pid
|
|
292
|
-
for pid, _, command in rows
|
|
293
|
-
if pid in descendants and command in _LEADER_SHAPED_COMMANDS
|
|
294
|
-
]
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
def contains_inline_secret(value: str) -> bool:
|
|
298
|
-
return (
|
|
299
|
-
_contains_secret_assignment(value)
|
|
300
|
-
or _contains_bearer_secret(value)
|
|
301
|
-
or any(chunk.startswith("sk-") or _looks_base64_secret(chunk) for chunk in value.split())
|
|
302
|
-
or value.startswith("sk-")
|
|
303
|
-
or _looks_base64_secret(value)
|
|
304
|
-
)
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
def _contains_secret_assignment(value: str) -> bool:
|
|
308
|
-
for line in value.splitlines():
|
|
309
|
-
for separator in ("=", ":"):
|
|
310
|
-
if separator not in line:
|
|
311
|
-
continue
|
|
312
|
-
key, raw = line.split(separator, 1)
|
|
313
|
-
normalized = re.sub(r"[^a-z0-9]", "", key.lower())
|
|
314
|
-
if normalized not in {"apikey", "token", "secret", "password", "credential"}:
|
|
315
|
-
continue
|
|
316
|
-
candidate = raw.strip().strip("'\"")
|
|
317
|
-
if candidate.startswith("sk-") or len(candidate) >= 8 or _looks_base64_secret(candidate):
|
|
318
|
-
return True
|
|
319
|
-
return False
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
def _contains_bearer_secret(value: str) -> bool:
|
|
323
|
-
return re.search(r"(?i)\bbearer\s+[A-Za-z0-9._~+/=-]{16,}", value) is not None
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
def _looks_base64_secret(value: str) -> bool:
|
|
327
|
-
return len(value) >= 32 and re.fullmatch(r"[A-Za-z0-9+/=_-]+", value) is not None
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
from team_agent.sessions.capture import (
|
|
4
|
-
capture_agent_session,
|
|
5
|
-
capture_missing_sessions,
|
|
6
|
-
clear_session_capture_fields,
|
|
7
|
-
copy_session_metadata,
|
|
8
|
-
)
|
|
9
|
-
from team_agent.sessions.inventory import sessions_overview
|
|
10
|
-
from team_agent.sessions.resume import (
|
|
11
|
-
attach_profile_resume_root,
|
|
12
|
-
prepare_resume_state,
|
|
13
|
-
recover_resume_session_from_events,
|
|
14
|
-
)
|
|
15
|
-
|
|
16
|
-
__all__ = [
|
|
17
|
-
"attach_profile_resume_root",
|
|
18
|
-
"capture_agent_session",
|
|
19
|
-
"capture_missing_sessions",
|
|
20
|
-
"clear_session_capture_fields",
|
|
21
|
-
"copy_session_metadata",
|
|
22
|
-
"prepare_resume_state",
|
|
23
|
-
"recover_resume_session_from_events",
|
|
24
|
-
"sessions_overview",
|
|
25
|
-
]
|
|
@@ -1,144 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
import time
|
|
4
|
-
from datetime import datetime, timezone
|
|
5
|
-
from pathlib import Path
|
|
6
|
-
from typing import Any
|
|
7
|
-
|
|
8
|
-
from team_agent.errors import RuntimeError as TeamAgentRuntimeError
|
|
9
|
-
from team_agent.events import EventLog
|
|
10
|
-
from team_agent.providers import get_adapter
|
|
11
|
-
from team_agent.state import SESSION_CAPTURE_FIELDS, SESSION_STATE_FIELDS
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
# Stage 7 S6 (2026-05-27): capture_agent_session used to do a single adapter
|
|
15
|
-
# call and silently return None on miss, leaving status='running' workers with
|
|
16
|
-
# session_id=null. Slow worker startups (Codex writing the rollout file a few
|
|
17
|
-
# tenths of a second after window creation) raced this check. We now poll on a
|
|
18
|
-
# small interval inside the caller's timeout_s budget so the adapter's own
|
|
19
|
-
# fast-path call doesn't have to absorb all the latency on its own.
|
|
20
|
-
_CAPTURE_POLL_INTERVAL_SECONDS = 0.05
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
def capture_missing_sessions(
|
|
24
|
-
workspace: Path,
|
|
25
|
-
state: dict[str, Any],
|
|
26
|
-
event_log: EventLog,
|
|
27
|
-
timeout_s: float,
|
|
28
|
-
log_miss: bool = True,
|
|
29
|
-
) -> list[str]:
|
|
30
|
-
captured: list[str] = []
|
|
31
|
-
for agent_id, agent_state in state.get("agents", {}).items():
|
|
32
|
-
if agent_state.get("session_id"):
|
|
33
|
-
continue
|
|
34
|
-
known_session_ids = {
|
|
35
|
-
str(item.get("session_id"))
|
|
36
|
-
for aid, item in state.get("agents", {}).items()
|
|
37
|
-
if aid != agent_id and item.get("session_id")
|
|
38
|
-
}
|
|
39
|
-
# capture_missing_sessions is invoked from coordinator_tick, diagnose,
|
|
40
|
-
# status, etc. with very short timeouts; a transient miss should NOT
|
|
41
|
-
# crash those paths. The loud raise contract belongs to direct callers
|
|
42
|
-
# (e.g. lifecycle start/restart) who own the worker's atomicity.
|
|
43
|
-
result = capture_agent_session(
|
|
44
|
-
workspace,
|
|
45
|
-
agent_id,
|
|
46
|
-
agent_state,
|
|
47
|
-
event_log,
|
|
48
|
-
timeout_s=timeout_s,
|
|
49
|
-
exclude_session_ids=known_session_ids,
|
|
50
|
-
raise_on_missed=False,
|
|
51
|
-
)
|
|
52
|
-
if result:
|
|
53
|
-
captured.append(agent_id)
|
|
54
|
-
elif log_miss:
|
|
55
|
-
event_log.write(
|
|
56
|
-
"session.capture_timeout",
|
|
57
|
-
agent_id=agent_id,
|
|
58
|
-
provider=agent_state.get("provider"),
|
|
59
|
-
timeout_s=timeout_s,
|
|
60
|
-
spawn_cwd=agent_state.get("spawn_cwd"),
|
|
61
|
-
)
|
|
62
|
-
return captured
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
def capture_agent_session(
|
|
66
|
-
workspace: Path,
|
|
67
|
-
agent_id: str,
|
|
68
|
-
agent_state: dict[str, Any],
|
|
69
|
-
event_log: EventLog,
|
|
70
|
-
timeout_s: float,
|
|
71
|
-
exclude_session_ids: set[str] | None = None,
|
|
72
|
-
raise_on_missed: bool = True,
|
|
73
|
-
) -> dict[str, Any] | None:
|
|
74
|
-
if agent_state.get("session_id"):
|
|
75
|
-
return None
|
|
76
|
-
adapter = get_adapter(agent_state["provider"])
|
|
77
|
-
spawn_context = {
|
|
78
|
-
"agent_id": agent_id,
|
|
79
|
-
"cwd": agent_state.get("spawn_cwd") or str(workspace),
|
|
80
|
-
"spawn_time": agent_state.get("spawned_at") or datetime.now(timezone.utc).isoformat(),
|
|
81
|
-
"tmux_target": f"{agent_state.get('session_name', '')}:{agent_state.get('window', agent_id)}",
|
|
82
|
-
"predetermined_session_id": agent_state.get("_pending_session_id"),
|
|
83
|
-
"exclude_session_ids": sorted(exclude_session_ids or set()),
|
|
84
|
-
"claude_projects_root": agent_state.get("claude_projects_root"),
|
|
85
|
-
"auth_mode": agent_state.get("auth_mode"),
|
|
86
|
-
}
|
|
87
|
-
deadline = time.monotonic() + max(timeout_s, 0.0)
|
|
88
|
-
while True:
|
|
89
|
-
# Pass timeout_s=0 so the adapter does a single fast-path check; the
|
|
90
|
-
# outer loop owns the polling budget so behaviour stays consistent
|
|
91
|
-
# whether or not the adapter has its own internal sleep.
|
|
92
|
-
result = adapter.capture_session_id(agent_id, spawn_context, timeout_s=0)
|
|
93
|
-
if isinstance(result, dict) and (result.get("session_id") or result.get("rollout_path")):
|
|
94
|
-
copy_session_metadata(agent_state, result)
|
|
95
|
-
agent_state.pop("_pending_session_id", None)
|
|
96
|
-
event_log.write(
|
|
97
|
-
"session.captured",
|
|
98
|
-
agent_id=agent_id,
|
|
99
|
-
provider=agent_state.get("provider"),
|
|
100
|
-
session_id=agent_state.get("session_id"),
|
|
101
|
-
rollout_path=agent_state.get("rollout_path"),
|
|
102
|
-
captured_via=agent_state.get("captured_via"),
|
|
103
|
-
attribution_confidence=agent_state.get("attribution_confidence"),
|
|
104
|
-
)
|
|
105
|
-
return result
|
|
106
|
-
if time.monotonic() >= deadline:
|
|
107
|
-
break
|
|
108
|
-
time.sleep(_CAPTURE_POLL_INTERVAL_SECONDS)
|
|
109
|
-
# Timeout. Slice 1 atomicity contract: a worker whose status is 'running'
|
|
110
|
-
# must NEVER be left with session_id=null — that half-state is what made
|
|
111
|
-
# Mac mini Stage 7 S5/S6 unreproducible and breaks resume on next restart.
|
|
112
|
-
# Emit a structured attention event so the coordinator/operator sees the
|
|
113
|
-
# miss, then raise so callers cannot accidentally treat the None as a
|
|
114
|
-
# silent "no-op". Non-running workers (still starting, paused, stopped)
|
|
115
|
-
# legitimately have no session yet, so they still get the silent-None
|
|
116
|
-
# return that existing callers expect.
|
|
117
|
-
if agent_state.get("status") == "running":
|
|
118
|
-
event_log.write(
|
|
119
|
-
"session.capture_required_attention",
|
|
120
|
-
agent_id=agent_id,
|
|
121
|
-
provider=agent_state.get("provider"),
|
|
122
|
-
timeout_s=timeout_s,
|
|
123
|
-
spawn_cwd=agent_state.get("spawn_cwd"),
|
|
124
|
-
session_name=agent_state.get("session_name"),
|
|
125
|
-
window=agent_state.get("window", agent_id),
|
|
126
|
-
)
|
|
127
|
-
if raise_on_missed:
|
|
128
|
-
raise TeamAgentRuntimeError(
|
|
129
|
-
f"Failed to capture session_id for agent {agent_id}: adapter "
|
|
130
|
-
f"did not produce a session within {timeout_s}s. Worker is "
|
|
131
|
-
"running but unidentifiable; this is a Slice 1 atomicity "
|
|
132
|
-
"violation."
|
|
133
|
-
)
|
|
134
|
-
return None
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
def copy_session_metadata(target: dict[str, Any], source: dict[str, Any]) -> None:
|
|
138
|
-
for key in SESSION_STATE_FIELDS:
|
|
139
|
-
target[key] = source.get(key)
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
def clear_session_capture_fields(target: dict[str, Any]) -> None:
|
|
143
|
-
for key in SESSION_CAPTURE_FIELDS:
|
|
144
|
-
target[key] = None
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
from pathlib import Path
|
|
4
|
-
from typing import Any
|
|
5
|
-
|
|
6
|
-
from team_agent.spec import load_spec
|
|
7
|
-
from team_agent.state import load_runtime_state
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
def sessions_overview(workspace: Path) -> dict[str, Any]:
|
|
11
|
-
state = load_runtime_state(workspace)
|
|
12
|
-
spec_path = Path(state.get("spec_path", workspace / "team.spec.yaml"))
|
|
13
|
-
spec = load_spec(spec_path) if spec_path.exists() else {}
|
|
14
|
-
tasks = state.get("tasks", [])
|
|
15
|
-
rows = []
|
|
16
|
-
for agent in spec.get("agents", []):
|
|
17
|
-
agent_state = state.get("agents", {}).get(agent["id"], {})
|
|
18
|
-
last_task = next((task.get("id") for task in reversed(tasks) if task.get("assignee") == agent["id"]), None)
|
|
19
|
-
rows.append(
|
|
20
|
-
{
|
|
21
|
-
"agent_id": agent["id"],
|
|
22
|
-
"provider": agent.get("provider"),
|
|
23
|
-
"model": agent.get("model"),
|
|
24
|
-
"profile": agent.get("profile"),
|
|
25
|
-
"session_id": agent_state.get("session_id"),
|
|
26
|
-
"resume_id": agent_state.get("resume_id"),
|
|
27
|
-
"rollout_path": agent_state.get("rollout_path"),
|
|
28
|
-
"captured_at": agent_state.get("captured_at"),
|
|
29
|
-
"captured_via": agent_state.get("captured_via"),
|
|
30
|
-
"attribution_confidence": agent_state.get("attribution_confidence"),
|
|
31
|
-
"spawn_cwd": agent_state.get("spawn_cwd"),
|
|
32
|
-
"context_usage": agent_state.get("context_usage"),
|
|
33
|
-
"status": agent_state.get("status", "unknown"),
|
|
34
|
-
"last_task": last_task,
|
|
35
|
-
"handoff_path": agent_state.get("handoff_path"),
|
|
36
|
-
"display_target": agent_state.get("display"),
|
|
37
|
-
"terminal_target": {
|
|
38
|
-
"session": state.get("session_name"),
|
|
39
|
-
"window": agent_state.get("window", agent["id"]),
|
|
40
|
-
"pane": agent_state.get("pane_id"),
|
|
41
|
-
},
|
|
42
|
-
}
|
|
43
|
-
)
|
|
44
|
-
return {"ok": True, "sessions": rows, "workspace": str(workspace)}
|
|
@@ -1,135 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
import json
|
|
4
|
-
from pathlib import Path
|
|
5
|
-
from typing import Any
|
|
6
|
-
|
|
7
|
-
from team_agent.events import EventLog
|
|
8
|
-
from team_agent.paths import logs_dir
|
|
9
|
-
from team_agent.profiles import prepare_agent_profile_launch
|
|
10
|
-
from team_agent.providers import ResumeUnavailable
|
|
11
|
-
from team_agent.sessions.capture import clear_session_capture_fields, copy_session_metadata
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
def attach_profile_resume_root(workspace: Path, command_agent: dict[str, Any], previous: dict[str, Any]) -> dict[str, Any]:
|
|
15
|
-
profile_launch = command_agent.get("_provider_profile") or prepare_agent_profile_launch(workspace, command_agent)
|
|
16
|
-
if not profile_launch:
|
|
17
|
-
return previous
|
|
18
|
-
command_agent["_provider_profile"] = profile_launch
|
|
19
|
-
root = profile_launch.get("claude_projects_root")
|
|
20
|
-
if not root:
|
|
21
|
-
return previous
|
|
22
|
-
prepared = dict(previous)
|
|
23
|
-
prepared["claude_projects_root"] = root
|
|
24
|
-
return prepared
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
def prepare_resume_state(
|
|
28
|
-
workspace: Path,
|
|
29
|
-
agent_id: str,
|
|
30
|
-
previous: dict[str, Any],
|
|
31
|
-
adapter: Any,
|
|
32
|
-
event_log: EventLog,
|
|
33
|
-
exclude_session_ids: set[str] | None = None,
|
|
34
|
-
allow_fresh_on_resume_failure: bool = False,
|
|
35
|
-
) -> dict[str, Any]:
|
|
36
|
-
prepared = dict(previous)
|
|
37
|
-
session_id = prepared.get("session_id")
|
|
38
|
-
if session_id and adapter.session_is_resumable(prepared, workspace):
|
|
39
|
-
return prepared
|
|
40
|
-
if session_id:
|
|
41
|
-
event_log.write(
|
|
42
|
-
"resume.session_unverified",
|
|
43
|
-
agent_id=agent_id,
|
|
44
|
-
provider=prepared.get("provider"),
|
|
45
|
-
session_id=session_id,
|
|
46
|
-
captured_via=prepared.get("captured_via"),
|
|
47
|
-
spawn_cwd=prepared.get("spawn_cwd"),
|
|
48
|
-
)
|
|
49
|
-
else:
|
|
50
|
-
event_log.write(
|
|
51
|
-
"resume.session_missing_repair_attempt",
|
|
52
|
-
agent_id=agent_id,
|
|
53
|
-
provider=prepared.get("provider"),
|
|
54
|
-
spawn_cwd=prepared.get("spawn_cwd"),
|
|
55
|
-
)
|
|
56
|
-
repaired = recover_resume_session_from_events(workspace, agent_id, prepared, adapter, exclude_session_ids or set())
|
|
57
|
-
if not repaired:
|
|
58
|
-
repaired = adapter.recover_session_id(agent_id, prepared, workspace, exclude_session_ids or set())
|
|
59
|
-
if repaired:
|
|
60
|
-
copy_session_metadata(prepared, repaired)
|
|
61
|
-
event_log.write(
|
|
62
|
-
"resume.session_repaired",
|
|
63
|
-
agent_id=agent_id,
|
|
64
|
-
provider=prepared.get("provider"),
|
|
65
|
-
old_session_id=session_id,
|
|
66
|
-
session_id=prepared.get("session_id"),
|
|
67
|
-
rollout_path=prepared.get("rollout_path"),
|
|
68
|
-
captured_via=prepared.get("captured_via"),
|
|
69
|
-
attribution_confidence=prepared.get("attribution_confidence"),
|
|
70
|
-
)
|
|
71
|
-
return prepared
|
|
72
|
-
if session_id and not allow_fresh_on_resume_failure:
|
|
73
|
-
event_log.write(
|
|
74
|
-
"resume.session_required_missing",
|
|
75
|
-
agent_id=agent_id,
|
|
76
|
-
provider=prepared.get("provider"),
|
|
77
|
-
old_session_id=session_id,
|
|
78
|
-
rollout_path=prepared.get("rollout_path"),
|
|
79
|
-
reason="provider transcript not found",
|
|
80
|
-
)
|
|
81
|
-
raise ResumeUnavailable(
|
|
82
|
-
f"Cannot resume agent {agent_id}: stored session {session_id} is not available. "
|
|
83
|
-
"Use --allow-fresh only if losing that worker context is acceptable."
|
|
84
|
-
)
|
|
85
|
-
clear_session_capture_fields(prepared)
|
|
86
|
-
event_log.write(
|
|
87
|
-
"resume.session_unavailable",
|
|
88
|
-
agent_id=agent_id,
|
|
89
|
-
provider=prepared.get("provider"),
|
|
90
|
-
old_session_id=session_id,
|
|
91
|
-
reason="provider transcript not found",
|
|
92
|
-
)
|
|
93
|
-
return prepared
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
def recover_resume_session_from_events(
|
|
97
|
-
workspace: Path,
|
|
98
|
-
agent_id: str,
|
|
99
|
-
previous: dict[str, Any],
|
|
100
|
-
adapter: Any,
|
|
101
|
-
exclude_session_ids: set[str],
|
|
102
|
-
) -> dict[str, Any] | None:
|
|
103
|
-
events_path = logs_dir(workspace) / "events.jsonl"
|
|
104
|
-
try:
|
|
105
|
-
lines = events_path.read_text(encoding="utf-8").splitlines()
|
|
106
|
-
except OSError:
|
|
107
|
-
return None
|
|
108
|
-
current_session_id = str(previous.get("session_id") or "")
|
|
109
|
-
for line in reversed(lines):
|
|
110
|
-
try:
|
|
111
|
-
event = json.loads(line)
|
|
112
|
-
except json.JSONDecodeError:
|
|
113
|
-
continue
|
|
114
|
-
if event.get("agent_id") != agent_id:
|
|
115
|
-
continue
|
|
116
|
-
if event.get("event") == "discard.session_tombstone":
|
|
117
|
-
return None
|
|
118
|
-
if event.get("event") != "session.captured":
|
|
119
|
-
continue
|
|
120
|
-
session_id = str(event.get("session_id") or "")
|
|
121
|
-
if not session_id or session_id == current_session_id or session_id in exclude_session_ids:
|
|
122
|
-
continue
|
|
123
|
-
candidate = dict(previous)
|
|
124
|
-
candidate.update(
|
|
125
|
-
{
|
|
126
|
-
"session_id": session_id,
|
|
127
|
-
"rollout_path": event.get("rollout_path"),
|
|
128
|
-
"captured_at": event.get("ts"),
|
|
129
|
-
"captured_via": "event_log_repair",
|
|
130
|
-
"attribution_confidence": event.get("attribution_confidence"),
|
|
131
|
-
}
|
|
132
|
-
)
|
|
133
|
-
if adapter.session_is_resumable(candidate, workspace):
|
|
134
|
-
return candidate
|
|
135
|
-
return None
|