@team-agent/installer 0.5.51 → 0.5.53

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 (79) hide show
  1. package/Cargo.lock +1 -1
  2. package/Cargo.toml +1 -1
  3. package/crates/team-agent/src/cli/adapters.rs +18 -2
  4. package/crates/team-agent/src/cli/emit.rs +17 -0
  5. package/crates/team-agent/src/cli/mod.rs +155 -4
  6. package/crates/team-agent/src/cli/named_address.rs +35 -67
  7. package/crates/team-agent/src/cli/spec.rs +3 -0
  8. package/crates/team-agent/src/cli/tests/main_preserved.rs +1 -0
  9. package/crates/team-agent/src/cli/types.rs +11 -0
  10. package/crates/team-agent/src/diagnose/orphans.rs +24 -3
  11. package/crates/team-agent/src/kill_audit.rs +67 -0
  12. package/crates/team-agent/src/leader/lease.rs +526 -73
  13. package/crates/team-agent/src/leader/rediscover/tests.rs +3 -0
  14. package/crates/team-agent/src/leader/rediscover.rs +6 -0
  15. package/crates/team-agent/src/leader/start.rs +144 -23
  16. package/crates/team-agent/src/leader/tests/idle.rs +3 -0
  17. package/crates/team-agent/src/leader/types.rs +6 -0
  18. package/crates/team-agent/src/lib.rs +1 -0
  19. package/crates/team-agent/src/lifecycle/launch/add_agent.rs +1 -1
  20. package/crates/team-agent/src/lifecycle/launch/add_agent_state.rs +12 -1
  21. package/crates/team-agent/src/lifecycle/launch/agent_state.rs +4 -0
  22. package/crates/team-agent/src/lifecycle/launch/clone_agent.rs +106 -0
  23. package/crates/team-agent/src/lifecycle/launch/fork_agent.rs +216 -208
  24. package/crates/team-agent/src/lifecycle/launch/fork_entry.rs +36 -0
  25. package/crates/team-agent/src/lifecycle/launch/fork_finalize.rs +238 -0
  26. package/crates/team-agent/src/lifecycle/launch/fork_state.rs +101 -5
  27. package/crates/team-agent/src/lifecycle/launch/quick_start.rs +1 -1
  28. package/crates/team-agent/src/lifecycle/launch/role_source.rs +170 -0
  29. package/crates/team-agent/src/lifecycle/launch/state_projection.rs +8 -8
  30. package/crates/team-agent/src/lifecycle/launch/worker_env.rs +1 -1
  31. package/crates/team-agent/src/lifecycle/launch.rs +14 -2
  32. package/crates/team-agent/src/lifecycle/restart/agent.rs +20 -4
  33. package/crates/team-agent/src/lifecycle/restart/common.rs +12 -0
  34. package/crates/team-agent/src/lifecycle/restart/rebuild.rs +113 -4
  35. package/crates/team-agent/src/lifecycle/restart/remove.rs +73 -10
  36. package/crates/team-agent/src/lifecycle/restart.rs +25 -1
  37. package/crates/team-agent/src/lifecycle/tests/lane_ops.rs +441 -12
  38. package/crates/team-agent/src/lifecycle/tests/launch_spawn.rs +20 -2
  39. package/crates/team-agent/src/lifecycle/tests/main_preserved.rs +1 -0
  40. package/crates/team-agent/src/lifecycle/tests/phase_b_contracts.rs +3 -1
  41. package/crates/team-agent/src/lifecycle/types.rs +17 -0
  42. package/crates/team-agent/src/mcp_server/lifecycle_tools/agent_ops.rs +40 -5
  43. package/crates/team-agent/src/mcp_server/lifecycle_tools/mod.rs +1 -1
  44. package/crates/team-agent/src/mcp_server/tests/tools.rs +18 -1
  45. package/crates/team-agent/src/mcp_server/tests/wire.rs +243 -167
  46. package/crates/team-agent/src/mcp_server/tests.rs +11 -4
  47. package/crates/team-agent/src/mcp_server/tools.rs +18 -4
  48. package/crates/team-agent/src/mcp_server/types.rs +3 -0
  49. package/crates/team-agent/src/mcp_server/wire.rs +30 -7
  50. package/crates/team-agent/src/messaging/delivery.rs +126 -147
  51. package/crates/team-agent/src/messaging/leader_channel.rs +198 -0
  52. package/crates/team-agent/src/messaging/leader_receiver.rs +68 -21
  53. package/crates/team-agent/src/messaging/mod.rs +5 -0
  54. package/crates/team-agent/src/messaging/tests/leader_channel.rs +169 -0
  55. package/crates/team-agent/src/messaging/tests/mod.rs +1 -0
  56. package/crates/team-agent/src/messaging/tests/runtime.rs +34 -23
  57. package/crates/team-agent/src/provider/adapter.rs +54 -13
  58. package/crates/team-agent/src/provider/adapters/claude_fork.rs +122 -0
  59. package/crates/team-agent/src/provider/adapters/copilot_fork.rs +306 -0
  60. package/crates/team-agent/src/provider/adapters/mod.rs +2 -0
  61. package/crates/team-agent/src/provider/session/capture.rs +321 -52
  62. package/crates/team-agent/src/provider/session/context_fork.rs +499 -0
  63. package/crates/team-agent/src/provider/session/mod.rs +6 -0
  64. package/crates/team-agent/src/provider/session_scan/claude.rs +1 -1
  65. package/crates/team-agent/src/provider/session_scan/codex.rs +144 -27
  66. package/crates/team-agent/src/provider/session_scan/common.rs +39 -6
  67. package/crates/team-agent/src/provider/session_scan/copilot.rs +47 -3
  68. package/crates/team-agent/src/provider/session_scan.rs +3 -3
  69. package/crates/team-agent/src/provider/tests/copilot_fork.rs +191 -0
  70. package/crates/team-agent/src/provider/tests.rs +1 -0
  71. package/crates/team-agent/src/state/persist.rs +282 -20
  72. package/crates/team-agent/src/state/projection.rs +245 -30
  73. package/crates/team-agent/src/state/repository/intent.rs +13 -0
  74. package/crates/team-agent/src/state/repository.rs +36 -12
  75. package/crates/team-agent/src/state/selector.rs +9 -18
  76. package/crates/team-agent/src/tmux_backend/tests.rs +51 -16
  77. package/crates/team-agent/src/tmux_backend.rs +29 -2
  78. package/package.json +4 -4
  79. package/skills/team-agent/SKILL.md +8 -3
