@team-agent/installer 0.5.66 → 0.5.68

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.
Files changed (164) hide show
  1. package/Cargo.lock +8 -1
  2. package/Cargo.toml +1 -1
  3. package/crates/team-agent/Cargo.toml +1 -0
  4. package/crates/team-agent/src/cli/adapters.rs +5 -0
  5. package/crates/team-agent/src/cli/diagnose.rs +82 -0
  6. package/crates/team-agent/src/cli/emit.rs +3 -3
  7. package/crates/team-agent/src/cli/grok_slot.rs +299 -0
  8. package/crates/team-agent/src/cli/leader.rs +21 -7
  9. package/crates/team-agent/src/cli/leaders.rs +125 -1
  10. package/crates/team-agent/src/cli/mod.rs +20 -0
  11. package/crates/team-agent/src/cli/send/presentation.rs +7 -1
  12. package/crates/team-agent/src/cli/spec.rs +2 -0
  13. package/crates/team-agent/src/cli/status_port/compact.rs +28 -0
  14. package/crates/team-agent/src/cli/status_port/snapshot.rs +25 -1
  15. package/crates/team-agent/src/cli/tests/base.rs +26 -3
  16. package/crates/team-agent/src/cli/tests/missing_subcommands.rs +129 -19
  17. package/crates/team-agent/src/cli/tests/shutdown_kill_plan.rs +387 -6
  18. package/crates/team-agent/src/cli/tests/status_send.rs +79 -10
  19. package/crates/team-agent/src/communication_mode/mod.rs +6 -4
  20. package/crates/team-agent/src/compiler.rs +59 -0
  21. package/crates/team-agent/src/coordinator/backoff.rs +55 -0
  22. package/crates/team-agent/src/coordinator/conpty_shim.rs +82 -0
  23. package/crates/team-agent/src/coordinator/health.rs +173 -0
  24. package/crates/team-agent/src/coordinator/mod.rs +31 -0
  25. package/crates/team-agent/src/coordinator/orphan.rs +31 -0
  26. package/crates/team-agent/src/coordinator/runtime_detectors.rs +30 -0
  27. package/crates/team-agent/src/coordinator/runtime_observation.rs +25 -0
  28. package/crates/team-agent/src/coordinator/steps/abnormal.rs +59 -0
  29. package/crates/team-agent/src/coordinator/steps/delivery.rs +10 -0
  30. package/crates/team-agent/src/coordinator/steps/health_sync.rs +10 -0
  31. package/crates/team-agent/src/coordinator/steps/mod.rs +22 -0
  32. package/crates/team-agent/src/coordinator/steps/persist.rs +10 -0
  33. package/crates/team-agent/src/coordinator/steps/runtime_prompts.rs +10 -0
  34. package/crates/team-agent/src/coordinator/steps/session_gate.rs +10 -0
  35. package/crates/team-agent/src/coordinator/tick.rs +133 -22
  36. package/crates/team-agent/src/coordinator/types.rs +49 -0
  37. package/crates/team-agent/src/db/message_store.rs +39 -5
  38. package/crates/team-agent/src/layout/worker_env.rs +20 -3
  39. package/crates/team-agent/src/layout/worker_window_helpers.rs +2 -0
  40. package/crates/team-agent/src/leader/provider_attribution.rs +53 -0
  41. package/crates/team-agent/src/leader/registry.rs +65 -0
  42. package/crates/team-agent/src/leader/start.rs +920 -63
  43. package/crates/team-agent/src/lifecycle/display.rs +56 -0
  44. package/crates/team-agent/src/lifecycle/helpers.rs +57 -0
  45. package/crates/team-agent/src/lifecycle/launch/add_agent.rs +332 -12
  46. package/crates/team-agent/src/lifecycle/launch/add_agent_state.rs +62 -0
  47. package/crates/team-agent/src/lifecycle/launch/agent_state.rs +59 -0
  48. package/crates/team-agent/src/lifecycle/launch/clone_agent.rs +101 -11
  49. package/crates/team-agent/src/lifecycle/launch/cursor_create_chat.rs +229 -0
  50. package/crates/team-agent/src/lifecycle/launch/cursor_mcp.rs +332 -0
  51. package/crates/team-agent/src/lifecycle/launch/fork_agent.rs +204 -456
  52. package/crates/team-agent/src/lifecycle/launch/fork_entry.rs +24 -0
  53. package/crates/team-agent/src/lifecycle/launch/grok_per_seat.rs +260 -0
  54. package/crates/team-agent/src/lifecycle/launch/identity.rs +146 -0
  55. package/crates/team-agent/src/lifecycle/launch/layout.rs +60 -0
  56. package/crates/team-agent/src/lifecycle/launch/leader_context.rs +112 -0
  57. package/crates/team-agent/src/lifecycle/launch/mcp_config.rs +530 -0
  58. package/crates/team-agent/src/lifecycle/launch/ownership.rs +40 -0
  59. package/crates/team-agent/src/lifecycle/launch/plan.rs +31 -0
  60. package/crates/team-agent/src/lifecycle/launch/quick_start.rs +66 -0
  61. package/crates/team-agent/src/lifecycle/launch/quick_start_transport.rs +78 -0
  62. package/crates/team-agent/src/lifecycle/launch/readiness.rs +85 -38
  63. package/crates/team-agent/src/lifecycle/launch/role_source.rs +44 -44
  64. package/crates/team-agent/src/lifecycle/launch/spawn.rs +62 -2
  65. package/crates/team-agent/src/lifecycle/launch/spec_state.rs +139 -0
  66. package/crates/team-agent/src/lifecycle/launch/state_projection.rs +109 -0
  67. package/crates/team-agent/src/lifecycle/launch/worker_env.rs +214 -1
  68. package/crates/team-agent/src/lifecycle/launch.rs +99 -29
  69. package/crates/team-agent/src/lifecycle/lock.rs +42 -1
  70. package/crates/team-agent/src/lifecycle/mod.rs +11 -0
  71. package/crates/team-agent/src/lifecycle/pane_input_lock.rs +161 -0
  72. package/crates/team-agent/src/lifecycle/profile_launch.rs +98 -0
  73. package/crates/team-agent/src/lifecycle/profile_smoke.rs +51 -1
  74. package/crates/team-agent/src/lifecycle/restart/agent.rs +124 -0
  75. package/crates/team-agent/src/lifecycle/restart/common.rs +369 -11
  76. package/crates/team-agent/src/lifecycle/restart/orchestrator.rs +27 -0
  77. package/crates/team-agent/src/lifecycle/restart/preflight.rs +25 -0
  78. package/crates/team-agent/src/lifecycle/restart/rebuild.rs +106 -0
  79. package/crates/team-agent/src/lifecycle/restart/remove.rs +196 -5
  80. package/crates/team-agent/src/lifecycle/restart/selection.rs +72 -0
  81. package/crates/team-agent/src/lifecycle/restart/team_state.rs +22 -0
  82. package/crates/team-agent/src/lifecycle/restart.rs +29 -0
  83. package/crates/team-agent/src/lifecycle/tests/agent_ops.rs +2 -3
  84. package/crates/team-agent/src/lifecycle/tests/clone_agent_preserves_source_tools.rs +309 -0
  85. package/crates/team-agent/src/lifecycle/tests/clone_fork_copilot_perms_red.rs +150 -157
  86. package/crates/team-agent/src/lifecycle/tests/copilot_provider_red.rs +2 -2
  87. package/crates/team-agent/src/lifecycle/tests/core.rs +4 -0
  88. package/crates/team-agent/src/lifecycle/tests/cursor_mcp_overlay.rs +287 -0
  89. package/crates/team-agent/src/lifecycle/tests/cursor_require_explicit_model_red.rs +229 -0
  90. package/crates/team-agent/src/lifecycle/tests/cursor_restart_resume_red.rs +353 -0
  91. package/crates/team-agent/src/lifecycle/tests/g1_silent_faces.rs +784 -0
  92. package/crates/team-agent/src/lifecycle/tests/gate_fixtures.rs +562 -0
  93. package/crates/team-agent/src/lifecycle/tests/grok_effort_argv_red.rs +239 -0
  94. package/crates/team-agent/src/lifecycle/tests/grok_mcp_overlay_red.rs +498 -0
  95. package/crates/team-agent/src/lifecycle/tests/grok_require_explicit_model_red.rs +250 -0
  96. package/crates/team-agent/src/lifecycle/tests/grok_restart_resume_red.rs +293 -0
  97. package/crates/team-agent/src/lifecycle/tests/lane_ops.rs +63 -38
  98. package/crates/team-agent/src/lifecycle/tests/launch_spawn.rs +3 -2
  99. package/crates/team-agent/src/lifecycle/tests/lifecycle_rollback_red.rs +16 -8
  100. package/crates/team-agent/src/lifecycle/tests/mcp_tool_name_format_red.rs +69 -0
  101. package/crates/team-agent/src/lifecycle/tests/phase_b_contracts.rs +20 -19
  102. package/crates/team-agent/src/lifecycle/tests/phase_golden.rs +97 -61
  103. package/crates/team-agent/src/lifecycle/tests/restart_rebind_hotfix_252_red.rs +2 -0
  104. package/crates/team-agent/src/lifecycle/tests/startup_latency_contract.rs +40 -7
  105. package/crates/team-agent/src/lifecycle/tests/test_isolation_escape_contract.rs +59 -1
  106. package/crates/team-agent/src/lifecycle/tests/worker_spawn_env_red.rs +69 -26
  107. package/crates/team-agent/src/lifecycle/tests.rs +23 -13
  108. package/crates/team-agent/src/lifecycle/types.rs +61 -0
  109. package/crates/team-agent/src/lifecycle/worker_command_context.rs +85 -11
  110. package/crates/team-agent/src/mcp_server/tests/scoped.rs +100 -1
  111. package/crates/team-agent/src/mcp_server/tests.rs +196 -2
  112. package/crates/team-agent/src/messaging/delivery.rs +405 -40
  113. package/crates/team-agent/src/messaging/helpers.rs +2 -0
  114. package/crates/team-agent/src/messaging/leader_receiver.rs +53 -1
  115. package/crates/team-agent/src/messaging/results.rs +4 -0
  116. package/crates/team-agent/src/messaging/send.rs +34 -0
  117. package/crates/team-agent/src/messaging/tests/dup_inject.rs +406 -0
  118. package/crates/team-agent/src/messaging/tests/e23.rs +9 -0
  119. package/crates/team-agent/src/messaging/tests/leader_inject_acceptance.rs +66 -0
  120. package/crates/team-agent/src/messaging/tests/mod.rs +1 -0
  121. package/crates/team-agent/src/messaging/types.rs +4 -1
  122. package/crates/team-agent/src/model/enums.rs +14 -5
  123. package/crates/team-agent/src/model/permissions.rs +3 -0
  124. package/crates/team-agent/src/os_probe.rs +73 -2
  125. package/crates/team-agent/src/provider/adapter.rs +231 -4
  126. package/crates/team-agent/src/provider/adapters/cursor_agent.rs +86 -0
  127. package/crates/team-agent/src/provider/adapters/grok.rs +157 -0
  128. package/crates/team-agent/src/provider/adapters/mod.rs +2 -0
  129. package/crates/team-agent/src/provider/bypass_flags.rs +46 -2
  130. package/crates/team-agent/src/provider/classify.rs +4 -2
  131. package/crates/team-agent/src/provider/faults.rs +2 -1
  132. package/crates/team-agent/src/provider/mod.rs +11 -0
  133. package/crates/team-agent/src/provider/session/capture.rs +158 -20
  134. package/crates/team-agent/src/provider/session/context_fork/claude.rs +38 -1
  135. package/crates/team-agent/src/provider/session/context_fork/codex.rs +72 -1
  136. package/crates/team-agent/src/provider/session/context_fork/outcome.rs +57 -1
  137. package/crates/team-agent/src/provider/session/context_fork.rs +77 -3
  138. package/crates/team-agent/src/provider/session/mod.rs +18 -0
  139. package/crates/team-agent/src/provider/session/resume.rs +57 -0
  140. package/crates/team-agent/src/provider/session_scan/claude.rs +125 -1
  141. package/crates/team-agent/src/provider/session_scan/codex.rs +94 -1
  142. package/crates/team-agent/src/provider/session_scan/common.rs +204 -2
  143. package/crates/team-agent/src/provider/session_scan/copilot.rs +33 -1
  144. package/crates/team-agent/src/provider/session_scan/cursor.rs +538 -0
  145. package/crates/team-agent/src/provider/session_scan/grok.rs +288 -0
  146. package/crates/team-agent/src/provider/session_scan.rs +8 -0
  147. package/crates/team-agent/src/provider/submit_now.rs +94 -0
  148. package/crates/team-agent/src/provider/tests/adapter.rs +304 -0
  149. package/crates/team-agent/src/provider/types.rs +4 -2
  150. package/crates/team-agent/src/provider/wire.rs +23 -1
  151. package/crates/team-agent/src/state/persist.rs +301 -8
  152. package/crates/team-agent/src/state/repository.rs +5 -0
  153. package/crates/team-agent/src/tmux_backend/tests.rs +1737 -42
  154. package/crates/team-agent/src/tmux_backend.rs +1064 -75
  155. package/crates/team-agent/src/transport/tests/wire.rs +28 -2
  156. package/crates/team-agent/src/transport.rs +248 -1
  157. package/npm/install.mjs +129 -73
  158. package/package.json +4 -4
  159. package/skills/team-agent/SKILL.md +33 -238
  160. package/skills/team-agent/command-coverage.json +31 -0
  161. package/crates/team-agent/src/lifecycle/launch/fork_agent/completion.rs +0 -59
  162. package/crates/team-agent/src/lifecycle/launch/fork_finalize.rs +0 -488
  163. package/crates/team-agent/src/lifecycle/launch/fork_pending.rs +0 -109
  164. package/crates/team-agent/src/lifecycle/launch/fork_state.rs +0 -447
