@team-agent/installer 0.5.50 → 0.5.52
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/adapters.rs +5 -3
- package/crates/team-agent/src/cli/emit.rs +2 -1
- package/crates/team-agent/src/cli/mod.rs +32 -14
- package/crates/team-agent/src/cli/named_address.rs +46 -115
- package/crates/team-agent/src/cli/send/coordinator.rs +163 -0
- package/crates/team-agent/src/cli/send/mailbox.rs +99 -0
- package/crates/team-agent/src/cli/send/persist.rs +154 -0
- package/crates/team-agent/src/cli/send/presentation.rs +333 -0
- package/crates/team-agent/src/cli/send/resolve.rs +361 -0
- package/crates/team-agent/src/cli/send.rs +44 -1169
- package/crates/team-agent/src/cli/spec.rs +1 -1
- package/crates/team-agent/src/cli/status_port/agents.rs +358 -0
- package/crates/team-agent/src/cli/status_port/approvals.rs +79 -0
- package/crates/team-agent/src/cli/status_port/compact.rs +207 -0
- package/crates/team-agent/src/cli/status_port/format.rs +145 -0
- package/crates/team-agent/src/cli/status_port/inbox.rs +36 -0
- package/crates/team-agent/src/cli/status_port/runtime.rs +195 -0
- package/crates/team-agent/src/cli/status_port/snapshot.rs +181 -0
- package/crates/team-agent/src/cli/status_port/store.rs +412 -0
- package/crates/team-agent/src/cli/status_port/tests.rs +54 -0
- package/crates/team-agent/src/cli/status_port.rs +47 -1548
- package/crates/team-agent/src/cli/tests/main_preserved.rs +1 -0
- package/crates/team-agent/src/cli/tests/run_delegation.rs +1 -0
- package/crates/team-agent/src/cli/types.rs +1 -0
- package/crates/team-agent/src/coordinator/conpty_shim.rs +34 -30
- package/crates/team-agent/src/coordinator/steps/abnormal.rs +135 -13
- package/crates/team-agent/src/coordinator/tick.rs +37 -0
- package/crates/team-agent/src/db/agent_health_capture.rs +18 -13
- package/crates/team-agent/src/db/message_store.rs +154 -44
- package/crates/team-agent/src/event_log.rs +73 -0
- package/crates/team-agent/src/leader/lease.rs +202 -16
- package/crates/team-agent/src/leader/start.rs +28 -4
- package/crates/team-agent/src/lifecycle/launch/add_agent.rs +424 -0
- package/crates/team-agent/src/lifecycle/launch/add_agent_state.rs +303 -0
- package/crates/team-agent/src/lifecycle/launch/agent_state.rs +160 -0
- package/crates/team-agent/src/lifecycle/launch/approval.rs +134 -0
- package/crates/team-agent/src/lifecycle/launch/fork_agent.rs +492 -0
- package/crates/team-agent/src/lifecycle/launch/fork_state.rs +297 -0
- package/crates/team-agent/src/lifecycle/launch/identity.rs +372 -0
- package/crates/team-agent/src/lifecycle/launch/layout.rs +313 -0
- package/crates/team-agent/src/lifecycle/launch/leader_context.rs +478 -0
- package/crates/team-agent/src/lifecycle/launch/mcp_config.rs +201 -0
- package/crates/team-agent/src/lifecycle/launch/ownership.rs +66 -0
- package/crates/team-agent/src/lifecycle/launch/quick_start.rs +477 -0
- package/crates/team-agent/src/lifecycle/launch/quick_start_transport.rs +278 -0
- package/crates/team-agent/src/lifecycle/launch/readiness.rs +123 -0
- package/crates/team-agent/src/lifecycle/launch/spawn.rs +377 -0
- package/crates/team-agent/src/lifecycle/launch/spec_state.rs +434 -0
- package/crates/team-agent/src/lifecycle/launch/state_projection.rs +499 -0
- package/crates/team-agent/src/lifecycle/launch/worker_env.rs +438 -0
- package/crates/team-agent/src/lifecycle/launch.rs +119 -5351
- package/crates/team-agent/src/lifecycle/restart/agent.rs +63 -29
- package/crates/team-agent/src/lifecycle/restart/common.rs +53 -27
- package/crates/team-agent/src/lifecycle/restart/rebuild.rs +167 -27
- package/crates/team-agent/src/lifecycle/restart/remove.rs +503 -77
- package/crates/team-agent/src/lifecycle/restart.rs +26 -2
- package/crates/team-agent/src/lifecycle/tests/lane_ops.rs +906 -17
- package/crates/team-agent/src/lifecycle/tests/launch_spawn.rs +67 -2
- package/crates/team-agent/src/lifecycle/tests/lifecycle_lock.rs +24 -1
- package/crates/team-agent/src/mcp_server/lifecycle_tools/agent_ops.rs +30 -7
- package/crates/team-agent/src/mcp_server/lifecycle_tools/state_status.rs +8 -4
- package/crates/team-agent/src/mcp_server/mod.rs +1 -1
- package/crates/team-agent/src/mcp_server/tests/send.rs +6 -10
- package/crates/team-agent/src/mcp_server/tests/tools.rs +18 -1
- package/crates/team-agent/src/mcp_server/tests.rs +11 -4
- package/crates/team-agent/src/mcp_server/tools.rs +14 -5
- package/crates/team-agent/src/messaging/activity.rs +4 -2
- package/crates/team-agent/src/messaging/address.rs +86 -0
- package/crates/team-agent/src/messaging/delivery.rs +288 -183
- package/crates/team-agent/src/messaging/helpers.rs +17 -13
- package/crates/team-agent/src/messaging/leader_channel.rs +171 -0
- package/crates/team-agent/src/messaging/leader_receiver.rs +60 -35
- package/crates/team-agent/src/messaging/mod.rs +15 -2
- package/crates/team-agent/src/messaging/persist.rs +309 -0
- package/crates/team-agent/src/messaging/results.rs +16 -24
- package/crates/team-agent/src/messaging/scheduler.rs +4 -2
- package/crates/team-agent/src/messaging/selftest.rs +19 -12
- package/crates/team-agent/src/messaging/send.rs +101 -49
- package/crates/team-agent/src/messaging/tests/leader_channel.rs +169 -0
- package/crates/team-agent/src/messaging/tests/leader_inject_acceptance.rs +305 -0
- package/crates/team-agent/src/messaging/tests/mod.rs +2 -0
- package/crates/team-agent/src/messaging/tests/runtime.rs +38 -17
- package/crates/team-agent/src/messaging/watchers.rs +13 -3
- package/crates/team-agent/src/redaction.rs +72 -2
- package/crates/team-agent/src/state/persist.rs +284 -21
- package/crates/team-agent/src/state/projection.rs +245 -30
- package/crates/team-agent/src/state/repository/intent.rs +13 -0
- package/crates/team-agent/src/state/repository/tests.rs +47 -0
- package/crates/team-agent/src/state/repository.rs +94 -27
- package/crates/team-agent/src/state/selector.rs +9 -18
- package/package.json +4 -4
|
@@ -45,20 +45,28 @@ pub fn deliver_stored_message(
|
|
|
45
45
|
) -> Result<DeliveryOutcome, MessagingError> {
|
|
46
46
|
let _ = (wait_visible, timeout);
|
|
47
47
|
let recipient = target.unwrap_or("leader");
|
|
48
|
-
let
|
|
49
|
-
|
|
48
|
+
let super::PersistResolution::Persisted(persisted) = super::persist::persist_internal_send(
|
|
49
|
+
workspace,
|
|
50
|
+
super::InternalSendKind::Delivery,
|
|
51
|
+
team.map(TeamKey::as_str),
|
|
50
52
|
task_id.map(crate::model::ids::TaskId::as_str),
|
|
51
53
|
sender,
|
|
52
54
|
recipient,
|
|
53
55
|
content,
|
|
54
56
|
None,
|
|
55
57
|
requires_ack,
|
|
56
|
-
|
|
57
|
-
|
|
58
|
+
None,
|
|
59
|
+
super::InitialDisposition::Accepted,
|
|
60
|
+
)?
|
|
61
|
+
else {
|
|
62
|
+
unreachable!("internal delivery does not accept caller-supplied ids")
|
|
63
|
+
};
|
|
64
|
+
let message_status = persisted.row_status.as_str().to_string();
|
|
65
|
+
let message_id = persisted.message_id;
|
|
58
66
|
Ok(DeliveryOutcome {
|
|
59
67
|
ok: true,
|
|
60
68
|
status: DeliveryStatus::Queued,
|
|
61
|
-
message_status: MessageStatusShadow(
|
|
69
|
+
message_status: MessageStatusShadow(message_status),
|
|
62
70
|
message_id: Some(message_id),
|
|
63
71
|
verification: None,
|
|
64
72
|
stage: None,
|
|
@@ -67,6 +75,17 @@ pub fn deliver_stored_message(
|
|
|
67
75
|
})
|
|
68
76
|
}
|
|
69
77
|
|
|
78
|
+
pub fn deliver_persisted_message(
|
|
79
|
+
workspace: &Path,
|
|
80
|
+
transport: &dyn Transport,
|
|
81
|
+
message_id: &str,
|
|
82
|
+
event_log: &EventLog,
|
|
83
|
+
state: &serde_json::Value,
|
|
84
|
+
) -> Result<DeliveryOutcome, MessagingError> {
|
|
85
|
+
let store = MessageStore::open(workspace)?;
|
|
86
|
+
deliver_pending_message(workspace, &store, transport, message_id, event_log, state)
|
|
87
|
+
}
|
|
88
|
+
|
|
70
89
|
// ===========================================================================
|
|
71
90
|
// delivery.py — 单条 tmux 注入投递 + trust 有界重试 + turn-open arm (card §16)
|
|
72
91
|
// ===========================================================================
|
|
@@ -200,6 +219,47 @@ pub fn deliver_pending_message(
|
|
|
200
219
|
}
|
|
201
220
|
_ => state,
|
|
202
221
|
};
|
|
222
|
+
if message.recipient == "leader" && message.status == "submitted_pending_acceptance" {
|
|
223
|
+
return observe_pending_leader_acceptance(store, event_log, state, message_id);
|
|
224
|
+
}
|
|
225
|
+
if message.recipient == "leader" && message.status == "queued_until_leader_attach" {
|
|
226
|
+
let Some(receiver) = leader_receiver_value(state) else {
|
|
227
|
+
return Ok(leader_acceptance_pending_outcome(
|
|
228
|
+
message_id,
|
|
229
|
+
"queued_until_leader_attach",
|
|
230
|
+
));
|
|
231
|
+
};
|
|
232
|
+
let channel_transport =
|
|
233
|
+
delivery_transport_for_recipient(workspace, transport, state, "leader");
|
|
234
|
+
let resolution = crate::messaging::resolve_live_leader_channel(
|
|
235
|
+
workspace,
|
|
236
|
+
receiver,
|
|
237
|
+
channel_transport.as_transport(),
|
|
238
|
+
);
|
|
239
|
+
let crate::messaging::LeaderChannelResolution::Live(channel) = resolution else {
|
|
240
|
+
return Ok(leader_acceptance_pending_outcome(
|
|
241
|
+
message_id,
|
|
242
|
+
"queued_until_leader_attach",
|
|
243
|
+
));
|
|
244
|
+
};
|
|
245
|
+
let metadata_drift = match &channel {
|
|
246
|
+
crate::messaging::LiveLeaderChannel::DirectTmux(channel) => {
|
|
247
|
+
channel.metadata_drift.clone()
|
|
248
|
+
}
|
|
249
|
+
crate::messaging::LiveLeaderChannel::CodexAppServer(_) => Vec::new(),
|
|
250
|
+
};
|
|
251
|
+
store.mark(message_id, "accepted", None)?;
|
|
252
|
+
event_log.write(
|
|
253
|
+
"leader_receiver.mailbox_requeued",
|
|
254
|
+
serde_json::json!({
|
|
255
|
+
"message_id": message_id,
|
|
256
|
+
"channel_live": true,
|
|
257
|
+
"metadata_drift": metadata_drift,
|
|
258
|
+
"requeue_trigger": "delivery_revalidation",
|
|
259
|
+
"revalidated_at": chrono::Utc::now().to_rfc3339(),
|
|
260
|
+
}),
|
|
261
|
+
)?;
|
|
262
|
+
}
|
|
203
263
|
let attempt = if store.claim_for_delivery(message_id)? {
|
|
204
264
|
message.delivery_attempts.saturating_add(1)
|
|
205
265
|
} else if message.status == "target_resolved" && message.error.is_some() {
|
|
@@ -217,88 +277,108 @@ pub fn deliver_pending_message(
|
|
|
217
277
|
});
|
|
218
278
|
};
|
|
219
279
|
if message.recipient == "leader" {
|
|
220
|
-
|
|
221
|
-
|
|
280
|
+
let Some(receiver) = leader_receiver_value(state) else {
|
|
281
|
+
store.mark(message_id, "failed", Some("leader_not_attached"))?;
|
|
282
|
+
event_log.write(
|
|
283
|
+
"leader_receiver.delivery_blocked",
|
|
284
|
+
serde_json::json!({
|
|
285
|
+
"message_id": message_id,
|
|
286
|
+
"sender": message.sender,
|
|
287
|
+
"reason": "leader_receiver_not_attached",
|
|
288
|
+
"error": "leader_not_attached",
|
|
289
|
+
"channel_reason": "receiver_missing",
|
|
290
|
+
"channel": "rebind_required",
|
|
291
|
+
"action": "run team-agent attach-leader or team-agent takeover",
|
|
292
|
+
"status": "unbound",
|
|
293
|
+
}),
|
|
294
|
+
)?;
|
|
295
|
+
return Ok(DeliveryOutcome {
|
|
296
|
+
ok: false,
|
|
297
|
+
status: DeliveryStatus::Refused,
|
|
298
|
+
message_status: MessageStatusShadow("failed".to_string()),
|
|
299
|
+
message_id: Some(message_id.to_string()),
|
|
300
|
+
verification: Some(
|
|
301
|
+
"run team-agent attach-leader or team-agent takeover".to_string(),
|
|
302
|
+
),
|
|
303
|
+
stage: None,
|
|
304
|
+
reason: Some(DeliveryRefusal::LeaderNotAttached),
|
|
305
|
+
channel: Some("rebind_required".to_string()),
|
|
306
|
+
});
|
|
307
|
+
};
|
|
308
|
+
if let Some(outcome) = leader_receiver_transport_conflict_outcome(
|
|
309
|
+
store,
|
|
310
|
+
event_log,
|
|
311
|
+
message_id,
|
|
312
|
+
receiver,
|
|
313
|
+
&message.sender,
|
|
314
|
+
)? {
|
|
315
|
+
return Ok(outcome);
|
|
316
|
+
}
|
|
317
|
+
let channel_transport =
|
|
318
|
+
delivery_transport_for_recipient(workspace, transport, state, "leader");
|
|
319
|
+
match crate::messaging::resolve_live_leader_channel(
|
|
320
|
+
workspace,
|
|
321
|
+
receiver,
|
|
322
|
+
channel_transport.as_transport(),
|
|
323
|
+
) {
|
|
324
|
+
crate::messaging::LeaderChannelResolution::Live(_) => {}
|
|
325
|
+
crate::messaging::LeaderChannelResolution::ProbeFailed(error) => {
|
|
326
|
+
store.mark(message_id, "target_resolved", Some(&error))?;
|
|
327
|
+
return Ok(DeliveryOutcome {
|
|
328
|
+
ok: false,
|
|
329
|
+
status: DeliveryStatus::Degraded,
|
|
330
|
+
message_status: MessageStatusShadow("target_resolved".to_string()),
|
|
331
|
+
message_id: Some(message_id.to_string()),
|
|
332
|
+
verification: Some(error),
|
|
333
|
+
stage: None,
|
|
334
|
+
reason: None,
|
|
335
|
+
channel: Some("rebind_required".to_string()),
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
crate::messaging::LeaderChannelResolution::Unbound(reason) => {
|
|
339
|
+
let receiver_status = receiver
|
|
340
|
+
.get("status")
|
|
341
|
+
.and_then(serde_json::Value::as_str)
|
|
342
|
+
.unwrap_or("unbound");
|
|
343
|
+
store.mark(message_id, "failed", Some("leader_not_attached"))?;
|
|
344
|
+
event_log.write(
|
|
345
|
+
"leader_receiver.delivery_blocked",
|
|
346
|
+
serde_json::json!({
|
|
347
|
+
"message_id": message_id,
|
|
348
|
+
"sender": message.sender,
|
|
349
|
+
"reason": "leader_receiver_not_attached",
|
|
350
|
+
"error": "leader_not_attached",
|
|
351
|
+
"channel_reason": format!("{reason:?}"),
|
|
352
|
+
"channel": "rebind_required",
|
|
353
|
+
"action": "run team-agent attach-leader or team-agent takeover",
|
|
354
|
+
"status": receiver_status,
|
|
355
|
+
}),
|
|
356
|
+
)?;
|
|
357
|
+
return Ok(DeliveryOutcome {
|
|
358
|
+
ok: false,
|
|
359
|
+
status: DeliveryStatus::Refused,
|
|
360
|
+
message_status: MessageStatusShadow("failed".to_string()),
|
|
361
|
+
message_id: Some(message_id.to_string()),
|
|
362
|
+
verification: Some(
|
|
363
|
+
"run team-agent attach-leader or team-agent takeover".to_string(),
|
|
364
|
+
),
|
|
365
|
+
stage: None,
|
|
366
|
+
reason: Some(DeliveryRefusal::LeaderNotAttached),
|
|
367
|
+
channel: Some("rebind_required".to_string()),
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
if crate::codex_app_server::receiver_is_app_server(receiver) {
|
|
372
|
+
return deliver_leader_via_app_server(
|
|
222
373
|
store,
|
|
223
374
|
event_log,
|
|
375
|
+
state,
|
|
224
376
|
message_id,
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
)
|
|
228
|
-
return Ok(outcome);
|
|
229
|
-
}
|
|
230
|
-
// 0.5.4 gate054 cross-team leak fix: a `leader_receiver.status`
|
|
231
|
-
// other than `"attached"` (restart demotes killed-session receivers
|
|
232
|
-
// to `"rebind_required"` — lifecycle/restart/rebuild.rs:1311) MUST
|
|
233
|
-
// refuse delivery before any pane injection or cross-server pane-id
|
|
234
|
-
// probing. Without this gate, a stale receiver on team A's socket
|
|
235
|
-
// could otherwise fall back to the same pane id on the main tmux
|
|
236
|
-
// server (delivery.rs:1150-1201) and inject A's leader traffic into
|
|
237
|
-
// team B's leader pane. See .team/artifacts/0.5.4-gate-crossteam-notify-triage.md.
|
|
238
|
-
if let Some(status) = leader_receiver_status(state) {
|
|
239
|
-
if status != "attached" {
|
|
240
|
-
store.mark(message_id, "failed", Some("leader_not_attached"))?;
|
|
241
|
-
event_log.write(
|
|
242
|
-
"leader_receiver.delivery_blocked",
|
|
243
|
-
serde_json::json!({
|
|
244
|
-
"message_id": message_id,
|
|
245
|
-
"sender": message.sender,
|
|
246
|
-
"reason": "leader_receiver_not_attached",
|
|
247
|
-
"channel": "rebind_required",
|
|
248
|
-
"action": "run team-agent attach-leader or team-agent takeover",
|
|
249
|
-
"status": status,
|
|
250
|
-
}),
|
|
251
|
-
)?;
|
|
252
|
-
return Ok(DeliveryOutcome {
|
|
253
|
-
ok: false,
|
|
254
|
-
status: DeliveryStatus::Refused,
|
|
255
|
-
message_status: MessageStatusShadow("failed".to_string()),
|
|
256
|
-
message_id: Some(message_id.to_string()),
|
|
257
|
-
verification: Some(
|
|
258
|
-
"run team-agent attach-leader or team-agent takeover".to_string(),
|
|
259
|
-
),
|
|
260
|
-
stage: None,
|
|
261
|
-
reason: Some(DeliveryRefusal::LeaderNotAttached),
|
|
262
|
-
channel: Some("rebind_required".to_string()),
|
|
263
|
-
});
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
if crate::codex_app_server::receiver_is_app_server(receiver) {
|
|
267
|
-
return deliver_leader_via_app_server(
|
|
268
|
-
store,
|
|
269
|
-
event_log,
|
|
270
|
-
state,
|
|
271
|
-
message_id,
|
|
272
|
-
&message,
|
|
273
|
-
canonical_owner_team_id.as_deref(),
|
|
274
|
-
);
|
|
275
|
-
}
|
|
377
|
+
&message,
|
|
378
|
+
canonical_owner_team_id.as_deref(),
|
|
379
|
+
);
|
|
276
380
|
}
|
|
277
381
|
}
|
|
278
|
-
if message.recipient == "leader" && leader_receiver_has_noncanonical_tmux_socket(state) {
|
|
279
|
-
store.mark(message_id, "failed", Some("leader_not_attached"))?;
|
|
280
|
-
event_log.write(
|
|
281
|
-
"leader_receiver.delivery_blocked",
|
|
282
|
-
serde_json::json!({
|
|
283
|
-
"message_id": message_id,
|
|
284
|
-
"sender": message.sender,
|
|
285
|
-
"reason": "leader_not_attached",
|
|
286
|
-
"channel": "rebind_required",
|
|
287
|
-
"action": "run team-agent claim-leader or team-agent takeover",
|
|
288
|
-
"error": "leader_receiver.tmux_socket is not a canonical full socket path",
|
|
289
|
-
}),
|
|
290
|
-
)?;
|
|
291
|
-
return Ok(DeliveryOutcome {
|
|
292
|
-
ok: false,
|
|
293
|
-
status: DeliveryStatus::Refused,
|
|
294
|
-
message_status: MessageStatusShadow("failed".to_string()),
|
|
295
|
-
message_id: Some(message_id.to_string()),
|
|
296
|
-
verification: Some("run team-agent claim-leader or team-agent takeover".to_string()),
|
|
297
|
-
stage: None,
|
|
298
|
-
reason: Some(DeliveryRefusal::LeaderNotAttached),
|
|
299
|
-
channel: Some("rebind_required".to_string()),
|
|
300
|
-
});
|
|
301
|
-
}
|
|
302
382
|
let delivery_transport =
|
|
303
383
|
delivery_transport_for_recipient(workspace, transport, state, &message.recipient);
|
|
304
384
|
let transport = delivery_transport.as_transport();
|
|
@@ -334,32 +414,6 @@ pub fn deliver_pending_message(
|
|
|
334
414
|
});
|
|
335
415
|
}
|
|
336
416
|
};
|
|
337
|
-
// Do not inject queued leader messages into a synthetic "leader" window.
|
|
338
|
-
if message.recipient == "leader"
|
|
339
|
-
&& !leader_receiver_pane_is_usable(transport, state, &live_targets)
|
|
340
|
-
{
|
|
341
|
-
store.mark(message_id, "failed", Some("leader_not_attached"))?;
|
|
342
|
-
event_log.write(
|
|
343
|
-
"leader_receiver.delivery_blocked",
|
|
344
|
-
serde_json::json!({
|
|
345
|
-
"message_id": message_id,
|
|
346
|
-
"sender": message.sender,
|
|
347
|
-
"reason": "leader_not_attached",
|
|
348
|
-
"channel": "rebind_required",
|
|
349
|
-
"action": "run team-agent claim-leader or team-agent takeover",
|
|
350
|
-
}),
|
|
351
|
-
)?;
|
|
352
|
-
return Ok(DeliveryOutcome {
|
|
353
|
-
ok: false,
|
|
354
|
-
status: DeliveryStatus::Refused,
|
|
355
|
-
message_status: MessageStatusShadow("failed".to_string()),
|
|
356
|
-
message_id: Some(message_id.to_string()),
|
|
357
|
-
verification: Some("run team-agent claim-leader or team-agent takeover".to_string()),
|
|
358
|
-
stage: None,
|
|
359
|
-
reason: Some(DeliveryRefusal::LeaderNotAttached),
|
|
360
|
-
channel: Some("rebind_required".to_string()),
|
|
361
|
-
});
|
|
362
|
-
}
|
|
363
417
|
let resolved = resolve_inject_target(state, &message.recipient, transport, &live_targets);
|
|
364
418
|
if let Some(stale) = resolved.stale_binding.as_ref() {
|
|
365
419
|
event_log.write(
|
|
@@ -524,14 +578,10 @@ pub fn deliver_pending_message(
|
|
|
524
578
|
}
|
|
525
579
|
let submit_verified = inject_submit_verified(&inject_report);
|
|
526
580
|
let readback_verified = pane_readback_verified(&inject_report);
|
|
527
|
-
//
|
|
528
|
-
//
|
|
529
|
-
//
|
|
530
|
-
let verified =
|
|
531
|
-
true
|
|
532
|
-
} else {
|
|
533
|
-
submit_verified
|
|
534
|
-
};
|
|
581
|
+
// A successful tmux command is never provider-acceptance proof. Leader and
|
|
582
|
+
// worker delivery both require post-submit evidence; leader additionally
|
|
583
|
+
// waits for its authoritative provider transcript below.
|
|
584
|
+
let verified = submit_verified;
|
|
535
585
|
if !verified {
|
|
536
586
|
let reason = if !readback_verified {
|
|
537
587
|
"pane_readback_unverified:capture_missing_token".to_string()
|
|
@@ -658,7 +708,26 @@ pub fn deliver_pending_message(
|
|
|
658
708
|
});
|
|
659
709
|
}
|
|
660
710
|
}
|
|
661
|
-
|
|
711
|
+
if is_leader_recipient {
|
|
712
|
+
store.record_delivery_submission(message_id, readback_verified)?;
|
|
713
|
+
if !leader_transcript_has_token(state, message_id) {
|
|
714
|
+
store.mark(message_id, "submitted_pending_acceptance", None)?;
|
|
715
|
+
event_log.write(
|
|
716
|
+
"leader_receiver.acceptance_pending",
|
|
717
|
+
serde_json::json!({
|
|
718
|
+
"message_id": message_id,
|
|
719
|
+
"reason": "provider_receipt_not_observed",
|
|
720
|
+
}),
|
|
721
|
+
)?;
|
|
722
|
+
return Ok(leader_acceptance_pending_outcome(
|
|
723
|
+
message_id,
|
|
724
|
+
"submitted_pending_acceptance",
|
|
725
|
+
));
|
|
726
|
+
}
|
|
727
|
+
store.mark_delivered_with_receipt(message_id)?;
|
|
728
|
+
} else {
|
|
729
|
+
store.mark(message_id, "delivered", None)?;
|
|
730
|
+
}
|
|
662
731
|
let mut delivered_event = serde_json::json!({"message_id": message_id});
|
|
663
732
|
if let Some(metadata) = resolved.metadata.as_ref() {
|
|
664
733
|
append_target_metadata(&mut delivered_event, metadata);
|
|
@@ -908,7 +977,8 @@ fn deliver_leader_via_app_server(
|
|
|
908
977
|
);
|
|
909
978
|
match crate::codex_app_server::submit_to_bound_thread(&binding, message_id, &rendered) {
|
|
910
979
|
Ok(submit) => {
|
|
911
|
-
store.
|
|
980
|
+
store.record_delivery_submission(message_id, true)?;
|
|
981
|
+
store.mark_delivered_with_receipt(message_id)?;
|
|
912
982
|
event_log.write(
|
|
913
983
|
"message.delivered",
|
|
914
984
|
serde_json::json!({"message_id": message_id}),
|
|
@@ -1482,32 +1552,6 @@ fn leader_receiver_pane_binding(state: &serde_json::Value) -> Option<PaneSocketB
|
|
|
1482
1552
|
.or_else(|| only_team_entry(state).and_then(leader_receiver_pane_binding_in_state))
|
|
1483
1553
|
}
|
|
1484
1554
|
|
|
1485
|
-
/// `state` is "usable" for leader delivery when the bound `leader_receiver.pane_id`
|
|
1486
|
-
/// is present and alive (in `live_targets` or `liveness` probe returns not-Dead).
|
|
1487
|
-
///
|
|
1488
|
-
/// **0.3.24 excision (U1-A real-machine RED v2)**: the wave-2 session+window drift
|
|
1489
|
-
/// fallback was removed. Drift now fails loudly as `leader_not_attached` —
|
|
1490
|
-
/// see resolve_inject_target and `.team/artifacts/u1-a-realmachine-v2-fix-or-excise.md`.
|
|
1491
|
-
fn leader_receiver_pane_is_usable(
|
|
1492
|
-
transport: &dyn Transport,
|
|
1493
|
-
state: &serde_json::Value,
|
|
1494
|
-
live_targets: &[PaneInfo],
|
|
1495
|
-
) -> bool {
|
|
1496
|
-
let Some(pane_id) = leader_receiver_pane_id(state) else {
|
|
1497
|
-
return false;
|
|
1498
|
-
};
|
|
1499
|
-
if live_targets
|
|
1500
|
-
.iter()
|
|
1501
|
-
.any(|target| target.pane_id.as_str() == pane_id)
|
|
1502
|
-
{
|
|
1503
|
-
return true;
|
|
1504
|
-
}
|
|
1505
|
-
!matches!(
|
|
1506
|
-
transport.liveness(&PaneId::new(pane_id)),
|
|
1507
|
-
Ok(PaneLiveness::Dead)
|
|
1508
|
-
)
|
|
1509
|
-
}
|
|
1510
|
-
|
|
1511
1555
|
/// 0.5.x Phase 1d Batch 4: the `Owned` variant now carries a boxed
|
|
1512
1556
|
/// `Transport` trait object rather than a concrete `TmuxBackend`. All
|
|
1513
1557
|
/// existing leader-fallback sites still supply a `TmuxBackend` here
|
|
@@ -1580,7 +1624,7 @@ fn delivery_transport_for_recipient<'a>(
|
|
|
1580
1624
|
// switching transports on a match can cross the team/tmux boundary and
|
|
1581
1625
|
// inject A's leader traffic into team B's leader pane. If the pane is
|
|
1582
1626
|
// absent on the recorded endpoint, let the downstream
|
|
1583
|
-
//
|
|
1627
|
+
// the shared leader-channel resolver produce `rebind_required`
|
|
1584
1628
|
// instead of falling back to another server.
|
|
1585
1629
|
let endpoint_backend = crate::transport_factory::tmux_endpoint_transport(socket);
|
|
1586
1630
|
DeliveryTransport::Owned(Box::new(endpoint_backend))
|
|
@@ -1622,20 +1666,6 @@ fn leader_receiver_tmux_socket(state: &serde_json::Value) -> Option<&str> {
|
|
|
1622
1666
|
leader_receiver_field(state, "tmux_socket")
|
|
1623
1667
|
}
|
|
1624
1668
|
|
|
1625
|
-
/// 0.5.4 gate054 cross-team leak fix: read the bound `leader_receiver.status`
|
|
1626
|
-
/// through the same team-scope resolution as [`leader_receiver_tmux_socket`].
|
|
1627
|
-
/// Restart marks demoted receivers as `"rebind_required"`
|
|
1628
|
-
/// (lifecycle/restart/rebuild.rs:1311); delivery must refuse before injecting
|
|
1629
|
-
/// or cross-server pane-id probing (delivery.rs:1150-1201).
|
|
1630
|
-
fn leader_receiver_status(state: &serde_json::Value) -> Option<&str> {
|
|
1631
|
-
leader_receiver_field(state, "status")
|
|
1632
|
-
}
|
|
1633
|
-
|
|
1634
|
-
fn leader_receiver_has_noncanonical_tmux_socket(state: &serde_json::Value) -> bool {
|
|
1635
|
-
leader_receiver_tmux_socket(state)
|
|
1636
|
-
.is_some_and(|socket| socket != "default" && !std::path::Path::new(socket).is_absolute())
|
|
1637
|
-
}
|
|
1638
|
-
|
|
1639
1669
|
fn leader_receiver_field<'a>(state: &'a serde_json::Value, field: &str) -> Option<&'a str> {
|
|
1640
1670
|
leader_receiver_field_in_state(state, field)
|
|
1641
1671
|
.or_else(|| {
|
|
@@ -1800,10 +1830,16 @@ pub fn deliver_pending_messages(
|
|
|
1800
1830
|
let conn = crate::db::schema::open_db(store.db_path())?;
|
|
1801
1831
|
let mut stmt = conn.prepare(
|
|
1802
1832
|
"select message_id from messages
|
|
1803
|
-
where status in (
|
|
1833
|
+
where status in (
|
|
1834
|
+
'pending', 'accepted', 'target_resolved',
|
|
1835
|
+
'submitted_pending_acceptance', 'queued_until_leader_attach', ?1
|
|
1836
|
+
)
|
|
1804
1837
|
order by created_at, message_id",
|
|
1805
1838
|
)?;
|
|
1806
|
-
let rows = stmt.query_map(
|
|
1839
|
+
let rows = stmt.query_map(
|
|
1840
|
+
[crate::message_store::MessageRowStatus::QueuedCoordinatorUnavailable.as_str()],
|
|
1841
|
+
|row| row.get::<_, String>(0),
|
|
1842
|
+
)?;
|
|
1807
1843
|
rows.collect::<Result<Vec<_>, _>>()?
|
|
1808
1844
|
};
|
|
1809
1845
|
let mut delivered = Vec::new();
|
|
@@ -1874,7 +1910,7 @@ pub fn deliver_pending_messages(
|
|
|
1874
1910
|
continue;
|
|
1875
1911
|
}
|
|
1876
1912
|
};
|
|
1877
|
-
if outcome.
|
|
1913
|
+
if matches!(outcome.status, DeliveryStatus::Delivered) {
|
|
1878
1914
|
delivered.push(message_id);
|
|
1879
1915
|
}
|
|
1880
1916
|
}
|
|
@@ -2017,6 +2053,73 @@ fn recipient_is_busy(state: &serde_json::Value, recipient: &str) -> bool {
|
|
|
2017
2053
|
== Some("busy")
|
|
2018
2054
|
}
|
|
2019
2055
|
|
|
2056
|
+
fn leader_rollout_path(state: &serde_json::Value) -> Option<std::path::PathBuf> {
|
|
2057
|
+
state
|
|
2058
|
+
.get("leader")
|
|
2059
|
+
.and_then(|leader| leader.get("rollout_path"))
|
|
2060
|
+
.and_then(serde_json::Value::as_str)
|
|
2061
|
+
.or_else(|| {
|
|
2062
|
+
leader_receiver_value(state)
|
|
2063
|
+
.and_then(|receiver| receiver.get("rollout_path"))
|
|
2064
|
+
.and_then(serde_json::Value::as_str)
|
|
2065
|
+
})
|
|
2066
|
+
.filter(|path| !path.is_empty())
|
|
2067
|
+
.map(std::path::PathBuf::from)
|
|
2068
|
+
}
|
|
2069
|
+
|
|
2070
|
+
pub(crate) fn leader_transcript_has_token(state: &serde_json::Value, message_id: &str) -> bool {
|
|
2071
|
+
let Some(path) = leader_rollout_path(state) else {
|
|
2072
|
+
return false;
|
|
2073
|
+
};
|
|
2074
|
+
rollout_tail_contains(
|
|
2075
|
+
&path,
|
|
2076
|
+
&format!("[team-agent-token:{message_id}]"),
|
|
2077
|
+
64 * 1024,
|
|
2078
|
+
)
|
|
2079
|
+
}
|
|
2080
|
+
|
|
2081
|
+
fn leader_acceptance_pending_outcome(message_id: &str, status: &str) -> DeliveryOutcome {
|
|
2082
|
+
DeliveryOutcome {
|
|
2083
|
+
ok: true,
|
|
2084
|
+
status: DeliveryStatus::RetryScheduled,
|
|
2085
|
+
message_status: MessageStatusShadow(status.to_string()),
|
|
2086
|
+
message_id: Some(message_id.to_string()),
|
|
2087
|
+
verification: Some("provider_receipt_not_observed".to_string()),
|
|
2088
|
+
stage: Some(DeliveryStage::Submit),
|
|
2089
|
+
reason: None,
|
|
2090
|
+
channel: Some("leader_acceptance_pending".to_string()),
|
|
2091
|
+
}
|
|
2092
|
+
}
|
|
2093
|
+
|
|
2094
|
+
fn observe_pending_leader_acceptance(
|
|
2095
|
+
store: &MessageStore,
|
|
2096
|
+
event_log: &EventLog,
|
|
2097
|
+
state: &serde_json::Value,
|
|
2098
|
+
message_id: &str,
|
|
2099
|
+
) -> Result<DeliveryOutcome, MessagingError> {
|
|
2100
|
+
if !leader_transcript_has_token(state, message_id) {
|
|
2101
|
+
return Ok(leader_acceptance_pending_outcome(
|
|
2102
|
+
message_id,
|
|
2103
|
+
"submitted_pending_acceptance",
|
|
2104
|
+
));
|
|
2105
|
+
}
|
|
2106
|
+
store.mark_delivered_with_receipt(message_id)?;
|
|
2107
|
+
event_log.write(
|
|
2108
|
+
"message.delivered",
|
|
2109
|
+
serde_json::json!({"message_id": message_id}),
|
|
2110
|
+
)?;
|
|
2111
|
+
Ok(DeliveryOutcome {
|
|
2112
|
+
ok: true,
|
|
2113
|
+
status: DeliveryStatus::Delivered,
|
|
2114
|
+
message_status: MessageStatusShadow("delivered".to_string()),
|
|
2115
|
+
message_id: Some(message_id.to_string()),
|
|
2116
|
+
verification: Some("provider_transcript_receipt".to_string()),
|
|
2117
|
+
stage: None,
|
|
2118
|
+
reason: None,
|
|
2119
|
+
channel: Some("leader_receiver".to_string()),
|
|
2120
|
+
})
|
|
2121
|
+
}
|
|
2122
|
+
|
|
2020
2123
|
/// `_handle_trust_retry_needed` (`delivery.py:221`):trust 应答失败时调度有界退避重试
|
|
2021
2124
|
/// (`attempt < MAX` → schedule;`>= MAX` → 终态 mark failed + `trust_auto_answer_exhausted`)。
|
|
2022
2125
|
pub fn handle_trust_retry_needed(
|
|
@@ -2341,8 +2444,10 @@ fn save_scoped_state(
|
|
|
2341
2444
|
state: &serde_json::Value,
|
|
2342
2445
|
owner_team_id: Option<&str>,
|
|
2343
2446
|
) -> Result<(), MessagingError> {
|
|
2344
|
-
|
|
2345
|
-
|
|
2447
|
+
let scoped_owner_team_id = owner_team_id
|
|
2448
|
+
.filter(|team| !team.is_empty())
|
|
2449
|
+
.filter(|_| {
|
|
2450
|
+
state
|
|
2346
2451
|
.get("teams")
|
|
2347
2452
|
.and_then(serde_json::Value::as_object)
|
|
2348
2453
|
.is_some_and(|teams| {
|
|
@@ -2354,14 +2459,13 @@ fn save_scoped_state(
|
|
|
2354
2459
|
})
|
|
2355
2460
|
.is_some_and(|team| teams.contains_key(&team))
|
|
2356
2461
|
})
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
}
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
}
|
|
2462
|
+
});
|
|
2463
|
+
crate::state::repository::StateRepository::new(workspace).save(
|
|
2464
|
+
crate::state::repository::StateWriteIntent::MessagingDeliveryState {
|
|
2465
|
+
owner_team_id: scoped_owner_team_id,
|
|
2466
|
+
},
|
|
2467
|
+
state,
|
|
2468
|
+
)?;
|
|
2365
2469
|
Ok(())
|
|
2366
2470
|
}
|
|
2367
2471
|
|
|
@@ -2374,8 +2478,10 @@ fn save_scoped_state_reapplying_after_conflict<F>(
|
|
|
2374
2478
|
where
|
|
2375
2479
|
F: FnOnce(&mut serde_json::Value),
|
|
2376
2480
|
{
|
|
2377
|
-
|
|
2378
|
-
|
|
2481
|
+
let scoped_owner_team_id = owner_team_id
|
|
2482
|
+
.filter(|team| !team.is_empty())
|
|
2483
|
+
.filter(|_| {
|
|
2484
|
+
state
|
|
2379
2485
|
.get("teams")
|
|
2380
2486
|
.and_then(serde_json::Value::as_object)
|
|
2381
2487
|
.is_some_and(|teams| {
|
|
@@ -2387,15 +2493,14 @@ where
|
|
|
2387
2493
|
})
|
|
2388
2494
|
.is_some_and(|team| teams.contains_key(&team))
|
|
2389
2495
|
})
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
}
|
|
2496
|
+
});
|
|
2497
|
+
crate::state::repository::StateRepository::new(workspace).save_reapplying(
|
|
2498
|
+
crate::state::repository::StateWriteIntent::MessagingDeliveryState {
|
|
2499
|
+
owner_team_id: scoped_owner_team_id,
|
|
2500
|
+
},
|
|
2501
|
+
state,
|
|
2502
|
+
reapply,
|
|
2503
|
+
)?;
|
|
2399
2504
|
Ok(())
|
|
2400
2505
|
}
|
|
2401
2506
|
|
|
@@ -274,21 +274,25 @@ pub fn fail_leader_delivery(
|
|
|
274
274
|
error: Option<&str>,
|
|
275
275
|
) -> Result<DeliveryOutcome, MessagingError> {
|
|
276
276
|
let store = MessageStore::open(workspace)?;
|
|
277
|
-
let
|
|
278
|
-
.get("sender")
|
|
279
|
-
.and_then(serde_json::Value::as_str)
|
|
280
|
-
.unwrap_or("system");
|
|
281
|
-
let content = payload
|
|
282
|
-
.get("content")
|
|
283
|
-
.and_then(serde_json::Value::as_str)
|
|
284
|
-
.unwrap_or("");
|
|
285
|
-
let task_id = payload.get("task_id").and_then(serde_json::Value::as_str);
|
|
286
|
-
let message_id = match payload
|
|
277
|
+
let Some(message_id) = payload
|
|
287
278
|
.get("message_id")
|
|
288
279
|
.and_then(serde_json::Value::as_str)
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
280
|
+
.map(ToOwned::to_owned)
|
|
281
|
+
else {
|
|
282
|
+
crate::event_log::EventLog::new(workspace).write(
|
|
283
|
+
"leader_receiver.delivery_failed_without_message",
|
|
284
|
+
serde_json::json!({"reason": serde_json::to_value(reason).ok(), "error": error}),
|
|
285
|
+
)?;
|
|
286
|
+
return Ok(DeliveryOutcome {
|
|
287
|
+
ok: false,
|
|
288
|
+
status: DeliveryStatus::FallbackLog,
|
|
289
|
+
message_status: MessageStatusShadow("failed".to_string()),
|
|
290
|
+
message_id: None,
|
|
291
|
+
verification: None,
|
|
292
|
+
stage: None,
|
|
293
|
+
reason: Some(reason),
|
|
294
|
+
channel: Some("fallback_log".to_string()),
|
|
295
|
+
});
|
|
292
296
|
};
|
|
293
297
|
store.mark(&message_id, "failed", error)?;
|
|
294
298
|
crate::event_log::EventLog::new(workspace).write(
|