@team-agent/installer 0.5.84 → 0.5.86
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/mod.rs +0 -1
- package/crates/team-agent/src/cli/profile.rs +4 -12
- package/crates/team-agent/src/cli/tests/verb_profile.rs +66 -0
- package/crates/team-agent/src/compiler/tests.rs +13 -24
- package/crates/team-agent/src/compiler.rs +1 -50
- package/crates/team-agent/src/leader/start.rs +0 -2
- package/crates/team-agent/src/lifecycle/launch/add_agent.rs +0 -1
- package/crates/team-agent/src/lifecycle/launch/add_agent_state.rs +0 -1
- package/crates/team-agent/src/lifecycle/launch/agent_state.rs +0 -6
- package/crates/team-agent/src/lifecycle/launch/identity.rs +0 -1
- package/crates/team-agent/src/lifecycle/launch/layout.rs +0 -1
- package/crates/team-agent/src/lifecycle/launch/leader_context.rs +0 -1
- package/crates/team-agent/src/lifecycle/launch/mcp_config.rs +1 -89
- package/crates/team-agent/src/lifecycle/launch/ownership.rs +0 -1
- package/crates/team-agent/src/lifecycle/launch/pi_mcp.rs +0 -3
- package/crates/team-agent/src/lifecycle/launch/quick_start.rs +14 -24
- package/crates/team-agent/src/lifecycle/launch/quick_start_transport.rs +0 -1
- package/crates/team-agent/src/lifecycle/launch/readiness.rs +0 -1
- package/crates/team-agent/src/lifecycle/launch/spawn.rs +4 -8
- package/crates/team-agent/src/lifecycle/launch/spec_state.rs +0 -1
- package/crates/team-agent/src/lifecycle/launch/state_projection.rs +0 -1
- package/crates/team-agent/src/lifecycle/launch/worker_env.rs +0 -1
- package/crates/team-agent/src/lifecycle/launch.rs +0 -9
- package/crates/team-agent/src/lifecycle/profile_launch.rs +1 -1
- package/crates/team-agent/src/lifecycle/restart/common.rs +6 -9
- package/crates/team-agent/src/lifecycle/tests/claude_compatible_config_red.rs +1 -3
- package/crates/team-agent/src/lifecycle/tests/clone_agent_preserves_source_tools.rs +42 -46
- package/crates/team-agent/src/lifecycle/tests/copilot_provider_red.rs +209 -52
- package/crates/team-agent/src/lifecycle/tests/core_034_real_red.rs +32 -77
- package/crates/team-agent/src/lifecycle/tests/launch_spawn.rs +2 -13
- package/crates/team-agent/src/lifecycle/tests/pi_catalog_red.rs +0 -1
- package/crates/team-agent/src/lifecycle/tests/pi_command_red.rs +0 -2
- package/crates/team-agent/src/lifecycle/tests/pi_compiler_red.rs +14 -85
- package/crates/team-agent/src/lifecycle/tests/pi_dual_entry_red.rs +1 -1
- package/crates/team-agent/src/lifecycle/tests/pi_executable_mcp_red.rs +0 -5
- package/crates/team-agent/src/lifecycle/tests/pi_session_delivery_red.rs +0 -1
- package/crates/team-agent/src/lifecycle/tests/restart_rebind_hotfix_252_red.rs +4 -4
- package/crates/team-agent/src/lifecycle/tests.rs +0 -1
- package/crates/team-agent/src/lifecycle/types.rs +0 -10
- package/crates/team-agent/src/lifecycle/worker_command_context.rs +4 -248
- package/crates/team-agent/src/messaging/results/collection_tests.rs +719 -0
- package/crates/team-agent/src/messaging/results.rs +104 -27
- package/crates/team-agent/src/model/enums.rs +0 -35
- package/crates/team-agent/src/model/mod.rs +0 -1
- package/crates/team-agent/src/model/spec.rs +23 -84
- package/crates/team-agent/src/provider/adapter.rs +48 -50
- package/crates/team-agent/src/provider/adapters/claude.rs +5 -30
- package/crates/team-agent/src/provider/adapters/codex.rs +5 -25
- package/crates/team-agent/src/provider/adapters/copilot.rs +9 -42
- package/crates/team-agent/src/provider/adapters/cursor_agent.rs +4 -8
- package/crates/team-agent/src/provider/adapters/grok.rs +5 -58
- package/crates/team-agent/src/provider/adapters/mod.rs +5 -7
- package/crates/team-agent/src/provider/adapters/pi.rs +1 -56
- package/crates/team-agent/src/provider/bypass_flags.rs +1 -2
- package/crates/team-agent/src/provider/session/capture.rs +4 -4
- package/crates/team-agent/src/provider/tests/adapter.rs +7 -24
- package/crates/team-agent/src/provider/types.rs +1 -1
- package/crates/team-agent/src/state/persist.rs +15 -1
- package/package.json +4 -4
- package/schemas/team.schema.json +4 -9
- package/crates/team-agent/src/lifecycle/tests/g1_silent_faces.rs +0 -784
- package/crates/team-agent/src/model/permissions.rs +0 -721
|
@@ -130,6 +130,12 @@ fn collect_scoped(
|
|
|
130
130
|
owner_team_id: Option<&str>,
|
|
131
131
|
) -> Result<serde_json::Value, MessagingError> {
|
|
132
132
|
let paths = collect_paths(workspace)?;
|
|
133
|
+
#[cfg(test)]
|
|
134
|
+
collection_tests::boundary("before_lock")?;
|
|
135
|
+
// Serialize collectors, but do not hold a SQLite write transaction while
|
|
136
|
+
// committing state. A process exit releases this existing OS file lock.
|
|
137
|
+
let collection_lock =
|
|
138
|
+
crate::state::persist::acquire_result_collection_lock(&paths.run_workspace)?;
|
|
133
139
|
let log = EventLog::new(&paths.run_workspace);
|
|
134
140
|
let resolved_owner_team_id = match owner_team_id.filter(|team| !team.is_empty()) {
|
|
135
141
|
Some(team) => Some(resolve_owner_team_for_read(
|
|
@@ -159,7 +165,11 @@ fn collect_scoped(
|
|
|
159
165
|
let store = MessageStore::open(&paths.run_workspace)?;
|
|
160
166
|
let conn = crate::db::schema::open_db(store.db_path())?;
|
|
161
167
|
if let Some(path) = result_file {
|
|
168
|
+
#[cfg(test)]
|
|
169
|
+
collection_tests::boundary("before_ingest")?;
|
|
162
170
|
ingest_result_file(&conn, path, owner_team_id)?;
|
|
171
|
+
#[cfg(test)]
|
|
172
|
+
collection_tests::boundary("after_ingest")?;
|
|
163
173
|
}
|
|
164
174
|
let sql = match owner_team_id {
|
|
165
175
|
Some(_) => {
|
|
@@ -197,7 +207,7 @@ fn collect_scoped(
|
|
|
197
207
|
let mut collected_results = Vec::new();
|
|
198
208
|
let mut invalid_results = Vec::new();
|
|
199
209
|
let mut fatal_invalid_results = 0usize;
|
|
200
|
-
let mut
|
|
210
|
+
let mut ready = Vec::new();
|
|
201
211
|
let mut task_updates = Vec::new();
|
|
202
212
|
for row in rows {
|
|
203
213
|
let envelope: serde_json::Value = match serde_json::from_str(&row.envelope) {
|
|
@@ -242,25 +252,75 @@ fn collect_scoped(
|
|
|
242
252
|
)?;
|
|
243
253
|
continue;
|
|
244
254
|
};
|
|
245
|
-
match owner_team_id {
|
|
246
|
-
Some(team) => {
|
|
247
|
-
conn.execute(
|
|
248
|
-
"update results set status = 'collected' where result_id = ?1 and owner_team_id = ?2",
|
|
249
|
-
params![row.result_id.as_str(), team],
|
|
250
|
-
)?;
|
|
251
|
-
}
|
|
252
|
-
None => {
|
|
253
|
-
conn.execute(
|
|
254
|
-
"update results set status = 'collected' where result_id = ?1",
|
|
255
|
-
params![row.result_id.as_str()],
|
|
256
|
-
)?;
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
255
|
if scope == "task" {
|
|
260
|
-
mark_task_done(&mut state, &row.task_id, &row.result_id);
|
|
261
256
|
task_updates.push((row.task_id.clone(), row.result_id.clone()));
|
|
262
|
-
state_dirty = true;
|
|
263
257
|
}
|
|
258
|
+
ready.push((row, envelope, scope));
|
|
259
|
+
}
|
|
260
|
+
// Pending DB rows remain eligible until the complete task delta is durable.
|
|
261
|
+
// Preserve the original created_at/result_id order: its last row per task wins.
|
|
262
|
+
if !task_updates.is_empty() {
|
|
263
|
+
let mut task_winners = Vec::new();
|
|
264
|
+
let mut updated = std::collections::BTreeSet::new();
|
|
265
|
+
for (task_id, result_id) in task_updates.iter().rev() {
|
|
266
|
+
if updated.insert(task_id) {
|
|
267
|
+
task_winners.push((task_id.clone(), result_id.clone()));
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
#[cfg(test)]
|
|
271
|
+
collection_tests::boundary("before_state")?;
|
|
272
|
+
state = crate::state::repository::StateRepository::new(&paths.run_workspace).commit(
|
|
273
|
+
crate::state::repository::StateWriteIntent::ResultCollection { owner_team_id },
|
|
274
|
+
|latest| {
|
|
275
|
+
for (task_id, result_id) in &task_winners {
|
|
276
|
+
let already_applied = latest
|
|
277
|
+
.get("tasks")
|
|
278
|
+
.and_then(serde_json::Value::as_array)
|
|
279
|
+
.is_some_and(|tasks| {
|
|
280
|
+
tasks.iter().any(|task| {
|
|
281
|
+
task.get("id").and_then(serde_json::Value::as_str)
|
|
282
|
+
== Some(task_id.as_str())
|
|
283
|
+
&& task.get("status").and_then(serde_json::Value::as_str)
|
|
284
|
+
== Some("done")
|
|
285
|
+
&& task
|
|
286
|
+
.get("accepted_result_id")
|
|
287
|
+
.and_then(serde_json::Value::as_str)
|
|
288
|
+
== Some(result_id.as_str())
|
|
289
|
+
})
|
|
290
|
+
});
|
|
291
|
+
if !already_applied {
|
|
292
|
+
mark_task_done(latest, task_id, result_id);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
},
|
|
296
|
+
)?;
|
|
297
|
+
for (task_id, result_id) in &task_winners {
|
|
298
|
+
let applied = state
|
|
299
|
+
.get("tasks")
|
|
300
|
+
.and_then(serde_json::Value::as_array)
|
|
301
|
+
.is_some_and(|tasks| {
|
|
302
|
+
tasks.iter().any(|task| {
|
|
303
|
+
task.get("id").and_then(serde_json::Value::as_str) == Some(task_id.as_str())
|
|
304
|
+
&& task.get("status").and_then(serde_json::Value::as_str)
|
|
305
|
+
== Some("done")
|
|
306
|
+
&& task
|
|
307
|
+
.get("accepted_result_id")
|
|
308
|
+
.and_then(serde_json::Value::as_str)
|
|
309
|
+
== Some(result_id.as_str())
|
|
310
|
+
})
|
|
311
|
+
});
|
|
312
|
+
if !applied {
|
|
313
|
+
return Err(MessagingError::Validation(format!(
|
|
314
|
+
"task result projection missing after commit: task_id={task_id} result_id={result_id}"
|
|
315
|
+
)));
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
#[cfg(test)]
|
|
319
|
+
collection_tests::boundary("after_state")?;
|
|
320
|
+
}
|
|
321
|
+
for (row, envelope, scope) in ready {
|
|
322
|
+
#[cfg(test)]
|
|
323
|
+
collection_tests::boundary("before_event")?;
|
|
264
324
|
log.write(
|
|
265
325
|
"collect.result",
|
|
266
326
|
serde_json::json!({
|
|
@@ -270,6 +330,29 @@ fn collect_scoped(
|
|
|
270
330
|
"scope": scope,
|
|
271
331
|
}),
|
|
272
332
|
)?;
|
|
333
|
+
#[cfg(test)]
|
|
334
|
+
collection_tests::boundary("after_event")?;
|
|
335
|
+
#[cfg(test)]
|
|
336
|
+
collection_tests::boundary("before_finalize")?;
|
|
337
|
+
let finalized = match owner_team_id {
|
|
338
|
+
Some(team) => {
|
|
339
|
+
conn.execute(
|
|
340
|
+
"update results set status = 'collected' where result_id = ?1 and owner_team_id = ?2 and status not in ('collected', 'invalid')",
|
|
341
|
+
params![row.result_id.as_str(), team],
|
|
342
|
+
)?
|
|
343
|
+
}
|
|
344
|
+
None => {
|
|
345
|
+
conn.execute(
|
|
346
|
+
"update results set status = 'collected' where result_id = ?1 and status not in ('collected', 'invalid')",
|
|
347
|
+
params![row.result_id.as_str()],
|
|
348
|
+
)?
|
|
349
|
+
}
|
|
350
|
+
};
|
|
351
|
+
#[cfg(test)]
|
|
352
|
+
collection_tests::boundary("after_finalize")?;
|
|
353
|
+
if finalized == 0 {
|
|
354
|
+
continue;
|
|
355
|
+
}
|
|
273
356
|
collected.push(envelope.clone());
|
|
274
357
|
let summary = serde_json::json!({
|
|
275
358
|
"result_id": row.result_id,
|
|
@@ -286,17 +369,8 @@ fn collect_scoped(
|
|
|
286
369
|
});
|
|
287
370
|
collected_results.push(summary);
|
|
288
371
|
}
|
|
289
|
-
if state_dirty {
|
|
290
|
-
state = crate::state::repository::StateRepository::new(&paths.run_workspace).commit(
|
|
291
|
-
crate::state::repository::StateWriteIntent::ResultCollection { owner_team_id },
|
|
292
|
-
|latest| {
|
|
293
|
-
for (task_id, result_id) in &task_updates {
|
|
294
|
-
mark_task_done(latest, task_id, result_id);
|
|
295
|
-
}
|
|
296
|
-
},
|
|
297
|
-
)?;
|
|
298
|
-
}
|
|
299
372
|
let counts = result_counts(&conn, owner_team_id)?;
|
|
373
|
+
drop(collection_lock);
|
|
300
374
|
// results.py:157 — ensure_coordinator=true runs the REAL ensure step; the
|
|
301
375
|
// `{ok:false,status:"not_required"}` literal is ONLY the ensure=false branch.
|
|
302
376
|
let coordinator = if ensure_coordinator {
|
|
@@ -1483,6 +1557,9 @@ pub fn collect_results_and_notify_watchers(
|
|
|
1483
1557
|
}))
|
|
1484
1558
|
}
|
|
1485
1559
|
|
|
1560
|
+
#[cfg(test)]
|
|
1561
|
+
mod collection_tests;
|
|
1562
|
+
|
|
1486
1563
|
#[cfg(test)]
|
|
1487
1564
|
mod tests {
|
|
1488
1565
|
use super::{format_report_result_notification, report_result};
|
|
@@ -227,29 +227,6 @@ pub enum PermissionMode {
|
|
|
227
227
|
Trusted,
|
|
228
228
|
}
|
|
229
229
|
|
|
230
|
-
/// 规范化 tool(`CANONICAL_TOOLS` `permissions.py:5-14`)。别名(`fs_*`/`@builtin`/`*`)
|
|
231
|
-
/// 在 `expand_tools` 展开 —— 那是单独的 alias 输入枚举,不在此(step 2 后续)。
|
|
232
|
-
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
|
|
233
|
-
#[serde(rename_all = "snake_case")]
|
|
234
|
-
pub enum Tool {
|
|
235
|
-
FsRead,
|
|
236
|
-
FsWrite,
|
|
237
|
-
FsList,
|
|
238
|
-
ExecuteBash,
|
|
239
|
-
GitDiff,
|
|
240
|
-
Network,
|
|
241
|
-
McpTeam,
|
|
242
|
-
ProviderBuiltin,
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
/// provider×tool enforcement(`PROVIDER_ENFORCEMENT` `permissions.py:44-65`)。
|
|
246
|
-
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
|
247
|
-
#[serde(rename_all = "snake_case")]
|
|
248
|
-
pub enum Enforcement {
|
|
249
|
-
Hard,
|
|
250
|
-
PromptOnly,
|
|
251
|
-
}
|
|
252
|
-
|
|
253
230
|
/// tmux pane 存活态(`state.py:336-341`)。**`Unknown` 既不可当 dead 也不可当 live**
|
|
254
231
|
/// (owner-gate `state.py:382` 用 `!= LIVE`)—— 穷尽 match,不 fallthrough(§11)。
|
|
255
232
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
|
@@ -345,18 +322,6 @@ mod tests {
|
|
|
345
322
|
serde_json::to_string(&DisplayBackend::Adaptive).unwrap(),
|
|
346
323
|
"\"adaptive\"",
|
|
347
324
|
),
|
|
348
|
-
(
|
|
349
|
-
serde_json::to_string(&Tool::ExecuteBash).unwrap(),
|
|
350
|
-
"\"execute_bash\"",
|
|
351
|
-
),
|
|
352
|
-
(
|
|
353
|
-
serde_json::to_string(&Tool::ProviderBuiltin).unwrap(),
|
|
354
|
-
"\"provider_builtin\"",
|
|
355
|
-
),
|
|
356
|
-
(
|
|
357
|
-
serde_json::to_string(&Enforcement::PromptOnly).unwrap(),
|
|
358
|
-
"\"prompt_only\"",
|
|
359
|
-
),
|
|
360
325
|
(
|
|
361
326
|
serde_json::to_string(&PaneLiveness::Unknown).unwrap(),
|
|
362
327
|
"\"unknown\"",
|
|
@@ -6,20 +6,18 @@
|
|
|
6
6
|
//! serde_json `preserve_order` 保证 child 迭代序 == Python 插入序。
|
|
7
7
|
//!
|
|
8
8
|
//! 本文件先落地 `validate_result_envelope`(自包含,step 7/11 的门)。`validate_spec`
|
|
9
|
-
//! 依赖 `
|
|
10
|
-
//! 待那几个叶子模块集成后再加。
|
|
9
|
+
//! 依赖 `find_dependency_cycle`(task_graph)+ yaml Value。
|
|
11
10
|
|
|
12
11
|
use std::path::Path;
|
|
13
12
|
|
|
14
13
|
use serde_json::Value;
|
|
15
14
|
|
|
16
|
-
use crate::model::enums::
|
|
15
|
+
use crate::model::enums::TaskStatus;
|
|
17
16
|
use crate::model::errors::ModelError;
|
|
18
17
|
use crate::model::ids::TaskId;
|
|
19
18
|
use crate::model::task_graph::{find_dependency_cycle, TaskNode};
|
|
20
19
|
use crate::model::yaml::Value as Yaml;
|
|
21
|
-
use crate::model::
|
|
22
|
-
use crate::provider::adapters::pi::first_unsupported_pi_tool_category;
|
|
20
|
+
use crate::model::yaml;
|
|
23
21
|
use crate::provider::wire::{is_claude_family, parse_canonical_provider};
|
|
24
22
|
|
|
25
23
|
/// result_envelope_v1 顶层 required(= allowed)。
|
|
@@ -333,12 +331,13 @@ fn basic_schema_errors(spec: &Yaml) -> Vec<String> {
|
|
|
333
331
|
if !matches!(mode, Some("supervisor_worker" | "swarm_limited")) {
|
|
334
332
|
e.push("/team/mode: invalid mode".to_string());
|
|
335
333
|
}
|
|
336
|
-
let leader_keys = &["id", "role", "provider", "model", "
|
|
334
|
+
let leader_keys = &["id", "role", "provider", "model", "context_policy"];
|
|
335
|
+
let leader_allowed = &["id", "role", "provider", "model", "tools", "context_policy"];
|
|
337
336
|
check_keys_y(
|
|
338
337
|
spec.get("leader"),
|
|
339
338
|
"/leader",
|
|
340
339
|
leader_keys,
|
|
341
|
-
|
|
340
|
+
leader_allowed,
|
|
342
341
|
&mut e,
|
|
343
342
|
);
|
|
344
343
|
let cp_keys = &[
|
|
@@ -407,7 +406,6 @@ fn check_agent(agent: &Yaml, path: &str, errors: &mut Vec<String>) {
|
|
|
407
406
|
"model",
|
|
408
407
|
"working_directory",
|
|
409
408
|
"system_prompt",
|
|
410
|
-
"tools",
|
|
411
409
|
"dangerously_skip_permissions",
|
|
412
410
|
"preferred_for",
|
|
413
411
|
"avoid_for",
|
|
@@ -483,7 +481,6 @@ fn check_agent(agent: &Yaml, path: &str, errors: &mut Vec<String>) {
|
|
|
483
481
|
&["inline", "file"],
|
|
484
482
|
errors,
|
|
485
483
|
);
|
|
486
|
-
check_list_y(agent.get("tools"), &format!("{path}/tools"), errors);
|
|
487
484
|
check_list_y(
|
|
488
485
|
agent.get("preferred_for"),
|
|
489
486
|
&format!("{path}/preferred_for"),
|
|
@@ -787,67 +784,6 @@ fn semantic_errors(spec: &Yaml, base_dir: &Path) -> Vec<String> {
|
|
|
787
784
|
));
|
|
788
785
|
}
|
|
789
786
|
}
|
|
790
|
-
let tools: Vec<&str> = agent
|
|
791
|
-
.get("tools")
|
|
792
|
-
.and_then(Yaml::as_list)
|
|
793
|
-
.unwrap_or(&[])
|
|
794
|
-
.iter()
|
|
795
|
-
.filter_map(Yaml::as_str)
|
|
796
|
-
.collect();
|
|
797
|
-
let expanded_tools = permissions::expand_tool_strings(tools);
|
|
798
|
-
for tool in &expanded_tools {
|
|
799
|
-
if !permissions::is_canonical_tool(tool) {
|
|
800
|
-
e.push(format!(
|
|
801
|
-
"/agents/{idx}/tools: unknown tool {}",
|
|
802
|
-
py_repr_str(tool)
|
|
803
|
-
));
|
|
804
|
-
}
|
|
805
|
-
}
|
|
806
|
-
if parse_canonical_provider(provider.and_then(Yaml::as_str).unwrap_or(""))
|
|
807
|
-
== Some(Provider::Pi)
|
|
808
|
-
{
|
|
809
|
-
if let Some(category) = first_unsupported_pi_tool_category(
|
|
810
|
-
expanded_tools
|
|
811
|
-
.iter()
|
|
812
|
-
.filter(|tool| permissions::is_canonical_tool(tool))
|
|
813
|
-
.map(String::as_str),
|
|
814
|
-
) {
|
|
815
|
-
e.push(format!(
|
|
816
|
-
"/agents/{idx}/tools: Pi does not support Team Agent tool category {category:?}; remove it from this agent's tools"
|
|
817
|
-
));
|
|
818
|
-
}
|
|
819
|
-
}
|
|
820
|
-
}
|
|
821
|
-
|
|
822
|
-
let leader_tools: Vec<&str> = leader
|
|
823
|
-
.and_then(|l| l.get("tools"))
|
|
824
|
-
.and_then(Yaml::as_list)
|
|
825
|
-
.unwrap_or(&[])
|
|
826
|
-
.iter()
|
|
827
|
-
.filter_map(Yaml::as_str)
|
|
828
|
-
.collect();
|
|
829
|
-
let expanded_leader_tools = permissions::expand_tool_strings(leader_tools);
|
|
830
|
-
for tool in &expanded_leader_tools {
|
|
831
|
-
if !permissions::is_canonical_tool(tool) {
|
|
832
|
-
e.push(format!(
|
|
833
|
-
"/leader/tools: unknown tool {}",
|
|
834
|
-
py_repr_str(tool)
|
|
835
|
-
));
|
|
836
|
-
}
|
|
837
|
-
}
|
|
838
|
-
if parse_canonical_provider(leader_provider.and_then(Yaml::as_str).unwrap_or(""))
|
|
839
|
-
== Some(Provider::Pi)
|
|
840
|
-
{
|
|
841
|
-
if let Some(category) = first_unsupported_pi_tool_category(
|
|
842
|
-
expanded_leader_tools
|
|
843
|
-
.iter()
|
|
844
|
-
.filter(|tool| permissions::is_canonical_tool(tool))
|
|
845
|
-
.map(String::as_str),
|
|
846
|
-
) {
|
|
847
|
-
e.push(format!(
|
|
848
|
-
"/leader/tools: Pi does not support Team Agent tool category {category:?}; remove it from the leader's tools"
|
|
849
|
-
));
|
|
850
|
-
}
|
|
851
787
|
}
|
|
852
788
|
|
|
853
789
|
let routing = spec.get("routing");
|
|
@@ -1070,7 +1006,7 @@ mod tests {
|
|
|
1070
1006
|
}
|
|
1071
1007
|
|
|
1072
1008
|
#[test]
|
|
1073
|
-
fn
|
|
1009
|
+
fn tools_are_transparent_for_all_providers() {
|
|
1074
1010
|
let pi_text = include_str!("testdata/team.spec.yaml")
|
|
1075
1011
|
.lines()
|
|
1076
1012
|
.filter(|line| {
|
|
@@ -1093,22 +1029,26 @@ mod tests {
|
|
|
1093
1029
|
1,
|
|
1094
1030
|
);
|
|
1095
1031
|
let bad_agent = yaml::loads(&bad_agent_text).unwrap();
|
|
1096
|
-
let error = validate_spec(&bad_agent, Path::new(TD)).unwrap_err().to_string();
|
|
1097
1032
|
assert!(
|
|
1098
|
-
|
|
1099
|
-
"
|
|
1033
|
+
validate_spec(&bad_agent, Path::new(TD)).is_ok(),
|
|
1034
|
+
"Pi tools metadata must be ignored"
|
|
1100
1035
|
);
|
|
1101
1036
|
|
|
1102
|
-
let
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1037
|
+
let missing_tools = pi_text
|
|
1038
|
+
.replacen(
|
|
1039
|
+
" tools:\n - fs_read\n - fs_list\n - mcp_team\n",
|
|
1040
|
+
"",
|
|
1041
|
+
1,
|
|
1042
|
+
)
|
|
1043
|
+
.replacen(
|
|
1044
|
+
" tools:\n - fs_read\n - fs_write\n - fs_list\n - execute_bash\n - mcp_team\n",
|
|
1045
|
+
"",
|
|
1046
|
+
1,
|
|
1047
|
+
);
|
|
1048
|
+
let missing_tools = yaml::loads(&missing_tools).unwrap();
|
|
1109
1049
|
assert!(
|
|
1110
|
-
|
|
1111
|
-
"
|
|
1050
|
+
validate_spec(&missing_tools, Path::new(TD)).is_ok(),
|
|
1051
|
+
"omitted tools metadata must be accepted"
|
|
1112
1052
|
);
|
|
1113
1053
|
}
|
|
1114
1054
|
|
|
@@ -1121,7 +1061,6 @@ mod tests {
|
|
|
1121
1061
|
"/version: must equal 1",
|
|
1122
1062
|
"/team/mode: invalid mode",
|
|
1123
1063
|
"/leader/provider: unknown provider 'badprov'",
|
|
1124
|
-
"/agents/0/tools: unknown tool 'banana'",
|
|
1125
1064
|
]
|
|
1126
1065
|
);
|
|
1127
1066
|
}
|