@team-agent/installer 0.3.1 → 0.3.3
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 +234 -26
- package/crates/team-agent/src/cli/diagnose.rs +144 -10
- package/crates/team-agent/src/cli/emit.rs +289 -54
- package/crates/team-agent/src/cli/leader.rs +37 -8
- package/crates/team-agent/src/cli/mod.rs +1281 -196
- package/crates/team-agent/src/cli/status_port.rs +195 -46
- 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 +59 -3
- package/crates/team-agent/src/cli/types.rs +18 -0
- package/crates/team-agent/src/compiler.rs +15 -5
- package/crates/team-agent/src/coordinator/health.rs +95 -17
- 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/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 +645 -47
- package/crates/team-agent/src/lifecycle/launch.rs +1061 -146
- package/crates/team-agent/src/lifecycle/mod.rs +2 -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 +99 -23
- package/crates/team-agent/src/lifecycle/restart/common.rs +183 -24
- package/crates/team-agent/src/lifecycle/restart/rebuild.rs +498 -22
- package/crates/team-agent/src/lifecycle/restart/remove.rs +27 -7
- package/crates/team-agent/src/lifecycle/restart/team_state.rs +19 -0
- package/crates/team-agent/src/lifecycle/restart.rs +24 -1
- package/crates/team-agent/src/lifecycle/tests/lane_ops.rs +5 -5
- package/crates/team-agent/src/lifecycle/tests/launch_spawn.rs +37 -7
- package/crates/team-agent/src/lifecycle/types.rs +19 -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 +470 -59
- package/crates/team-agent/src/messaging/mod.rs +9 -6
- package/crates/team-agent/src/messaging/results.rs +353 -63
- 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 +564 -63
- 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/helpers.rs +10 -1
- 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 +170 -1
- package/crates/team-agent/src/state/projection.rs +141 -8
- package/crates/team-agent/src/state/selector.rs +5 -2
- package/crates/team-agent/src/tmux_backend.rs +161 -64
- 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(
|
|
@@ -415,25 +458,58 @@ fn write_start_agent_start_event(
|
|
|
415
458
|
let mcp_config = adapter
|
|
416
459
|
.mcp_config(auth_mode)
|
|
417
460
|
.map_err(|e| LifecycleError::Provider(e.to_string()))?;
|
|
418
|
-
let
|
|
461
|
+
let team_id = agent
|
|
462
|
+
.get("owner_team_id")
|
|
463
|
+
.and_then(|v| v.as_str());
|
|
464
|
+
let mcp_config = crate::lifecycle::launch::resolve_mcp_config(
|
|
465
|
+
mcp_config,
|
|
466
|
+
workspace,
|
|
467
|
+
agent_id.as_str(),
|
|
468
|
+
team_id.unwrap_or(""),
|
|
469
|
+
);
|
|
470
|
+
let mcp_config_path =
|
|
471
|
+
crate::lifecycle::launch::write_worker_mcp_config(workspace, agent_id.as_str(), &mcp_config)?;
|
|
472
|
+
let profile_launch =
|
|
473
|
+
crate::lifecycle::profile_launch::prepare_provider_profile_launch_from_json(
|
|
474
|
+
workspace,
|
|
475
|
+
agent_id.as_str(),
|
|
476
|
+
agent,
|
|
477
|
+
Some(&mcp_config),
|
|
478
|
+
)?;
|
|
479
|
+
let command_model = profile_launch
|
|
480
|
+
.command_overrides
|
|
481
|
+
.model
|
|
482
|
+
.as_deref()
|
|
483
|
+
.or(model);
|
|
484
|
+
let context = crate::provider::ProviderCommandContext {
|
|
485
|
+
auth_mode,
|
|
486
|
+
mcp_config: Some(&mcp_config),
|
|
487
|
+
system_prompt: role,
|
|
488
|
+
model: command_model,
|
|
489
|
+
tools: &tool_refs,
|
|
490
|
+
profile_launch: Some(&profile_launch),
|
|
491
|
+
};
|
|
492
|
+
let mut plan = match session_id {
|
|
419
493
|
Some(session_id) => adapter
|
|
420
|
-
.
|
|
421
|
-
Some(session_id),
|
|
422
|
-
auth_mode,
|
|
423
|
-
Some(&mcp_config),
|
|
424
|
-
role,
|
|
425
|
-
model,
|
|
426
|
-
&tool_refs,
|
|
427
|
-
)
|
|
494
|
+
.build_resume_command_plan(Some(session_id), context)
|
|
428
495
|
.map_err(|e| LifecycleError::Provider(e.to_string()))?,
|
|
429
496
|
None => adapter
|
|
430
|
-
.
|
|
497
|
+
.build_command_plan(context)
|
|
431
498
|
.map_err(|e| LifecycleError::Provider(e.to_string()))?,
|
|
432
499
|
};
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
500
|
+
if !plan.managed_mcp_config && !profile_launch.managed_mcp_config {
|
|
501
|
+
crate::lifecycle::launch::point_native_mcp_config_at_file(
|
|
502
|
+
&mut plan.argv,
|
|
503
|
+
provider,
|
|
504
|
+
&mcp_config_path,
|
|
505
|
+
);
|
|
506
|
+
}
|
|
507
|
+
crate::lifecycle::launch::fill_spawn_placeholders_full(
|
|
508
|
+
&mut plan.argv,
|
|
509
|
+
workspace,
|
|
510
|
+
agent_id.as_str(),
|
|
511
|
+
team_id,
|
|
512
|
+
);
|
|
437
513
|
let tmux_start_mode = if into_existing_session {
|
|
438
514
|
"new-window"
|
|
439
515
|
} else {
|
|
@@ -450,7 +526,7 @@ fn write_start_agent_start_event(
|
|
|
450
526
|
"session": session_name.as_str(),
|
|
451
527
|
"window": window,
|
|
452
528
|
"tmux_start_mode": tmux_start_mode,
|
|
453
|
-
"command": argv,
|
|
529
|
+
"command": plan.argv,
|
|
454
530
|
"mcp_config": agent.get("mcp_config").cloned().unwrap_or(serde_json::Value::Null),
|
|
455
531
|
}),
|
|
456
532
|
)
|
|
@@ -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());
|
|
@@ -32,24 +39,6 @@ pub(super) fn spawn_agent_window(
|
|
|
32
39
|
};
|
|
33
40
|
let tools = crate::lifecycle::launch::worker_tool_refs(agent_tool_strings(agent), safety);
|
|
34
41
|
let tool_refs: Vec<&str> = tools.iter().map(String::as_str).collect();
|
|
35
|
-
let mcp_config = adapter
|
|
36
|
-
.mcp_config(auth_mode)
|
|
37
|
-
.map_err(|e| LifecycleError::Provider(e.to_string()))?;
|
|
38
|
-
let mut argv = match resume_session_id {
|
|
39
|
-
Some(session_id) => adapter
|
|
40
|
-
.build_resume_command_with_context(
|
|
41
|
-
Some(session_id),
|
|
42
|
-
auth_mode,
|
|
43
|
-
Some(&mcp_config),
|
|
44
|
-
role,
|
|
45
|
-
model,
|
|
46
|
-
&tool_refs,
|
|
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
|
-
};
|
|
53
42
|
// owner_team_id resolution: prefer the runtime-state row's `owner_team_id` (set by
|
|
54
43
|
// launch/restart); fall back to the active team key for paths that don't write the
|
|
55
44
|
// row first (e.g. add-agent calls spawn before upserting team metadata).
|
|
@@ -63,22 +52,78 @@ pub(super) fn spawn_agent_window(
|
|
|
63
52
|
let key = crate::messaging::leader_receiver::active_team_key(workspace, &state_for_team);
|
|
64
53
|
(!key.is_empty()).then_some(key)
|
|
65
54
|
});
|
|
55
|
+
let mcp_config = adapter
|
|
56
|
+
.mcp_config(auth_mode)
|
|
57
|
+
.map_err(|e| LifecycleError::Provider(e.to_string()))?;
|
|
58
|
+
let mcp_config = crate::lifecycle::launch::resolve_mcp_config(
|
|
59
|
+
mcp_config,
|
|
60
|
+
workspace,
|
|
61
|
+
agent_id.as_str(),
|
|
62
|
+
team_id.as_deref().unwrap_or(""),
|
|
63
|
+
);
|
|
64
|
+
let mcp_config_path =
|
|
65
|
+
crate::lifecycle::launch::write_worker_mcp_config(workspace, agent_id.as_str(), &mcp_config)?;
|
|
66
|
+
let profile_launch =
|
|
67
|
+
crate::lifecycle::profile_launch::prepare_provider_profile_launch_from_json(
|
|
68
|
+
workspace,
|
|
69
|
+
agent_id.as_str(),
|
|
70
|
+
agent,
|
|
71
|
+
Some(&mcp_config),
|
|
72
|
+
)?;
|
|
73
|
+
let command_model = profile_launch
|
|
74
|
+
.command_overrides
|
|
75
|
+
.model
|
|
76
|
+
.as_deref()
|
|
77
|
+
.or(model);
|
|
78
|
+
let context = crate::provider::ProviderCommandContext {
|
|
79
|
+
auth_mode,
|
|
80
|
+
mcp_config: Some(&mcp_config),
|
|
81
|
+
system_prompt: role,
|
|
82
|
+
model: command_model,
|
|
83
|
+
tools: &tool_refs,
|
|
84
|
+
profile_launch: Some(&profile_launch),
|
|
85
|
+
};
|
|
86
|
+
let mut plan = match resume_session_id {
|
|
87
|
+
Some(session_id) => adapter
|
|
88
|
+
.build_resume_command_plan(Some(session_id), context)
|
|
89
|
+
.map_err(|e| LifecycleError::Provider(e.to_string()))?,
|
|
90
|
+
None => adapter
|
|
91
|
+
.build_command_plan(context)
|
|
92
|
+
.map_err(|e| LifecycleError::Provider(e.to_string()))?,
|
|
93
|
+
};
|
|
94
|
+
if !plan.managed_mcp_config && !profile_launch.managed_mcp_config {
|
|
95
|
+
crate::lifecycle::launch::point_native_mcp_config_at_file(
|
|
96
|
+
&mut plan.argv,
|
|
97
|
+
provider,
|
|
98
|
+
&mcp_config_path,
|
|
99
|
+
);
|
|
100
|
+
}
|
|
66
101
|
crate::lifecycle::launch::fill_spawn_placeholders_full(
|
|
67
|
-
&mut argv,
|
|
102
|
+
&mut plan.argv,
|
|
68
103
|
workspace,
|
|
69
104
|
agent_id.as_str(),
|
|
70
105
|
team_id.as_deref(),
|
|
71
106
|
);
|
|
72
107
|
let window = WindowName::new(agent_id.as_str());
|
|
73
|
-
let env = crate::lifecycle::launch::inherited_env_with_team_overrides(
|
|
108
|
+
let mut env = crate::lifecycle::launch::inherited_env_with_team_overrides(
|
|
74
109
|
workspace,
|
|
75
110
|
agent_id.as_str(),
|
|
76
111
|
team_id.as_deref(),
|
|
77
112
|
);
|
|
113
|
+
crate::lifecycle::launch::apply_profile_launch_env(&mut env, &profile_launch);
|
|
114
|
+
let spawn_cwd = spawn_cwd_override
|
|
115
|
+
.or_else(|| {
|
|
116
|
+
agent
|
|
117
|
+
.get("spawn_cwd")
|
|
118
|
+
.and_then(|v| v.as_str())
|
|
119
|
+
.filter(|cwd| !cwd.is_empty())
|
|
120
|
+
.map(Path::new)
|
|
121
|
+
})
|
|
122
|
+
.unwrap_or(workspace);
|
|
78
123
|
let result = if into_existing_session {
|
|
79
|
-
transport.spawn_into(session_name, &window, &argv,
|
|
124
|
+
transport.spawn_into(session_name, &window, &plan.argv, spawn_cwd, &env)
|
|
80
125
|
} else {
|
|
81
|
-
transport.spawn_first(session_name, &window, &argv,
|
|
126
|
+
transport.spawn_first(session_name, &window, &plan.argv, spawn_cwd, &env)
|
|
82
127
|
};
|
|
83
128
|
let spawn = result.map_err(|e| LifecycleError::Transport(e.to_string()))?;
|
|
84
129
|
let _ = adapter.handle_startup_prompts(
|
|
@@ -87,7 +132,11 @@ pub(super) fn spawn_agent_window(
|
|
|
87
132
|
30,
|
|
88
133
|
0.5,
|
|
89
134
|
);
|
|
90
|
-
Ok(
|
|
135
|
+
Ok(SpawnedAgentWindow {
|
|
136
|
+
spawn,
|
|
137
|
+
plan,
|
|
138
|
+
profile_launch,
|
|
139
|
+
})
|
|
91
140
|
}
|
|
92
141
|
|
|
93
142
|
pub(super) fn start_coordinator_for_workspace(workspace: &Path) -> Result<bool, LifecycleError> {
|
|
@@ -97,6 +146,13 @@ pub(super) fn start_coordinator_for_workspace(workspace: &Path) -> Result<bool,
|
|
|
97
146
|
.map_err(|e| LifecycleError::StatePersist(e.to_string()))
|
|
98
147
|
}
|
|
99
148
|
|
|
149
|
+
pub(super) fn persist_effective_approval_policy_for_restart(
|
|
150
|
+
agent: &mut serde_json::Map<String, serde_json::Value>,
|
|
151
|
+
safety: &DangerousApproval,
|
|
152
|
+
) {
|
|
153
|
+
crate::lifecycle::launch::persist_effective_approval_policy(agent, safety);
|
|
154
|
+
}
|
|
155
|
+
|
|
100
156
|
pub(super) fn state_session_name(state: &serde_json::Value) -> SessionName {
|
|
101
157
|
state
|
|
102
158
|
.get("session_name")
|
|
@@ -160,6 +216,109 @@ pub(super) fn agent_rollout_path(agent: &serde_json::Value) -> Option<RolloutPat
|
|
|
160
216
|
.map(RolloutPath::new)
|
|
161
217
|
}
|
|
162
218
|
|
|
219
|
+
pub(crate) fn refresh_missing_provider_sessions(
|
|
220
|
+
state: &mut serde_json::Value,
|
|
221
|
+
) -> Result<bool, LifecycleError> {
|
|
222
|
+
crate::session_capture::capture_missing_provider_sessions_once(
|
|
223
|
+
state,
|
|
224
|
+
&mut crate::provider::get_adapter,
|
|
225
|
+
false,
|
|
226
|
+
0,
|
|
227
|
+
)
|
|
228
|
+
.map(|report| report.changed)
|
|
229
|
+
.map_err(|e| LifecycleError::Provider(e.to_string()))
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
pub(crate) fn converge_missing_provider_sessions(
|
|
233
|
+
state: &mut serde_json::Value,
|
|
234
|
+
deadline: std::time::Duration,
|
|
235
|
+
poll_interval: std::time::Duration,
|
|
236
|
+
workspace: &Path,
|
|
237
|
+
allow_fresh: bool,
|
|
238
|
+
) -> Result<crate::session_capture::SessionConvergence, LifecycleError> {
|
|
239
|
+
crate::session_capture::converge_missing_provider_sessions(
|
|
240
|
+
state,
|
|
241
|
+
&mut crate::provider::get_adapter,
|
|
242
|
+
deadline,
|
|
243
|
+
poll_interval,
|
|
244
|
+
restart_required_missing_session_agent_ids,
|
|
245
|
+
|progress| {
|
|
246
|
+
let pending_agent_ids = progress.pending_agent_ids.clone();
|
|
247
|
+
write_session_convergence_progress_event(
|
|
248
|
+
workspace,
|
|
249
|
+
serde_json::json!({
|
|
250
|
+
"ts": chrono::Utc::now().to_rfc3339(),
|
|
251
|
+
"event": "provider.session.converging",
|
|
252
|
+
"iteration": progress.iteration,
|
|
253
|
+
"elapsed_ms": progress.elapsed_ms,
|
|
254
|
+
"deadline_ms": progress.deadline_ms,
|
|
255
|
+
"changed": progress.changed,
|
|
256
|
+
"assigned": progress.assigned,
|
|
257
|
+
"missing": progress.missing,
|
|
258
|
+
"required_missing": progress.required_missing_agent_ids.clone(),
|
|
259
|
+
"required_missing_agent_ids": progress.required_missing_agent_ids,
|
|
260
|
+
"pending": pending_agent_ids,
|
|
261
|
+
"pending_agent_ids": progress.pending_agent_ids,
|
|
262
|
+
"candidate_count_by_agent": progress.candidate_count_by_agent,
|
|
263
|
+
"remaining_ms": progress.remaining_ms,
|
|
264
|
+
"allow_fresh": allow_fresh,
|
|
265
|
+
}),
|
|
266
|
+
)
|
|
267
|
+
},
|
|
268
|
+
)
|
|
269
|
+
.map_err(LifecycleError::StatePersist)
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
fn write_session_convergence_progress_event(
|
|
273
|
+
workspace: &Path,
|
|
274
|
+
event: serde_json::Value,
|
|
275
|
+
) -> Result<(), String> {
|
|
276
|
+
use std::io::Write as _;
|
|
277
|
+
|
|
278
|
+
let path = workspace.join(".team").join("logs").join("events.jsonl");
|
|
279
|
+
if let Some(parent) = path.parent() {
|
|
280
|
+
std::fs::create_dir_all(parent).map_err(|e| e.to_string())?;
|
|
281
|
+
}
|
|
282
|
+
let line = serde_json::to_string(&event).map_err(|e| e.to_string())?;
|
|
283
|
+
let mut file = std::fs::OpenOptions::new()
|
|
284
|
+
.create(true)
|
|
285
|
+
.append(true)
|
|
286
|
+
.open(path)
|
|
287
|
+
.map_err(|e| e.to_string())?;
|
|
288
|
+
file.write_all(line.as_bytes())
|
|
289
|
+
.and_then(|_| file.write_all(b"\n"))
|
|
290
|
+
.map_err(|e| e.to_string())
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
pub(crate) fn restart_required_missing_session_agent_ids(state: &serde_json::Value) -> Vec<String> {
|
|
294
|
+
let mut missing = crate::session_capture::incomplete_resumable_agent_ids(state)
|
|
295
|
+
.into_iter()
|
|
296
|
+
.filter(|agent_id| {
|
|
297
|
+
let Some(agent) = state.get("agents").and_then(|agents| agents.get(agent_id)) else {
|
|
298
|
+
return false;
|
|
299
|
+
};
|
|
300
|
+
let missing_session_id = agent
|
|
301
|
+
.get("session_id")
|
|
302
|
+
.and_then(|value| value.as_str())
|
|
303
|
+
.is_none_or(|session| session.is_empty());
|
|
304
|
+
let is_running = agent
|
|
305
|
+
.get("status")
|
|
306
|
+
.and_then(|value| value.as_str())
|
|
307
|
+
.is_some_and(|status| status == "running");
|
|
308
|
+
let has_live_pane_binding = agent
|
|
309
|
+
.get("pane_id")
|
|
310
|
+
.and_then(|value| value.as_str())
|
|
311
|
+
.is_some_and(|pane| !pane.is_empty());
|
|
312
|
+
let has_interaction_marker = agent
|
|
313
|
+
.get("first_send_at")
|
|
314
|
+
.and_then(|value| value.as_str())
|
|
315
|
+
.is_some_and(|value| !value.is_empty());
|
|
316
|
+
missing_session_id && is_running && (has_live_pane_binding || has_interaction_marker)
|
|
317
|
+
})
|
|
318
|
+
.collect::<Vec<_>>();
|
|
319
|
+
missing.sort();
|
|
320
|
+
missing
|
|
321
|
+
}
|
|
163
322
|
/// Tools list off an agent's runtime state entry (`tools: [...]`). Restart paths
|
|
164
323
|
/// don't have the full spec object, only the runtime state — so they read tools from
|
|
165
324
|
/// the state row, falling back to an empty list. Contract C requires the worker
|