@team-agent/installer 0.4.2 → 0.4.3
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
CHANGED
package/Cargo.toml
CHANGED
|
@@ -4850,6 +4850,16 @@ fn spec_agents(spec: &Value) -> Vec<AgentId> {
|
|
|
4850
4850
|
.collect()
|
|
4851
4851
|
}
|
|
4852
4852
|
|
|
4853
|
+
/// Bug 1 (0.4.2): expose spec agent id set so the restart path can filter
|
|
4854
|
+
/// state.agents to only the agents currently defined in the rebuilt spec.
|
|
4855
|
+
/// Returns a `BTreeSet<String>` for O(log n) membership checks.
|
|
4856
|
+
pub(crate) fn spec_agent_id_set(spec: &Value) -> std::collections::BTreeSet<String> {
|
|
4857
|
+
spec_agent_values(spec)
|
|
4858
|
+
.into_iter()
|
|
4859
|
+
.filter_map(|agent| agent.get("id").and_then(Value::as_str).map(str::to_string))
|
|
4860
|
+
.collect()
|
|
4861
|
+
}
|
|
4862
|
+
|
|
4853
4863
|
fn spec_agent_values(spec: &Value) -> Vec<&Value> {
|
|
4854
4864
|
spec.get("agents")
|
|
4855
4865
|
.and_then(Value::as_list)
|
|
@@ -142,6 +142,41 @@ pub fn restart_with_transport_with_session_convergence_deadline(
|
|
|
142
142
|
LifecycleError::TeamSelect("active team spec workspace not found".to_string())
|
|
143
143
|
})?;
|
|
144
144
|
let safety = crate::lifecycle::launch::effective_runtime_config(&spec)?;
|
|
145
|
+
// Bug 1 (0.4.2 P0): the rebuilt spec is the single source of truth for the
|
|
146
|
+
// active roster. Any agent in state.agents that is NOT in the rebuilt
|
|
147
|
+
// spec is a stale leftover (role doc was deleted between sessions) and
|
|
148
|
+
// must NOT be restarted — otherwise a `team-agent restart` would re-spawn
|
|
149
|
+
// a removed worker from stale state. Prune state.agents in-place before
|
|
150
|
+
// convergence/plan/spawn so every downstream step (resume validation,
|
|
151
|
+
// event log, spawn loop) sees the bounded roster.
|
|
152
|
+
let spec_agent_ids = crate::lifecycle::launch::spec_agent_id_set(&spec);
|
|
153
|
+
if let Some(agents_obj) = state.get_mut("agents").and_then(|v| v.as_object_mut()) {
|
|
154
|
+
let stale_ids: Vec<String> = agents_obj
|
|
155
|
+
.keys()
|
|
156
|
+
.filter(|id| !spec_agent_ids.contains(id.as_str()))
|
|
157
|
+
.cloned()
|
|
158
|
+
.collect();
|
|
159
|
+
for stale_id in &stale_ids {
|
|
160
|
+
agents_obj.remove(stale_id);
|
|
161
|
+
let _ = crate::event_log::EventLog::new(&selected.run_workspace).write(
|
|
162
|
+
"restart.agent_skipped_not_in_spec",
|
|
163
|
+
serde_json::json!({
|
|
164
|
+
"agent_id": stale_id,
|
|
165
|
+
"team_key": selected.team_key.as_str(),
|
|
166
|
+
"reason": "agent present in state.agents but absent from rebuilt spec; \
|
|
167
|
+
role doc was removed — restart will not respawn removed workers",
|
|
168
|
+
"action": format!(
|
|
169
|
+
"to permanently remove run `team-agent remove-agent {stale_id}`; \
|
|
170
|
+
to re-add restore the role doc under agents/<id>.md and run \
|
|
171
|
+
`team-agent restart`"
|
|
172
|
+
),
|
|
173
|
+
}),
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
if !stale_ids.is_empty() {
|
|
177
|
+
save_restart_state(&selected.run_workspace, &mut state, &selected.team_key)?;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
145
180
|
let mut convergence = converge_missing_provider_sessions(
|
|
146
181
|
&mut state,
|
|
147
182
|
session_convergence_deadline(session_converge_deadline_ms),
|
|
@@ -1072,6 +1072,12 @@ fn scan_session_candidates_once(
|
|
|
1072
1072
|
// identity match (TEAM_AGENT_ID literal in the transcript head, or
|
|
1073
1073
|
// path-encoded agent_id). If none, return empty — capture stays
|
|
1074
1074
|
// pending/ambiguous, which is safer than misattribution.
|
|
1075
|
+
//
|
|
1076
|
+
// Bug 2 (0.4.2 P0) reinforcement: even when positive-identity
|
|
1077
|
+
// filtering returns no candidates, do NOT let downstream
|
|
1078
|
+
// time-window narrowing run for Claude when expected_session_id
|
|
1079
|
+
// misses — that's exactly the leader-session-leak symptom the
|
|
1080
|
+
// architect cataloged in .team/artifacts/bug-042-restart-scope-and-session.md.
|
|
1075
1081
|
if matches!(provider, Provider::Claude | Provider::ClaudeCode) {
|
|
1076
1082
|
let positive_only: Vec<CapturedSessionCandidate> = out
|
|
1077
1083
|
.iter()
|
|
@@ -2044,6 +2050,50 @@ mod e6_session_attribution_tests {
|
|
|
2044
2050
|
f.set_modified(when).unwrap();
|
|
2045
2051
|
}
|
|
2046
2052
|
|
|
2053
|
+
/// Bug 2 (0.4.2 P0): when expected_session_id is set (restart --allow-fresh
|
|
2054
|
+
/// pre-allocates a UUID via --session-id) but the Claude CLI didn't adopt
|
|
2055
|
+
/// it (no <expected>.jsonl on disk), capture must NOT fall back to
|
|
2056
|
+
/// time-window narrowing over leader transcripts sharing the same cwd.
|
|
2057
|
+
/// Pre-fix: leader transcript with later mtime would be silently picked
|
|
2058
|
+
/// up as the worker session. Post-fix: empty list returned (caller treats
|
|
2059
|
+
/// as session-not-yet-captured and retries on next tick).
|
|
2060
|
+
#[test]
|
|
2061
|
+
fn scan_expected_session_id_miss_refuses_to_pick_leader_sibling() {
|
|
2062
|
+
let base = tmp_root("strict-no-leader-fallback");
|
|
2063
|
+
let cwd = base.join("ws");
|
|
2064
|
+
std::fs::create_dir_all(&cwd).unwrap();
|
|
2065
|
+
let proj = base.join("projects");
|
|
2066
|
+
// Simulate the leader's transcript and a stale earlier transcript in
|
|
2067
|
+
// the same cwd. NEITHER matches expected_session_id.
|
|
2068
|
+
let leader = write_transcript(&proj, "11111111-1111-4111-8111-111111111111", &cwd);
|
|
2069
|
+
let stale = write_transcript(&proj, "22222222-2222-4222-8222-222222222222", &cwd);
|
|
2070
|
+
let _ = (leader, stale);
|
|
2071
|
+
let ctx = CaptureSessionContext {
|
|
2072
|
+
agent_id: "claude-worker".to_string(),
|
|
2073
|
+
spawn_cwd: cwd.clone(),
|
|
2074
|
+
pane_id: None,
|
|
2075
|
+
pane_pid: None,
|
|
2076
|
+
spawned_at: Some("2020-01-01T00:00:00+00:00".to_string()),
|
|
2077
|
+
// Worker was spawned with --session-id <expected> but Claude
|
|
2078
|
+
// didn't write <expected>.jsonl.
|
|
2079
|
+
expected_session_id: Some(SessionId::new(
|
|
2080
|
+
"99999999-9999-4999-8999-999999999999",
|
|
2081
|
+
)),
|
|
2082
|
+
provider_projects_root: Some(proj.clone()),
|
|
2083
|
+
};
|
|
2084
|
+
let out = scan_session_candidates_once(Provider::ClaudeCode, &ctx).unwrap();
|
|
2085
|
+
assert!(
|
|
2086
|
+
out.is_empty(),
|
|
2087
|
+
"expected_session_id set + no exact match → must NOT fall back to \
|
|
2088
|
+
time-window narrowing (would grab leader's transcript). \
|
|
2089
|
+
got={:?}",
|
|
2090
|
+
out.iter()
|
|
2091
|
+
.filter_map(|c| c.captured.session_id.as_ref().map(|s| s.as_str().to_string()))
|
|
2092
|
+
.collect::<Vec<_>>()
|
|
2093
|
+
);
|
|
2094
|
+
let _ = std::fs::remove_dir_all(&base);
|
|
2095
|
+
}
|
|
2096
|
+
|
|
2047
2097
|
// ── E11 层1:copilot session 归因(expected-id 优先,不抓 leader latest)──
|
|
2048
2098
|
struct HomeGuard {
|
|
2049
2099
|
prev: Option<std::ffi::OsString>,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@team-agent/installer",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.3",
|
|
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.4.
|
|
24
|
-
"@team-agent/cli-darwin-x64": "0.4.
|
|
25
|
-
"@team-agent/cli-linux-x64": "0.4.
|
|
23
|
+
"@team-agent/cli-darwin-arm64": "0.4.3",
|
|
24
|
+
"@team-agent/cli-darwin-x64": "0.4.3",
|
|
25
|
+
"@team-agent/cli-linux-x64": "0.4.3"
|
|
26
26
|
},
|
|
27
27
|
"scripts": {
|
|
28
28
|
"postinstall": "node npm/bincheck.mjs",
|