@team-agent/installer 0.5.45 → 0.5.47
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Cargo.lock +1 -1
- package/Cargo.toml +1 -1
- package/crates/team-agent/src/cli/emit.rs +33 -11
- package/crates/team-agent/src/cli/mod.rs +6 -0
- package/crates/team-agent/src/cli/status_port.rs +72 -4
- package/crates/team-agent/src/coordinator/steps/abnormal.rs +14 -22
- package/crates/team-agent/src/event_log.rs +3 -3
- package/crates/team-agent/src/lib.rs +1 -0
- package/crates/team-agent/src/lifecycle/profile_smoke.rs +3 -13
- package/crates/team-agent/src/lifecycle/restart/agent.rs +103 -27
- package/crates/team-agent/src/lifecycle/restart/common.rs +136 -0
- package/crates/team-agent/src/lifecycle/restart/rebuild.rs +168 -0
- package/crates/team-agent/src/lifecycle/tests/agent_ops.rs +7 -7
- package/crates/team-agent/src/lifecycle/tests/phase_b_contracts.rs +1 -1
- package/crates/team-agent/src/lifecycle/tests/phase_golden.rs +3 -0
- package/crates/team-agent/src/lifecycle/types.rs +3 -0
- package/crates/team-agent/src/mcp_server/lifecycle_tools/agent_ops.rs +10 -0
- package/crates/team-agent/src/mcp_server/wire.rs +3 -1
- package/crates/team-agent/src/provider/session/capture.rs +113 -22
- package/crates/team-agent/src/provider/session_scan/codex.rs +51 -4
- package/crates/team-agent/src/redaction.rs +185 -0
- package/crates/team-agent/src/tmux_backend/tests.rs +116 -10
- package/crates/team-agent/src/tmux_backend.rs +6 -16
- package/crates/team-agent/src/transport/tests/wire.rs +6 -0
- package/crates/team-agent/src/transport.rs +8 -2
- package/package.json +4 -4
|
@@ -14,6 +14,142 @@ pub(super) struct SpawnedAgentWindow {
|
|
|
14
14
|
pub owner_team_id: Option<String>,
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
+
#[derive(Clone)]
|
|
18
|
+
pub(super) struct SameRoleCohortTarget {
|
|
19
|
+
pub agent_id: String,
|
|
20
|
+
pub window: String,
|
|
21
|
+
pub expected_pane_id: Option<String>,
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
impl SameRoleCohortTarget {
|
|
25
|
+
pub(super) fn new(agent_id: &AgentId, window: &str) -> Self {
|
|
26
|
+
Self {
|
|
27
|
+
agent_id: agent_id.as_str().to_string(),
|
|
28
|
+
window: window.to_string(),
|
|
29
|
+
expected_pane_id: None,
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
pub(super) fn with_expected_pane_id(mut self, pane_id: Option<&str>) -> Self {
|
|
34
|
+
self.expected_pane_id = pane_id.map(ToString::to_string);
|
|
35
|
+
self
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
pub(super) fn is_per_agent_cohort_window(window: &str, agent_id: &AgentId) -> bool {
|
|
40
|
+
window == agent_id.as_str() && !crate::lifecycle::launch::is_adaptive_layout_window_pub(window)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
pub(super) fn same_role_cohort_pre_spawn_error(
|
|
44
|
+
transport: &dyn crate::transport::Transport,
|
|
45
|
+
session_name: &SessionName,
|
|
46
|
+
operation: &str,
|
|
47
|
+
targets: &[SameRoleCohortTarget],
|
|
48
|
+
) -> Option<String> {
|
|
49
|
+
same_role_cohort_error(transport, session_name, operation, targets, 0, true)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
pub(super) fn same_role_cohort_exactly_one_error(
|
|
53
|
+
transport: &dyn crate::transport::Transport,
|
|
54
|
+
session_name: &SessionName,
|
|
55
|
+
operation: &str,
|
|
56
|
+
targets: &[SameRoleCohortTarget],
|
|
57
|
+
) -> Option<String> {
|
|
58
|
+
same_role_cohort_error(transport, session_name, operation, targets, 1, false)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
pub(super) fn retire_expected_same_role_cohorts(
|
|
62
|
+
transport: &dyn crate::transport::Transport,
|
|
63
|
+
operation: &str,
|
|
64
|
+
targets: &[SameRoleCohortTarget],
|
|
65
|
+
) -> Result<(), String> {
|
|
66
|
+
for target in targets {
|
|
67
|
+
let Some(pane_id) = target.expected_pane_id.as_deref() else {
|
|
68
|
+
continue;
|
|
69
|
+
};
|
|
70
|
+
let pane = crate::transport::PaneId::new(pane_id);
|
|
71
|
+
if transport.has_pane(&pane).ok().flatten() == Some(false) {
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
transport.kill_pane(&pane).map_err(|error| {
|
|
75
|
+
format!(
|
|
76
|
+
"{operation} failed to retire old same-role cohort {}:window={}:pane={pane_id}: {error}",
|
|
77
|
+
target.agent_id, target.window
|
|
78
|
+
)
|
|
79
|
+
})?;
|
|
80
|
+
}
|
|
81
|
+
Ok(())
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
fn same_role_cohort_error(
|
|
85
|
+
transport: &dyn crate::transport::Transport,
|
|
86
|
+
session_name: &SessionName,
|
|
87
|
+
operation: &str,
|
|
88
|
+
targets: &[SameRoleCohortTarget],
|
|
89
|
+
expected_live: usize,
|
|
90
|
+
expected_old_only: bool,
|
|
91
|
+
) -> Option<String> {
|
|
92
|
+
let panes = transport.list_targets().ok()?;
|
|
93
|
+
let mut cardinality_proofs = Vec::new();
|
|
94
|
+
let mut binding_proofs = Vec::new();
|
|
95
|
+
for target in targets {
|
|
96
|
+
let live_panes = panes
|
|
97
|
+
.iter()
|
|
98
|
+
.filter(|pane| pane.session.as_str() == session_name.as_str())
|
|
99
|
+
.filter(|pane| {
|
|
100
|
+
pane.window_name
|
|
101
|
+
.as_ref()
|
|
102
|
+
.is_some_and(|window| window.as_str() == target.window)
|
|
103
|
+
})
|
|
104
|
+
.filter(|pane| {
|
|
105
|
+
!expected_old_only
|
|
106
|
+
|| target
|
|
107
|
+
.expected_pane_id
|
|
108
|
+
.as_deref()
|
|
109
|
+
.is_some_and(|expected| pane.pane_id.as_str() == expected)
|
|
110
|
+
})
|
|
111
|
+
.filter(|pane| {
|
|
112
|
+
transport
|
|
113
|
+
.liveness(&pane.pane_id)
|
|
114
|
+
.is_ok_and(|live| live == crate::transport::PaneLiveness::Live)
|
|
115
|
+
})
|
|
116
|
+
.map(|pane| pane.pane_id.as_str().to_string())
|
|
117
|
+
.collect::<Vec<_>>();
|
|
118
|
+
if live_panes.len() != expected_live {
|
|
119
|
+
cardinality_proofs.push(format!(
|
|
120
|
+
"{}:window={}:live_panes=[{}]",
|
|
121
|
+
target.agent_id,
|
|
122
|
+
target.window,
|
|
123
|
+
live_panes.join(",")
|
|
124
|
+
));
|
|
125
|
+
} else if !expected_old_only {
|
|
126
|
+
let observed = live_panes.first().map(String::as_str);
|
|
127
|
+
if target.expected_pane_id.as_deref() != observed {
|
|
128
|
+
binding_proofs.push(format!(
|
|
129
|
+
"{}:window={}:expected_pane={}:observed_pane={}",
|
|
130
|
+
target.agent_id,
|
|
131
|
+
target.window,
|
|
132
|
+
target.expected_pane_id.as_deref().unwrap_or("<missing>"),
|
|
133
|
+
observed.unwrap_or("<missing>")
|
|
134
|
+
));
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
if !binding_proofs.is_empty() {
|
|
139
|
+
return Some(format!(
|
|
140
|
+
"{operation} refused: spawn identity/binding mismatch; {}",
|
|
141
|
+
binding_proofs.join("; ")
|
|
142
|
+
));
|
|
143
|
+
}
|
|
144
|
+
if !cardinality_proofs.is_empty() {
|
|
145
|
+
return Some(format!(
|
|
146
|
+
"{operation} refused: same-role cohort duplicate proof failed; {}",
|
|
147
|
+
cardinality_proofs.join("; ")
|
|
148
|
+
));
|
|
149
|
+
}
|
|
150
|
+
None
|
|
151
|
+
}
|
|
152
|
+
|
|
17
153
|
#[allow(clippy::too_many_arguments)]
|
|
18
154
|
pub(super) fn spawn_agent_window(
|
|
19
155
|
workspace: &Path,
|
|
@@ -428,6 +428,18 @@ fn restart_with_selected_team_and_transport(
|
|
|
428
428
|
let live_worker_session_deferred_teardown =
|
|
429
429
|
session_live_or_default(transport, &session_name, false)
|
|
430
430
|
&& !collect_live_agents_from_state(&state).is_empty();
|
|
431
|
+
let same_role_cohort_targets =
|
|
432
|
+
same_role_cohort_targets_from_decisions(&state, spec_workspace, &plan.decisions);
|
|
433
|
+
let pre_spawn_pane_ids = if live_worker_session_deferred_teardown {
|
|
434
|
+
transport
|
|
435
|
+
.list_targets()
|
|
436
|
+
.map_err(|error| LifecycleError::Transport(error.to_string()))?
|
|
437
|
+
.into_iter()
|
|
438
|
+
.map(|pane| pane.pane_id.as_str().to_string())
|
|
439
|
+
.collect::<std::collections::BTreeSet<_>>()
|
|
440
|
+
} else {
|
|
441
|
+
std::collections::BTreeSet::new()
|
|
442
|
+
};
|
|
431
443
|
if session_live_or_default(transport, &session_name, false)
|
|
432
444
|
&& !live_worker_session_deferred_teardown
|
|
433
445
|
{
|
|
@@ -779,6 +791,46 @@ fn restart_with_selected_team_and_transport(
|
|
|
779
791
|
);
|
|
780
792
|
}
|
|
781
793
|
// END_B5_RESTART_ISOLATION_LOOP
|
|
794
|
+
if live_worker_session_deferred_teardown {
|
|
795
|
+
let successful_agent_ids = successful_agents
|
|
796
|
+
.iter()
|
|
797
|
+
.map(|agent| agent.agent_id.as_str())
|
|
798
|
+
.collect::<std::collections::BTreeSet<_>>();
|
|
799
|
+
let old_successful_targets = same_role_cohort_targets
|
|
800
|
+
.iter()
|
|
801
|
+
.filter(|target| successful_agent_ids.contains(target.agent_id.as_str()))
|
|
802
|
+
.cloned()
|
|
803
|
+
.collect::<Vec<_>>();
|
|
804
|
+
let successful_targets =
|
|
805
|
+
same_role_cohort_targets_from_decisions(&state, spec_workspace, &successful_agents);
|
|
806
|
+
let actual_spawn_panes = actual_successful_restart_spawn_panes(
|
|
807
|
+
transport,
|
|
808
|
+
&session_name,
|
|
809
|
+
&successful_targets,
|
|
810
|
+
&pre_spawn_pane_ids,
|
|
811
|
+
)?;
|
|
812
|
+
if let Some(error) =
|
|
813
|
+
actual_restart_spawn_binding_error(&successful_targets, &actual_spawn_panes)
|
|
814
|
+
{
|
|
815
|
+
cleanup_actual_restart_spawns(transport, &actual_spawn_panes);
|
|
816
|
+
return Err(LifecycleError::RequirementUnmet(error));
|
|
817
|
+
}
|
|
818
|
+
if let Err(error) =
|
|
819
|
+
retire_expected_same_role_cohorts(transport, "restart", &old_successful_targets)
|
|
820
|
+
{
|
|
821
|
+
cleanup_actual_restart_spawns(transport, &actual_spawn_panes);
|
|
822
|
+
return Err(LifecycleError::RequirementUnmet(error));
|
|
823
|
+
}
|
|
824
|
+
if let Some(error) = same_role_cohort_exactly_one_error(
|
|
825
|
+
transport,
|
|
826
|
+
&session_name,
|
|
827
|
+
"restart",
|
|
828
|
+
&successful_targets,
|
|
829
|
+
) {
|
|
830
|
+
cleanup_actual_restart_spawns(transport, &actual_spawn_panes);
|
|
831
|
+
return Err(LifecycleError::RequirementUnmet(error));
|
|
832
|
+
}
|
|
833
|
+
}
|
|
782
834
|
let mut topology_authority_agent_ids = successful_agents
|
|
783
835
|
.iter()
|
|
784
836
|
.map(|agent| agent.agent_id.as_str().to_string())
|
|
@@ -2090,6 +2142,122 @@ fn mark_fake_harness_agent_respawned(
|
|
|
2090
2142
|
agent.insert("owner_team_id".to_string(), serde_json::json!(team_key));
|
|
2091
2143
|
}
|
|
2092
2144
|
|
|
2145
|
+
fn same_role_cohort_targets_from_decisions(
|
|
2146
|
+
state: &serde_json::Value,
|
|
2147
|
+
spec_workspace: &Path,
|
|
2148
|
+
agents: &[RestartedAgent],
|
|
2149
|
+
) -> Vec<SameRoleCohortTarget> {
|
|
2150
|
+
let mut targets = Vec::new();
|
|
2151
|
+
for restarted in agents {
|
|
2152
|
+
let agent_id = &restarted.agent_id;
|
|
2153
|
+
let raw_agent = state
|
|
2154
|
+
.get("agents")
|
|
2155
|
+
.and_then(|agents| agents.get(agent_id.as_str()));
|
|
2156
|
+
let effective_agent = raw_agent.map(|agent| {
|
|
2157
|
+
rehydrate_agent_command_context_from_spec(spec_workspace, agent_id, agent)
|
|
2158
|
+
});
|
|
2159
|
+
if effective_agent
|
|
2160
|
+
.as_ref()
|
|
2161
|
+
.and_then(|agent| agent.get("provider"))
|
|
2162
|
+
.and_then(serde_json::Value::as_str)
|
|
2163
|
+
.is_some_and(|provider| provider.eq_ignore_ascii_case("fake"))
|
|
2164
|
+
{
|
|
2165
|
+
continue;
|
|
2166
|
+
}
|
|
2167
|
+
let window = raw_agent
|
|
2168
|
+
.and_then(|agent| agent.get("window"))
|
|
2169
|
+
.and_then(serde_json::Value::as_str)
|
|
2170
|
+
.filter(|value| !value.is_empty())
|
|
2171
|
+
.unwrap_or_else(|| agent_id.as_str());
|
|
2172
|
+
if is_per_agent_cohort_window(window, agent_id) {
|
|
2173
|
+
let expected_pane_id = raw_agent
|
|
2174
|
+
.and_then(|agent| agent.get("pane_id"))
|
|
2175
|
+
.and_then(serde_json::Value::as_str)
|
|
2176
|
+
.filter(|value| !value.is_empty());
|
|
2177
|
+
targets.push(
|
|
2178
|
+
SameRoleCohortTarget::new(agent_id, window).with_expected_pane_id(expected_pane_id),
|
|
2179
|
+
);
|
|
2180
|
+
}
|
|
2181
|
+
}
|
|
2182
|
+
targets
|
|
2183
|
+
}
|
|
2184
|
+
|
|
2185
|
+
fn actual_successful_restart_spawn_panes(
|
|
2186
|
+
transport: &dyn crate::transport::Transport,
|
|
2187
|
+
session_name: &SessionName,
|
|
2188
|
+
targets: &[SameRoleCohortTarget],
|
|
2189
|
+
pre_spawn_pane_ids: &std::collections::BTreeSet<String>,
|
|
2190
|
+
) -> Result<Vec<crate::transport::PaneInfo>, LifecycleError> {
|
|
2191
|
+
let windows = targets
|
|
2192
|
+
.iter()
|
|
2193
|
+
.map(|target| target.window.as_str())
|
|
2194
|
+
.collect::<std::collections::BTreeSet<_>>();
|
|
2195
|
+
let panes = transport
|
|
2196
|
+
.list_targets()
|
|
2197
|
+
.map_err(|error| LifecycleError::Transport(error.to_string()))?
|
|
2198
|
+
.into_iter()
|
|
2199
|
+
.filter(|pane| !pre_spawn_pane_ids.contains(pane.pane_id.as_str()))
|
|
2200
|
+
.filter(|pane| pane.session.as_str() == session_name.as_str())
|
|
2201
|
+
.filter(|pane| {
|
|
2202
|
+
pane.window_name
|
|
2203
|
+
.as_ref()
|
|
2204
|
+
.is_some_and(|window| windows.contains(window.as_str()))
|
|
2205
|
+
})
|
|
2206
|
+
.filter(|pane| {
|
|
2207
|
+
transport
|
|
2208
|
+
.liveness(&pane.pane_id)
|
|
2209
|
+
.is_ok_and(|state| state == crate::transport::PaneLiveness::Live)
|
|
2210
|
+
})
|
|
2211
|
+
.collect();
|
|
2212
|
+
Ok(panes)
|
|
2213
|
+
}
|
|
2214
|
+
|
|
2215
|
+
fn actual_restart_spawn_binding_error(
|
|
2216
|
+
targets: &[SameRoleCohortTarget],
|
|
2217
|
+
actual_spawn_panes: &[crate::transport::PaneInfo],
|
|
2218
|
+
) -> Option<String> {
|
|
2219
|
+
let mut mismatches = Vec::new();
|
|
2220
|
+
for target in targets {
|
|
2221
|
+
let observed = actual_spawn_panes
|
|
2222
|
+
.iter()
|
|
2223
|
+
.filter(|pane| {
|
|
2224
|
+
pane.window_name
|
|
2225
|
+
.as_ref()
|
|
2226
|
+
.is_some_and(|window| window.as_str() == target.window)
|
|
2227
|
+
})
|
|
2228
|
+
.map(|pane| pane.pane_id.as_str().to_string())
|
|
2229
|
+
.collect::<Vec<_>>();
|
|
2230
|
+
if observed.len() != 1
|
|
2231
|
+
|| target.expected_pane_id.as_deref() != observed.first().map(String::as_str)
|
|
2232
|
+
{
|
|
2233
|
+
mismatches.push(format!(
|
|
2234
|
+
"{}:window={}:expected_pane={}:actual_spawn_panes=[{}]",
|
|
2235
|
+
target.agent_id,
|
|
2236
|
+
target.window,
|
|
2237
|
+
target.expected_pane_id.as_deref().unwrap_or("<missing>"),
|
|
2238
|
+
observed.join(",")
|
|
2239
|
+
));
|
|
2240
|
+
}
|
|
2241
|
+
}
|
|
2242
|
+
if mismatches.is_empty() {
|
|
2243
|
+
None
|
|
2244
|
+
} else {
|
|
2245
|
+
Some(format!(
|
|
2246
|
+
"restart refused: spawn identity/binding mismatch; {}",
|
|
2247
|
+
mismatches.join("; ")
|
|
2248
|
+
))
|
|
2249
|
+
}
|
|
2250
|
+
}
|
|
2251
|
+
|
|
2252
|
+
fn cleanup_actual_restart_spawns(
|
|
2253
|
+
transport: &dyn crate::transport::Transport,
|
|
2254
|
+
actual_spawn_panes: &[crate::transport::PaneInfo],
|
|
2255
|
+
) {
|
|
2256
|
+
for pane in actual_spawn_panes {
|
|
2257
|
+
let _ = transport.kill_pane(&pane.pane_id);
|
|
2258
|
+
}
|
|
2259
|
+
}
|
|
2260
|
+
|
|
2093
2261
|
fn write_fake_harness_spawn_argv_event(
|
|
2094
2262
|
workspace: &Path,
|
|
2095
2263
|
decision: &RestartedAgent,
|
|
@@ -402,6 +402,11 @@ case "$*" in
|
|
|
402
402
|
;;
|
|
403
403
|
esac
|
|
404
404
|
case "$*" in
|
|
405
|
+
*"new-window "*|*"new-session "*)
|
|
406
|
+
: > "$spawned_marker"
|
|
407
|
+
printf '%%9288\n'
|
|
408
|
+
exit 0
|
|
409
|
+
;;
|
|
405
410
|
*"display-message -p -t $pane #{pane_id}"*)
|
|
406
411
|
if [ -f "$killed_marker" ]; then
|
|
407
412
|
echo "can't find pane: $pane" >&2
|
|
@@ -410,11 +415,6 @@ case "$*" in
|
|
|
410
415
|
printf '%s\n' "$pane"
|
|
411
416
|
exit 0
|
|
412
417
|
;;
|
|
413
|
-
*"display-message -p -t $session:alpha #{pane_id}"*)
|
|
414
|
-
: > "$spawned_marker"
|
|
415
|
-
printf '%%9288\n'
|
|
416
|
-
exit 0
|
|
417
|
-
;;
|
|
418
418
|
*"list-windows -t $session -F #{window_name}"*)
|
|
419
419
|
exit 0
|
|
420
420
|
;;
|
|
@@ -873,8 +873,8 @@ fn reset_agent_stop_not_proven_grep_guard_hard_gate_wired() {
|
|
|
873
873
|
"reset_agent_at_paths must emit reset_agent.stop_not_proven via the writer"
|
|
874
874
|
);
|
|
875
875
|
assert!(
|
|
876
|
-
contents.contains("if !agent_is_paused
|
|
877
|
-
"gate must run
|
|
876
|
+
contents.contains("if !agent_is_paused {"),
|
|
877
|
+
"gate must run after stop for every non-paused reset, including stop.stopped=true residue"
|
|
878
878
|
);
|
|
879
879
|
assert!(
|
|
880
880
|
contents.contains("list_same_role_panes") && contents.contains("is_per_agent_window"),
|
|
@@ -744,7 +744,7 @@ impl Transport for BRealTransport {
|
|
|
744
744
|
if matches!(self.mode, BRealMode::Misowned) {
|
|
745
745
|
targets.push(pane_info("team-phaseb", "w2", "%5", 502));
|
|
746
746
|
}
|
|
747
|
-
if matches!(self.mode, BRealMode::Owned) {
|
|
747
|
+
if matches!(self.mode, BRealMode::Owned) && !self.spawns.lock().unwrap().is_empty() {
|
|
748
748
|
targets.push(pane_info("team-phaseb", "w1", "%10", 501));
|
|
749
749
|
}
|
|
750
750
|
Ok(targets)
|
|
@@ -553,6 +553,9 @@ fn normalize_value(value: Value, ctx: &mut NormalizeCtx, key: Option<&str>) -> V
|
|
|
553
553
|
let sorted = map.into_iter().collect::<BTreeMap<_, _>>();
|
|
554
554
|
let mut out = Map::new();
|
|
555
555
|
for (child_key, child) in sorted {
|
|
556
|
+
if matches!(child_key.as_str(), "model_source" | "provider_source") {
|
|
557
|
+
continue;
|
|
558
|
+
}
|
|
556
559
|
out.insert(
|
|
557
560
|
child_key.clone(),
|
|
558
561
|
normalize_value(child, ctx, Some(child_key.as_str())),
|
|
@@ -632,6 +632,9 @@ pub enum ResetAgentOutcome {
|
|
|
632
632
|
discarded_session_id: Option<SessionId>,
|
|
633
633
|
session_id: Option<SessionId>,
|
|
634
634
|
new_session_id: Option<SessionId>,
|
|
635
|
+
capture_state: String,
|
|
636
|
+
reset_proof: String,
|
|
637
|
+
weak_reset_warning: Option<String>,
|
|
635
638
|
},
|
|
636
639
|
/// 未传 discard_session → 拒绝(不丢上下文的误用保护)。
|
|
637
640
|
Refused { reason: ResetRefusal },
|
|
@@ -67,6 +67,9 @@ pub(crate) fn reset_agent(
|
|
|
67
67
|
discarded_session_id,
|
|
68
68
|
session_id,
|
|
69
69
|
new_session_id,
|
|
70
|
+
capture_state,
|
|
71
|
+
reset_proof,
|
|
72
|
+
weak_reset_warning,
|
|
70
73
|
} => Ok(ToolOk {
|
|
71
74
|
fields: object_fields(serde_json::json!({
|
|
72
75
|
"ok": true,
|
|
@@ -78,6 +81,13 @@ pub(crate) fn reset_agent(
|
|
|
78
81
|
"discarded_session_id": discarded_session_id.as_ref().map(|id| id.as_str()),
|
|
79
82
|
"session_id": session_id.as_ref().map(|id| id.as_str()),
|
|
80
83
|
"new_session_id": new_session_id.as_ref().map(|id| id.as_str()),
|
|
84
|
+
"capture_state": match capture_state.as_str() {
|
|
85
|
+
"captured" => "captured",
|
|
86
|
+
"attribution_ambiguous" => "attribution_ambiguous",
|
|
87
|
+
_ => "transcript_missing",
|
|
88
|
+
},
|
|
89
|
+
"reset_proof": if reset_proof == "weak" { "weak" } else { "strong" },
|
|
90
|
+
"weak_reset_warning": weak_reset_warning,
|
|
81
91
|
})),
|
|
82
92
|
}),
|
|
83
93
|
ResetAgentOutcome::Refused { reason } => Ok(ToolOk {
|
|
@@ -204,6 +204,7 @@ fn run_stdio_loop_inner<R: BufRead, W: Write>(
|
|
|
204
204
|
report.requests_read = report.requests_read.saturating_add(1);
|
|
205
205
|
let frame = handle_stdin_line(tools, &line, report)?;
|
|
206
206
|
if let Some(value) = frame {
|
|
207
|
+
let value = crate::redaction::redact_external_value(&value);
|
|
207
208
|
serde_json::to_writer(&mut *writer, &value)?;
|
|
208
209
|
writer.write_all(b"\n")?;
|
|
209
210
|
writer.flush()?;
|
|
@@ -266,7 +267,8 @@ fn rpc_id_from_request(request: &Value) -> RpcId {
|
|
|
266
267
|
}
|
|
267
268
|
|
|
268
269
|
fn tool_call_result_value(is_error: bool, body: &Value) -> Value {
|
|
269
|
-
let
|
|
270
|
+
let body = crate::redaction::redact_external_value(body);
|
|
271
|
+
let text = json_dumps_default(&body);
|
|
270
272
|
let mut content = serde_json::Map::new();
|
|
271
273
|
content.insert("type".to_string(), Value::String("text".to_string()));
|
|
272
274
|
content.insert("text".to_string(), Value::String(text));
|