@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.
- package/Cargo.lock +1 -1
- package/Cargo.toml +1 -1
- package/crates/team-agent/src/cli/emit.rs +30 -2
- package/crates/team-agent/src/cli/mod.rs +1 -0
- package/crates/team-agent/src/cli/send/mailbox.rs +68 -25
- package/crates/team-agent/src/cli/spec.rs +1 -1
- package/crates/team-agent/src/lifecycle/launch/fork_agent.rs +89 -95
- package/crates/team-agent/src/lifecycle/launch/fork_finalize.rs +104 -0
- package/crates/team-agent/src/lifecycle/launch/fork_pending.rs +109 -0
- package/crates/team-agent/src/lifecycle/launch/fork_state.rs +49 -0
- package/crates/team-agent/src/lifecycle/launch.rs +1 -0
- package/crates/team-agent/src/lifecycle/types.rs +8 -0
- package/crates/team-agent/src/mcp_server/lifecycle_tools/agent_ops.rs +1 -0
- package/crates/team-agent/src/mcp_server/normalize.rs +53 -1
- package/crates/team-agent/src/mcp_server/tools.rs +8 -1
- package/crates/team-agent/src/provider/session/capture.rs +37 -1
- package/crates/team-agent/src/provider/session/context_fork/claude.rs +85 -0
- package/crates/team-agent/src/provider/session/context_fork/codex.rs +65 -0
- package/crates/team-agent/src/provider/session/context_fork/outcome.rs +138 -0
- package/crates/team-agent/src/provider/session/context_fork.rs +30 -135
- package/crates/team-agent/src/provider/session/mod.rs +2 -1
- package/crates/team-agent/src/provider/session_scan/claude.rs +1 -1
- package/package.json +4 -4
- package/skills/team-agent/SKILL.md +1 -0
|
@@ -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,
|
|
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,
|
|
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(
|
|
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.
|
|
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.
|
|
24
|
-
"@team-agent/cli-darwin-x64": "0.5.
|
|
25
|
-
"@team-agent/cli-linux-x64": "0.5.
|
|
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.
|