@@ -1,447 +0,0 @@
1
- use std::collections::{BTreeMap, BTreeSet};
2
- use std::path::{Path, PathBuf};
3
- use std::process::Command;
4
-
5
- use crate::lifecycle::*;
6
- use crate::model::enums::{AuthMode, DisplayBackend, PaneLiveness, Provider, ProviderEffort};
7
- use crate::model::ids::AgentId;
8
- use crate::model::permissions::{self, AgentPermissionInput};
9
- use crate::model::yaml::{self, Value};
10
- use crate::state::persist::load_runtime_state;
11
- use crate::transport::{PaneId, SessionName, Target, Transport, WindowName};
12
-
13
- use crate::lifecycle::lock::{acquire_agent_lifecycle_lock, LifecycleLockRequest};
14
-
15
- use super::*;
16
-
17
- #[path = "fork_pending.rs"]
18
- mod fork_pending;
19
- pub(super) use fork_pending::upsert_pending_forked_agent_state;
20
-
21
- pub(super) fn reserve_forked_agent_state(
22
- state: &mut serde_json::Value,
23
- source_agent_id: &AgentId,
24
- as_agent_id: &AgentId,
25
- spec_agent: &Value,
26
- dynamic_role_file: &Path,
27
- ) -> Result<(), LifecycleError> {
28
- let agents = state
29
- .as_object_mut()
30
- .ok_or_else(|| {
31
- LifecycleError::StatePersist("runtime state root is not an object".to_string())
32
- })?
33
- .entry("agents".to_string())
34
- .or_insert_with(|| serde_json::json!({}));
35
- let agent_map = agents.as_object_mut().ok_or_else(|| {
36
- LifecycleError::StatePersist("runtime state agents is not an object".to_string())
37
- })?;
38
- if agent_map.contains_key(as_agent_id.as_str()) {
39
- return Err(LifecycleError::RequirementUnmet(format!(
40
- "agent id already exists: {as_agent_id}"
41
- )));
42
- }
43
- crate::lifecycle::restart::remove::clear_agent_retirement_in_state(state, as_agent_id);
44
- let agents = state
45
- .get_mut("agents")
46
- .and_then(serde_json::Value::as_object_mut)
47
- .ok_or_else(|| {
48
- LifecycleError::StatePersist("runtime state agents is not an object".to_string())
49
- })?;
50
- let mut entry = serde_json::json!({
51
- "status": "forking",
52
- "agent_id": as_agent_id.as_str(),
53
- "window": as_agent_id.as_str(),
54
- "forked_from": source_agent_id.as_str(),
55
- "dynamic_role_file": dynamic_role_file.to_string_lossy().to_string(),
56
- "role_source_ownership": "managed",
57
- });
58
- if let Some(object) = entry.as_object_mut() {
59
- for key in [
60
- "provider",
61
- "auth_mode",
62
- "model",
63
- "profile",
64
- "role",
65
- "effort",
66
- ] {
67
- if let Some(value) = spec_agent.get(key) {
68
- object.insert(key.to_string(), yaml_value_to_json(value));
69
- }
70
- }
71
- }
72
- agents.insert(as_agent_id.as_str().to_string(), entry);
73
- Ok(())
74
- }
75
-
76
- pub(super) fn maybe_fail_fork_after_spawn(step: &str) -> Result<(), LifecycleError> {
77
- let Ok(reason) = std::env::var("TEAM_AGENT_TEST_FAIL_FORK_AFTER_SPAWN") else {
78
- return Ok(());
79
- };
80
- if reason.is_empty() {
81
- return Ok(());
82
- }
83
- let should_fail = reason == step || (step == "start_coordinator" && reason == "coordinator");
84
- if !should_fail {
85
- return Ok(());
86
- }
87
- Err(LifecycleError::StatePersist(format!(
88
- "injected fork failure after spawn: {reason}"
89
- )))
90
- }
91
-
92
- pub(super) fn cleanup_fork_mcp_artifacts(
93
- workspace: &Path,
94
- agent_id: &AgentId,
95
- mcp_config_path: &Path,
96
- profile_launch: &crate::provider::ProviderProfileLaunch,
97
- ) {
98
- let _ = std::fs::remove_file(mcp_config_path);
99
- let _ = std::fs::remove_file(
100
- workspace
101
- .join(".team/runtime/provider-env")
102
- .join(format!("{}.env", agent_id.as_str())),
103
- );
104
- let _ = std::fs::remove_dir_all(
105
- workspace
106
- .join(".team/runtime/copilot-instructions")
107
- .join(agent_id.as_str()),
108
- );
109
- if let Some(config_dir) = profile_launch.claude_config_dir.as_ref() {
110
- let _ = std::fs::remove_dir_all(config_dir.parent().unwrap_or(config_dir));
111
- }
112
- }
113
-
114
- pub(super) fn leader_id_matches(spec: &Value, agent_id: &AgentId) -> bool {
115
- spec.get("leader")
116
- .and_then(|v| v.get("id"))
117
- .and_then(Value::as_str)
118
- .map(|id| id == agent_id.as_str())
119
- .unwrap_or(false)
120
- }
121
-
122
- pub(super) fn find_spec_agent<'a>(spec: &'a Value, agent_id: &AgentId) -> Option<&'a Value> {
123
- let leader_is_agent = spec
124
- .get("leader")
125
- .and_then(|v| v.get("id"))
126
- .and_then(Value::as_str)
127
- .map(|id| id == agent_id.as_str())
128
- .unwrap_or(false);
129
- if leader_is_agent {
130
- return None;
131
- }
132
- spec.get("agents")?.as_list()?.iter().find(|agent| {
133
- agent
134
- .get("id")
135
- .and_then(Value::as_str)
136
- .map(|id| id == agent_id.as_str())
137
- .unwrap_or(false)
138
- })
139
- }
140
-
141
- pub(super) fn fork_spec_agent<'a>(spec: &'a Value, agent_id: &AgentId) -> Option<&'a Value> {
142
- find_spec_agent(spec, agent_id)
143
- }
144
-
145
- pub(super) fn fork_source_tuple(
146
- state: &serde_json::Value,
147
- source_agent_id: &AgentId,
148
- ) -> Result<(crate::provider::SessionId, PathBuf), LifecycleError> {
149
- let source = state
150
- .get("agents")
151
- .and_then(|agents| agents.get(source_agent_id.as_str()))
152
- .ok_or_else(|| {
153
- LifecycleError::Provider(format!("unknown worker agent id: {source_agent_id}"))
154
- })?;
155
- let field = |name: &str| {
156
- source
157
- .get(name)
158
- .and_then(serde_json::Value::as_str)
159
- .filter(|value| !value.is_empty())
160
- };
161
- let (Some(session_id), Some(backing)) = (field("session_id"), field("rollout_path")) else {
162
- return Err(LifecycleError::Provider(format!(
163
- "cannot fork {source_agent_id}: source session backing is missing or incomplete \
164
- (session_id+rollout_path+captured_at+captured_via required)"
165
- )));
166
- };
167
- if field("captured_at").is_none() || field("captured_via").is_none() {
168
- return Err(LifecycleError::Provider(format!(
169
- "cannot fork {source_agent_id}: source session backing is missing or incomplete \
170
- (session_id+rollout_path+captured_at+captured_via required)"
171
- )));
172
- }
173
- let backing = PathBuf::from(backing);
174
- if !backing.is_file() {
175
- return Err(LifecycleError::Provider(format!(
176
- "cannot fork {source_agent_id}: source session backing is not readable: {}",
177
- backing.display()
178
- )));
179
- }
180
- Ok((
181
- crate::provider::SessionId::new(session_id.to_string()),
182
- backing,
183
- ))
184
- }
185
-
186
- pub(super) fn append_forked_agent(
187
- spec: &Value,
188
- source_agent: &Value,
189
- source_agent_id: &AgentId,
190
- as_agent_id: &AgentId,
191
- label: Option<&str>,
192
- ) -> Result<Value, LifecycleError> {
193
- let mut new_agent = source_agent.clone();
194
- set_yaml_map_value(
195
- &mut new_agent,
196
- "id",
197
- Value::Str(as_agent_id.as_str().to_string()),
198
- )?;
199
- // golden operations.py:315 `str(label or new_agent.get("role") or as_agent_id)` — Python `or`
200
- // falsiness: an EMPTY-string label/role is falsy and falls through to the next tier.
201
- // The label IS the forked agent's new role (it feeds the identity prompt — B2 family).
202
- let role = label
203
- .filter(|s| !s.is_empty())
204
- .or_else(|| {
205
- new_agent
206
- .get("role")
207
- .and_then(Value::as_str)
208
- .filter(|s| !s.is_empty())
209
- })
210
- .unwrap_or_else(|| as_agent_id.as_str())
211
- .to_string();
212
- set_yaml_map_value(&mut new_agent, "role", Value::Str(role.clone()))?;
213
- set_yaml_map_value(
214
- &mut new_agent,
215
- "forked_from",
216
- Value::Str(source_agent_id.as_str().to_string()),
217
- )?;
218
- set_yaml_map_value(
219
- &mut new_agent,
220
- "preferred_for",
221
- Value::List(vec![
222
- Value::Str(as_agent_id.as_str().to_string()),
223
- Value::Str(role),
224
- ]),
225
- )?;
226
-
227
- let Value::Map(pairs) = spec else {
228
- return Err(LifecycleError::Compile(
229
- "spec root is not a map".to_string(),
230
- ));
231
- };
232
- let mut out = Vec::new();
233
- for (key, value) in pairs {
234
- if key == "agents" {
235
- let mut agents = value
236
- .as_list()
237
- .map(|items| items.to_vec())
238
- .unwrap_or_default();
239
- agents.push(new_agent.clone());
240
- out.push((key.clone(), Value::List(agents)));
241
- } else if key == "runtime" {
242
- out.push((key.clone(), runtime_with_startup_agent(value, as_agent_id)));
243
- } else {
244
- out.push((key.clone(), value.clone()));
245
- }
246
- }
247
- Ok(Value::Map(out))
248
- }
249
-
250
- pub(super) fn set_yaml_map_value(
251
- value: &mut Value,
252
- key: &str,
253
- next: Value,
254
- ) -> Result<(), LifecycleError> {
255
- let Value::Map(pairs) = value else {
256
- return Err(LifecycleError::Compile(
257
- "agent entry is not a map".to_string(),
258
- ));
259
- };
260
- if let Some((_, existing)) = pairs.iter_mut().find(|(k, _)| k == key) {
261
- *existing = next;
262
- } else {
263
- pairs.push((key.to_string(), next));
264
- }
265
- Ok(())
266
- }
267
-
268
- pub(super) fn runtime_with_startup_agent(runtime: &Value, agent_id: &AgentId) -> Value {
269
- let Value::Map(pairs) = runtime else {
270
- return runtime.clone();
271
- };
272
- let mut out = Vec::new();
273
- let mut saw_startup = false;
274
- for (key, value) in pairs {
275
- if key == "startup_order" {
276
- saw_startup = true;
277
- let mut order = value
278
- .as_list()
279
- .map(|items| items.to_vec())
280
- .unwrap_or_default();
281
- let already_present = order.iter().any(|item| {
282
- item.as_str()
283
- .map(|id| id == agent_id.as_str())
284
- .unwrap_or(false)
285
- });
286
- if !already_present {
287
- order.push(Value::Str(agent_id.as_str().to_string()));
288
- }
289
- out.push((key.clone(), Value::List(order)));
290
- } else {
291
- out.push((key.clone(), value.clone()));
292
- }
293
- }
294
- if !saw_startup {
295
- out.push((
296
- "startup_order".to_string(),
297
- Value::List(vec![Value::Str(agent_id.as_str().to_string())]),
298
- ));
299
- }
300
- Value::Map(out)
301
- }
302
-
303
- pub(super) fn upsert_forked_agent_state(
304
- state: &mut serde_json::Value,
305
- source_agent_id: &AgentId,
306
- as_agent_id: &AgentId,
307
- spec_agent: &Value,
308
- safety: &DangerousApproval,
309
- plan: &crate::provider::CommandPlan,
310
- profile_launch: &crate::provider::ProviderProfileLaunch,
311
- spawn: &crate::transport::SpawnResult,
312
- spawn_cwd: &Path,
313
- profile_dir: Option<&Path>,
314
- dynamic_role_file: &Path,
315
- context_proof: &crate::provider::session::ContextForkProof,
316
- spawned_at: &str,
317
- spawn_epoch: u64,
318
- ) -> Result<(), LifecycleError> {
319
- if !state.is_object() {
320
- *state = serde_json::json!({});
321
- }
322
- let Some(root) = state.as_object_mut() else {
323
- return Err(LifecycleError::StatePersist(
324
- "runtime state root is not an object".to_string(),
325
- ));
326
- };
327
- let agents = root
328
- .entry("agents".to_string())
329
- .or_insert_with(|| serde_json::json!({}));
330
- if !agents.is_object() {
331
- *agents = serde_json::json!({});
332
- }
333
- let Some(agent_map) = agents.as_object_mut() else {
334
- return Err(LifecycleError::StatePersist(
335
- "runtime state agents is not an object".to_string(),
336
- ));
337
- };
338
- let provider = spec_agent
339
- .get("provider")
340
- .and_then(Value::as_str)
341
- .unwrap_or("codex");
342
- let mut entry = serde_json::Map::new();
343
- entry.insert("status".to_string(), serde_json::json!("running"));
344
- entry.insert("provider".to_string(), serde_json::json!(provider));
345
- entry.insert(
346
- "agent_id".to_string(),
347
- serde_json::json!(as_agent_id.as_str()),
348
- );
349
- entry.insert(
350
- "window".to_string(),
351
- serde_json::json!(as_agent_id.as_str()),
352
- );
353
- entry.insert(
354
- "forked_from".to_string(),
355
- serde_json::json!(source_agent_id.as_str()),
356
- );
357
- entry.insert(
358
- "fork_source_session_id".to_string(),
359
- serde_json::json!(context_proof.source_session_id.as_str()),
360
- );
361
- entry.insert("capture_state".to_string(), serde_json::json!("captured"));
362
- entry.insert(
363
- "dynamic_role_file".to_string(),
364
- serde_json::json!(dynamic_role_file.to_string_lossy().to_string()),
365
- );
366
- entry.insert(
367
- "role_source_ownership".to_string(),
368
- serde_json::json!("managed"),
369
- );
370
- entry.insert(
371
- "spawn_cwd".to_string(),
372
- serde_json::json!(spawn_cwd.to_string_lossy().to_string()),
373
- );
374
- entry.insert(
375
- "pane_id".to_string(),
376
- serde_json::json!(spawn.pane_id.as_str()),
377
- );
378
- entry.insert("spawned_at".to_string(), serde_json::json!(spawned_at));
379
- entry.insert("spawn_epoch".to_string(), serde_json::json!(spawn_epoch));
380
- if let Some(pid) = spawn.child_pid {
381
- entry.insert("pane_pid".to_string(), serde_json::json!(pid));
382
- }
383
- for key in [
384
- "auth_mode",
385
- "model",
386
- "model_source",
387
- "profile",
388
- "_profile_dir",
389
- "role",
390
- // 0.4.x provider effort MVP step 8: fork inherits compiled effort.
391
- "effort",
392
- ] {
393
- if let Some(value) = spec_agent.get(key) {
394
- entry.insert(key.to_string(), yaml_value_to_json(value));
395
- }
396
- }
397
- if spec_agent.get("profile").is_some() && !entry.contains_key("_profile_dir") {
398
- if let Some(profile_dir) = profile_dir {
399
- entry.insert(
400
- "_profile_dir".to_string(),
401
- serde_json::json!(profile_dir.to_string_lossy().to_string()),
402
- );
403
- }
404
- }
405
- entry.insert(
406
- "session_id".to_string(),
407
- serde_json::json!(context_proof.new_session_id.as_str()),
408
- );
409
- entry.insert(
410
- "rollout_path".to_string(),
411
- serde_json::json!(context_proof.backing_path.to_string_lossy().to_string()),
412
- );
413
- entry.insert(
414
- "captured_at".to_string(),
415
- serde_json::json!(chrono::Utc::now().to_rfc3339()),
416
- );
417
- entry.insert(
418
- "captured_via".to_string(),
419
- serde_json::json!(context_proof.captured_via),
420
- );
421
- entry.insert(
422
- "attribution_confidence".to_string(),
423
- serde_json::json!(context_proof.attribution_confidence),
424
- );
425
- if let Some(root) = context_proof.managed_backing_root.as_ref() {
426
- entry.insert(
427
- "session_backing_root".to_string(),
428
- serde_json::json!(root.to_string_lossy().to_string()),
429
- );
430
- entry.insert(
431
- "session_backing_ownership".to_string(),
432
- serde_json::json!("managed"),
433
- );
434
- }
435
- persist_command_plan_state(&mut entry, plan, profile_launch);
436
- agent_map.insert(
437
- as_agent_id.as_str().to_string(),
438
- serde_json::Value::Object(entry),
439
- );
440
- if let Some(entry) = agent_map
441
- .get_mut(as_agent_id.as_str())
442
- .and_then(serde_json::Value::as_object_mut)
443
- {
444
- persist_effective_approval_policy(entry, safety);
445
- }
446
- Ok(())
447
- }