@team-agent/installer 0.5.98 → 0.5.99
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 +0 -7
- package/crates/team-agent/src/cli/diagnose.rs +0 -16
- package/crates/team-agent/src/cli/emit.rs +15 -20
- package/crates/team-agent/src/cli/mod.rs +130 -31
- package/crates/team-agent/src/cli/spec.rs +7 -7
- package/crates/team-agent/src/cli/tests/main_preserved.rs +0 -1
- package/crates/team-agent/src/cli/tests/missing_subcommands.rs +0 -1
- 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/types.rs +0 -6
- package/crates/team-agent/src/compiler/tests.rs +5 -8
- package/crates/team-agent/src/compiler.rs +6 -14
- 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 +0 -9
- 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 +7 -7
- package/crates/team-agent/src/lifecycle/tests/pi_executable_mcp_red.rs +1 -1
- package/crates/team-agent/src/lifecycle/tests/startup_latency_contract.rs +3 -4
- 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/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 +6 -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/team-agent-operator.md +3 -10
- 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
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
//! provides:
|
|
5
5
|
//! - name: quick_start
|
|
6
6
|
//! what: 由角色目录一键起队
|
|
7
|
-
//! - name:
|
|
8
|
-
//! what:
|
|
9
|
-
//! - name:
|
|
7
|
+
//! - name: quick_start_in_workspace_with_backend
|
|
8
|
+
//! what: 带 transport 后端选择的起队入口
|
|
9
|
+
//! - name: quick_start_with_transport_in_workspace
|
|
10
10
|
//! what: 起队的实体实现,含 leader pane 校验、层级门与已有 runtime 的早退
|
|
11
11
|
//! depends:
|
|
12
12
|
//! - crate::compiler
|
|
@@ -29,7 +29,7 @@ use std::path::{Path, PathBuf};
|
|
|
29
29
|
use std::process::Command;
|
|
30
30
|
|
|
31
31
|
use crate::lifecycle::*;
|
|
32
|
-
use crate::model::enums::{AuthMode,
|
|
32
|
+
use crate::model::enums::{AuthMode, PaneLiveness, Provider, ProviderEffort};
|
|
33
33
|
use crate::model::ids::AgentId;
|
|
34
34
|
use crate::model::yaml::{self, Value};
|
|
35
35
|
use crate::state::persist::load_runtime_state;
|
|
@@ -1042,9 +1042,8 @@ pub(crate) fn quick_start_in_workspace(
|
|
|
1042
1042
|
}
|
|
1043
1043
|
|
|
1044
1044
|
/// ---
|
|
1045
|
-
/// purpose:
|
|
1045
|
+
/// purpose: 带后端字面量的起队入口
|
|
1046
1046
|
/// params:
|
|
1047
|
-
/// open_display: 为假时把 spec 的显示后端改成 none
|
|
1048
1047
|
/// backend: 未给或为 tmux 时走既有 tmux 路径,其余字面量经 transport 工厂解析
|
|
1049
1048
|
/// returns: 起队报告
|
|
1050
1049
|
/// errors: 后端字面量不认识时返回 TeamSelect;工厂拒绝或后端不可用时如实报错,不退回 tmux
|
|
@@ -1061,13 +1060,12 @@ pub(crate) fn quick_start_in_workspace(
|
|
|
1061
1060
|
/// its spawn/inject/capture calls to `TransportError::MuxUnavailable`
|
|
1062
1061
|
/// honestly — MUST-NOT-13 + CR C-1 ①. Users see a real "conpty
|
|
1063
1062
|
/// unavailable" error rather than a silent tmux fallback.
|
|
1064
|
-
pub fn
|
|
1063
|
+
pub fn quick_start_in_workspace_with_backend(
|
|
1065
1064
|
workspace: &Path,
|
|
1066
1065
|
agents_dir: &Path,
|
|
1067
1066
|
name: Option<&str>,
|
|
1068
1067
|
yes: bool,
|
|
1069
1068
|
team_id: Option<&str>,
|
|
1070
|
-
open_display: bool,
|
|
1071
1069
|
backend: Option<&str>,
|
|
1072
1070
|
) -> Result<QuickStartReport, LifecycleError> {
|
|
1073
1071
|
let workspace = explicit_quick_start_workspace(workspace);
|
|
@@ -1079,14 +1077,13 @@ pub fn quick_start_in_workspace_with_display_and_backend(
|
|
|
1079
1077
|
let is_tmux_or_default = matches!(literal, None | Some("") | Some("tmux") | Some("TMUX"));
|
|
1080
1078
|
if is_tmux_or_default {
|
|
1081
1079
|
let transport = quick_start_tmux_backend(&workspace);
|
|
1082
|
-
return
|
|
1080
|
+
return quick_start_with_transport_in_workspace(
|
|
1083
1081
|
&workspace,
|
|
1084
1082
|
agents_dir,
|
|
1085
1083
|
name,
|
|
1086
1084
|
yes,
|
|
1087
1085
|
team_id,
|
|
1088
1086
|
&transport,
|
|
1089
|
-
open_display,
|
|
1090
1087
|
);
|
|
1091
1088
|
}
|
|
1092
1089
|
// Explicit non-tmux backend: route through the factory. Parse the
|
|
@@ -1181,14 +1178,13 @@ pub fn quick_start_in_workspace_with_display_and_backend(
|
|
|
1181
1178
|
let resolved = crate::transport_factory::resolve_transport(input)
|
|
1182
1179
|
.map_err(|e| LifecycleError::TeamSelect(e.to_string()))?;
|
|
1183
1180
|
// Hand the boxed backend down as `&dyn Transport`.
|
|
1184
|
-
|
|
1181
|
+
quick_start_with_transport_in_workspace(
|
|
1185
1182
|
&workspace,
|
|
1186
1183
|
agents_dir,
|
|
1187
1184
|
name,
|
|
1188
1185
|
yes,
|
|
1189
1186
|
team_id,
|
|
1190
1187
|
&*resolved.backend,
|
|
1191
|
-
open_display,
|
|
1192
1188
|
)
|
|
1193
1189
|
}
|
|
1194
1190
|
|
|
@@ -1209,64 +1205,40 @@ pub(crate) fn quick_start_with_transport(
|
|
|
1209
1205
|
quick_start_with_transport_in_workspace(&workspace, agents_dir, name, yes, team_id, transport)
|
|
1210
1206
|
}
|
|
1211
1207
|
|
|
1212
|
-
/// ---
|
|
1213
|
-
/// purpose: 带注入 transport 与显式 workspace 的起队入口,默认开显示
|
|
1214
|
-
/// returns: 起队报告
|
|
1215
|
-
/// errors: 透传实体实现的错误
|
|
1216
|
-
/// contract_id: lifecycle.quick_start.entry
|
|
1217
|
-
/// ---
|
|
1218
|
-
pub(crate) fn quick_start_with_transport_in_workspace(
|
|
1219
|
-
workspace: &Path,
|
|
1220
|
-
agents_dir: &Path,
|
|
1221
|
-
name: Option<&str>,
|
|
1222
|
-
yes: bool,
|
|
1223
|
-
team_id: Option<&str>,
|
|
1224
|
-
transport: &dyn Transport,
|
|
1225
|
-
) -> Result<QuickStartReport, LifecycleError> {
|
|
1226
|
-
quick_start_with_transport_in_workspace_with_display(
|
|
1227
|
-
workspace, agents_dir, name, yes, team_id, transport, true,
|
|
1228
|
-
)
|
|
1229
|
-
}
|
|
1230
|
-
|
|
1231
1208
|
/// ---
|
|
1232
1209
|
/// purpose: 起队的实体实现,校验 leader pane、编译 spec、定团队键、判层级、必要时早退,然后起队并给 attach 指引
|
|
1233
|
-
/// params:
|
|
1234
|
-
/// open_display: 为假时把显示后端改成 none
|
|
1235
1210
|
/// returns: 起队报告,含 session 名与 attach 命令
|
|
1236
1211
|
/// errors: leader pane 环境无效返回 RequirementUnmet;角色目录不存在或编译失败返回 Compile;嵌套层级超限返回 RequirementUnmet;读 state 失败返回 StatePersist
|
|
1237
1212
|
/// ---
|
|
1238
|
-
pub(crate) fn
|
|
1213
|
+
pub(crate) fn quick_start_with_transport_in_workspace(
|
|
1239
1214
|
workspace: &Path,
|
|
1240
1215
|
agents_dir: &Path,
|
|
1241
1216
|
name: Option<&str>,
|
|
1242
1217
|
yes: bool,
|
|
1243
1218
|
team_id: Option<&str>,
|
|
1244
1219
|
transport: &dyn Transport,
|
|
1245
|
-
open_display: bool,
|
|
1246
1220
|
) -> Result<QuickStartReport, LifecycleError> {
|
|
1247
1221
|
let mut discover = |requested: &str| {
|
|
1248
1222
|
crate::lifecycle::launch::pi_mcp::pi_model_candidates(requested).map_err(|_| ())
|
|
1249
1223
|
};
|
|
1250
|
-
|
|
1224
|
+
quick_start_with_transport_in_workspace_pi_preflight(
|
|
1251
1225
|
workspace,
|
|
1252
1226
|
agents_dir,
|
|
1253
1227
|
name,
|
|
1254
1228
|
yes,
|
|
1255
1229
|
team_id,
|
|
1256
1230
|
transport,
|
|
1257
|
-
open_display,
|
|
1258
1231
|
&mut discover,
|
|
1259
1232
|
)
|
|
1260
1233
|
}
|
|
1261
1234
|
|
|
1262
|
-
pub(crate) fn
|
|
1235
|
+
pub(crate) fn quick_start_with_transport_in_workspace_pi_preflight(
|
|
1263
1236
|
workspace: &Path,
|
|
1264
1237
|
agents_dir: &Path,
|
|
1265
1238
|
name: Option<&str>,
|
|
1266
1239
|
yes: bool,
|
|
1267
1240
|
team_id: Option<&str>,
|
|
1268
1241
|
transport: &dyn Transport,
|
|
1269
|
-
open_display: bool,
|
|
1270
1242
|
discover: &mut dyn FnMut(&str) -> Result<Vec<String>, ()>,
|
|
1271
1243
|
) -> Result<QuickStartReport, LifecycleError> {
|
|
1272
1244
|
// B-7 / 036b N38 三行 fail-fast — TEAM_AGENT_LEADER_PANE_ID 主动路径在 quick-start
|
|
@@ -1288,9 +1260,6 @@ pub(crate) fn quick_start_with_transport_in_workspace_with_display_pi_preflight(
|
|
|
1288
1260
|
let mut spec = crate::compiler::compile_team(agents_dir)
|
|
1289
1261
|
.map_err(|e| LifecycleError::Compile(e.to_string()))?;
|
|
1290
1262
|
override_spec_workspace(&mut spec, &workspace);
|
|
1291
|
-
if !open_display {
|
|
1292
|
-
override_spec_display_backend(&mut spec, "none");
|
|
1293
|
-
}
|
|
1294
1263
|
let explicit_team_key = quick_start_requested_team_key(team_id, name).map(str::to_string);
|
|
1295
1264
|
let canonical_team_key = explicit_team_key
|
|
1296
1265
|
.clone()
|
|
@@ -1487,7 +1456,7 @@ pub(crate) fn quick_start_with_transport_in_workspace_with_display_pi_preflight(
|
|
|
1487
1456
|
team_depth.team_depth,
|
|
1488
1457
|
)?;
|
|
1489
1458
|
// Fresh initialization owns this one fail-closed bind attempt. It is
|
|
1490
|
-
// independent of
|
|
1459
|
+
// independent of worker layout, so receiver binding is always attempted
|
|
1491
1460
|
// binding. Readiness receives true only after canonical registry readback.
|
|
1492
1461
|
let _ = take_fresh_bind_refusal();
|
|
1493
1462
|
launch.leader_receiver_attached = bind_fresh_quick_start_leader(
|
|
@@ -1562,18 +1531,12 @@ pub(crate) fn quick_start_with_transport_in_workspace_with_display_pi_preflight(
|
|
|
1562
1531
|
));
|
|
1563
1532
|
}
|
|
1564
1533
|
next_actions.extend(attach_commands.iter().cloned());
|
|
1565
|
-
let display_backend = state
|
|
1566
|
-
.get("display_backend")
|
|
1567
|
-
.and_then(serde_json::Value::as_str)
|
|
1568
|
-
.unwrap_or("none")
|
|
1569
|
-
.to_string();
|
|
1570
1534
|
quick_start_phase_timer.emit(&workspace, "launch.phase", "completed");
|
|
1571
1535
|
Ok(QuickStartReport::Ready {
|
|
1572
1536
|
session_name,
|
|
1573
1537
|
launch: Box::new(launch),
|
|
1574
1538
|
next_actions,
|
|
1575
1539
|
attach_commands,
|
|
1576
|
-
display_backend,
|
|
1577
1540
|
worker_readiness,
|
|
1578
1541
|
team: state_team_key,
|
|
1579
1542
|
})
|
|
@@ -26,7 +26,7 @@ use std::path::{Path, PathBuf};
|
|
|
26
26
|
use std::process::Command;
|
|
27
27
|
|
|
28
28
|
use crate::lifecycle::*;
|
|
29
|
-
use crate::model::enums::{AuthMode,
|
|
29
|
+
use crate::model::enums::{AuthMode, PaneLiveness, Provider, ProviderEffort};
|
|
30
30
|
use crate::model::ids::AgentId;
|
|
31
31
|
use crate::model::yaml::{self, Value};
|
|
32
32
|
use crate::state::persist::load_runtime_state;
|
|
@@ -270,18 +270,12 @@ pub(super) fn attach_commands_for_runtime_windows<'a>(
|
|
|
270
270
|
|
|
271
271
|
/// ---
|
|
272
272
|
/// purpose: 由本次起的席位推出窗口名
|
|
273
|
-
/// returns:
|
|
273
|
+
/// returns: 排序去重后的窗口名,按席位名分配独立窗口
|
|
274
274
|
/// ---
|
|
275
275
|
pub(super) fn started_attach_window_names(started: &[StartedAgent]) -> Vec<String> {
|
|
276
276
|
let mut windows = started
|
|
277
277
|
.iter()
|
|
278
|
-
.map(|started|
|
|
279
|
-
started
|
|
280
|
-
.layout_window
|
|
281
|
-
.as_ref()
|
|
282
|
-
.map(|window| window.as_str().to_string())
|
|
283
|
-
.unwrap_or_else(|| started.agent_id.as_str().to_string())
|
|
284
|
-
})
|
|
278
|
+
.map(|started| started.agent_id.as_str().to_string())
|
|
285
279
|
.collect::<Vec<_>>();
|
|
286
280
|
windows.sort();
|
|
287
281
|
windows.dedup();
|
|
@@ -28,7 +28,7 @@ use std::path::{Path, PathBuf};
|
|
|
28
28
|
use std::process::Command;
|
|
29
29
|
|
|
30
30
|
use crate::lifecycle::*;
|
|
31
|
-
use crate::model::enums::{AuthMode,
|
|
31
|
+
use crate::model::enums::{AuthMode, PaneLiveness, Provider, ProviderEffort};
|
|
32
32
|
use crate::model::ids::AgentId;
|
|
33
33
|
use crate::model::yaml::{self, Value};
|
|
34
34
|
use crate::state::persist::load_runtime_state;
|
|
@@ -30,7 +30,7 @@ use std::process::Command;
|
|
|
30
30
|
|
|
31
31
|
use crate::lifecycle::profile_launch::parse_provider;
|
|
32
32
|
use crate::lifecycle::*;
|
|
33
|
-
use crate::model::enums::{AuthMode,
|
|
33
|
+
use crate::model::enums::{AuthMode, PaneLiveness, Provider, ProviderEffort};
|
|
34
34
|
use crate::model::ids::AgentId;
|
|
35
35
|
use crate::model::yaml::{self, Value};
|
|
36
36
|
use crate::state::persist::load_runtime_state;
|
|
@@ -73,22 +73,6 @@ pub(super) fn spawn_agents(
|
|
|
73
73
|
spec.get("runtime").and_then(|v| v.get("fast")),
|
|
74
74
|
Some(Value::Bool(true))
|
|
75
75
|
);
|
|
76
|
-
let display_backend = spec_display_backend(spec);
|
|
77
|
-
let active_agent_ids = spec_agent_values(spec)
|
|
78
|
-
.into_iter()
|
|
79
|
-
.filter_map(|agent| {
|
|
80
|
-
if agent_is_paused(agent) {
|
|
81
|
-
None
|
|
82
|
-
} else {
|
|
83
|
-
agent.get("id").and_then(Value::as_str).map(AgentId::new)
|
|
84
|
-
}
|
|
85
|
-
})
|
|
86
|
-
.collect::<Vec<_>>();
|
|
87
|
-
let layout_plan = if display_backend == DisplayBackend::Adaptive {
|
|
88
|
-
adaptive_layout_plan(&active_agent_ids, ADAPTIVE_LAYOUT_MAX_PER_WINDOW)
|
|
89
|
-
} else {
|
|
90
|
-
Vec::new()
|
|
91
|
-
};
|
|
92
76
|
let mut started = Vec::new();
|
|
93
77
|
for agent in spec_agent_values(spec) {
|
|
94
78
|
let Some(agent_id_raw) = agent.get("id").and_then(Value::as_str) else {
|
|
@@ -392,15 +376,7 @@ pub(super) fn spawn_agents(
|
|
|
392
376
|
);
|
|
393
377
|
}
|
|
394
378
|
}
|
|
395
|
-
//
|
|
396
|
-
// with Python-parity 1-window-per-agent placement. Window name =
|
|
397
|
-
// `agent_id`; first worker creates the session via spawn_first,
|
|
398
|
-
// subsequent workers each get a new window via spawn_into. No splits
|
|
399
|
-
// in the worker session — Step 8's `assert_overlay_call_site` would
|
|
400
|
-
// catch any drift if a split call snuck back in. The `placement`
|
|
401
|
-
// variable is set to None to signal "no adaptive layout" to all
|
|
402
|
-
// downstream consumers (display dict, layout_window persistence).
|
|
403
|
-
let placement: Option<LayoutPlacement> = None;
|
|
379
|
+
// One independent tmux window per worker; no GUI/display layout.
|
|
404
380
|
let window = WindowName::new(agent_id_raw);
|
|
405
381
|
let spawn = if started.is_empty() {
|
|
406
382
|
transport.spawn_first_with_env_unset(
|
|
@@ -436,35 +412,6 @@ pub(super) fn spawn_agents(
|
|
|
436
412
|
if matches!(transport.liveness(&spawn.pane_id), Ok(PaneLiveness::Dead)) {
|
|
437
413
|
continue;
|
|
438
414
|
}
|
|
439
|
-
if placement.is_some() {
|
|
440
|
-
configure_adaptive_pane_title(
|
|
441
|
-
workspace,
|
|
442
|
-
transport,
|
|
443
|
-
session_name,
|
|
444
|
-
&window,
|
|
445
|
-
&spawn.pane_id,
|
|
446
|
-
agent_id_raw,
|
|
447
|
-
);
|
|
448
|
-
}
|
|
449
|
-
let display = if placement.is_some() {
|
|
450
|
-
WorkerDisplay::Adaptive {
|
|
451
|
-
status: DisplayStatus::Opened,
|
|
452
|
-
window: Some(spawn.window.clone()),
|
|
453
|
-
workspace_window: None,
|
|
454
|
-
pane_id: Some(spawn.pane_id.clone()),
|
|
455
|
-
pane_title: Some(agent_id_raw.to_string()),
|
|
456
|
-
target: Some(spawn.pane_id.as_str().to_string()),
|
|
457
|
-
target_worker_session: Some(session_name.as_str().to_string()),
|
|
458
|
-
linked_session: None,
|
|
459
|
-
leader_session: Some(session_name.clone()),
|
|
460
|
-
display_session: None,
|
|
461
|
-
fallback: None,
|
|
462
|
-
}
|
|
463
|
-
} else {
|
|
464
|
-
WorkerDisplay::Blocked {
|
|
465
|
-
reason: AdaptiveBlockReason::NotImplementedThisPlatform,
|
|
466
|
-
}
|
|
467
|
-
};
|
|
468
415
|
started.push(StartedAgent {
|
|
469
416
|
agent_id,
|
|
470
417
|
start_mode: StartMode::Fresh,
|
|
@@ -479,12 +426,6 @@ pub(super) fn spawn_agents(
|
|
|
479
426
|
.clone()
|
|
480
427
|
.or_else(|| profile_launch.claude_projects_root.clone()),
|
|
481
428
|
managed_mcp_config: plan.managed_mcp_config || profile_launch.managed_mcp_config,
|
|
482
|
-
layout_window: placement
|
|
483
|
-
.as_ref()
|
|
484
|
-
.map(|placement| placement.layout_window.clone()),
|
|
485
|
-
layout_index: placement.as_ref().map(|placement| placement.layout_index),
|
|
486
|
-
pane_index: placement.as_ref().map(|placement| placement.pane_index),
|
|
487
|
-
display,
|
|
488
429
|
});
|
|
489
430
|
}
|
|
490
431
|
Ok(started)
|
|
@@ -60,7 +60,7 @@ use std::path::{Path, PathBuf};
|
|
|
60
60
|
use std::process::Command;
|
|
61
61
|
|
|
62
62
|
use crate::lifecycle::*;
|
|
63
|
-
use crate::model::enums::{AuthMode,
|
|
63
|
+
use crate::model::enums::{AuthMode, PaneLiveness, Provider, ProviderEffort};
|
|
64
64
|
use crate::model::ids::AgentId;
|
|
65
65
|
use crate::model::yaml::{self, Value};
|
|
66
66
|
use crate::state::persist::load_runtime_state;
|
|
@@ -68,7 +68,6 @@ use crate::transport::{PaneId, SessionName, Target, Transport, WindowName};
|
|
|
68
68
|
|
|
69
69
|
use crate::lifecycle::lock::{acquire_agent_lifecycle_lock, LifecycleLockRequest};
|
|
70
70
|
|
|
71
|
-
use super::identity::spec_display_backend;
|
|
72
71
|
use super::leader_context::{caller_provider_for_seed_with_lookup, seed_unbound_launched_owner};
|
|
73
72
|
use super::worker_env::spawn_timestamp;
|
|
74
73
|
|
|
@@ -77,7 +76,7 @@ use super::worker_env::spawn_timestamp;
|
|
|
77
76
|
/// params:
|
|
78
77
|
/// team_key: 本团队的 runtime 键
|
|
79
78
|
/// scoped_endpoint: 本次起队已选中的 transport tmux endpoint,与 fresh bind 同源
|
|
80
|
-
/// returns: 含 spec_path、workspace、team_dir、session_name、leader、agents、tasks
|
|
79
|
+
/// returns: 含 spec_path、workspace、team_dir、session_name、leader、agents、tasks 的 state;随后按环境种入 launched owner,种不到则种一份 unbound owner
|
|
81
80
|
/// ---
|
|
82
81
|
pub(super) fn initial_runtime_state(
|
|
83
82
|
spec: &Value,
|
|
@@ -113,7 +112,6 @@ pub(super) fn initial_runtime_state(
|
|
|
113
112
|
}
|
|
114
113
|
agents.insert(id.to_string(), value);
|
|
115
114
|
}
|
|
116
|
-
let display_backend = spec_display_backend(spec);
|
|
117
115
|
let mut state = serde_json::Map::new();
|
|
118
116
|
state.insert(
|
|
119
117
|
"spec_path".to_string(),
|
|
@@ -140,10 +138,6 @@ pub(super) fn initial_runtime_state(
|
|
|
140
138
|
);
|
|
141
139
|
state.insert("agents".to_string(), serde_json::Value::Object(agents));
|
|
142
140
|
state.insert("tasks".to_string(), spec_tasks_json(spec));
|
|
143
|
-
state.insert(
|
|
144
|
-
"display_backend".to_string(),
|
|
145
|
-
serde_json::json!(display_backend),
|
|
146
|
-
);
|
|
147
141
|
state.insert("is_external_leader".to_string(), serde_json::json!(false));
|
|
148
142
|
let mut state = serde_json::Value::Object(state);
|
|
149
143
|
if !seed_launched_owner_from_env(&mut state, scoped_endpoint) {
|
|
@@ -377,13 +371,6 @@ pub(crate) fn override_spec_workspace(spec: &mut Value, workspace: &Path) {
|
|
|
377
371
|
}
|
|
378
372
|
}
|
|
379
373
|
|
|
380
|
-
/// ---
|
|
381
|
-
/// purpose: 就地把 spec 的 runtime.display_backend 改成给定值
|
|
382
|
-
/// ---
|
|
383
|
-
pub(super) fn override_spec_display_backend(spec: &mut Value, display_backend: &str) {
|
|
384
|
-
override_spec_runtime_str(spec, "display_backend", display_backend);
|
|
385
|
-
}
|
|
386
|
-
|
|
387
374
|
/// ---
|
|
388
375
|
/// purpose: 就地写 spec 的 runtime 段下某个字符串字段
|
|
389
376
|
/// params:
|
|
@@ -35,11 +35,11 @@ use std::process::Command;
|
|
|
35
35
|
use crate::lifecycle::lock::{acquire_agent_lifecycle_lock, LifecycleLockRequest};
|
|
36
36
|
use crate::lifecycle::profile_launch::parse_provider;
|
|
37
37
|
use crate::lifecycle::*;
|
|
38
|
-
use crate::model::enums::{AuthMode,
|
|
38
|
+
use crate::model::enums::{AuthMode, PaneLiveness, Provider, ProviderEffort};
|
|
39
39
|
use crate::model::ids::AgentId;
|
|
40
40
|
use crate::model::yaml::{self, Value};
|
|
41
41
|
use crate::state::persist::load_runtime_state;
|
|
42
|
-
use crate::transport::{PaneId, SessionName, Target, Transport
|
|
42
|
+
use crate::transport::{PaneId, SessionName, Target, Transport};
|
|
43
43
|
|
|
44
44
|
use super::agent_state::running_agent_state;
|
|
45
45
|
use super::identity::{explicit_active_team_key, runtime_team_key_for_spec};
|
|
@@ -132,11 +132,7 @@ pub(super) fn persist_spawn_agent_state(
|
|
|
132
132
|
continue;
|
|
133
133
|
}
|
|
134
134
|
let started_agent = started.iter().find(|agent| agent.agent_id.as_str() == id);
|
|
135
|
-
let window =
|
|
136
|
-
.and_then(|started| started.layout_window.as_ref())
|
|
137
|
-
.map(WindowName::as_str)
|
|
138
|
-
.or_else(|| agent.get("window").and_then(Value::as_str))
|
|
139
|
-
.unwrap_or(id);
|
|
135
|
+
let window = agent.get("window").and_then(Value::as_str).unwrap_or(id);
|
|
140
136
|
if !live_started_agents.contains(id)
|
|
141
137
|
|| (!live_windows.is_empty() && !live_windows.contains(window))
|
|
142
138
|
{
|
|
@@ -28,7 +28,7 @@ use std::path::{Path, PathBuf};
|
|
|
28
28
|
use std::process::Command;
|
|
29
29
|
|
|
30
30
|
use crate::lifecycle::*;
|
|
31
|
-
use crate::model::enums::{AuthMode,
|
|
31
|
+
use crate::model::enums::{AuthMode, PaneLiveness, Provider, ProviderEffort};
|
|
32
32
|
use crate::model::ids::AgentId;
|
|
33
33
|
use crate::model::yaml::{self, Value};
|
|
34
34
|
use crate::state::persist::load_runtime_state;
|
|
@@ -15,7 +15,7 @@ use std::collections::{BTreeMap, BTreeSet};
|
|
|
15
15
|
use std::path::{Path, PathBuf};
|
|
16
16
|
use std::process::Command;
|
|
17
17
|
|
|
18
|
-
use crate::model::enums::{AuthMode,
|
|
18
|
+
use crate::model::enums::{AuthMode, PaneLiveness, Provider, ProviderEffort};
|
|
19
19
|
use crate::model::ids::AgentId;
|
|
20
20
|
use crate::model::yaml::{self, Value};
|
|
21
21
|
use crate::state::persist::load_runtime_state;
|
|
@@ -370,10 +370,9 @@ pub(crate) use identity::{
|
|
|
370
370
|
|
|
371
371
|
mod quick_start;
|
|
372
372
|
pub(super) use quick_start::*;
|
|
373
|
-
pub use quick_start::{quick_start,
|
|
373
|
+
pub use quick_start::{quick_start, quick_start_in_workspace_with_backend};
|
|
374
374
|
pub(crate) use quick_start::{
|
|
375
375
|
quick_start_in_workspace, quick_start_with_transport, quick_start_with_transport_in_workspace,
|
|
376
|
-
quick_start_with_transport_in_workspace_with_display,
|
|
377
376
|
};
|
|
378
377
|
|
|
379
378
|
mod quick_start_transport;
|
|
@@ -426,7 +425,7 @@ pub(crate) use spec_state::{
|
|
|
426
425
|
};
|
|
427
426
|
use spec_state::{
|
|
428
427
|
env_nonempty, has_positive_caller_leader_env, initial_runtime_state,
|
|
429
|
-
|
|
428
|
+
override_spec_runtime_str, seed_launched_owner_from_env,
|
|
430
429
|
spec_agent_values, spec_agents, spec_default_assignee, spec_routes, spec_session_name,
|
|
431
430
|
spec_tasks_json, team_workspace, yaml_value_to_json,
|
|
432
431
|
};
|
|
@@ -20,8 +20,6 @@
|
|
|
20
20
|
//! (`stop_agent`/`reset_agent`/`add_agent`/`fork_agent`)、`lifecycle/agents.py`
|
|
21
21
|
//! (`remove_agent` + `_RemoveRollback`)、`lifecycle/paste_buffer_hygiene.py`。
|
|
22
22
|
//! - `restart/orchestration.py`(`restart` Route B)、`restart/selection.py`、`restart/snapshot.py`。
|
|
23
|
-
//! - `display/backend.py`/`adaptive.py`/`tiling.py`/`workspace.py`/`worker_window.py`/
|
|
24
|
-
//! `ghostty.py`/`close.py`/`rebuild.py`。
|
|
25
23
|
//! - `orchestrator/__init__.py`/`plan.py`/`state.py`(plan 多 stage 状态机)。
|
|
26
24
|
//! - `diagnose/quick_start.py`(`quick_start`/`prepare_quick_start_team`/`wait_ready`)。
|
|
27
25
|
//!
|
|
@@ -52,7 +50,6 @@
|
|
|
52
50
|
// (unimplemented!() stub 不被拦);tests 子模块各自 allow。
|
|
53
51
|
#![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
|
|
54
52
|
|
|
55
|
-
pub mod display;
|
|
56
53
|
pub mod helpers;
|
|
57
54
|
pub mod launch;
|
|
58
55
|
pub(crate) mod lock;
|
|
@@ -70,12 +67,7 @@ use crate::model::ids::AgentId;
|
|
|
70
67
|
use crate::provider::{RolloutPath, SessionId};
|
|
71
68
|
use crate::transport::{PaneId, SessionName, WindowName};
|
|
72
69
|
|
|
73
|
-
// 复用既有 enum(model::enums)。`DisplayBackend` 已在 step2 定义并带 `has_worker_views()`。
|
|
74
|
-
pub use crate::model::enums::DisplayBackend;
|
|
75
|
-
|
|
76
70
|
pub use types::*;
|
|
77
|
-
|
|
78
|
-
pub use display::*;
|
|
79
71
|
pub use launch::*;
|
|
80
72
|
pub use restart::*;
|
|
81
73
|
|
|
@@ -228,16 +228,19 @@ pub(crate) fn start_agent_at_paths(
|
|
|
228
228
|
// spawn (defensive). The check itself remains a no-op on healthy
|
|
229
229
|
// state — assert_topology_invariants from Step 1 catches the
|
|
230
230
|
// upstream corruption.
|
|
231
|
-
let
|
|
232
|
-
|
|
231
|
+
let noop_pane_conflict = noop_pane.as_ref().is_some_and(|pane| {
|
|
232
|
+
pane_conflicts_with_pane_id(&state, agent_id, &raw_agent, pane.pane_id.as_str())
|
|
233
|
+
});
|
|
234
|
+
let has_collision = pane_conflicts_with_leader_or_other(&state, agent_id, &raw_agent)
|
|
235
|
+
|| noop_pane_conflict;
|
|
236
|
+
if has_collision {
|
|
233
237
|
eprintln!(
|
|
234
|
-
"team_agent::layout
|
|
238
|
+
"team_agent::layout pane_collision agent_id=`{agent_id}` \
|
|
235
239
|
action=forcing_fresh_spawn \
|
|
236
|
-
(
|
|
237
|
-
investigate upstream state corruption)"
|
|
240
|
+
(the persisted or discovered pane is owned by another live seat)"
|
|
238
241
|
);
|
|
239
242
|
}
|
|
240
|
-
let agent_live = agent_live &&
|
|
243
|
+
let agent_live = agent_live && !has_collision;
|
|
241
244
|
// 0.5.66 bypass 单源 §2.6 — dangerously_skip_permissions drift 挡板:
|
|
242
245
|
// Noop 路径不 spawn,若声明面(角色 md / spec)与运行面(as-launched)不一致,
|
|
243
246
|
// 拒 Noop 报明,防"改了 md 却以为生效、进程仍带旧 bypass 跑着"的暗门。
|
|
@@ -678,6 +681,15 @@ fn pane_conflicts_with_leader_or_other(
|
|
|
678
681
|
else {
|
|
679
682
|
return false;
|
|
680
683
|
};
|
|
684
|
+
pane_conflicts_with_pane_id(state, agent_id, agent, pane_id)
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
fn pane_conflicts_with_pane_id(
|
|
688
|
+
state: &serde_json::Value,
|
|
689
|
+
agent_id: &crate::model::ids::AgentId,
|
|
690
|
+
agent: &serde_json::Value,
|
|
691
|
+
pane_id: &str,
|
|
692
|
+
) -> bool {
|
|
681
693
|
let state_socket = runtime_tmux_socket(state);
|
|
682
694
|
let agent_socket = tmux_socket_field(agent).or(state_socket);
|
|
683
695
|
// Check leader anchor.
|
|
@@ -693,7 +705,12 @@ fn pane_conflicts_with_leader_or_other(
|
|
|
693
705
|
// Check other agents.
|
|
694
706
|
if let Some(agents) = state.get("agents").and_then(serde_json::Value::as_object) {
|
|
695
707
|
for (id, other) in agents {
|
|
696
|
-
if id == agent_id.as_str()
|
|
708
|
+
if id == agent_id.as_str()
|
|
709
|
+
|| matches!(
|
|
710
|
+
other.get("status").and_then(serde_json::Value::as_str),
|
|
711
|
+
Some("stopped" | "paused" | "failed" | "removed")
|
|
712
|
+
)
|
|
713
|
+
{
|
|
697
714
|
continue;
|
|
698
715
|
}
|
|
699
716
|
let other_socket = tmux_socket_field(other).or(state_socket);
|
|
@@ -1165,7 +1182,6 @@ pub(super) fn stop_agent_at_paths(
|
|
|
1165
1182
|
);
|
|
1166
1183
|
let _ = write_stop_drain_event(workspace, agent_id, &target_str, &drain_evidence);
|
|
1167
1184
|
}
|
|
1168
|
-
close_agent_display(&mut state, agent_id);
|
|
1169
1185
|
mark_agent_stopped(&mut state, agent_id, agent, &window)?;
|
|
1170
1186
|
let team_key = restart_projection_team_key(&state, team);
|
|
1171
1187
|
// golden operations.py:95: save_team_scoped_state (team projection) — NOT a raw save, so a
|
|
@@ -2041,6 +2057,44 @@ mod tests {
|
|
|
2041
2057
|
);
|
|
2042
2058
|
}
|
|
2043
2059
|
|
|
2060
|
+
#[test]
|
|
2061
|
+
fn e51_restart_rejects_discovered_peer_pane_for_stale_worker() {
|
|
2062
|
+
let socket = "/private/tmp/tmux-501/ta-worker";
|
|
2063
|
+
let state = serde_json::json!({
|
|
2064
|
+
"tmux_endpoint": socket,
|
|
2065
|
+
"agents": {
|
|
2066
|
+
"worker": {"pane_id": "%1", "status": "running"},
|
|
2067
|
+
"peer": {"pane_id": "%0", "status": "running"}
|
|
2068
|
+
}
|
|
2069
|
+
});
|
|
2070
|
+
let agent_id = AgentId::new("worker");
|
|
2071
|
+
let agent = state["agents"]["worker"].clone();
|
|
2072
|
+
|
|
2073
|
+
assert!(
|
|
2074
|
+
pane_conflicts_with_pane_id(&state, &agent_id, &agent, "%0"),
|
|
2075
|
+
"a discovered live pane owned by a running peer must not be rebound"
|
|
2076
|
+
);
|
|
2077
|
+
}
|
|
2078
|
+
|
|
2079
|
+
#[test]
|
|
2080
|
+
fn e51_restart_ignores_stopped_peer_pane_residue() {
|
|
2081
|
+
let socket = "/private/tmp/tmux-501/ta-worker";
|
|
2082
|
+
let state = serde_json::json!({
|
|
2083
|
+
"tmux_endpoint": socket,
|
|
2084
|
+
"agents": {
|
|
2085
|
+
"worker": {"pane_id": "%1", "status": "running"},
|
|
2086
|
+
"peer": {"pane_id": "%0", "status": "stopped"}
|
|
2087
|
+
}
|
|
2088
|
+
});
|
|
2089
|
+
let agent_id = AgentId::new("worker");
|
|
2090
|
+
let agent = state["agents"]["worker"].clone();
|
|
2091
|
+
|
|
2092
|
+
assert!(
|
|
2093
|
+
!pane_conflicts_with_pane_id(&state, &agent_id, &agent, "%0"),
|
|
2094
|
+
"stopped peer residue must not claim a pane for a live restart"
|
|
2095
|
+
);
|
|
2096
|
+
}
|
|
2097
|
+
|
|
2044
2098
|
// 0.5.66 bypass 单源 §4.1:Noop drift 检测——声明面与运行面不一致 → drift。
|
|
2045
2099
|
#[test]
|
|
2046
2100
|
fn test_restart_noop_denied_on_drift() {
|
|
@@ -2047,38 +2047,6 @@ pub(super) fn window_exists(
|
|
|
2047
2047
|
}
|
|
2048
2048
|
}
|
|
2049
2049
|
|
|
2050
|
-
/// ---
|
|
2051
|
-
/// purpose: 在 state 里把该席位的显示标记为已关
|
|
2052
|
-
/// params:
|
|
2053
|
-
/// state: 就地改写;只有 ghostty 工作区后端会被改写状态与标题,其余后端不动
|
|
2054
|
-
/// ---
|
|
2055
|
-
pub(super) fn close_agent_display(state: &mut serde_json::Value, agent_id: &AgentId) {
|
|
2056
|
-
let Some(display) = state
|
|
2057
|
-
.get_mut("agents")
|
|
2058
|
-
.and_then(|v| v.as_object_mut())
|
|
2059
|
-
.and_then(|agents| agents.get_mut(agent_id.as_str()))
|
|
2060
|
-
.and_then(|agent| agent.get_mut("display"))
|
|
2061
|
-
.and_then(|display| display.as_object_mut())
|
|
2062
|
-
else {
|
|
2063
|
-
return;
|
|
2064
|
-
};
|
|
2065
|
-
let backend = display
|
|
2066
|
-
.get("backend")
|
|
2067
|
-
.and_then(|v| v.as_str())
|
|
2068
|
-
.unwrap_or("")
|
|
2069
|
-
.to_string();
|
|
2070
|
-
// golden operations.py:88-92: close_ghostty_display (display/close.py:17-48) mutates NOTHING in the
|
|
2071
|
-
// persisted state for a ghostty_window; only the ghostty_workspace slot is relabeled
|
|
2072
|
-
// (close.py:84-85: status="stopped", pane_title=f"stopped: {agent_id}") and re-assigned back.
|
|
2073
|
-
if backend == "ghostty_workspace" {
|
|
2074
|
-
display.insert("status".to_string(), serde_json::json!("stopped"));
|
|
2075
|
-
display.insert(
|
|
2076
|
-
"pane_title".to_string(),
|
|
2077
|
-
serde_json::json!(format!("stopped: {}", agent_id.as_str())),
|
|
2078
|
-
);
|
|
2079
|
-
}
|
|
2080
|
-
}
|
|
2081
|
-
|
|
2082
2050
|
/// ---
|
|
2083
2051
|
/// purpose: 丢弃该席位的会话捕获字段并标记为已停
|
|
2084
2052
|
/// params:
|
|
@@ -14,7 +14,6 @@
|
|
|
14
14
|
//! what: 按 team 参数或唯一性选出重启目标
|
|
15
15
|
//! depends:
|
|
16
16
|
//! - crate::lifecycle::lock
|
|
17
|
-
//! - crate::lifecycle::display
|
|
18
17
|
//! - crate::lifecycle::restart::selection
|
|
19
18
|
//! - crate::lifecycle::restart::preflight
|
|
20
19
|
//! - crate::lifecycle::restart::remove
|
|
@@ -1367,14 +1366,6 @@ fn restart_with_selected_team_and_transport(
|
|
|
1367
1366
|
Some(selected.team_key.as_str()),
|
|
1368
1367
|
&state,
|
|
1369
1368
|
);
|
|
1370
|
-
if let Ok(probe) = crate::lifecycle::display::probe_display_capabilities(&selected.run_workspace)
|
|
1371
|
-
{
|
|
1372
|
-
let _ = crate::lifecycle::display::rebuild_adaptive_display_after_rebind(
|
|
1373
|
-
&selected.run_workspace,
|
|
1374
|
-
&session_name,
|
|
1375
|
-
&probe,
|
|
1376
|
-
);
|
|
1377
|
-
}
|
|
1378
1369
|
// 0.3.28 Step 1: topology invariant guard (warn-only). Same pattern as
|
|
1379
1370
|
// `lifecycle::launch::launch_with_transport_in_workspace` — logs to stderr,
|
|
1380
1371
|
// never panics. Hard error path is deferred to Step 10.
|
|
@@ -184,7 +184,7 @@ fn write_team_docs(workspace: &Path) {
|
|
|
184
184
|
std::fs::write(
|
|
185
185
|
workspace.join("TEAM.md"),
|
|
186
186
|
format!(
|
|
187
|
-
"---\nname: {TEAM_NAME}\nobjective: clone-agent tools preservation\nprovider: fake\
|
|
187
|
+
"---\nname: {TEAM_NAME}\nobjective: clone-agent tools preservation\nprovider: fake\n---\n"
|
|
188
188
|
),
|
|
189
189
|
)
|
|
190
190
|
.expect("TEAM.md");
|
|
@@ -324,7 +324,6 @@ fn clone_ok(case: &Case, source: &str, dest: &str, phase: &str) {
|
|
|
324
324
|
case.ws(),
|
|
325
325
|
"--team",
|
|
326
326
|
TEAM_NAME,
|
|
327
|
-
"--no-display",
|
|
328
327
|
"--json",
|
|
329
328
|
]);
|
|
330
329
|
let stderr = String::from_utf8_lossy(&out.stderr);
|