@team-agent/installer 0.3.2 → 0.3.4
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 +34 -1
- package/Cargo.toml +1 -1
- package/crates/team-agent/Cargo.toml +1 -1
- package/crates/team-agent/src/cli/adapters.rs +196 -19
- package/crates/team-agent/src/cli/diagnose.rs +145 -11
- package/crates/team-agent/src/cli/emit.rs +287 -53
- package/crates/team-agent/src/cli/leader.rs +37 -8
- package/crates/team-agent/src/cli/mod.rs +807 -316
- package/crates/team-agent/src/cli/status_port.rs +25 -2
- package/crates/team-agent/src/cli/tests/divergence.rs +1 -2
- package/crates/team-agent/src/cli/tests/lane_c.rs +23 -13
- package/crates/team-agent/src/cli/tests/main_preserved.rs +2 -0
- package/crates/team-agent/src/cli/tests/run_delegation.rs +57 -3
- package/crates/team-agent/src/cli/types.rs +17 -0
- package/crates/team-agent/src/compiler/tests.rs +2 -2
- package/crates/team-agent/src/compiler.rs +16 -6
- package/crates/team-agent/src/coordinator/health.rs +89 -20
- package/crates/team-agent/src/coordinator/mod.rs +4 -0
- package/crates/team-agent/src/coordinator/runtime_detectors.rs +500 -0
- package/crates/team-agent/src/coordinator/runtime_observation.rs +58 -0
- package/crates/team-agent/src/coordinator/tests/watch.rs +4 -2
- package/crates/team-agent/src/coordinator/tick.rs +222 -69
- package/crates/team-agent/src/coordinator/types.rs +15 -3
- package/crates/team-agent/src/db/schema.rs +37 -2
- package/crates/team-agent/src/diagnose/comms.rs +226 -0
- package/crates/team-agent/src/diagnose/mod.rs +45 -0
- package/crates/team-agent/src/diagnose/orphans.rs +658 -0
- package/crates/team-agent/src/fake_worker.rs +146 -3
- package/crates/team-agent/src/leader/start.rs +121 -23
- package/crates/team-agent/src/leader/types.rs +44 -1
- package/crates/team-agent/src/lib.rs +3 -0
- package/crates/team-agent/src/lifecycle/display.rs +648 -50
- package/crates/team-agent/src/lifecycle/launch.rs +1048 -264
- package/crates/team-agent/src/lifecycle/mod.rs +3 -0
- package/crates/team-agent/src/lifecycle/profile_launch.rs +810 -0
- package/crates/team-agent/src/lifecycle/profile_smoke.rs +522 -0
- package/crates/team-agent/src/lifecycle/restart/agent.rs +113 -26
- package/crates/team-agent/src/lifecycle/restart/common.rs +189 -102
- package/crates/team-agent/src/lifecycle/restart/rebuild.rs +465 -25
- package/crates/team-agent/src/lifecycle/restart/remove.rs +22 -6
- package/crates/team-agent/src/lifecycle/restart/team_state.rs +19 -0
- package/crates/team-agent/src/lifecycle/restart.rs +4 -1
- package/crates/team-agent/src/lifecycle/tests/core.rs +4 -4
- package/crates/team-agent/src/lifecycle/tests/lane_ops.rs +5 -5
- package/crates/team-agent/src/lifecycle/tests/launch_spawn.rs +39 -9
- package/crates/team-agent/src/lifecycle/types.rs +23 -0
- package/crates/team-agent/src/lifecycle/worker_command_context.rs +326 -0
- package/crates/team-agent/src/mcp_server/helpers.rs +1 -0
- package/crates/team-agent/src/mcp_server/lifecycle_tools/agent_ops.rs +341 -0
- package/crates/team-agent/src/mcp_server/lifecycle_tools/mod.rs +10 -0
- package/crates/team-agent/src/mcp_server/lifecycle_tools/state_status.rs +158 -0
- package/crates/team-agent/src/mcp_server/mod.rs +3 -74
- package/crates/team-agent/src/mcp_server/tests/scoped.rs +1 -1
- package/crates/team-agent/src/mcp_server/tests/send.rs +6 -5
- package/crates/team-agent/src/mcp_server/tools.rs +312 -111
- package/crates/team-agent/src/mcp_server/types.rs +6 -4
- package/crates/team-agent/src/mcp_server/wire.rs +19 -7
- package/crates/team-agent/src/message_store.rs +21 -4
- package/crates/team-agent/src/messaging/delivery.rs +87 -37
- package/crates/team-agent/src/messaging/mod.rs +9 -6
- package/crates/team-agent/src/messaging/results.rs +153 -16
- package/crates/team-agent/src/messaging/selftest.rs +199 -12
- package/crates/team-agent/src/messaging/send.rs +35 -3
- package/crates/team-agent/src/messaging/tests/runtime.rs +19 -4
- package/crates/team-agent/src/messaging/types.rs +11 -3
- package/crates/team-agent/src/os_probe.rs +119 -0
- package/crates/team-agent/src/packaging/migrate.rs +10 -2
- package/crates/team-agent/src/packaging/tests.rs +23 -0
- package/crates/team-agent/src/provider/adapter.rs +483 -67
- package/crates/team-agent/src/provider/approvals/runtime_prompts.rs +1 -7
- package/crates/team-agent/src/provider/classify.rs +51 -4
- package/crates/team-agent/src/provider/startup_prompt.rs +94 -0
- package/crates/team-agent/src/provider/types.rs +47 -0
- package/crates/team-agent/src/session_capture.rs +616 -0
- package/crates/team-agent/src/state/persist.rs +57 -0
- package/crates/team-agent/src/state/projection.rs +32 -23
- package/crates/team-agent/src/state/selector.rs +5 -2
- package/crates/team-agent/src/tmux_backend.rs +151 -60
- package/crates/team-agent/src/transport/test_support.rs +9 -0
- package/crates/team-agent/src/transport/tests/wire.rs +4 -0
- package/crates/team-agent/src/transport.rs +13 -2
- package/package.json +4 -4
|
@@ -75,7 +75,8 @@ pub(crate) fn start_agent_at_paths(
|
|
|
75
75
|
let agent = state
|
|
76
76
|
.get("agents")
|
|
77
77
|
.and_then(|v| v.get(agent_id.as_str()))
|
|
78
|
-
.ok_or_else(|| LifecycleError::RequirementUnmet(format!("agent {agent_id} not found")))
|
|
78
|
+
.ok_or_else(|| LifecycleError::RequirementUnmet(format!("agent {agent_id} not found")))?
|
|
79
|
+
.clone();
|
|
79
80
|
if agent
|
|
80
81
|
.get("paused")
|
|
81
82
|
.and_then(serde_json::Value::as_bool)
|
|
@@ -84,7 +85,7 @@ pub(crate) fn start_agent_at_paths(
|
|
|
84
85
|
return Ok(StartAgentOutcome::Paused { agent_id: agent_id.clone() });
|
|
85
86
|
}
|
|
86
87
|
let session_name = state_session_name(&state);
|
|
87
|
-
let window = agent_window(agent, agent_id);
|
|
88
|
+
let window = agent_window(&agent, agent_id);
|
|
88
89
|
if !force && window_exists(transport, &session_name, &window) {
|
|
89
90
|
mark_agent_running_noop(&mut state, agent_id, &session_name, &window)?;
|
|
90
91
|
crate::state::projection::save_team_scoped_state(workspace, &state)
|
|
@@ -104,9 +105,9 @@ pub(crate) fn start_agent_at_paths(
|
|
|
104
105
|
target,
|
|
105
106
|
});
|
|
106
107
|
}
|
|
107
|
-
let provider = agent_provider(agent);
|
|
108
|
-
let session_id = agent_session_id(agent);
|
|
109
|
-
let rollout_path = agent_rollout_path(agent);
|
|
108
|
+
let provider = agent_provider(&agent);
|
|
109
|
+
let session_id = agent_session_id(&agent);
|
|
110
|
+
let rollout_path = agent_rollout_path(&agent);
|
|
110
111
|
let rollout_exists = rollout_path
|
|
111
112
|
.as_ref()
|
|
112
113
|
.map(|p| p.as_path().exists())
|
|
@@ -125,20 +126,25 @@ pub(crate) fn start_agent_at_paths(
|
|
|
125
126
|
};
|
|
126
127
|
let into_existing_session =
|
|
127
128
|
session_live_or_default(transport, &session_name, session_name_present(&state));
|
|
129
|
+
let safety = crate::lifecycle::launch::effective_runtime_config_for_worker_spawn()?;
|
|
128
130
|
let spawn = spawn_agent_window(
|
|
129
131
|
workspace,
|
|
130
132
|
&session_name,
|
|
131
133
|
agent_id,
|
|
132
|
-
agent,
|
|
134
|
+
&agent,
|
|
133
135
|
spawn_session_id,
|
|
134
136
|
into_existing_session,
|
|
135
137
|
transport,
|
|
138
|
+
Some(&safety),
|
|
136
139
|
None,
|
|
137
140
|
)?;
|
|
141
|
+
mark_agent_started(&mut state, agent_id, &window, &spawn, &safety)?;
|
|
142
|
+
crate::state::projection::save_team_scoped_state(workspace, &state)
|
|
143
|
+
.map_err(|e| LifecycleError::StatePersist(e.to_string()))?;
|
|
138
144
|
write_start_agent_start_event(
|
|
139
145
|
workspace,
|
|
140
146
|
agent_id,
|
|
141
|
-
agent,
|
|
147
|
+
&agent,
|
|
142
148
|
provider,
|
|
143
149
|
start_mode,
|
|
144
150
|
&session_name,
|
|
@@ -154,7 +160,7 @@ pub(crate) fn start_agent_at_paths(
|
|
|
154
160
|
coordinator_started,
|
|
155
161
|
},
|
|
156
162
|
start_mode,
|
|
157
|
-
target: spawn.pane_id.as_str().to_string(),
|
|
163
|
+
target: spawn.spawn.pane_id.as_str().to_string(),
|
|
158
164
|
session_id,
|
|
159
165
|
rollout_path,
|
|
160
166
|
})
|
|
@@ -281,6 +287,43 @@ pub(super) fn resolve_team_scoped_state_or_refuse(
|
|
|
281
287
|
state.ok_or_else(|| LifecycleError::StatePersist("resolve_team_scoped_state returned no state".to_string()))
|
|
282
288
|
}
|
|
283
289
|
|
|
290
|
+
fn mark_agent_started(
|
|
291
|
+
state: &mut serde_json::Value,
|
|
292
|
+
agent_id: &AgentId,
|
|
293
|
+
window: &str,
|
|
294
|
+
spawn: &SpawnedAgentWindow,
|
|
295
|
+
safety: &DangerousApproval,
|
|
296
|
+
) -> Result<(), LifecycleError> {
|
|
297
|
+
let Some(agent) = state
|
|
298
|
+
.get_mut("agents")
|
|
299
|
+
.and_then(serde_json::Value::as_object_mut)
|
|
300
|
+
.and_then(|agents| agents.get_mut(agent_id.as_str()))
|
|
301
|
+
.and_then(serde_json::Value::as_object_mut)
|
|
302
|
+
else {
|
|
303
|
+
return Err(LifecycleError::StatePersist(format!(
|
|
304
|
+
"agent {} state is not an object",
|
|
305
|
+
agent_id
|
|
306
|
+
)));
|
|
307
|
+
};
|
|
308
|
+
agent.insert("status".to_string(), serde_json::json!("running"));
|
|
309
|
+
agent.insert("agent_id".to_string(), serde_json::json!(agent_id.as_str()));
|
|
310
|
+
agent.insert("window".to_string(), serde_json::json!(window));
|
|
311
|
+
agent.insert(
|
|
312
|
+
"pane_id".to_string(),
|
|
313
|
+
serde_json::json!(spawn.spawn.pane_id.as_str()),
|
|
314
|
+
);
|
|
315
|
+
if let Some(pane_pid) = spawn.spawn.child_pid {
|
|
316
|
+
agent.insert("pane_pid".to_string(), serde_json::json!(pane_pid));
|
|
317
|
+
}
|
|
318
|
+
crate::lifecycle::launch::persist_command_plan_state(
|
|
319
|
+
agent,
|
|
320
|
+
&spawn.plan,
|
|
321
|
+
&spawn.profile_launch,
|
|
322
|
+
);
|
|
323
|
+
crate::lifecycle::launch::persist_effective_approval_policy(agent, safety);
|
|
324
|
+
Ok(())
|
|
325
|
+
}
|
|
326
|
+
|
|
284
327
|
/// `reset_agent(workspace, agent_id, discard_session, open_display, team)`
|
|
285
328
|
/// (`lifecycle/operations.py:102`)。discard + 重起;**未传 discard_session → 拒绝**。
|
|
286
329
|
pub fn reset_agent(
|
|
@@ -408,32 +451,76 @@ fn write_start_agent_start_event(
|
|
|
408
451
|
let adapter = crate::provider::get_adapter(provider);
|
|
409
452
|
// Contract C / F6.4: event log must record the same context-aware argv that the
|
|
410
453
|
// actual spawn used — so the role/tools/MCP context appears in `start_agent.agent_start`.
|
|
411
|
-
let role = agent.get("role").and_then(|v| v.as_str());
|
|
412
454
|
let safety = crate::lifecycle::launch::effective_runtime_config_for_worker_spawn()?;
|
|
413
|
-
let
|
|
414
|
-
|
|
455
|
+
let command_agent =
|
|
456
|
+
crate::lifecycle::worker_command_context::WorkerCommandAgent::from_json(
|
|
457
|
+
agent,
|
|
458
|
+
Some(agent_id.as_str()),
|
|
459
|
+
provider,
|
|
460
|
+
);
|
|
461
|
+
let system_prompt =
|
|
462
|
+
crate::lifecycle::worker_command_context::compile_worker_system_prompt(&command_agent)?;
|
|
463
|
+
let tools = crate::lifecycle::worker_command_context::resolved_tool_strings_for_command(
|
|
464
|
+
&command_agent,
|
|
465
|
+
provider,
|
|
466
|
+
&safety,
|
|
467
|
+
)?;
|
|
468
|
+
let resolved_tool_refs: Vec<&str> = tools.iter().map(String::as_str).collect();
|
|
415
469
|
let mcp_config = adapter
|
|
416
470
|
.mcp_config(auth_mode)
|
|
417
471
|
.map_err(|e| LifecycleError::Provider(e.to_string()))?;
|
|
418
|
-
let
|
|
472
|
+
let team_id = agent
|
|
473
|
+
.get("owner_team_id")
|
|
474
|
+
.and_then(|v| v.as_str());
|
|
475
|
+
let mcp_config = crate::lifecycle::launch::resolve_mcp_config(
|
|
476
|
+
mcp_config,
|
|
477
|
+
workspace,
|
|
478
|
+
agent_id.as_str(),
|
|
479
|
+
team_id.unwrap_or(""),
|
|
480
|
+
);
|
|
481
|
+
let mcp_config_path =
|
|
482
|
+
crate::lifecycle::launch::write_worker_mcp_config(workspace, agent_id.as_str(), &mcp_config)?;
|
|
483
|
+
let profile_launch =
|
|
484
|
+
crate::lifecycle::profile_launch::prepare_provider_profile_launch_from_json(
|
|
485
|
+
workspace,
|
|
486
|
+
agent_id.as_str(),
|
|
487
|
+
agent,
|
|
488
|
+
Some(&mcp_config),
|
|
489
|
+
)?;
|
|
490
|
+
let command_model = profile_launch
|
|
491
|
+
.command_overrides
|
|
492
|
+
.model
|
|
493
|
+
.as_deref()
|
|
494
|
+
.or(model);
|
|
495
|
+
let context = crate::provider::ProviderCommandContext {
|
|
496
|
+
auth_mode,
|
|
497
|
+
mcp_config: Some(&mcp_config),
|
|
498
|
+
system_prompt: Some(system_prompt.as_str()),
|
|
499
|
+
model: command_model,
|
|
500
|
+
tools: &resolved_tool_refs,
|
|
501
|
+
profile_launch: Some(&profile_launch),
|
|
502
|
+
};
|
|
503
|
+
let mut plan = match session_id {
|
|
419
504
|
Some(session_id) => adapter
|
|
420
|
-
.
|
|
421
|
-
Some(session_id),
|
|
422
|
-
auth_mode,
|
|
423
|
-
Some(&mcp_config),
|
|
424
|
-
role,
|
|
425
|
-
model,
|
|
426
|
-
&tool_refs,
|
|
427
|
-
)
|
|
505
|
+
.build_resume_command_plan(Some(session_id), context)
|
|
428
506
|
.map_err(|e| LifecycleError::Provider(e.to_string()))?,
|
|
429
507
|
None => adapter
|
|
430
|
-
.
|
|
508
|
+
.build_command_plan(context)
|
|
431
509
|
.map_err(|e| LifecycleError::Provider(e.to_string()))?,
|
|
432
510
|
};
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
511
|
+
if !plan.managed_mcp_config && !profile_launch.managed_mcp_config {
|
|
512
|
+
crate::lifecycle::launch::point_native_mcp_config_at_file(
|
|
513
|
+
&mut plan.argv,
|
|
514
|
+
provider,
|
|
515
|
+
&mcp_config_path,
|
|
516
|
+
);
|
|
517
|
+
}
|
|
518
|
+
crate::lifecycle::launch::fill_spawn_placeholders_full(
|
|
519
|
+
&mut plan.argv,
|
|
520
|
+
workspace,
|
|
521
|
+
agent_id.as_str(),
|
|
522
|
+
team_id,
|
|
523
|
+
);
|
|
437
524
|
let tmux_start_mode = if into_existing_session {
|
|
438
525
|
"new-window"
|
|
439
526
|
} else {
|
|
@@ -450,7 +537,7 @@ fn write_start_agent_start_event(
|
|
|
450
537
|
"session": session_name.as_str(),
|
|
451
538
|
"window": window,
|
|
452
539
|
"tmux_start_mode": tmux_start_mode,
|
|
453
|
-
"command": argv,
|
|
540
|
+
"command": plan.argv,
|
|
454
541
|
"mcp_config": agent.get("mcp_config").cloned().unwrap_or(serde_json::Value::Null),
|
|
455
542
|
}),
|
|
456
543
|
)
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
use super::*;
|
|
2
2
|
|
|
3
|
+
pub(super) struct SpawnedAgentWindow {
|
|
4
|
+
pub spawn: crate::transport::SpawnResult,
|
|
5
|
+
pub plan: crate::provider::CommandPlan,
|
|
6
|
+
pub profile_launch: crate::provider::ProviderProfileLaunch,
|
|
7
|
+
}
|
|
8
|
+
|
|
3
9
|
pub(super) fn spawn_agent_window(
|
|
4
10
|
workspace: &Path,
|
|
5
11
|
session_name: &SessionName,
|
|
@@ -9,7 +15,8 @@ pub(super) fn spawn_agent_window(
|
|
|
9
15
|
into_existing_session: bool,
|
|
10
16
|
transport: &dyn crate::transport::Transport,
|
|
11
17
|
safety: Option<&DangerousApproval>,
|
|
12
|
-
|
|
18
|
+
spawn_cwd_override: Option<&Path>,
|
|
19
|
+
) -> Result<SpawnedAgentWindow, LifecycleError> {
|
|
13
20
|
let provider = agent_provider(agent);
|
|
14
21
|
let auth_mode = agent_auth_mode(agent);
|
|
15
22
|
let model = agent.get("model").and_then(|v| v.as_str());
|
|
@@ -22,7 +29,6 @@ pub(super) fn spawn_agent_window(
|
|
|
22
29
|
// Contract C / F6.4: thread compiled role/tools/MCP context through restart as well —
|
|
23
30
|
// a restarted worker must come back up with the SAME callable MCP capability + role
|
|
24
31
|
// prompt as a fresh launch, else `report_result` becomes unreachable after every restart.
|
|
25
|
-
let role = agent.get("role").and_then(|v| v.as_str());
|
|
26
32
|
let detected_safety;
|
|
27
33
|
let safety = if let Some(safety) = safety {
|
|
28
34
|
safety
|
|
@@ -30,26 +36,20 @@ pub(super) fn spawn_agent_window(
|
|
|
30
36
|
detected_safety = crate::lifecycle::launch::effective_runtime_config_for_worker_spawn()?;
|
|
31
37
|
&detected_safety
|
|
32
38
|
};
|
|
33
|
-
let
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
)
|
|
48
|
-
.map_err(|e| LifecycleError::Provider(e.to_string()))?,
|
|
49
|
-
None => adapter
|
|
50
|
-
.build_command_with_tools(auth_mode, Some(&mcp_config), role, model, &tool_refs)
|
|
51
|
-
.map_err(|e| LifecycleError::Provider(e.to_string()))?,
|
|
52
|
-
};
|
|
39
|
+
let command_agent =
|
|
40
|
+
crate::lifecycle::worker_command_context::WorkerCommandAgent::from_json(
|
|
41
|
+
agent,
|
|
42
|
+
Some(agent_id.as_str()),
|
|
43
|
+
provider,
|
|
44
|
+
);
|
|
45
|
+
let system_prompt =
|
|
46
|
+
crate::lifecycle::worker_command_context::compile_worker_system_prompt(&command_agent)?;
|
|
47
|
+
let tools = crate::lifecycle::worker_command_context::resolved_tool_strings_for_command(
|
|
48
|
+
&command_agent,
|
|
49
|
+
provider,
|
|
50
|
+
safety,
|
|
51
|
+
)?;
|
|
52
|
+
let resolved_tool_refs: Vec<&str> = tools.iter().map(String::as_str).collect();
|
|
53
53
|
// owner_team_id resolution: prefer the runtime-state row's `owner_team_id` (set by
|
|
54
54
|
// launch/restart); fall back to the active team key for paths that don't write the
|
|
55
55
|
// row first (e.g. add-agent calls spawn before upserting team metadata).
|
|
@@ -63,22 +63,78 @@ pub(super) fn spawn_agent_window(
|
|
|
63
63
|
let key = crate::messaging::leader_receiver::active_team_key(workspace, &state_for_team);
|
|
64
64
|
(!key.is_empty()).then_some(key)
|
|
65
65
|
});
|
|
66
|
+
let mcp_config = adapter
|
|
67
|
+
.mcp_config(auth_mode)
|
|
68
|
+
.map_err(|e| LifecycleError::Provider(e.to_string()))?;
|
|
69
|
+
let mcp_config = crate::lifecycle::launch::resolve_mcp_config(
|
|
70
|
+
mcp_config,
|
|
71
|
+
workspace,
|
|
72
|
+
agent_id.as_str(),
|
|
73
|
+
team_id.as_deref().unwrap_or(""),
|
|
74
|
+
);
|
|
75
|
+
let mcp_config_path =
|
|
76
|
+
crate::lifecycle::launch::write_worker_mcp_config(workspace, agent_id.as_str(), &mcp_config)?;
|
|
77
|
+
let profile_launch =
|
|
78
|
+
crate::lifecycle::profile_launch::prepare_provider_profile_launch_from_json(
|
|
79
|
+
workspace,
|
|
80
|
+
agent_id.as_str(),
|
|
81
|
+
agent,
|
|
82
|
+
Some(&mcp_config),
|
|
83
|
+
)?;
|
|
84
|
+
let command_model = profile_launch
|
|
85
|
+
.command_overrides
|
|
86
|
+
.model
|
|
87
|
+
.as_deref()
|
|
88
|
+
.or(model);
|
|
89
|
+
let context = crate::provider::ProviderCommandContext {
|
|
90
|
+
auth_mode,
|
|
91
|
+
mcp_config: Some(&mcp_config),
|
|
92
|
+
system_prompt: Some(system_prompt.as_str()),
|
|
93
|
+
model: command_model,
|
|
94
|
+
tools: &resolved_tool_refs,
|
|
95
|
+
profile_launch: Some(&profile_launch),
|
|
96
|
+
};
|
|
97
|
+
let mut plan = match resume_session_id {
|
|
98
|
+
Some(session_id) => adapter
|
|
99
|
+
.build_resume_command_plan(Some(session_id), context)
|
|
100
|
+
.map_err(|e| LifecycleError::Provider(e.to_string()))?,
|
|
101
|
+
None => adapter
|
|
102
|
+
.build_command_plan(context)
|
|
103
|
+
.map_err(|e| LifecycleError::Provider(e.to_string()))?,
|
|
104
|
+
};
|
|
105
|
+
if !plan.managed_mcp_config && !profile_launch.managed_mcp_config {
|
|
106
|
+
crate::lifecycle::launch::point_native_mcp_config_at_file(
|
|
107
|
+
&mut plan.argv,
|
|
108
|
+
provider,
|
|
109
|
+
&mcp_config_path,
|
|
110
|
+
);
|
|
111
|
+
}
|
|
66
112
|
crate::lifecycle::launch::fill_spawn_placeholders_full(
|
|
67
|
-
&mut argv,
|
|
113
|
+
&mut plan.argv,
|
|
68
114
|
workspace,
|
|
69
115
|
agent_id.as_str(),
|
|
70
116
|
team_id.as_deref(),
|
|
71
117
|
);
|
|
72
118
|
let window = WindowName::new(agent_id.as_str());
|
|
73
|
-
let env = crate::lifecycle::launch::inherited_env_with_team_overrides(
|
|
119
|
+
let mut env = crate::lifecycle::launch::inherited_env_with_team_overrides(
|
|
74
120
|
workspace,
|
|
75
121
|
agent_id.as_str(),
|
|
76
122
|
team_id.as_deref(),
|
|
77
123
|
);
|
|
124
|
+
crate::lifecycle::launch::apply_profile_launch_env(&mut env, &profile_launch);
|
|
125
|
+
let spawn_cwd = spawn_cwd_override
|
|
126
|
+
.or_else(|| {
|
|
127
|
+
agent
|
|
128
|
+
.get("spawn_cwd")
|
|
129
|
+
.and_then(|v| v.as_str())
|
|
130
|
+
.filter(|cwd| !cwd.is_empty())
|
|
131
|
+
.map(Path::new)
|
|
132
|
+
})
|
|
133
|
+
.unwrap_or(workspace);
|
|
78
134
|
let result = if into_existing_session {
|
|
79
|
-
transport.spawn_into(session_name, &window, &argv,
|
|
135
|
+
transport.spawn_into(session_name, &window, &plan.argv, spawn_cwd, &env)
|
|
80
136
|
} else {
|
|
81
|
-
transport.spawn_first(session_name, &window, &argv,
|
|
137
|
+
transport.spawn_first(session_name, &window, &plan.argv, spawn_cwd, &env)
|
|
82
138
|
};
|
|
83
139
|
let spawn = result.map_err(|e| LifecycleError::Transport(e.to_string()))?;
|
|
84
140
|
let _ = adapter.handle_startup_prompts(
|
|
@@ -87,7 +143,11 @@ pub(super) fn spawn_agent_window(
|
|
|
87
143
|
30,
|
|
88
144
|
0.5,
|
|
89
145
|
);
|
|
90
|
-
Ok(
|
|
146
|
+
Ok(SpawnedAgentWindow {
|
|
147
|
+
spawn,
|
|
148
|
+
plan,
|
|
149
|
+
profile_launch,
|
|
150
|
+
})
|
|
91
151
|
}
|
|
92
152
|
|
|
93
153
|
pub(super) fn start_coordinator_for_workspace(workspace: &Path) -> Result<bool, LifecycleError> {
|
|
@@ -97,6 +157,13 @@ pub(super) fn start_coordinator_for_workspace(workspace: &Path) -> Result<bool,
|
|
|
97
157
|
.map_err(|e| LifecycleError::StatePersist(e.to_string()))
|
|
98
158
|
}
|
|
99
159
|
|
|
160
|
+
pub(super) fn persist_effective_approval_policy_for_restart(
|
|
161
|
+
agent: &mut serde_json::Map<String, serde_json::Value>,
|
|
162
|
+
safety: &DangerousApproval,
|
|
163
|
+
) {
|
|
164
|
+
crate::lifecycle::launch::persist_effective_approval_policy(agent, safety);
|
|
165
|
+
}
|
|
166
|
+
|
|
100
167
|
pub(super) fn state_session_name(state: &serde_json::Value) -> SessionName {
|
|
101
168
|
state
|
|
102
169
|
.get("session_name")
|
|
@@ -163,86 +230,106 @@ pub(super) fn agent_rollout_path(agent: &serde_json::Value) -> Option<RolloutPat
|
|
|
163
230
|
pub(crate) fn refresh_missing_provider_sessions(
|
|
164
231
|
state: &mut serde_json::Value,
|
|
165
232
|
) -> Result<bool, LifecycleError> {
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
233
|
+
crate::session_capture::capture_missing_provider_sessions_once(
|
|
234
|
+
state,
|
|
235
|
+
&mut crate::provider::get_adapter,
|
|
236
|
+
false,
|
|
237
|
+
0,
|
|
238
|
+
)
|
|
239
|
+
.map(|report| report.changed)
|
|
240
|
+
.map_err(|e| LifecycleError::Provider(e.to_string()))
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
pub(crate) fn converge_missing_provider_sessions(
|
|
244
|
+
state: &mut serde_json::Value,
|
|
245
|
+
deadline: std::time::Duration,
|
|
246
|
+
poll_interval: std::time::Duration,
|
|
247
|
+
workspace: &Path,
|
|
248
|
+
allow_fresh: bool,
|
|
249
|
+
) -> Result<crate::session_capture::SessionConvergence, LifecycleError> {
|
|
250
|
+
crate::session_capture::converge_missing_provider_sessions(
|
|
251
|
+
state,
|
|
252
|
+
&mut crate::provider::get_adapter,
|
|
253
|
+
deadline,
|
|
254
|
+
poll_interval,
|
|
255
|
+
restart_required_missing_session_agent_ids,
|
|
256
|
+
|progress| {
|
|
257
|
+
let pending_agent_ids = progress.pending_agent_ids.clone();
|
|
258
|
+
write_session_convergence_progress_event(
|
|
259
|
+
workspace,
|
|
260
|
+
serde_json::json!({
|
|
261
|
+
"ts": chrono::Utc::now().to_rfc3339(),
|
|
262
|
+
"event": "provider.session.converging",
|
|
263
|
+
"iteration": progress.iteration,
|
|
264
|
+
"elapsed_ms": progress.elapsed_ms,
|
|
265
|
+
"deadline_ms": progress.deadline_ms,
|
|
266
|
+
"changed": progress.changed,
|
|
267
|
+
"assigned": progress.assigned,
|
|
268
|
+
"missing": progress.missing,
|
|
269
|
+
"required_missing": progress.required_missing_agent_ids.clone(),
|
|
270
|
+
"required_missing_agent_ids": progress.required_missing_agent_ids,
|
|
271
|
+
"pending": pending_agent_ids,
|
|
272
|
+
"pending_agent_ids": progress.pending_agent_ids,
|
|
273
|
+
"candidate_count_by_agent": progress.candidate_count_by_agent,
|
|
274
|
+
"remaining_ms": progress.remaining_ms,
|
|
275
|
+
"allow_fresh": allow_fresh,
|
|
276
|
+
}),
|
|
277
|
+
)
|
|
278
|
+
},
|
|
279
|
+
)
|
|
280
|
+
.map_err(LifecycleError::StatePersist)
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
fn write_session_convergence_progress_event(
|
|
284
|
+
workspace: &Path,
|
|
285
|
+
event: serde_json::Value,
|
|
286
|
+
) -> Result<(), String> {
|
|
287
|
+
use std::io::Write as _;
|
|
288
|
+
|
|
289
|
+
let path = workspace.join(".team").join("logs").join("events.jsonl");
|
|
290
|
+
if let Some(parent) = path.parent() {
|
|
291
|
+
std::fs::create_dir_all(parent).map_err(|e| e.to_string())?;
|
|
224
292
|
}
|
|
225
|
-
|
|
293
|
+
let line = serde_json::to_string(&event).map_err(|e| e.to_string())?;
|
|
294
|
+
let mut file = std::fs::OpenOptions::new()
|
|
295
|
+
.create(true)
|
|
296
|
+
.append(true)
|
|
297
|
+
.open(path)
|
|
298
|
+
.map_err(|e| e.to_string())?;
|
|
299
|
+
file.write_all(line.as_bytes())
|
|
300
|
+
.and_then(|_| file.write_all(b"\n"))
|
|
301
|
+
.map_err(|e| e.to_string())
|
|
226
302
|
}
|
|
227
303
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
.
|
|
239
|
-
|
|
240
|
-
.
|
|
241
|
-
.
|
|
304
|
+
pub(crate) fn restart_required_missing_session_agent_ids(state: &serde_json::Value) -> Vec<String> {
|
|
305
|
+
let mut missing = crate::session_capture::incomplete_resumable_agent_ids(state)
|
|
306
|
+
.into_iter()
|
|
307
|
+
.filter(|agent_id| {
|
|
308
|
+
let Some(agent) = state.get("agents").and_then(|agents| agents.get(agent_id)) else {
|
|
309
|
+
return false;
|
|
310
|
+
};
|
|
311
|
+
let missing_session_id = agent
|
|
312
|
+
.get("session_id")
|
|
313
|
+
.and_then(|value| value.as_str())
|
|
314
|
+
.is_none_or(|session| session.is_empty());
|
|
315
|
+
let is_running = agent
|
|
316
|
+
.get("status")
|
|
317
|
+
.and_then(|value| value.as_str())
|
|
318
|
+
.is_some_and(|status| status == "running");
|
|
319
|
+
let has_live_pane_binding = agent
|
|
320
|
+
.get("pane_id")
|
|
321
|
+
.and_then(|value| value.as_str())
|
|
322
|
+
.is_some_and(|pane| !pane.is_empty());
|
|
323
|
+
let has_interaction_marker = agent
|
|
324
|
+
.get("first_send_at")
|
|
325
|
+
.and_then(|value| value.as_str())
|
|
326
|
+
.is_some_and(|value| !value.is_empty());
|
|
327
|
+
missing_session_id && is_running && (has_live_pane_binding || has_interaction_marker)
|
|
242
328
|
})
|
|
243
|
-
.
|
|
329
|
+
.collect::<Vec<_>>();
|
|
330
|
+
missing.sort();
|
|
331
|
+
missing
|
|
244
332
|
}
|
|
245
|
-
|
|
246
333
|
pub(super) fn agent_window(agent: &serde_json::Value, agent_id: &AgentId) -> String {
|
|
247
334
|
agent
|
|
248
335
|
.get("window")
|