@team-agent/installer 0.3.14 → 0.3.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Cargo.lock +1 -1
- package/Cargo.toml +1 -1
- package/crates/team-agent/src/cli/adapters.rs +1 -0
- package/crates/team-agent/src/cli/emit.rs +92 -11
- package/crates/team-agent/src/cli/mod.rs +60 -10
- package/crates/team-agent/src/cli/send.rs +252 -0
- package/crates/team-agent/src/cli/tests/run_delegation.rs +15 -3
- package/crates/team-agent/src/cli/tests/shutdown_kill_plan.rs +159 -2
- package/crates/team-agent/src/cli/types.rs +30 -1
- package/crates/team-agent/src/compiler/tests.rs +2 -2
- package/crates/team-agent/src/compiler.rs +1 -1
- package/crates/team-agent/src/fake_worker.rs +32 -145
- package/crates/team-agent/src/lifecycle/display.rs +3 -3
- package/crates/team-agent/src/lifecycle/launch.rs +560 -69
- package/crates/team-agent/src/lifecycle/restart/agent.rs +137 -17
- package/crates/team-agent/src/lifecycle/restart/common.rs +38 -2
- package/crates/team-agent/src/lifecycle/restart/rebuild.rs +72 -1
- package/crates/team-agent/src/lifecycle/tests/core.rs +14 -5
- package/crates/team-agent/src/lifecycle/tests/launch_spawn.rs +668 -8
- package/crates/team-agent/src/lifecycle/types.rs +5 -0
- package/crates/team-agent/src/lifecycle/worker_command_context.rs +8 -0
- package/crates/team-agent/src/mcp_server/wire.rs +153 -3
- package/crates/team-agent/src/messaging/leader_receiver.rs +276 -43
- package/crates/team-agent/src/messaging/mod.rs +6 -3
- package/crates/team-agent/src/messaging/results.rs +240 -158
- package/crates/team-agent/src/messaging/send.rs +3 -2
- package/crates/team-agent/src/messaging/tests/e23.rs +35 -0
- package/crates/team-agent/src/messaging/tests/mod.rs +6 -2
- package/crates/team-agent/src/messaging/tests/runtime.rs +9 -9
- package/crates/team-agent/src/os_probe.rs +11 -0
- package/crates/team-agent/src/state/persist.rs +6 -0
- package/crates/team-agent/src/tmux_backend.rs +85 -0
- package/crates/team-agent/src/transport/test_support.rs +46 -1
- package/crates/team-agent/src/transport.rs +27 -0
- package/package.json +4 -4
- package/skills/team-agent/references/recovery-runbook.md +277 -0
|
@@ -86,7 +86,14 @@ pub(crate) fn start_agent_at_paths(
|
|
|
86
86
|
}
|
|
87
87
|
let session_name = state_session_name(&state);
|
|
88
88
|
let window = agent_window(&agent, agent_id);
|
|
89
|
-
|
|
89
|
+
let adaptive_layout =
|
|
90
|
+
open_display && crate::lifecycle::launch::state_uses_adaptive_layout(&state);
|
|
91
|
+
let agent_live = if adaptive_layout {
|
|
92
|
+
agent_pane_live(transport, &agent)
|
|
93
|
+
} else {
|
|
94
|
+
window_exists(transport, &session_name, &window)
|
|
95
|
+
};
|
|
96
|
+
if !force && agent_live {
|
|
90
97
|
mark_agent_running_noop(&mut state, agent_id, &session_name, &window)?;
|
|
91
98
|
crate::state::projection::save_team_scoped_state(workspace, &state)
|
|
92
99
|
.map_err(|e| LifecycleError::StatePersist(e.to_string()))?;
|
|
@@ -141,6 +148,28 @@ pub(crate) fn start_agent_at_paths(
|
|
|
141
148
|
let into_existing_session =
|
|
142
149
|
session_live_or_default(transport, &session_name, session_name_present(&state));
|
|
143
150
|
let safety = crate::lifecycle::launch::effective_runtime_config_for_worker_spawn()?;
|
|
151
|
+
let layout_placement = if adaptive_layout {
|
|
152
|
+
crate::lifecycle::launch::adaptive_existing_placement_for_agent(
|
|
153
|
+
&state,
|
|
154
|
+
transport,
|
|
155
|
+
&session_name,
|
|
156
|
+
agent_id,
|
|
157
|
+
)
|
|
158
|
+
.or_else(|| {
|
|
159
|
+
crate::lifecycle::launch::adaptive_placement_for_agent(
|
|
160
|
+
&state,
|
|
161
|
+
transport,
|
|
162
|
+
&session_name,
|
|
163
|
+
agent_id,
|
|
164
|
+
)
|
|
165
|
+
})
|
|
166
|
+
} else {
|
|
167
|
+
None
|
|
168
|
+
};
|
|
169
|
+
let spawn_window = layout_placement
|
|
170
|
+
.as_ref()
|
|
171
|
+
.map(|placement| placement.layout_window.as_str().to_string())
|
|
172
|
+
.unwrap_or_else(|| window.clone());
|
|
144
173
|
let spawn = spawn_agent_window(
|
|
145
174
|
workspace,
|
|
146
175
|
&session_name,
|
|
@@ -150,9 +179,10 @@ pub(crate) fn start_agent_at_paths(
|
|
|
150
179
|
into_existing_session,
|
|
151
180
|
transport,
|
|
152
181
|
Some(&safety),
|
|
182
|
+
layout_placement.as_ref(),
|
|
153
183
|
None,
|
|
154
184
|
)?;
|
|
155
|
-
mark_agent_started(&mut state, agent_id, &
|
|
185
|
+
mark_agent_started(&mut state, agent_id, &spawn_window, &spawn, &safety)?;
|
|
156
186
|
crate::state::projection::save_team_scoped_state(workspace, &state)
|
|
157
187
|
.map_err(|e| LifecycleError::StatePersist(e.to_string()))?;
|
|
158
188
|
write_start_agent_start_event(
|
|
@@ -162,9 +192,9 @@ pub(crate) fn start_agent_at_paths(
|
|
|
162
192
|
provider,
|
|
163
193
|
start_mode,
|
|
164
194
|
&session_name,
|
|
165
|
-
&
|
|
195
|
+
&spawn_window,
|
|
166
196
|
spawn_session_id,
|
|
167
|
-
into_existing_session,
|
|
197
|
+
tmux_start_mode_for_spawn(&spawn, into_existing_session),
|
|
168
198
|
)?;
|
|
169
199
|
let coordinator_started = start_coordinator_for_workspace(workspace)?;
|
|
170
200
|
Ok(StartAgentOutcome::Running {
|
|
@@ -180,6 +210,55 @@ pub(crate) fn start_agent_at_paths(
|
|
|
180
210
|
})
|
|
181
211
|
}
|
|
182
212
|
|
|
213
|
+
fn agent_pane_live(
|
|
214
|
+
transport: &dyn crate::transport::Transport,
|
|
215
|
+
agent: &serde_json::Value,
|
|
216
|
+
) -> bool {
|
|
217
|
+
let Some(pane) = agent
|
|
218
|
+
.get("pane_id")
|
|
219
|
+
.and_then(serde_json::Value::as_str)
|
|
220
|
+
.filter(|pane| !pane.is_empty())
|
|
221
|
+
.map(crate::transport::PaneId::new)
|
|
222
|
+
else {
|
|
223
|
+
return false;
|
|
224
|
+
};
|
|
225
|
+
agent_pane_live_by_id(transport, &pane)
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
fn agent_pane_live_by_id(
|
|
229
|
+
transport: &dyn crate::transport::Transport,
|
|
230
|
+
pane: &crate::transport::PaneId,
|
|
231
|
+
) -> bool {
|
|
232
|
+
match transport.has_pane(&pane) {
|
|
233
|
+
Ok(Some(live)) => live,
|
|
234
|
+
Ok(None) | Err(_) => !matches!(
|
|
235
|
+
transport.liveness(&pane),
|
|
236
|
+
Ok(crate::model::enums::PaneLiveness::Dead)
|
|
237
|
+
),
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
fn tmux_start_mode_for_spawn(
|
|
242
|
+
spawn: &SpawnedAgentWindow,
|
|
243
|
+
into_existing_session: bool,
|
|
244
|
+
) -> &'static str {
|
|
245
|
+
if let Some(placement) = spawn.layout_placement.as_ref() {
|
|
246
|
+
if placement.starts_window {
|
|
247
|
+
if into_existing_session {
|
|
248
|
+
"new-window"
|
|
249
|
+
} else {
|
|
250
|
+
"new-session"
|
|
251
|
+
}
|
|
252
|
+
} else {
|
|
253
|
+
"split-window"
|
|
254
|
+
}
|
|
255
|
+
} else if into_existing_session {
|
|
256
|
+
"new-window"
|
|
257
|
+
} else {
|
|
258
|
+
"new-session"
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
183
262
|
/// `stop_agent(workspace, agent_id, team)`(`lifecycle/operations.py:62`)。
|
|
184
263
|
/// owner-gate → kill window → **同时关显示** → 写 state。
|
|
185
264
|
pub fn stop_agent(
|
|
@@ -233,16 +312,34 @@ pub(super) fn stop_agent_at_paths(
|
|
|
233
312
|
.filter(|s| !s.is_empty())
|
|
234
313
|
.unwrap_or_else(|| agent_id.as_str())
|
|
235
314
|
.to_string();
|
|
236
|
-
let
|
|
237
|
-
|
|
315
|
+
let pane_id = state
|
|
316
|
+
.get("agents")
|
|
317
|
+
.and_then(|v| v.get(agent_id.as_str()))
|
|
318
|
+
.and_then(|v| v.get("pane_id"))
|
|
319
|
+
.and_then(|v| v.as_str())
|
|
320
|
+
.filter(|pane| !pane.is_empty())
|
|
321
|
+
.map(crate::transport::PaneId::new);
|
|
322
|
+
let target_str = pane_id
|
|
323
|
+
.as_ref()
|
|
324
|
+
.map(|pane| pane.as_str().to_string())
|
|
325
|
+
.unwrap_or_else(|| format!("{}:{window}", session_name.as_str()));
|
|
326
|
+
let stopped = pane_id
|
|
327
|
+
.as_ref()
|
|
328
|
+
.map(|pane| agent_pane_live_by_id(transport, pane))
|
|
329
|
+
.unwrap_or_else(|| window_exists(transport, &session_name, &window));
|
|
238
330
|
if stopped {
|
|
239
|
-
let target = Target::SessionWindow {
|
|
240
|
-
session: session_name.clone(),
|
|
241
|
-
window: WindowName::new(&window),
|
|
242
|
-
};
|
|
243
331
|
// golden operations.py:84-86: a non-zero kill-window raises
|
|
244
332
|
// RuntimeError(f"failed to stop agent {agent_id}: {proc.stderr.strip()}").
|
|
245
|
-
if let
|
|
333
|
+
let kill_result = if let Some(pane) = pane_id.as_ref() {
|
|
334
|
+
transport.kill_pane(pane)
|
|
335
|
+
} else {
|
|
336
|
+
let target = Target::SessionWindow {
|
|
337
|
+
session: session_name.clone(),
|
|
338
|
+
window: WindowName::new(&window),
|
|
339
|
+
};
|
|
340
|
+
transport.kill_window(&target)
|
|
341
|
+
};
|
|
342
|
+
if let Err(e) = kill_result {
|
|
246
343
|
let stderr = match &e {
|
|
247
344
|
crate::transport::TransportError::Subprocess { stderr, .. } => stderr.trim().to_string(),
|
|
248
345
|
other => other.to_string(),
|
|
@@ -335,6 +432,34 @@ fn mark_agent_started(
|
|
|
335
432
|
&spawn.profile_launch,
|
|
336
433
|
);
|
|
337
434
|
crate::lifecycle::launch::persist_effective_approval_policy(agent, safety);
|
|
435
|
+
if let Some(placement) = spawn.layout_placement.as_ref() {
|
|
436
|
+
agent.insert(
|
|
437
|
+
"layout_window".to_string(),
|
|
438
|
+
serde_json::json!(placement.layout_window.as_str()),
|
|
439
|
+
);
|
|
440
|
+
agent.insert(
|
|
441
|
+
"layout_index".to_string(),
|
|
442
|
+
serde_json::json!(placement.layout_index),
|
|
443
|
+
);
|
|
444
|
+
agent.insert("pane_index".to_string(), serde_json::json!(placement.pane_index));
|
|
445
|
+
agent.insert(
|
|
446
|
+
"display".to_string(),
|
|
447
|
+
serde_json::json!({
|
|
448
|
+
"backend": "adaptive",
|
|
449
|
+
"status": "opened",
|
|
450
|
+
"window": placement.layout_window.as_str(),
|
|
451
|
+
"workspace_window": null,
|
|
452
|
+
"pane_id": spawn.spawn.pane_id.as_str(),
|
|
453
|
+
"pane_title": agent_id.as_str(),
|
|
454
|
+
"target": spawn.spawn.pane_id.as_str(),
|
|
455
|
+
"target_worker_session": spawn.spawn.session.as_str(),
|
|
456
|
+
"linked_session": null,
|
|
457
|
+
"leader_session": spawn.spawn.session.as_str(),
|
|
458
|
+
"display_session": null,
|
|
459
|
+
"fallback": null,
|
|
460
|
+
}),
|
|
461
|
+
);
|
|
462
|
+
}
|
|
338
463
|
Ok(())
|
|
339
464
|
}
|
|
340
465
|
|
|
@@ -458,7 +583,7 @@ fn write_start_agent_start_event(
|
|
|
458
583
|
session_name: &SessionName,
|
|
459
584
|
window: &str,
|
|
460
585
|
session_id: Option<&SessionId>,
|
|
461
|
-
|
|
586
|
+
tmux_start_mode: &'static str,
|
|
462
587
|
) -> Result<(), LifecycleError> {
|
|
463
588
|
let auth_mode = agent_auth_mode(agent);
|
|
464
589
|
let model = agent.get("model").and_then(|v| v.as_str());
|
|
@@ -535,11 +660,6 @@ fn write_start_agent_start_event(
|
|
|
535
660
|
agent_id.as_str(),
|
|
536
661
|
team_id,
|
|
537
662
|
);
|
|
538
|
-
let tmux_start_mode = if into_existing_session {
|
|
539
|
-
"new-window"
|
|
540
|
-
} else {
|
|
541
|
-
"new-session"
|
|
542
|
-
};
|
|
543
663
|
crate::event_log::EventLog::new(workspace)
|
|
544
664
|
.write(
|
|
545
665
|
"start_agent.agent_start",
|
|
@@ -4,6 +4,7 @@ pub(super) struct SpawnedAgentWindow {
|
|
|
4
4
|
pub spawn: crate::transport::SpawnResult,
|
|
5
5
|
pub plan: crate::provider::CommandPlan,
|
|
6
6
|
pub profile_launch: crate::provider::ProviderProfileLaunch,
|
|
7
|
+
pub layout_placement: Option<crate::lifecycle::launch::LayoutPlacement>,
|
|
7
8
|
}
|
|
8
9
|
|
|
9
10
|
pub(super) fn spawn_agent_window(
|
|
@@ -15,6 +16,7 @@ pub(super) fn spawn_agent_window(
|
|
|
15
16
|
into_existing_session: bool,
|
|
16
17
|
transport: &dyn crate::transport::Transport,
|
|
17
18
|
safety: Option<&DangerousApproval>,
|
|
19
|
+
layout_placement: Option<&crate::lifecycle::launch::LayoutPlacement>,
|
|
18
20
|
spawn_cwd_override: Option<&Path>,
|
|
19
21
|
) -> Result<SpawnedAgentWindow, LifecycleError> {
|
|
20
22
|
let provider = agent_provider(agent);
|
|
@@ -114,7 +116,9 @@ pub(super) fn spawn_agent_window(
|
|
|
114
116
|
agent_id.as_str(),
|
|
115
117
|
team_id.as_deref(),
|
|
116
118
|
);
|
|
117
|
-
let window =
|
|
119
|
+
let window = layout_placement
|
|
120
|
+
.map(|placement| placement.layout_window.clone())
|
|
121
|
+
.unwrap_or_else(|| WindowName::new(agent_id.as_str()));
|
|
118
122
|
let mut env = crate::lifecycle::launch::inherited_env_with_team_overrides(
|
|
119
123
|
workspace,
|
|
120
124
|
agent_id.as_str(),
|
|
@@ -132,7 +136,38 @@ pub(super) fn spawn_agent_window(
|
|
|
132
136
|
})
|
|
133
137
|
.unwrap_or(workspace);
|
|
134
138
|
let env_unset: Vec<String> = profile_launch.env_unset.iter().cloned().collect();
|
|
135
|
-
let result = if
|
|
139
|
+
let result = if let Some(placement) = layout_placement {
|
|
140
|
+
if placement.starts_window {
|
|
141
|
+
if into_existing_session {
|
|
142
|
+
transport.spawn_into_with_env_unset(
|
|
143
|
+
session_name,
|
|
144
|
+
&window,
|
|
145
|
+
&plan.argv,
|
|
146
|
+
spawn_cwd,
|
|
147
|
+
&env,
|
|
148
|
+
&env_unset,
|
|
149
|
+
)
|
|
150
|
+
} else {
|
|
151
|
+
transport.spawn_first_with_env_unset(
|
|
152
|
+
session_name,
|
|
153
|
+
&window,
|
|
154
|
+
&plan.argv,
|
|
155
|
+
spawn_cwd,
|
|
156
|
+
&env,
|
|
157
|
+
&env_unset,
|
|
158
|
+
)
|
|
159
|
+
}
|
|
160
|
+
} else {
|
|
161
|
+
transport.spawn_split_with_env_unset(
|
|
162
|
+
session_name,
|
|
163
|
+
&window,
|
|
164
|
+
&plan.argv,
|
|
165
|
+
spawn_cwd,
|
|
166
|
+
&env,
|
|
167
|
+
&env_unset,
|
|
168
|
+
)
|
|
169
|
+
}
|
|
170
|
+
} else if into_existing_session {
|
|
136
171
|
transport.spawn_into_with_env_unset(
|
|
137
172
|
session_name,
|
|
138
173
|
&window,
|
|
@@ -162,6 +197,7 @@ pub(super) fn spawn_agent_window(
|
|
|
162
197
|
spawn,
|
|
163
198
|
plan,
|
|
164
199
|
profile_launch,
|
|
200
|
+
layout_placement: layout_placement.cloned(),
|
|
165
201
|
})
|
|
166
202
|
}
|
|
167
203
|
|
|
@@ -281,6 +281,12 @@ pub fn restart_with_transport_with_session_convergence_deadline(
|
|
|
281
281
|
if fatal_resume_failure {
|
|
282
282
|
continue;
|
|
283
283
|
}
|
|
284
|
+
let layout_placement = crate::lifecycle::launch::adaptive_existing_placement_for_agent(
|
|
285
|
+
&state,
|
|
286
|
+
transport,
|
|
287
|
+
&session_name,
|
|
288
|
+
&decision.agent_id,
|
|
289
|
+
);
|
|
284
290
|
let spawn = match spawn_agent_window(
|
|
285
291
|
&selected.run_workspace,
|
|
286
292
|
&session_name,
|
|
@@ -290,6 +296,7 @@ pub fn restart_with_transport_with_session_convergence_deadline(
|
|
|
290
296
|
session_live,
|
|
291
297
|
transport,
|
|
292
298
|
Some(&safety),
|
|
299
|
+
layout_placement.as_ref(),
|
|
293
300
|
Some(spec_workspace),
|
|
294
301
|
) {
|
|
295
302
|
Ok(spawn) => spawn,
|
|
@@ -312,7 +319,14 @@ pub fn restart_with_transport_with_session_convergence_deadline(
|
|
|
312
319
|
};
|
|
313
320
|
if let Err(error) = verify_spawned_agent_live(&decision.agent_id, &spawn, transport)
|
|
314
321
|
.and_then(|_| {
|
|
315
|
-
mark_agent_respawned(
|
|
322
|
+
mark_agent_respawned(
|
|
323
|
+
&mut state,
|
|
324
|
+
&decision.agent_id,
|
|
325
|
+
decision.restart_mode,
|
|
326
|
+
&spawn,
|
|
327
|
+
transport,
|
|
328
|
+
&safety,
|
|
329
|
+
)
|
|
316
330
|
})
|
|
317
331
|
{
|
|
318
332
|
let error = error.to_string();
|
|
@@ -851,6 +865,7 @@ fn mark_restart_targets_stopped_after_teardown(
|
|
|
851
865
|
fn mark_agent_respawned(
|
|
852
866
|
state: &mut serde_json::Value,
|
|
853
867
|
agent_id: &AgentId,
|
|
868
|
+
restart_mode: StartMode,
|
|
854
869
|
spawn: &SpawnedAgentWindow,
|
|
855
870
|
transport: &dyn crate::transport::Transport,
|
|
856
871
|
safety: &DangerousApproval,
|
|
@@ -867,6 +882,10 @@ fn mark_agent_respawned(
|
|
|
867
882
|
)));
|
|
868
883
|
};
|
|
869
884
|
agent.insert("status".to_string(), serde_json::json!("running"));
|
|
885
|
+
agent.insert(
|
|
886
|
+
"window".to_string(),
|
|
887
|
+
serde_json::json!(spawn.spawn.window.as_str()),
|
|
888
|
+
);
|
|
870
889
|
agent.insert(
|
|
871
890
|
"pane_id".to_string(),
|
|
872
891
|
serde_json::json!(spawn.spawn.pane_id.as_str()),
|
|
@@ -882,8 +901,60 @@ fn mark_agent_respawned(
|
|
|
882
901
|
if let Some(pane_pid) = pane_pid {
|
|
883
902
|
agent.insert("pane_pid".to_string(), serde_json::json!(pane_pid));
|
|
884
903
|
}
|
|
904
|
+
if matches!(
|
|
905
|
+
restart_mode,
|
|
906
|
+
StartMode::Fresh | StartMode::FreshAfterMissingRollout
|
|
907
|
+
) {
|
|
908
|
+
if let Some(session_id) = spawn.plan.expected_session_id.as_ref() {
|
|
909
|
+
agent.insert(
|
|
910
|
+
"session_id".to_string(),
|
|
911
|
+
serde_json::json!(session_id.as_str()),
|
|
912
|
+
);
|
|
913
|
+
}
|
|
914
|
+
}
|
|
885
915
|
crate::lifecycle::launch::persist_command_plan_state(agent, &spawn.plan, &spawn.profile_launch);
|
|
886
916
|
persist_effective_approval_policy_for_restart(agent, safety);
|
|
917
|
+
if let Some(placement) = spawn.layout_placement.as_ref() {
|
|
918
|
+
agent.insert(
|
|
919
|
+
"layout_window".to_string(),
|
|
920
|
+
serde_json::json!(placement.layout_window.as_str()),
|
|
921
|
+
);
|
|
922
|
+
agent.insert(
|
|
923
|
+
"layout_index".to_string(),
|
|
924
|
+
serde_json::json!(placement.layout_index),
|
|
925
|
+
);
|
|
926
|
+
agent.insert("pane_index".to_string(), serde_json::json!(placement.pane_index));
|
|
927
|
+
agent.insert(
|
|
928
|
+
"display".to_string(),
|
|
929
|
+
serde_json::json!({
|
|
930
|
+
"backend": "adaptive",
|
|
931
|
+
"status": "opened",
|
|
932
|
+
"window": placement.layout_window.as_str(),
|
|
933
|
+
"workspace_window": null,
|
|
934
|
+
"pane_id": spawn.spawn.pane_id.as_str(),
|
|
935
|
+
"pane_title": agent_id.as_str(),
|
|
936
|
+
"target": spawn.spawn.pane_id.as_str(),
|
|
937
|
+
"target_worker_session": spawn.spawn.session.as_str(),
|
|
938
|
+
"linked_session": null,
|
|
939
|
+
"leader_session": spawn.spawn.session.as_str(),
|
|
940
|
+
"display_session": null,
|
|
941
|
+
"fallback": null,
|
|
942
|
+
}),
|
|
943
|
+
);
|
|
944
|
+
}
|
|
945
|
+
if matches!(
|
|
946
|
+
restart_mode,
|
|
947
|
+
StartMode::Fresh | StartMode::FreshAfterMissingRollout
|
|
948
|
+
) && spawn.plan.expected_session_id.is_some()
|
|
949
|
+
{
|
|
950
|
+
agent.insert("rollout_path".to_string(), serde_json::Value::Null);
|
|
951
|
+
agent.insert("captured_at".to_string(), serde_json::Value::Null);
|
|
952
|
+
agent.insert("captured_via".to_string(), serde_json::Value::Null);
|
|
953
|
+
agent.insert(
|
|
954
|
+
"attribution_confidence".to_string(),
|
|
955
|
+
serde_json::Value::Null,
|
|
956
|
+
);
|
|
957
|
+
}
|
|
887
958
|
agent.remove("startup_prompts");
|
|
888
959
|
agent.remove("startup_prompt_status");
|
|
889
960
|
Ok(())
|
|
@@ -213,14 +213,21 @@ fn plan_condition_rejects_out_of_grammar() {
|
|
|
213
213
|
|
|
214
214
|
// ───────────────────────────────────────────────────────────────────────
|
|
215
215
|
// resolve_display_backend — display/backend.py
|
|
216
|
-
// 默认 none
|
|
216
|
+
// 默认 adaptive;显式 none 是逃生口。
|
|
217
217
|
// ───────────────────────────────────────────────────────────────────────
|
|
218
218
|
|
|
219
219
|
#[test]
|
|
220
|
-
fn
|
|
220
|
+
fn resolve_backend_defaults_to_adaptive_when_none_requested() {
|
|
221
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);
|
|
222
229
|
assert_eq!(r.backend, DisplayBackend::None);
|
|
223
|
-
assert!(
|
|
230
|
+
assert!(r.non_default, "显式 none 是偏离默认 adaptive 的逃生口");
|
|
224
231
|
}
|
|
225
232
|
|
|
226
233
|
#[test]
|
|
@@ -732,8 +739,10 @@ fn remove_agent_without_from_spec_is_refused_confirm() {
|
|
|
732
739
|
Ok(RemoveAgentOutcome::RefusedFromSpecConfirm { agent_id }) => {
|
|
733
740
|
assert_eq!(agent_id, aid("w1"));
|
|
734
741
|
}
|
|
735
|
-
// 若先撞 owner / 缺 state 门也可接受(门在 confirm 之前)。
|
|
736
|
-
Err(LifecycleError::OwnerRefused(_))
|
|
742
|
+
// 若先撞 owner / 缺 state / runtime-missing 门也可接受(门在 confirm 之前)。
|
|
743
|
+
Err(LifecycleError::OwnerRefused(_))
|
|
744
|
+
| Err(LifecycleError::RequirementUnmet(_))
|
|
745
|
+
| Err(LifecycleError::TeamSelect(_)) => {}
|
|
737
746
|
other => panic!("from_spec=false 应 RefusedFromSpecConfirm 或更上游门:{other:?}"),
|
|
738
747
|
}
|
|
739
748
|
}
|