@team-agent/installer 0.3.2 → 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 +196 -19
- package/crates/team-agent/src/cli/diagnose.rs +144 -10
- package/crates/team-agent/src/cli/emit.rs +286 -52
- package/crates/team-agent/src/cli/leader.rs +37 -8
- package/crates/team-agent/src/cli/mod.rs +799 -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.rs +15 -5
- 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/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 +818 -116
- 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 +177 -83
- package/crates/team-agent/src/lifecycle/restart/rebuild.rs +443 -9
- 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/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 +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 +97 -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(
|
|
@@ -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")
|
|
@@ -163,68 +219,106 @@ pub(super) fn agent_rollout_path(agent: &serde_json::Value) -> Option<RolloutPat
|
|
|
163
219
|
pub(crate) fn refresh_missing_provider_sessions(
|
|
164
220
|
state: &mut serde_json::Value,
|
|
165
221
|
) -> 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
|
-
|
|
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())?;
|
|
224
281
|
}
|
|
225
|
-
|
|
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())
|
|
226
291
|
}
|
|
227
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
|
+
}
|
|
228
322
|
/// Tools list off an agent's runtime state entry (`tools: [...]`). Restart paths
|
|
229
323
|
/// don't have the full spec object, only the runtime state — so they read tools from
|
|
230
324
|
/// the state row, falling back to an empty list. Contract C requires the worker
|