@team-agent/installer 0.5.57 → 0.5.59

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.
@@ -5,7 +5,28 @@ use std::time::{Duration, SystemTime};
5
5
  use crate::model::enums::Provider;
6
6
  use crate::provider::{CommandPlan, ProviderError, SessionId};
7
7
 
8
+ #[derive(Debug, thiserror::Error)]
9
+ pub(crate) enum ContextForkTermination {
10
+ #[error(
11
+ "context_fork_unverified: {provider:?} produced no readable NEW session backing within {deadline_ms}ms"
12
+ )]
13
+ Timeout {
14
+ provider: Provider,
15
+ deadline_ms: u128,
16
+ },
17
+ #[error(transparent)]
18
+ Rejected(#[from] ProviderError),
19
+ }
20
+
21
+ mod claude;
22
+ mod codex;
23
+ mod outcome;
24
+ pub(crate) use outcome::{
25
+ observe_context_fork, transition_pending_context_fork, ContextForkOutcome, PendingContextFork,
26
+ };
27
+
8
28
  pub(crate) fn context_fork_convergence_deadline(provider: Provider) -> Duration {
29
+ // Expiration is consumed by ContextForkOutcome::Pending(PendingContextFork).
9
30
  match provider {
10
31
  Provider::Claude | Provider::ClaudeCode => Duration::from_secs(45),
11
32
  Provider::Codex => Duration::from_secs(10),
@@ -54,12 +75,12 @@ pub(crate) fn verify_context_fork(
54
75
  spawn_cwd: &Path,
55
76
  spawned_at: &str,
56
77
  deadline: Duration,
57
- ) -> Result<ContextForkProof, ProviderError> {
78
+ ) -> Result<ContextForkProof, ContextForkTermination> {
58
79
  if provider == Provider::Copilot {
59
- return verify_copilot_fork(source_session_id, plan);
80
+ return verify_copilot_fork(source_session_id, plan).map_err(Into::into);
60
81
  }
61
82
  if provider == Provider::Codex {
62
- return verify_codex_fork(
83
+ return codex::verify_codex_fork(
63
84
  source_session_id,
64
85
  plan,
65
86
  before,
@@ -70,132 +91,20 @@ pub(crate) fn verify_context_fork(
70
91
  );
71
92
  }
72
93
  if matches!(provider, Provider::Claude | Provider::ClaudeCode) {
73
- return verify_claude_fork(
94
+ return claude::verify_claude_fork(
74
95
  provider,
75
96
  source_session_id,
76
97
  plan,
77
98
  before,
78
99
  expected_backing_path,
100
+ spawn_cwd,
79
101
  deadline,
80
102
  );
81
103
  }
82
104
  Err(ProviderError::CaptureFailed(format!(
83
105
  "context_fork_unverified: {provider:?} has no verifiable fork backing"
84
- )))
85
- }
86
- fn verify_claude_fork(
87
- provider: Provider,
88
- source_session_id: &SessionId,
89
- plan: &CommandPlan,
90
- before: &ContextBackingSnapshot,
91
- expected_backing_path: Option<&Path>,
92
- deadline: Duration,
93
- ) -> Result<ContextForkProof, ProviderError> {
94
- let expected = plan.expected_session_id.as_ref().ok_or_else(|| {
95
- ProviderError::CaptureFailed(
96
- "context_fork_unverified: Claude plan has no expected session id".to_string(),
97
- )
98
- })?;
99
- let path = expected_backing_path.ok_or_else(|| {
100
- ProviderError::CaptureFailed(
101
- "context_fork_unverified: Claude plan has no exact snapshot backing".to_string(),
102
- )
103
- })?;
104
- let expected_name = format!("{}.jsonl", expected.as_str());
105
- if path.file_name().and_then(|name| name.to_str()) != Some(expected_name.as_str()) {
106
- return Err(ProviderError::CaptureFailed(format!(
107
- "context_fork_unverified: Claude snapshot backing does not match expected session {}",
108
- expected.as_str()
109
- )));
110
- }
111
- let started = std::time::Instant::now();
112
- loop {
113
- if let Some(stamp) = file_stamp(path) {
114
- let changed = before.files.get(path).is_none_or(|old| *old != stamp);
115
- let observed_matches =
116
- session_id_from_jsonl(path).is_none_or(|observed| observed == expected.as_str());
117
- if changed && readable_jsonl(path) && observed_matches && expected != source_session_id
118
- {
119
- return Ok(ContextForkProof {
120
- provider,
121
- source_session_id: source_session_id.clone(),
122
- new_session_id: expected.clone(),
123
- backing_path: path.to_path_buf(),
124
- captured_via: "context_fork_verified".to_string(),
125
- attribution_confidence: "high".to_string(),
126
- managed_backing_root: None,
127
- });
128
- }
129
- }
130
- if started.elapsed() >= deadline {
131
- break;
132
- }
133
- std::thread::sleep(Duration::from_millis(50));
134
- }
135
- Err(ProviderError::CaptureFailed(format!(
136
- "context_fork_unverified: {provider:?} produced no readable NEW session backing within {}ms",
137
- deadline.as_millis()
138
- )))
139
- }
140
-
141
- fn verify_codex_fork(
142
- source_session_id: &SessionId,
143
- plan: &CommandPlan,
144
- before: &ContextBackingSnapshot,
145
- agent_id: &str,
146
- spawn_cwd: &Path,
147
- spawned_at: &str,
148
- deadline: Duration,
149
- ) -> Result<ContextForkProof, ProviderError> {
150
- let context = crate::provider::session_scan::CaptureSessionContext {
151
- agent_id: agent_id.to_string(),
152
- spawn_cwd: spawn_cwd.to_path_buf(),
153
- pane_id: None,
154
- pane_pid: None,
155
- spawned_at: Some(spawned_at.to_string()),
156
- expected_session_id: plan.expected_session_id.clone(),
157
- provider_projects_root: plan.provider_projects_root.clone(),
158
- };
159
- let started = std::time::Instant::now();
160
- loop {
161
- let current = jsonl_files(&before.root);
162
- for candidate in
163
- crate::provider::session_scan::scan_session_candidates_once(Provider::Codex, &context)?
164
- {
165
- let Some(path) = candidate.captured.rollout_path.as_ref() else {
166
- continue;
167
- };
168
- let Some(stamp) = current.get(path.as_path()) else {
169
- continue;
170
- };
171
- if before.files.get(path.as_path()) == Some(stamp) {
172
- continue;
173
- }
174
- let Some(new_session_id) = candidate.captured.session_id else {
175
- continue;
176
- };
177
- if new_session_id == *source_session_id {
178
- continue;
179
- }
180
- return Ok(ContextForkProof {
181
- provider: Provider::Codex,
182
- source_session_id: source_session_id.clone(),
183
- new_session_id,
184
- backing_path: path.as_path().to_path_buf(),
185
- captured_via: "context_fork_verified".to_string(),
186
- attribution_confidence: "high".to_string(),
187
- managed_backing_root: None,
188
- });
189
- }
190
- if started.elapsed() >= deadline {
191
- break;
192
- }
193
- std::thread::sleep(Duration::from_millis(50));
194
- }
195
- Err(ProviderError::CaptureFailed(format!(
196
- "context_fork_unverified: Codex produced no readable NEW session backing within {}ms",
197
- deadline.as_millis()
198
- )))
106
+ ))
107
+ .into())
199
108
  }
200
109
 
201
110
  fn verify_copilot_fork(
@@ -242,6 +151,8 @@ fn verify_copilot_fork(
242
151
  "context_fork_unverified: copilot NEW session is absent from session-store".to_string(),
243
152
  ));
244
153
  }
154
+ // Copilot's isolated database row is its synchronous fork proof; the
155
+ // pre-spawn materialization baseline is never accepted by transcript scan.
245
156
  Ok(ContextForkProof {
246
157
  provider: Provider::Copilot,
247
158
  source_session_id: source_session_id.clone(),
@@ -335,22 +246,6 @@ mod tests {
335
246
  use super::*;
336
247
  use std::io::Write;
337
248
 
338
- #[test]
339
- fn context_fork_deadlines_are_provider_specific() {
340
- assert_eq!(
341
- context_fork_convergence_deadline(Provider::Claude),
342
- Duration::from_secs(45)
343
- );
344
- assert_eq!(
345
- context_fork_convergence_deadline(Provider::Codex),
346
- Duration::from_secs(10)
347
- );
348
- assert_eq!(
349
- context_fork_convergence_deadline(Provider::Copilot),
350
- Duration::from_secs(5)
351
- );
352
- }
353
-
354
249
  #[test]
355
250
  fn codex_fork_proof_ignores_changed_stale_and_foreign_rollouts() {
356
251
  let root =
@@ -10,7 +10,8 @@ pub mod resume;
10
10
 
11
11
  pub use context_fork::ContextForkProof;
12
12
  pub(crate) use context_fork::{
13
- context_fork_convergence_deadline, verify_context_fork, ContextBackingSnapshot,
13
+ context_fork_convergence_deadline, observe_context_fork, transition_pending_context_fork,
14
+ ContextBackingSnapshot, ContextForkOutcome, PendingContextFork,
14
15
  };
15
16
 
16
17
  pub use resume::{
@@ -5,7 +5,7 @@ use crate::provider::Provider;
5
5
 
6
6
  use super::{CaptureSessionContext, CapturedSessionCandidate};
7
7
 
8
- pub(super) fn projects_dir_for_cwd(home: &Path, spawn_cwd: &Path) -> Option<PathBuf> {
8
+ pub(crate) fn projects_dir_for_cwd(home: &Path, spawn_cwd: &Path) -> Option<PathBuf> {
9
9
  let canonical = std::fs::canonicalize(spawn_cwd).unwrap_or_else(|_| spawn_cwd.to_path_buf());
10
10
  let encoded = encode_projects_dir(&canonical.to_string_lossy());
11
11
  if encoded.is_empty() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@team-agent/installer",
3
- "version": "0.5.57",
3
+ "version": "0.5.59",
4
4
  "description": "npx installer for Team Agent",
5
5
  "keywords": [
6
6
  "codex",
@@ -20,9 +20,9 @@
20
20
  "team-agent-installer": "npm/install.mjs"
21
21
  },
22
22
  "optionalDependencies": {
23
- "@team-agent/cli-darwin-arm64": "0.5.57",
24
- "@team-agent/cli-darwin-x64": "0.5.57",
25
- "@team-agent/cli-linux-x64": "0.5.57"
23
+ "@team-agent/cli-darwin-arm64": "0.5.59",
24
+ "@team-agent/cli-darwin-x64": "0.5.59",
25
+ "@team-agent/cli-linux-x64": "0.5.59"
26
26
  },
27
27
  "scripts": {
28
28
  "postinstall": "node npm/bincheck.mjs",
@@ -166,6 +166,7 @@ For diagnosis, run `team-agent profile show deepseek --workspace . --json`; neve
166
166
  - `quick-start` is only for first-time team creation from role docs. If that team already has runtime state, use `team-agent restart . --team <session_name_or_team_name>` to resume it. If restart cannot recover context, explain the loss and wait for explicit user consent before using `team-agent restart . --allow-fresh`; never reset context through quick-start.
167
167
  - If the user explicitly asks a worker to create or operate a nested child team, first read `references/team-in-team.md`. Child teams must use an independent child workspace, never the parent `.team/current`.
168
168
  - `team-agent send --watch-result coder "Do the bounded task"` sends a direct worker message, returns after delivery, and lets the coordinator collect/report completion asynchronously.
169
+ - Positional `TO` has two co-equal forms: an in-team short name, for example `team-agent send reviewer "Review this change"`, and a fully-qualified logical name, `<workspace>::<team>/<agent>`. Use the fully-qualified form across workspaces or when the local team scope is ambiguous.
169
170
  - Advanced orchestration callers may add `--presentation-sink leader|casefile|silent --message-class CLASS [--case-id CASE]`. All sinks remain durable and pullable; `casefile`/`silent` suppress only live leader injection. Missing presentation metadata preserves the normal leader-visible behavior.
170
171
  - After `send --watch-result` succeeds, do not run `sleep`, `status`, `inbox`, or `collect` polling loops unless the user explicitly asks for diagnosis; the coordinator will notify the leader when the result arrives.
171
172
  - `team-agent send --task task_initial "Start"` routes by task.