@team-agent/installer 0.5.98 → 0.5.100
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 +1 -1
- package/Cargo.toml +1 -1
- package/crates/team-agent/src/cli/adapters.rs +1 -163
- package/crates/team-agent/src/cli/diagnose.rs +3 -19
- package/crates/team-agent/src/cli/emit.rs +20 -204
- package/crates/team-agent/src/cli/mod.rs +138 -205
- package/crates/team-agent/src/cli/send/coordinator.rs +2 -2
- package/crates/team-agent/src/cli/spec.rs +41 -67
- package/crates/team-agent/src/cli/tests/main_preserved.rs +2 -186
- package/crates/team-agent/src/cli/tests/missing_subcommands.rs +9 -10
- package/crates/team-agent/src/cli/tests/run_delegation.rs +0 -3
- package/crates/team-agent/src/cli/tests/verb_validate.rs +0 -1
- package/crates/team-agent/src/cli/triage.rs +1 -1
- package/crates/team-agent/src/cli/types.rs +0 -55
- package/crates/team-agent/src/compiler/tests.rs +5 -8
- package/crates/team-agent/src/compiler.rs +6 -14
- package/crates/team-agent/src/leader/lease.rs +1 -1
- package/crates/team-agent/src/lifecycle/launch/add_agent.rs +1 -1
- package/crates/team-agent/src/lifecycle/launch/add_agent_state.rs +1 -1
- package/crates/team-agent/src/lifecycle/launch/agent_state.rs +5 -25
- package/crates/team-agent/src/lifecycle/launch/identity.rs +2 -18
- package/crates/team-agent/src/lifecycle/launch/layout.rs +7 -29
- package/crates/team-agent/src/lifecycle/launch/leader_context.rs +1 -1
- package/crates/team-agent/src/lifecycle/launch/mcp_config.rs +1 -1
- package/crates/team-agent/src/lifecycle/launch/ownership.rs +1 -1
- package/crates/team-agent/src/lifecycle/launch/pi_mcp.rs +7 -7
- package/crates/team-agent/src/lifecycle/launch/quick_start.rs +12 -49
- package/crates/team-agent/src/lifecycle/launch/quick_start_transport.rs +3 -9
- package/crates/team-agent/src/lifecycle/launch/readiness.rs +1 -1
- package/crates/team-agent/src/lifecycle/launch/spawn.rs +2 -61
- package/crates/team-agent/src/lifecycle/launch/spec_state.rs +2 -15
- package/crates/team-agent/src/lifecycle/launch/state_projection.rs +3 -7
- package/crates/team-agent/src/lifecycle/launch/worker_env.rs +1 -1
- package/crates/team-agent/src/lifecycle/launch.rs +3 -4
- package/crates/team-agent/src/lifecycle/mod.rs +0 -8
- package/crates/team-agent/src/lifecycle/restart/agent.rs +62 -8
- package/crates/team-agent/src/lifecycle/restart/common.rs +0 -32
- package/crates/team-agent/src/lifecycle/restart/rebuild.rs +6 -15
- package/crates/team-agent/src/lifecycle/tests/b0_reader_hideone_audit_contract.rs +2 -2
- package/crates/team-agent/src/lifecycle/tests/claude_profile_launch_red.rs +1 -1
- package/crates/team-agent/src/lifecycle/tests/clone_agent_preserves_source_tools.rs +1 -2
- package/crates/team-agent/src/lifecycle/tests/clone_fork_copilot_perms_red.rs +0 -1
- package/crates/team-agent/src/lifecycle/tests/core.rs +0 -213
- package/crates/team-agent/src/lifecycle/tests/issue238_codex_effort_contract.rs +96 -0
- package/crates/team-agent/src/lifecycle/tests/lane_ops.rs +1 -0
- package/crates/team-agent/src/lifecycle/tests/launch_spawn.rs +31 -77
- package/crates/team-agent/src/lifecycle/tests/main_preserved.rs +2 -2
- package/crates/team-agent/src/lifecycle/tests/phase_b_contracts.rs +4 -8
- package/crates/team-agent/src/lifecycle/tests/phase_golden.rs +16 -9
- package/crates/team-agent/src/lifecycle/tests/pi_executable_mcp_red.rs +1 -1
- package/crates/team-agent/src/lifecycle/tests/realmachine_clusters_1_6_red.rs +0 -60
- package/crates/team-agent/src/lifecycle/tests/startup_latency_contract.rs +3 -4
- package/crates/team-agent/src/lifecycle/tests/team_in_team_identity_scope_red.rs +2 -2
- package/crates/team-agent/src/lifecycle/tests.rs +1 -2
- package/crates/team-agent/src/lifecycle/types.rs +3 -133
- package/crates/team-agent/src/messaging/send.rs +2 -2
- package/crates/team-agent/src/model/enums.rs +21 -71
- package/crates/team-agent/src/model/spec.rs +5 -20
- package/crates/team-agent/src/provider/adapters/codex.rs +2 -2
- package/crates/team-agent/src/provider/types.rs +7 -6
- package/crates/team-agent/src/state/repository.rs +0 -5
- package/examples/team.spec.yaml +0 -1
- package/package.json +4 -4
- package/schemas/team.schema.json +2 -2
- package/skills/team-agent/SKILL.md +1 -1
- package/skills/team-agent/references/recovery-runbook.md +2 -2
- package/skills/team-agent/references/team-agent-operator.md +3 -10
- package/skills/team-agent/references/team-in-team.md +0 -6
- package/crates/team-agent/src/lifecycle/display.rs +0 -550
- package/crates/team-agent/src/lifecycle/tests/core_034_real_red.rs +0 -884
- package/crates/team-agent/src/lifecycle/tests/display_adaptive_red.rs +0 -481
|
@@ -45,11 +45,11 @@ use serde_json::{json, Map, Value};
|
|
|
45
45
|
use thiserror::Error;
|
|
46
46
|
|
|
47
47
|
// REUSE in-tree(只 import,不 redefine):
|
|
48
|
-
use crate::messaging::{self,
|
|
48
|
+
use crate::messaging::{self, MessageTarget, SendOptions, TrustedSender};
|
|
49
49
|
use crate::model::ids::{TaskId, TeamKey};
|
|
50
50
|
|
|
51
51
|
pub(crate) const COMMS_BOUNDARY_TEXT: &str = "validates live pane binding consistency and zero-token comms contracts. Does NOT perform live runtime message round-trip. (zero token, zero pollution)";
|
|
52
|
-
pub(crate) const QUICK_START_REMINDER: &str = "Reminder: Do not inspect raw worker
|
|
52
|
+
pub(crate) const QUICK_START_REMINDER: &str = "Reminder: Do not inspect raw worker pane output during normal operation. Use team-agent status / inbox / collect instead. Wait for report_result.";
|
|
53
53
|
pub(crate) const SEND_REMINDER: &str = "Message delivered. Wait for the worker to report_result. Do not poll the worker terminal with capture-pane.";
|
|
54
54
|
pub(crate) const STATUS_REMINDER: &str = "Results are finalized automatically; use --watch-result or team-agent inbox AGENT -n 3 for transport fallback. Do not capture-pane worker terminals.";
|
|
55
55
|
|
|
@@ -119,7 +119,7 @@ mod hermetic_test_support;
|
|
|
119
119
|
pub mod status_port;
|
|
120
120
|
|
|
121
121
|
/// PLACEHOLDER → step13 lifecycle(`runtime.{quick_start,start_agent,add_agent,fork_agent,
|
|
122
|
-
/// remove_agent,start_agent,stop_agent,reset_agent,restart,shutdown,start_leader
|
|
122
|
+
/// remove_agent,start_agent,stop_agent,reset_agent,restart,shutdown,start_leader}`)。
|
|
123
123
|
/// `quick_start.py` 物理在本子系统但实现属 step 13(card)。本层只声明委派面。
|
|
124
124
|
pub mod lifecycle_port {
|
|
125
125
|
use super::*;
|
|
@@ -133,16 +133,14 @@ pub mod lifecycle_port {
|
|
|
133
133
|
name: Option<&str>,
|
|
134
134
|
team_id: Option<&str>,
|
|
135
135
|
yes: bool,
|
|
136
|
-
open_display: bool,
|
|
137
136
|
backend: Option<&str>,
|
|
138
137
|
) -> Result<Value, CliError> {
|
|
139
|
-
match crate::lifecycle::
|
|
138
|
+
match crate::lifecycle::quick_start_in_workspace_with_backend(
|
|
140
139
|
workspace,
|
|
141
140
|
agents_dir,
|
|
142
141
|
name,
|
|
143
142
|
yes,
|
|
144
143
|
team_id,
|
|
145
|
-
open_display,
|
|
146
144
|
backend,
|
|
147
145
|
) {
|
|
148
146
|
Ok(report) => Ok(quick_start_value(report)),
|
|
@@ -1155,10 +1153,7 @@ pub mod lifecycle_port {
|
|
|
1155
1153
|
}
|
|
1156
1154
|
}
|
|
1157
1155
|
}
|
|
1158
|
-
|
|
1159
|
-
&run_workspace,
|
|
1160
|
-
session,
|
|
1161
|
-
);
|
|
1156
|
+
cleanup_owned_team_windows(&run_workspace, transport, &state, session);
|
|
1162
1157
|
} else {
|
|
1163
1158
|
let targets = vec![session.as_str().to_string()];
|
|
1164
1159
|
let event_log = crate::event_log::EventLog::new(&run_workspace);
|
|
@@ -1185,10 +1180,7 @@ pub mod lifecycle_port {
|
|
|
1185
1180
|
} else {
|
|
1186
1181
|
push_unique_session(&mut killed_sessions, session.clone());
|
|
1187
1182
|
}
|
|
1188
|
-
|
|
1189
|
-
&run_workspace,
|
|
1190
|
-
session,
|
|
1191
|
-
);
|
|
1183
|
+
cleanup_owned_team_windows(&run_workspace, transport, &state, session);
|
|
1192
1184
|
}
|
|
1193
1185
|
}
|
|
1194
1186
|
}
|
|
@@ -1473,6 +1465,127 @@ pub mod lifecycle_port {
|
|
|
1473
1465
|
Ok(response)
|
|
1474
1466
|
}
|
|
1475
1467
|
|
|
1468
|
+
/// Remove legacy adaptive display windows without reopening a display backend.
|
|
1469
|
+
/// Every target is scoped to the selected transport and passes the same
|
|
1470
|
+
/// ownership/protection audit as the rest of shutdown. Unknown or protected
|
|
1471
|
+
/// targets are intentionally left in place.
|
|
1472
|
+
fn cleanup_owned_team_windows(
|
|
1473
|
+
workspace: &Path,
|
|
1474
|
+
transport: &dyn crate::transport::Transport,
|
|
1475
|
+
state: &Value,
|
|
1476
|
+
session: &crate::transport::SessionName,
|
|
1477
|
+
) {
|
|
1478
|
+
let Some(agents) = state.get("agents").and_then(Value::as_object) else {
|
|
1479
|
+
return;
|
|
1480
|
+
};
|
|
1481
|
+
let leader_session = state
|
|
1482
|
+
.get("leader_receiver")
|
|
1483
|
+
.and_then(|receiver| receiver.get("session_name"))
|
|
1484
|
+
.and_then(Value::as_str)
|
|
1485
|
+
.filter(|value| !value.is_empty())
|
|
1486
|
+
.unwrap_or_else(|| session.as_str());
|
|
1487
|
+
let leader_panes = collect_state_leader_anchor_pane_ids(
|
|
1488
|
+
state,
|
|
1489
|
+
transport.tmux_endpoint().as_deref(),
|
|
1490
|
+
);
|
|
1491
|
+
let event_log = crate::event_log::EventLog::new(workspace);
|
|
1492
|
+
let team_key = state.get("team_key").and_then(Value::as_str);
|
|
1493
|
+
let generation = state_generation(state);
|
|
1494
|
+
let overview_prefix = format!("team-agent:{}:overview", session.as_str());
|
|
1495
|
+
let mut windows = std::collections::BTreeSet::new();
|
|
1496
|
+
let mut panes = std::collections::BTreeSet::new();
|
|
1497
|
+
for (_agent_id, agent) in agents {
|
|
1498
|
+
let Some(display) = agent.get("display").and_then(Value::as_object) else {
|
|
1499
|
+
continue;
|
|
1500
|
+
};
|
|
1501
|
+
if display.get("backend").and_then(Value::as_str) != Some("adaptive") {
|
|
1502
|
+
continue;
|
|
1503
|
+
}
|
|
1504
|
+
if display.get("status").and_then(Value::as_str) == Some("stopped") {
|
|
1505
|
+
continue;
|
|
1506
|
+
}
|
|
1507
|
+
if let Some(window) = display
|
|
1508
|
+
.get("workspace_window")
|
|
1509
|
+
.or_else(|| display.get("window"))
|
|
1510
|
+
.or_else(|| agent.get("window"))
|
|
1511
|
+
.and_then(Value::as_str)
|
|
1512
|
+
.filter(|value| !value.is_empty())
|
|
1513
|
+
{
|
|
1514
|
+
if window == overview_prefix || window.starts_with(&format!("{overview_prefix}-")) {
|
|
1515
|
+
windows.insert(format!("{leader_session}:{window}"));
|
|
1516
|
+
}
|
|
1517
|
+
}
|
|
1518
|
+
if let Some(pane) = display.get("pane_id").and_then(Value::as_str) {
|
|
1519
|
+
if pane.starts_with('%') && !leader_panes.contains(pane) {
|
|
1520
|
+
panes.insert(pane.to_string());
|
|
1521
|
+
}
|
|
1522
|
+
}
|
|
1523
|
+
}
|
|
1524
|
+
if let Ok(live_windows) = transport.list_windows(&crate::transport::SessionName::new(
|
|
1525
|
+
leader_session.to_string(),
|
|
1526
|
+
)) {
|
|
1527
|
+
for window in live_windows {
|
|
1528
|
+
let name = window.as_str();
|
|
1529
|
+
if name == overview_prefix || name.starts_with(&format!("{overview_prefix}-")) {
|
|
1530
|
+
windows.insert(format!("{leader_session}:{name}"));
|
|
1531
|
+
}
|
|
1532
|
+
}
|
|
1533
|
+
}
|
|
1534
|
+
let live_targets = transport.list_targets().unwrap_or_default();
|
|
1535
|
+
for target in &live_targets {
|
|
1536
|
+
if leader_panes.contains(target.pane_id.as_str()) {
|
|
1537
|
+
continue;
|
|
1538
|
+
}
|
|
1539
|
+
if let Some(window) = target.window_name.as_ref() {
|
|
1540
|
+
if windows.contains(&format!("{}:{}", target.session.as_str(), window.as_str())) {
|
|
1541
|
+
panes.remove(target.pane_id.as_str());
|
|
1542
|
+
}
|
|
1543
|
+
}
|
|
1544
|
+
}
|
|
1545
|
+
for target in windows {
|
|
1546
|
+
let Some((target_session, target_window)) = target.split_once(':') else {
|
|
1547
|
+
continue;
|
|
1548
|
+
};
|
|
1549
|
+
let target = crate::transport::Target::SessionWindow {
|
|
1550
|
+
session: crate::transport::SessionName::new(target_session.to_string()),
|
|
1551
|
+
window: crate::transport::WindowName::new(target_window.to_string()),
|
|
1552
|
+
};
|
|
1553
|
+
let target_text = format!("{target_session}:{target_window}");
|
|
1554
|
+
let audit = crate::kill_audit::pre_kill_audit_scoped(
|
|
1555
|
+
&event_log,
|
|
1556
|
+
transport,
|
|
1557
|
+
"shutdown.legacy_display_window",
|
|
1558
|
+
"kill-window",
|
|
1559
|
+
std::slice::from_ref(&target_text),
|
|
1560
|
+
workspace,
|
|
1561
|
+
team_key,
|
|
1562
|
+
generation,
|
|
1563
|
+
"session_marker+state_display_window+team_tag",
|
|
1564
|
+
"positive workspace/team ownership",
|
|
1565
|
+
);
|
|
1566
|
+
if audit.is_ok() {
|
|
1567
|
+
let _ = transport.kill_window(&target);
|
|
1568
|
+
}
|
|
1569
|
+
}
|
|
1570
|
+
for pane in panes {
|
|
1571
|
+
let audit = crate::kill_audit::pre_kill_audit_scoped(
|
|
1572
|
+
&event_log,
|
|
1573
|
+
transport,
|
|
1574
|
+
"shutdown.legacy_display_pane",
|
|
1575
|
+
"kill-pane",
|
|
1576
|
+
std::slice::from_ref(&pane),
|
|
1577
|
+
workspace,
|
|
1578
|
+
team_key,
|
|
1579
|
+
generation,
|
|
1580
|
+
"state_display_pane+team_session",
|
|
1581
|
+
"positive workspace/team ownership",
|
|
1582
|
+
);
|
|
1583
|
+
if audit.is_ok() {
|
|
1584
|
+
let _ = transport.kill_pane(&crate::transport::PaneId::new(pane));
|
|
1585
|
+
}
|
|
1586
|
+
}
|
|
1587
|
+
}
|
|
1588
|
+
|
|
1476
1589
|
/// 0.5.x Windows portability Batch 6 Option A: locate and
|
|
1477
1590
|
/// terminate the ConPTY shim if `state.transport.shim.pid` is
|
|
1478
1591
|
/// recorded. Returns a JSON blob for the shutdown response so
|
|
@@ -2635,7 +2748,6 @@ pub mod lifecycle_port {
|
|
|
2635
2748
|
workspace: &Path,
|
|
2636
2749
|
agent: &str,
|
|
2637
2750
|
force: bool,
|
|
2638
|
-
open_display: bool,
|
|
2639
2751
|
allow_fresh: bool,
|
|
2640
2752
|
team: Option<&str>,
|
|
2641
2753
|
) -> Result<Value, CliError> {
|
|
@@ -2644,7 +2756,7 @@ pub mod lifecycle_port {
|
|
|
2644
2756
|
workspace,
|
|
2645
2757
|
&agent_id,
|
|
2646
2758
|
force,
|
|
2647
|
-
|
|
2759
|
+
true,
|
|
2648
2760
|
allow_fresh,
|
|
2649
2761
|
team,
|
|
2650
2762
|
) {
|
|
@@ -2712,7 +2824,6 @@ pub mod lifecycle_port {
|
|
|
2712
2824
|
workspace: &Path,
|
|
2713
2825
|
agent: &str,
|
|
2714
2826
|
discard_session: bool,
|
|
2715
|
-
open_display: bool,
|
|
2716
2827
|
team: Option<&str>,
|
|
2717
2828
|
) -> Result<Value, CliError> {
|
|
2718
2829
|
let agent_id = crate::model::ids::AgentId::new(agent);
|
|
@@ -2720,7 +2831,7 @@ pub mod lifecycle_port {
|
|
|
2720
2831
|
workspace,
|
|
2721
2832
|
&agent_id,
|
|
2722
2833
|
discard_session,
|
|
2723
|
-
|
|
2834
|
+
true,
|
|
2724
2835
|
team,
|
|
2725
2836
|
) {
|
|
2726
2837
|
Ok(crate::lifecycle::ResetAgentOutcome::Reset {
|
|
@@ -2763,7 +2874,6 @@ pub mod lifecycle_port {
|
|
|
2763
2874
|
workspace: &Path,
|
|
2764
2875
|
agent: &str,
|
|
2765
2876
|
role_file: &str,
|
|
2766
|
-
open_display: bool,
|
|
2767
2877
|
team: Option<&str>,
|
|
2768
2878
|
force: bool,
|
|
2769
2879
|
) -> Result<Value, CliError> {
|
|
@@ -2772,7 +2882,7 @@ pub mod lifecycle_port {
|
|
|
2772
2882
|
workspace,
|
|
2773
2883
|
&agent_id,
|
|
2774
2884
|
Path::new(role_file),
|
|
2775
|
-
|
|
2885
|
+
true,
|
|
2776
2886
|
team,
|
|
2777
2887
|
force,
|
|
2778
2888
|
) {
|
|
@@ -2811,12 +2921,11 @@ pub mod lifecycle_port {
|
|
|
2811
2921
|
source_agent: &str,
|
|
2812
2922
|
as_agent_id: &str,
|
|
2813
2923
|
label: Option<&str>,
|
|
2814
|
-
open_display: bool,
|
|
2815
2924
|
team: Option<&str>,
|
|
2816
2925
|
) -> Result<Value, CliError> {
|
|
2817
2926
|
let source = crate::model::ids::AgentId::new(source_agent);
|
|
2818
2927
|
let dest = crate::model::ids::AgentId::new(as_agent_id);
|
|
2819
|
-
match crate::lifecycle::fork_agent(workspace, &source, &dest, label,
|
|
2928
|
+
match crate::lifecycle::fork_agent(workspace, &source, &dest, label, true, team) {
|
|
2820
2929
|
Ok(report) => Ok(json!({
|
|
2821
2930
|
"ok": true,
|
|
2822
2931
|
"source_agent_id": report.source_agent_id.as_str(),
|
|
@@ -2833,12 +2942,11 @@ pub mod lifecycle_port {
|
|
|
2833
2942
|
source_agent: &str,
|
|
2834
2943
|
as_agent_id: &str,
|
|
2835
2944
|
label: Option<&str>,
|
|
2836
|
-
open_display: bool,
|
|
2837
2945
|
team: Option<&str>,
|
|
2838
2946
|
) -> Result<Value, CliError> {
|
|
2839
2947
|
let source = crate::model::ids::AgentId::new(source_agent);
|
|
2840
2948
|
let dest = crate::model::ids::AgentId::new(as_agent_id);
|
|
2841
|
-
match crate::lifecycle::clone_agent(workspace, &source, &dest, label,
|
|
2949
|
+
match crate::lifecycle::clone_agent(workspace, &source, &dest, label, true, team) {
|
|
2842
2950
|
Ok(report) => Ok(json!({
|
|
2843
2951
|
"ok": true,
|
|
2844
2952
|
"status": "cloned",
|
|
@@ -3024,60 +3132,6 @@ pub mod lifecycle_port {
|
|
|
3024
3132
|
format!("'{}'", raw.replace('\'', "'\\''"))
|
|
3025
3133
|
}
|
|
3026
3134
|
}
|
|
3027
|
-
///
|
|
3028
|
-
/// `runtime.acknowledge_idle`(`cmd_acknowledge_idle`)。
|
|
3029
|
-
pub fn acknowledge_idle(workspace: &Path, team: Option<&str>) -> Result<Value, CliError> {
|
|
3030
|
-
let mut state = crate::state::persist::load_runtime_state(workspace)
|
|
3031
|
-
.map_err(|e| CliError::Runtime(e.to_string()))?;
|
|
3032
|
-
let team = team
|
|
3033
|
-
.map(ToString::to_string)
|
|
3034
|
-
.or_else(|| {
|
|
3035
|
-
state
|
|
3036
|
-
.get("active_team_key")
|
|
3037
|
-
.and_then(Value::as_str)
|
|
3038
|
-
.map(ToString::to_string)
|
|
3039
|
-
})
|
|
3040
|
-
.filter(|s| !s.is_empty())
|
|
3041
|
-
.or_else(|| {
|
|
3042
|
-
workspace
|
|
3043
|
-
.file_name()
|
|
3044
|
-
.map(|name| name.to_string_lossy().to_string())
|
|
3045
|
-
})
|
|
3046
|
-
.unwrap_or_else(|| "current".to_string());
|
|
3047
|
-
let now = chrono::Utc::now().to_rfc3339();
|
|
3048
|
-
let ttl_seconds = 1800;
|
|
3049
|
-
let expires_at = (chrono::Utc::now() + chrono::Duration::seconds(ttl_seconds)).to_rfc3339();
|
|
3050
|
-
record_idle_acknowledged(&mut state, &team, &now, &expires_at, ttl_seconds);
|
|
3051
|
-
suppress_team_idle_fallbacks(&mut state, &team, &now, &expires_at, ttl_seconds);
|
|
3052
|
-
let agent_id = state
|
|
3053
|
-
.get("agents")
|
|
3054
|
-
.and_then(Value::as_object)
|
|
3055
|
-
.and_then(|agents| agents.keys().next().cloned())
|
|
3056
|
-
.map(Value::String)
|
|
3057
|
-
.unwrap_or(Value::Null);
|
|
3058
|
-
crate::state::repository::StateRepository::new(workspace)
|
|
3059
|
-
.save(
|
|
3060
|
-
crate::state::repository::StateWriteIntent::IdleAck {
|
|
3061
|
-
team_key: Some(&team),
|
|
3062
|
-
},
|
|
3063
|
-
&state,
|
|
3064
|
-
)
|
|
3065
|
-
.map_err(|e| CliError::Runtime(e.to_string()))?;
|
|
3066
|
-
crate::event_log::EventLog::new(workspace)
|
|
3067
|
-
.write(
|
|
3068
|
-
"coordinator.idle_acknowledged",
|
|
3069
|
-
json!({"team": team, "ttl_seconds": ttl_seconds}),
|
|
3070
|
-
)
|
|
3071
|
-
.map_err(|e| CliError::Runtime(e.to_string()))?;
|
|
3072
|
-
Ok(json!({
|
|
3073
|
-
"ok": true,
|
|
3074
|
-
"team": team,
|
|
3075
|
-
"agent_id": agent_id,
|
|
3076
|
-
"acknowledged_at": now,
|
|
3077
|
-
"expires_at": expires_at,
|
|
3078
|
-
"ttl_seconds": ttl_seconds,
|
|
3079
|
-
}))
|
|
3080
|
-
}
|
|
3081
3135
|
|
|
3082
3136
|
fn error_value(error: crate::lifecycle::LifecycleError) -> Value {
|
|
3083
3137
|
let message = error.to_string();
|
|
@@ -3132,118 +3186,6 @@ pub mod lifecycle_port {
|
|
|
3132
3186
|
None
|
|
3133
3187
|
}
|
|
3134
3188
|
|
|
3135
|
-
fn record_idle_acknowledged(
|
|
3136
|
-
state: &mut Value,
|
|
3137
|
-
team: &str,
|
|
3138
|
-
acknowledged_at: &str,
|
|
3139
|
-
expires_at: &str,
|
|
3140
|
-
ttl_seconds: i64,
|
|
3141
|
-
) {
|
|
3142
|
-
let Some(root) = state.as_object_mut() else {
|
|
3143
|
-
return;
|
|
3144
|
-
};
|
|
3145
|
-
let coordinator = root
|
|
3146
|
-
.entry("coordinator")
|
|
3147
|
-
.or_insert_with(|| json!({}))
|
|
3148
|
-
.as_object_mut();
|
|
3149
|
-
let Some(coordinator) = coordinator else {
|
|
3150
|
-
return;
|
|
3151
|
-
};
|
|
3152
|
-
let idle = coordinator
|
|
3153
|
-
.entry("idle_acknowledged")
|
|
3154
|
-
.or_insert_with(|| json!({}))
|
|
3155
|
-
.as_object_mut();
|
|
3156
|
-
let Some(idle) = idle else {
|
|
3157
|
-
return;
|
|
3158
|
-
};
|
|
3159
|
-
idle.insert(
|
|
3160
|
-
team.to_string(),
|
|
3161
|
-
json!({"acknowledged_at": acknowledged_at, "expires_at": expires_at, "ttl_seconds": ttl_seconds}),
|
|
3162
|
-
);
|
|
3163
|
-
}
|
|
3164
|
-
|
|
3165
|
-
fn suppress_team_idle_fallbacks(
|
|
3166
|
-
state: &mut Value,
|
|
3167
|
-
team: &str,
|
|
3168
|
-
suppressed_at: &str,
|
|
3169
|
-
expires_at: &str,
|
|
3170
|
-
ttl_seconds: i64,
|
|
3171
|
-
) {
|
|
3172
|
-
let agents = state
|
|
3173
|
-
.get("agents")
|
|
3174
|
-
.and_then(Value::as_object)
|
|
3175
|
-
.map(|obj| obj.keys().cloned().collect::<Vec<_>>())
|
|
3176
|
-
.unwrap_or_default();
|
|
3177
|
-
for agent in agents {
|
|
3178
|
-
upsert_suppression(
|
|
3179
|
-
state,
|
|
3180
|
-
SuppressionRecord {
|
|
3181
|
-
team,
|
|
3182
|
-
agent_id: &agent,
|
|
3183
|
-
alert_type: "idle_fallback",
|
|
3184
|
-
suppressed_by: "manual_acknowledge",
|
|
3185
|
-
suppressed_at,
|
|
3186
|
-
expires_at,
|
|
3187
|
-
ttl_seconds,
|
|
3188
|
-
},
|
|
3189
|
-
);
|
|
3190
|
-
}
|
|
3191
|
-
}
|
|
3192
|
-
|
|
3193
|
-
struct SuppressionRecord<'a> {
|
|
3194
|
-
team: &'a str,
|
|
3195
|
-
agent_id: &'a str,
|
|
3196
|
-
alert_type: &'a str,
|
|
3197
|
-
suppressed_by: &'a str,
|
|
3198
|
-
suppressed_at: &'a str,
|
|
3199
|
-
expires_at: &'a str,
|
|
3200
|
-
ttl_seconds: i64,
|
|
3201
|
-
}
|
|
3202
|
-
|
|
3203
|
-
fn upsert_suppression(state: &mut Value, record: SuppressionRecord<'_>) {
|
|
3204
|
-
let Some(root) = state.as_object_mut() else {
|
|
3205
|
-
return;
|
|
3206
|
-
};
|
|
3207
|
-
let Some(coordinator) = root
|
|
3208
|
-
.entry("coordinator")
|
|
3209
|
-
.or_insert_with(|| json!({}))
|
|
3210
|
-
.as_object_mut()
|
|
3211
|
-
else {
|
|
3212
|
-
return;
|
|
3213
|
-
};
|
|
3214
|
-
let Some(all) = coordinator
|
|
3215
|
-
.entry("suppressed_idle_alerts")
|
|
3216
|
-
.or_insert_with(|| json!({}))
|
|
3217
|
-
.as_object_mut()
|
|
3218
|
-
else {
|
|
3219
|
-
return;
|
|
3220
|
-
};
|
|
3221
|
-
let Some(team_map) = all
|
|
3222
|
-
.entry(record.team.to_string())
|
|
3223
|
-
.or_insert_with(|| json!({}))
|
|
3224
|
-
.as_object_mut()
|
|
3225
|
-
else {
|
|
3226
|
-
return;
|
|
3227
|
-
};
|
|
3228
|
-
let Some(agent_map) = team_map
|
|
3229
|
-
.entry(record.agent_id.to_string())
|
|
3230
|
-
.or_insert_with(|| json!({}))
|
|
3231
|
-
.as_object_mut()
|
|
3232
|
-
else {
|
|
3233
|
-
return;
|
|
3234
|
-
};
|
|
3235
|
-
agent_map.insert(
|
|
3236
|
-
record.alert_type.to_string(),
|
|
3237
|
-
json!({
|
|
3238
|
-
"suppressed_at": record.suppressed_at,
|
|
3239
|
-
"suppressed_by": record.suppressed_by,
|
|
3240
|
-
"manual_acknowledge": true,
|
|
3241
|
-
"expires_at": record.expires_at,
|
|
3242
|
-
"ttl_seconds": record.ttl_seconds,
|
|
3243
|
-
}),
|
|
3244
|
-
);
|
|
3245
|
-
}
|
|
3246
|
-
|
|
3247
3189
|
const COMPACT_READINESS_KEYS: [&str; 6] = [
|
|
3248
3190
|
"all_workers_spawned",
|
|
3249
3191
|
"state",
|
|
@@ -3340,10 +3282,10 @@ pub mod lifecycle_port {
|
|
|
3340
3282
|
{
|
|
3341
3283
|
return vec![
|
|
3342
3284
|
"repair state.session_name to the worker session; it currently names the leader launcher session".to_string(),
|
|
3343
|
-
format!("team-agent
|
|
3285
|
+
format!("team-agent doctor --team {team} --json"),
|
|
3344
3286
|
];
|
|
3345
3287
|
}
|
|
3346
|
-
let mut actions = vec![format!("team-agent
|
|
3288
|
+
let mut actions = vec![format!("team-agent doctor --team {team} --json")];
|
|
3347
3289
|
if issue_ids
|
|
3348
3290
|
.iter()
|
|
3349
3291
|
.any(|id| id.contains("socket") || id.contains("endpoint"))
|
|
@@ -3362,7 +3304,6 @@ pub mod lifecycle_port {
|
|
|
3362
3304
|
launch,
|
|
3363
3305
|
next_actions,
|
|
3364
3306
|
attach_commands,
|
|
3365
|
-
display_backend,
|
|
3366
3307
|
worker_readiness,
|
|
3367
3308
|
team,
|
|
3368
3309
|
} => {
|
|
@@ -3502,7 +3443,6 @@ pub mod lifecycle_port {
|
|
|
3502
3443
|
"team": team,
|
|
3503
3444
|
"agent_ids": launch.started.iter().map(|agent| agent.agent_id.as_str()).collect::<Vec<_>>(),
|
|
3504
3445
|
"dry_run": launch.dry_run,
|
|
3505
|
-
"display_backend": display_backend,
|
|
3506
3446
|
"next_actions": next_actions,
|
|
3507
3447
|
"attach_commands": attach_commands,
|
|
3508
3448
|
"reminder": crate::cli::QUICK_START_REMINDER,
|
|
@@ -3638,12 +3578,6 @@ pub mod lifecycle_port {
|
|
|
3638
3578
|
claude_config_dir: None,
|
|
3639
3579
|
provider_projects_root: None,
|
|
3640
3580
|
managed_mcp_config: false,
|
|
3641
|
-
layout_window: None,
|
|
3642
|
-
layout_index: None,
|
|
3643
|
-
pane_index: None,
|
|
3644
|
-
display: crate::lifecycle::WorkerDisplay::Blocked {
|
|
3645
|
-
reason: crate::lifecycle::AdaptiveBlockReason::AggregatorRebuildFailed,
|
|
3646
|
-
},
|
|
3647
3581
|
}],
|
|
3648
3582
|
dry_run: false,
|
|
3649
3583
|
tmux_endpoint: None,
|
|
@@ -3655,7 +3589,6 @@ pub mod lifecycle_port {
|
|
|
3655
3589
|
}),
|
|
3656
3590
|
next_actions: Vec::new(),
|
|
3657
3591
|
attach_commands: Vec::new(),
|
|
3658
|
-
display_backend: "none".to_string(),
|
|
3659
3592
|
worker_readiness: crate::lifecycle::QuickStartReadiness::PendingToolLoad,
|
|
3660
3593
|
team: "team-demo".to_string(),
|
|
3661
3594
|
});
|
|
@@ -3773,7 +3706,7 @@ pub mod lifecycle_port {
|
|
|
3773
3706
|
"status": "partial",
|
|
3774
3707
|
"reason": "restart_agent_failed",
|
|
3775
3708
|
"failed_agents": [{"agent_id": "worker", "error": "spawn failed"}],
|
|
3776
|
-
"next_actions": ["
|
|
3709
|
+
"next_actions": ["reset-agent worker --discard-session"],
|
|
3777
3710
|
"attach_commands": [],
|
|
3778
3711
|
"coordinator": {"status": "running", "transport": "default"}
|
|
3779
3712
|
});
|
|
@@ -3781,7 +3714,7 @@ pub mod lifecycle_port {
|
|
|
3781
3714
|
compact_restart_value(&mut default);
|
|
3782
3715
|
assert_eq!(default["status"], json!("partial"));
|
|
3783
3716
|
assert_eq!(default["reason"], json!("restart_agent_failed"));
|
|
3784
|
-
assert_eq!(default["next_actions"], json!(["
|
|
3717
|
+
assert_eq!(default["next_actions"], json!(["reset-agent worker --discard-session"]));
|
|
3785
3718
|
assert!(default.get("coordinator").is_none());
|
|
3786
3719
|
assert!(detail.get("coordinator").is_some());
|
|
3787
3720
|
}
|
|
@@ -4063,7 +3996,7 @@ pub mod lifecycle_port {
|
|
|
4063
3996
|
"phase": failure.phase,
|
|
4064
3997
|
"error": failure.error,
|
|
4065
3998
|
"action": format!(
|
|
4066
|
-
"inspect worker {} output, then
|
|
3999
|
+
"inspect worker {} output, then reset that worker with `team-agent reset-agent {} --discard-session` or rerun `team-agent restart --allow-fresh`",
|
|
4067
4000
|
failure.agent_id,
|
|
4068
4001
|
failure.agent_id
|
|
4069
4002
|
),
|
|
@@ -4109,7 +4042,7 @@ pub mod lifecycle_port {
|
|
|
4109
4042
|
"phase": failure.phase,
|
|
4110
4043
|
"error": failure.error,
|
|
4111
4044
|
"action": format!(
|
|
4112
|
-
"inspect worker {} output, then
|
|
4045
|
+
"inspect worker {} output, then reset that worker with `team-agent reset-agent {} --discard-session` or rerun `team-agent restart --allow-fresh`",
|
|
4113
4046
|
failure.agent_id,
|
|
4114
4047
|
failure.agent_id
|
|
4115
4048
|
),
|
|
@@ -27,13 +27,13 @@ pub(super) fn dirty_topology_refusal_value(
|
|
|
27
27
|
"status": "refused_dirty_topology",
|
|
28
28
|
"reason": reason,
|
|
29
29
|
"session_name": session_name,
|
|
30
|
-
"error": "send refused: tmux endpoint/socket topology is inconsistent; run
|
|
30
|
+
"error": "send refused: tmux endpoint/socket topology is inconsistent; run doctor from the intended leader socket before sending",
|
|
31
31
|
"issues": issue_ids
|
|
32
32
|
.iter()
|
|
33
33
|
.map(|id| json!({"id": id}))
|
|
34
34
|
.collect::<Vec<_>>(),
|
|
35
35
|
"next_actions": [
|
|
36
|
-
"team-agent
|
|
36
|
+
"team-agent doctor --json",
|
|
37
37
|
format!("team-agent claim-leader --team {repair_team} --confirm --json"),
|
|
38
38
|
format!("team-agent takeover --team {repair_team} --confirm --json")
|
|
39
39
|
],
|