@team-agent/installer 0.5.66 → 0.5.69
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 +156 -0
- package/crates/team-agent/src/cli/emit.rs +6 -5
- package/crates/team-agent/src/cli/grok_slot.rs +299 -0
- package/crates/team-agent/src/cli/leader.rs +22 -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 +3 -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 +121 -5
- 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 +31 -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 +3 -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 +973 -75
- 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 +398 -26
- package/crates/team-agent/src/lifecycle/launch/add_agent_state.rs +80 -0
- package/crates/team-agent/src/lifecycle/launch/agent_state.rs +60 -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 +548 -0
- package/crates/team-agent/src/lifecycle/launch/cursor_mcp_iso.rs +120 -0
- package/crates/team-agent/src/lifecycle/launch/fork_agent.rs +208 -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 +482 -1
- package/crates/team-agent/src/lifecycle/launch/ownership.rs +40 -0
- package/crates/team-agent/src/lifecycle/launch/pi_mcp.rs +830 -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 +87 -3
- 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 +127 -25
- package/crates/team-agent/src/lifecycle/launch/spec_state.rs +140 -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 +107 -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 +99 -0
- package/crates/team-agent/src/lifecycle/profile_smoke.rs +52 -1
- package/crates/team-agent/src/lifecycle/restart/agent.rs +214 -160
- package/crates/team-agent/src/lifecycle/restart/common.rs +513 -37
- 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 +233 -6
- 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/claude_profile_launch_red.rs +24 -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 +464 -0
- package/crates/team-agent/src/lifecycle/tests/cursor_require_explicit_model_red.rs +247 -0
- package/crates/team-agent/src/lifecycle/tests/cursor_restart_resume_red.rs +353 -0
- package/crates/team-agent/src/lifecycle/tests/fixtures/pi_list_models_g0.stdout.txt +222 -0
- package/crates/team-agent/src/lifecycle/tests/g1_silent_faces.rs +787 -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 +485 -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/harvest2_a_batch_red.rs +1 -1
- 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/pi_catalog_red.rs +97 -0
- package/crates/team-agent/src/lifecycle/tests/pi_command_red.rs +128 -0
- package/crates/team-agent/src/lifecycle/tests/pi_compiler_red.rs +207 -0
- package/crates/team-agent/src/lifecycle/tests/pi_dual_entry_red.rs +838 -0
- package/crates/team-agent/src/lifecycle/tests/pi_executable_mcp_red.rs +320 -0
- package/crates/team-agent/src/lifecycle/tests/pi_lifecycle_doctor_red.rs +98 -0
- package/crates/team-agent/src/lifecycle/tests/pi_session_delivery_red.rs +216 -0
- package/crates/team-agent/src/lifecycle/tests/restart_rebind_hotfix_252_red.rs +3 -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 +30 -13
- package/crates/team-agent/src/lifecycle/types.rs +61 -0
- package/crates/team-agent/src/lifecycle/worker_command_context.rs +143 -12
- 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 +430 -41
- 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 +17 -6
- 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 +259 -6
- package/crates/team-agent/src/provider/adapters/cursor_agent.rs +89 -0
- package/crates/team-agent/src/provider/adapters/grok.rs +157 -0
- package/crates/team-agent/src/provider/adapters/mod.rs +3 -0
- package/crates/team-agent/src/provider/adapters/pi.rs +152 -0
- package/crates/team-agent/src/provider/bypass_flags.rs +47 -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 +78 -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 +205 -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/pi.rs +172 -0
- package/crates/team-agent/src/provider/session_scan.rs +12 -0
- package/crates/team-agent/src/provider/submit_now.rs +94 -0
- package/crates/team-agent/src/provider/tests/adapter.rs +384 -0
- package/crates/team-agent/src/provider/types.rs +4 -2
- package/crates/team-agent/src/provider/wire.rs +29 -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 +1116 -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 +32 -239
- 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,56 @@ 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_single_enter = recipient_requires_single_enter(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_single_enter, || {
|
|
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()
|
|
596
|
+
&& recipient_allows_explicit_queue_flush(state, &message.recipient)
|
|
597
|
+
{
|
|
598
|
+
match crate::provider::submit_now::flush_explicit_queue(transport, &target) {
|
|
599
|
+
Ok(flush) if flush.extra_enters > 0 => {
|
|
600
|
+
let _ = event_log.write(
|
|
601
|
+
"send.grok_send_now",
|
|
602
|
+
serde_json::json!({
|
|
603
|
+
"message_id": message_id,
|
|
604
|
+
"recipient": message.recipient,
|
|
605
|
+
"extra_enters": flush.extra_enters,
|
|
606
|
+
"mark_cleared": flush.mark_cleared,
|
|
607
|
+
}),
|
|
608
|
+
);
|
|
609
|
+
}
|
|
610
|
+
Ok(_) => {}
|
|
611
|
+
Err(error) => {
|
|
612
|
+
let _ = event_log.write(
|
|
613
|
+
"send.grok_send_now_failed",
|
|
614
|
+
serde_json::json!({
|
|
615
|
+
"message_id": message_id,
|
|
616
|
+
"recipient": message.recipient,
|
|
617
|
+
"error": error.to_string(),
|
|
618
|
+
}),
|
|
619
|
+
);
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
report
|
|
624
|
+
}
|
|
514
625
|
Err(error) => {
|
|
515
626
|
let reason = format!("inject_failed:{error}");
|
|
516
627
|
if message.recipient == "leader" {
|
|
@@ -538,6 +649,7 @@ pub fn deliver_pending_message(
|
|
|
538
649
|
reason: Some(DeliveryRefusal::LeaderNotAttached),
|
|
539
650
|
channel: Some("rebind_required".to_string()),
|
|
540
651
|
ack_forced_off: false,
|
|
652
|
+
turn_verification: None,
|
|
541
653
|
});
|
|
542
654
|
}
|
|
543
655
|
if transport_error_is_target_missing(&error) {
|
|
@@ -582,6 +694,7 @@ pub fn deliver_pending_message(
|
|
|
582
694
|
reason: None,
|
|
583
695
|
channel: None,
|
|
584
696
|
ack_forced_off: false,
|
|
697
|
+
turn_verification: None,
|
|
585
698
|
});
|
|
586
699
|
}
|
|
587
700
|
store.mark(message_id, "target_resolved", Some(&reason))?;
|
|
@@ -595,12 +708,26 @@ pub fn deliver_pending_message(
|
|
|
595
708
|
reason: None,
|
|
596
709
|
channel: None,
|
|
597
710
|
ack_forced_off: false,
|
|
711
|
+
turn_verification: None,
|
|
598
712
|
});
|
|
599
713
|
}
|
|
600
714
|
};
|
|
601
715
|
if let Some(error) = submit_observer.take_error() {
|
|
602
|
-
|
|
716
|
+
// Token on pane/transcript = physical Enter already out → degrade.
|
|
717
|
+
// Poison before submit (no token) still returns Err → item_blocked.
|
|
718
|
+
absorb_state_error_only_after_physical_submit(
|
|
719
|
+
transport,
|
|
720
|
+
state,
|
|
721
|
+
&message.recipient,
|
|
722
|
+
&target,
|
|
723
|
+
message_id,
|
|
724
|
+
event_log,
|
|
725
|
+
"submit_observer",
|
|
726
|
+
error,
|
|
727
|
+
)?;
|
|
603
728
|
}
|
|
729
|
+
persist_submit_verification(workspace, message_id, &inject_report);
|
|
730
|
+
let turn_verification = inject_report.turn_verification;
|
|
604
731
|
let submit_verified = inject_submit_verified(&inject_report);
|
|
605
732
|
let readback_verified = pane_readback_verified(&inject_report);
|
|
606
733
|
// A successful tmux command is never provider-acceptance proof. Leader and
|
|
@@ -655,6 +782,7 @@ pub fn deliver_pending_message(
|
|
|
655
782
|
reason: None,
|
|
656
783
|
channel: None,
|
|
657
784
|
ack_forced_off: false,
|
|
785
|
+
turn_verification: Some(turn_verification),
|
|
658
786
|
});
|
|
659
787
|
}
|
|
660
788
|
store.mark(message_id, "submitted_unverified", Some(&reason))?;
|
|
@@ -668,6 +796,7 @@ pub fn deliver_pending_message(
|
|
|
668
796
|
reason: None,
|
|
669
797
|
channel: None,
|
|
670
798
|
ack_forced_off: false,
|
|
799
|
+
turn_verification: Some(turn_verification),
|
|
671
800
|
});
|
|
672
801
|
}
|
|
673
802
|
// S1-CAPTURE-001 (0.4.8, CR M4 Claude phase-1): for Claude/ClaudeCode
|
|
@@ -733,6 +862,7 @@ pub fn deliver_pending_message(
|
|
|
733
862
|
reason: None,
|
|
734
863
|
channel: None,
|
|
735
864
|
ack_forced_off: false,
|
|
865
|
+
turn_verification: Some(turn_verification),
|
|
736
866
|
});
|
|
737
867
|
}
|
|
738
868
|
}
|
|
@@ -745,7 +875,9 @@ pub fn deliver_pending_message(
|
|
|
745
875
|
"leader_receiver.receipt_source_unavailable",
|
|
746
876
|
serde_json::json!({"message_id": message_id}),
|
|
747
877
|
)?;
|
|
748
|
-
|
|
878
|
+
let mut outcome = leader_receipt_source_unavailable_outcome(message_id);
|
|
879
|
+
outcome.turn_verification = Some(turn_verification);
|
|
880
|
+
return Ok(outcome);
|
|
749
881
|
}
|
|
750
882
|
LeaderReceiptObservation::TokenAbsent => {
|
|
751
883
|
store.mark(message_id, "submitted_pending_acceptance", None)?;
|
|
@@ -756,10 +888,10 @@ pub fn deliver_pending_message(
|
|
|
756
888
|
"reason": "provider_receipt_not_observed",
|
|
757
889
|
}),
|
|
758
890
|
)?;
|
|
759
|
-
|
|
760
|
-
message_id,
|
|
761
|
-
|
|
762
|
-
)
|
|
891
|
+
let mut outcome =
|
|
892
|
+
leader_acceptance_pending_outcome(message_id, "submitted_pending_acceptance");
|
|
893
|
+
outcome.turn_verification = Some(turn_verification);
|
|
894
|
+
return Ok(outcome);
|
|
763
895
|
}
|
|
764
896
|
LeaderReceiptObservation::TokenObserved => {}
|
|
765
897
|
}
|
|
@@ -782,14 +914,26 @@ pub fn deliver_pending_message(
|
|
|
782
914
|
reason: None,
|
|
783
915
|
channel: None,
|
|
784
916
|
ack_forced_off: false,
|
|
917
|
+
turn_verification: Some(turn_verification),
|
|
785
918
|
};
|
|
786
|
-
stamp_first_send_at_if_leader_to_worker_scoped(
|
|
919
|
+
if let Err(error) = stamp_first_send_at_if_leader_to_worker_scoped(
|
|
787
920
|
workspace,
|
|
788
921
|
&message.sender,
|
|
789
922
|
&message.recipient,
|
|
790
923
|
canonical_owner_team_id.as_deref(),
|
|
791
|
-
)
|
|
792
|
-
|
|
924
|
+
) {
|
|
925
|
+
absorb_state_error_only_after_physical_submit(
|
|
926
|
+
transport,
|
|
927
|
+
state,
|
|
928
|
+
&message.recipient,
|
|
929
|
+
&target,
|
|
930
|
+
message_id,
|
|
931
|
+
event_log,
|
|
932
|
+
"stamp_first_send_at",
|
|
933
|
+
error,
|
|
934
|
+
)?;
|
|
935
|
+
}
|
|
936
|
+
if let Err(error) = record_turn_open_if_leader_to_worker_scoped(
|
|
793
937
|
workspace,
|
|
794
938
|
&message.sender,
|
|
795
939
|
&message.recipient,
|
|
@@ -797,7 +941,18 @@ pub fn deliver_pending_message(
|
|
|
797
941
|
event_log,
|
|
798
942
|
canonical_owner_team_id.as_deref(),
|
|
799
943
|
resolved.metadata.as_ref(),
|
|
800
|
-
)
|
|
944
|
+
) {
|
|
945
|
+
absorb_state_error_only_after_physical_submit(
|
|
946
|
+
transport,
|
|
947
|
+
state,
|
|
948
|
+
&message.recipient,
|
|
949
|
+
&target,
|
|
950
|
+
message_id,
|
|
951
|
+
event_log,
|
|
952
|
+
"turn_open_after_delivery",
|
|
953
|
+
error,
|
|
954
|
+
)?;
|
|
955
|
+
}
|
|
801
956
|
Ok(outcome)
|
|
802
957
|
}
|
|
803
958
|
|
|
@@ -864,6 +1019,7 @@ pub(crate) fn mark_worker_target_missing(
|
|
|
864
1019
|
reason: Some(DeliveryRefusal::TmuxTargetMissing),
|
|
865
1020
|
channel: Some("delivery_blocked".to_string()),
|
|
866
1021
|
ack_forced_off: false,
|
|
1022
|
+
turn_verification: None,
|
|
867
1023
|
})
|
|
868
1024
|
}
|
|
869
1025
|
|
|
@@ -904,6 +1060,27 @@ pub fn requeue_worker_target_missing_messages(
|
|
|
904
1060
|
Ok(ids)
|
|
905
1061
|
}
|
|
906
1062
|
|
|
1063
|
+
/// 把 post-Enter 消费判定落到 workspace,供确认面扫盘。
|
|
1064
|
+
///
|
|
1065
|
+
/// //! purpose: persist SubmitVerification after inject
|
|
1066
|
+
/// //! contract: file contains Debug variant name (SubmitConsumptionUnverified / EnterSentWithoutPlaceholderCheck)
|
|
1067
|
+
/// //! boundary: write failure must not fail delivery
|
|
1068
|
+
fn persist_submit_verification(workspace: &Path, message_id: &str, report: &InjectReport) {
|
|
1069
|
+
let dir = workspace.join(".team").join("runtime");
|
|
1070
|
+
if std::fs::create_dir_all(&dir).is_err() {
|
|
1071
|
+
return;
|
|
1072
|
+
}
|
|
1073
|
+
let body = serde_json::json!({
|
|
1074
|
+
"message_id": message_id,
|
|
1075
|
+
"submit_verification": format!("{:?}", report.submit_verification),
|
|
1076
|
+
"inject_verification": format!("{:?}", report.inject_verification),
|
|
1077
|
+
"turn_verification": turn_verification_wire(report.turn_verification),
|
|
1078
|
+
"submit_verified": inject_submit_verified(report),
|
|
1079
|
+
"attempts": report.attempts,
|
|
1080
|
+
});
|
|
1081
|
+
let _ = std::fs::write(dir.join("submit-verification.json"), format!("{body}\n"));
|
|
1082
|
+
}
|
|
1083
|
+
|
|
907
1084
|
/// 0.3.27: promoted to pub(crate) for leader_receiver.rs verification gate.
|
|
908
1085
|
pub(crate) fn inject_submit_verified(report: &InjectReport) -> bool {
|
|
909
1086
|
match report.submit_verification {
|
|
@@ -1018,6 +1195,7 @@ fn deliver_leader_via_app_server(
|
|
|
1018
1195
|
reason: None,
|
|
1019
1196
|
channel: Some("codex_app_server".to_string()),
|
|
1020
1197
|
ack_forced_off: false,
|
|
1198
|
+
turn_verification: None,
|
|
1021
1199
|
})
|
|
1022
1200
|
}
|
|
1023
1201
|
Err(error) => app_server_delivery_failure(
|
|
@@ -1064,6 +1242,7 @@ fn app_server_delivery_failure(
|
|
|
1064
1242
|
reason: Some(DeliveryRefusal::RecipientBusy),
|
|
1065
1243
|
channel: Some("leader_busy".to_string()),
|
|
1066
1244
|
ack_forced_off: false,
|
|
1245
|
+
turn_verification: None,
|
|
1067
1246
|
})
|
|
1068
1247
|
}
|
|
1069
1248
|
crate::codex_app_server::AppServerError::ThreadStale { expected, actual } => {
|
|
@@ -1103,6 +1282,7 @@ fn app_server_delivery_failure(
|
|
|
1103
1282
|
reason: Some(DeliveryRefusal::MissingPermissions),
|
|
1104
1283
|
channel: Some("codex_app_server".to_string()),
|
|
1105
1284
|
ack_forced_off: false,
|
|
1285
|
+
turn_verification: None,
|
|
1106
1286
|
})
|
|
1107
1287
|
}
|
|
1108
1288
|
crate::codex_app_server::AppServerError::ProtocolMismatch(_)
|
|
@@ -1155,6 +1335,7 @@ fn rebind_required_outcome(message_id: &str, action: &str) -> DeliveryOutcome {
|
|
|
1155
1335
|
reason: Some(DeliveryRefusal::LeaderNotAttached),
|
|
1156
1336
|
channel: Some("rebind_required".to_string()),
|
|
1157
1337
|
ack_forced_off: false,
|
|
1338
|
+
turn_verification: None,
|
|
1158
1339
|
}
|
|
1159
1340
|
}
|
|
1160
1341
|
|
|
@@ -1558,6 +1739,7 @@ fn leader_receiver_transport_conflict_outcome(
|
|
|
1558
1739
|
reason: Some(DeliveryRefusal::LeaderNotAttached),
|
|
1559
1740
|
channel: Some("rebind_required".to_string()),
|
|
1560
1741
|
ack_forced_off: false,
|
|
1742
|
+
turn_verification: None,
|
|
1561
1743
|
}));
|
|
1562
1744
|
}
|
|
1563
1745
|
}
|
|
@@ -1973,23 +2155,6 @@ fn message_for_delivery(
|
|
|
1973
2155
|
Ok(message)
|
|
1974
2156
|
}
|
|
1975
2157
|
|
|
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
2158
|
/// Pre-inject gate (Contract B): peek the recipient pane and answer "is there an
|
|
1994
2159
|
/// actionable provider startup prompt right now (trust menu or update prompt)" using
|
|
1995
2160
|
/// the SHARED provider/startup_prompt recognizers — no second classifier, no provider
|
|
@@ -2058,11 +2223,67 @@ fn recipient_pane_has_actionable_startup_prompt(
|
|
|
2058
2223
|
crate::provider::classify_copilot_startup_screen(&captured),
|
|
2059
2224
|
crate::provider::StartupScreenDecision::AnswerWorkspaceTrust
|
|
2060
2225
|
),
|
|
2061
|
-
Provider::
|
|
2226
|
+
Provider::Pi
|
|
2227
|
+
| Provider::Grok
|
|
2228
|
+
| Provider::CursorAgent
|
|
2229
|
+
| Provider::GeminiCli
|
|
2230
|
+
| Provider::Fake => false,
|
|
2062
2231
|
}
|
|
2063
2232
|
}
|
|
2064
2233
|
|
|
2065
|
-
fn
|
|
2234
|
+
fn recipient_provider(state: &serde_json::Value, recipient: &str) -> Option<Provider> {
|
|
2235
|
+
state
|
|
2236
|
+
.get("agents")
|
|
2237
|
+
.and_then(serde_json::Value::as_object)
|
|
2238
|
+
.and_then(|agents| agents.get(recipient))
|
|
2239
|
+
.and_then(|agent| agent.get("provider"))
|
|
2240
|
+
.and_then(serde_json::Value::as_str)
|
|
2241
|
+
.and_then(parse_canonical_provider)
|
|
2242
|
+
}
|
|
2243
|
+
|
|
2244
|
+
/// ---
|
|
2245
|
+
/// purpose: paste→Enter 地板按收件人 provider 选择
|
|
2246
|
+
/// contract: CursorAgent 与 Grok 为 1s;其余(含 claude)为 ZERO
|
|
2247
|
+
/// boundary: 不改 cursor 单回车闸;不把地板套到 claude/codex
|
|
2248
|
+
/// ---
|
|
2249
|
+
pub(crate) fn paste_to_submit_floor_for_recipient(
|
|
2250
|
+
state: &serde_json::Value,
|
|
2251
|
+
recipient: &str,
|
|
2252
|
+
) -> Duration {
|
|
2253
|
+
match recipient_provider(state, recipient) {
|
|
2254
|
+
Some(Provider::CursorAgent) | Some(Provider::Grok) => {
|
|
2255
|
+
crate::tmux_backend::CURSOR_PASTE_TO_SUBMIT_FLOOR
|
|
2256
|
+
}
|
|
2257
|
+
_ => Duration::ZERO,
|
|
2258
|
+
}
|
|
2259
|
+
}
|
|
2260
|
+
|
|
2261
|
+
/// ---
|
|
2262
|
+
/// purpose: 判断收件 provider 是否必须禁止 submit observer 重按 Enter
|
|
2263
|
+
/// returns: CursorAgent 与 Pi 为 true,其余为 false
|
|
2264
|
+
/// ---
|
|
2265
|
+
pub(crate) fn recipient_requires_single_enter(state: &serde_json::Value, recipient: &str) -> bool {
|
|
2266
|
+
matches!(
|
|
2267
|
+
recipient_provider(state, recipient),
|
|
2268
|
+
Some(Provider::CursorAgent | Provider::Pi)
|
|
2269
|
+
)
|
|
2270
|
+
}
|
|
2271
|
+
|
|
2272
|
+
/// ---
|
|
2273
|
+
/// purpose: 判断收件 provider 是否允许 Grok send-now 显式队列 flush
|
|
2274
|
+
/// returns: CursorAgent 与 Pi 为 false,其余为 true
|
|
2275
|
+
/// ---
|
|
2276
|
+
pub(crate) fn recipient_allows_explicit_queue_flush(
|
|
2277
|
+
state: &serde_json::Value,
|
|
2278
|
+
recipient: &str,
|
|
2279
|
+
) -> bool {
|
|
2280
|
+
!matches!(
|
|
2281
|
+
recipient_provider(state, recipient),
|
|
2282
|
+
Some(Provider::CursorAgent | Provider::Pi)
|
|
2283
|
+
)
|
|
2284
|
+
}
|
|
2285
|
+
|
|
2286
|
+
pub(crate) fn recipient_is_busy(state: &serde_json::Value, recipient: &str) -> bool {
|
|
2066
2287
|
state
|
|
2067
2288
|
.get("agents")
|
|
2068
2289
|
.and_then(serde_json::Value::as_object)
|
|
@@ -2129,6 +2350,7 @@ fn leader_acceptance_pending_outcome(message_id: &str, status: &str) -> Delivery
|
|
|
2129
2350
|
reason: None,
|
|
2130
2351
|
channel: Some("leader_acceptance_pending".to_string()),
|
|
2131
2352
|
ack_forced_off: false,
|
|
2353
|
+
turn_verification: None,
|
|
2132
2354
|
}
|
|
2133
2355
|
}
|
|
2134
2356
|
|
|
@@ -2143,6 +2365,7 @@ pub(crate) fn leader_receipt_source_unavailable_outcome(message_id: &str) -> Del
|
|
|
2143
2365
|
reason: None,
|
|
2144
2366
|
channel: Some("leader_receipt_source_unavailable".to_string()),
|
|
2145
2367
|
ack_forced_off: false,
|
|
2368
|
+
turn_verification: None,
|
|
2146
2369
|
}
|
|
2147
2370
|
}
|
|
2148
2371
|
|
|
@@ -2184,6 +2407,7 @@ fn observe_pending_leader_acceptance(
|
|
|
2184
2407
|
reason: None,
|
|
2185
2408
|
channel: Some("leader_receiver".to_string()),
|
|
2186
2409
|
ack_forced_off: false,
|
|
2410
|
+
turn_verification: None,
|
|
2187
2411
|
})
|
|
2188
2412
|
}
|
|
2189
2413
|
|
|
@@ -2214,6 +2438,7 @@ pub fn handle_trust_retry_needed(
|
|
|
2214
2438
|
reason: None,
|
|
2215
2439
|
channel: None,
|
|
2216
2440
|
ack_forced_off: false,
|
|
2441
|
+
turn_verification: None,
|
|
2217
2442
|
});
|
|
2218
2443
|
}
|
|
2219
2444
|
let next_attempt = payload.attempt.saturating_add(1);
|
|
@@ -2254,6 +2479,7 @@ pub fn handle_trust_retry_needed(
|
|
|
2254
2479
|
reason: None,
|
|
2255
2480
|
channel: None,
|
|
2256
2481
|
ack_forced_off: false,
|
|
2482
|
+
turn_verification: None,
|
|
2257
2483
|
})
|
|
2258
2484
|
}
|
|
2259
2485
|
|
|
@@ -2751,6 +2977,7 @@ fn refuse_owner_team_resolution(
|
|
|
2751
2977
|
reason: Some(refusal),
|
|
2752
2978
|
channel: Some("owner_team_resolution".to_string()),
|
|
2753
2979
|
ack_forced_off: false,
|
|
2980
|
+
turn_verification: None,
|
|
2754
2981
|
})
|
|
2755
2982
|
}
|
|
2756
2983
|
|
|
@@ -2808,6 +3035,7 @@ pub fn retry_injection_after_trust_auto_answer(
|
|
|
2808
3035
|
reason: None,
|
|
2809
3036
|
channel: None,
|
|
2810
3037
|
ack_forced_off: false,
|
|
3038
|
+
turn_verification: None,
|
|
2811
3039
|
})
|
|
2812
3040
|
}
|
|
2813
3041
|
|
|
@@ -2844,6 +3072,134 @@ fn rollout_tail_contains(path: &std::path::Path, needle: &str, tail_bytes: u64)
|
|
|
2844
3072
|
rollout_tail_contains_result(path, needle, tail_bytes).unwrap_or(false)
|
|
2845
3073
|
}
|
|
2846
3074
|
|
|
3075
|
+
fn message_token_marker(message_id: &str) -> String {
|
|
3076
|
+
format!("[team-agent-token:{message_id}]")
|
|
3077
|
+
}
|
|
3078
|
+
|
|
3079
|
+
fn text_contains_message_token(text: &str, message_id: &str) -> bool {
|
|
3080
|
+
!message_id.is_empty() && text.contains(&message_token_marker(message_id))
|
|
3081
|
+
}
|
|
3082
|
+
|
|
3083
|
+
/// ---
|
|
3084
|
+
/// purpose: 同 message_id 已在 pane 或转录出现则视为已注入
|
|
3085
|
+
/// contract:
|
|
3086
|
+
/// provides:
|
|
3087
|
+
/// - name: token_already_visible
|
|
3088
|
+
/// what: capture Tail(80) 或 recipient rollout_path 尾含 `[team-agent-token:{id}]` 则为 true
|
|
3089
|
+
/// boundary:
|
|
3090
|
+
/// - capture 失败不当已见(倒向允许注入,避免丢信)
|
|
3091
|
+
/// - 不请求 Full(wave-2:滚出屏的 trust 残渣不当成已见/可行动)
|
|
3092
|
+
/// - 不把 JSON 字段里的 message_id 当 pane token
|
|
3093
|
+
/// ---
|
|
3094
|
+
pub(crate) fn token_already_visible(
|
|
3095
|
+
transport: &dyn Transport,
|
|
3096
|
+
state: &serde_json::Value,
|
|
3097
|
+
recipient: &str,
|
|
3098
|
+
target: &Target,
|
|
3099
|
+
message_id: &str,
|
|
3100
|
+
) -> bool {
|
|
3101
|
+
if message_id.is_empty() {
|
|
3102
|
+
return false;
|
|
3103
|
+
}
|
|
3104
|
+
let marker = message_token_marker(message_id);
|
|
3105
|
+
match transport.capture(target, CaptureRange::Tail(80)) {
|
|
3106
|
+
Ok(captured) if text_contains_message_token(&captured.text, message_id) => return true,
|
|
3107
|
+
_ => {}
|
|
3108
|
+
}
|
|
3109
|
+
recipient_transcript_contains(state, recipient, &marker)
|
|
3110
|
+
}
|
|
3111
|
+
|
|
3112
|
+
fn recipient_transcript_contains(state: &serde_json::Value, recipient: &str, marker: &str) -> bool {
|
|
3113
|
+
for path in recipient_transcript_paths(state, recipient) {
|
|
3114
|
+
if rollout_tail_contains(&path, marker, 64 * 1024) {
|
|
3115
|
+
return true;
|
|
3116
|
+
}
|
|
3117
|
+
}
|
|
3118
|
+
false
|
|
3119
|
+
}
|
|
3120
|
+
|
|
3121
|
+
fn recipient_transcript_paths(
|
|
3122
|
+
state: &serde_json::Value,
|
|
3123
|
+
recipient: &str,
|
|
3124
|
+
) -> Vec<std::path::PathBuf> {
|
|
3125
|
+
let mut paths = Vec::new();
|
|
3126
|
+
let push = |paths: &mut Vec<std::path::PathBuf>, value: Option<&serde_json::Value>| {
|
|
3127
|
+
if let Some(path) = value
|
|
3128
|
+
.and_then(serde_json::Value::as_str)
|
|
3129
|
+
.filter(|p| !p.is_empty())
|
|
3130
|
+
{
|
|
3131
|
+
paths.push(std::path::PathBuf::from(path));
|
|
3132
|
+
}
|
|
3133
|
+
};
|
|
3134
|
+
if recipient == "leader" {
|
|
3135
|
+
push(
|
|
3136
|
+
&mut paths,
|
|
3137
|
+
leader_receiver_record_path(state, "rollout_path"),
|
|
3138
|
+
);
|
|
3139
|
+
}
|
|
3140
|
+
let agent = state.get("agents").and_then(|agents| agents.get(recipient));
|
|
3141
|
+
push(
|
|
3142
|
+
&mut paths,
|
|
3143
|
+
agent.and_then(|agent| agent.get("rollout_path")),
|
|
3144
|
+
);
|
|
3145
|
+
paths
|
|
3146
|
+
}
|
|
3147
|
+
|
|
3148
|
+
fn leader_receiver_record_path<'a>(
|
|
3149
|
+
state: &'a serde_json::Value,
|
|
3150
|
+
field: &str,
|
|
3151
|
+
) -> Option<&'a serde_json::Value> {
|
|
3152
|
+
state
|
|
3153
|
+
.get("leader_receiver")
|
|
3154
|
+
.and_then(|receiver| receiver.get(field))
|
|
3155
|
+
.or_else(|| {
|
|
3156
|
+
let active = state
|
|
3157
|
+
.get("active_team_key")
|
|
3158
|
+
.and_then(serde_json::Value::as_str)?;
|
|
3159
|
+
state
|
|
3160
|
+
.get("teams")
|
|
3161
|
+
.and_then(serde_json::Value::as_object)
|
|
3162
|
+
.and_then(|teams| teams.get(active))
|
|
3163
|
+
.and_then(|team| team.get("leader_receiver"))
|
|
3164
|
+
.and_then(|receiver| receiver.get(field))
|
|
3165
|
+
})
|
|
3166
|
+
}
|
|
3167
|
+
|
|
3168
|
+
fn absorb_state_error_only_after_physical_submit(
|
|
3169
|
+
transport: &dyn Transport,
|
|
3170
|
+
state: &serde_json::Value,
|
|
3171
|
+
recipient: &str,
|
|
3172
|
+
target: &Target,
|
|
3173
|
+
message_id: &str,
|
|
3174
|
+
event_log: &EventLog,
|
|
3175
|
+
stage: &str,
|
|
3176
|
+
error: MessagingError,
|
|
3177
|
+
) -> Result<(), MessagingError> {
|
|
3178
|
+
if token_already_visible(transport, state, recipient, target, message_id) {
|
|
3179
|
+
log_post_physical_submit_state_error(event_log, message_id, stage, &error)
|
|
3180
|
+
} else {
|
|
3181
|
+
Err(error)
|
|
3182
|
+
}
|
|
3183
|
+
}
|
|
3184
|
+
|
|
3185
|
+
fn log_post_physical_submit_state_error(
|
|
3186
|
+
event_log: &EventLog,
|
|
3187
|
+
message_id: &str,
|
|
3188
|
+
stage: &str,
|
|
3189
|
+
error: &MessagingError,
|
|
3190
|
+
) -> Result<(), MessagingError> {
|
|
3191
|
+
event_log.write(
|
|
3192
|
+
"delivery.post_submit_state_degraded",
|
|
3193
|
+
serde_json::json!({
|
|
3194
|
+
"message_id": message_id,
|
|
3195
|
+
"stage": stage,
|
|
3196
|
+
"error": error.to_string(),
|
|
3197
|
+
"physical_submit": true,
|
|
3198
|
+
}),
|
|
3199
|
+
)?;
|
|
3200
|
+
Ok(())
|
|
3201
|
+
}
|
|
3202
|
+
|
|
2847
3203
|
fn rollout_tail_contains_result(
|
|
2848
3204
|
path: &std::path::Path,
|
|
2849
3205
|
needle: &str,
|
|
@@ -2860,3 +3216,36 @@ fn rollout_tail_contains_result(
|
|
|
2860
3216
|
let haystack = String::from_utf8_lossy(&buf);
|
|
2861
3217
|
Ok(haystack.contains(needle))
|
|
2862
3218
|
}
|
|
3219
|
+
|
|
3220
|
+
#[cfg(test)]
|
|
3221
|
+
mod paste_floor_tests {
|
|
3222
|
+
use super::*;
|
|
3223
|
+
use std::time::Duration;
|
|
3224
|
+
|
|
3225
|
+
fn state_with_provider(provider: &str) -> serde_json::Value {
|
|
3226
|
+
serde_json::json!({
|
|
3227
|
+
"agents": { "w1": { "provider": provider } }
|
|
3228
|
+
})
|
|
3229
|
+
}
|
|
3230
|
+
|
|
3231
|
+
#[test]
|
|
3232
|
+
fn grok_paste_to_submit_floor_matches_cursor() {
|
|
3233
|
+
let grok = paste_to_submit_floor_for_recipient(&state_with_provider("grok"), "w1");
|
|
3234
|
+
let cursor = paste_to_submit_floor_for_recipient(&state_with_provider("cursor_agent"), "w1");
|
|
3235
|
+
assert_eq!(grok, crate::tmux_backend::CURSOR_PASTE_TO_SUBMIT_FLOOR);
|
|
3236
|
+
assert_eq!(cursor, crate::tmux_backend::CURSOR_PASTE_TO_SUBMIT_FLOOR);
|
|
3237
|
+
assert_eq!(grok, cursor);
|
|
3238
|
+
}
|
|
3239
|
+
|
|
3240
|
+
#[test]
|
|
3241
|
+
fn claude_and_codex_paste_to_submit_floor_stays_zero() {
|
|
3242
|
+
assert_eq!(
|
|
3243
|
+
paste_to_submit_floor_for_recipient(&state_with_provider("claude"), "w1"),
|
|
3244
|
+
Duration::ZERO
|
|
3245
|
+
);
|
|
3246
|
+
assert_eq!(
|
|
3247
|
+
paste_to_submit_floor_for_recipient(&state_with_provider("codex"), "w1"),
|
|
3248
|
+
Duration::ZERO
|
|
3249
|
+
);
|
|
3250
|
+
}
|
|
3251
|
+
}
|