@team-agent/installer 0.5.59 → 0.5.61
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Cargo.lock +1 -1
- package/Cargo.toml +1 -1
- package/crates/team-agent/src/cli/adapters.rs +44 -1
- package/crates/team-agent/src/cli/diagnose.rs +183 -0
- package/crates/team-agent/src/cli/emit.rs +63 -14
- package/crates/team-agent/src/cli/leader.rs +8 -1
- package/crates/team-agent/src/cli/mod.rs +263 -5
- package/crates/team-agent/src/cli/named_address.rs +18 -1
- package/crates/team-agent/src/cli/send/presentation.rs +2 -0
- package/crates/team-agent/src/cli/spec.rs +4 -1
- package/crates/team-agent/src/cli/status_port/store.rs +7 -3
- package/crates/team-agent/src/cli/tests/named_address.rs +65 -0
- package/crates/team-agent/src/cli/types.rs +33 -0
- package/crates/team-agent/src/communication_mode/mod.rs +53 -0
- package/crates/team-agent/src/compiler/tests.rs +5 -5
- package/crates/team-agent/src/compiler.rs +53 -1
- package/crates/team-agent/src/coordinator/tick.rs +21 -8
- package/crates/team-agent/src/db/message_store.rs +45 -1
- package/crates/team-agent/src/fake_worker.rs +21 -1
- package/crates/team-agent/src/leader/start.rs +732 -94
- package/crates/team-agent/src/leader/tests/identity.rs +64 -57
- package/crates/team-agent/src/leader/tests/identity_session_names.rs +42 -0
- package/crates/team-agent/src/lib.rs +4 -0
- package/crates/team-agent/src/lifecycle/launch/fork_agent/completion.rs +58 -0
- package/crates/team-agent/src/lifecycle/launch/fork_agent.rs +57 -51
- package/crates/team-agent/src/lifecycle/launch/fork_finalize.rs +148 -8
- package/crates/team-agent/src/lifecycle/launch/fork_state.rs +5 -0
- package/crates/team-agent/src/lifecycle/launch/spawn.rs +1 -1
- package/crates/team-agent/src/lifecycle/launch.rs +1 -1
- package/crates/team-agent/src/lifecycle/restart/agent.rs +1 -1
- package/crates/team-agent/src/lifecycle/restart/common.rs +1 -1
- package/crates/team-agent/src/lifecycle/tests/lane_ops.rs +35 -1
- package/crates/team-agent/src/lifecycle/types.rs +1 -0
- package/crates/team-agent/src/lifecycle/worker_command_context.rs +29 -11
- package/crates/team-agent/src/mcp_server/normalize.rs +1 -0
- package/crates/team-agent/src/mcp_server/tests/send.rs +26 -0
- package/crates/team-agent/src/mcp_server/tests/wire.rs +1 -1
- package/crates/team-agent/src/mcp_server/tools.rs +100 -66
- package/crates/team-agent/src/mcp_server/types.rs +5 -0
- package/crates/team-agent/src/mcp_server/wire.rs +27 -21
- package/crates/team-agent/src/messaging/delivery.rs +105 -38
- package/crates/team-agent/src/messaging/leader_channel.rs +33 -10
- package/crates/team-agent/src/messaging/leader_receiver.rs +40 -23
- package/crates/team-agent/src/messaging/presentation.rs +109 -0
- package/crates/team-agent/src/messaging/results.rs +165 -17
- package/crates/team-agent/src/messaging/send.rs +94 -81
- package/crates/team-agent/src/messaging/tests/leader_channel.rs +6 -6
- package/crates/team-agent/src/messaging/tests/leader_inject_acceptance.rs +187 -0
- package/crates/team-agent/src/messaging/tests/runtime.rs +13 -6
- package/crates/team-agent/src/messaging/types.rs +5 -0
- package/crates/team-agent/src/messaging/watchers.rs +5 -0
- package/crates/team-agent/src/model/mod.rs +1 -0
- package/crates/team-agent/src/model/pane_authority_refusal.rs +358 -0
- package/crates/team-agent/src/model/spec.rs +16 -0
- package/crates/team-agent/src/provider/adapter.rs +57 -0
- package/crates/team-agent/src/provider/adapters/claude_fork.rs +10 -2
- package/crates/team-agent/src/provider/session/capture.rs +69 -34
- package/crates/team-agent/src/provider/session/context_fork/codex.rs +386 -9
- package/crates/team-agent/src/provider/session/context_fork/outcome.rs +3 -14
- package/crates/team-agent/src/provider/session/context_fork.rs +7 -2
- package/crates/team-agent/src/provider/session/mod.rs +3 -2
- package/crates/team-agent/src/provider/session_scan/claude.rs +92 -3
- package/crates/team-agent/src/provider/session_scan/codex.rs +28 -0
- package/crates/team-agent/src/provider/session_scan/common.rs +34 -8
- package/crates/team-agent/src/provider/session_scan.rs +8 -0
- package/crates/team-agent/src/topology.rs +3 -0
- package/package.json +4 -4
- package/skills/team-agent/command-coverage.json +379 -0
|
@@ -120,7 +120,7 @@ pub(super) fn spawn_agents(
|
|
|
120
120
|
agent,
|
|
121
121
|
Some(agent_id_raw),
|
|
122
122
|
provider,
|
|
123
|
-
)
|
|
123
|
+
)?;
|
|
124
124
|
let system_prompt =
|
|
125
125
|
crate::lifecycle::worker_command_context::compile_worker_system_prompt(&command_agent)?;
|
|
126
126
|
let tools = crate::lifecycle::worker_command_context::resolved_tool_strings_for_command(
|
|
@@ -295,8 +295,8 @@ mod fork_state;
|
|
|
295
295
|
pub(super) use fork_state::*;
|
|
296
296
|
|
|
297
297
|
mod fork_finalize;
|
|
298
|
-
pub(crate) use fork_finalize::finalize_pending_fork_capture;
|
|
299
298
|
pub(super) use fork_finalize::*;
|
|
299
|
+
pub(crate) use fork_finalize::{finalize_pending_fork_capture, ContextForkFinalized};
|
|
300
300
|
|
|
301
301
|
mod role_source;
|
|
302
302
|
pub(super) use role_source::*;
|
|
@@ -1635,7 +1635,7 @@ fn write_start_agent_start_event(
|
|
|
1635
1635
|
agent,
|
|
1636
1636
|
Some(agent_id.as_str()),
|
|
1637
1637
|
provider,
|
|
1638
|
-
)
|
|
1638
|
+
)?;
|
|
1639
1639
|
let system_prompt =
|
|
1640
1640
|
crate::lifecycle::worker_command_context::compile_worker_system_prompt(&command_agent)?;
|
|
1641
1641
|
let tools = crate::lifecycle::worker_command_context::resolved_tool_strings_for_command(
|
|
@@ -220,7 +220,7 @@ pub(super) fn spawn_agent_window(
|
|
|
220
220
|
agent,
|
|
221
221
|
Some(agent_id.as_str()),
|
|
222
222
|
provider,
|
|
223
|
-
)
|
|
223
|
+
)?;
|
|
224
224
|
let system_prompt =
|
|
225
225
|
crate::lifecycle::worker_command_context::compile_worker_system_prompt(&command_agent)?;
|
|
226
226
|
let tools = crate::lifecycle::worker_command_context::resolved_tool_strings_for_command(
|
|
@@ -1178,7 +1178,29 @@ pub(super) fn fork_ws(alpha_role: &str) -> PathBuf {
|
|
|
1178
1178
|
std::process::id(),
|
|
1179
1179
|
n
|
|
1180
1180
|
));
|
|
1181
|
-
std::fs::write(
|
|
1181
|
+
std::fs::write(
|
|
1182
|
+
&rollout,
|
|
1183
|
+
format!(
|
|
1184
|
+
"{}\n{}\n",
|
|
1185
|
+
json!({
|
|
1186
|
+
"type": "session_meta",
|
|
1187
|
+
"payload": {
|
|
1188
|
+
"id": "sess-a",
|
|
1189
|
+
"cwd": ws,
|
|
1190
|
+
}
|
|
1191
|
+
}),
|
|
1192
|
+
json!({
|
|
1193
|
+
"type": "response_item",
|
|
1194
|
+
"payload": {
|
|
1195
|
+
"content": [{
|
|
1196
|
+
"type": "input_text",
|
|
1197
|
+
"text": "You are Team Agent worker `alpha` with role `fixture`."
|
|
1198
|
+
}]
|
|
1199
|
+
}
|
|
1200
|
+
})
|
|
1201
|
+
),
|
|
1202
|
+
)
|
|
1203
|
+
.expect("seed identity-complete fork source rollout");
|
|
1182
1204
|
crate::state::persist::save_runtime_state(
|
|
1183
1205
|
&ws,
|
|
1184
1206
|
&json!({
|
|
@@ -1669,6 +1691,18 @@ fn lanea_fork_gate_error_text_and_spec_rollback_on_adapter_arm() {
|
|
|
1669
1691
|
fn lanea_fork_report_session_id_is_not_pane_id() {
|
|
1670
1692
|
let _home = LaneHomeGuard::enter("fork-report");
|
|
1671
1693
|
let ws = fork_ws(DELEG_ROLE_ALPHA); // codex+subscription -> native fork supported -> full success path
|
|
1694
|
+
let mut state = crate::state::persist::load_runtime_state(&ws).unwrap();
|
|
1695
|
+
let source = PathBuf::from(state["agents"]["alpha"]["rollout_path"].as_str().unwrap());
|
|
1696
|
+
let codex_root = std::env::var_os("HOME")
|
|
1697
|
+
.map(PathBuf::from)
|
|
1698
|
+
.unwrap()
|
|
1699
|
+
.join(".codex/sessions/lane-fixture");
|
|
1700
|
+
std::fs::create_dir_all(&codex_root).unwrap();
|
|
1701
|
+
let discoverable_source = codex_root.join("rollout-sess-a.jsonl");
|
|
1702
|
+
std::fs::copy(source, &discoverable_source).unwrap();
|
|
1703
|
+
state["agents"]["alpha"]["rollout_path"] =
|
|
1704
|
+
json!(discoverable_source.to_string_lossy().to_string());
|
|
1705
|
+
crate::state::persist::save_runtime_state(&ws, &state).unwrap();
|
|
1672
1706
|
let tx = LaneTransport::new("team-laneateam", &[]);
|
|
1673
1707
|
let report =
|
|
1674
1708
|
fork_agent_with_transport(&ws, &aid("alpha"), &aid("newfork"), None, false, None, &tx)
|
|
@@ -36,6 +36,7 @@ pub mod event_names {
|
|
|
36
36
|
pub const ADD_FAILED: &str = "lifecycle.add_failed";
|
|
37
37
|
pub const REMOVE_STEP_COMPLETED: &str = "lifecycle.remove_step_completed";
|
|
38
38
|
pub const REMOVE_ROLLED_BACK: &str = "lifecycle.remove_rolled_back";
|
|
39
|
+
pub const CONTEXT_FORK: &str = "context_fork";
|
|
39
40
|
// restart 决策事件(Route B audit 契约必发)。
|
|
40
41
|
pub const RESTART_RESUME_DECISION: &str = "restart.resume_decision";
|
|
41
42
|
pub const RESTART_ATOMIC_REFUSAL: &str = "restart.atomic_refusal";
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
use std::path::Path;
|
|
2
2
|
|
|
3
|
+
use crate::communication_mode::CommunicationMode;
|
|
3
4
|
use crate::lifecycle::types::{DangerousApproval, LifecycleError};
|
|
4
5
|
use crate::model::enums::{Enforcement, Provider};
|
|
5
6
|
use crate::model::ids::AgentId;
|
|
@@ -12,15 +13,10 @@ output. All communication must go through Team Agent MCP tools.
|
|
|
12
13
|
|
|
13
14
|
## Communication (mandatory)
|
|
14
15
|
|
|
15
|
-
- Progress, blockers, questions: team_orchestrator.send_message(to='leader', content='...')
|
|
16
16
|
- Coordinate with teammate: team_orchestrator.send_message(to='<agent_id>', content='...')
|
|
17
17
|
- Broadcast to all teammates: team_orchestrator.send_message(to='*', content='...')
|
|
18
18
|
- Task complete: team_orchestrator.report_result(summary='...') — call exactly once
|
|
19
19
|
|
|
20
|
-
When you receive a message from the leader or a teammate, you MUST respond
|
|
21
|
-
through MCP tools. Writing a reply in your terminal does nothing — the sender
|
|
22
|
-
will never see it.
|
|
23
|
-
|
|
24
20
|
## Rules
|
|
25
21
|
|
|
26
22
|
- Do not pass sender, task_id, or schema_version — the MCP runtime fills them.
|
|
@@ -45,6 +41,7 @@ pub(crate) struct WorkerCommandAgent {
|
|
|
45
41
|
system_prompt_inline: Option<String>,
|
|
46
42
|
system_prompt_file: Option<String>,
|
|
47
43
|
output_contract_format: Option<String>,
|
|
44
|
+
communication_mode: CommunicationMode,
|
|
48
45
|
}
|
|
49
46
|
|
|
50
47
|
impl WorkerCommandAgent {
|
|
@@ -52,9 +49,9 @@ impl WorkerCommandAgent {
|
|
|
52
49
|
agent: &crate::model::yaml::Value,
|
|
53
50
|
fallback_id: Option<&str>,
|
|
54
51
|
provider: Provider,
|
|
55
|
-
) -> Self {
|
|
52
|
+
) -> Result<Self, LifecycleError> {
|
|
56
53
|
let system_prompt = agent.get("system_prompt");
|
|
57
|
-
Self {
|
|
54
|
+
Ok(Self {
|
|
58
55
|
id: agent
|
|
59
56
|
.get("id")
|
|
60
57
|
.and_then(crate::model::yaml::Value::as_str)
|
|
@@ -90,16 +87,21 @@ impl WorkerCommandAgent {
|
|
|
90
87
|
.and_then(|contract| contract.get("format"))
|
|
91
88
|
.and_then(crate::model::yaml::Value::as_str)
|
|
92
89
|
.map(str::to_string),
|
|
93
|
-
|
|
90
|
+
communication_mode: communication_mode(
|
|
91
|
+
agent
|
|
92
|
+
.get("communication_mode")
|
|
93
|
+
.and_then(crate::model::yaml::Value::as_str),
|
|
94
|
+
)?,
|
|
95
|
+
})
|
|
94
96
|
}
|
|
95
97
|
|
|
96
98
|
pub(crate) fn from_json(
|
|
97
99
|
agent: &serde_json::Value,
|
|
98
100
|
fallback_id: Option<&str>,
|
|
99
101
|
provider: Provider,
|
|
100
|
-
) -> Self {
|
|
102
|
+
) -> Result<Self, LifecycleError> {
|
|
101
103
|
let system_prompt = agent.get("system_prompt");
|
|
102
|
-
Self {
|
|
104
|
+
Ok(Self {
|
|
103
105
|
id: agent
|
|
104
106
|
.get("id")
|
|
105
107
|
.and_then(serde_json::Value::as_str)
|
|
@@ -135,7 +137,12 @@ impl WorkerCommandAgent {
|
|
|
135
137
|
.and_then(|contract| contract.get("format"))
|
|
136
138
|
.and_then(serde_json::Value::as_str)
|
|
137
139
|
.map(str::to_string),
|
|
138
|
-
|
|
140
|
+
communication_mode: communication_mode(
|
|
141
|
+
agent
|
|
142
|
+
.get("communication_mode")
|
|
143
|
+
.and_then(serde_json::Value::as_str),
|
|
144
|
+
)?,
|
|
145
|
+
})
|
|
139
146
|
}
|
|
140
147
|
}
|
|
141
148
|
|
|
@@ -149,6 +156,7 @@ pub(crate) fn compile_worker_system_prompt(
|
|
|
149
156
|
let mut chunks = vec![
|
|
150
157
|
identity_section(agent),
|
|
151
158
|
runtime_contract_section(),
|
|
159
|
+
agent.communication_mode.runtime_contract().to_string(),
|
|
152
160
|
role_body(agent)?,
|
|
153
161
|
];
|
|
154
162
|
if let Some(contract) = output_contract(agent) {
|
|
@@ -197,6 +205,14 @@ fn runtime_contract_section() -> String {
|
|
|
197
205
|
RUNTIME_CONTRACT_SECTION.to_string()
|
|
198
206
|
}
|
|
199
207
|
|
|
208
|
+
fn communication_mode(value: Option<&str>) -> Result<CommunicationMode, LifecycleError> {
|
|
209
|
+
let Some(value) = value else {
|
|
210
|
+
return Ok(CommunicationMode::default());
|
|
211
|
+
};
|
|
212
|
+
CommunicationMode::parse(value)
|
|
213
|
+
.ok_or_else(|| LifecycleError::Compile(format!("unknown communication_mode {value:?}")))
|
|
214
|
+
}
|
|
215
|
+
|
|
200
216
|
fn identity_section(agent: &WorkerCommandAgent) -> String {
|
|
201
217
|
format!(
|
|
202
218
|
"You are Team Agent worker `{}` with role `{}`. When asked about your role or identity, answer with this Team Agent worker identity first, not only the generic provider product identity.",
|
|
@@ -291,6 +307,7 @@ mod tests {
|
|
|
291
307
|
system_prompt_inline: None,
|
|
292
308
|
system_prompt_file: None,
|
|
293
309
|
output_contract_format: None,
|
|
310
|
+
communication_mode: CommunicationMode::default(),
|
|
294
311
|
};
|
|
295
312
|
let tools =
|
|
296
313
|
resolved_tool_strings_for_command(&agent, Provider::ClaudeCode, &disabled_safety())
|
|
@@ -335,6 +352,7 @@ mod tests {
|
|
|
335
352
|
system_prompt_inline: Some("Implement the assigned slice.".to_string()),
|
|
336
353
|
system_prompt_file: None,
|
|
337
354
|
output_contract_format: Some("result_envelope_v1".to_string()),
|
|
355
|
+
communication_mode: CommunicationMode::default(),
|
|
338
356
|
};
|
|
339
357
|
let prompt = compile_worker_system_prompt(&agent).unwrap();
|
|
340
358
|
assert!(
|
|
@@ -78,6 +78,7 @@ fn send_outcome_worker_accepted_envelope_byte_stable() {
|
|
|
78
78
|
let outcome = SendOutcome::WorkerAccepted {
|
|
79
79
|
message_id: "42".to_string(),
|
|
80
80
|
poll_via: "team-agent inbox 42".to_string(),
|
|
81
|
+
warning: None,
|
|
81
82
|
};
|
|
82
83
|
let v = outcome.to_value();
|
|
83
84
|
assert_eq!(
|
|
@@ -160,6 +161,7 @@ fn send_message_worker_recipient_returns_accepted_with_poll_hint() {
|
|
|
160
161
|
Ok(SendOutcome::WorkerAccepted {
|
|
161
162
|
message_id,
|
|
162
163
|
poll_via,
|
|
164
|
+
..
|
|
163
165
|
}) => {
|
|
164
166
|
assert!(!message_id.is_empty());
|
|
165
167
|
assert_eq!(poll_via, format!("team-agent inbox {message_id}"));
|
|
@@ -353,6 +355,30 @@ fn send_message_leader_recipient_is_direct_not_accepted() {
|
|
|
353
355
|
);
|
|
354
356
|
}
|
|
355
357
|
|
|
358
|
+
#[test]
|
|
359
|
+
fn send_message_mailbox_is_durable_without_live_injection() {
|
|
360
|
+
let tools = TeamOrchestratorTools::with_identity(
|
|
361
|
+
&unique_ws("send-leader-mailbox"),
|
|
362
|
+
Some(AgentId::new("worker-1")),
|
|
363
|
+
Some(TeamKey::new("teamA")),
|
|
364
|
+
);
|
|
365
|
+
let mailbox = json!(true);
|
|
366
|
+
let outcome = tools
|
|
367
|
+
.send_message_with_presentation(
|
|
368
|
+
&MessageTarget::Single("leader".to_string()),
|
|
369
|
+
"stored update",
|
|
370
|
+
None,
|
|
371
|
+
None,
|
|
372
|
+
None,
|
|
373
|
+
Some(&mailbox),
|
|
374
|
+
None,
|
|
375
|
+
)
|
|
376
|
+
.expect("mailbox send persists");
|
|
377
|
+
let value = outcome.to_value();
|
|
378
|
+
assert_eq!(value.get("status"), Some(&json!("stored_only")));
|
|
379
|
+
assert!(value.get("message_id").and_then(Value::as_str).is_some());
|
|
380
|
+
}
|
|
381
|
+
|
|
356
382
|
// ════════════════════════════════════════════════════════════════════════
|
|
357
383
|
// CROSS-TEAM PRE-REFUSAL (C23) — refuse_cross_team_peer (tools.py:185-213)
|
|
358
384
|
// ════════════════════════════════════════════════════════════════════════
|
|
@@ -79,7 +79,7 @@ fn tools_contract_has_thirteen_tools_in_order() {
|
|
|
79
79
|
.unwrap();
|
|
80
80
|
assert_eq!(
|
|
81
81
|
send["description"],
|
|
82
|
-
json!("Send a message to a teammate, the leader, or '*' for all other team members.
|
|
82
|
+
json!("Send a message to a teammate, the leader, or '*' for all other team members. mailbox=true stores durably without live injection; the default is live delivery.")
|
|
83
83
|
);
|
|
84
84
|
assert_eq!(send["inputSchema"]["additionalProperties"], json!(false));
|
|
85
85
|
assert_eq!(send["inputSchema"]["required"], json!(["to", "content"]));
|
|
@@ -17,17 +17,17 @@ use crate::state::persist::{
|
|
|
17
17
|
};
|
|
18
18
|
|
|
19
19
|
// ── REUSE: step 11 messaging delegate surface ───────────────────────────────
|
|
20
|
-
use crate::messaging::{self, MessageTarget, SendOptions, TrustedSender};
|
|
20
|
+
use crate::messaging::{self, DeliveryStatus, MessageTarget, SendOptions, TrustedSender};
|
|
21
21
|
|
|
22
22
|
use super::helpers::{
|
|
23
23
|
current_reportable_message_for, delivery_outcome_value, direct_message_attribution_for,
|
|
24
|
-
ensure_object, enum_value,
|
|
25
|
-
|
|
26
|
-
|
|
24
|
+
ensure_object, enum_value, is_worker_recipient, json_dumps_default, latest_task_for_assignee,
|
|
25
|
+
non_empty_string, object_fields, requires_ack_for_target, tool_runtime_error,
|
|
26
|
+
DirectMessageAttribution,
|
|
27
27
|
};
|
|
28
28
|
use super::normalize::{
|
|
29
|
-
compact_tool_result, normalize_report_envelope,
|
|
30
|
-
|
|
29
|
+
compact_tool_result, normalize_report_envelope, report_result_integrity_warnings,
|
|
30
|
+
validate_test_evidence_schema,
|
|
31
31
|
};
|
|
32
32
|
use super::types::{
|
|
33
33
|
Scope, SendOutcome, ToolError, ToolErrorReason, ToolOk, ToolResult, VisiblePeers,
|
|
@@ -116,6 +116,22 @@ impl TeamOrchestratorTools {
|
|
|
116
116
|
"ValueError",
|
|
117
117
|
));
|
|
118
118
|
};
|
|
119
|
+
if let Some(route) = task.get("result_route") {
|
|
120
|
+
let Some(route) = route.as_str() else {
|
|
121
|
+
return Err(ToolError::new(
|
|
122
|
+
ToolErrorReason::InvalidToolArguments,
|
|
123
|
+
"assign_task task.result_route must be 'leader' or 'pipeline'",
|
|
124
|
+
"ValueError",
|
|
125
|
+
));
|
|
126
|
+
};
|
|
127
|
+
if crate::messaging::results::ResultRoute::parse(route).is_none() {
|
|
128
|
+
return Err(ToolError::new(
|
|
129
|
+
ToolErrorReason::InvalidToolArguments,
|
|
130
|
+
format!("assign_task task.result_route has unknown value: {route}"),
|
|
131
|
+
"ValueError",
|
|
132
|
+
));
|
|
133
|
+
}
|
|
134
|
+
}
|
|
119
135
|
|
|
120
136
|
let task_value = Value::Object(task_obj.clone());
|
|
121
137
|
let recovery = task_recovery_marker(&task_value);
|
|
@@ -185,6 +201,7 @@ impl TeamOrchestratorTools {
|
|
|
185
201
|
requires_ack,
|
|
186
202
|
scope_override,
|
|
187
203
|
None,
|
|
204
|
+
None,
|
|
188
205
|
)
|
|
189
206
|
}
|
|
190
207
|
|
|
@@ -195,17 +212,20 @@ impl TeamOrchestratorTools {
|
|
|
195
212
|
task_id: Option<&str>,
|
|
196
213
|
requires_ack: Option<bool>,
|
|
197
214
|
scope_override: Option<Scope>,
|
|
215
|
+
mailbox: Option<&Value>,
|
|
198
216
|
presentation: Option<&Value>,
|
|
199
217
|
) -> Result<SendOutcome, ToolError> {
|
|
200
|
-
let
|
|
201
|
-
crate::messaging::presentation::
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
218
|
+
let normalized =
|
|
219
|
+
crate::messaging::presentation::normalize_send_presentation(mailbox, presentation)
|
|
220
|
+
.map_err(|error| {
|
|
221
|
+
ToolError::new(
|
|
222
|
+
ToolErrorReason::InvalidToolArguments,
|
|
223
|
+
format!("invalid send routing: {error}"),
|
|
224
|
+
"PresentationError",
|
|
225
|
+
)
|
|
226
|
+
})?;
|
|
227
|
+
let presentation = normalized.request;
|
|
228
|
+
let deprecation = normalized.deprecation;
|
|
209
229
|
let canonical_owner_team = self.canonical_owner_team_key()?;
|
|
210
230
|
if matches!(scope_override, Some(Scope::Workspace)) {
|
|
211
231
|
return Err(self.rpc_scope_refused(
|
|
@@ -261,6 +281,21 @@ impl TeamOrchestratorTools {
|
|
|
261
281
|
if is_worker_recipient(to) {
|
|
262
282
|
let out = messaging::send_message(&self.workspace, to, content, &opts)
|
|
263
283
|
.map_err(tool_runtime_error)?;
|
|
284
|
+
if matches!(out.status, DeliveryStatus::StoredOnly) {
|
|
285
|
+
let value = delivery_outcome_value(&out);
|
|
286
|
+
let mut ok = compact_tool_result(&value)?;
|
|
287
|
+
if let Some(verification) = out.verification.as_deref() {
|
|
288
|
+
ok.fields.insert(
|
|
289
|
+
"verification".to_string(),
|
|
290
|
+
Value::String(verification.to_string()),
|
|
291
|
+
);
|
|
292
|
+
}
|
|
293
|
+
if let Some(deprecation) = deprecation {
|
|
294
|
+
ok.fields
|
|
295
|
+
.insert("warning".to_string(), Value::String(deprecation));
|
|
296
|
+
}
|
|
297
|
+
return Ok(SendOutcome::Direct(ok));
|
|
298
|
+
}
|
|
264
299
|
// tools.py:175-181 — accepted+poll_via ONLY for a REAL message_id; any other
|
|
265
300
|
// outcome falls back to the compacted direct result. Never invent an
|
|
266
301
|
// `mcp_<timestamp>` id: it does not exist in the store and makes the
|
|
@@ -276,12 +311,25 @@ impl TeamOrchestratorTools {
|
|
|
276
311
|
return Ok(SendOutcome::WorkerAccepted {
|
|
277
312
|
poll_via: format!("team-agent inbox {message_id}"),
|
|
278
313
|
message_id,
|
|
314
|
+
warning: deprecation,
|
|
279
315
|
});
|
|
280
316
|
}
|
|
281
317
|
let out = messaging::send_message(&self.workspace, to, content, &opts)
|
|
282
318
|
.map_err(tool_runtime_error)?;
|
|
283
319
|
let value = delivery_outcome_value(&out);
|
|
284
|
-
let ok = compact_tool_result(&value)?;
|
|
320
|
+
let mut ok = compact_tool_result(&value)?;
|
|
321
|
+
if matches!(out.status, DeliveryStatus::StoredOnly) {
|
|
322
|
+
if let Some(verification) = out.verification.as_deref() {
|
|
323
|
+
ok.fields.insert(
|
|
324
|
+
"verification".to_string(),
|
|
325
|
+
Value::String(verification.to_string()),
|
|
326
|
+
);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
if let Some(deprecation) = deprecation {
|
|
330
|
+
ok.fields
|
|
331
|
+
.insert("warning".to_string(), Value::String(deprecation));
|
|
332
|
+
}
|
|
285
333
|
Ok(SendOutcome::Direct(ok))
|
|
286
334
|
}
|
|
287
335
|
|
|
@@ -352,10 +400,11 @@ impl TeamOrchestratorTools {
|
|
|
352
400
|
task_id: Option<&str>,
|
|
353
401
|
agent_id: Option<&str>,
|
|
354
402
|
) -> ToolResult {
|
|
403
|
+
let status = enum_value(status);
|
|
355
404
|
self.report_result_with_presentation(
|
|
356
405
|
envelope,
|
|
357
406
|
summary,
|
|
358
|
-
status,
|
|
407
|
+
status.as_str(),
|
|
359
408
|
changes,
|
|
360
409
|
tests,
|
|
361
410
|
risks,
|
|
@@ -372,7 +421,7 @@ impl TeamOrchestratorTools {
|
|
|
372
421
|
&self,
|
|
373
422
|
envelope: Option<&Value>,
|
|
374
423
|
summary: Option<&str>,
|
|
375
|
-
status:
|
|
424
|
+
status: Option<&str>,
|
|
376
425
|
changes: Option<&[Value]>,
|
|
377
426
|
tests: Option<&[Value]>,
|
|
378
427
|
risks: Option<&[Value]>,
|
|
@@ -404,7 +453,10 @@ impl TeamOrchestratorTools {
|
|
|
404
453
|
);
|
|
405
454
|
}
|
|
406
455
|
if !obj.contains_key("status") {
|
|
407
|
-
obj.insert(
|
|
456
|
+
obj.insert(
|
|
457
|
+
"status".to_string(),
|
|
458
|
+
Value::String(status.unwrap_or("success").to_string()),
|
|
459
|
+
);
|
|
408
460
|
}
|
|
409
461
|
if !obj.contains_key("task_id") {
|
|
410
462
|
// Blocker-1 (prerelease 0.4.0): scoped-team task inference +
|
|
@@ -499,29 +551,36 @@ impl TeamOrchestratorTools {
|
|
|
499
551
|
obj.insert("agent_id".to_string(), Value::String(resolved));
|
|
500
552
|
}
|
|
501
553
|
if !obj.contains_key("changes") {
|
|
502
|
-
|
|
554
|
+
obj.insert(
|
|
555
|
+
"changes".to_string(),
|
|
556
|
+
Value::Array(changes.unwrap_or(&[]).to_vec()),
|
|
557
|
+
);
|
|
503
558
|
}
|
|
504
559
|
if !obj.contains_key("tests") {
|
|
505
|
-
|
|
560
|
+
obj.insert(
|
|
561
|
+
"tests".to_string(),
|
|
562
|
+
Value::Array(tests.unwrap_or(&[]).to_vec()),
|
|
563
|
+
);
|
|
506
564
|
}
|
|
507
565
|
if !obj.contains_key("risks") {
|
|
508
|
-
|
|
566
|
+
obj.insert(
|
|
567
|
+
"risks".to_string(),
|
|
568
|
+
Value::Array(risks.unwrap_or(&[]).to_vec()),
|
|
569
|
+
);
|
|
509
570
|
}
|
|
510
571
|
if !obj.contains_key("artifacts") {
|
|
511
|
-
|
|
572
|
+
obj.insert(
|
|
573
|
+
"artifacts".to_string(),
|
|
574
|
+
Value::Array(artifacts.unwrap_or(&[]).to_vec()),
|
|
575
|
+
);
|
|
512
576
|
}
|
|
513
577
|
if !obj.contains_key("next_actions") {
|
|
514
|
-
|
|
578
|
+
obj.insert(
|
|
579
|
+
"next_actions".to_string(),
|
|
580
|
+
Value::Array(next_actions.unwrap_or(&[]).to_vec()),
|
|
581
|
+
);
|
|
515
582
|
}
|
|
516
583
|
}
|
|
517
|
-
// T3-1 cr verdict (refined): an unknown non-empty status literal normalizes to
|
|
518
|
-
// Partial and must be OBSERVABLE at this ingestion boundary (the envelope-borne
|
|
519
|
-
// path; the wire `status` arg path emits at dispatch). Never a silent swallow.
|
|
520
|
-
if let Some(raw) =
|
|
521
|
-
normalize_result_status_observed(base.get("status").and_then(Value::as_str)).1
|
|
522
|
-
{
|
|
523
|
-
self.note_unknown_result_status(&raw);
|
|
524
|
-
}
|
|
525
584
|
if let Err(error) = validate_test_evidence_schema(base.get("tests")) {
|
|
526
585
|
return Err(ToolError::new(
|
|
527
586
|
ToolErrorReason::InvalidToolArguments,
|
|
@@ -538,8 +597,15 @@ impl TeamOrchestratorTools {
|
|
|
538
597
|
));
|
|
539
598
|
}
|
|
540
599
|
let warnings = report_result_integrity_warnings(&base, &normalized);
|
|
541
|
-
let mut env_value =
|
|
542
|
-
|
|
600
|
+
let mut env_value = base;
|
|
601
|
+
if let Some(obj) = env_value.as_object_mut() {
|
|
602
|
+
obj.entry("schema_version")
|
|
603
|
+
.or_insert_with(|| Value::String("result_envelope_v1".to_string()));
|
|
604
|
+
obj.insert(
|
|
605
|
+
"presentation".to_string(),
|
|
606
|
+
serde_json::to_value(&normalized.presentation).unwrap_or(Value::Null),
|
|
607
|
+
);
|
|
608
|
+
}
|
|
543
609
|
if !warnings.is_empty() {
|
|
544
610
|
if let Some(obj) = env_value.as_object_mut() {
|
|
545
611
|
obj.insert("warnings".to_string(), Value::Array(warnings));
|
|
@@ -555,20 +621,6 @@ impl TeamOrchestratorTools {
|
|
|
555
621
|
.and_then(|value| compact_tool_result(&value))
|
|
556
622
|
}
|
|
557
623
|
|
|
558
|
-
/// T3-1 cr verdict: the observable record of an unknown→Partial status
|
|
559
|
-
/// normalization (`provider.result.unknown_status_normalized`, raw literal
|
|
560
|
-
/// included) — MUST-NOT-13: the swallow is never silent.
|
|
561
|
-
pub(crate) fn note_unknown_result_status(&self, raw: &str) {
|
|
562
|
-
let _ = EventLog::new(&self.workspace).write(
|
|
563
|
-
"provider.result.unknown_status_normalized",
|
|
564
|
-
serde_json::json!({
|
|
565
|
-
"agent_id": self.agent_id.as_ref().map(AgentId::as_str),
|
|
566
|
-
"raw_status": raw,
|
|
567
|
-
"normalized": "partial",
|
|
568
|
-
}),
|
|
569
|
-
);
|
|
570
|
-
}
|
|
571
|
-
|
|
572
624
|
/// `update_state` (`tools.py:316-325`): delegated through the lifecycle tools
|
|
573
625
|
/// facade. S0 preserves the old placeholder behavior.
|
|
574
626
|
pub fn update_state(&self, note: &str) -> ToolResult {
|
|
@@ -1179,24 +1231,6 @@ fn merge_object_fields(existing: &mut Value, incoming: &Value) {
|
|
|
1179
1231
|
}
|
|
1180
1232
|
}
|
|
1181
1233
|
|
|
1182
|
-
fn copy_report_attribution_fields(source: &Value, target: &mut Value) {
|
|
1183
|
-
let Some(src) = source.as_object() else {
|
|
1184
|
-
return;
|
|
1185
|
-
};
|
|
1186
|
-
let Some(dst) = target.as_object_mut() else {
|
|
1187
|
-
return;
|
|
1188
|
-
};
|
|
1189
|
-
for key in [
|
|
1190
|
-
"attributed_message_id",
|
|
1191
|
-
"attribution_scope",
|
|
1192
|
-
"task_id_source",
|
|
1193
|
-
] {
|
|
1194
|
-
if let Some(value) = src.get(key) {
|
|
1195
|
-
dst.entry(key.to_string()).or_insert(value.clone());
|
|
1196
|
-
}
|
|
1197
|
-
}
|
|
1198
|
-
}
|
|
1199
|
-
|
|
1200
1234
|
fn push_report_warning(obj: &mut serde_json::Map<String, Value>, warning: Value) {
|
|
1201
1235
|
let Some(code) = warning.get("code").and_then(Value::as_str) else {
|
|
1202
1236
|
return;
|
|
@@ -322,6 +322,7 @@ pub enum SendOutcome {
|
|
|
322
322
|
message_id: String,
|
|
323
323
|
/// Byte-stable `"team-agent inbox <message_id>"`.
|
|
324
324
|
poll_via: String,
|
|
325
|
+
warning: Option<String>,
|
|
325
326
|
},
|
|
326
327
|
/// Compacted delegate result (leader / `*` / broadcast / fanout).
|
|
327
328
|
Direct(ToolOk),
|
|
@@ -335,12 +336,16 @@ impl SendOutcome {
|
|
|
335
336
|
SendOutcome::WorkerAccepted {
|
|
336
337
|
message_id,
|
|
337
338
|
poll_via,
|
|
339
|
+
warning,
|
|
338
340
|
} => {
|
|
339
341
|
let mut obj = serde_json::Map::new();
|
|
340
342
|
obj.insert("status".to_string(), Value::String("accepted".to_string()));
|
|
341
343
|
obj.insert("delivery_pending".to_string(), Value::Bool(true));
|
|
342
344
|
obj.insert("poll_via".to_string(), Value::String(poll_via.clone()));
|
|
343
345
|
obj.insert("message_id".to_string(), Value::String(message_id.clone()));
|
|
346
|
+
if let Some(warning) = warning {
|
|
347
|
+
obj.insert("warning".to_string(), Value::String(warning.clone()));
|
|
348
|
+
}
|
|
344
349
|
Value::Object(obj)
|
|
345
350
|
}
|
|
346
351
|
SendOutcome::Direct(ok) => Value::Object(ok.fields.clone()),
|