@@ -0,0 +1,306 @@
1
+ use std::path::{Path, PathBuf};
2
+
3
+ use rusqlite::{params, Connection};
4
+
5
+ use crate::provider::{next_session_token, ProviderError, SessionId};
6
+
7
+ const SESSION_TABLES: [&str; 6] = [
8
+ "turns",
9
+ "checkpoints",
10
+ "session_files",
11
+ "session_refs",
12
+ "forge_trajectory_events",
13
+ "search_index",
14
+ ];
15
+
16
+ #[derive(Debug, Clone)]
17
+ pub(crate) struct CopilotForkMaterialization {
18
+ session_id: SessionId,
19
+ home: PathBuf,
20
+ keep: bool,
21
+ }
22
+
23
+ impl CopilotForkMaterialization {
24
+ pub(crate) fn session_id(&self) -> &SessionId {
25
+ &self.session_id
26
+ }
27
+
28
+ pub(crate) fn home(&self) -> &Path {
29
+ &self.home
30
+ }
31
+
32
+ pub(crate) fn keep(&mut self) {
33
+ self.keep = true;
34
+ }
35
+ }
36
+
37
+ impl Drop for CopilotForkMaterialization {
38
+ fn drop(&mut self) {
39
+ if !self.keep {
40
+ let _ = std::fs::remove_dir_all(&self.home);
41
+ }
42
+ }
43
+ }
44
+
45
+ pub(crate) fn copilot_home() -> Result<PathBuf, ProviderError> {
46
+ if let Some(home) = std::env::var_os("COPILOT_HOME").filter(|value| !value.is_empty()) {
47
+ return Ok(PathBuf::from(home));
48
+ }
49
+ std::env::var_os("HOME")
50
+ .map(PathBuf::from)
51
+ .map(|home| home.join(".copilot"))
52
+ .ok_or_else(|| ProviderError::Io("HOME is unset; cannot resolve COPILOT_HOME".to_string()))
53
+ }
54
+
55
+ pub(crate) fn materialize_copilot_fork(
56
+ workspace: &Path,
57
+ agent_id: &str,
58
+ source_session_id: &SessionId,
59
+ ) -> Result<CopilotForkMaterialization, ProviderError> {
60
+ let source_home = copilot_home()?;
61
+ let source_dir = source_home
62
+ .join("session-state")
63
+ .join(source_session_id.as_str());
64
+ let source_db = source_home.join("session-store.db");
65
+ if !source_dir.is_dir() || !source_db.is_file() {
66
+ return Err(ProviderError::Io(format!(
67
+ "copilot source backing is incomplete under {}",
68
+ source_home.display()
69
+ )));
70
+ }
71
+
72
+ let session_id = SessionId::new(next_session_token());
73
+ let parent = workspace
74
+ .join(".team/runtime/provider-session-forks/copilot")
75
+ .join(agent_id);
76
+ let home = parent.join(session_id.as_str());
77
+ let temp = parent.join(format!(
78
+ ".{}.tmp-{}",
79
+ session_id.as_str(),
80
+ std::process::id()
81
+ ));
82
+ if home.exists() || temp.exists() {
83
+ return Err(ProviderError::Io(format!(
84
+ "copilot fork destination already exists: {}",
85
+ home.display()
86
+ )));
87
+ }
88
+ std::fs::create_dir_all(temp.join("session-state"))
89
+ .map_err(|error| ProviderError::Io(error.to_string()))?;
90
+
91
+ let result = (|| {
92
+ let target_session_dir = temp.join("session-state").join(session_id.as_str());
93
+ copy_tree(&source_dir, &target_session_dir)?;
94
+ rewrite_text_tree(
95
+ &target_session_dir,
96
+ source_session_id.as_str(),
97
+ session_id.as_str(),
98
+ )?;
99
+ rewrite_per_session_db(
100
+ &target_session_dir.join("session.db"),
101
+ source_session_id.as_str(),
102
+ session_id.as_str(),
103
+ )?;
104
+ clone_and_rekey_store(
105
+ &source_db,
106
+ &temp.join("session-store.db"),
107
+ source_session_id.as_str(),
108
+ session_id.as_str(),
109
+ )?;
110
+ std::fs::rename(&temp, &home).map_err(|error| ProviderError::Io(error.to_string()))
111
+ })();
112
+ if let Err(error) = result {
113
+ let _ = std::fs::remove_dir_all(&temp);
114
+ return Err(error);
115
+ }
116
+ Ok(CopilotForkMaterialization {
117
+ session_id,
118
+ home,
119
+ keep: false,
120
+ })
121
+ }
122
+
123
+ fn copy_tree(source: &Path, target: &Path) -> Result<(), ProviderError> {
124
+ std::fs::create_dir_all(target).map_err(|error| ProviderError::Io(error.to_string()))?;
125
+ for entry in std::fs::read_dir(source).map_err(|error| ProviderError::Io(error.to_string()))? {
126
+ let entry = entry.map_err(|error| ProviderError::Io(error.to_string()))?;
127
+ let source_path = entry.path();
128
+ let target_path = target.join(entry.file_name());
129
+ if source_path.is_dir() {
130
+ copy_tree(&source_path, &target_path)?;
131
+ } else {
132
+ std::fs::copy(&source_path, &target_path)
133
+ .map_err(|error| ProviderError::Io(error.to_string()))?;
134
+ }
135
+ }
136
+ Ok(())
137
+ }
138
+
139
+ fn rewrite_text_tree(root: &Path, source: &str, target: &str) -> Result<(), ProviderError> {
140
+ for entry in std::fs::read_dir(root).map_err(|error| ProviderError::Io(error.to_string()))? {
141
+ let entry = entry.map_err(|error| ProviderError::Io(error.to_string()))?;
142
+ let path = entry.path();
143
+ if path.is_dir() {
144
+ rewrite_text_tree(&path, source, target)?;
145
+ continue;
146
+ }
147
+ if path.extension().and_then(|ext| ext.to_str()) == Some("db") {
148
+ continue;
149
+ }
150
+ let Ok(text) = std::fs::read_to_string(&path) else {
151
+ continue;
152
+ };
153
+ if text.contains(source) {
154
+ std::fs::write(&path, text.replace(source, target))
155
+ .map_err(|error| ProviderError::Io(error.to_string()))?;
156
+ }
157
+ }
158
+ Ok(())
159
+ }
160
+
161
+ fn clone_and_rekey_store(
162
+ source_db: &Path,
163
+ target_db: &Path,
164
+ source: &str,
165
+ target: &str,
166
+ ) -> Result<(), ProviderError> {
167
+ let source_conn = Connection::open(source_db).map_err(sql_error)?;
168
+ source_conn
169
+ .execute("VACUUM INTO ?1", [target_db.to_string_lossy().as_ref()])
170
+ .map_err(sql_error)?;
171
+ drop(source_conn);
172
+
173
+ let mut conn = Connection::open(target_db).map_err(sql_error)?;
174
+ conn.pragma_update(None, "foreign_keys", "OFF")
175
+ .map_err(sql_error)?;
176
+ let tx = conn.transaction().map_err(sql_error)?;
177
+ let session_rows = tx
178
+ .execute(
179
+ "update sessions set id = ?1 where id = ?2",
180
+ params![target, source],
181
+ )
182
+ .map_err(sql_error)?;
183
+ if session_rows != 1 {
184
+ return Err(ProviderError::Io(format!(
185
+ "copilot session-store source row count must be 1, got {session_rows}"
186
+ )));
187
+ }
188
+ for table in SESSION_TABLES {
189
+ require_session_column(&tx, table)?;
190
+ tx.execute(
191
+ &format!("update {table} set session_id = ?1 where session_id = ?2"),
192
+ params![target, source],
193
+ )
194
+ .map_err(sql_error)?;
195
+ }
196
+ tx.commit().map_err(sql_error)?;
197
+ verify_rekeyed_store(&conn, source, target)
198
+ }
199
+
200
+ fn require_session_column(conn: &Connection, table: &str) -> Result<(), ProviderError> {
201
+ let mut stmt = conn
202
+ .prepare(&format!("pragma table_info({table})"))
203
+ .map_err(sql_error)?;
204
+ let mut rows = stmt.query([]).map_err(sql_error)?;
205
+ while let Some(row) = rows.next().map_err(sql_error)? {
206
+ let name: String = row.get(1).map_err(sql_error)?;
207
+ if name == "session_id" {
208
+ return Ok(());
209
+ }
210
+ }
211
+ Err(ProviderError::Io(format!(
212
+ "copilot session-store table {table} is missing session_id"
213
+ )))
214
+ }
215
+
216
+ fn verify_rekeyed_store(
217
+ conn: &Connection,
218
+ source: &str,
219
+ target: &str,
220
+ ) -> Result<(), ProviderError> {
221
+ let target_sessions: i64 = conn
222
+ .query_row(
223
+ "select count(*) from sessions where id = ?1",
224
+ [target],
225
+ |row| row.get(0),
226
+ )
227
+ .map_err(sql_error)?;
228
+ let source_sessions: i64 = conn
229
+ .query_row(
230
+ "select count(*) from sessions where id = ?1",
231
+ [source],
232
+ |row| row.get(0),
233
+ )
234
+ .map_err(sql_error)?;
235
+ if target_sessions != 1 || source_sessions != 0 {
236
+ return Err(ProviderError::Io(
237
+ "copilot session-store session id rekey verification failed".to_string(),
238
+ ));
239
+ }
240
+ for table in SESSION_TABLES {
241
+ let remaining: i64 = conn
242
+ .query_row(
243
+ &format!("select count(*) from {table} where session_id = ?1"),
244
+ [source],
245
+ |row| row.get(0),
246
+ )
247
+ .map_err(sql_error)?;
248
+ if remaining != 0 {
249
+ return Err(ProviderError::Io(format!(
250
+ "copilot session-store table {table} still references source session"
251
+ )));
252
+ }
253
+ }
254
+ Ok(())
255
+ }
256
+
257
+ fn rewrite_per_session_db(path: &Path, source: &str, target: &str) -> Result<(), ProviderError> {
258
+ if !path.is_file() {
259
+ return Ok(());
260
+ }
261
+ let mut conn = Connection::open(path).map_err(sql_error)?;
262
+ let columns: Vec<(String, String)> = {
263
+ let mut tables = conn
264
+ .prepare("select name from sqlite_master where type = 'table'")
265
+ .map_err(sql_error)?;
266
+ let names = tables
267
+ .query_map([], |row| row.get::<_, String>(0))
268
+ .map_err(sql_error)?
269
+ .collect::<Result<Vec<_>, _>>()
270
+ .map_err(sql_error)?;
271
+ let mut columns = Vec::new();
272
+ for table in names {
273
+ let mut stmt = conn
274
+ .prepare(&format!(
275
+ "pragma table_info('{}')",
276
+ table.replace('\'', "''")
277
+ ))
278
+ .map_err(sql_error)?;
279
+ let rows = stmt
280
+ .query_map([], |row| row.get::<_, String>(1))
281
+ .map_err(sql_error)?;
282
+ for column in rows {
283
+ let column = column.map_err(sql_error)?;
284
+ if column == "session_id" || column == "recipient_session_id" {
285
+ columns.push((table.clone(), column));
286
+ }
287
+ }
288
+ }
289
+ columns
290
+ };
291
+ let tx = conn.transaction().map_err(sql_error)?;
292
+ for (table, column) in columns {
293
+ let table = table.replace('"', "\"\"");
294
+ let column = column.replace('"', "\"\"");
295
+ tx.execute(
296
+ &format!("update \"{table}\" set \"{column}\" = ?1 where \"{column}\" = ?2"),
297
+ params![target, source],
298
+ )
299
+ .map_err(sql_error)?;
300
+ }
301
+ tx.commit().map_err(sql_error)
302
+ }
303
+
304
+ fn sql_error(error: rusqlite::Error) -> ProviderError {
305
+ ProviderError::Io(format!("copilot sqlite: {error}"))
306
+ }
@@ -22,6 +22,8 @@
22
22
  //! per-file layout.
23
23
 
24
24
  pub(crate) mod claude;
25
+ pub(crate) mod claude_fork;
25
26
  pub(crate) mod codex;
26
27
  pub(crate) mod copilot;
28
+ pub(crate) mod copilot_fork;
27
29
  pub(crate) mod fake;