@team-agent/installer 0.5.75 → 0.5.78
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/README.md +9 -0
- package/README.zh.md +8 -0
- package/crates/team-agent/src/cli/adapters.rs +184 -125
- package/crates/team-agent/src/cli/diagnose.rs +333 -68
- package/crates/team-agent/src/cli/emit.rs +59 -15
- package/crates/team-agent/src/cli/mod.rs +600 -134
- package/crates/team-agent/src/cli/models.rs +485 -44
- package/crates/team-agent/src/cli/named_address.rs +19 -5
- package/crates/team-agent/src/cli/profile.rs +35 -4
- package/crates/team-agent/src/cli/spec.rs +7 -2
- package/crates/team-agent/src/cli/status_port/brief.rs +1137 -0
- package/crates/team-agent/src/cli/status_port/compact.rs +12 -8
- package/crates/team-agent/src/cli/status_port/snapshot.rs +15 -12
- package/crates/team-agent/src/cli/status_port/store.rs +1 -1
- package/crates/team-agent/src/cli/status_port.rs +8 -0
- package/crates/team-agent/src/cli/tests/missing_subcommands.rs +62 -8
- package/crates/team-agent/src/cli/tests/mod.rs +1 -0
- package/crates/team-agent/src/cli/tests/named_address.rs +70 -0
- package/crates/team-agent/src/cli/tests/run_delegation.rs +29 -24
- package/crates/team-agent/src/cli/tests/status_brief.rs +691 -0
- package/crates/team-agent/src/cli/tests/status_send.rs +8 -2
- package/crates/team-agent/src/cli/tests/verb_profile.rs +81 -6
- package/crates/team-agent/src/cli/types.rs +1 -0
- package/crates/team-agent/src/leader/lease.rs +538 -41
- package/crates/team-agent/src/leader/owner_bind.rs +40 -12
- package/crates/team-agent/src/leader/rediscover/tests.rs +65 -0
- package/crates/team-agent/src/leader/rediscover.rs +36 -6
- package/crates/team-agent/src/leader/start.rs +151 -14
- package/crates/team-agent/src/leader/tests/wake_start_owner.rs +15 -9
- package/crates/team-agent/src/leader/types.rs +4 -0
- package/crates/team-agent/src/lifecycle/launch/add_agent.rs +28 -227
- package/crates/team-agent/src/lifecycle/launch/add_agent_state.rs +0 -18
- package/crates/team-agent/src/lifecycle/launch/cursor_mcp.rs +94 -227
- package/crates/team-agent/src/lifecycle/launch/mcp_config.rs +59 -10
- package/crates/team-agent/src/lifecycle/launch/pi_mcp.rs +138 -14
- package/crates/team-agent/src/lifecycle/launch/quick_start.rs +1449 -282
- package/crates/team-agent/src/lifecycle/launch/readiness.rs +349 -4
- package/crates/team-agent/src/lifecycle/launch/spawn.rs +11 -11
- package/crates/team-agent/src/lifecycle/launch/spec_state.rs +35 -3
- package/crates/team-agent/src/lifecycle/launch/worker_env.rs +16 -0
- package/crates/team-agent/src/lifecycle/launch.rs +30 -7
- package/crates/team-agent/src/lifecycle/profile_launch.rs +37 -3
- package/crates/team-agent/src/lifecycle/restart/agent.rs +112 -100
- package/crates/team-agent/src/lifecycle/restart/common.rs +82 -52
- package/crates/team-agent/src/lifecycle/restart/rebuild.rs +1002 -65
- package/crates/team-agent/src/lifecycle/restart.rs +2 -2
- package/crates/team-agent/src/lifecycle/tests/acceptance_b_batch_red.rs +3 -0
- package/crates/team-agent/src/lifecycle/tests/b0_reader_hideone_audit_contract.rs +45 -14
- package/crates/team-agent/src/lifecycle/tests/behavioral_diff_264_red.rs +15 -0
- package/crates/team-agent/src/lifecycle/tests/claude_compatible_config_red.rs +5 -0
- package/crates/team-agent/src/lifecycle/tests/claude_profile_launch_red.rs +11 -13
- package/crates/team-agent/src/lifecycle/tests/clone_agent_preserves_source_tools.rs +119 -3
- package/crates/team-agent/src/lifecycle/tests/communication_mode_runtime_contract_red.rs +12 -0
- package/crates/team-agent/src/lifecycle/tests/copilot_provider_red.rs +22 -0
- package/crates/team-agent/src/lifecycle/tests/core_034_real_red.rs +12 -0
- package/crates/team-agent/src/lifecycle/tests/cursor_mcp_overlay.rs +265 -198
- package/crates/team-agent/src/lifecycle/tests/cursor_require_explicit_model_red.rs +14 -30
- package/crates/team-agent/src/lifecycle/tests/cursor_restart_resume_red.rs +3 -0
- package/crates/team-agent/src/lifecycle/tests/g1_silent_faces.rs +2 -5
- package/crates/team-agent/src/lifecycle/tests/grok_effort_argv_red.rs +10 -0
- package/crates/team-agent/src/lifecycle/tests/grok_mcp_overlay_red.rs +58 -35
- package/crates/team-agent/src/lifecycle/tests/grok_require_explicit_model_red.rs +10 -0
- package/crates/team-agent/src/lifecycle/tests/grok_restart_resume_red.rs +9 -0
- package/crates/team-agent/src/lifecycle/tests/harvest2_a_batch_red.rs +4 -3
- package/crates/team-agent/src/lifecycle/tests/lane_ops.rs +0 -168
- package/crates/team-agent/src/lifecycle/tests/launch_spawn.rs +145 -212
- package/crates/team-agent/src/lifecycle/tests/phase_b_contracts.rs +16 -2
- package/crates/team-agent/src/lifecycle/tests/phase_golden.rs +69 -28
- package/crates/team-agent/src/lifecycle/tests/pi_dual_entry_red.rs +78 -0
- package/crates/team-agent/src/lifecycle/tests/team_in_team_identity_scope_red.rs +3 -0
- package/crates/team-agent/src/lifecycle/tests/test_isolation_escape_contract.rs +51 -10
- package/crates/team-agent/src/lifecycle/tests/upgrade_compat_0211_red.rs +22 -10
- package/crates/team-agent/src/lifecycle/tests/verify_rs031_window_consistency_red.rs +50 -7
- package/crates/team-agent/src/lifecycle/tests/worker_spawn_env_red.rs +137 -0
- package/crates/team-agent/src/lifecycle/types.rs +12 -0
- package/crates/team-agent/src/mcp_server/wire.rs +140 -11
- package/crates/team-agent/src/messaging/delivery.rs +67 -8
- package/crates/team-agent/src/messaging/leader_channel.rs +56 -0
- package/crates/team-agent/src/messaging/tests/runtime.rs +10 -2
- package/crates/team-agent/src/messaging/watchers.rs +48 -4
- package/crates/team-agent/src/provider/adapters/cursor_agent.rs +2 -5
- package/crates/team-agent/src/state/paths.rs +17 -2
- package/crates/team-agent/src/state/persist.rs +97 -1
- package/crates/team-agent/src/state/projection.rs +18 -2
- package/crates/team-agent/src/state/repository.rs +29 -0
- package/crates/team-agent/src/state/selector.rs +23 -3
- package/crates/team-agent/src/test_support/models_timeout_fixture.rs +144 -0
- package/crates/team-agent/src/tmux_backend/tests.rs +121 -4
- package/crates/team-agent/src/tmux_backend.rs +208 -118
- package/crates/team-agent/src/transport.rs +72 -0
- package/crates/team-agent/src/transport_factory.rs +53 -0
- package/package.json +4 -5
- package/skills/team-agent/SKILL.md +45 -17
- package/skills/team-agent/command-coverage.json +4 -29
- package/npm/check-skill-links.mjs +0 -88
- package/skills/team-agent/references/team-agent-operator.md +0 -672
|
@@ -65,10 +65,11 @@ use crate::provider::wire::{parse_provider, provider_wire};
|
|
|
65
65
|
use crate::transport::{
|
|
66
66
|
command_basename, normalize_capture, tmux_capture_argv, tmux_empty_inject_argv,
|
|
67
67
|
tmux_inject_text_argv, tmux_query_argv, tmux_send_keys_argv, tmux_send_submit_argv,
|
|
68
|
-
tmux_spawn_argv, AttachOutcome, BackendKind,
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
68
|
+
tmux_spawn_argv, AttachOutcome, BackendKind, BusySignalKind, CaptureRange,
|
|
69
|
+
CaptureSampleOutcome, CapturedText, InjectPayload, InjectReport, InjectStage,
|
|
70
|
+
InjectVerification, InputSurfaceProbe, Key, PaneField, PaneId, PaneInfo, PaneMode,
|
|
71
|
+
QueryOutcome, SessionName, SetEnvOutcome, SpawnResult, SubmitAttemptObservation,
|
|
72
|
+
SubmitConsumptionReason, SubmitDiagnostics, SubmitObserver, SubmitVerification, Target,
|
|
72
73
|
Transport, TransportError, TurnVerification, WindowName,
|
|
73
74
|
};
|
|
74
75
|
|
|
@@ -242,8 +243,6 @@ impl TmuxSocketEndpoint {
|
|
|
242
243
|
pub(crate) enum RuntimeTmuxEndpointSource {
|
|
243
244
|
StateTmuxEndpoint,
|
|
244
245
|
StateTmuxSocket,
|
|
245
|
-
LeaderReceiverTmuxEndpoint,
|
|
246
|
-
LeaderReceiverTmuxSocket,
|
|
247
246
|
WorkspaceFallback,
|
|
248
247
|
}
|
|
249
248
|
|
|
@@ -257,8 +256,6 @@ impl RuntimeTmuxEndpointSource {
|
|
|
257
256
|
match self {
|
|
258
257
|
Self::StateTmuxEndpoint => "state.tmux_endpoint",
|
|
259
258
|
Self::StateTmuxSocket => "state.tmux_socket",
|
|
260
|
-
Self::LeaderReceiverTmuxEndpoint => "leader_receiver.tmux_endpoint",
|
|
261
|
-
Self::LeaderReceiverTmuxSocket => "leader_receiver.tmux_socket",
|
|
262
259
|
Self::WorkspaceFallback => "workspace_fallback",
|
|
263
260
|
}
|
|
264
261
|
}
|
|
@@ -671,50 +668,6 @@ fn runtime_tmux_endpoint_from_state(
|
|
|
671
668
|
})
|
|
672
669
|
}
|
|
673
670
|
|
|
674
|
-
/// ---
|
|
675
|
-
/// purpose: 读取 selected team's persisted leader receiver endpoint
|
|
676
|
-
/// params:
|
|
677
|
-
/// state: 运行期全量或 selected-team 投影 JSON
|
|
678
|
-
/// returns: nested selected receiver 优先,其次投影顶层 receiver;二者都缺则 None
|
|
679
|
-
/// boundary: 只读 owning receiver,不读取普通 tmux_endpoint,不探活
|
|
680
|
-
/// ---
|
|
681
|
-
pub(crate) fn owning_tmux_endpoint_from_state(
|
|
682
|
-
state: &serde_json::Value,
|
|
683
|
-
) -> Option<(&str, RuntimeTmuxEndpointSource)> {
|
|
684
|
-
fn receiver_endpoint(state: &serde_json::Value) -> Option<(&str, RuntimeTmuxEndpointSource)> {
|
|
685
|
-
state
|
|
686
|
-
.pointer("/leader_receiver/tmux_endpoint")
|
|
687
|
-
.and_then(serde_json::Value::as_str)
|
|
688
|
-
.filter(|endpoint| !endpoint.is_empty())
|
|
689
|
-
.map(|endpoint| {
|
|
690
|
-
(
|
|
691
|
-
endpoint,
|
|
692
|
-
RuntimeTmuxEndpointSource::LeaderReceiverTmuxEndpoint,
|
|
693
|
-
)
|
|
694
|
-
})
|
|
695
|
-
.or_else(|| {
|
|
696
|
-
state
|
|
697
|
-
.pointer("/leader_receiver/tmux_socket")
|
|
698
|
-
.and_then(serde_json::Value::as_str)
|
|
699
|
-
.filter(|endpoint| !endpoint.is_empty())
|
|
700
|
-
.map(|endpoint| {
|
|
701
|
-
(
|
|
702
|
-
endpoint,
|
|
703
|
-
RuntimeTmuxEndpointSource::LeaderReceiverTmuxSocket,
|
|
704
|
-
)
|
|
705
|
-
})
|
|
706
|
-
})
|
|
707
|
-
}
|
|
708
|
-
|
|
709
|
-
state
|
|
710
|
-
.get("active_team_key")
|
|
711
|
-
.and_then(serde_json::Value::as_str)
|
|
712
|
-
.filter(|team_key| !team_key.is_empty())
|
|
713
|
-
.and_then(|team_key| state.get("teams").and_then(|teams| teams.get(team_key)))
|
|
714
|
-
.and_then(receiver_endpoint)
|
|
715
|
-
.or_else(|| receiver_endpoint(state))
|
|
716
|
-
}
|
|
717
|
-
|
|
718
671
|
/// CP-1 socket name: SHORT + DETERMINISTIC per canonical workspace path. `ta-` + 12 hex chars of a
|
|
719
672
|
/// stable FNV-1a hash over the canonicalized path. AF_UNIX `sun_path` is ~104 chars and the socket
|
|
720
673
|
/// lives at `/tmp/tmux-<uid>/<name>`, so we must NOT use the (~88-char) session name. §10: a
|
|
@@ -758,10 +711,6 @@ pub fn runtime_tmux_endpoint_from_state_pub(
|
|
|
758
711
|
let src = match source {
|
|
759
712
|
RuntimeTmuxEndpointSource::StateTmuxEndpoint => "state.tmux_endpoint",
|
|
760
713
|
RuntimeTmuxEndpointSource::StateTmuxSocket => "state.tmux_socket",
|
|
761
|
-
RuntimeTmuxEndpointSource::LeaderReceiverTmuxEndpoint => {
|
|
762
|
-
"leader_receiver.tmux_endpoint"
|
|
763
|
-
}
|
|
764
|
-
RuntimeTmuxEndpointSource::LeaderReceiverTmuxSocket => "leader_receiver.tmux_socket",
|
|
765
714
|
RuntimeTmuxEndpointSource::WorkspaceFallback => "workspace_fallback",
|
|
766
715
|
};
|
|
767
716
|
(endpoint.to_string(), src)
|
|
@@ -1530,6 +1479,14 @@ fn submit_diagnostics_for_inject(
|
|
|
1530
1479
|
surface: InjectTargetSurface,
|
|
1531
1480
|
) -> SubmitDiagnostics {
|
|
1532
1481
|
SubmitDiagnostics {
|
|
1482
|
+
consumption_reason: SubmitConsumptionReason::Unknown,
|
|
1483
|
+
// No pre-paste capture exists on this path; keep the diagnostic null.
|
|
1484
|
+
before_busy_signal_kind: None,
|
|
1485
|
+
busy_signal_kind: None,
|
|
1486
|
+
busy_line_from_bottom: None,
|
|
1487
|
+
current_marker_in_bottom_15: None,
|
|
1488
|
+
paste_identity_in_composer: None,
|
|
1489
|
+
consumption_from_capture_result: None,
|
|
1533
1490
|
appear_gate_elapsed_ms: 0,
|
|
1534
1491
|
appear_gate_matched: false,
|
|
1535
1492
|
total_elapsed_ms,
|
|
@@ -2118,6 +2075,56 @@ pub(crate) fn consumption_from_capture(
|
|
|
2118
2075
|
}
|
|
2119
2076
|
}
|
|
2120
2077
|
|
|
2078
|
+
#[derive(Debug, Clone, Copy, Default)]
|
|
2079
|
+
struct ConsumptionCaptureObservation {
|
|
2080
|
+
busy: Option<BusySignalMatch>,
|
|
2081
|
+
marker_in_bottom_15: Option<bool>,
|
|
2082
|
+
paste_identity_in_composer: Option<bool>,
|
|
2083
|
+
consumption_from_capture_result: Option<bool>,
|
|
2084
|
+
}
|
|
2085
|
+
|
|
2086
|
+
fn consumption_capture_observation(
|
|
2087
|
+
text: &str,
|
|
2088
|
+
marker: Option<&str>,
|
|
2089
|
+
token_ever_visible: bool,
|
|
2090
|
+
tracked: Option<PasteLatch>,
|
|
2091
|
+
) -> ConsumptionCaptureObservation {
|
|
2092
|
+
let Some(marker) = marker else {
|
|
2093
|
+
return ConsumptionCaptureObservation::default();
|
|
2094
|
+
};
|
|
2095
|
+
ConsumptionCaptureObservation {
|
|
2096
|
+
busy: provider_busy_signal_match(text),
|
|
2097
|
+
marker_in_bottom_15: Some(token_in_bottom_n(text, marker, 15)),
|
|
2098
|
+
paste_identity_in_composer: tracked
|
|
2099
|
+
.map(|tracked| this_paste_identity_in_composer(text, marker, Some(tracked))),
|
|
2100
|
+
consumption_from_capture_result: consumption_from_capture(
|
|
2101
|
+
text,
|
|
2102
|
+
marker,
|
|
2103
|
+
token_ever_visible,
|
|
2104
|
+
tracked,
|
|
2105
|
+
),
|
|
2106
|
+
}
|
|
2107
|
+
}
|
|
2108
|
+
|
|
2109
|
+
#[derive(Debug, Clone, Copy, Default)]
|
|
2110
|
+
struct ConsumptionDiagnostics {
|
|
2111
|
+
busy_signal_kind: Option<BusySignalKind>,
|
|
2112
|
+
busy_line_from_bottom: Option<u8>,
|
|
2113
|
+
current_marker_in_bottom_15: Option<bool>,
|
|
2114
|
+
paste_identity_in_composer: Option<bool>,
|
|
2115
|
+
consumption_from_capture_result: Option<bool>,
|
|
2116
|
+
}
|
|
2117
|
+
|
|
2118
|
+
impl ConsumptionDiagnostics {
|
|
2119
|
+
fn record(&mut self, observation: ConsumptionCaptureObservation) {
|
|
2120
|
+
self.busy_signal_kind = observation.busy.map(|match_| match_.kind);
|
|
2121
|
+
self.busy_line_from_bottom = observation.busy.map(|match_| match_.line_from_bottom);
|
|
2122
|
+
self.current_marker_in_bottom_15 = observation.marker_in_bottom_15;
|
|
2123
|
+
self.paste_identity_in_composer = observation.paste_identity_in_composer;
|
|
2124
|
+
self.consumption_from_capture_result = observation.consumption_from_capture_result;
|
|
2125
|
+
}
|
|
2126
|
+
}
|
|
2127
|
+
|
|
2121
2128
|
/// ---
|
|
2122
2129
|
/// purpose: Gone 分支在判已消费前复核 composer 占位符(PasteLatch 身份)
|
|
2123
2130
|
/// contract: 锁定身份仍在,或未锁定但 grok `[Pasted: …]` 仍在 ⇒ Some(false)
|
|
@@ -2147,37 +2154,67 @@ fn marker_position_from_bottom(text: &str, marker: &str) -> Option<u32> {
|
|
|
2147
2154
|
None
|
|
2148
2155
|
}
|
|
2149
2156
|
|
|
2150
|
-
|
|
2157
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
2158
|
+
struct BusySignalMatch {
|
|
2159
|
+
kind: BusySignalKind,
|
|
2160
|
+
line_from_bottom: u8,
|
|
2161
|
+
}
|
|
2162
|
+
|
|
2163
|
+
fn busy_signal_kind_in_line(line: &str) -> Option<BusySignalKind> {
|
|
2164
|
+
let lower = line.to_ascii_lowercase();
|
|
2165
|
+
if lower.contains("working") {
|
|
2166
|
+
return Some(BusySignalKind::Working);
|
|
2167
|
+
}
|
|
2168
|
+
if lower.contains("thinking") {
|
|
2169
|
+
return Some(BusySignalKind::Thinking);
|
|
2170
|
+
}
|
|
2171
|
+
if lower.contains("processing") {
|
|
2172
|
+
return Some(BusySignalKind::Processing);
|
|
2173
|
+
}
|
|
2174
|
+
if lower.contains("esc to interrupt") {
|
|
2175
|
+
return Some(BusySignalKind::EscToInterrupt);
|
|
2176
|
+
}
|
|
2177
|
+
if line.contains('●')
|
|
2178
|
+
|| line.contains('⏳')
|
|
2179
|
+
|| line.contains('⠋')
|
|
2180
|
+
|| line.contains('⠙')
|
|
2181
|
+
|| line.contains('⠹')
|
|
2182
|
+
|| line.contains('⠸')
|
|
2183
|
+
|| line.contains('⠼')
|
|
2184
|
+
|| line.contains('⠴')
|
|
2185
|
+
|| line.contains('⠦')
|
|
2186
|
+
|| line.contains('⠧')
|
|
2187
|
+
|| line.contains('⠇')
|
|
2188
|
+
|| line.contains('⠏')
|
|
2189
|
+
|| line.contains('✶')
|
|
2190
|
+
|| line.contains('✢')
|
|
2191
|
+
|| line.contains('✻')
|
|
2192
|
+
|| line.contains('✽')
|
|
2193
|
+
|| line.contains('✳')
|
|
2194
|
+
{
|
|
2195
|
+
return Some(BusySignalKind::SpinnerGlyph);
|
|
2196
|
+
}
|
|
2197
|
+
None
|
|
2198
|
+
}
|
|
2199
|
+
|
|
2200
|
+
fn provider_busy_signal_match(text: &str) -> Option<BusySignalMatch> {
|
|
2151
2201
|
text.lines()
|
|
2152
2202
|
.rev()
|
|
2153
2203
|
.filter(|line| !line.trim().is_empty())
|
|
2154
2204
|
.take(15)
|
|
2155
|
-
.
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|| line.contains('●')
|
|
2162
|
-
|| line.contains('⏳')
|
|
2163
|
-
|| line.contains('⠋')
|
|
2164
|
-
|| line.contains('⠙')
|
|
2165
|
-
|| line.contains('⠹')
|
|
2166
|
-
|| line.contains('⠸')
|
|
2167
|
-
|| line.contains('⠼')
|
|
2168
|
-
|| line.contains('⠴')
|
|
2169
|
-
|| line.contains('⠦')
|
|
2170
|
-
|| line.contains('⠧')
|
|
2171
|
-
|| line.contains('⠇')
|
|
2172
|
-
|| line.contains('⠏')
|
|
2173
|
-
|| line.contains('✶')
|
|
2174
|
-
|| line.contains('✢')
|
|
2175
|
-
|| line.contains('✻')
|
|
2176
|
-
|| line.contains('✽')
|
|
2177
|
-
|| line.contains('✳')
|
|
2205
|
+
.enumerate()
|
|
2206
|
+
.find_map(|(line_from_bottom, line)| {
|
|
2207
|
+
busy_signal_kind_in_line(line).map(|kind| BusySignalMatch {
|
|
2208
|
+
kind,
|
|
2209
|
+
line_from_bottom: line_from_bottom as u8,
|
|
2210
|
+
})
|
|
2178
2211
|
})
|
|
2179
2212
|
}
|
|
2180
2213
|
|
|
2214
|
+
fn provider_busy_signal_in_tail(text: &str) -> bool {
|
|
2215
|
+
provider_busy_signal_match(text).is_some()
|
|
2216
|
+
}
|
|
2217
|
+
|
|
2181
2218
|
/// ---
|
|
2182
2219
|
/// purpose: 入箱 vs 开跑(G4)。给定最近一次 pane 尾,回答席位是否因此开跑。
|
|
2183
2220
|
/// params:
|
|
@@ -3227,6 +3264,8 @@ impl Transport for TmuxBackend {
|
|
|
3227
3264
|
let mut consumption_attempts: u32 = 0;
|
|
3228
3265
|
let mut capture_retries: u32 = 0;
|
|
3229
3266
|
let mut consumed: Option<bool> = None;
|
|
3267
|
+
let mut consumption_reason = SubmitConsumptionReason::Unknown;
|
|
3268
|
+
let mut consumption_diagnostics = ConsumptionDiagnostics::default();
|
|
3230
3269
|
let mut attempts_detail: Vec<SubmitAttemptObservation> = Vec::new();
|
|
3231
3270
|
let mut any_attempt_matched = false;
|
|
3232
3271
|
|
|
@@ -3267,6 +3306,13 @@ impl Transport for TmuxBackend {
|
|
|
3267
3306
|
token_seen_after_enter = Some(false);
|
|
3268
3307
|
}
|
|
3269
3308
|
tracked_paste = latch_paste(&cap.text, tracked_paste);
|
|
3309
|
+
let observation = consumption_capture_observation(
|
|
3310
|
+
&cap.text,
|
|
3311
|
+
marker,
|
|
3312
|
+
token_ever_visible,
|
|
3313
|
+
tracked_paste,
|
|
3314
|
+
);
|
|
3315
|
+
consumption_diagnostics.record(observation);
|
|
3270
3316
|
let obs = submit_attempt_observation(
|
|
3271
3317
|
attempt_index,
|
|
3272
3318
|
&cap,
|
|
@@ -3279,18 +3325,16 @@ impl Transport for TmuxBackend {
|
|
|
3279
3325
|
attempts_detail.push(obs);
|
|
3280
3326
|
// NeverSeen 不得把 token 缺席写成 consumed;
|
|
3281
3327
|
// 见过再消失,或本次占位符身份离开 composer,才停手。
|
|
3282
|
-
if
|
|
3283
|
-
|
|
3284
|
-
|
|
3285
|
-
token_ever_visible,
|
|
3286
|
-
tracked_paste,
|
|
3287
|
-
) == Some(true)
|
|
3288
|
-
{
|
|
3328
|
+
if observation.consumption_from_capture_result == Some(true) {
|
|
3329
|
+
consumption_reason =
|
|
3330
|
+
SubmitConsumptionReason::ConsumptionFromCapture;
|
|
3289
3331
|
consumed = Some(true);
|
|
3290
3332
|
break;
|
|
3291
3333
|
}
|
|
3292
3334
|
if cursor_single_enter_enabled() {
|
|
3293
|
-
if
|
|
3335
|
+
if observation.busy.is_some() {
|
|
3336
|
+
consumption_reason =
|
|
3337
|
+
SubmitConsumptionReason::CursorPollingBusy;
|
|
3294
3338
|
consumed = Some(true);
|
|
3295
3339
|
break;
|
|
3296
3340
|
}
|
|
@@ -3375,6 +3419,13 @@ impl Transport for TmuxBackend {
|
|
|
3375
3419
|
token_seen_after_enter = Some(false);
|
|
3376
3420
|
}
|
|
3377
3421
|
tracked_paste = latch_paste(&cap.text, tracked_paste);
|
|
3422
|
+
let observation = consumption_capture_observation(
|
|
3423
|
+
&cap.text,
|
|
3424
|
+
marker,
|
|
3425
|
+
token_ever_visible,
|
|
3426
|
+
tracked_paste,
|
|
3427
|
+
);
|
|
3428
|
+
consumption_diagnostics.record(observation);
|
|
3378
3429
|
let obs = submit_attempt_observation(
|
|
3379
3430
|
attempt_index,
|
|
3380
3431
|
&cap,
|
|
@@ -3385,24 +3436,22 @@ impl Transport for TmuxBackend {
|
|
|
3385
3436
|
any_attempt_matched = true;
|
|
3386
3437
|
}
|
|
3387
3438
|
attempts_detail.push(obs);
|
|
3388
|
-
if
|
|
3389
|
-
|
|
3390
|
-
|
|
3391
|
-
token_ever_visible,
|
|
3392
|
-
tracked_paste,
|
|
3393
|
-
) == Some(true)
|
|
3394
|
-
{
|
|
3439
|
+
if observation.consumption_from_capture_result == Some(true) {
|
|
3440
|
+
consumption_reason =
|
|
3441
|
+
SubmitConsumptionReason::ConsumptionFromCapture;
|
|
3395
3442
|
found_consumed = true;
|
|
3396
3443
|
break;
|
|
3397
3444
|
}
|
|
3398
|
-
if cursor_single_enter_enabled()
|
|
3399
|
-
|
|
3400
|
-
|
|
3445
|
+
if cursor_single_enter_enabled() && observation.busy.is_some() {
|
|
3446
|
+
consumption_reason =
|
|
3447
|
+
SubmitConsumptionReason::CursorPollingBusy;
|
|
3401
3448
|
found_consumed = true;
|
|
3402
3449
|
break;
|
|
3403
3450
|
}
|
|
3404
3451
|
}
|
|
3405
3452
|
Err(_) => {
|
|
3453
|
+
consumption_diagnostics
|
|
3454
|
+
.record(ConsumptionCaptureObservation::default());
|
|
3406
3455
|
capture_tally.record_err();
|
|
3407
3456
|
capture_retries = capture_retries.saturating_add(1);
|
|
3408
3457
|
consecutive_capture_failures =
|
|
@@ -3426,7 +3475,8 @@ impl Transport for TmuxBackend {
|
|
|
3426
3475
|
}
|
|
3427
3476
|
} else {
|
|
3428
3477
|
// Non-token payload: single Enter, no consumption check
|
|
3429
|
-
// (0.3.27).
|
|
3478
|
+
// (0.3.27). Keep the explicit no-marker reason.
|
|
3479
|
+
consumption_reason = SubmitConsumptionReason::NoMarker;
|
|
3430
3480
|
consumed = Some(true);
|
|
3431
3481
|
break;
|
|
3432
3482
|
}
|
|
@@ -3451,21 +3501,38 @@ impl Transport for TmuxBackend {
|
|
|
3451
3501
|
Some(false) => match self.capture(target, CaptureRange::Tail(15)) {
|
|
3452
3502
|
Ok(cap) => {
|
|
3453
3503
|
last_turn_text = Some(cap.text.clone());
|
|
3504
|
+
let observation = consumption_capture_observation(
|
|
3505
|
+
&cap.text,
|
|
3506
|
+
marker,
|
|
3507
|
+
token_ever_visible,
|
|
3508
|
+
tracked_paste,
|
|
3509
|
+
);
|
|
3510
|
+
consumption_diagnostics.record(observation);
|
|
3454
3511
|
attempts_detail.push(submit_attempt_observation(
|
|
3455
3512
|
consumption_attempts.max(1),
|
|
3456
3513
|
&cap,
|
|
3457
3514
|
marker,
|
|
3458
3515
|
inject_start.elapsed().as_millis() as u64,
|
|
3459
3516
|
));
|
|
3460
|
-
if
|
|
3517
|
+
if observation.busy.is_some() {
|
|
3518
|
+
consumption_reason = SubmitConsumptionReason::FallbackBusy;
|
|
3461
3519
|
SubmitVerification::EnterSentWithoutPlaceholderCheck
|
|
3462
3520
|
} else {
|
|
3521
|
+
consumption_reason = SubmitConsumptionReason::Unverified;
|
|
3463
3522
|
SubmitVerification::SubmitConsumptionUnverified
|
|
3464
3523
|
}
|
|
3465
3524
|
}
|
|
3466
|
-
Err(_) =>
|
|
3525
|
+
Err(_) => {
|
|
3526
|
+
consumption_diagnostics
|
|
3527
|
+
.record(ConsumptionCaptureObservation::default());
|
|
3528
|
+
consumption_reason = SubmitConsumptionReason::Unverified;
|
|
3529
|
+
SubmitVerification::SubmitConsumptionUnverified
|
|
3530
|
+
}
|
|
3467
3531
|
},
|
|
3468
|
-
None =>
|
|
3532
|
+
None => {
|
|
3533
|
+
consumption_reason = SubmitConsumptionReason::Unverified;
|
|
3534
|
+
SubmitVerification::SubmitConsumptionUnverified
|
|
3535
|
+
}
|
|
3469
3536
|
};
|
|
3470
3537
|
// Unverified + 折行缺口:只补一颗 C-m。A 的 should_resubmit_enter
|
|
3471
3538
|
// 已为真(latch/底15 token,含打满 cap)时 B 不介入。身份拿不到
|
|
@@ -3511,6 +3578,13 @@ impl Transport for TmuxBackend {
|
|
|
3511
3578
|
token_seen_after_enter = Some(false);
|
|
3512
3579
|
}
|
|
3513
3580
|
tracked_paste = latch_paste(&cap.text, tracked_paste);
|
|
3581
|
+
let observation = consumption_capture_observation(
|
|
3582
|
+
&cap.text,
|
|
3583
|
+
marker,
|
|
3584
|
+
token_ever_visible,
|
|
3585
|
+
tracked_paste,
|
|
3586
|
+
);
|
|
3587
|
+
consumption_diagnostics.record(observation);
|
|
3514
3588
|
let obs = submit_attempt_observation(
|
|
3515
3589
|
consumption_attempts.max(1),
|
|
3516
3590
|
&cap,
|
|
@@ -3519,20 +3593,22 @@ impl Transport for TmuxBackend {
|
|
|
3519
3593
|
);
|
|
3520
3594
|
attempts_detail.push(obs);
|
|
3521
3595
|
last_turn_text = Some(cap.text.clone());
|
|
3522
|
-
|
|
3523
|
-
|
|
3524
|
-
|
|
3525
|
-
|
|
3526
|
-
|
|
3527
|
-
) == Some(true)
|
|
3528
|
-
|| provider_busy_signal_in_tail(&cap.text)
|
|
3529
|
-
{
|
|
3596
|
+
let capture_consumed = observation
|
|
3597
|
+
.consumption_from_capture_result
|
|
3598
|
+
== Some(true);
|
|
3599
|
+
let busy = observation.busy.is_some();
|
|
3600
|
+
if capture_consumed || busy {
|
|
3530
3601
|
// Gone+无 latch 会把折行 token 写成已消费;身份仍在则不算。
|
|
3531
3602
|
if !this_paste_identity_in_composer(
|
|
3532
3603
|
&cap.text,
|
|
3533
3604
|
m,
|
|
3534
3605
|
tracked_paste,
|
|
3535
3606
|
) {
|
|
3607
|
+
consumption_reason = if capture_consumed {
|
|
3608
|
+
SubmitConsumptionReason::ConsumptionFromCapture
|
|
3609
|
+
} else {
|
|
3610
|
+
SubmitConsumptionReason::FallbackBusy
|
|
3611
|
+
};
|
|
3536
3612
|
found_consumed = true;
|
|
3537
3613
|
break;
|
|
3538
3614
|
}
|
|
@@ -3542,11 +3618,14 @@ impl Transport for TmuxBackend {
|
|
|
3542
3618
|
m,
|
|
3543
3619
|
tracked_paste,
|
|
3544
3620
|
) {
|
|
3621
|
+
consumption_reason = SubmitConsumptionReason::Unknown;
|
|
3545
3622
|
found_consumed = true;
|
|
3546
3623
|
break;
|
|
3547
3624
|
}
|
|
3548
3625
|
}
|
|
3549
3626
|
Err(_) => {
|
|
3627
|
+
consumption_diagnostics
|
|
3628
|
+
.record(ConsumptionCaptureObservation::default());
|
|
3550
3629
|
capture_tally.record_err();
|
|
3551
3630
|
break;
|
|
3552
3631
|
}
|
|
@@ -3562,6 +3641,24 @@ impl Transport for TmuxBackend {
|
|
|
3562
3641
|
}
|
|
3563
3642
|
let total_elapsed_ms = inject_start.elapsed().as_millis() as u64;
|
|
3564
3643
|
let surface = probe_inject_target_surface(self, target);
|
|
3644
|
+
let mut submit_diagnostics = submit_diagnostics_for_inject(
|
|
3645
|
+
total_elapsed_ms,
|
|
3646
|
+
attempts_detail,
|
|
3647
|
+
&capture_tally,
|
|
3648
|
+
token_seen_after_paste,
|
|
3649
|
+
token_seen_after_enter,
|
|
3650
|
+
surface,
|
|
3651
|
+
);
|
|
3652
|
+
submit_diagnostics.consumption_reason = consumption_reason;
|
|
3653
|
+
submit_diagnostics.busy_signal_kind = consumption_diagnostics.busy_signal_kind;
|
|
3654
|
+
submit_diagnostics.busy_line_from_bottom =
|
|
3655
|
+
consumption_diagnostics.busy_line_from_bottom;
|
|
3656
|
+
submit_diagnostics.current_marker_in_bottom_15 =
|
|
3657
|
+
consumption_diagnostics.current_marker_in_bottom_15;
|
|
3658
|
+
submit_diagnostics.paste_identity_in_composer =
|
|
3659
|
+
consumption_diagnostics.paste_identity_in_composer;
|
|
3660
|
+
submit_diagnostics.consumption_from_capture_result =
|
|
3661
|
+
consumption_diagnostics.consumption_from_capture_result;
|
|
3565
3662
|
return Ok(InjectReport {
|
|
3566
3663
|
stage_reached: InjectStage::Submit,
|
|
3567
3664
|
inject_verification: inject_verification_after_readback(
|
|
@@ -3574,14 +3671,7 @@ impl Transport for TmuxBackend {
|
|
|
3574
3671
|
last_turn_text.as_deref(),
|
|
3575
3672
|
),
|
|
3576
3673
|
attempts: consumption_attempts.saturating_add(capture_retries),
|
|
3577
|
-
submit_diagnostics: Some(
|
|
3578
|
-
total_elapsed_ms,
|
|
3579
|
-
attempts_detail,
|
|
3580
|
-
&capture_tally,
|
|
3581
|
-
token_seen_after_paste,
|
|
3582
|
-
token_seen_after_enter,
|
|
3583
|
-
surface,
|
|
3584
|
-
)),
|
|
3674
|
+
submit_diagnostics: Some(submit_diagnostics),
|
|
3585
3675
|
});
|
|
3586
3676
|
}
|
|
3587
3677
|
}
|
|
@@ -467,6 +467,21 @@ pub struct InjectReport {
|
|
|
467
467
|
/// pasted-prompt branch + (informational) for the appear-gate poll.
|
|
468
468
|
#[derive(Debug, Clone, PartialEq, Eq, Default)]
|
|
469
469
|
pub struct SubmitDiagnostics {
|
|
470
|
+
/// The concrete branch that produced the post-Enter consumption outcome.
|
|
471
|
+
/// Unknown is retained for paths that do not expose a branch reason.
|
|
472
|
+
pub consumption_reason: SubmitConsumptionReason,
|
|
473
|
+
/// Reserved for a pre-paste baseline; this path has no such capture and emits null.
|
|
474
|
+
pub before_busy_signal_kind: Option<BusySignalKind>,
|
|
475
|
+
/// Structured busy predicate category for the accepted/current capture.
|
|
476
|
+
pub busy_signal_kind: Option<BusySignalKind>,
|
|
477
|
+
/// Bottom-relative non-empty line index of the busy match.
|
|
478
|
+
pub busy_line_from_bottom: Option<u8>,
|
|
479
|
+
/// Whether this message marker is in the bottom 15 non-empty lines.
|
|
480
|
+
pub current_marker_in_bottom_15: Option<bool>,
|
|
481
|
+
/// Whether the tracked paste identity is still in the composer.
|
|
482
|
+
pub paste_identity_in_composer: Option<bool>,
|
|
483
|
+
/// Result of the structural consumption predicate on the current capture.
|
|
484
|
+
pub consumption_from_capture_result: Option<bool>,
|
|
470
485
|
/// Time spent in the appear-gate (poll for the pasted-content placeholder
|
|
471
486
|
/// before Enter). When `saw_pasted_prompt == false` this is the time we
|
|
472
487
|
/// spent polling before falling through to the E46 token path.
|
|
@@ -505,6 +520,63 @@ pub struct SubmitDiagnostics {
|
|
|
505
520
|
pub target_pane_query_matched: Option<bool>,
|
|
506
521
|
}
|
|
507
522
|
|
|
523
|
+
/// Structured category returned by the provider-busy predicate.
|
|
524
|
+
///
|
|
525
|
+
/// This is diagnostic metadata only; it does not change delivery semantics.
|
|
526
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
527
|
+
pub enum BusySignalKind {
|
|
528
|
+
Working,
|
|
529
|
+
Thinking,
|
|
530
|
+
Processing,
|
|
531
|
+
EscToInterrupt,
|
|
532
|
+
SpinnerGlyph,
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
impl BusySignalKind {
|
|
536
|
+
pub fn as_str(self) -> &'static str {
|
|
537
|
+
match self {
|
|
538
|
+
Self::Working => "working",
|
|
539
|
+
Self::Thinking => "thinking",
|
|
540
|
+
Self::Processing => "processing",
|
|
541
|
+
Self::EscToInterrupt => "esc_to_interrupt",
|
|
542
|
+
Self::SpinnerGlyph => "spinner_glyph",
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
/// Structured reason for the post-Enter consumption observation.
|
|
548
|
+
///
|
|
549
|
+
/// This is diagnostic metadata only; it does not change delivery semantics.
|
|
550
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
|
551
|
+
pub enum SubmitConsumptionReason {
|
|
552
|
+
/// The token/input identity disappeared in the consumption capture.
|
|
553
|
+
ConsumptionFromCapture,
|
|
554
|
+
/// Cursor's bounded polling observed a provider-busy signal.
|
|
555
|
+
CursorPollingBusy,
|
|
556
|
+
/// The final fallback capture observed a provider-busy signal.
|
|
557
|
+
FallbackBusy,
|
|
558
|
+
/// The payload had no token marker, so no structural consumption probe ran.
|
|
559
|
+
NoMarker,
|
|
560
|
+
/// The bounded probe did not establish consumption.
|
|
561
|
+
Unverified,
|
|
562
|
+
/// An older or otherwise uncovered path did not provide a reason.
|
|
563
|
+
#[default]
|
|
564
|
+
Unknown,
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
impl SubmitConsumptionReason {
|
|
568
|
+
pub fn as_str(self) -> &'static str {
|
|
569
|
+
match self {
|
|
570
|
+
Self::ConsumptionFromCapture => "consumption_from_capture",
|
|
571
|
+
Self::CursorPollingBusy => "cursor_poll_busy",
|
|
572
|
+
Self::FallbackBusy => "fallback_busy",
|
|
573
|
+
Self::NoMarker => "no_marker",
|
|
574
|
+
Self::Unverified => "unverified",
|
|
575
|
+
Self::Unknown => "unknown",
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
|
|
508
580
|
/// Last capture sample of the inject token path. Distinguishes "read failed"
|
|
509
581
|
/// from "read succeeded and token was absent". Does not change delivery gates.
|
|
510
582
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
|
@@ -51,6 +51,59 @@ use crate::conpty::ConPtyBackend;
|
|
|
51
51
|
use crate::tmux_backend::TmuxBackend;
|
|
52
52
|
use crate::transport::{BackendKind, Transport};
|
|
53
53
|
|
|
54
|
+
#[cfg(test)]
|
|
55
|
+
thread_local! {
|
|
56
|
+
static LEADER_ENDPOINT_TRANSPORT_OVERRIDE: std::cell::RefCell<
|
|
57
|
+
Option<(String, crate::transport::test_support::OfflineTransport)>,
|
|
58
|
+
> = std::cell::RefCell::new(None);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/// Production live-probe transport for a recorded leader endpoint.
|
|
62
|
+
/// `classify_leader_binding`, restart `transport_for_receiver`, and
|
|
63
|
+
/// `attach_leader_targets` all go through this function.
|
|
64
|
+
pub fn leader_endpoint_transport(endpoint: &str) -> Box<dyn Transport> {
|
|
65
|
+
#[cfg(test)]
|
|
66
|
+
if let Some(injected) = LEADER_ENDPOINT_TRANSPORT_OVERRIDE.with(|slot| {
|
|
67
|
+
slot.borrow().as_ref().and_then(|(expected, transport)| {
|
|
68
|
+
(expected == endpoint).then(|| Box::new(transport.clone()) as Box<dyn Transport>)
|
|
69
|
+
})
|
|
70
|
+
}) {
|
|
71
|
+
return injected;
|
|
72
|
+
}
|
|
73
|
+
if endpoint.is_empty() || endpoint == "default" {
|
|
74
|
+
Box::new(tmux_default_transport())
|
|
75
|
+
} else {
|
|
76
|
+
Box::new(tmux_endpoint_transport(endpoint))
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
#[cfg(test)]
|
|
81
|
+
struct LeaderEndpointTransportGuard {
|
|
82
|
+
previous: Option<(String, crate::transport::test_support::OfflineTransport)>,
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
#[cfg(test)]
|
|
86
|
+
impl Drop for LeaderEndpointTransportGuard {
|
|
87
|
+
fn drop(&mut self) {
|
|
88
|
+
LEADER_ENDPOINT_TRANSPORT_OVERRIDE.with(|slot| {
|
|
89
|
+
*slot.borrow_mut() = self.previous.take();
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
#[cfg(test)]
|
|
95
|
+
pub fn with_leader_endpoint_transport<T>(
|
|
96
|
+
endpoint: &str,
|
|
97
|
+
transport: crate::transport::test_support::OfflineTransport,
|
|
98
|
+
f: impl FnOnce() -> T,
|
|
99
|
+
) -> T {
|
|
100
|
+
let previous = LEADER_ENDPOINT_TRANSPORT_OVERRIDE.with(|slot| {
|
|
101
|
+
slot.replace(Some((endpoint.to_string(), transport)))
|
|
102
|
+
});
|
|
103
|
+
let _guard = LeaderEndpointTransportGuard { previous };
|
|
104
|
+
f()
|
|
105
|
+
}
|
|
106
|
+
|
|
54
107
|
/// User-visible backend selector on the CLI (`--backend tmux|conpty`).
|
|
55
108
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
56
109
|
pub enum RequestedTransportBackend {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@team-agent/installer",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.78",
|
|
4
4
|
"description": "npx installer for Team Agent",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"codex",
|
|
@@ -20,9 +20,9 @@
|
|
|
20
20
|
"team-agent-installer": "npm/install.mjs"
|
|
21
21
|
},
|
|
22
22
|
"optionalDependencies": {
|
|
23
|
-
"@team-agent/cli-darwin-arm64": "0.5.
|
|
24
|
-
"@team-agent/cli-darwin-x64": "0.5.
|
|
25
|
-
"@team-agent/cli-linux-x64": "0.5.
|
|
23
|
+
"@team-agent/cli-darwin-arm64": "0.5.78",
|
|
24
|
+
"@team-agent/cli-darwin-x64": "0.5.78",
|
|
25
|
+
"@team-agent/cli-linux-x64": "0.5.78"
|
|
26
26
|
},
|
|
27
27
|
"scripts": {
|
|
28
28
|
"postinstall": "node npm/bincheck.mjs",
|
|
@@ -33,7 +33,6 @@
|
|
|
33
33
|
"files": [
|
|
34
34
|
"npm/bincheck.mjs",
|
|
35
35
|
"npm/install.mjs",
|
|
36
|
-
"npm/check-skill-links.mjs",
|
|
37
36
|
"skills",
|
|
38
37
|
"templates",
|
|
39
38
|
"examples",
|