@team-agent/installer 0.5.81 → 0.5.83
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 +155 -5
- package/crates/team-agent/src/cli/leaders.rs +304 -167
- package/crates/team-agent/src/cli/spec.rs +1 -1
- package/crates/team-agent/src/cli/types.rs +11 -0
- package/crates/team-agent/src/communication_mode/mod.rs +3 -3
- package/crates/team-agent/src/leader/registry.rs +295 -64
- package/crates/team-agent/src/lifecycle/launch/pi_mcp.rs +0 -2
- package/crates/team-agent/src/lifecycle/tests/communication_mode_runtime_contract_red.rs +15 -4
- package/crates/team-agent/src/lifecycle/tests/pi_catalog_red.rs +0 -1
- package/crates/team-agent/src/lifecycle/tests/pi_command_red.rs +3 -4
- package/crates/team-agent/src/lifecycle/tests/pi_compiler_red.rs +0 -1
- package/crates/team-agent/src/lifecycle/tests/pi_executable_mcp_red.rs +5 -0
- package/crates/team-agent/src/lifecycle/tests/pi_session_delivery_red.rs +0 -1
- package/crates/team-agent/src/lifecycle/worker_command_context.rs +47 -13
- package/crates/team-agent/src/provider/adapters/pi.rs +6 -6
- package/crates/team-agent/src/state/persist.rs +50 -1
- package/package.json +4 -4
|
@@ -28,20 +28,20 @@ use crate::model::permissions::{resolve_permissions, AgentPermissionInput};
|
|
|
28
28
|
|
|
29
29
|
const RUNTIME_CONTRACT_SECTION: &str = r#"# Team Agent Teammate Runtime Contract
|
|
30
30
|
|
|
31
|
-
You are a
|
|
32
|
-
|
|
31
|
+
You are a Team Agent teammate; leader cannot see terminal output.
|
|
32
|
+
All communication must go through Team Agent MCP tools.
|
|
33
33
|
|
|
34
|
-
## Communication
|
|
34
|
+
## Communication:
|
|
35
35
|
|
|
36
|
-
-
|
|
37
|
-
- Broadcast
|
|
38
|
-
-
|
|
36
|
+
- Teammate: {send_message}(to='<agent_id>', content='...')
|
|
37
|
+
- Broadcast: {send_message}(to='*', content='...')
|
|
38
|
+
- Complete: {report_result}(summary='...') — call exactly once
|
|
39
39
|
|
|
40
|
-
## Rules
|
|
40
|
+
## Rules:
|
|
41
41
|
|
|
42
|
-
- Do not pass sender, task_id, or schema_version —
|
|
43
|
-
-
|
|
44
|
-
- On 500/529/rate
|
|
42
|
+
- Do not pass sender, task_id, or schema_version — MCP fills them.
|
|
43
|
+
- Do not reply to pure ACKs, greetings, or unchanged status notices (such as "paused" or "waiting"); after reporting a blocker once or completing a task, remain silent until a new actionable instruction arrives.
|
|
44
|
+
- On 500/529/rate limits, retry only after 1-2 minutes."#;
|
|
45
45
|
|
|
46
46
|
// 0.4.11 trimmed: the runtime contract section above already covers
|
|
47
47
|
// send_message signatures and report_result exactly-once. The output
|
|
@@ -363,9 +363,7 @@ fn pi_communication_contract(mode: CommunicationMode) -> String {
|
|
|
363
363
|
|
|
364
364
|
- Progress, blockers, questions: mcp({tool:"team_orchestrator_send_message", args:{to:"leader", content:"..."}})
|
|
365
365
|
|
|
366
|
-
|
|
367
|
-
through MCP tools. Writing a reply in your terminal does nothing — the sender
|
|
368
|
-
will never see it."#
|
|
366
|
+
Respond through Team Agent MCP tools only to actionable requests or questions; writing in your terminal does not deliver it."#
|
|
369
367
|
.to_string(),
|
|
370
368
|
CommunicationMode::Orchestrated => mode.runtime_contract("mcp"),
|
|
371
369
|
}
|
|
@@ -598,4 +596,40 @@ mod tests {
|
|
|
598
596
|
assert!(prompt.contains(&slowdown_phrase));
|
|
599
597
|
assert!(prompt.contains("Runtime Developer"));
|
|
600
598
|
}
|
|
599
|
+
|
|
600
|
+
#[test]
|
|
601
|
+
fn all_provider_mode_prompts_share_silence_contract_without_unconditional_replies() {
|
|
602
|
+
for provider in [Provider::Codex, Provider::Pi] {
|
|
603
|
+
for mode in CommunicationMode::ALL.iter().copied() {
|
|
604
|
+
let agent = WorkerCommandAgent {
|
|
605
|
+
id: Some("worker".to_string()),
|
|
606
|
+
provider,
|
|
607
|
+
role: Some("developer".to_string()),
|
|
608
|
+
declared_tools: Some(vec!["mcp_team".to_string()]),
|
|
609
|
+
system_prompt_inline: Some("worker body".to_string()),
|
|
610
|
+
system_prompt_file: None,
|
|
611
|
+
output_contract_format: Some("result_envelope_v1".to_string()),
|
|
612
|
+
communication_mode: mode,
|
|
613
|
+
dangerously_skip_permissions: false,
|
|
614
|
+
};
|
|
615
|
+
let prompt = compile_worker_system_prompt(&agent).unwrap();
|
|
616
|
+
assert!(prompt.contains(
|
|
617
|
+
"Do not reply to pure ACKs, greetings, or unchanged status notices (such as \"paused\" or \"waiting\"); after reporting a blocker once or completing a task, remain silent until a new actionable instruction arrives."
|
|
618
|
+
));
|
|
619
|
+
assert!(!prompt.contains("Silence and resumption (mandatory)"));
|
|
620
|
+
assert!(!prompt.contains("When you receive a message from the leader or a teammate, you MUST respond"));
|
|
621
|
+
match mode {
|
|
622
|
+
CommunicationMode::LeaderCentric => {
|
|
623
|
+
assert!(prompt.contains(
|
|
624
|
+
"Respond through Team Agent MCP tools only to actionable requests or questions; writing in your terminal does not deliver it."
|
|
625
|
+
));
|
|
626
|
+
}
|
|
627
|
+
CommunicationMode::Orchestrated => {
|
|
628
|
+
assert!(prompt.contains("Respond to task-related messages through Team Agent MCP tools."));
|
|
629
|
+
assert!(prompt.contains("A pure ACK, unrelated status, or non-task message does not require a response."));
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
}
|
|
601
635
|
}
|
|
@@ -58,7 +58,6 @@ pub(crate) enum PiSessionSelector<'a> {
|
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
pub(crate) struct PiCommandRequest<'a> {
|
|
61
|
-
pub executable: &'a Path,
|
|
62
61
|
pub extension: &'a Path,
|
|
63
62
|
pub model: Option<&'a str>,
|
|
64
63
|
pub effort: Option<ProviderEffort>,
|
|
@@ -72,14 +71,13 @@ pub(crate) struct PiCommandRequest<'a> {
|
|
|
72
71
|
|
|
73
72
|
/// ---
|
|
74
73
|
/// purpose: 从已验证 semantic request 构造仅追加 Team Agent MCP/提示/session 的 Pi argv
|
|
75
|
-
/// returns: exact ordered fresh/resume argv
|
|
74
|
+
/// returns: exact ordered fresh/resume argv,argv[0] 固定为裸 `pi`
|
|
76
75
|
/// errors: 必需字段、mcp_team 或工具 category 非法时返回 ProviderError
|
|
77
76
|
/// ---
|
|
78
77
|
pub(crate) fn build_pi_command_argv(
|
|
79
78
|
request: PiCommandRequest<'_>,
|
|
80
79
|
) -> Result<Vec<String>, ProviderError> {
|
|
81
|
-
if request.
|
|
82
|
-
|| request.extension.as_os_str().is_empty()
|
|
80
|
+
if request.extension.as_os_str().is_empty()
|
|
83
81
|
|| request
|
|
84
82
|
.session_dir
|
|
85
83
|
.is_some_and(|session_dir| session_dir.as_os_str().is_empty())
|
|
@@ -88,7 +86,7 @@ pub(crate) fn build_pi_command_argv(
|
|
|
88
86
|
|| request.agent_id.trim().is_empty()
|
|
89
87
|
{
|
|
90
88
|
return Err(ProviderError::Command(
|
|
91
|
-
"Pi command requires
|
|
89
|
+
"Pi command requires extension, prompt, and agent id".to_string(),
|
|
92
90
|
));
|
|
93
91
|
}
|
|
94
92
|
|
|
@@ -110,8 +108,10 @@ pub(crate) fn build_pi_command_argv(
|
|
|
110
108
|
));
|
|
111
109
|
}
|
|
112
110
|
|
|
111
|
+
// Executable discovery/preflight belongs to the lifecycle materializer;
|
|
112
|
+
// the actual command must remain PATH-resolved at spawn time.
|
|
113
113
|
let mut argv = vec![
|
|
114
|
-
|
|
114
|
+
"pi".to_string(),
|
|
115
115
|
"-e".to_string(),
|
|
116
116
|
request.extension.to_string_lossy().into_owned(),
|
|
117
117
|
];
|
|
@@ -1706,12 +1706,35 @@ pub(crate) fn load_runtime_state_without_migrations(workspace: &Path) -> Result<
|
|
|
1706
1706
|
Ok(state)
|
|
1707
1707
|
}
|
|
1708
1708
|
|
|
1709
|
+
/// Run a read-only callback while holding the canonical `state-save` lock.
|
|
1710
|
+
/// Unlike `load_runtime_state`, this helper performs no migration, cache
|
|
1711
|
+
/// update, or write. A missing state file is represented as `None` so callers
|
|
1712
|
+
/// can conservatively retain derived indexes during workspace unavailability.
|
|
1713
|
+
pub(crate) fn with_runtime_state_lock_without_migrations<T>(
|
|
1714
|
+
workspace: &Path,
|
|
1715
|
+
callback: impl FnOnce(Option<Value>) -> T,
|
|
1716
|
+
) -> Result<T, StateError> {
|
|
1717
|
+
let path = runtime_state_path(workspace);
|
|
1718
|
+
if !path.exists() {
|
|
1719
|
+
return Ok(callback(None));
|
|
1720
|
+
}
|
|
1721
|
+
let _lock = RuntimeLock::acquire(workspace, "state-save", 2.0)?;
|
|
1722
|
+
let state = match std::fs::read_to_string(&path) {
|
|
1723
|
+
Ok(text) => Some(serde_json::from_str::<Value>(&text)?),
|
|
1724
|
+
Err(error) if error.kind() == io::ErrorKind::NotFound => None,
|
|
1725
|
+
Err(error) => return Err(error.into()),
|
|
1726
|
+
};
|
|
1727
|
+
Ok(callback(state))
|
|
1728
|
+
}
|
|
1729
|
+
|
|
1709
1730
|
pub(crate) fn save_runtime_state_without_migrations(
|
|
1710
1731
|
workspace: &Path,
|
|
1711
1732
|
state: &Value,
|
|
1712
1733
|
) -> Result<(), StateError> {
|
|
1713
1734
|
let path = runtime_state_path(workspace);
|
|
1714
|
-
|
|
1735
|
+
// Raw writers share the canonical lock so readers/pruners cannot race
|
|
1736
|
+
// a restore write through a parallel lock namespace.
|
|
1737
|
+
let _lock = RuntimeLock::acquire(workspace, "state-save", 2.0)?;
|
|
1715
1738
|
if let Some(parent) = path.parent() {
|
|
1716
1739
|
std::fs::create_dir_all(parent)?;
|
|
1717
1740
|
}
|
|
@@ -3546,4 +3569,30 @@ mod tests {
|
|
|
3546
3569
|
2,
|
|
3547
3570
|
);
|
|
3548
3571
|
}
|
|
3572
|
+
|
|
3573
|
+
#[test]
|
|
3574
|
+
fn state_save_lock_serializes_raw_writer_and_prune_reader() {
|
|
3575
|
+
let ws = temp_ws();
|
|
3576
|
+
save_runtime_state(&ws, &json!({"teams": {"fresh": {}}})).unwrap();
|
|
3577
|
+
let lock = RuntimeLock::acquire(&ws, "state-save", 2.0).unwrap();
|
|
3578
|
+
|
|
3579
|
+
let writer_ws = ws.clone();
|
|
3580
|
+
let writer = std::thread::spawn(move || {
|
|
3581
|
+
save_runtime_state_without_migrations(&writer_ws, &json!({"teams": {"fresh": {}}}))
|
|
3582
|
+
});
|
|
3583
|
+
std::thread::sleep(Duration::from_millis(100));
|
|
3584
|
+
assert!(!writer.is_finished(), "raw writer must share state-save lock");
|
|
3585
|
+
drop(lock);
|
|
3586
|
+
writer.join().unwrap().unwrap();
|
|
3587
|
+
|
|
3588
|
+
let lock = RuntimeLock::acquire(&ws, "state-save", 2.0).unwrap();
|
|
3589
|
+
let reader_ws = ws.clone();
|
|
3590
|
+
let reader = std::thread::spawn(move || {
|
|
3591
|
+
with_runtime_state_lock_without_migrations(&reader_ws, |state| state.is_some())
|
|
3592
|
+
});
|
|
3593
|
+
std::thread::sleep(Duration::from_millis(100));
|
|
3594
|
+
assert!(!reader.is_finished(), "prune reader must use state-save lock");
|
|
3595
|
+
drop(lock);
|
|
3596
|
+
assert!(reader.join().unwrap().unwrap());
|
|
3597
|
+
}
|
|
3549
3598
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@team-agent/installer",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.83",
|
|
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.83",
|
|
24
|
+
"@team-agent/cli-darwin-x64": "0.5.83",
|
|
25
|
+
"@team-agent/cli-linux-x64": "0.5.83"
|
|
26
26
|
},
|
|
27
27
|
"scripts": {
|
|
28
28
|
"postinstall": "node npm/bincheck.mjs",
|