@team-agent/installer 0.5.66 → 0.5.68
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 +8 -1
- package/Cargo.toml +1 -1
- package/crates/team-agent/Cargo.toml +1 -0
- package/crates/team-agent/src/cli/adapters.rs +5 -0
- package/crates/team-agent/src/cli/diagnose.rs +82 -0
- package/crates/team-agent/src/cli/emit.rs +3 -3
- package/crates/team-agent/src/cli/grok_slot.rs +299 -0
- package/crates/team-agent/src/cli/leader.rs +21 -7
- package/crates/team-agent/src/cli/leaders.rs +125 -1
- package/crates/team-agent/src/cli/mod.rs +20 -0
- package/crates/team-agent/src/cli/send/presentation.rs +7 -1
- package/crates/team-agent/src/cli/spec.rs +2 -0
- package/crates/team-agent/src/cli/status_port/compact.rs +28 -0
- package/crates/team-agent/src/cli/status_port/snapshot.rs +25 -1
- package/crates/team-agent/src/cli/tests/base.rs +26 -3
- package/crates/team-agent/src/cli/tests/missing_subcommands.rs +129 -19
- package/crates/team-agent/src/cli/tests/shutdown_kill_plan.rs +387 -6
- package/crates/team-agent/src/cli/tests/status_send.rs +79 -10
- package/crates/team-agent/src/communication_mode/mod.rs +6 -4
- package/crates/team-agent/src/compiler.rs +59 -0
- package/crates/team-agent/src/coordinator/backoff.rs +55 -0
- package/crates/team-agent/src/coordinator/conpty_shim.rs +82 -0
- package/crates/team-agent/src/coordinator/health.rs +173 -0
- package/crates/team-agent/src/coordinator/mod.rs +31 -0
- package/crates/team-agent/src/coordinator/orphan.rs +31 -0
- package/crates/team-agent/src/coordinator/runtime_detectors.rs +30 -0
- package/crates/team-agent/src/coordinator/runtime_observation.rs +25 -0
- package/crates/team-agent/src/coordinator/steps/abnormal.rs +59 -0
- package/crates/team-agent/src/coordinator/steps/delivery.rs +10 -0
- package/crates/team-agent/src/coordinator/steps/health_sync.rs +10 -0
- package/crates/team-agent/src/coordinator/steps/mod.rs +22 -0
- package/crates/team-agent/src/coordinator/steps/persist.rs +10 -0
- package/crates/team-agent/src/coordinator/steps/runtime_prompts.rs +10 -0
- package/crates/team-agent/src/coordinator/steps/session_gate.rs +10 -0
- package/crates/team-agent/src/coordinator/tick.rs +133 -22
- package/crates/team-agent/src/coordinator/types.rs +49 -0
- package/crates/team-agent/src/db/message_store.rs +39 -5
- package/crates/team-agent/src/layout/worker_env.rs +20 -3
- package/crates/team-agent/src/layout/worker_window_helpers.rs +2 -0
- package/crates/team-agent/src/leader/provider_attribution.rs +53 -0
- package/crates/team-agent/src/leader/registry.rs +65 -0
- package/crates/team-agent/src/leader/start.rs +920 -63
- package/crates/team-agent/src/lifecycle/display.rs +56 -0
- package/crates/team-agent/src/lifecycle/helpers.rs +57 -0
- package/crates/team-agent/src/lifecycle/launch/add_agent.rs +332 -12
- package/crates/team-agent/src/lifecycle/launch/add_agent_state.rs +62 -0
- package/crates/team-agent/src/lifecycle/launch/agent_state.rs +59 -0
- package/crates/team-agent/src/lifecycle/launch/clone_agent.rs +101 -11
- package/crates/team-agent/src/lifecycle/launch/cursor_create_chat.rs +229 -0
- package/crates/team-agent/src/lifecycle/launch/cursor_mcp.rs +332 -0
- package/crates/team-agent/src/lifecycle/launch/fork_agent.rs +204 -456
- package/crates/team-agent/src/lifecycle/launch/fork_entry.rs +24 -0
- package/crates/team-agent/src/lifecycle/launch/grok_per_seat.rs +260 -0
- package/crates/team-agent/src/lifecycle/launch/identity.rs +146 -0
- package/crates/team-agent/src/lifecycle/launch/layout.rs +60 -0
- package/crates/team-agent/src/lifecycle/launch/leader_context.rs +112 -0
- package/crates/team-agent/src/lifecycle/launch/mcp_config.rs +530 -0
- package/crates/team-agent/src/lifecycle/launch/ownership.rs +40 -0
- package/crates/team-agent/src/lifecycle/launch/plan.rs +31 -0
- package/crates/team-agent/src/lifecycle/launch/quick_start.rs +66 -0
- package/crates/team-agent/src/lifecycle/launch/quick_start_transport.rs +78 -0
- package/crates/team-agent/src/lifecycle/launch/readiness.rs +85 -38
- package/crates/team-agent/src/lifecycle/launch/role_source.rs +44 -44
- package/crates/team-agent/src/lifecycle/launch/spawn.rs +62 -2
- package/crates/team-agent/src/lifecycle/launch/spec_state.rs +139 -0
- package/crates/team-agent/src/lifecycle/launch/state_projection.rs +109 -0
- package/crates/team-agent/src/lifecycle/launch/worker_env.rs +214 -1
- package/crates/team-agent/src/lifecycle/launch.rs +99 -29
- package/crates/team-agent/src/lifecycle/lock.rs +42 -1
- package/crates/team-agent/src/lifecycle/mod.rs +11 -0
- package/crates/team-agent/src/lifecycle/pane_input_lock.rs +161 -0
- package/crates/team-agent/src/lifecycle/profile_launch.rs +98 -0
- package/crates/team-agent/src/lifecycle/profile_smoke.rs +51 -1
- package/crates/team-agent/src/lifecycle/restart/agent.rs +124 -0
- package/crates/team-agent/src/lifecycle/restart/common.rs +369 -11
- package/crates/team-agent/src/lifecycle/restart/orchestrator.rs +27 -0
- package/crates/team-agent/src/lifecycle/restart/preflight.rs +25 -0
- package/crates/team-agent/src/lifecycle/restart/rebuild.rs +106 -0
- package/crates/team-agent/src/lifecycle/restart/remove.rs +196 -5
- package/crates/team-agent/src/lifecycle/restart/selection.rs +72 -0
- package/crates/team-agent/src/lifecycle/restart/team_state.rs +22 -0
- package/crates/team-agent/src/lifecycle/restart.rs +29 -0
- package/crates/team-agent/src/lifecycle/tests/agent_ops.rs +2 -3
- package/crates/team-agent/src/lifecycle/tests/clone_agent_preserves_source_tools.rs +309 -0
- package/crates/team-agent/src/lifecycle/tests/clone_fork_copilot_perms_red.rs +150 -157
- package/crates/team-agent/src/lifecycle/tests/copilot_provider_red.rs +2 -2
- package/crates/team-agent/src/lifecycle/tests/core.rs +4 -0
- package/crates/team-agent/src/lifecycle/tests/cursor_mcp_overlay.rs +287 -0
- package/crates/team-agent/src/lifecycle/tests/cursor_require_explicit_model_red.rs +229 -0
- package/crates/team-agent/src/lifecycle/tests/cursor_restart_resume_red.rs +353 -0
- package/crates/team-agent/src/lifecycle/tests/g1_silent_faces.rs +784 -0
- package/crates/team-agent/src/lifecycle/tests/gate_fixtures.rs +562 -0
- package/crates/team-agent/src/lifecycle/tests/grok_effort_argv_red.rs +239 -0
- package/crates/team-agent/src/lifecycle/tests/grok_mcp_overlay_red.rs +498 -0
- package/crates/team-agent/src/lifecycle/tests/grok_require_explicit_model_red.rs +250 -0
- package/crates/team-agent/src/lifecycle/tests/grok_restart_resume_red.rs +293 -0
- package/crates/team-agent/src/lifecycle/tests/lane_ops.rs +63 -38
- package/crates/team-agent/src/lifecycle/tests/launch_spawn.rs +3 -2
- package/crates/team-agent/src/lifecycle/tests/lifecycle_rollback_red.rs +16 -8
- package/crates/team-agent/src/lifecycle/tests/mcp_tool_name_format_red.rs +69 -0
- package/crates/team-agent/src/lifecycle/tests/phase_b_contracts.rs +20 -19
- package/crates/team-agent/src/lifecycle/tests/phase_golden.rs +97 -61
- package/crates/team-agent/src/lifecycle/tests/restart_rebind_hotfix_252_red.rs +2 -0
- package/crates/team-agent/src/lifecycle/tests/startup_latency_contract.rs +40 -7
- package/crates/team-agent/src/lifecycle/tests/test_isolation_escape_contract.rs +59 -1
- package/crates/team-agent/src/lifecycle/tests/worker_spawn_env_red.rs +69 -26
- package/crates/team-agent/src/lifecycle/tests.rs +23 -13
- package/crates/team-agent/src/lifecycle/types.rs +61 -0
- package/crates/team-agent/src/lifecycle/worker_command_context.rs +85 -11
- package/crates/team-agent/src/mcp_server/tests/scoped.rs +100 -1
- package/crates/team-agent/src/mcp_server/tests.rs +196 -2
- package/crates/team-agent/src/messaging/delivery.rs +405 -40
- package/crates/team-agent/src/messaging/helpers.rs +2 -0
- package/crates/team-agent/src/messaging/leader_receiver.rs +53 -1
- package/crates/team-agent/src/messaging/results.rs +4 -0
- package/crates/team-agent/src/messaging/send.rs +34 -0
- package/crates/team-agent/src/messaging/tests/dup_inject.rs +406 -0
- package/crates/team-agent/src/messaging/tests/e23.rs +9 -0
- package/crates/team-agent/src/messaging/tests/leader_inject_acceptance.rs +66 -0
- package/crates/team-agent/src/messaging/tests/mod.rs +1 -0
- package/crates/team-agent/src/messaging/types.rs +4 -1
- package/crates/team-agent/src/model/enums.rs +14 -5
- package/crates/team-agent/src/model/permissions.rs +3 -0
- package/crates/team-agent/src/os_probe.rs +73 -2
- package/crates/team-agent/src/provider/adapter.rs +231 -4
- package/crates/team-agent/src/provider/adapters/cursor_agent.rs +86 -0
- package/crates/team-agent/src/provider/adapters/grok.rs +157 -0
- package/crates/team-agent/src/provider/adapters/mod.rs +2 -0
- package/crates/team-agent/src/provider/bypass_flags.rs +46 -2
- package/crates/team-agent/src/provider/classify.rs +4 -2
- package/crates/team-agent/src/provider/faults.rs +2 -1
- package/crates/team-agent/src/provider/mod.rs +11 -0
- package/crates/team-agent/src/provider/session/capture.rs +158 -20
- package/crates/team-agent/src/provider/session/context_fork/claude.rs +38 -1
- package/crates/team-agent/src/provider/session/context_fork/codex.rs +72 -1
- package/crates/team-agent/src/provider/session/context_fork/outcome.rs +57 -1
- package/crates/team-agent/src/provider/session/context_fork.rs +77 -3
- package/crates/team-agent/src/provider/session/mod.rs +18 -0
- package/crates/team-agent/src/provider/session/resume.rs +57 -0
- package/crates/team-agent/src/provider/session_scan/claude.rs +125 -1
- package/crates/team-agent/src/provider/session_scan/codex.rs +94 -1
- package/crates/team-agent/src/provider/session_scan/common.rs +204 -2
- package/crates/team-agent/src/provider/session_scan/copilot.rs +33 -1
- package/crates/team-agent/src/provider/session_scan/cursor.rs +538 -0
- package/crates/team-agent/src/provider/session_scan/grok.rs +288 -0
- package/crates/team-agent/src/provider/session_scan.rs +8 -0
- package/crates/team-agent/src/provider/submit_now.rs +94 -0
- package/crates/team-agent/src/provider/tests/adapter.rs +304 -0
- package/crates/team-agent/src/provider/types.rs +4 -2
- package/crates/team-agent/src/provider/wire.rs +23 -1
- package/crates/team-agent/src/state/persist.rs +301 -8
- package/crates/team-agent/src/state/repository.rs +5 -0
- package/crates/team-agent/src/tmux_backend/tests.rs +1737 -42
- package/crates/team-agent/src/tmux_backend.rs +1064 -75
- package/crates/team-agent/src/transport/tests/wire.rs +28 -2
- package/crates/team-agent/src/transport.rs +248 -1
- package/npm/install.mjs +129 -73
- package/package.json +4 -4
- package/skills/team-agent/SKILL.md +33 -238
- package/skills/team-agent/command-coverage.json +31 -0
- package/crates/team-agent/src/lifecycle/launch/fork_agent/completion.rs +0 -59
- package/crates/team-agent/src/lifecycle/launch/fork_finalize.rs +0 -488
- package/crates/team-agent/src/lifecycle/launch/fork_pending.rs +0 -109
- package/crates/team-agent/src/lifecycle/launch/fork_state.rs +0 -447
|
@@ -1,9 +1,25 @@
|
|
|
1
|
+
//! ---
|
|
2
|
+
//! purpose: 把已认领消息注入目标 pane 并记录投递阶段
|
|
3
|
+
//! contract:
|
|
4
|
+
//! provides:
|
|
5
|
+
//! - name: deliver_stored_message
|
|
6
|
+
//! what: 单次物理注入;grok 队列默认再按回车顶出;cursor 打开单回车闸且不 flush send-now
|
|
7
|
+
//! boundary:
|
|
8
|
+
//! - 不把物理 success 写成 delivered
|
|
9
|
+
//! - 不重粘文本
|
|
10
|
+
//! - error-bypass(target_resolved+error)只经 claim 原子闸,禁止旁路双投
|
|
11
|
+
//! - 物理 Enter 已出(pane/转录已有 token)后 observer/state 落盘失败不得返 Err 重投
|
|
12
|
+
//! - 提交前持久化失败仍返 Err,由 batch 记 item_blocked 隔离
|
|
13
|
+
//! - 同 message_id 的 token 已在 pane/转录则拒绝二次注入
|
|
14
|
+
//! maturity: wired
|
|
15
|
+
//! ---
|
|
1
16
|
//!
|
|
2
17
|
//! internal_delivery.py + delivery.py — coordinator/调度器侧 thin wrapper + 单条 tmux 注入投递
|
|
3
18
|
//! + trust 有界重试 + turn-open arm (card §16/§65)。
|
|
4
19
|
|
|
5
20
|
use std::cell::{Cell, RefCell};
|
|
6
21
|
use std::path::Path;
|
|
22
|
+
use std::time::Duration;
|
|
7
23
|
|
|
8
24
|
use rusqlite::{params, OptionalExtension};
|
|
9
25
|
|
|
@@ -15,8 +31,9 @@ use crate::provider::wire::{
|
|
|
15
31
|
is_claude_family, parse_canonical_provider, parse_provider, provider_wire,
|
|
16
32
|
};
|
|
17
33
|
use crate::transport::{
|
|
18
|
-
submit_verification_wire,
|
|
19
|
-
|
|
34
|
+
submit_verification_wire, turn_verification_wire, CaptureRange, InjectPayload, InjectReport,
|
|
35
|
+
InjectVerification, Key, PaneId, PaneInfo, SessionName, SubmitObserver, SubmitVerification,
|
|
36
|
+
Target, Transport, WindowName,
|
|
20
37
|
};
|
|
21
38
|
|
|
22
39
|
use super::helpers::{message_exists, MessageStatusShadow};
|
|
@@ -75,6 +92,7 @@ pub fn deliver_stored_message(
|
|
|
75
92
|
reason: None,
|
|
76
93
|
channel: None,
|
|
77
94
|
ack_forced_off: false,
|
|
95
|
+
turn_verification: None,
|
|
78
96
|
})
|
|
79
97
|
}
|
|
80
98
|
|
|
@@ -183,6 +201,7 @@ pub fn deliver_pending_message(
|
|
|
183
201
|
reason: None,
|
|
184
202
|
channel: None,
|
|
185
203
|
ack_forced_off: false,
|
|
204
|
+
turn_verification: None,
|
|
186
205
|
});
|
|
187
206
|
}
|
|
188
207
|
let message = message_for_delivery(store, message_id)?;
|
|
@@ -197,6 +216,7 @@ pub fn deliver_pending_message(
|
|
|
197
216
|
reason: Some(DeliveryRefusal::UnknownRecipient),
|
|
198
217
|
channel: None,
|
|
199
218
|
ack_forced_off: false,
|
|
219
|
+
turn_verification: None,
|
|
200
220
|
});
|
|
201
221
|
};
|
|
202
222
|
let mut canonical_owner_team_id = message.owner_team_id.clone();
|
|
@@ -272,8 +292,6 @@ pub fn deliver_pending_message(
|
|
|
272
292
|
}
|
|
273
293
|
let attempt = if store.claim_for_delivery(message_id)? {
|
|
274
294
|
message.delivery_attempts.saturating_add(1)
|
|
275
|
-
} else if message.status == "target_resolved" && message.error.is_some() {
|
|
276
|
-
bump_delivery_attempts(store, message_id)?
|
|
277
295
|
} else {
|
|
278
296
|
return Ok(DeliveryOutcome {
|
|
279
297
|
ok: false,
|
|
@@ -285,8 +303,31 @@ pub fn deliver_pending_message(
|
|
|
285
303
|
reason: Some(DeliveryRefusal::MessageAlreadyClaimed),
|
|
286
304
|
channel: None,
|
|
287
305
|
ack_forced_off: false,
|
|
306
|
+
turn_verification: None,
|
|
288
307
|
});
|
|
289
308
|
};
|
|
309
|
+
// Attach may requeue this row after the coordinator captured `state` but
|
|
310
|
+
// before this claim. Revalidate through the same owner-team projection
|
|
311
|
+
// that carries a canonical top-level leader binding into the scoped view.
|
|
312
|
+
let fresh_leader_state = if message.recipient == "leader" {
|
|
313
|
+
Some(match canonical_owner_team_id.as_deref() {
|
|
314
|
+
Some(team) => match project_state_for_owner_team(
|
|
315
|
+
workspace,
|
|
316
|
+
team,
|
|
317
|
+
&serde_json::Value::Null,
|
|
318
|
+
Some(store),
|
|
319
|
+
Some(message_id),
|
|
320
|
+
Some(event_log),
|
|
321
|
+
)? {
|
|
322
|
+
OwnerTeamProjection::Projected { state, .. } => state,
|
|
323
|
+
OwnerTeamProjection::Refused(outcome) => return Ok(outcome),
|
|
324
|
+
},
|
|
325
|
+
None => crate::state::persist::load_runtime_state(workspace)?,
|
|
326
|
+
})
|
|
327
|
+
} else {
|
|
328
|
+
None
|
|
329
|
+
};
|
|
330
|
+
let state = fresh_leader_state.as_ref().unwrap_or(state);
|
|
290
331
|
if message.recipient == "leader" {
|
|
291
332
|
let Some(receiver) = leader_receiver_value(state) else {
|
|
292
333
|
store.mark(message_id, "failed", Some("leader_not_attached"))?;
|
|
@@ -315,6 +356,7 @@ pub fn deliver_pending_message(
|
|
|
315
356
|
reason: Some(DeliveryRefusal::LeaderNotAttached),
|
|
316
357
|
channel: Some("rebind_required".to_string()),
|
|
317
358
|
ack_forced_off: false,
|
|
359
|
+
turn_verification: None,
|
|
318
360
|
});
|
|
319
361
|
};
|
|
320
362
|
if let Some(outcome) = leader_receiver_transport_conflict_outcome(
|
|
@@ -346,6 +388,7 @@ pub fn deliver_pending_message(
|
|
|
346
388
|
reason: None,
|
|
347
389
|
channel: Some("rebind_required".to_string()),
|
|
348
390
|
ack_forced_off: false,
|
|
391
|
+
turn_verification: None,
|
|
349
392
|
});
|
|
350
393
|
}
|
|
351
394
|
crate::messaging::LeaderChannelResolution::Unbound(reason) => {
|
|
@@ -385,6 +428,7 @@ pub fn deliver_pending_message(
|
|
|
385
428
|
reason: Some(refusal),
|
|
386
429
|
channel: Some("rebind_required".to_string()),
|
|
387
430
|
ack_forced_off: false,
|
|
431
|
+
turn_verification: None,
|
|
388
432
|
});
|
|
389
433
|
}
|
|
390
434
|
}
|
|
@@ -432,6 +476,7 @@ pub fn deliver_pending_message(
|
|
|
432
476
|
reason: None,
|
|
433
477
|
channel: None,
|
|
434
478
|
ack_forced_off: false,
|
|
479
|
+
turn_verification: None,
|
|
435
480
|
});
|
|
436
481
|
}
|
|
437
482
|
};
|
|
@@ -480,6 +525,30 @@ pub fn deliver_pending_message(
|
|
|
480
525
|
reason: None,
|
|
481
526
|
channel: None,
|
|
482
527
|
ack_forced_off: false,
|
|
528
|
+
turn_verification: None,
|
|
529
|
+
});
|
|
530
|
+
}
|
|
531
|
+
if token_already_visible(transport, state, &message.recipient, &target, message_id) {
|
|
532
|
+
event_log.write(
|
|
533
|
+
"delivery.duplicate_token_refused",
|
|
534
|
+
serde_json::json!({
|
|
535
|
+
"message_id": message_id,
|
|
536
|
+
"recipient": message.recipient,
|
|
537
|
+
"reason": "message_token_already_visible",
|
|
538
|
+
}),
|
|
539
|
+
)?;
|
|
540
|
+
store.mark(message_id, "delivered", None)?;
|
|
541
|
+
return Ok(DeliveryOutcome {
|
|
542
|
+
ok: true,
|
|
543
|
+
status: DeliveryStatus::AlreadyDelivered,
|
|
544
|
+
message_status: MessageStatusShadow("delivered".to_string()),
|
|
545
|
+
message_id: Some(message_id.to_string()),
|
|
546
|
+
verification: Some("message_token_already_visible".to_string()),
|
|
547
|
+
stage: Some(DeliveryStage::Submit),
|
|
548
|
+
reason: None,
|
|
549
|
+
channel: None,
|
|
550
|
+
ack_forced_off: false,
|
|
551
|
+
turn_verification: None,
|
|
483
552
|
});
|
|
484
553
|
}
|
|
485
554
|
let rendered = render_message(
|
|
@@ -503,14 +572,54 @@ pub fn deliver_pending_message(
|
|
|
503
572
|
canonical_owner_team_id.as_deref(),
|
|
504
573
|
resolved.metadata.as_ref(),
|
|
505
574
|
);
|
|
506
|
-
let
|
|
507
|
-
|
|
508
|
-
&
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
575
|
+
let recipient_cursor = recipient_is_cursor_agent(state, &message.recipient);
|
|
576
|
+
let inject_attempt = crate::tmux_backend::with_paste_to_submit_floor(
|
|
577
|
+
paste_to_submit_floor_for_recipient(state, &message.recipient),
|
|
578
|
+
|| {
|
|
579
|
+
crate::tmux_backend::with_cursor_single_enter(recipient_cursor, || {
|
|
580
|
+
transport.inject_with_submit_observer(
|
|
581
|
+
&target,
|
|
582
|
+
&payload,
|
|
583
|
+
Key::Enter,
|
|
584
|
+
true,
|
|
585
|
+
Some(&submit_observer),
|
|
586
|
+
)
|
|
587
|
+
})
|
|
588
|
+
},
|
|
589
|
+
);
|
|
590
|
+
let inject_report = match inject_attempt {
|
|
591
|
+
Ok(report) => {
|
|
592
|
+
// grok 忙时第一次回车只入显式队列。默认 send-now:只重按回车顶出去。
|
|
593
|
+
// 无 `Enter:send now` 时零次额外按键,claude/codex 行为不变。
|
|
594
|
+
// cursor:第二下 Enter 打断进行中回合,不走 flush。
|
|
595
|
+
if !crate::provider::submit_now::keep_provider_queue_requested() && !recipient_cursor {
|
|
596
|
+
match crate::provider::submit_now::flush_explicit_queue(transport, &target) {
|
|
597
|
+
Ok(flush) if flush.extra_enters > 0 => {
|
|
598
|
+
let _ = event_log.write(
|
|
599
|
+
"send.grok_send_now",
|
|
600
|
+
serde_json::json!({
|
|
601
|
+
"message_id": message_id,
|
|
602
|
+
"recipient": message.recipient,
|
|
603
|
+
"extra_enters": flush.extra_enters,
|
|
604
|
+
"mark_cleared": flush.mark_cleared,
|
|
605
|
+
}),
|
|
606
|
+
);
|
|
607
|
+
}
|
|
608
|
+
Ok(_) => {}
|
|
609
|
+
Err(error) => {
|
|
610
|
+
let _ = event_log.write(
|
|
611
|
+
"send.grok_send_now_failed",
|
|
612
|
+
serde_json::json!({
|
|
613
|
+
"message_id": message_id,
|
|
614
|
+
"recipient": message.recipient,
|
|
615
|
+
"error": error.to_string(),
|
|
616
|
+
}),
|
|
617
|
+
);
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
report
|
|
622
|
+
}
|
|
514
623
|
Err(error) => {
|
|
515
624
|
let reason = format!("inject_failed:{error}");
|
|
516
625
|
if message.recipient == "leader" {
|
|
@@ -538,6 +647,7 @@ pub fn deliver_pending_message(
|
|
|
538
647
|
reason: Some(DeliveryRefusal::LeaderNotAttached),
|
|
539
648
|
channel: Some("rebind_required".to_string()),
|
|
540
649
|
ack_forced_off: false,
|
|
650
|
+
turn_verification: None,
|
|
541
651
|
});
|
|
542
652
|
}
|
|
543
653
|
if transport_error_is_target_missing(&error) {
|
|
@@ -582,6 +692,7 @@ pub fn deliver_pending_message(
|
|
|
582
692
|
reason: None,
|
|
583
693
|
channel: None,
|
|
584
694
|
ack_forced_off: false,
|
|
695
|
+
turn_verification: None,
|
|
585
696
|
});
|
|
586
697
|
}
|
|
587
698
|
store.mark(message_id, "target_resolved", Some(&reason))?;
|
|
@@ -595,12 +706,26 @@ pub fn deliver_pending_message(
|
|
|
595
706
|
reason: None,
|
|
596
707
|
channel: None,
|
|
597
708
|
ack_forced_off: false,
|
|
709
|
+
turn_verification: None,
|
|
598
710
|
});
|
|
599
711
|
}
|
|
600
712
|
};
|
|
601
713
|
if let Some(error) = submit_observer.take_error() {
|
|
602
|
-
|
|
714
|
+
// Token on pane/transcript = physical Enter already out → degrade.
|
|
715
|
+
// Poison before submit (no token) still returns Err → item_blocked.
|
|
716
|
+
absorb_state_error_only_after_physical_submit(
|
|
717
|
+
transport,
|
|
718
|
+
state,
|
|
719
|
+
&message.recipient,
|
|
720
|
+
&target,
|
|
721
|
+
message_id,
|
|
722
|
+
event_log,
|
|
723
|
+
"submit_observer",
|
|
724
|
+
error,
|
|
725
|
+
)?;
|
|
603
726
|
}
|
|
727
|
+
persist_submit_verification(workspace, message_id, &inject_report);
|
|
728
|
+
let turn_verification = inject_report.turn_verification;
|
|
604
729
|
let submit_verified = inject_submit_verified(&inject_report);
|
|
605
730
|
let readback_verified = pane_readback_verified(&inject_report);
|
|
606
731
|
// A successful tmux command is never provider-acceptance proof. Leader and
|
|
@@ -655,6 +780,7 @@ pub fn deliver_pending_message(
|
|
|
655
780
|
reason: None,
|
|
656
781
|
channel: None,
|
|
657
782
|
ack_forced_off: false,
|
|
783
|
+
turn_verification: Some(turn_verification),
|
|
658
784
|
});
|
|
659
785
|
}
|
|
660
786
|
store.mark(message_id, "submitted_unverified", Some(&reason))?;
|
|
@@ -668,6 +794,7 @@ pub fn deliver_pending_message(
|
|
|
668
794
|
reason: None,
|
|
669
795
|
channel: None,
|
|
670
796
|
ack_forced_off: false,
|
|
797
|
+
turn_verification: Some(turn_verification),
|
|
671
798
|
});
|
|
672
799
|
}
|
|
673
800
|
// S1-CAPTURE-001 (0.4.8, CR M4 Claude phase-1): for Claude/ClaudeCode
|
|
@@ -733,6 +860,7 @@ pub fn deliver_pending_message(
|
|
|
733
860
|
reason: None,
|
|
734
861
|
channel: None,
|
|
735
862
|
ack_forced_off: false,
|
|
863
|
+
turn_verification: Some(turn_verification),
|
|
736
864
|
});
|
|
737
865
|
}
|
|
738
866
|
}
|
|
@@ -745,7 +873,9 @@ pub fn deliver_pending_message(
|
|
|
745
873
|
"leader_receiver.receipt_source_unavailable",
|
|
746
874
|
serde_json::json!({"message_id": message_id}),
|
|
747
875
|
)?;
|
|
748
|
-
|
|
876
|
+
let mut outcome = leader_receipt_source_unavailable_outcome(message_id);
|
|
877
|
+
outcome.turn_verification = Some(turn_verification);
|
|
878
|
+
return Ok(outcome);
|
|
749
879
|
}
|
|
750
880
|
LeaderReceiptObservation::TokenAbsent => {
|
|
751
881
|
store.mark(message_id, "submitted_pending_acceptance", None)?;
|
|
@@ -756,10 +886,10 @@ pub fn deliver_pending_message(
|
|
|
756
886
|
"reason": "provider_receipt_not_observed",
|
|
757
887
|
}),
|
|
758
888
|
)?;
|
|
759
|
-
|
|
760
|
-
message_id,
|
|
761
|
-
|
|
762
|
-
)
|
|
889
|
+
let mut outcome =
|
|
890
|
+
leader_acceptance_pending_outcome(message_id, "submitted_pending_acceptance");
|
|
891
|
+
outcome.turn_verification = Some(turn_verification);
|
|
892
|
+
return Ok(outcome);
|
|
763
893
|
}
|
|
764
894
|
LeaderReceiptObservation::TokenObserved => {}
|
|
765
895
|
}
|
|
@@ -782,14 +912,26 @@ pub fn deliver_pending_message(
|
|
|
782
912
|
reason: None,
|
|
783
913
|
channel: None,
|
|
784
914
|
ack_forced_off: false,
|
|
915
|
+
turn_verification: Some(turn_verification),
|
|
785
916
|
};
|
|
786
|
-
stamp_first_send_at_if_leader_to_worker_scoped(
|
|
917
|
+
if let Err(error) = stamp_first_send_at_if_leader_to_worker_scoped(
|
|
787
918
|
workspace,
|
|
788
919
|
&message.sender,
|
|
789
920
|
&message.recipient,
|
|
790
921
|
canonical_owner_team_id.as_deref(),
|
|
791
|
-
)
|
|
792
|
-
|
|
922
|
+
) {
|
|
923
|
+
absorb_state_error_only_after_physical_submit(
|
|
924
|
+
transport,
|
|
925
|
+
state,
|
|
926
|
+
&message.recipient,
|
|
927
|
+
&target,
|
|
928
|
+
message_id,
|
|
929
|
+
event_log,
|
|
930
|
+
"stamp_first_send_at",
|
|
931
|
+
error,
|
|
932
|
+
)?;
|
|
933
|
+
}
|
|
934
|
+
if let Err(error) = record_turn_open_if_leader_to_worker_scoped(
|
|
793
935
|
workspace,
|
|
794
936
|
&message.sender,
|
|
795
937
|
&message.recipient,
|
|
@@ -797,7 +939,18 @@ pub fn deliver_pending_message(
|
|
|
797
939
|
event_log,
|
|
798
940
|
canonical_owner_team_id.as_deref(),
|
|
799
941
|
resolved.metadata.as_ref(),
|
|
800
|
-
)
|
|
942
|
+
) {
|
|
943
|
+
absorb_state_error_only_after_physical_submit(
|
|
944
|
+
transport,
|
|
945
|
+
state,
|
|
946
|
+
&message.recipient,
|
|
947
|
+
&target,
|
|
948
|
+
message_id,
|
|
949
|
+
event_log,
|
|
950
|
+
"turn_open_after_delivery",
|
|
951
|
+
error,
|
|
952
|
+
)?;
|
|
953
|
+
}
|
|
801
954
|
Ok(outcome)
|
|
802
955
|
}
|
|
803
956
|
|
|
@@ -864,6 +1017,7 @@ pub(crate) fn mark_worker_target_missing(
|
|
|
864
1017
|
reason: Some(DeliveryRefusal::TmuxTargetMissing),
|
|
865
1018
|
channel: Some("delivery_blocked".to_string()),
|
|
866
1019
|
ack_forced_off: false,
|
|
1020
|
+
turn_verification: None,
|
|
867
1021
|
})
|
|
868
1022
|
}
|
|
869
1023
|
|
|
@@ -904,6 +1058,27 @@ pub fn requeue_worker_target_missing_messages(
|
|
|
904
1058
|
Ok(ids)
|
|
905
1059
|
}
|
|
906
1060
|
|
|
1061
|
+
/// 把 post-Enter 消费判定落到 workspace,供确认面扫盘。
|
|
1062
|
+
///
|
|
1063
|
+
/// //! purpose: persist SubmitVerification after inject
|
|
1064
|
+
/// //! contract: file contains Debug variant name (SubmitConsumptionUnverified / EnterSentWithoutPlaceholderCheck)
|
|
1065
|
+
/// //! boundary: write failure must not fail delivery
|
|
1066
|
+
fn persist_submit_verification(workspace: &Path, message_id: &str, report: &InjectReport) {
|
|
1067
|
+
let dir = workspace.join(".team").join("runtime");
|
|
1068
|
+
if std::fs::create_dir_all(&dir).is_err() {
|
|
1069
|
+
return;
|
|
1070
|
+
}
|
|
1071
|
+
let body = serde_json::json!({
|
|
1072
|
+
"message_id": message_id,
|
|
1073
|
+
"submit_verification": format!("{:?}", report.submit_verification),
|
|
1074
|
+
"inject_verification": format!("{:?}", report.inject_verification),
|
|
1075
|
+
"turn_verification": turn_verification_wire(report.turn_verification),
|
|
1076
|
+
"submit_verified": inject_submit_verified(report),
|
|
1077
|
+
"attempts": report.attempts,
|
|
1078
|
+
});
|
|
1079
|
+
let _ = std::fs::write(dir.join("submit-verification.json"), format!("{body}\n"));
|
|
1080
|
+
}
|
|
1081
|
+
|
|
907
1082
|
/// 0.3.27: promoted to pub(crate) for leader_receiver.rs verification gate.
|
|
908
1083
|
pub(crate) fn inject_submit_verified(report: &InjectReport) -> bool {
|
|
909
1084
|
match report.submit_verification {
|
|
@@ -1018,6 +1193,7 @@ fn deliver_leader_via_app_server(
|
|
|
1018
1193
|
reason: None,
|
|
1019
1194
|
channel: Some("codex_app_server".to_string()),
|
|
1020
1195
|
ack_forced_off: false,
|
|
1196
|
+
turn_verification: None,
|
|
1021
1197
|
})
|
|
1022
1198
|
}
|
|
1023
1199
|
Err(error) => app_server_delivery_failure(
|
|
@@ -1064,6 +1240,7 @@ fn app_server_delivery_failure(
|
|
|
1064
1240
|
reason: Some(DeliveryRefusal::RecipientBusy),
|
|
1065
1241
|
channel: Some("leader_busy".to_string()),
|
|
1066
1242
|
ack_forced_off: false,
|
|
1243
|
+
turn_verification: None,
|
|
1067
1244
|
})
|
|
1068
1245
|
}
|
|
1069
1246
|
crate::codex_app_server::AppServerError::ThreadStale { expected, actual } => {
|
|
@@ -1103,6 +1280,7 @@ fn app_server_delivery_failure(
|
|
|
1103
1280
|
reason: Some(DeliveryRefusal::MissingPermissions),
|
|
1104
1281
|
channel: Some("codex_app_server".to_string()),
|
|
1105
1282
|
ack_forced_off: false,
|
|
1283
|
+
turn_verification: None,
|
|
1106
1284
|
})
|
|
1107
1285
|
}
|
|
1108
1286
|
crate::codex_app_server::AppServerError::ProtocolMismatch(_)
|
|
@@ -1155,6 +1333,7 @@ fn rebind_required_outcome(message_id: &str, action: &str) -> DeliveryOutcome {
|
|
|
1155
1333
|
reason: Some(DeliveryRefusal::LeaderNotAttached),
|
|
1156
1334
|
channel: Some("rebind_required".to_string()),
|
|
1157
1335
|
ack_forced_off: false,
|
|
1336
|
+
turn_verification: None,
|
|
1158
1337
|
}
|
|
1159
1338
|
}
|
|
1160
1339
|
|
|
@@ -1558,6 +1737,7 @@ fn leader_receiver_transport_conflict_outcome(
|
|
|
1558
1737
|
reason: Some(DeliveryRefusal::LeaderNotAttached),
|
|
1559
1738
|
channel: Some("rebind_required".to_string()),
|
|
1560
1739
|
ack_forced_off: false,
|
|
1740
|
+
turn_verification: None,
|
|
1561
1741
|
}));
|
|
1562
1742
|
}
|
|
1563
1743
|
}
|
|
@@ -1973,23 +2153,6 @@ fn message_for_delivery(
|
|
|
1973
2153
|
Ok(message)
|
|
1974
2154
|
}
|
|
1975
2155
|
|
|
1976
|
-
fn bump_delivery_attempts(store: &MessageStore, message_id: &str) -> Result<u32, MessagingError> {
|
|
1977
|
-
let conn = crate::db::schema::open_db(store.db_path())?;
|
|
1978
|
-
conn.execute(
|
|
1979
|
-
"update messages
|
|
1980
|
-
set delivery_attempts = delivery_attempts + 1,
|
|
1981
|
-
updated_at = ?2
|
|
1982
|
-
where message_id = ?1",
|
|
1983
|
-
params![message_id, chrono::Utc::now().to_rfc3339()],
|
|
1984
|
-
)?;
|
|
1985
|
-
let attempts = conn.query_row(
|
|
1986
|
-
"select delivery_attempts from messages where message_id = ?1",
|
|
1987
|
-
params![message_id],
|
|
1988
|
-
|row| row.get::<_, i64>(0),
|
|
1989
|
-
)?;
|
|
1990
|
-
Ok(attempts.max(0) as u32)
|
|
1991
|
-
}
|
|
1992
|
-
|
|
1993
2156
|
/// Pre-inject gate (Contract B): peek the recipient pane and answer "is there an
|
|
1994
2157
|
/// actionable provider startup prompt right now (trust menu or update prompt)" using
|
|
1995
2158
|
/// the SHARED provider/startup_prompt recognizers — no second classifier, no provider
|
|
@@ -2058,7 +2221,41 @@ fn recipient_pane_has_actionable_startup_prompt(
|
|
|
2058
2221
|
crate::provider::classify_copilot_startup_screen(&captured),
|
|
2059
2222
|
crate::provider::StartupScreenDecision::AnswerWorkspaceTrust
|
|
2060
2223
|
),
|
|
2061
|
-
Provider::GeminiCli | Provider::Fake => false,
|
|
2224
|
+
Provider::Grok | Provider::CursorAgent | Provider::GeminiCli | Provider::Fake => false,
|
|
2225
|
+
}
|
|
2226
|
+
}
|
|
2227
|
+
|
|
2228
|
+
fn recipient_provider(state: &serde_json::Value, recipient: &str) -> Option<Provider> {
|
|
2229
|
+
state
|
|
2230
|
+
.get("agents")
|
|
2231
|
+
.and_then(serde_json::Value::as_object)
|
|
2232
|
+
.and_then(|agents| agents.get(recipient))
|
|
2233
|
+
.and_then(|agent| agent.get("provider"))
|
|
2234
|
+
.and_then(serde_json::Value::as_str)
|
|
2235
|
+
.and_then(parse_canonical_provider)
|
|
2236
|
+
}
|
|
2237
|
+
|
|
2238
|
+
fn recipient_is_cursor_agent(state: &serde_json::Value, recipient: &str) -> bool {
|
|
2239
|
+
matches!(
|
|
2240
|
+
recipient_provider(state, recipient),
|
|
2241
|
+
Some(Provider::CursorAgent)
|
|
2242
|
+
)
|
|
2243
|
+
}
|
|
2244
|
+
|
|
2245
|
+
/// ---
|
|
2246
|
+
/// purpose: paste→Enter 地板按收件人 provider 选择
|
|
2247
|
+
/// contract: CursorAgent 与 Grok 为 1s;其余(含 claude)为 ZERO
|
|
2248
|
+
/// boundary: 不改 cursor 单回车闸;不把地板套到 claude/codex
|
|
2249
|
+
/// ---
|
|
2250
|
+
pub(crate) fn paste_to_submit_floor_for_recipient(
|
|
2251
|
+
state: &serde_json::Value,
|
|
2252
|
+
recipient: &str,
|
|
2253
|
+
) -> Duration {
|
|
2254
|
+
match recipient_provider(state, recipient) {
|
|
2255
|
+
Some(Provider::CursorAgent) | Some(Provider::Grok) => {
|
|
2256
|
+
crate::tmux_backend::CURSOR_PASTE_TO_SUBMIT_FLOOR
|
|
2257
|
+
}
|
|
2258
|
+
_ => Duration::ZERO,
|
|
2062
2259
|
}
|
|
2063
2260
|
}
|
|
2064
2261
|
|
|
@@ -2129,6 +2326,7 @@ fn leader_acceptance_pending_outcome(message_id: &str, status: &str) -> Delivery
|
|
|
2129
2326
|
reason: None,
|
|
2130
2327
|
channel: Some("leader_acceptance_pending".to_string()),
|
|
2131
2328
|
ack_forced_off: false,
|
|
2329
|
+
turn_verification: None,
|
|
2132
2330
|
}
|
|
2133
2331
|
}
|
|
2134
2332
|
|
|
@@ -2143,6 +2341,7 @@ pub(crate) fn leader_receipt_source_unavailable_outcome(message_id: &str) -> Del
|
|
|
2143
2341
|
reason: None,
|
|
2144
2342
|
channel: Some("leader_receipt_source_unavailable".to_string()),
|
|
2145
2343
|
ack_forced_off: false,
|
|
2344
|
+
turn_verification: None,
|
|
2146
2345
|
}
|
|
2147
2346
|
}
|
|
2148
2347
|
|
|
@@ -2184,6 +2383,7 @@ fn observe_pending_leader_acceptance(
|
|
|
2184
2383
|
reason: None,
|
|
2185
2384
|
channel: Some("leader_receiver".to_string()),
|
|
2186
2385
|
ack_forced_off: false,
|
|
2386
|
+
turn_verification: None,
|
|
2187
2387
|
})
|
|
2188
2388
|
}
|
|
2189
2389
|
|
|
@@ -2214,6 +2414,7 @@ pub fn handle_trust_retry_needed(
|
|
|
2214
2414
|
reason: None,
|
|
2215
2415
|
channel: None,
|
|
2216
2416
|
ack_forced_off: false,
|
|
2417
|
+
turn_verification: None,
|
|
2217
2418
|
});
|
|
2218
2419
|
}
|
|
2219
2420
|
let next_attempt = payload.attempt.saturating_add(1);
|
|
@@ -2254,6 +2455,7 @@ pub fn handle_trust_retry_needed(
|
|
|
2254
2455
|
reason: None,
|
|
2255
2456
|
channel: None,
|
|
2256
2457
|
ack_forced_off: false,
|
|
2458
|
+
turn_verification: None,
|
|
2257
2459
|
})
|
|
2258
2460
|
}
|
|
2259
2461
|
|
|
@@ -2751,6 +2953,7 @@ fn refuse_owner_team_resolution(
|
|
|
2751
2953
|
reason: Some(refusal),
|
|
2752
2954
|
channel: Some("owner_team_resolution".to_string()),
|
|
2753
2955
|
ack_forced_off: false,
|
|
2956
|
+
turn_verification: None,
|
|
2754
2957
|
})
|
|
2755
2958
|
}
|
|
2756
2959
|
|
|
@@ -2808,6 +3011,7 @@ pub fn retry_injection_after_trust_auto_answer(
|
|
|
2808
3011
|
reason: None,
|
|
2809
3012
|
channel: None,
|
|
2810
3013
|
ack_forced_off: false,
|
|
3014
|
+
turn_verification: None,
|
|
2811
3015
|
})
|
|
2812
3016
|
}
|
|
2813
3017
|
|
|
@@ -2844,6 +3048,134 @@ fn rollout_tail_contains(path: &std::path::Path, needle: &str, tail_bytes: u64)
|
|
|
2844
3048
|
rollout_tail_contains_result(path, needle, tail_bytes).unwrap_or(false)
|
|
2845
3049
|
}
|
|
2846
3050
|
|
|
3051
|
+
fn message_token_marker(message_id: &str) -> String {
|
|
3052
|
+
format!("[team-agent-token:{message_id}]")
|
|
3053
|
+
}
|
|
3054
|
+
|
|
3055
|
+
fn text_contains_message_token(text: &str, message_id: &str) -> bool {
|
|
3056
|
+
!message_id.is_empty() && text.contains(&message_token_marker(message_id))
|
|
3057
|
+
}
|
|
3058
|
+
|
|
3059
|
+
/// ---
|
|
3060
|
+
/// purpose: 同 message_id 已在 pane 或转录出现则视为已注入
|
|
3061
|
+
/// contract:
|
|
3062
|
+
/// provides:
|
|
3063
|
+
/// - name: token_already_visible
|
|
3064
|
+
/// what: capture Tail(80) 或 recipient rollout_path 尾含 `[team-agent-token:{id}]` 则为 true
|
|
3065
|
+
/// boundary:
|
|
3066
|
+
/// - capture 失败不当已见(倒向允许注入,避免丢信)
|
|
3067
|
+
/// - 不请求 Full(wave-2:滚出屏的 trust 残渣不当成已见/可行动)
|
|
3068
|
+
/// - 不把 JSON 字段里的 message_id 当 pane token
|
|
3069
|
+
/// ---
|
|
3070
|
+
pub(crate) fn token_already_visible(
|
|
3071
|
+
transport: &dyn Transport,
|
|
3072
|
+
state: &serde_json::Value,
|
|
3073
|
+
recipient: &str,
|
|
3074
|
+
target: &Target,
|
|
3075
|
+
message_id: &str,
|
|
3076
|
+
) -> bool {
|
|
3077
|
+
if message_id.is_empty() {
|
|
3078
|
+
return false;
|
|
3079
|
+
}
|
|
3080
|
+
let marker = message_token_marker(message_id);
|
|
3081
|
+
match transport.capture(target, CaptureRange::Tail(80)) {
|
|
3082
|
+
Ok(captured) if text_contains_message_token(&captured.text, message_id) => return true,
|
|
3083
|
+
_ => {}
|
|
3084
|
+
}
|
|
3085
|
+
recipient_transcript_contains(state, recipient, &marker)
|
|
3086
|
+
}
|
|
3087
|
+
|
|
3088
|
+
fn recipient_transcript_contains(state: &serde_json::Value, recipient: &str, marker: &str) -> bool {
|
|
3089
|
+
for path in recipient_transcript_paths(state, recipient) {
|
|
3090
|
+
if rollout_tail_contains(&path, marker, 64 * 1024) {
|
|
3091
|
+
return true;
|
|
3092
|
+
}
|
|
3093
|
+
}
|
|
3094
|
+
false
|
|
3095
|
+
}
|
|
3096
|
+
|
|
3097
|
+
fn recipient_transcript_paths(
|
|
3098
|
+
state: &serde_json::Value,
|
|
3099
|
+
recipient: &str,
|
|
3100
|
+
) -> Vec<std::path::PathBuf> {
|
|
3101
|
+
let mut paths = Vec::new();
|
|
3102
|
+
let push = |paths: &mut Vec<std::path::PathBuf>, value: Option<&serde_json::Value>| {
|
|
3103
|
+
if let Some(path) = value
|
|
3104
|
+
.and_then(serde_json::Value::as_str)
|
|
3105
|
+
.filter(|p| !p.is_empty())
|
|
3106
|
+
{
|
|
3107
|
+
paths.push(std::path::PathBuf::from(path));
|
|
3108
|
+
}
|
|
3109
|
+
};
|
|
3110
|
+
if recipient == "leader" {
|
|
3111
|
+
push(
|
|
3112
|
+
&mut paths,
|
|
3113
|
+
leader_receiver_record_path(state, "rollout_path"),
|
|
3114
|
+
);
|
|
3115
|
+
}
|
|
3116
|
+
let agent = state.get("agents").and_then(|agents| agents.get(recipient));
|
|
3117
|
+
push(
|
|
3118
|
+
&mut paths,
|
|
3119
|
+
agent.and_then(|agent| agent.get("rollout_path")),
|
|
3120
|
+
);
|
|
3121
|
+
paths
|
|
3122
|
+
}
|
|
3123
|
+
|
|
3124
|
+
fn leader_receiver_record_path<'a>(
|
|
3125
|
+
state: &'a serde_json::Value,
|
|
3126
|
+
field: &str,
|
|
3127
|
+
) -> Option<&'a serde_json::Value> {
|
|
3128
|
+
state
|
|
3129
|
+
.get("leader_receiver")
|
|
3130
|
+
.and_then(|receiver| receiver.get(field))
|
|
3131
|
+
.or_else(|| {
|
|
3132
|
+
let active = state
|
|
3133
|
+
.get("active_team_key")
|
|
3134
|
+
.and_then(serde_json::Value::as_str)?;
|
|
3135
|
+
state
|
|
3136
|
+
.get("teams")
|
|
3137
|
+
.and_then(serde_json::Value::as_object)
|
|
3138
|
+
.and_then(|teams| teams.get(active))
|
|
3139
|
+
.and_then(|team| team.get("leader_receiver"))
|
|
3140
|
+
.and_then(|receiver| receiver.get(field))
|
|
3141
|
+
})
|
|
3142
|
+
}
|
|
3143
|
+
|
|
3144
|
+
fn absorb_state_error_only_after_physical_submit(
|
|
3145
|
+
transport: &dyn Transport,
|
|
3146
|
+
state: &serde_json::Value,
|
|
3147
|
+
recipient: &str,
|
|
3148
|
+
target: &Target,
|
|
3149
|
+
message_id: &str,
|
|
3150
|
+
event_log: &EventLog,
|
|
3151
|
+
stage: &str,
|
|
3152
|
+
error: MessagingError,
|
|
3153
|
+
) -> Result<(), MessagingError> {
|
|
3154
|
+
if token_already_visible(transport, state, recipient, target, message_id) {
|
|
3155
|
+
log_post_physical_submit_state_error(event_log, message_id, stage, &error)
|
|
3156
|
+
} else {
|
|
3157
|
+
Err(error)
|
|
3158
|
+
}
|
|
3159
|
+
}
|
|
3160
|
+
|
|
3161
|
+
fn log_post_physical_submit_state_error(
|
|
3162
|
+
event_log: &EventLog,
|
|
3163
|
+
message_id: &str,
|
|
3164
|
+
stage: &str,
|
|
3165
|
+
error: &MessagingError,
|
|
3166
|
+
) -> Result<(), MessagingError> {
|
|
3167
|
+
event_log.write(
|
|
3168
|
+
"delivery.post_submit_state_degraded",
|
|
3169
|
+
serde_json::json!({
|
|
3170
|
+
"message_id": message_id,
|
|
3171
|
+
"stage": stage,
|
|
3172
|
+
"error": error.to_string(),
|
|
3173
|
+
"physical_submit": true,
|
|
3174
|
+
}),
|
|
3175
|
+
)?;
|
|
3176
|
+
Ok(())
|
|
3177
|
+
}
|
|
3178
|
+
|
|
2847
3179
|
fn rollout_tail_contains_result(
|
|
2848
3180
|
path: &std::path::Path,
|
|
2849
3181
|
needle: &str,
|
|
@@ -2860,3 +3192,36 @@ fn rollout_tail_contains_result(
|
|
|
2860
3192
|
let haystack = String::from_utf8_lossy(&buf);
|
|
2861
3193
|
Ok(haystack.contains(needle))
|
|
2862
3194
|
}
|
|
3195
|
+
|
|
3196
|
+
#[cfg(test)]
|
|
3197
|
+
mod paste_floor_tests {
|
|
3198
|
+
use super::*;
|
|
3199
|
+
use std::time::Duration;
|
|
3200
|
+
|
|
3201
|
+
fn state_with_provider(provider: &str) -> serde_json::Value {
|
|
3202
|
+
serde_json::json!({
|
|
3203
|
+
"agents": { "w1": { "provider": provider } }
|
|
3204
|
+
})
|
|
3205
|
+
}
|
|
3206
|
+
|
|
3207
|
+
#[test]
|
|
3208
|
+
fn grok_paste_to_submit_floor_matches_cursor() {
|
|
3209
|
+
let grok = paste_to_submit_floor_for_recipient(&state_with_provider("grok"), "w1");
|
|
3210
|
+
let cursor = paste_to_submit_floor_for_recipient(&state_with_provider("cursor_agent"), "w1");
|
|
3211
|
+
assert_eq!(grok, crate::tmux_backend::CURSOR_PASTE_TO_SUBMIT_FLOOR);
|
|
3212
|
+
assert_eq!(cursor, crate::tmux_backend::CURSOR_PASTE_TO_SUBMIT_FLOOR);
|
|
3213
|
+
assert_eq!(grok, cursor);
|
|
3214
|
+
}
|
|
3215
|
+
|
|
3216
|
+
#[test]
|
|
3217
|
+
fn claude_and_codex_paste_to_submit_floor_stays_zero() {
|
|
3218
|
+
assert_eq!(
|
|
3219
|
+
paste_to_submit_floor_for_recipient(&state_with_provider("claude"), "w1"),
|
|
3220
|
+
Duration::ZERO
|
|
3221
|
+
);
|
|
3222
|
+
assert_eq!(
|
|
3223
|
+
paste_to_submit_floor_for_recipient(&state_with_provider("codex"), "w1"),
|
|
3224
|
+
Duration::ZERO
|
|
3225
|
+
);
|
|
3226
|
+
}
|
|
3227
|
+
}
|