@team-agent/installer 0.5.97 → 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 -64
- package/crates/team-agent/src/cli/diagnose.rs +0 -16
- package/crates/team-agent/src/cli/emit.rs +15 -42
- package/crates/team-agent/src/cli/mod.rs +130 -31
- package/crates/team-agent/src/cli/spec.rs +7 -10
- 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/mod.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 -14
- 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 +23 -38
- package/crates/team-agent/src/cli/tests/compile.rs +0 -192
- 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
|
@@ -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);
|
|
@@ -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",
|