@team-agent/installer 0.5.59 → 0.5.61

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 (68) hide show
  1. package/Cargo.lock +1 -1
  2. package/Cargo.toml +1 -1
  3. package/crates/team-agent/src/cli/adapters.rs +44 -1
  4. package/crates/team-agent/src/cli/diagnose.rs +183 -0
  5. package/crates/team-agent/src/cli/emit.rs +63 -14
  6. package/crates/team-agent/src/cli/leader.rs +8 -1
  7. package/crates/team-agent/src/cli/mod.rs +263 -5
  8. package/crates/team-agent/src/cli/named_address.rs +18 -1
  9. package/crates/team-agent/src/cli/send/presentation.rs +2 -0
  10. package/crates/team-agent/src/cli/spec.rs +4 -1
  11. package/crates/team-agent/src/cli/status_port/store.rs +7 -3
  12. package/crates/team-agent/src/cli/tests/named_address.rs +65 -0
  13. package/crates/team-agent/src/cli/types.rs +33 -0
  14. package/crates/team-agent/src/communication_mode/mod.rs +53 -0
  15. package/crates/team-agent/src/compiler/tests.rs +5 -5
  16. package/crates/team-agent/src/compiler.rs +53 -1
  17. package/crates/team-agent/src/coordinator/tick.rs +21 -8
  18. package/crates/team-agent/src/db/message_store.rs +45 -1
  19. package/crates/team-agent/src/fake_worker.rs +21 -1
  20. package/crates/team-agent/src/leader/start.rs +732 -94
  21. package/crates/team-agent/src/leader/tests/identity.rs +64 -57
  22. package/crates/team-agent/src/leader/tests/identity_session_names.rs +42 -0
  23. package/crates/team-agent/src/lib.rs +4 -0
  24. package/crates/team-agent/src/lifecycle/launch/fork_agent/completion.rs +58 -0
  25. package/crates/team-agent/src/lifecycle/launch/fork_agent.rs +57 -51
  26. package/crates/team-agent/src/lifecycle/launch/fork_finalize.rs +148 -8
  27. package/crates/team-agent/src/lifecycle/launch/fork_state.rs +5 -0
  28. package/crates/team-agent/src/lifecycle/launch/spawn.rs +1 -1
  29. package/crates/team-agent/src/lifecycle/launch.rs +1 -1
  30. package/crates/team-agent/src/lifecycle/restart/agent.rs +1 -1
  31. package/crates/team-agent/src/lifecycle/restart/common.rs +1 -1
  32. package/crates/team-agent/src/lifecycle/tests/lane_ops.rs +35 -1
  33. package/crates/team-agent/src/lifecycle/types.rs +1 -0
  34. package/crates/team-agent/src/lifecycle/worker_command_context.rs +29 -11
  35. package/crates/team-agent/src/mcp_server/normalize.rs +1 -0
  36. package/crates/team-agent/src/mcp_server/tests/send.rs +26 -0
  37. package/crates/team-agent/src/mcp_server/tests/wire.rs +1 -1
  38. package/crates/team-agent/src/mcp_server/tools.rs +100 -66
  39. package/crates/team-agent/src/mcp_server/types.rs +5 -0
  40. package/crates/team-agent/src/mcp_server/wire.rs +27 -21
  41. package/crates/team-agent/src/messaging/delivery.rs +105 -38
  42. package/crates/team-agent/src/messaging/leader_channel.rs +33 -10
  43. package/crates/team-agent/src/messaging/leader_receiver.rs +40 -23
  44. package/crates/team-agent/src/messaging/presentation.rs +109 -0
  45. package/crates/team-agent/src/messaging/results.rs +165 -17
  46. package/crates/team-agent/src/messaging/send.rs +94 -81
  47. package/crates/team-agent/src/messaging/tests/leader_channel.rs +6 -6
  48. package/crates/team-agent/src/messaging/tests/leader_inject_acceptance.rs +187 -0
  49. package/crates/team-agent/src/messaging/tests/runtime.rs +13 -6
  50. package/crates/team-agent/src/messaging/types.rs +5 -0
  51. package/crates/team-agent/src/messaging/watchers.rs +5 -0
  52. package/crates/team-agent/src/model/mod.rs +1 -0
  53. package/crates/team-agent/src/model/pane_authority_refusal.rs +358 -0
  54. package/crates/team-agent/src/model/spec.rs +16 -0
  55. package/crates/team-agent/src/provider/adapter.rs +57 -0
  56. package/crates/team-agent/src/provider/adapters/claude_fork.rs +10 -2
  57. package/crates/team-agent/src/provider/session/capture.rs +69 -34
  58. package/crates/team-agent/src/provider/session/context_fork/codex.rs +386 -9
  59. package/crates/team-agent/src/provider/session/context_fork/outcome.rs +3 -14
  60. package/crates/team-agent/src/provider/session/context_fork.rs +7 -2
  61. package/crates/team-agent/src/provider/session/mod.rs +3 -2
  62. package/crates/team-agent/src/provider/session_scan/claude.rs +92 -3
  63. package/crates/team-agent/src/provider/session_scan/codex.rs +28 -0
  64. package/crates/team-agent/src/provider/session_scan/common.rs +34 -8
  65. package/crates/team-agent/src/provider/session_scan.rs +8 -0
  66. package/crates/team-agent/src/topology.rs +3 -0
  67. package/package.json +4 -4
  68. package/skills/team-agent/command-coverage.json +379 -0
