@team-agent/installer 0.5.6 → 0.5.8

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.
@@ -112,29 +112,43 @@ fn send_outcome_direct_renders_compact_body() {
112
112
  // ════════════════════════════════════════════════════════════════════════
113
113
  // CONTROL-PLANE: send_message worker recipient → WorkerAccepted (tools.py:135-183)
114
114
  // ════════════════════════════════════════════════════════════════════════
115
- #[test]
116
- fn send_message_worker_recipient_returns_accepted_with_poll_hint() {
117
- // A worker recipient w/ a delivered message_id → async accepted carrying the
118
- // byte-stable poll hint. Identity anchored on injected env (no candidate scan).
119
- // golden: a leader WITH owner_team_id on an unseeded ws would hit the C23 cross-team
120
- // refusal first (worker-1 not in visible peers) -> PeerNotInScope. owner_team_id=None
121
- // (legacy single-team) bypasses that, isolating the worker-recipient accepted path.
122
- // The cross-team refusal has its own tests (refuse_cross_team_peer_* / send_message_cross_team_*).
123
- // A-7: the accepted path needs a REAL stored message_id (tools.py:175-181 —
124
- // fabricated ids are gone), so the workspace seeds a running worker-1 the
125
- // delivery layer can actually queue for.
126
- let ws = unique_ws("send-worker");
115
+ fn seed_current_worker_state(tag: &str) -> std::path::PathBuf {
116
+ let ws = unique_ws(tag);
127
117
  crate::state::persist::save_runtime_state(
128
118
  &ws,
129
119
  &serde_json::json!({
130
120
  "session_name": "team-x",
121
+ "active_team_key": "current",
131
122
  "agents": {
132
123
  "worker-1": {"status": "running", "agent_id": "worker-1", "window": "worker-1"},
133
124
  },
125
+ "teams": {
126
+ "current": {
127
+ "status": "alive",
128
+ "agents": {
129
+ "worker-1": {"status": "running", "agent_id": "worker-1", "window": "worker-1"},
130
+ },
131
+ },
132
+ },
134
133
  }),
135
134
  )
136
135
  .unwrap();
137
- let tools = TeamOrchestratorTools::with_identity(&ws, Some(AgentId::new("leader")), None);
136
+ ws
137
+ }
138
+
139
+ #[test]
140
+ fn send_message_worker_recipient_returns_accepted_with_poll_hint() {
141
+ // A worker recipient w/ a delivered message_id → async accepted carrying the
142
+ // byte-stable poll hint. Identity anchored on injected env (no candidate scan).
143
+ // A-7: the accepted path needs a REAL stored message_id (tools.py:175-181 —
144
+ // fabricated ids are gone), so the workspace seeds a running worker-1 the
145
+ // delivery layer can actually queue for.
146
+ let ws = seed_current_worker_state("send-worker");
147
+ let tools = TeamOrchestratorTools::with_identity(
148
+ &ws,
149
+ Some(AgentId::new("leader")),
150
+ Some(TeamKey::new("current")),
151
+ );
138
152
  let outcome = tools.send_message(
139
153
  &MessageTarget::Single("worker-1".to_string()),
140
154
  "do the thing",
@@ -155,6 +169,104 @@ fn send_message_worker_recipient_returns_accepted_with_poll_hint() {
155
169
  }
156
170
  }
157
171
 
172
+ #[test]
173
+ fn ordinary_send_assign_shape_has_no_recovery_marker() {
174
+ let ws = seed_current_worker_state("ordinary-no-recovery");
175
+ let tools = TeamOrchestratorTools::with_identity(
176
+ &ws,
177
+ Some(AgentId::new("leader")),
178
+ Some(TeamKey::new("current")),
179
+ );
180
+
181
+ let sent = tools
182
+ .send_message(
183
+ &MessageTarget::Single("worker-1".to_string()),
184
+ "ordinary send",
185
+ None,
186
+ None,
187
+ None,
188
+ None,
189
+ )
190
+ .expect("ordinary send ok")
191
+ .to_value();
192
+ assert!(
193
+ !sent.as_object().unwrap().contains_key("recovery"),
194
+ "ordinary send must not carry recovery marker: {sent}"
195
+ );
196
+ assert!(
197
+ sent.get("acceptance_marker").is_none(),
198
+ "ordinary send must not carry acceptance marker: {sent}"
199
+ );
200
+
201
+ let assigned = tools
202
+ .assign_task(
203
+ &json!({"id": "ordinary-task", "assignee": "worker-1", "title": "ordinary"}),
204
+ None,
205
+ )
206
+ .expect("ordinary assign ok");
207
+ let assigned_value = serde_json::to_value(&assigned).expect("serialize ordinary assign");
208
+ assert!(
209
+ !assigned_value.as_object().unwrap().contains_key("recovery"),
210
+ "ordinary assign must not carry recovery marker: {assigned_value}"
211
+ );
212
+ assert!(
213
+ assigned_value.get("acceptance_marker").is_none(),
214
+ "ordinary assign must not carry acceptance marker: {assigned_value}"
215
+ );
216
+
217
+ let recovery_false_assigned = tools
218
+ .assign_task(
219
+ &json!({
220
+ "id": "ordinary-recovery-false-task",
221
+ "assignee": "worker-1",
222
+ "title": "ordinary recovery false",
223
+ "recovery": false,
224
+ }),
225
+ None,
226
+ )
227
+ .expect("recovery=false assign ok");
228
+ let recovery_false_value =
229
+ serde_json::to_value(&recovery_false_assigned).expect("serialize recovery=false assign");
230
+ assert!(
231
+ !recovery_false_value
232
+ .as_object()
233
+ .unwrap()
234
+ .contains_key("recovery"),
235
+ "recovery=false assign must not carry recovery marker: {recovery_false_value}"
236
+ );
237
+ assert!(
238
+ recovery_false_value.get("acceptance_marker").is_none(),
239
+ "recovery=false assign must not carry acceptance marker: {recovery_false_value}"
240
+ );
241
+ }
242
+
243
+ #[test]
244
+ fn recovery_assign_shape_has_structured_marker() {
245
+ let ws = seed_current_worker_state("recovery-marker");
246
+ let tools = TeamOrchestratorTools::with_identity(
247
+ &ws,
248
+ Some(AgentId::new("leader")),
249
+ Some(TeamKey::new("current")),
250
+ );
251
+
252
+ let assigned = tools
253
+ .assign_task(
254
+ &json!({
255
+ "id": "recovery-task",
256
+ "assignee": "worker-1",
257
+ "title": "recover",
258
+ "recovery": true,
259
+ }),
260
+ None,
261
+ )
262
+ .expect("recovery assign ok");
263
+ assert_eq!(assigned.fields.get("recovery"), Some(&json!(true)));
264
+ assert_eq!(
265
+ assigned.fields.get("acceptance_marker"),
266
+ Some(&json!("recovery"))
267
+ );
268
+ }
269
+
158
270
  #[test]
159
271
  fn send_message_worker_recipient_surfaces_dead_coordinator_warning() {
160
272
  let ws = unique_ws("send-worker-dead-coord");
@@ -116,6 +116,7 @@ impl TeamOrchestratorTools {
116
116
  };
117
117
 
118
118
  let task_value = Value::Object(task_obj.clone());
119
+ let recovery = task_recovery_marker(&task_value);
119
120
  let mut state = load_runtime_state(&self.workspace).map_err(tool_runtime_error)?;
120
121
  ensure_object(&mut state);
121
122
  let team_key = self
@@ -139,7 +140,16 @@ impl TeamOrchestratorTools {
139
140
  None,
140
141
  None,
141
142
  )?;
142
- compact_tool_result(&out.to_value())
143
+ let mut ok = compact_tool_result(&out.to_value())?;
144
+ if recovery {
145
+ ok.fields
146
+ .insert("recovery".to_string(), serde_json::json!(true));
147
+ ok.fields.insert(
148
+ "acceptance_marker".to_string(),
149
+ Value::String("recovery".to_string()),
150
+ );
151
+ }
152
+ Ok(ok)
143
153
  }
144
154
 
145
155
  /// `send_message` (`tools.py:135-183`): C14/C15/C17 scope resolution.
@@ -989,6 +999,11 @@ fn assignment_message(task: &Value, explicit: Option<&str>) -> String {
989
999
  json_dumps_default(task)
990
1000
  }
991
1001
 
1002
+ fn task_recovery_marker(task: &Value) -> bool {
1003
+ task.get("recovery").and_then(Value::as_bool) == Some(true)
1004
+ || task.get("acceptance_marker").and_then(Value::as_str) == Some("recovery")
1005
+ }
1006
+
992
1007
  fn scope_override_name(scope: Scope) -> Option<&'static str> {
993
1008
  match scope {
994
1009
  Scope::Team => Some("team"),
@@ -1981,12 +1981,19 @@ fn arm_turn_open(state: &mut serde_json::Value, recipient: &str, message_id: &Op
1981
1981
  .and_then(|agents| agents.get_mut(recipient))
1982
1982
  .and_then(serde_json::Value::as_object_mut)
1983
1983
  {
1984
- agent.insert(
1985
- "current_task_id".to_string(),
1986
- message_id.as_ref().map_or(serde_json::Value::Null, |id| {
1984
+ // Phase-DX E2 (CR P0 red line #5): the leader→worker turn message id.
1985
+ // Renamed from a misleading legacy field name (which was never a task
1986
+ // FSM id — task FSM is A1) to `current_turn_message_id`, matching its
1987
+ // actual semantic. Reader compatibility lives in `coordinator/tick.rs`
1988
+ // (whitelisted for the transition) and `mcp_server/helpers.rs`; other
1989
+ // messaging/lifecycle/mcp_server code MUST NOT treat this as
1990
+ // authoritative task state.
1991
+ let field = message_id
1992
+ .as_ref()
1993
+ .map_or(serde_json::Value::Null, |id| {
1987
1994
  serde_json::Value::String(id.clone())
1988
- }),
1989
- );
1995
+ });
1996
+ agent.insert("current_turn_message_id".to_string(), field);
1990
1997
  }
1991
1998
  }
1992
1999
 
@@ -1581,10 +1581,15 @@ fn deliver_pending_submit_unverified_is_not_saved_by_readback() {
1581
1581
  Some(&serde_json::json!(message_id)),
1582
1582
  "current-turn fact is armed immediately after physical inject succeeds"
1583
1583
  );
1584
+ // Phase-DX E2 rename: the leader→worker turn message id lives under
1585
+ // `current_turn_message_id` (the previous name misleadingly implied a task
1586
+ // FSM id — that is A1 territory). Same value written by
1587
+ // `delivery::arm_turn_open`; the invariant of arming-without-delivered is
1588
+ // unchanged.
1584
1589
  assert_eq!(
1585
- updated.pointer("/agents/w1/current_task_id"),
1590
+ updated.pointer("/agents/w1/current_turn_message_id"),
1586
1591
  Some(&serde_json::json!(message_id)),
1587
- "agent current_task_id is armed without marking the message delivered"
1592
+ "agent current-turn message id is armed without marking the message delivered"
1588
1593
  );
1589
1594
  }
1590
1595
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@team-agent/installer",
3
- "version": "0.5.6",
3
+ "version": "0.5.8",
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.6",
24
- "@team-agent/cli-darwin-x64": "0.5.6",
25
- "@team-agent/cli-linux-x64": "0.5.6"
23
+ "@team-agent/cli-darwin-arm64": "0.5.8",
24
+ "@team-agent/cli-darwin-x64": "0.5.8",
25
+ "@team-agent/cli-linux-x64": "0.5.8"
26
26
  },
27
27
  "scripts": {
28
28
  "postinstall": "node npm/bincheck.mjs",