@team-agent/installer 0.5.33 → 0.5.35
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
|
@@ -52,6 +52,14 @@ pub fn agent_summary_counts(agents: &Value, health: &Value) -> SummaryCounts {
|
|
|
52
52
|
.and_then(|v| v.get("status"))
|
|
53
53
|
.and_then(Value::as_str)
|
|
54
54
|
.unwrap_or("");
|
|
55
|
+
// 0.5.35 R4 (`.team/artifacts/managed-leader-provider-reentry-locate.md`):
|
|
56
|
+
// canonical `worker_state=UNKNOWN` / `activity.status=uncertain`
|
|
57
|
+
// beats legacy `agent_health.status=WORKING` so the five-line
|
|
58
|
+
// summary stops counting a runtime-honestly-UNKNOWN agent as busy.
|
|
59
|
+
if canonical_worker_state_is_unknown(agent) {
|
|
60
|
+
counts.unknown += 1;
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
55
63
|
match classify_agent_bucket(raw, hstatus) {
|
|
56
64
|
SummaryBucket::Running => counts.running += 1,
|
|
57
65
|
SummaryBucket::Busy => counts.busy += 1,
|
|
@@ -64,6 +72,22 @@ pub fn agent_summary_counts(agents: &Value, health: &Value) -> SummaryCounts {
|
|
|
64
72
|
counts
|
|
65
73
|
}
|
|
66
74
|
|
|
75
|
+
/// 0.5.35 R4: return true when the runtime classifier has written a
|
|
76
|
+
/// canonical non-decisive observation onto the agent row. Used by
|
|
77
|
+
/// `agent_summary_counts` and `csv_agent_status` so the "UNKNOWN-first"
|
|
78
|
+
/// signal beats legacy `agent_health.status`.
|
|
79
|
+
fn canonical_worker_state_is_unknown(agent: &Value) -> bool {
|
|
80
|
+
let worker_state_unknown = agent
|
|
81
|
+
.get("worker_state")
|
|
82
|
+
.and_then(Value::as_str)
|
|
83
|
+
.is_some_and(|value| value.eq_ignore_ascii_case("UNKNOWN"));
|
|
84
|
+
let activity_uncertain = agent
|
|
85
|
+
.pointer("/activity/status")
|
|
86
|
+
.and_then(Value::as_str)
|
|
87
|
+
.is_some_and(|value| value.eq_ignore_ascii_case("uncertain"));
|
|
88
|
+
worker_state_unknown || activity_uncertain
|
|
89
|
+
}
|
|
90
|
+
|
|
67
91
|
fn stale_agent_bucket(agent: &Value) -> Option<SummaryBucket> {
|
|
68
92
|
if agent.get("stale").and_then(Value::as_bool) != Some(true) {
|
|
69
93
|
return None;
|
|
@@ -224,6 +248,10 @@ fn csv_agent_status(
|
|
|
224
248
|
if matches!(raw.as_str(), "running" | "busy" | "working") && !pane_present {
|
|
225
249
|
return "错误";
|
|
226
250
|
}
|
|
251
|
+
// 0.5.35 R4: canonical UNKNOWN beats legacy WORKING in human CSV too.
|
|
252
|
+
if canonical_worker_state_is_unknown(agent) {
|
|
253
|
+
return "未知";
|
|
254
|
+
}
|
|
227
255
|
if raw == "idle" || hstatus == "idle" {
|
|
228
256
|
return "空闲";
|
|
229
257
|
}
|
|
@@ -223,7 +223,22 @@ pub fn execute_leader_plan(
|
|
|
223
223
|
&& !std::io::stdin().is_terminal()
|
|
224
224
|
&& insert_detach_flag(&mut argv);
|
|
225
225
|
if plan.mode == LeaderStartMode::ExecProvider && !plan.is_external_leader {
|
|
226
|
-
|
|
226
|
+
// 0.5.35 (`.team/artifacts/managed-leader-provider-reentry-locate.md`
|
|
227
|
+
// §5/§6): three-way classify BEFORE persist. Same physical pane =
|
|
228
|
+
// provider process replacement (refresh only). Different pane with
|
|
229
|
+
// an existing canonical binding = no canonical rewrite (claim /
|
|
230
|
+
// takeover is the recovery path, §7). Unbound = first-time write.
|
|
231
|
+
match classify_exec_provider_binding(workspace, plan)? {
|
|
232
|
+
ExecProviderBinding::ManagedReentry => {
|
|
233
|
+
refresh_managed_leader_provider_binding(plan, workspace)?;
|
|
234
|
+
}
|
|
235
|
+
ExecProviderBinding::DifferentPaneAlreadyBound => {
|
|
236
|
+
// No canonical state write. Provider still runs below.
|
|
237
|
+
}
|
|
238
|
+
ExecProviderBinding::Unbound => {
|
|
239
|
+
persist_exec_provider_leader_binding(plan, workspace)?;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
227
242
|
} else if plan.is_external_leader {
|
|
228
243
|
persist_external_leader_topology_marker(plan, workspace)?;
|
|
229
244
|
}
|
|
@@ -904,6 +919,184 @@ fn persist_exec_provider_leader_binding(
|
|
|
904
919
|
Ok(())
|
|
905
920
|
}
|
|
906
921
|
|
|
922
|
+
/// 0.5.35 (`.team/artifacts/managed-leader-provider-reentry-locate.md` §5/§6):
|
|
923
|
+
/// three-way classification of an in-tmux ExecProvider invocation. Physical-
|
|
924
|
+
/// channel-first — never consults `pane_current_command` or gates on
|
|
925
|
+
/// `leader_session_uuid` equality (§6 forbids uuid-mismatch hard refusal).
|
|
926
|
+
enum ExecProviderBinding {
|
|
927
|
+
ManagedReentry,
|
|
928
|
+
DifferentPaneAlreadyBound,
|
|
929
|
+
Unbound,
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
fn classify_exec_provider_binding(
|
|
933
|
+
workspace: &Path,
|
|
934
|
+
plan: &LeaderStartPlan,
|
|
935
|
+
) -> Result<ExecProviderBinding, LeaderError> {
|
|
936
|
+
let Some(pane) = std::env::var("TMUX_PANE")
|
|
937
|
+
.ok()
|
|
938
|
+
.filter(|value| !value.trim().is_empty())
|
|
939
|
+
else {
|
|
940
|
+
return Ok(ExecProviderBinding::Unbound);
|
|
941
|
+
};
|
|
942
|
+
let state = match crate::state::persist::load_runtime_state(workspace) {
|
|
943
|
+
Ok(state) => state,
|
|
944
|
+
Err(_) => return Ok(ExecProviderBinding::Unbound),
|
|
945
|
+
};
|
|
946
|
+
let team_key = plan
|
|
947
|
+
.identity
|
|
948
|
+
.as_ref()
|
|
949
|
+
.map(|identity| identity.team_id.as_str().to_string())
|
|
950
|
+
.or_else(|| {
|
|
951
|
+
state
|
|
952
|
+
.get("active_team_key")
|
|
953
|
+
.and_then(serde_json::Value::as_str)
|
|
954
|
+
.filter(|s| !s.is_empty())
|
|
955
|
+
.map(str::to_string)
|
|
956
|
+
});
|
|
957
|
+
let Some(team_key) = team_key else {
|
|
958
|
+
return Ok(ExecProviderBinding::Unbound);
|
|
959
|
+
};
|
|
960
|
+
let Some(receiver) = state
|
|
961
|
+
.pointer(&format!("/teams/{team_key}/leader_receiver"))
|
|
962
|
+
.and_then(serde_json::Value::as_object)
|
|
963
|
+
else {
|
|
964
|
+
return Ok(ExecProviderBinding::Unbound);
|
|
965
|
+
};
|
|
966
|
+
let receiver_pane = receiver
|
|
967
|
+
.get("pane_id")
|
|
968
|
+
.or_else(|| receiver.get("pane"))
|
|
969
|
+
.and_then(serde_json::Value::as_str)
|
|
970
|
+
.unwrap_or("");
|
|
971
|
+
if receiver_pane.is_empty() {
|
|
972
|
+
return Ok(ExecProviderBinding::Unbound);
|
|
973
|
+
}
|
|
974
|
+
if receiver_pane != pane {
|
|
975
|
+
return Ok(ExecProviderBinding::DifferentPaneAlreadyBound);
|
|
976
|
+
}
|
|
977
|
+
let pane_id = PaneId::new(pane.clone());
|
|
978
|
+
let target = current_tmux_pane_info(&pane_id);
|
|
979
|
+
let leader_prefixed_session = target
|
|
980
|
+
.as_ref()
|
|
981
|
+
.map(|t| t.session.as_str().starts_with(LEADER_SESSION_PREFIX))
|
|
982
|
+
.unwrap_or(false);
|
|
983
|
+
if !leader_prefixed_session {
|
|
984
|
+
return Ok(ExecProviderBinding::DifferentPaneAlreadyBound);
|
|
985
|
+
}
|
|
986
|
+
let recorded_socket = receiver
|
|
987
|
+
.get("tmux_socket")
|
|
988
|
+
.and_then(serde_json::Value::as_str)
|
|
989
|
+
.filter(|s| !s.is_empty());
|
|
990
|
+
if let Some(recorded_socket) = recorded_socket {
|
|
991
|
+
let caller_socket = crate::tmux_backend::socket_name_from_tmux_env();
|
|
992
|
+
if caller_socket.as_deref() != Some(recorded_socket) {
|
|
993
|
+
return Ok(ExecProviderBinding::DifferentPaneAlreadyBound);
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
Ok(ExecProviderBinding::ManagedReentry)
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
/// 0.5.35: same-pane provider re-entry refresh path. Preserves canonical
|
|
1000
|
+
/// team runtime identity (`session_name`/`team_dir`/`spec_path`/`agents`/
|
|
1001
|
+
/// `tasks`) and epoch non-regressively; updates only diagnostic receiver
|
|
1002
|
+
/// fields. Stage 3 strip preserved (root `team_owner`/`leader_receiver`/
|
|
1003
|
+
/// `owner_epoch` are NOT reintroduced — writes go through
|
|
1004
|
+
/// `state::ownership::write_owner` into the canonical teams.<key> slot).
|
|
1005
|
+
fn refresh_managed_leader_provider_binding(
|
|
1006
|
+
plan: &LeaderStartPlan,
|
|
1007
|
+
workspace: &Path,
|
|
1008
|
+
) -> Result<(), LeaderError> {
|
|
1009
|
+
let identity = plan
|
|
1010
|
+
.identity
|
|
1011
|
+
.as_ref()
|
|
1012
|
+
.ok_or_else(|| LeaderError::Start("managed leader re-entry identity missing".to_string()))?;
|
|
1013
|
+
let pane = std::env::var("TMUX_PANE")
|
|
1014
|
+
.ok()
|
|
1015
|
+
.filter(|value| !value.trim().is_empty())
|
|
1016
|
+
.ok_or_else(|| LeaderError::Start("managed leader re-entry pane missing".to_string()))?;
|
|
1017
|
+
let pane_id = PaneId::new(pane.clone());
|
|
1018
|
+
let target = current_tmux_pane_info(&pane_id);
|
|
1019
|
+
let mut state = crate::state::persist::load_runtime_state(workspace)
|
|
1020
|
+
.unwrap_or_else(|_| serde_json::json!({}));
|
|
1021
|
+
let team_key = identity.team_id.as_str();
|
|
1022
|
+
let existing_receiver = state
|
|
1023
|
+
.pointer(&format!("/teams/{team_key}/leader_receiver"))
|
|
1024
|
+
.cloned()
|
|
1025
|
+
.unwrap_or_else(|| serde_json::json!({}));
|
|
1026
|
+
let existing_owner = crate::state::ownership::read_owner_value(&state, team_key)
|
|
1027
|
+
.cloned()
|
|
1028
|
+
.unwrap_or_else(|| serde_json::json!({}));
|
|
1029
|
+
let existing_epoch = state
|
|
1030
|
+
.pointer(&format!("/teams/{team_key}/owner_epoch"))
|
|
1031
|
+
.and_then(serde_json::Value::as_u64)
|
|
1032
|
+
.or_else(|| existing_owner.get("owner_epoch").and_then(serde_json::Value::as_u64))
|
|
1033
|
+
.or_else(|| existing_receiver.get("owner_epoch").and_then(serde_json::Value::as_u64))
|
|
1034
|
+
.unwrap_or(0);
|
|
1035
|
+
let now = chrono::Utc::now().to_rfc3339();
|
|
1036
|
+
let provider = serde_json::to_value(plan.provider)?;
|
|
1037
|
+
let mut receiver = match existing_receiver.as_object() {
|
|
1038
|
+
Some(map) => serde_json::Value::Object(map.clone()),
|
|
1039
|
+
None => serde_json::json!({}),
|
|
1040
|
+
};
|
|
1041
|
+
if let Some(obj) = receiver.as_object_mut() {
|
|
1042
|
+
obj.insert("mode".to_string(), serde_json::json!("direct_tmux"));
|
|
1043
|
+
obj.insert("status".to_string(), serde_json::json!("attached"));
|
|
1044
|
+
obj.insert("provider".to_string(), provider.clone());
|
|
1045
|
+
obj.insert("pane_id".to_string(), serde_json::json!(pane));
|
|
1046
|
+
obj.insert("pane".to_string(), serde_json::json!(pane));
|
|
1047
|
+
obj.insert("attached_at".to_string(), serde_json::json!(now.clone()));
|
|
1048
|
+
obj.insert("discovery".to_string(), serde_json::json!("managed_leader"));
|
|
1049
|
+
obj.insert("owner_epoch".to_string(), serde_json::json!(existing_epoch));
|
|
1050
|
+
obj.entry("leader_session_uuid".to_string())
|
|
1051
|
+
.or_insert_with(|| serde_json::json!(identity.leader_session_uuid.as_str()));
|
|
1052
|
+
if let Some(target) = target.as_ref() {
|
|
1053
|
+
obj.insert("session_name".to_string(), serde_json::json!(target.session.as_str()));
|
|
1054
|
+
if let Some(window_name) = target.window_name.as_ref() {
|
|
1055
|
+
obj.insert("window_name".to_string(), serde_json::json!(window_name.as_str()));
|
|
1056
|
+
}
|
|
1057
|
+
if let Some(pane_pid) = target.pane_pid {
|
|
1058
|
+
obj.insert("pane_pid".to_string(), serde_json::json!(pane_pid));
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
let mut owner = match existing_owner.as_object() {
|
|
1063
|
+
Some(map) => serde_json::Value::Object(map.clone()),
|
|
1064
|
+
None => serde_json::json!({}),
|
|
1065
|
+
};
|
|
1066
|
+
if let Some(obj) = owner.as_object_mut() {
|
|
1067
|
+
obj.insert("pane_id".to_string(), serde_json::json!(pane));
|
|
1068
|
+
obj.insert("provider".to_string(), provider);
|
|
1069
|
+
obj.insert("owner_epoch".to_string(), serde_json::json!(existing_epoch));
|
|
1070
|
+
obj.entry("machine_fingerprint".to_string())
|
|
1071
|
+
.or_insert_with(|| serde_json::json!(identity.machine_fingerprint.as_str()));
|
|
1072
|
+
obj.entry("leader_session_uuid".to_string())
|
|
1073
|
+
.or_insert_with(|| serde_json::json!(identity.leader_session_uuid.as_str()));
|
|
1074
|
+
obj.entry("claimed_via".to_string())
|
|
1075
|
+
.or_insert_with(|| serde_json::json!("claim-leader"));
|
|
1076
|
+
obj.entry("os_user".to_string())
|
|
1077
|
+
.or_insert_with(|| serde_json::json!(identity.os_user.as_str()));
|
|
1078
|
+
}
|
|
1079
|
+
if let Some(obj) = state.as_object_mut() {
|
|
1080
|
+
obj.insert("active_team_key".to_string(), serde_json::json!(team_key));
|
|
1081
|
+
obj.insert(
|
|
1082
|
+
"leader_client".to_string(),
|
|
1083
|
+
serde_json::json!({
|
|
1084
|
+
"diagnostic_only": true,
|
|
1085
|
+
"attach_mode": "exec-provider",
|
|
1086
|
+
"tmux": std::env::var("TMUX").ok(),
|
|
1087
|
+
"reentry": true,
|
|
1088
|
+
}),
|
|
1089
|
+
);
|
|
1090
|
+
}
|
|
1091
|
+
let record = crate::state::ownership::OwnershipWrite::new()
|
|
1092
|
+
.with_leader_receiver(receiver)
|
|
1093
|
+
.with_team_owner(owner)
|
|
1094
|
+
.with_owner_epoch(existing_epoch);
|
|
1095
|
+
crate::state::ownership::write_owner(&mut state, team_key, record);
|
|
1096
|
+
crate::state::persist::save_runtime_state(workspace, &state)?;
|
|
1097
|
+
Ok(())
|
|
1098
|
+
}
|
|
1099
|
+
|
|
907
1100
|
fn current_tmux_pane_info(pane_id: &PaneId) -> Option<crate::transport::PaneInfo> {
|
|
908
1101
|
tmux_transport_for_current_pane()
|
|
909
1102
|
.list_targets()
|
|
@@ -163,20 +163,27 @@ pub(crate) fn latest_prompt_signal(scrollback: &str) -> Option<AgentActivity> {
|
|
|
163
163
|
}
|
|
164
164
|
let mut has_idle_prompt = false;
|
|
165
165
|
let mut has_live_working_indicator = false;
|
|
166
|
+
let mut has_fake_ready_structural = false;
|
|
166
167
|
for line in &active_region {
|
|
167
168
|
let lower = line.to_ascii_lowercase();
|
|
168
169
|
if line.contains('❯') || line.contains('›') {
|
|
169
170
|
has_idle_prompt = true;
|
|
170
171
|
}
|
|
171
|
-
// 0.5.
|
|
172
|
+
// 0.5.34 (`0532-r1-real-fail-triage.md` §6/§7 + te msg_94957b9c55b0):
|
|
172
173
|
// fake provider owns explicit READY/WORKING markers (fake_worker.rs:47/59).
|
|
173
|
-
//
|
|
174
|
-
//
|
|
175
|
-
//
|
|
176
|
-
//
|
|
177
|
-
//
|
|
174
|
+
// - READY is a STRUCTURAL non-busy signal that suppresses the
|
|
175
|
+
// `recent_provider_output` false-busy on first post-restart capture,
|
|
176
|
+
// but must NOT resolve to Idle (unknown-never-idle discipline —
|
|
177
|
+
// post-restart READY is the fake worker's boot heartbeat, not proof
|
|
178
|
+
// of an idle prompt). Route it through Uncertain with a distinct
|
|
179
|
+
// rationale so callers can trace the source.
|
|
180
|
+
// - WORKING is a structural busy signal (parity with the codex
|
|
181
|
+
// `• Working (` composer indicator).
|
|
182
|
+
// Scope: literal `TEAM_AGENT_FAKE_*` tokens only — do NOT infer that
|
|
183
|
+
// arbitrary "ready" prose from real providers means either idle or
|
|
184
|
+
// non-busy.
|
|
178
185
|
if line.contains("TEAM_AGENT_FAKE_READY") {
|
|
179
|
-
|
|
186
|
+
has_fake_ready_structural = true;
|
|
180
187
|
}
|
|
181
188
|
if line.contains("TEAM_AGENT_FAKE_WORKING") {
|
|
182
189
|
has_live_working_indicator = true;
|
|
@@ -208,6 +215,17 @@ pub(crate) fn latest_prompt_signal(scrollback: &str) -> Option<AgentActivity> {
|
|
|
208
215
|
if has_idle_prompt {
|
|
209
216
|
return Some(idle_activity());
|
|
210
217
|
}
|
|
218
|
+
// 0.5.34: fake READY is structural but not decisive. Return Uncertain
|
|
219
|
+
// so the classifier short-circuits (avoiding recent_provider_output
|
|
220
|
+
// false-busy) without asserting idle. `latest_pane_signal_is_structural`
|
|
221
|
+
// in coordinator/tick.rs treats `Some(_)` as structural.
|
|
222
|
+
if has_fake_ready_structural {
|
|
223
|
+
return Some(AgentActivity {
|
|
224
|
+
status: ActivityStatus::Uncertain,
|
|
225
|
+
confidence: 0.6,
|
|
226
|
+
rationale: "fake_ready_structural".to_string(),
|
|
227
|
+
});
|
|
228
|
+
}
|
|
211
229
|
// No structural signal in the bottom region → caller (activity.rs:184)
|
|
212
230
|
// gets None and treats as no-decisive-signal → Uncertain (IRON LAW).
|
|
213
231
|
None
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@team-agent/installer",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.35",
|
|
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.35",
|
|
24
|
+
"@team-agent/cli-darwin-x64": "0.5.35",
|
|
25
|
+
"@team-agent/cli-linux-x64": "0.5.35"
|
|
26
26
|
},
|
|
27
27
|
"scripts": {
|
|
28
28
|
"postinstall": "node npm/bincheck.mjs",
|