@@ -12,6 +12,11 @@ use super::{AuthMode, Provider};
12
12
 
13
13
  pub use crate::provider::session_scan::{CaptureSessionContext, CapturedSessionCandidate};
14
14
 
15
+ pub trait ForkBackingMaterialization {
16
+ fn path(&self) -> &Path;
17
+ fn handoff(&mut self);
18
+ }
19
+
15
20
  // ===========================================================================
16
21
  // TRAIT: ProviderAdapter (method SIGNATURES only — 无 body)
17
22
  // ===========================================================================
@@ -187,6 +192,24 @@ pub trait ProviderAdapter {
187
192
  .map(CommandPlan::argv_only)
188
193
  }
189
194
 
195
+ fn materialize_fork_backing(
196
+ &self,
197
+ source_path: &Path,
198
+ source_session_id: &SessionId,
199
+ source_agent_id: &str,
200
+ target_agent_id: &str,
201
+ plan: &mut CommandPlan,
202
+ ) -> Result<Option<Box<dyn ForkBackingMaterialization>>, ProviderError> {
203
+ let _ = (
204
+ source_path,
205
+ source_session_id,
206
+ source_agent_id,
207
+ target_agent_id,
208
+ plan,
209
+ );
210
+ Ok(None)
211
+ }
212
+
190
213
  /// 计算本 provider 该用的 MCP server 配置(`adapter.py` mcp_config;claude
191
214
  /// compatible_api 走 `ensure_compatible_claude_mcp_config`)。
192
215
  fn mcp_config(&self, auth_mode: AuthMode) -> Result<McpConfig, ProviderError>;
@@ -549,6 +572,29 @@ impl ProviderAdapter for BasicProviderAdapter {
549
572
  }
550
573
  }
551
574
 
575
+ fn materialize_fork_backing(
576
+ &self,
577
+ source_path: &Path,
578
+ source_session_id: &SessionId,
579
+ source_agent_id: &str,
580
+ target_agent_id: &str,
581
+ plan: &mut CommandPlan,
582
+ ) -> Result<Option<Box<dyn ForkBackingMaterialization>>, ProviderError> {
583
+ match self.provider {
584
+ Provider::Codex => crate::provider::session::materialize_codex_fork(
585
+ source_path,
586
+ source_session_id,
587
+ source_agent_id,
588
+ target_agent_id,
589
+ plan,
590
+ )
591
+ .map(|materialized| {
592
+ Some(Box::new(materialized) as Box<dyn ForkBackingMaterialization>)
593
+ }),
594
+ _ => Ok(None),
595
+ }
596
+ }
597
+
552
598
  fn capture_session_id(
553
599
  &self,
554
600
  agent_id: &str,
@@ -1198,3 +1244,14 @@ pub(crate) fn next_session_token() -> String {
1198
1244
  bytes[15],
1199
1245
  )
1200
1246
  }
