@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
|
@@ -211,159 +211,6 @@ fn plan_condition_rejects_out_of_grammar() {
|
|
|
211
211
|
}
|
|
212
212
|
}
|
|
213
213
|
|
|
214
|
-
// ───────────────────────────────────────────────────────────────────────
|
|
215
|
-
// resolve_display_backend — display/backend.py
|
|
216
|
-
// 默认 adaptive;显式 none 是逃生口。
|
|
217
|
-
// ───────────────────────────────────────────────────────────────────────
|
|
218
|
-
|
|
219
|
-
#[test]
|
|
220
|
-
fn resolve_backend_defaults_to_adaptive_when_none_requested() {
|
|
221
|
-
let r = resolve_display_backend(None, None);
|
|
222
|
-
assert_eq!(r.backend, DisplayBackend::Adaptive);
|
|
223
|
-
assert!(!r.non_default, "默认 adaptive 不应标记 non_default");
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
#[test]
|
|
227
|
-
fn resolve_backend_explicit_none_is_escape_hatch() {
|
|
228
|
-
let r = resolve_display_backend(Some(DisplayBackend::None), None);
|
|
229
|
-
assert_eq!(r.backend, DisplayBackend::None);
|
|
230
|
-
assert!(r.non_default, "显式 none 是偏离默认 adaptive 的逃生口");
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
#[test]
|
|
234
|
-
fn resolve_backend_requested_overrides_and_marks_non_default() {
|
|
235
|
-
// 显式 requested 非 adaptive → 采用且 non_default=true(非静默发事件)。
|
|
236
|
-
let r = resolve_display_backend(Some(DisplayBackend::GhosttyWindow), None);
|
|
237
|
-
assert_eq!(r.backend, DisplayBackend::GhosttyWindow);
|
|
238
|
-
assert!(r.non_default);
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
#[test]
|
|
242
|
-
fn resolve_backend_recorded_used_when_no_request() {
|
|
243
|
-
// 无 requested 但 state 有 recorded → 复用 recorded(restart 一致性)。
|
|
244
|
-
let r = resolve_display_backend(None, Some(DisplayBackend::GhosttyWorkspace));
|
|
245
|
-
assert_eq!(r.backend, DisplayBackend::GhosttyWorkspace);
|
|
246
|
-
assert!(r.non_default);
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
// ───────────────────────────────────────────────────────────────────────
|
|
250
|
-
// probe_display_capabilities — display/adaptive.py:31 (C13)
|
|
251
|
-
// 分支只看 probe 结果,NOT cfg!(target_os)。golden 实跑:
|
|
252
|
-
// linux no-tmux : in_tmux=false, adaptive_status=leader_not_in_tmux, reason=leader_not_in_tmux
|
|
253
|
-
// linux in-tmux : in_tmux=true, adaptive_status=available(opened), reason=None, caps both true
|
|
254
|
-
// windows/wsl : in_tmux=false, adaptive_status=not_implemented_this_platform, caps both false
|
|
255
|
-
// blocked 是 typed outcome(DisplayStatus::Blocked + reason),NOT error。
|
|
256
|
-
// 注:RUST 入口签名为 probe_display_capabilities(workspace) —— 它内部读环境/平台;
|
|
257
|
-
// 本测试在干净 CI workspace 上跑,只断言"不是 Err(平台降级是 typed outcome)"
|
|
258
|
-
// 以及 reason 必属封闭集(若有)。
|
|
259
|
-
// ───────────────────────────────────────────────────────────────────────
|
|
260
|
-
|
|
261
|
-
#[test]
|
|
262
|
-
fn probe_never_errors_platform_degradation_is_typed_outcome() {
|
|
263
|
-
// C13:能力性降级绝不走 LifecycleError —— 它是 DisplayStatus::Blocked + reason。
|
|
264
|
-
let ws = temp_ws();
|
|
265
|
-
let probe =
|
|
266
|
-
probe_display_capabilities(&ws).expect("probe 平台降级必须是 typed outcome,不是 Err");
|
|
267
|
-
// 若 blocked,reason 必属 AdaptiveBlockReason 封闭集且与 status 一致。
|
|
268
|
-
match probe.adaptive_status {
|
|
269
|
-
DisplayStatus::Blocked => {
|
|
270
|
-
assert!(probe.reason.is_some(), "blocked 必带 reason(C16 封闭集)")
|
|
271
|
-
}
|
|
272
|
-
DisplayStatus::Opened => assert!(probe.reason.is_none(), "opened 不应带 block reason"),
|
|
273
|
-
DisplayStatus::Stopped => {}
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
#[test]
|
|
278
|
-
fn probe_caps_consistent_with_in_tmux() {
|
|
279
|
-
// caps.adaptive_display == (in_tmux && 平台支持);不在 tmux → caps 全 false。
|
|
280
|
-
let ws = temp_ws();
|
|
281
|
-
let probe = probe_display_capabilities(&ws).expect("probe 不应 Err");
|
|
282
|
-
if !probe.in_tmux {
|
|
283
|
-
assert!(!probe.caps.adaptive_display);
|
|
284
|
-
assert!(!probe.caps.tmux_append_windows);
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
// ───────────────────────────────────────────────────────────────────────
|
|
289
|
-
// AdaptiveBlockReason 封闭集 — ADAPTIVE_BLOCK_REASONS (adaptive.py:21)
|
|
290
|
-
// golden 实跑(6 个,无更多无更少):
|
|
291
|
-
// aggregator_rebuild_failed, leader_not_in_tmux, not_implemented_this_platform,
|
|
292
|
-
// split_failed, window_create_failed, worker_session_missing
|
|
293
|
-
// serde rename = snake_case,JSON 名与 Python 字符串一致。
|
|
294
|
-
// ───────────────────────────────────────────────────────────────────────
|
|
295
|
-
|
|
296
|
-
/// 封闭集 ALL — 一处穷举所有 AdaptiveBlockReason 变体(新增第 7 个变体会编译错此 match,
|
|
297
|
-
/// 把 cardinality 锁成编译期事实)。golden ADAPTIVE_BLOCK_REASONS 恰 6 个(adaptive.py:21)。
|
|
298
|
-
const ALL_BLOCK_REASONS: [AdaptiveBlockReason; 6] = [
|
|
299
|
-
AdaptiveBlockReason::LeaderNotInTmux,
|
|
300
|
-
AdaptiveBlockReason::SplitFailed,
|
|
301
|
-
AdaptiveBlockReason::WindowCreateFailed,
|
|
302
|
-
AdaptiveBlockReason::WorkerSessionMissing,
|
|
303
|
-
AdaptiveBlockReason::NotImplementedThisPlatform,
|
|
304
|
-
AdaptiveBlockReason::AggregatorRebuildFailed,
|
|
305
|
-
];
|
|
306
|
-
|
|
307
|
-
#[test]
|
|
308
|
-
fn adaptive_block_reason_serde_names_match_python_and_set_is_exactly_six() {
|
|
309
|
-
// (a) 每个变体 wire-name 与 Python 字符串一致。
|
|
310
|
-
let expected = [
|
|
311
|
-
"\"leader_not_in_tmux\"",
|
|
312
|
-
"\"split_failed\"",
|
|
313
|
-
"\"window_create_failed\"",
|
|
314
|
-
"\"worker_session_missing\"",
|
|
315
|
-
"\"not_implemented_this_platform\"",
|
|
316
|
-
"\"aggregator_rebuild_failed\"",
|
|
317
|
-
];
|
|
318
|
-
for (variant, want) in ALL_BLOCK_REASONS.iter().zip(expected.iter()) {
|
|
319
|
-
assert_eq!(&serde_json::to_string(variant).unwrap(), want);
|
|
320
|
-
}
|
|
321
|
-
// (b) 封闭集 CARDINALITY == 6(无多无少;rogue 第 7 变体使 ALL_BLOCK_REASONS match 编译失败)。
|
|
322
|
-
let mut names: Vec<String> = ALL_BLOCK_REASONS
|
|
323
|
-
.iter()
|
|
324
|
-
.map(|r| serde_json::to_string(r).unwrap())
|
|
325
|
-
.collect();
|
|
326
|
-
names.sort();
|
|
327
|
-
names.dedup();
|
|
328
|
-
assert_eq!(
|
|
329
|
-
names.len(),
|
|
330
|
-
6,
|
|
331
|
-
"ADAPTIVE_BLOCK_REASONS 恰 6 个,无重复无遗漏"
|
|
332
|
-
);
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
#[test]
|
|
336
|
-
fn adaptive_blocked_out_of_set_reason_bottoms_to_aggregator_rebuild_failed() {
|
|
337
|
-
// adaptive.py 兜底:越界 / aggregator 重建失败 → AggregatorRebuildFailed(documented overflow)。
|
|
338
|
-
// RED:经真实路径 —— probe 在 blocked 平台 open_worker_displays,worker display 必属封闭集;
|
|
339
|
-
// 这里用 probe.reason 越界场景驱动 open_worker_displays 而非静态枚举,捕获 reason 发射回归。
|
|
340
|
-
let ws = temp_ws();
|
|
341
|
-
let probe = DisplayProbe {
|
|
342
|
-
in_tmux: true,
|
|
343
|
-
platform: "linux".to_string(),
|
|
344
|
-
leader_session: Some(sess("leader")),
|
|
345
|
-
leader_pane: None,
|
|
346
|
-
caps: CapsFlags {
|
|
347
|
-
tmux_append_windows: true,
|
|
348
|
-
adaptive_display: true,
|
|
349
|
-
},
|
|
350
|
-
// 模拟 aggregator 重建失败封闭:open 必把 worker display 兜底成 AggregatorRebuildFailed。
|
|
351
|
-
adaptive_status: DisplayStatus::Blocked,
|
|
352
|
-
reason: Some(AdaptiveBlockReason::AggregatorRebuildFailed),
|
|
353
|
-
};
|
|
354
|
-
let rep = open_worker_displays(&ws, &sess("team-a"), DisplayBackend::Adaptive, &probe)
|
|
355
|
-
.expect("C14:显示失败不阻塞 readiness");
|
|
356
|
-
for (id, d) in rep.displays.iter() {
|
|
357
|
-
match d {
|
|
358
|
-
WorkerDisplay::Blocked { reason } => assert!(
|
|
359
|
-
ALL_BLOCK_REASONS.contains(reason),
|
|
360
|
-
"worker {id} 的 block reason 必属封闭集:{reason:?}"
|
|
361
|
-
),
|
|
362
|
-
other => panic!("blocked probe 下 worker {id} 应 Blocked:{other:?}"),
|
|
363
|
-
}
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
|
|
367
214
|
#[test]
|
|
368
215
|
fn start_mode_serde_names_match_python_start_mode_strings() {
|
|
369
216
|
// 低价值 wire-format 守卫:start_mode ∈ {"resumed","fresh","fresh_after_missing_rollout","noop"}。
|
|
@@ -1300,66 +1147,6 @@ impl Drop for EnvVarGuard {
|
|
|
1300
1147
|
}
|
|
1301
1148
|
}
|
|
1302
1149
|
|
|
1303
|
-
// ───────────────────────────────────────────────────────────────────────
|
|
1304
|
-
// open_worker_displays — worker_window.py (C14)
|
|
1305
|
-
// 显示失败不阻塞 readiness:probe 为 blocked 平台时,returns typed Blocked displays,
|
|
1306
|
-
// 绝不 Err。这里用 backend=None(无 worker views)验证至少不 panic 成 Err。
|
|
1307
|
-
// ───────────────────────────────────────────────────────────────────────
|
|
1308
|
-
|
|
1309
|
-
#[test]
|
|
1310
|
-
fn open_worker_displays_blocked_probe_yields_typed_blocked_not_error() {
|
|
1311
|
-
let ws = temp_ws();
|
|
1312
|
-
let probe = DisplayProbe {
|
|
1313
|
-
in_tmux: false,
|
|
1314
|
-
platform: "windows".to_string(),
|
|
1315
|
-
leader_session: None,
|
|
1316
|
-
leader_pane: None,
|
|
1317
|
-
caps: CapsFlags {
|
|
1318
|
-
tmux_append_windows: false,
|
|
1319
|
-
adaptive_display: false,
|
|
1320
|
-
},
|
|
1321
|
-
adaptive_status: DisplayStatus::Blocked,
|
|
1322
|
-
reason: Some(AdaptiveBlockReason::NotImplementedThisPlatform),
|
|
1323
|
-
};
|
|
1324
|
-
let rep = open_worker_displays(&ws, &sess("team-a"), DisplayBackend::Adaptive, &probe)
|
|
1325
|
-
.expect("C14:显示失败不阻塞 readiness —— 不得 Err");
|
|
1326
|
-
// 每个 worker 的 display 在 blocked 平台上应是 Blocked 变体(若有 worker)。
|
|
1327
|
-
for (id, d) in rep.displays.iter() {
|
|
1328
|
-
assert!(
|
|
1329
|
-
matches!(
|
|
1330
|
-
d,
|
|
1331
|
-
WorkerDisplay::Blocked {
|
|
1332
|
-
reason: AdaptiveBlockReason::NotImplementedThisPlatform
|
|
1333
|
-
}
|
|
1334
|
-
),
|
|
1335
|
-
"worker {id} 在 windows 平台应 Blocked(not_implemented):{d:?}"
|
|
1336
|
-
);
|
|
1337
|
-
}
|
|
1338
|
-
}
|
|
1339
|
-
|
|
1340
|
-
// ───────────────────────────────────────────────────────────────────────
|
|
1341
|
-
// close_team_display_backends — display/close.py (C9 close-by-recorded-backend)
|
|
1342
|
-
// 空 workspace 无 recorded backend 无 session → 空 closed / 空 orphans(不 Err)。
|
|
1343
|
-
// adaptive 只删带 team-tag 的窗口(C2 leader pane 安全)。
|
|
1344
|
-
// ───────────────────────────────────────────────────────────────────────
|
|
1345
|
-
|
|
1346
|
-
#[test]
|
|
1347
|
-
fn close_team_display_empty_workspace_closes_nothing_not_error() {
|
|
1348
|
-
let ws = temp_ws();
|
|
1349
|
-
let rep = close_team_display_backends(&ws, &sess("team-a"))
|
|
1350
|
-
.expect("C9:无 recorded backend 应 Ok(空报告),不是 Err");
|
|
1351
|
-
assert!(
|
|
1352
|
-
rep.closed.is_empty(),
|
|
1353
|
-
"无 recorded backend 不应关任何东西:{:?}",
|
|
1354
|
-
rep.closed
|
|
1355
|
-
);
|
|
1356
|
-
assert!(
|
|
1357
|
-
rep.orphans_cleaned.is_empty(),
|
|
1358
|
-
"空 workspace 无 orphan:{:?}",
|
|
1359
|
-
rep.orphans_cleaned
|
|
1360
|
-
);
|
|
1361
|
-
}
|
|
1362
|
-
|
|
1363
1150
|
// ───────────────────────────────────────────────────────────────────────
|
|
1364
1151
|
// fork_agent — operations.py:284 (native session fork eligibility)
|
|
1365
1152
|
// 资格门(adapter.supports_session_fork = auth_mode != "compatible_api"):
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
//! Issue #238 lifecycle-side contracts for persisted/recovered effort and Pi
|
|
2
|
+
//! leader argument admission. This module deliberately calls the same
|
|
3
|
+
//! lifecycle helpers used by spawn/restart instead of duplicating resolution.
|
|
4
|
+
|
|
5
|
+
#![allow(clippy::expect_used, clippy::panic, clippy::unwrap_used)]
|
|
6
|
+
|
|
7
|
+
use crate::lifecycle::launch::{
|
|
8
|
+
provider_effort_for_spawn, provider_effort_for_spawn_json, provider_effort_from_raw,
|
|
9
|
+
};
|
|
10
|
+
use crate::lifecycle::launch::pi_mcp::parse_pi_leader_args;
|
|
11
|
+
use crate::model::enums::{Provider, ProviderEffort};
|
|
12
|
+
use crate::model::yaml::Value as Yaml;
|
|
13
|
+
use serde_json::json;
|
|
14
|
+
|
|
15
|
+
fn args(items: &[&str]) -> Vec<String> {
|
|
16
|
+
items.iter().map(|item| (*item).to_string()).collect()
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
#[test]
|
|
20
|
+
fn r6_spawn_and_persisted_recovery_preserve_codex_effort_literals() {
|
|
21
|
+
for raw in ["max", "ultra"] {
|
|
22
|
+
let yaml_agent = Yaml::Map(vec![(
|
|
23
|
+
"effort".to_string(),
|
|
24
|
+
Yaml::Str(raw.to_string()),
|
|
25
|
+
)]);
|
|
26
|
+
let json_agent = json!({"effort": raw});
|
|
27
|
+
|
|
28
|
+
let direct = provider_effort_from_raw(Some(raw), Provider::Codex)
|
|
29
|
+
.expect("direct lifecycle resolution")
|
|
30
|
+
.expect("Codex effort");
|
|
31
|
+
let spawned = provider_effort_for_spawn(&yaml_agent, Provider::Codex)
|
|
32
|
+
.expect("YAML spawn resolution")
|
|
33
|
+
.expect("spawned Codex effort");
|
|
34
|
+
let recovered = provider_effort_for_spawn_json(&json_agent, Provider::Codex)
|
|
35
|
+
.expect("JSON recovery resolution")
|
|
36
|
+
.expect("recovered Codex effort");
|
|
37
|
+
assert_eq!(direct.as_str(), raw);
|
|
38
|
+
assert_eq!(spawned.as_str(), raw);
|
|
39
|
+
assert_eq!(recovered.as_str(), raw);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
#[test]
|
|
44
|
+
fn r6_unsupported_ultra_is_not_silently_dropped_on_legacy_provider() {
|
|
45
|
+
let error = provider_effort_from_raw(Some("ultra"), Provider::Fake)
|
|
46
|
+
.expect_err("ultra must not be silently ignored")
|
|
47
|
+
.to_string();
|
|
48
|
+
assert!(error.contains("effort 'ultra' is only supported by codex"), "{error}");
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
#[test]
|
|
52
|
+
fn r7_pi_leader_parser_resolves_thinking_by_provider_semantics() {
|
|
53
|
+
let parsed = parse_pi_leader_args(&args(&[
|
|
54
|
+
"--",
|
|
55
|
+
"--model",
|
|
56
|
+
"openai-codex/gpt-5.6-luna",
|
|
57
|
+
"--thinking",
|
|
58
|
+
"max",
|
|
59
|
+
]))
|
|
60
|
+
.expect("Pi max arguments");
|
|
61
|
+
assert_eq!(parsed.model.as_deref(), Some("openai-codex/gpt-5.6-luna"));
|
|
62
|
+
assert_eq!(parsed.effort, Some(ProviderEffort::Max));
|
|
63
|
+
|
|
64
|
+
let error = parse_pi_leader_args(&args(&[
|
|
65
|
+
"--model",
|
|
66
|
+
"openai-codex/gpt-5.6-luna",
|
|
67
|
+
"--thinking",
|
|
68
|
+
"ultra",
|
|
69
|
+
]))
|
|
70
|
+
.expect_err("Pi ultra must be rejected by shared resolver")
|
|
71
|
+
.to_string();
|
|
72
|
+
assert!(error.contains("effort 'ultra' is only supported by codex"), "{error}");
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
#[test]
|
|
76
|
+
fn r7_pi_leader_parser_keeps_boundary_errors_fail_closed() {
|
|
77
|
+
for invalid in [
|
|
78
|
+
args(&["--thinking"]),
|
|
79
|
+
args(&["--unknown", "value"]),
|
|
80
|
+
args(&["--model", "codex"]),
|
|
81
|
+
args(&["--model", "openai-codex/gpt-5.6-luna", "--model", "other/x"]),
|
|
82
|
+
] {
|
|
83
|
+
assert!(parse_pi_leader_args(&invalid).is_err(), "must reject {invalid:?}");
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
let no_effort = parse_pi_leader_args(&args(&["--model", "openai-codex/gpt-5.6-luna"]))
|
|
87
|
+
.expect("Pi model-only arguments");
|
|
88
|
+
assert_eq!(no_effort.effort, None);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
#[test]
|
|
92
|
+
fn r7_pi_provider_authority_is_not_model_namespace_authority() {
|
|
93
|
+
let error = provider_effort_from_raw(Some("ultra"), Provider::Pi)
|
|
94
|
+
.expect_err("Pi ultra");
|
|
95
|
+
assert!(error.to_string().contains("effort 'ultra' is only supported by codex"));
|
|
96
|
+
}
|
|
@@ -1284,6 +1284,7 @@ fn lanea_stop_window_present_kills_and_stopped_true() {
|
|
|
1284
1284
|
// touches the display (mark_agent_stopped leaves it as-is) and hardcodes display_closed:false. RED: the
|
|
1285
1285
|
// persisted display.status/pane_title are the in-process observable.
|
|
1286
1286
|
#[test]
|
|
1287
|
+
#[ignore = "legacy external display contract removed; runtime is silent tmux only"]
|
|
1287
1288
|
fn lanea_stop_ghostty_workspace_relabels_slot_to_stopped() {
|
|
1288
1289
|
let ws = lanea_ws_agents(json!({
|
|
1289
1290
|
"alpha": { "status": "running", "provider": "codex", "window": "alpha",
|
|
@@ -1382,13 +1382,12 @@ fn quick_start_default_adaptive_groups_workers_into_layout_panes() {
|
|
|
1382
1382
|
let report = quick_start_with_transport(&team, None, true, None, &transport)
|
|
1383
1383
|
.expect("quick_start_with_transport must reach Ready");
|
|
1384
1384
|
|
|
1385
|
-
let (launch, attach_commands
|
|
1385
|
+
let (launch, attach_commands) = match report {
|
|
1386
1386
|
QuickStartReport::Ready {
|
|
1387
1387
|
launch,
|
|
1388
1388
|
attach_commands,
|
|
1389
|
-
display_backend,
|
|
1390
1389
|
..
|
|
1391
|
-
} => (*launch, attach_commands
|
|
1390
|
+
} => (*launch, attach_commands),
|
|
1392
1391
|
other => panic!("quick_start must reach Ready; got {other:?}"),
|
|
1393
1392
|
};
|
|
1394
1393
|
// 0.3.28 Step 4b: adaptive 3-pane tiling replaced with 1-window-per-agent
|
|
@@ -1424,7 +1423,6 @@ fn quick_start_default_adaptive_groups_workers_into_layout_panes() {
|
|
|
1424
1423
|
attach_commands.iter().any(|cmd| cmd.contains(":w1")),
|
|
1425
1424
|
"attach commands must include the per-agent windows: {attach_commands:?}"
|
|
1426
1425
|
);
|
|
1427
|
-
let _ = display_backend; // display_backend value preserved by upstream
|
|
1428
1426
|
}
|
|
1429
1427
|
|
|
1430
1428
|
#[test]
|
|
@@ -1602,50 +1600,6 @@ fn attach_window_names_for_state_agents_include_managed_leader_and_layout_window
|
|
|
1602
1600
|
assert_eq!(windows, vec!["leader", "team-w1", "team-w2"]);
|
|
1603
1601
|
}
|
|
1604
1602
|
|
|
1605
|
-
#[test]
|
|
1606
|
-
#[serial(env)]
|
|
1607
|
-
fn quick_start_no_display_keeps_one_window_per_agent() {
|
|
1608
|
-
let _hermetic = enter_hermetic("launch-spawn-no-display");
|
|
1609
|
-
let roles = ["w1", "w2"]
|
|
1610
|
-
.into_iter()
|
|
1611
|
-
.map(|id| (format!("{id}.md"), role_doc(id)))
|
|
1612
|
-
.collect::<Vec<_>>();
|
|
1613
|
-
let role_refs = roles
|
|
1614
|
-
.iter()
|
|
1615
|
-
.map(|(file, doc)| (file.as_str(), doc.as_str()))
|
|
1616
|
-
.collect::<Vec<_>>();
|
|
1617
|
-
let team = quick_start_team_dir_with_roles(&role_refs);
|
|
1618
|
-
let workspace = team.parent().expect("team_workspace(team_dir) = parent");
|
|
1619
|
-
seed_healthy_coordinator(workspace);
|
|
1620
|
-
let transport = OfflineTransport::new();
|
|
1621
|
-
|
|
1622
|
-
let report = quick_start_with_transport_in_workspace_with_display(
|
|
1623
|
-
workspace, &team, None, true, None, &transport, false,
|
|
1624
|
-
)
|
|
1625
|
-
.expect("quick_start_with_transport must reach Ready");
|
|
1626
|
-
|
|
1627
|
-
let display_backend = match report {
|
|
1628
|
-
QuickStartReport::Ready {
|
|
1629
|
-
display_backend, ..
|
|
1630
|
-
} => display_backend,
|
|
1631
|
-
other => panic!("quick_start must reach Ready; got {other:?}"),
|
|
1632
|
-
};
|
|
1633
|
-
assert_eq!(display_backend, "none");
|
|
1634
|
-
assert_eq!(
|
|
1635
|
-
transport
|
|
1636
|
-
.spawn_records()
|
|
1637
|
-
.iter()
|
|
1638
|
-
.map(|(kind, _)| kind.as_str())
|
|
1639
|
-
.collect::<Vec<_>>(),
|
|
1640
|
-
vec!["spawn_first", "spawn_into"],
|
|
1641
|
-
"--no-display must use the legacy one-worker-window spawn path"
|
|
1642
|
-
);
|
|
1643
|
-
let (_raw, state) = raw_runtime_state(workspace);
|
|
1644
|
-
assert_eq!(state["display_backend"], json!("none"));
|
|
1645
|
-
assert_eq!(state.pointer("/agents/w1/window"), Some(&json!("w1")));
|
|
1646
|
-
assert_eq!(state.pointer("/agents/w1/layout_window"), None);
|
|
1647
|
-
}
|
|
1648
|
-
|
|
1649
1603
|
// REAL-MACHINE residency boundary (acceptance framework): the PUBLIC quick_start (real TmuxBackend +
|
|
1650
1604
|
// real start_coordinator) on a fresh ws must leave a LIVE tmux session AND a ps-verifiable resident
|
|
1651
1605
|
// coordinator daemon (start_coordinator -> live pid). The framework verifies residency via ps; here we
|
|
@@ -2778,6 +2732,7 @@ fn fork_refuses_source_with_partial_tuple() {
|
|
|
2778
2732
|
}
|
|
2779
2733
|
|
|
2780
2734
|
#[test]
|
|
2735
|
+
#[ignore = "legacy adaptive display layout contract removed; runtime is silent tmux only"]
|
|
2781
2736
|
fn add_agent_adaptive_splits_last_non_full_layout_window() {
|
|
2782
2737
|
let roles = [("w1.md", role_doc("w1")), ("w2.md", role_doc("w2"))];
|
|
2783
2738
|
let role_refs = roles
|
|
@@ -2790,7 +2745,7 @@ fn add_agent_adaptive_splits_last_non_full_layout_window() {
|
|
|
2790
2745
|
&team,
|
|
2791
2746
|
&json!({
|
|
2792
2747
|
"session_name": session,
|
|
2793
|
-
"
|
|
2748
|
+
"legacy_display_key": "adaptive",
|
|
2794
2749
|
"active_team_key": "teamdir",
|
|
2795
2750
|
"agents": {
|
|
2796
2751
|
"w1": {"status": "running", "provider": "codex", "window": "team-w1", "layout_window": "team-w1", "layout_index": 0, "pane_index": 0, "pane_id": "%1"},
|
|
@@ -2835,6 +2790,7 @@ fn add_agent_adaptive_splits_last_non_full_layout_window() {
|
|
|
2835
2790
|
}
|
|
2836
2791
|
|
|
2837
2792
|
#[test]
|
|
2793
|
+
#[ignore = "legacy adaptive display layout contract removed; runtime is silent tmux only"]
|
|
2838
2794
|
fn add_agent_adaptive_creates_suffix_window_when_last_layout_full_and_name_collides() {
|
|
2839
2795
|
let roles = [
|
|
2840
2796
|
("w1.md", role_doc("w1")),
|
|
@@ -2851,7 +2807,7 @@ fn add_agent_adaptive_creates_suffix_window_when_last_layout_full_and_name_colli
|
|
|
2851
2807
|
&team,
|
|
2852
2808
|
&json!({
|
|
2853
2809
|
"session_name": session,
|
|
2854
|
-
"
|
|
2810
|
+
"legacy_display_key": "adaptive",
|
|
2855
2811
|
"active_team_key": "teamdir",
|
|
2856
2812
|
"agents": {
|
|
2857
2813
|
"w1": {"status": "running", "provider": "codex", "window": "team-w1", "layout_window": "team-w1", "layout_index": 0, "pane_index": 0, "pane_id": "%1"},
|
|
@@ -2918,7 +2874,7 @@ fn stop_agent_adaptive_kills_target_pane_not_shared_layout_window() {
|
|
|
2918
2874
|
&team,
|
|
2919
2875
|
&json!({
|
|
2920
2876
|
"session_name": session,
|
|
2921
|
-
"
|
|
2877
|
+
"legacy_display_key": "adaptive",
|
|
2922
2878
|
"agents": {
|
|
2923
2879
|
"w1": {"status": "running", "provider": "codex", "window": "team-w1", "layout_window": "team-w1", "layout_index": 0, "pane_index": 0, "pane_id": "%1"},
|
|
2924
2880
|
"w2": {"status": "running", "provider": "codex", "window": "team-w1", "layout_window": "team-w1", "layout_index": 0, "pane_index": 1, "pane_id": "%2"}
|
|
@@ -2954,13 +2910,14 @@ fn stop_agent_adaptive_kills_target_pane_not_shared_layout_window() {
|
|
|
2954
2910
|
}
|
|
2955
2911
|
|
|
2956
2912
|
#[test]
|
|
2913
|
+
#[ignore = "legacy adaptive display layout contract removed; runtime is silent tmux only"]
|
|
2957
2914
|
fn start_agent_adaptive_restarts_missing_pane_in_existing_layout_window() {
|
|
2958
2915
|
let ws = temp_ws();
|
|
2959
2916
|
crate::state::persist::save_runtime_state(
|
|
2960
2917
|
&ws,
|
|
2961
2918
|
&json!({
|
|
2962
2919
|
"session_name": "team-layout-restart",
|
|
2963
|
-
"
|
|
2920
|
+
"legacy_display_key": "adaptive",
|
|
2964
2921
|
"agents": {
|
|
2965
2922
|
"w1": {"status": "running", "provider": "codex", "role": "w1", "model": "gpt-5.5", "auth_mode": "subscription", "window": "team-w1", "layout_window": "team-w1", "layout_index": 0, "pane_index": 0, "pane_id": "%1"},
|
|
2966
2923
|
"w2": {"status": "running", "provider": "codex", "role": "w2", "model": "gpt-5.5", "auth_mode": "subscription", "window": "team-w1", "layout_window": "team-w1", "layout_index": 0, "pane_index": 1, "pane_id": "%2"}
|
|
@@ -3050,9 +3007,9 @@ fn quick_start_seeds_tasks_key_from_compiled_spec() {
|
|
|
3050
3007
|
}
|
|
3051
3008
|
|
|
3052
3009
|
// Stage A — golden launch/core.py:62-71 seeded top-level runtime state in insertion order:
|
|
3053
|
-
// spec_path, workspace, team_dir, team_key, session_name, leader, agents, tasks
|
|
3010
|
+
// spec_path, workspace, team_dir, team_key, session_name, leader, agents, tasks.
|
|
3054
3011
|
//
|
|
3055
|
-
//
|
|
3012
|
+
// The runtime state is tmux-only; no display backend is persisted.
|
|
3056
3013
|
// NEW (Bug 1/2 — team-in-team state scope, see tests/team_in_team_state_scope_red.rs):
|
|
3057
3014
|
// `active_team_key` and `teams` are appended at the tail so the runtime can carry the
|
|
3058
3015
|
// nested team-in-team scope alongside the original flat fields. Owner-binding fields
|
|
@@ -3065,11 +3022,10 @@ fn quick_start_seeds_tasks_key_from_compiled_spec() {
|
|
|
3065
3022
|
// R1: topology is explicit; fresh managed teams carry `is_external_leader=false`
|
|
3066
3023
|
// before the team-in-team suffix. Canonical `team_key` is seeded before owner
|
|
3067
3024
|
// binding; the remaining order stays the golden prefix + topology marker + new
|
|
3068
|
-
// suffix (active_team_key, teams).
|
|
3069
|
-
// (default adaptive), not the raw optional spec field.
|
|
3025
|
+
// suffix (active_team_key, teams).
|
|
3070
3026
|
#[test]
|
|
3071
3027
|
#[serial(env)]
|
|
3072
|
-
fn
|
|
3028
|
+
fn quick_start_state_seeds_spec_path_workspace_leader() {
|
|
3073
3029
|
let _hermetic = enter_hermetic("launch-spawn-state-seeds");
|
|
3074
3030
|
// Bug 2 owner team-scope: top-level owner triple is dropped when the seeded
|
|
3075
3031
|
// pane is empty; an ambient TMUX_PANE (tests run inside the dev's tmux session)
|
|
@@ -3106,7 +3062,6 @@ fn quick_start_state_seeds_spec_path_workspace_leader_display_backend() {
|
|
|
3106
3062
|
"leader",
|
|
3107
3063
|
"agents",
|
|
3108
3064
|
"tasks",
|
|
3109
|
-
"display_backend",
|
|
3110
3065
|
"is_external_leader",
|
|
3111
3066
|
// 0.5.x Phase 1d hot-path 接线 (裁决1 msg_76e1d98202b8):
|
|
3112
3067
|
// `transport = { kind, source }` inserted here by
|
|
@@ -3155,11 +3110,6 @@ fn quick_start_state_seeds_spec_path_workspace_leader_display_backend() {
|
|
|
3155
3110
|
"existing tasks value must remain seeded from compiled spec; got {:?}",
|
|
3156
3111
|
state["tasks"]
|
|
3157
3112
|
);
|
|
3158
|
-
assert_eq!(
|
|
3159
|
-
state["display_backend"],
|
|
3160
|
-
json!("adaptive"),
|
|
3161
|
-
"adaptive layout directive: resolve_display_backend(None, source='launch') defaults to adaptive"
|
|
3162
|
-
);
|
|
3163
3113
|
assert_eq!(state["is_external_leader"], json!(false));
|
|
3164
3114
|
}
|
|
3165
3115
|
|
|
@@ -3602,7 +3552,7 @@ fn add_agent_reachability_gate_passes_when_spawn_pane_addressable() {
|
|
|
3602
3552
|
// ═════════════════════════════════════════════════════════════════════════════
|
|
3603
3553
|
// E43 add-agent window layout drift (0.3.24 bug#3, demo-director startup blocker).
|
|
3604
3554
|
//
|
|
3605
|
-
// Root cause (architect res_f27ff9b29957): with `
|
|
3555
|
+
// Root cause (architect res_f27ff9b29957): with `legacy_display_key=adaptive` in
|
|
3606
3556
|
// state AND stale `agents.<id>.layout_window="team-w2"` residue, the existing
|
|
3607
3557
|
// `adaptive_placement_for_agent` validates a layout_window claim by checking
|
|
3608
3558
|
// whether the existing agent's `pane_id` is in `live_panes` — but it does NOT
|
|
@@ -3641,7 +3591,7 @@ fn e43_adaptive_placement_skips_existing_agent_whose_live_pane_window_name_diffe
|
|
|
3641
3591
|
use crate::transport::{SessionName, WindowName};
|
|
3642
3592
|
|
|
3643
3593
|
let state = json!({
|
|
3644
|
-
"
|
|
3594
|
+
"legacy_display_key": "adaptive",
|
|
3645
3595
|
"session_name": "team-iso",
|
|
3646
3596
|
"agents": {
|
|
3647
3597
|
"architect": {
|
|
@@ -3698,13 +3648,14 @@ fn e43_adaptive_placement_skips_existing_agent_whose_live_pane_window_name_diffe
|
|
|
3698
3648
|
/// behaviour (groups the new agent into that window). This is the canonical
|
|
3699
3649
|
/// adaptive 1-window-multiple-panes use case.
|
|
3700
3650
|
#[test]
|
|
3651
|
+
#[ignore = "legacy adaptive display layout contract removed; runtime is silent tmux only"]
|
|
3701
3652
|
fn e43_adaptive_placement_groups_into_layout_window_when_live_pane_matches_claim() {
|
|
3702
3653
|
use crate::lifecycle::launch::adaptive_placement_for_agent;
|
|
3703
3654
|
use crate::model::ids::AgentId;
|
|
3704
3655
|
use crate::transport::{SessionName, WindowName};
|
|
3705
3656
|
|
|
3706
3657
|
let state = json!({
|
|
3707
|
-
"
|
|
3658
|
+
"legacy_display_key": "adaptive",
|
|
3708
3659
|
"session_name": "team-iso",
|
|
3709
3660
|
"agents": {
|
|
3710
3661
|
"alpha": {
|
|
@@ -3750,13 +3701,14 @@ fn e43_adaptive_placement_groups_into_layout_window_when_live_pane_matches_claim
|
|
|
3750
3701
|
/// agent_id itself — so the subsequent spawn opens a new window, not a split
|
|
3751
3702
|
/// of a phantom.
|
|
3752
3703
|
#[test]
|
|
3704
|
+
#[ignore = "legacy adaptive display layout contract removed; runtime is silent tmux only"]
|
|
3753
3705
|
fn e43_adaptive_existing_placement_falls_back_to_agent_id_window_when_claim_missing_from_live() {
|
|
3754
3706
|
use crate::lifecycle::launch::adaptive_existing_placement_for_agent;
|
|
3755
3707
|
use crate::model::ids::AgentId;
|
|
3756
3708
|
use crate::transport::{SessionName, WindowName};
|
|
3757
3709
|
|
|
3758
3710
|
let state = json!({
|
|
3759
|
-
"
|
|
3711
|
+
"legacy_display_key": "adaptive",
|
|
3760
3712
|
"session_name": "team-iso",
|
|
3761
3713
|
"agents": {
|
|
3762
3714
|
"demo-director": {
|
|
@@ -3814,13 +3766,14 @@ fn e43_adaptive_existing_placement_falls_back_to_agent_id_window_when_claim_miss
|
|
|
3814
3766
|
/// (return a starts_window=false placement that splits into the existing
|
|
3815
3767
|
/// window).
|
|
3816
3768
|
#[test]
|
|
3769
|
+
#[ignore = "legacy adaptive display layout contract removed; runtime is silent tmux only"]
|
|
3817
3770
|
fn e43_adaptive_existing_placement_keeps_starts_window_false_when_claim_in_live_windows() {
|
|
3818
3771
|
use crate::lifecycle::launch::adaptive_existing_placement_for_agent;
|
|
3819
3772
|
use crate::model::ids::AgentId;
|
|
3820
3773
|
use crate::transport::{SessionName, WindowName};
|
|
3821
3774
|
|
|
3822
3775
|
let state = json!({
|
|
3823
|
-
"
|
|
3776
|
+
"legacy_display_key": "adaptive",
|
|
3824
3777
|
"session_name": "team-iso",
|
|
3825
3778
|
"agents": {
|
|
3826
3779
|
"alpha": {
|
|
@@ -3861,7 +3814,7 @@ fn e43_adaptive_existing_placement_keeps_starts_window_false_when_claim_in_live_
|
|
|
3861
3814
|
|
|
3862
3815
|
/// **RED — Fix C end-to-end**: `add_agent` end-to-end must NOT emit a
|
|
3863
3816
|
/// spawn_split with the stale window name. With state carrying
|
|
3864
|
-
/// `
|
|
3817
|
+
/// `legacy_display_key=adaptive` + stale `layout_window=team-w2` residue and
|
|
3865
3818
|
/// live tmux windows named per-agent, the spawn must be a `spawn_into`
|
|
3866
3819
|
/// (new window) — NOT `spawn_split` against the missing 'team-w2'.
|
|
3867
3820
|
#[test]
|
|
@@ -3885,7 +3838,7 @@ fn e43_add_agent_does_not_split_against_phantom_layout_window() {
|
|
|
3885
3838
|
&team,
|
|
3886
3839
|
&json!({
|
|
3887
3840
|
"session_name": "team-iso",
|
|
3888
|
-
"
|
|
3841
|
+
"legacy_display_key": "adaptive",
|
|
3889
3842
|
"agents": {
|
|
3890
3843
|
"architect": {
|
|
3891
3844
|
"status": "running",
|
|
@@ -3939,7 +3892,7 @@ fn e43_add_agent_does_not_split_against_phantom_layout_window() {
|
|
|
3939
3892
|
// E45 per-agent window drift (0.3.24 task#326 P0).
|
|
3940
3893
|
//
|
|
3941
3894
|
// Surfaced AFTER E43 (c5ed576) fixed the team-w2 phantom split. New repro:
|
|
3942
|
-
// state has
|
|
3895
|
+
// state has legacy_display_key=adaptive + an existing `developer` worker whose
|
|
3943
3896
|
// state row carries `layout_window=developer, layout_index=0, pane_id=%519`,
|
|
3944
3897
|
// live tmux shows `developer` window with that pane. add-agent demo-director
|
|
3945
3898
|
// resolves placement via `adaptive_placement_for_agent`, which finds the
|
|
@@ -3971,7 +3924,7 @@ fn e45_adaptive_placement_returns_none_when_no_real_team_w_window_present_in_sta
|
|
|
3971
3924
|
// literally `developer` (per-agent name, NOT team-wN), pane_id %519
|
|
3972
3925
|
// lives under the `developer` window.
|
|
3973
3926
|
let state = json!({
|
|
3974
|
-
"
|
|
3927
|
+
"legacy_display_key": "adaptive",
|
|
3975
3928
|
"session_name": "team-iso",
|
|
3976
3929
|
"agents": {
|
|
3977
3930
|
"developer": {
|
|
@@ -4014,7 +3967,7 @@ fn e45_adaptive_placement_returns_none_when_no_real_team_w_window_present_in_sta
|
|
|
4014
3967
|
"E45 (0.3.24 bug#4): placement must return None when the live \
|
|
4015
3968
|
session has NO real `team-w<N>` adaptive window. Per-agent windows \
|
|
4016
3969
|
like `developer` are NOT adaptive layout windows — they are \
|
|
4017
|
-
per-agent topology, even if
|
|
3970
|
+
per-agent topology, even if legacy_display_key=adaptive lingers in \
|
|
4018
3971
|
state. With no real adaptive layout, the caller falls back to \
|
|
4019
3972
|
opening a new per-agent window named after agent_id. Got \
|
|
4020
3973
|
{placement:?} — must be None to prevent split-window -t :developer \
|
|
@@ -4033,7 +3986,7 @@ fn e45_adaptive_existing_placement_returns_none_when_claim_is_per_agent_window()
|
|
|
4033
3986
|
use crate::transport::{SessionName, WindowName};
|
|
4034
3987
|
|
|
4035
3988
|
let state = json!({
|
|
4036
|
-
"
|
|
3989
|
+
"legacy_display_key": "adaptive",
|
|
4037
3990
|
"session_name": "team-iso",
|
|
4038
3991
|
"agents": {
|
|
4039
3992
|
"developer": {
|
|
@@ -4087,14 +4040,14 @@ fn e45_add_agent_opens_new_window_does_not_split_into_per_agent_window() {
|
|
|
4087
4040
|
let role_file = team.join("worker2-role.md");
|
|
4088
4041
|
std::fs::write(&role_file, DELEG_ROLE_WORKER2).unwrap();
|
|
4089
4042
|
|
|
4090
|
-
// Real-machine state:
|
|
4043
|
+
// Real-machine state: legacy_display_key=adaptive, but the existing
|
|
4091
4044
|
// `developer` worker carries per-agent layout_window=developer with
|
|
4092
4045
|
// layout_index=0. macmini-equivalent state shape.
|
|
4093
4046
|
crate::state::persist::save_runtime_state(
|
|
4094
4047
|
&team,
|
|
4095
4048
|
&json!({
|
|
4096
4049
|
"session_name": "team-iso",
|
|
4097
|
-
"
|
|
4050
|
+
"legacy_display_key": "adaptive",
|
|
4098
4051
|
"agents": {
|
|
4099
4052
|
"developer": {
|
|
4100
4053
|
"status": "running",
|
|
@@ -4159,13 +4112,14 @@ fn e45_add_agent_opens_new_window_does_not_split_into_per_agent_window() {
|
|
|
4159
4112
|
/// existing E43+adaptive grouping behaviour is preserved (split into team-w1
|
|
4160
4113
|
/// as before). E45 only changes behaviour for per-agent windows.
|
|
4161
4114
|
#[test]
|
|
4115
|
+
#[ignore = "legacy adaptive display layout contract removed; runtime is silent tmux only"]
|
|
4162
4116
|
fn e45_real_team_w_adaptive_layout_still_groups_into_layout_window() {
|
|
4163
4117
|
use crate::lifecycle::launch::adaptive_placement_for_agent;
|
|
4164
4118
|
use crate::model::ids::AgentId;
|
|
4165
4119
|
use crate::transport::{SessionName, WindowName};
|
|
4166
4120
|
|
|
4167
4121
|
let state = json!({
|
|
4168
|
-
"
|
|
4122
|
+
"legacy_display_key": "adaptive",
|
|
4169
4123
|
"session_name": "team-iso",
|
|
4170
4124
|
"agents": {
|
|
4171
4125
|
"alpha": {
|