@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,5 +1,22 @@
1
1
  {
2
2
  "schema_version": "team-agent-skill-command-coverage-v3",
3
+ "authority": {
4
+ "kind": "normative_handbook_plus_live_help",
5
+ "handbook": {
6
+ "path": "docs/reference/team-agent-operator.md",
7
+ "start_marker": "<!-- command-coverage:normative-start -->",
8
+ "end_marker": "<!-- command-coverage:normative-end -->"
9
+ },
10
+ "live_help": {
11
+ "argv": ["team-agent", "--help"],
12
+ "source": "exact_test_binary",
13
+ "root_command_policy": "canonical_handbook_commands_may_use_compatibility_forms"
14
+ },
15
+ "compact_skill_smoke": {
16
+ "path": "skills/team-agent/SKILL.md",
17
+ "policy": "quick_start_and_send_are_subset_only"
18
+ }
19
+ },
3
20
  "commands": [
4
21
  {
5
22
  "command": "team-agent add-agent <agent> --role-file <file>",
@@ -312,6 +329,20 @@
312
329
  "owner": "runtime-owner:messaging",
313
330
  "plan": "Execute the exact argv in an isolated coder fixture and bind assertions to blocked-before-watch or the same-message result closure."
314
331
  },
332
+ {
333
+ "command": "team-agent send TO MESSAGE",
334
+ "bucket": "declared_gap",
335
+ "covered": false,
336
+ "owner": "runtime-owner:messaging",
337
+ "plan": "Execute the compact-skill positional send smoke with representative target and message tokens, then bind delivery truth."
338
+ },
339
+ {
340
+ "command": "team-agent send reviewer \"...\"",
341
+ "bucket": "declared_gap",
342
+ "covered": false,
343
+ "owner": "runtime-owner:messaging",
344
+ "plan": "Execute the compact-skill reviewer send smoke and bind recipient and delivery fields."
345
+ },
315
346
  {
316
347
  "command": "team-agent send reviewer \"Review this change\"",
317
348
  "bucket": "declared_gap",
@@ -1,59 +0,0 @@
1
- //!
2
- use super::super::*;
3
-
4
- pub(super) struct CompleteForkInput<'a> {
5
- pub workspace: &'a Path,
6
- pub team_key: &'a str,
7
- pub agent_id: &'a AgentId,
8
- pub spawn: &'a crate::transport::SpawnResult,
9
- pub window: &'a WindowName,
10
- pub transport: &'a dyn Transport,
11
- pub session_name: &'a SessionName,
12
- pub mcp_config_path: &'a Path,
13
- pub profile_launch: &'a crate::provider::ProviderProfileLaunch,
14
- pub materialized_role: &'a mut MaterializedRole,
15
- pub claude_fork:
16
- &'a mut Option<crate::provider::adapters::claude_fork::ClaudeForkMaterialization>,
17
- pub copilot_fork:
18
- &'a mut Option<crate::provider::adapters::copilot_fork::CopilotForkMaterialization>,
19
- }
20
-
21
- pub(super) fn complete_fork(input: CompleteForkInput<'_>) -> Result<bool, LifecycleError> {
22
- if let Err(error) = verify_fork_registration(
23
- input.workspace,
24
- input.team_key,
25
- input.agent_id,
26
- input.spawn,
27
- input.window,
28
- ) {
29
- rollback_fork_after_spawn(
30
- input.workspace,
31
- input.transport,
32
- input.session_name,
33
- input.window,
34
- input.mcp_config_path,
35
- input.agent_id,
36
- input.profile_launch,
37
- input.team_key,
38
- );
39
- return Err(error);
40
- }
41
- let coordinator_started = start_fork_coordinator(ForkCoordinatorInput {
42
- workspace: input.workspace,
43
- team_key: input.team_key,
44
- agent_id: input.agent_id,
45
- transport: input.transport,
46
- session_name: input.session_name,
47
- window: input.window,
48
- mcp_config_path: input.mcp_config_path,
49
- profile_launch: input.profile_launch,
50
- })?;
51
- input.materialized_role.keep();
52
- if let Some(materialized) = input.claude_fork.as_mut() {
53
- materialized.keep();
54
- }
55
- if let Some(materialized) = input.copilot_fork.as_mut() {
56
- materialized.keep();
57
- }
58
- Ok(coordinator_started)
59
- }
@@ -1,488 +0,0 @@
1
- //!
2
- use super::*;
3
-
4
- pub(super) struct ForkPostSpawnInput<'a> {
5
- pub workspace: &'a Path,
6
- pub transport: &'a dyn Transport,
7
- pub session_name: &'a SessionName,
8
- pub window: &'a WindowName,
9
- pub mcp_config_path: &'a Path,
10
- pub agent_id: &'a AgentId,
11
- pub profile_launch: &'a crate::provider::ProviderProfileLaunch,
12
- pub team_key: &'a str,
13
- pub spawn: &'a crate::transport::SpawnResult,
14
- }
15
-
16
- pub(super) fn ensure_fork_spawn_live(input: ForkPostSpawnInput<'_>) -> Result<(), LifecycleError> {
17
- let rollback = || {
18
- rollback_fork_after_spawn(
19
- input.workspace,
20
- input.transport,
21
- input.session_name,
22
- input.window,
23
- input.mcp_config_path,
24
- input.agent_id,
25
- input.profile_launch,
26
- input.team_key,
27
- );
28
- };
29
- if !matches!(
30
- input.transport.liveness(&input.spawn.pane_id),
31
- Ok(PaneLiveness::Live)
32
- ) {
33
- rollback();
34
- return Err(LifecycleError::RequirementUnmet(format!(
35
- "fork process is not live after spawn: agent={} pane={}",
36
- input.agent_id,
37
- input.spawn.pane_id.as_str()
38
- )));
39
- }
40
- Ok(())
41
- }
42
-
43
- pub(super) fn prepare_claude_fork_backing(
44
- provider: Provider,
45
- plan: &crate::provider::CommandPlan,
46
- source_backing: &Path,
47
- source_session_id: &crate::provider::SessionId,
48
- source_agent_id: &AgentId,
49
- target_agent_id: &AgentId,
50
- ) -> Result<Option<crate::provider::adapters::claude_fork::ClaudeForkMaterialization>, LifecycleError>
51
- {
52
- if !matches!(provider, Provider::Claude | Provider::ClaudeCode) {
53
- return Ok(None);
54
- }
55
- let target_session_id = plan.expected_session_id.as_ref().ok_or_else(|| {
56
- LifecycleError::Provider("claude fork plan has no snapshot session id".to_string())
57
- })?;
58
- crate::provider::adapters::claude_fork::materialize_claude_fork(
59
- source_backing,
60
- source_session_id,
61
- target_session_id,
62
- source_agent_id.as_str(),
63
- target_agent_id.as_str(),
64
- )
65
- .map(Some)
66
- .map_err(|error| {
67
- LifecycleError::Provider(format!(
68
- "context_fork_unavailable: claude snapshot copy failed before spawn: {error}"
69
- ))
70
- })
71
- }
72
-
73
- pub(super) struct ForkCoordinatorInput<'a> {
74
- pub workspace: &'a Path,
75
- pub team_key: &'a str,
76
- pub agent_id: &'a AgentId,
77
- pub transport: &'a dyn Transport,
78
- pub session_name: &'a SessionName,
79
- pub window: &'a WindowName,
80
- pub mcp_config_path: &'a Path,
81
- pub profile_launch: &'a crate::provider::ProviderProfileLaunch,
82
- }
83
-
84
- pub(super) fn start_fork_coordinator(
85
- input: ForkCoordinatorInput<'_>,
86
- ) -> Result<bool, LifecycleError> {
87
- let rollback = || {
88
- rollback_fork_after_spawn(
89
- input.workspace,
90
- input.transport,
91
- input.session_name,
92
- input.window,
93
- input.mcp_config_path,
94
- input.agent_id,
95
- input.profile_launch,
96
- input.team_key,
97
- );
98
- };
99
- if let Err(error) = maybe_fail_fork_after_spawn("start_coordinator") {
100
- rollback();
101
- return Err(error);
102
- }
103
- crate::coordinator::start_coordinator(&crate::coordinator::WorkspacePath::new(
104
- input.workspace.to_path_buf(),
105
- ))
106
- .map(|report| report.ok)
107
- .map_err(|error| {
108
- rollback();
109
- LifecycleError::StatePersist(error.to_string())
110
- })
111
- }
112
-
113
- pub(super) struct ForkFinalizeInput<'a> {
114
- pub workspace: &'a Path,
115
- pub team_key: &'a str,
116
- pub source_agent_id: &'a AgentId,
117
- pub agent_id: &'a AgentId,
118
- pub spec_agent: &'a Value,
119
- pub safety: &'a DangerousApproval,
120
- pub plan: &'a crate::provider::CommandPlan,
121
- pub profile_launch: &'a crate::provider::ProviderProfileLaunch,
122
- pub spawn: &'a crate::transport::SpawnResult,
123
- pub profile_dir: &'a Path,
124
- pub dynamic_role_file: &'a Path,
125
- pub context_proof: &'a crate::provider::session::ContextForkProof,
126
- pub spawned_at: &'a str,
127
- pub spawn_epoch: u64,
128
- }
129
-
130
- #[derive(Debug, Clone, PartialEq, Eq)]
131
- pub(crate) struct ContextForkFinalized {
132
- source_agent_id: String,
133
- agent_id: String,
134
- session_id: String,
135
- rollout_path: String,
136
- captured_via: String,
137
- attribution_confidence: String,
138
- }
139
-
140
- impl ContextForkFinalized {
141
- fn new(
142
- source_agent_id: &str,
143
- agent_id: &str,
144
- session_id: &str,
145
- rollout_path: &Path,
146
- captured_via: &str,
147
- attribution_confidence: &str,
148
- ) -> Self {
149
- Self {
150
- source_agent_id: source_agent_id.to_string(),
151
- agent_id: agent_id.to_string(),
152
- session_id: session_id.to_string(),
153
- rollout_path: rollout_path.to_string_lossy().to_string(),
154
- captured_via: captured_via.to_string(),
155
- attribution_confidence: attribution_confidence.to_string(),
156
- }
157
- }
158
-
159
- fn from_captured(
160
- source_agent_id: &str,
161
- agent_id: &str,
162
- captured: &crate::provider::CapturedSession,
163
- ) -> Option<Self> {
164
- let captured_via = serde_json::to_value(captured.captured_via).ok()?;
165
- let attribution_confidence = serde_json::to_value(captured.attribution_confidence).ok()?;
166
- Some(Self::new(
167
- source_agent_id,
168
- agent_id,
169
- captured.session_id.as_ref()?.as_str(),
170
- captured.rollout_path.as_ref()?.as_path(),
171
- captured_via.as_str()?,
172
- attribution_confidence.as_str()?,
173
- ))
174
- }
175
-
176
- pub(crate) fn write_audit(
177
- &self,
178
- event_log: &crate::event_log::EventLog,
179
- ) -> Result<(), crate::event_log::EventLogError> {
180
- event_log
181
- .write(
182
- crate::lifecycle::types::event_names::CONTEXT_FORK,
183
- serde_json::json!({
184
- "source_agent_id": self.source_agent_id,
185
- "agent_id": self.agent_id,
186
- "session_id": self.session_id,
187
- "rollout_path": self.rollout_path,
188
- "captured_via": self.captured_via,
189
- "attribution_confidence": self.attribution_confidence,
190
- }),
191
- )
192
- .map(|_| ())
193
- }
194
- }
195
-
196
- pub(super) fn finalize_fork_state(
197
- input: ForkFinalizeInput<'_>,
198
- ) -> Result<ContextForkFinalized, LifecycleError> {
199
- let _lock = acquire_agent_lifecycle_lock(LifecycleLockRequest {
200
- workspace: input.workspace,
201
- operation: "fork-agent-finalize",
202
- team: Some(input.team_key),
203
- agent_id: Some(input.agent_id),
204
- })?;
205
- let mut next_state = crate::state::selector::resolve_active_team(
206
- input.workspace,
207
- Some(input.team_key),
208
- crate::state::selector::SelectorMode::RequireSpec,
209
- )
210
- .map_err(|error| LifecycleError::TeamSelect(error.to_string()))?
211
- .state;
212
- upsert_forked_agent_state(
213
- &mut next_state,
214
- input.source_agent_id,
215
- input.agent_id,
216
- input.spec_agent,
217
- input.safety,
218
- input.plan,
219
- input.profile_launch,
220
- input.spawn,
221
- input.workspace,
222
- Some(input.profile_dir),
223
- input.dynamic_role_file,
224
- input.context_proof,
225
- input.spawned_at,
226
- input.spawn_epoch,
227
- )?;
228
- if let Some(agent) = next_state
229
- .get_mut("agents")
230
- .and_then(serde_json::Value::as_object_mut)
231
- .and_then(|agents| agents.get_mut(input.agent_id.as_str()))
232
- .and_then(serde_json::Value::as_object_mut)
233
- {
234
- persist_effective_approval_policy(agent, input.safety);
235
- // 0.5.66 bypass 单源 §2.6:fork fresh spawn 记录运行面 bypass 值。
236
- agent.insert(
237
- "as_launched_dangerously_skip_permissions".to_string(),
238
- serde_json::json!(input.safety.enabled),
239
- );
240
- }
241
- maybe_fail_fork_after_spawn("save_runtime_state")?;
242
- crate::state::repository::StateRepository::new(input.workspace)
243
- .save(
244
- crate::state::repository::StateWriteIntent::ForkAgent {
245
- team_key: input.team_key,
246
- agent_id: input.agent_id.as_str(),
247
- },
248
- &next_state,
249
- )
250
- .map_err(|error| LifecycleError::StatePersist(error.to_string()))?;
251
- Ok(ContextForkFinalized::new(
252
- input.source_agent_id.as_str(),
253
- input.agent_id.as_str(),
254
- input.context_proof.new_session_id.as_str(),
255
- &input.context_proof.backing_path,
256
- &input.context_proof.captured_via,
257
- &input.context_proof.attribution_confidence,
258
- ))
259
- }
260
-
261
- pub(super) struct ForkPendingFinalizeInput<'a> {
262
- pub workspace: &'a Path,
263
- pub team_key: &'a str,
264
- pub source_agent_id: &'a AgentId,
265
- pub agent_id: &'a AgentId,
266
- pub spec_agent: &'a Value,
267
- pub safety: &'a DangerousApproval,
268
- pub plan: &'a crate::provider::CommandPlan,
269
- pub profile_launch: &'a crate::provider::ProviderProfileLaunch,
270
- pub spawn: &'a crate::transport::SpawnResult,
271
- pub profile_dir: &'a Path,
272
- pub dynamic_role_file: &'a Path,
273
- pub pending: &'a crate::provider::session::PendingContextFork,
274
- pub spawn_epoch: u64,
275
- }
276
-
277
- pub(super) fn finalize_pending_fork_state(
278
- input: ForkPendingFinalizeInput<'_>,
279
- ) -> Result<(), LifecycleError> {
280
- let _lock = acquire_agent_lifecycle_lock(LifecycleLockRequest {
281
- workspace: input.workspace,
282
- operation: "fork-agent-pending",
283
- team: Some(input.team_key),
284
- agent_id: Some(input.agent_id),
285
- })?;
286
- let mut next_state = crate::state::selector::resolve_active_team(
287
- input.workspace,
288
- Some(input.team_key),
289
- crate::state::selector::SelectorMode::RequireSpec,
290
- )
291
- .map_err(|error| LifecycleError::TeamSelect(error.to_string()))?
292
- .state;
293
- upsert_pending_forked_agent_state(
294
- &mut next_state,
295
- input.source_agent_id,
296
- input.agent_id,
297
- input.spec_agent,
298
- input.safety,
299
- input.plan,
300
- input.profile_launch,
301
- input.spawn,
302
- Some(input.profile_dir),
303
- input.dynamic_role_file,
304
- input.pending,
305
- input.spawn_epoch,
306
- )?;
307
- maybe_fail_fork_after_spawn("save_runtime_state")?;
308
- crate::state::repository::StateRepository::new(input.workspace)
309
- .save(
310
- crate::state::repository::StateWriteIntent::ForkAgent {
311
- team_key: input.team_key,
312
- agent_id: input.agent_id.as_str(),
313
- },
314
- &next_state,
315
- )
316
- .map_err(|error| LifecycleError::StatePersist(error.to_string()))
317
- }
318
-
319
- pub(crate) fn finalize_pending_fork_capture(
320
- agent: &mut serde_json::Map<String, serde_json::Value>,
321
- captured: &crate::provider::CapturedSession,
322
- ) -> Option<ContextForkFinalized> {
323
- let Some(session_id) = captured.session_id.as_ref() else {
324
- return None;
325
- };
326
- let Some(rollout_path) = captured.rollout_path.as_ref() else {
327
- return None;
328
- };
329
- if agent
330
- .get("fork_source_session_id")
331
- .and_then(serde_json::Value::as_str)
332
- == Some(session_id.as_str())
333
- {
334
- return None;
335
- }
336
- let finalized = ContextForkFinalized::from_captured(
337
- agent
338
- .get("forked_from")
339
- .and_then(serde_json::Value::as_str)
340
- .unwrap_or_default(),
341
- agent
342
- .get("agent_id")
343
- .and_then(serde_json::Value::as_str)
344
- .or_else(|| {
345
- agent
346
- .get("pending_target_agent")
347
- .and_then(serde_json::Value::as_str)
348
- })
349
- .unwrap_or_default(),
350
- captured,
351
- )?;
352
- agent.insert(
353
- "session_id".to_string(),
354
- serde_json::json!(session_id.as_str()),
355
- );
356
- agent.insert(
357
- "rollout_path".to_string(),
358
- serde_json::json!(rollout_path.as_path().to_string_lossy()),
359
- );
360
- agent.insert(
361
- "captured_at".to_string(),
362
- serde_json::json!(chrono::Utc::now().to_rfc3339()),
363
- );
364
- agent.insert(
365
- "captured_via".to_string(),
366
- serde_json::to_value(captured.captured_via).unwrap_or(serde_json::Value::Null),
367
- );
368
- agent.insert(
369
- "attribution_confidence".to_string(),
370
- serde_json::to_value(captured.attribution_confidence).unwrap_or(serde_json::Value::Null),
371
- );
372
- agent.remove("_pending_session_id");
373
- agent.remove("attribution_ambiguous");
374
- agent.remove("pending_target_agent");
375
- agent.remove("pending_grace_secs");
376
- agent.insert("capture_state".to_string(), serde_json::json!("captured"));
377
- Some(finalized)
378
- }
379
-
380
- #[cfg(test)]
381
- mod tests {
382
- use super::*;
383
-
384
- #[test]
385
- fn context_fork_finalized_writes_catalog_event_and_canonical_fields() {
386
- let workspace = std::env::temp_dir().join(format!(
387
- "team-agent-context-fork-audit-{}",
388
- std::process::id()
389
- ));
390
- let _ = std::fs::remove_dir_all(&workspace);
391
- std::fs::create_dir_all(&workspace).expect("create audit workspace");
392
- let event_log = crate::event_log::EventLog::new(&workspace);
393
- ContextForkFinalized::new(
394
- "source",
395
- "target",
396
- "target-session",
397
- Path::new("/tmp/target.jsonl"),
398
- "context_fork_verified",
399
- "high",
400
- )
401
- .write_audit(&event_log)
402
- .expect("write context fork audit");
403
-
404
- let events = event_log.tail(0).expect("read context fork audit");
405
- let event = events.last().expect("context fork audit event");
406
- assert_eq!(
407
- event.get("event").and_then(serde_json::Value::as_str),
408
- Some(crate::lifecycle::types::event_names::CONTEXT_FORK)
409
- );
410
- assert_eq!(
411
- event
412
- .get("source_agent_id")
413
- .and_then(serde_json::Value::as_str),
414
- Some("source")
415
- );
416
- assert_eq!(
417
- event.get("agent_id").and_then(serde_json::Value::as_str),
418
- Some("target")
419
- );
420
- assert!(event.get("prompt").is_none());
421
- let _ = std::fs::remove_dir_all(workspace);
422
- }
423
- }
424
-
425
- pub(super) fn verify_fork_registration(
426
- workspace: &Path,
427
- team_key: &str,
428
- agent_id: &AgentId,
429
- spawn: &crate::transport::SpawnResult,
430
- window: &WindowName,
431
- ) -> Result<(), LifecycleError> {
432
- let saved = crate::state::projection::select_runtime_state(workspace, Some(team_key))
433
- .map_err(|error| LifecycleError::StatePersist(error.to_string()))?;
434
- let agent = saved
435
- .get("agents")
436
- .and_then(|agents| agents.get(agent_id.as_str()))
437
- .ok_or_else(|| LifecycleError::StatePersist("canonical team row is missing".to_string()))?;
438
- if agent.get("pane_id").and_then(serde_json::Value::as_str) != Some(spawn.pane_id.as_str()) {
439
- return Err(LifecycleError::StatePersist(
440
- "canonical team pane_id does not match spawned pane".to_string(),
441
- ));
442
- }
443
- if agent.get("window").and_then(serde_json::Value::as_str) != Some(window.as_str()) {
444
- return Err(LifecycleError::StatePersist(
445
- "canonical team window does not match spawned window".to_string(),
446
- ));
447
- }
448
- if let Some(pid) = spawn.child_pid {
449
- if agent.get("pane_pid").and_then(serde_json::Value::as_u64) != Some(u64::from(pid)) {
450
- return Err(LifecycleError::StatePersist(
451
- "canonical team pane_pid does not match spawned process".to_string(),
452
- ));
453
- }
454
- }
455
- Ok(())
456
- }
457
-
458
- pub(super) fn rollback_fork_after_spawn(
459
- workspace: &Path,
460
- transport: &dyn Transport,
461
- session_name: &SessionName,
462
- window: &WindowName,
463
- mcp_config_path: &Path,
464
- agent_id: &AgentId,
465
- profile_launch: &crate::provider::ProviderProfileLaunch,
466
- team_key: &str,
467
- ) {
468
- let _ = transport.kill_window(&Target::SessionWindow {
469
- session: session_name.clone(),
470
- window: window.clone(),
471
- });
472
- if let Ok(_lock) = acquire_agent_lifecycle_lock(LifecycleLockRequest {
473
- workspace,
474
- operation: "fork-agent-rollback",
475
- team: Some(team_key),
476
- agent_id: Some(agent_id),
477
- }) {
478
- let _ = crate::lifecycle::restart::remove::remove_agent_with_transport_locked(
479
- workspace,
480
- agent_id,
481
- true,
482
- true,
483
- Some(team_key),
484
- transport,
485
- );
486
- }
487
- cleanup_fork_mcp_artifacts(workspace, agent_id, mcp_config_path, profile_launch);
488
- }
@@ -1,109 +0,0 @@
1
- use super::*;
2
-
3
- #[allow(clippy::too_many_arguments)]
4
- pub(crate) fn upsert_pending_forked_agent_state(
5
- state: &mut serde_json::Value,
6
- source_agent_id: &AgentId,
7
- as_agent_id: &AgentId,
8
- spec_agent: &Value,
9
- safety: &DangerousApproval,
10
- plan: &crate::provider::CommandPlan,
11
- profile_launch: &crate::provider::ProviderProfileLaunch,
12
- spawn: &crate::transport::SpawnResult,
13
- profile_dir: Option<&Path>,
14
- dynamic_role_file: &Path,
15
- pending: &crate::provider::session::PendingContextFork,
16
- spawn_epoch: u64,
17
- ) -> Result<(), LifecycleError> {
18
- let root = state.as_object_mut().ok_or_else(|| {
19
- LifecycleError::StatePersist("runtime state root is not an object".to_string())
20
- })?;
21
- let agents = root
22
- .entry("agents".to_string())
23
- .or_insert_with(|| serde_json::json!({}))
24
- .as_object_mut()
25
- .ok_or_else(|| {
26
- LifecycleError::StatePersist("runtime state agents is not an object".to_string())
27
- })?;
28
- let mut entry = serde_json::Map::new();
29
- entry.insert("status".to_string(), serde_json::json!("running"));
30
- entry.insert(
31
- "capture_state".to_string(),
32
- serde_json::json!("pending_context_fork"),
33
- );
34
- entry.insert(
35
- "agent_id".to_string(),
36
- serde_json::json!(as_agent_id.as_str()),
37
- );
38
- entry.insert(
39
- "window".to_string(),
40
- serde_json::json!(as_agent_id.as_str()),
41
- );
42
- entry.insert(
43
- "forked_from".to_string(),
44
- serde_json::json!(source_agent_id.as_str()),
45
- );
46
- entry.insert(
47
- "fork_source_session_id".to_string(),
48
- serde_json::json!(pending.source_session_id.as_str()),
49
- );
50
- entry.insert(
51
- "pending_target_agent".to_string(),
52
- serde_json::json!(pending.target_agent),
53
- );
54
- entry.insert(
55
- "dynamic_role_file".to_string(),
56
- serde_json::json!(dynamic_role_file.to_string_lossy().to_string()),
57
- );
58
- entry.insert(
59
- "role_source_ownership".to_string(),
60
- serde_json::json!("managed"),
61
- );
62
- entry.insert(
63
- "spawn_cwd".to_string(),
64
- serde_json::json!(pending
65
- .scanner_context
66
- .spawn_cwd
67
- .to_string_lossy()
68
- .to_string()),
69
- );
70
- entry.insert(
71
- "pane_id".to_string(),
72
- serde_json::json!(spawn.pane_id.as_str()),
73
- );
74
- entry.insert(
75
- "spawned_at".to_string(),
76
- serde_json::json!(pending.spawned_at),
77
- );
78
- entry.insert("spawn_epoch".to_string(), serde_json::json!(spawn_epoch));
79
- if let Some(pid) = spawn.child_pid {
80
- entry.insert("pane_pid".to_string(), serde_json::json!(pid));
81
- }
82
- for key in [
83
- "provider",
84
- "auth_mode",
85
- "model",
86
- "profile",
87
- "role",
88
- "effort",
89
- ] {
90
- if let Some(value) = spec_agent.get(key) {
91
- entry.insert(key.to_string(), yaml_value_to_json(value));
92
- }
93
- }
94
- if spec_agent.get("profile").is_some() {
95
- if let Some(profile_dir) = profile_dir {
96
- entry.insert(
97
- "_profile_dir".to_string(),
98
- serde_json::json!(profile_dir.to_string_lossy().to_string()),
99
- );
100
- }
101
- }
102
- persist_command_plan_state(&mut entry, plan, profile_launch);
103
- persist_effective_approval_policy(&mut entry, safety);
104
- agents.insert(
105
- as_agent_id.as_str().to_string(),
106
- serde_json::Value::Object(entry),
107
- );
108
- Ok(())
109
- }