1247
+
1248
+ #[cfg(test)]
1249
+ mod fork_materialization_source_guard {
1250
+ #[test]
1251
+ fn lifecycle_uses_provider_neutral_fork_materialization_dispatch() {
1252
+ let source = include_str!("../lifecycle/launch/fork_agent.rs");
1253
+ assert!(source.contains(".materialize_fork_backing("));
1254
+ assert!(!source.contains("materialize_codex_fork"));
1255
+ assert!(!source.contains("codex_fork"));
1256
+ }
1257
+ }
@@ -30,6 +30,8 @@ pub(crate) fn materialize_claude_fork(
30
30
  source_path: &Path,
31
31
  source_session_id: &SessionId,
32
32
  target_session_id: &SessionId,
33
+ source_agent_id: &str,
34
+ target_agent_id: &str,
33
35
  ) -> Result<ClaudeForkMaterialization, ProviderError> {
34
36
  let parent = source_path.parent().ok_or_else(|| {
35
37
  ProviderError::Io(format!(
@@ -46,7 +48,12 @@ pub(crate) fn materialize_claude_fork(
46
48
  }
47
49
  let source = std::fs::read_to_string(source_path)
48
50
  .map_err(|error| ProviderError::Io(error.to_string()))?;
49
- let rewritten = source.replace(source_session_id.as_str(), target_session_id.as_str());
51
+ let rewritten = source
52
+ .replace(source_session_id.as_str(), target_session_id.as_str())
53
+ .replace(
54
+ &format!("You are Team Agent worker `{source_agent_id}`"),
55
+ &format!("You are Team Agent worker `{target_agent_id}`"),
56
+ );
50
57
  validate_jsonl(&rewritten)?;
51
58
  let temp = parent.join(format!(
52
59
  ".{}.tmp-{}",
@@ -108,7 +115,8 @@ mod tests {
108
115
  std::fs::write(&source_path, &source).unwrap();
109
116
 
110
117
  let mut materialized =
111
- materialize_claude_fork(&source_path, &source_id, &target_id).unwrap();
118
+ materialize_claude_fork(&source_path, &source_id, &target_id, "source", "target")
119
+ .unwrap();
112
120
  let target_path = dir.join(format!("{}.jsonl", target_id.as_str()));
113
121
  let target = std::fs::read_to_string(&target_path).unwrap();
114
122
  assert!(!target.contains(source_id.as_str()));
@@ -29,6 +29,7 @@ pub struct CapturePassReport {
29
29
  /// (coordinator tick) emits a throttled
30
30
  /// `provider.session.transcript_missing` event for each entry.
31
31
  pub transcript_missing: Vec<TranscriptMissing>,
32
+ pub(crate) context_forks: Vec<crate::lifecycle::launch::ContextForkFinalized>,
32
33
  }
33
34
 
34
35
  #[derive(Debug, Clone, PartialEq, Eq)]
@@ -283,12 +284,22 @@ where
283
284
  .zip(candidate.captured.session_id.as_ref())
284
285
  .is_some_and(|(expected, captured)| expected.as_str() != captured.as_str());
285
286
  if mismatch {
286
- report.ambiguous.push(AmbiguousSessionCapture {
287
- agent_id: item.agent_id.clone(),
288
- spawn_cwd: item.context.spawn_cwd.to_string_lossy().to_string(),
289
- });
287
+ let ambiguity_changed = agent_obj
288
+ .get("attribution_ambiguous")
289
+ .and_then(Value::as_bool)
290
+ != Some(true);
291
+ if !finalize_ambiguous || ambiguity_changed {
292
+ report.ambiguous.push(AmbiguousSessionCapture {
293
+ agent_id: item.agent_id.clone(),
294
+ spawn_cwd: item.context.spawn_cwd.to_string_lossy().to_string(),
295
+ });
296
+ }
290
297
  if finalize_ambiguous {
291
- agent_obj.insert("attribution_ambiguous".to_string(), serde_json::json!(true));
298
+ if ambiguity_changed {
299
+ agent_obj
300
+ .insert("attribution_ambiguous".to_string(), serde_json::json!(true));
301
+ report.changed = true;
302
+ }
292
303
  // 0.4.6 tuple-atomic contract (audit:93): do NOT write
293
304
  // `captured_at` for ambiguity diagnostics. `captured_at`
294
305
  // is part of the authoritative tuple and may only be
@@ -298,7 +309,6 @@ where
298
309
  // "looks captured" rows. Ambiguity diagnostics live in
299
310
  // the `attribution_ambiguous` flag + events; persist
300
311
  // event log for the timestamp.
301
- report.changed = true;
302
312
  }
303
313
  continue;
304
314
  }
@@ -306,26 +316,39 @@ where
306
316
  // partial candidates (no session_id or no rollout_path). When
307
317
  // it returns false, the row stays unchanged; capture re-runs
308
318
  // on the next coordinator tick.
309
- if apply_captured_session(agent_obj, &candidate.captured) {
319
+ if let Some(applied) = apply_captured_session(agent_obj, &candidate.captured) {
310
320
  report.changed = true;
311
321
  report.assigned.push(item.agent_id);
322
+ if let AppliedCapture::ContextFork(context_fork) = applied {
323
+ report.context_forks.push(context_fork);
324
+ }
312
325
  }
313
326
  continue;
314
327
  }
315
328
  if ambiguous_ids.contains(&item.agent_id) {
316
- report.ambiguous.push(AmbiguousSessionCapture {
317
- agent_id: item.agent_id.clone(),
318
- spawn_cwd: item.context.spawn_cwd.to_string_lossy().to_string(),
319
- });
329
+ let ambiguity_changed = agent_obj
330
+ .get("attribution_ambiguous")
331
+ .and_then(Value::as_bool)
332
+ != Some(true)
333
+ || agent_obj.get("capture_state").and_then(Value::as_str)
334
+ != Some("attribution_ambiguous");
335
+ if !finalize_ambiguous || ambiguity_changed {
336
+ report.ambiguous.push(AmbiguousSessionCapture {
337
+ agent_id: item.agent_id.clone(),
338
+ spawn_cwd: item.context.spawn_cwd.to_string_lossy().to_string(),
339
+ });
340
+ }
320
341
  if finalize_ambiguous {
321
- agent_obj.insert("attribution_ambiguous".to_string(), serde_json::json!(true));
322
- agent_obj.insert(
323
- "capture_state".to_string(),
324
- serde_json::json!("attribution_ambiguous"),
325
- );
326
- // 0.4.6 tuple-atomic contract: see comment above. Removed
327
- // `captured_at` ambiguity write.
328
- report.changed = true;
342
+ if ambiguity_changed {
343
+ agent_obj.insert("attribution_ambiguous".to_string(), serde_json::json!(true));
344
+ agent_obj.insert(
345
+ "capture_state".to_string(),
346
+ serde_json::json!("attribution_ambiguous"),
347
+ );
348
+ // 0.4.6 tuple-atomic contract: see comment above. Removed
349
+ // `captured_at` ambiguity write.
350
+ report.changed = true;
351
+ }
329
352
  }
330
353
  continue;
331
354
  }
@@ -662,6 +685,9 @@ fn pending_session_capture<F>(
662
685
  where
663
686
  F: FnMut(Provider) -> Box<dyn ProviderAdapter>,
664
687
  {
688
+ if agent.get("capture_state").and_then(Value::as_str) == Some("attribution_ambiguous") {
689
+ return None;
690
+ }
665
691
  let provider = incomplete_resumable_provider(agent, adapter_for)?;
666
692
  let spawn_cwd = agent
667
693
  .get("spawn_cwd")
@@ -676,6 +702,17 @@ where
676
702
  .and_then(Value::as_str)
677
703
  .filter(|value| !value.is_empty())
678
704
  .map(str::to_string);
705
+ let expected_session_id = agent
706
+ .get("_pending_session_id")
707
+ .and_then(Value::as_str)
708
+ .filter(|value| !value.is_empty())
709
+ .map(SessionId::new);
710
+ if matches!(provider, Provider::Codex)
711
+ && agent.get("capture_state").and_then(Value::as_str) == Some("pending_context_fork")
712
+ && expected_session_id.is_none()
713
+ {
714
+ return None;
715
+ }
679
716
  Some(PendingSessionCapture {
680
717
  agent_id: agent_id.to_string(),
681
718
  provider,
@@ -699,15 +736,7 @@ where
699
736
  .and_then(Value::as_u64)
700
737
  .and_then(|pid| u32::try_from(pid).ok()),
701
738
  spawned_at,
702
- expected_session_id: if matches!(provider, Provider::Codex) {
703
- None
704
- } else {
705
- agent
706
- .get("_pending_session_id")
707
- .and_then(Value::as_str)
708
- .filter(|value| !value.is_empty())
709
- .map(SessionId::new)
710
- },
739
+ expected_session_id,
711
740
  provider_projects_root: agent
712
741
  .get("claude_projects_root")
713
742
  .and_then(Value::as_str)
@@ -1144,6 +1173,11 @@ fn candidate_key(owner: &PendingSessionCapture, candidate: &CapturedSessionCandi
1144
1173
  .join("|")
1145
1174
  }
1146
1175
 
1176
+ enum AppliedCapture {
1177
+ Session,
1178
+ ContextFork(crate::lifecycle::launch::ContextForkFinalized),
1179
+ }
1180
+
1147
1181
  /// 0.4.6 tuple-atomic contract (audit §Capture 修改清单, line 111): writes
1148
1182
  /// the authoritative session tuple if and only if the candidate carries
1149
1183
  /// BOTH `session_id` and `rollout_path`. A partial candidate (e.g.
@@ -1154,15 +1188,16 @@ fn candidate_key(owner: &PendingSessionCapture, candidate: &CapturedSessionCandi
1154
1188
  fn apply_captured_session(
1155
1189
  agent_obj: &mut serde_json::Map<String, Value>,
1156
1190
  captured: &CapturedSession,
1157
- ) -> bool {
1191
+ ) -> Option<AppliedCapture> {
1158
1192
  if agent_obj.get("capture_state").and_then(Value::as_str) == Some("pending_context_fork") {
1159
- return crate::lifecycle::finalize_pending_fork_capture(agent_obj, captured);
1193
+ return crate::lifecycle::finalize_pending_fork_capture(agent_obj, captured)
1194
+ .map(AppliedCapture::ContextFork);
1160
1195
  }
1161
1196
  let Some(session_id) = captured.session_id.as_ref() else {
1162
- return false;
1197
+ return None;
1163
1198
  };
1164
1199
  let Some(rollout_path) = captured.rollout_path.as_ref() else {
1165
- return false;
1200
+ return None;
1166
1201
  };
1167
1202
  agent_obj.insert(
1168
1203
  "session_id".to_string(),
@@ -1192,7 +1227,7 @@ fn apply_captured_session(
1192
1227
  // diagnose/status observability.
1193
1228
  agent_obj.remove("_pending_session_id");
1194
1229
  agent_obj.insert("capture_state".to_string(), serde_json::json!("captured"));
1195
- true
1230
+ Some(AppliedCapture::Session)
1196
1231
  }
1197
1232
 
1198
1233
  fn claimed_provider_session_keys(
@@ -2621,7 +2656,7 @@ mod u1_tests {
2621
2656
  spawn_cwd: PathBuf::from("/tmp/cwd"),
2622
2657
  };
2623
2658
  let written = super::apply_captured_session(&mut agent, &captured);
2624
- assert!(written, "apply must return true for valid captured");
2659
+ assert!(written.is_some(), "apply must accept valid captured");
2625
2660
  assert!(
2626
2661
  agent.get("_pending_session_id").is_none(),
2627
2662
  "S1-CAPTURE-001: _pending_session_id must be removed after capture \