@team-agent/installer 0.5.110 → 0.5.112
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 +231 -812
- package/crates/team-agent/src/cli/diagnose.rs +2 -424
- package/crates/team-agent/src/cli/emit.rs +493 -451
- package/crates/team-agent/src/cli/leader.rs +4 -9
- package/crates/team-agent/src/cli/leaders.rs +90 -29
- package/crates/team-agent/src/cli/mod.rs +348 -169
- package/crates/team-agent/src/cli/models.rs +80 -24
- package/crates/team-agent/src/cli/named_address.rs +114 -25
- package/crates/team-agent/src/cli/route.rs +10 -5
- package/crates/team-agent/src/cli/send/presentation.rs +60 -41
- package/crates/team-agent/src/cli/send/resolve.rs +25 -18
- package/crates/team-agent/src/cli/send.rs +4 -4
- package/crates/team-agent/src/cli/spec.rs +53 -109
- package/crates/team-agent/src/cli/tests/base.rs +189 -82
- package/crates/team-agent/src/cli/tests/lane_c.rs +1 -2
- package/crates/team-agent/src/cli/tests/leader_watch.rs +2 -86
- package/crates/team-agent/src/cli/tests/main_preserved.rs +15 -140
- package/crates/team-agent/src/cli/tests/missing_subcommands.rs +1 -258
- package/crates/team-agent/src/cli/tests/mod.rs +0 -2
- package/crates/team-agent/src/cli/tests/named_address.rs +19 -22
- package/crates/team-agent/src/cli/tests/status_send.rs +15 -10
- package/crates/team-agent/src/cli/tests/verb_install_skill.rs +2 -2
- package/crates/team-agent/src/cli/types.rs +26 -121
- package/crates/team-agent/src/coordinator/health.rs +3 -283
- package/crates/team-agent/src/coordinator/mod.rs +0 -5
- package/crates/team-agent/src/coordinator/tests/basics.rs +0 -9
- package/crates/team-agent/src/coordinator/tests/mod.rs +0 -1
- package/crates/team-agent/src/coordinator/tests/tick_core.rs +0 -17
- package/crates/team-agent/src/coordinator/types.rs +1 -21
- package/crates/team-agent/src/db/message_store.rs +0 -42
- package/crates/team-agent/src/db/migration.rs +49 -9
- package/crates/team-agent/src/db/schema.rs +27 -20
- package/crates/team-agent/src/leader/owner_bind.rs +0 -24
- package/crates/team-agent/src/leader/registry.rs +3 -3
- package/crates/team-agent/src/leader/tests/byte_findings.rs +0 -20
- package/crates/team-agent/src/leader/tests/identity.rs +0 -32
- package/crates/team-agent/src/leader/tests/lease_claim.rs +0 -55
- package/crates/team-agent/src/lifecycle/tests/pi_dual_entry_red.rs +4 -9
- package/crates/team-agent/src/lifecycle/tests/restart_session_capture_red.rs +2 -51
- package/crates/team-agent/src/lifecycle/tests/stale_team_saveconflict_contract.rs +3 -1
- package/crates/team-agent/src/lifecycle/tests/status_credential_redaction_contract.rs +23 -23
- package/crates/team-agent/src/lifecycle/tests/subprep_codex_trust_roundtrip_red.rs +29 -55
- package/crates/team-agent/src/messaging/mod.rs +0 -2
- package/crates/team-agent/src/messaging/results.rs +0 -58
- package/crates/team-agent/src/messaging/tests/runtime.rs +0 -46
- package/crates/team-agent/src/state/repository.rs +1 -5
- package/package.json +4 -4
- package/templates/team_state.md +1 -1
- package/crates/team-agent/src/cli/tests/peer_allow.rs +0 -164
- package/crates/team-agent/src/cli/tests/verb_validate.rs +0 -195
- package/crates/team-agent/src/coordinator/tests/watch.rs +0 -223
- package/crates/team-agent/src/messaging/peers.rs +0 -34
|
@@ -23,7 +23,7 @@ use std::time::{Duration, Instant};
|
|
|
23
23
|
|
|
24
24
|
use serde_json::{json, Value};
|
|
25
25
|
use serial_test::serial;
|
|
26
|
-
use team_agent::cli::{
|
|
26
|
+
use team_agent::cli::{cmd_doctor, emit, CmdOutput, DoctorArgs};
|
|
27
27
|
use team_agent::event_log::EventLog;
|
|
28
28
|
use team_agent::message_store::MessageStore;
|
|
29
29
|
use team_agent::state::persist::save_runtime_state;
|
|
@@ -334,11 +334,10 @@ fn red6_cli_restart_reports_and_top_level_error_logs_are_redacted() {
|
|
|
334
334
|
env.scrub_tmux();
|
|
335
335
|
env.assert_no_real_tmux();
|
|
336
336
|
let workspace = env.workspace("red6");
|
|
337
|
-
let spec = write_invalid_validate_spec(&workspace, &marker);
|
|
338
337
|
let mut combined = String::new();
|
|
339
338
|
for json_mode in [true, false] {
|
|
340
339
|
let mut command = Command::new(crate::lifecycle::tests::test_binary_path());
|
|
341
|
-
command.arg("
|
|
340
|
+
command.arg("profile").arg(credential_url(&marker)).arg("synthetic-profile");
|
|
342
341
|
if json_mode {
|
|
343
342
|
command.arg("--json");
|
|
344
343
|
}
|
|
@@ -351,7 +350,7 @@ fn red6_cli_restart_reports_and_top_level_error_logs_are_redacted() {
|
|
|
351
350
|
.expect("run top-level CLI error boundary");
|
|
352
351
|
assert!(
|
|
353
352
|
!output.status.success(),
|
|
354
|
-
"invalid
|
|
353
|
+
"invalid profile subcommand must reach CLI error"
|
|
355
354
|
);
|
|
356
355
|
combined.push_str(&String::from_utf8_lossy(&output.stdout));
|
|
357
356
|
combined.push_str(&String::from_utf8_lossy(&output.stderr));
|
|
@@ -391,15 +390,30 @@ fn red7_pane_and_profile_scrubbers_converge_without_losing_existing_policy() {
|
|
|
391
390
|
let env = hermetic_guard::HermeticTestEnv::enter("redact-red7");
|
|
392
391
|
let pane_policy_preserved = pane_excerpt_uses_shared_redaction(&env, &marker);
|
|
393
392
|
let team = write_profile_fixture(env.root(), server.base_url(), &profile_token);
|
|
394
|
-
let result =
|
|
393
|
+
let result = cmd_doctor(&DoctorArgs {
|
|
394
|
+
spec: Some(team.join("TEAM.md")),
|
|
395
|
+
workspace: team,
|
|
396
|
+
gate: None,
|
|
397
|
+
comms: false,
|
|
398
|
+
team: None,
|
|
399
|
+
fix: false,
|
|
400
|
+
fix_schema: false,
|
|
401
|
+
cleanup_orphans: false,
|
|
402
|
+
confirm: false,
|
|
403
|
+
json: true,
|
|
404
|
+
})
|
|
395
405
|
.expect("profile smoke returns a diagnostic report");
|
|
396
406
|
let report = match result.output {
|
|
397
407
|
CmdOutput::Json(value) => value,
|
|
398
|
-
other => panic!("expected JSON
|
|
408
|
+
other => panic!("expected JSON doctor output, got {other:?}"),
|
|
399
409
|
};
|
|
400
|
-
let
|
|
401
|
-
let
|
|
402
|
-
|
|
410
|
+
let report_text = report.to_string();
|
|
411
|
+
let text = report
|
|
412
|
+
.get("profile_smoke")
|
|
413
|
+
.unwrap_or_else(|| panic!("doctor must expose profile_smoke; report={report}"))
|
|
414
|
+
.to_string();
|
|
415
|
+
let profile_policy_preserved = !report_text.contains(&marker)
|
|
416
|
+
&& !report_text.contains(&profile_token)
|
|
403
417
|
&& !text.contains(tail_sentinel)
|
|
404
418
|
&& text.contains(REDACTED)
|
|
405
419
|
&& text.contains("proxy.invalid:8443/path")
|
|
@@ -555,20 +569,6 @@ fn run_mcp_status_frames(
|
|
|
555
569
|
.collect()
|
|
556
570
|
}
|
|
557
571
|
|
|
558
|
-
fn write_invalid_validate_spec(workspace: &Path, marker: &str) -> PathBuf {
|
|
559
|
-
let path = workspace.join("invalid-provider.spec.yaml");
|
|
560
|
-
fs::write(
|
|
561
|
-
&path,
|
|
562
|
-
format!(
|
|
563
|
-
"version: 1\nteam:\n id: current\n name: current\n session_name: team-redaction-contract\n workspace: \"{}\"\nleader:\n provider: \"{}\"\nagents:\n - id: worker\n provider: fake\n model: fake\n role: Worker\n window: worker\ntasks: []\n",
|
|
564
|
-
workspace.display(),
|
|
565
|
-
credential_url(marker)
|
|
566
|
-
),
|
|
567
|
-
)
|
|
568
|
-
.unwrap();
|
|
569
|
-
path
|
|
570
|
-
}
|
|
571
|
-
|
|
572
572
|
fn read_cli_error_logs(workspace: &Path) -> String {
|
|
573
573
|
let dir = workspace.join(".team/logs");
|
|
574
574
|
fs::read_dir(dir)
|
|
@@ -92,15 +92,10 @@ const SUBROOT_FULL_TRUST_PANE_AFTER_QUICKSTART: &str = r#"> You are in /private/
|
|
|
92
92
|
│ directory: /private/tmp/…/teamdir │
|
|
93
93
|
╰───────────────────────────────────────╯
|
|
94
94
|
|
|
95
|
-
|
|
96
95
|
› Find and fix a bug in @filename
|
|
97
96
|
|
|
98
97
|
gpt-5.5 default · /private/tmp/ta-subroot-SUB-PROBE-1-trust-startup.uL9Boc/te…
|
|
99
98
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
99
|
"#;
|
|
105
100
|
|
|
106
101
|
fn tmp_dir(tag: &str) -> TempWorkspace {
|
|
@@ -419,8 +414,7 @@ fn contract_a_own_workspace_trust_is_actionable_not_ready_and_foreign_is_pending
|
|
|
419
414
|
}
|
|
420
415
|
|
|
421
416
|
#[test]
|
|
422
|
-
fn
|
|
423
|
-
use team_agent::cli::{cmd_wait_ready, CmdOutput, WaitReadyArgs};
|
|
417
|
+
fn contract_a_runtime_readiness_is_false_while_actionable_trust_remains() {
|
|
424
418
|
use team_agent::provider::{classify_codex_startup_screen, StartupScreenDecision};
|
|
425
419
|
|
|
426
420
|
assert_eq!(
|
|
@@ -429,67 +423,47 @@ fn contract_a_probe5_quick_start_ready_is_invalid_while_actionable_trust_remains
|
|
|
429
423
|
"fixture sanity: probe5 pane text is an actionable own-workspace trust prompt"
|
|
430
424
|
);
|
|
431
425
|
|
|
432
|
-
let ws = tmp_dir("a-
|
|
426
|
+
let ws = tmp_dir("a-runtime-readiness-trust");
|
|
433
427
|
let mcp_config = ws.join(".team").join("runtime").join("mcp").join("w1.json");
|
|
434
428
|
std::fs::create_dir_all(mcp_config.parent().unwrap()).unwrap();
|
|
435
429
|
std::fs::write(&mcp_config, "{}").unwrap();
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
430
|
+
// Keep every other readiness gate satisfied; an unattached leader would
|
|
431
|
+
// make ready=false independently of the trust prompt under test.
|
|
432
|
+
let mut state = json!({
|
|
433
|
+
"session_name": "team-subprep",
|
|
434
|
+
"tmux_session_present": true,
|
|
435
|
+
"leader_receiver": {"status": "attached", "pane_id": "%9"},
|
|
436
|
+
"agents": {
|
|
437
|
+
"w1": {
|
|
438
|
+
"provider": "codex",
|
|
439
|
+
"status": "awaiting_trust_prompt",
|
|
440
|
+
"startup_prompt_status": "awaiting_trust_prompt",
|
|
441
|
+
"startup_prompts": "awaiting_trust_prompt",
|
|
442
|
+
"pane_id": "%7",
|
|
443
|
+
"window": "w1",
|
|
444
|
+
"mcp_config": mcp_config.to_string_lossy().to_string(),
|
|
445
|
+
"task_prompt_delivered": true
|
|
451
446
|
}
|
|
452
|
-
}
|
|
453
|
-
)
|
|
454
|
-
.unwrap();
|
|
455
|
-
|
|
456
|
-
let result = cmd_wait_ready(&WaitReadyArgs {
|
|
457
|
-
workspace: ws.to_path_buf(),
|
|
458
|
-
timeout: 0.0,
|
|
459
|
-
json: true,
|
|
460
|
-
team: None,
|
|
461
|
-
})
|
|
462
|
-
.expect("wait-ready SUT should return shaped JSON");
|
|
463
|
-
let value = match result.output {
|
|
464
|
-
CmdOutput::Json(value) => value,
|
|
465
|
-
other => panic!("wait-ready --json must return JSON output, got {other:?}"),
|
|
466
|
-
};
|
|
447
|
+
}
|
|
448
|
+
});
|
|
467
449
|
|
|
450
|
+
let value = crate::cli::diagnose::wait_readiness(&state);
|
|
468
451
|
assert_eq!(
|
|
469
|
-
value["
|
|
470
|
-
json!(false),
|
|
471
|
-
"Contract A: quick-start/wait-ready readiness must not report ok=true while actionable trust remains; got {value}"
|
|
472
|
-
);
|
|
473
|
-
assert_eq!(
|
|
474
|
-
value["status"],
|
|
475
|
-
json!("pending"),
|
|
476
|
-
"Contract A: actionable trust prompt should keep readiness pending, not ready; got {value}"
|
|
477
|
-
);
|
|
478
|
-
assert_eq!(
|
|
479
|
-
value["reason"],
|
|
480
|
-
json!("awaiting_trust_prompt"),
|
|
481
|
-
"Contract A: not-ready reason must identify the startup trust boundary; got {value}"
|
|
482
|
-
);
|
|
483
|
-
assert_eq!(
|
|
484
|
-
value["readiness"]["awaiting_trust_prompt"],
|
|
452
|
+
value["awaiting_trust_prompt"],
|
|
485
453
|
json!(true),
|
|
486
454
|
"Contract A: ready logic must surface awaiting_trust_prompt=true; got {value}"
|
|
487
455
|
);
|
|
488
456
|
assert_eq!(
|
|
489
|
-
value["
|
|
457
|
+
value["ready"],
|
|
490
458
|
json!(false),
|
|
491
459
|
"Contract A: ready must stay false until the actionable trust prompt is cleared; got {value}"
|
|
492
460
|
);
|
|
461
|
+
state["agents"]["w1"]["status"] = json!("running");
|
|
462
|
+
state["agents"]["w1"]["startup_prompt_status"] = json!("complete");
|
|
463
|
+
state["agents"]["w1"]["startup_prompts"] = json!("complete");
|
|
464
|
+
let cleared = crate::cli::diagnose::wait_readiness(&state);
|
|
465
|
+
assert_eq!(cleared["awaiting_trust_prompt"], json!(false));
|
|
466
|
+
assert_eq!(cleared["ready"], json!(true), "positive control: {cleared}");
|
|
493
467
|
}
|
|
494
468
|
|
|
495
469
|
#[test]
|
|
@@ -67,7 +67,6 @@ pub mod delivery;
|
|
|
67
67
|
pub mod helpers;
|
|
68
68
|
pub mod leader_channel;
|
|
69
69
|
pub mod leader_receiver;
|
|
70
|
-
pub mod peers;
|
|
71
70
|
pub mod persist;
|
|
72
71
|
pub mod presentation;
|
|
73
72
|
pub mod results;
|
|
@@ -96,7 +95,6 @@ pub use leader_receiver::{
|
|
|
96
95
|
mirror_peer_message_to_leader, send_to_leader_receiver,
|
|
97
96
|
send_to_leader_receiver_with_presentation,
|
|
98
97
|
};
|
|
99
|
-
pub use peers::allow_peer_talk;
|
|
100
98
|
pub use persist::{
|
|
101
99
|
persist_resolved_send, DeliveryBlocker, InitialDisposition, InternalSendKind,
|
|
102
100
|
LogicalRecipient, PersistResolution, PersistedSend, ResolvedSendIntent, SendOrigin,
|
|
@@ -45,64 +45,6 @@ impl Default for ResultRoute {
|
|
|
45
45
|
}
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
pub fn results_for_case(
|
|
49
|
-
workspace: &Path,
|
|
50
|
-
case_id: &str,
|
|
51
|
-
owner_team_id: Option<&str>,
|
|
52
|
-
requested_owner_team_id: Option<&str>,
|
|
53
|
-
) -> Result<Vec<serde_json::Value>, MessagingError> {
|
|
54
|
-
let current_db = workspace.join(".team/runtime/team.db");
|
|
55
|
-
let legacy_db = workspace.join(".team/team.db");
|
|
56
|
-
if current_db.exists() {
|
|
57
|
-
return results_for_case_in_db(
|
|
58
|
-
¤t_db,
|
|
59
|
-
case_id,
|
|
60
|
-
owner_team_id,
|
|
61
|
-
requested_owner_team_id,
|
|
62
|
-
);
|
|
63
|
-
}
|
|
64
|
-
if legacy_db.exists() {
|
|
65
|
-
return results_for_case_in_db(&legacy_db, case_id, owner_team_id, requested_owner_team_id);
|
|
66
|
-
}
|
|
67
|
-
Ok(Vec::new())
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
fn results_for_case_in_db(
|
|
71
|
-
db_path: &Path,
|
|
72
|
-
case_id: &str,
|
|
73
|
-
owner_team_id: Option<&str>,
|
|
74
|
-
requested_owner_team_id: Option<&str>,
|
|
75
|
-
) -> Result<Vec<serde_json::Value>, MessagingError> {
|
|
76
|
-
let conn = crate::db::schema::open_db(db_path)?;
|
|
77
|
-
// The second predicate works around
|
|
78
|
-
// `.team/bugs/BUG-caseid-writable-when-route-undeclared-20260730.md`;
|
|
79
|
-
// it is not design intent.
|
|
80
|
-
let case_predicate = "task_id = ?1 or json_extract(envelope,'$.presentation.case_id') = ?1";
|
|
81
|
-
let sql = match owner_team_id {
|
|
82
|
-
Some(_) => format!(
|
|
83
|
-
"select envelope from results where ({case_predicate}) \
|
|
84
|
-
and (owner_team_id = ?2 or owner_team_id = ?3) \
|
|
85
|
-
order by created_at, result_id"
|
|
86
|
-
),
|
|
87
|
-
None => format!(
|
|
88
|
-
"select envelope from results where {case_predicate} order by created_at, result_id"
|
|
89
|
-
),
|
|
90
|
-
};
|
|
91
|
-
let mut stmt = conn.prepare(&sql)?;
|
|
92
|
-
let row_mapper = |row: &rusqlite::Row<'_>| row.get::<_, String>(0);
|
|
93
|
-
let rows = match (owner_team_id, requested_owner_team_id) {
|
|
94
|
-
(Some(team), Some(requested_team)) => {
|
|
95
|
-
stmt.query_map(params![case_id, team, requested_team], row_mapper)
|
|
96
|
-
}
|
|
97
|
-
(Some(team), None) => stmt.query_map(params![case_id, team, team], row_mapper),
|
|
98
|
-
(None, _) => stmt.query_map(params![case_id], row_mapper),
|
|
99
|
-
}?
|
|
100
|
-
.collect::<Result<Vec<_>, _>>()?;
|
|
101
|
-
rows.into_iter()
|
|
102
|
-
.map(|envelope| serde_json::from_str(&envelope).map_err(MessagingError::from))
|
|
103
|
-
.collect()
|
|
104
|
-
}
|
|
105
|
-
|
|
106
48
|
/// `collect` (`results.py:45`):投递 pending、捞 uncollected results、校验 envelope、更新任务态、
|
|
107
49
|
/// 写 team_state、ensure coordinator。CLI `collect` + coordinator tick 调。
|
|
108
50
|
pub fn collect(
|
|
@@ -1090,52 +1090,6 @@ fn collect_rejects_message_scoped_result_without_matching_recipient() {
|
|
|
1090
1090
|
);
|
|
1091
1091
|
}
|
|
1092
1092
|
|
|
1093
|
-
#[test]
|
|
1094
|
-
fn allow_peer_talk_records_bidirectional_allowlist_and_event() {
|
|
1095
|
-
let ws = tmp_ws("allowpeer");
|
|
1096
|
-
let out = allow_peer_talk(&ws, "alice", "bob").unwrap();
|
|
1097
|
-
assert_eq!(out.get("ok").and_then(|v| v.as_bool()), Some(true));
|
|
1098
|
-
assert_eq!(out.get("a").and_then(|v| v.as_str()), Some("alice"));
|
|
1099
|
-
assert_eq!(out.get("b").and_then(|v| v.as_str()), Some("bob"));
|
|
1100
|
-
assert_eq!(
|
|
1101
|
-
out.get("status").and_then(|v| v.as_str()),
|
|
1102
|
-
Some("compat_noop")
|
|
1103
|
-
);
|
|
1104
|
-
assert_eq!(
|
|
1105
|
-
out.get("reason").and_then(|v| v.as_str()),
|
|
1106
|
-
Some("team_scoped_peer_messages_enabled")
|
|
1107
|
-
);
|
|
1108
|
-
|
|
1109
|
-
let store = store_for(&ws);
|
|
1110
|
-
let conn = seed_conn(&store);
|
|
1111
|
-
let rows = conn
|
|
1112
|
-
.prepare("select a, b from peer_allowlist order by a, b")
|
|
1113
|
-
.unwrap()
|
|
1114
|
-
.query_map([], |row| {
|
|
1115
|
-
Ok((row.get::<_, String>(0)?, row.get::<_, String>(1)?))
|
|
1116
|
-
})
|
|
1117
|
-
.unwrap()
|
|
1118
|
-
.collect::<Result<Vec<_>, _>>()
|
|
1119
|
-
.unwrap();
|
|
1120
|
-
assert_eq!(
|
|
1121
|
-
rows,
|
|
1122
|
-
vec![
|
|
1123
|
-
("alice".to_string(), "bob".to_string()),
|
|
1124
|
-
("bob".to_string(), "alice".to_string()),
|
|
1125
|
-
]
|
|
1126
|
-
);
|
|
1127
|
-
|
|
1128
|
-
let events = EventLog::new(&ws).tail(10).unwrap();
|
|
1129
|
-
let event = events
|
|
1130
|
-
.iter()
|
|
1131
|
-
.find(|event| {
|
|
1132
|
-
event.get("event").and_then(|v| v.as_str()) == Some("communication.peer_allowed")
|
|
1133
|
-
})
|
|
1134
|
-
.expect("communication.peer_allowed event");
|
|
1135
|
-
assert_eq!(event.get("a").and_then(|v| v.as_str()), Some("alice"));
|
|
1136
|
-
assert_eq!(event.get("b").and_then(|v| v.as_str()), Some("bob"));
|
|
1137
|
-
}
|
|
1138
|
-
|
|
1139
1093
|
// ════════════════════════════════════════════════════════════════════════
|
|
1140
1094
|
// GROUP R — run_comms_selftest: §84 / MUST-NOT-13 zero-provider-SDK gate.
|
|
1141
1095
|
// diagnose/comms.py:21-47. The whole point: assert zero provider client calls.
|
|
@@ -527,7 +527,6 @@ pub enum StateWriteIntent<'a> {
|
|
|
527
527
|
TaskRepair {
|
|
528
528
|
team_key: Option<&'a str>,
|
|
529
529
|
},
|
|
530
|
-
FakeE2eSeed,
|
|
531
530
|
SelfMigration,
|
|
532
531
|
}
|
|
533
532
|
|
|
@@ -743,10 +742,7 @@ fn route_direct(
|
|
|
743
742
|
},
|
|
744
743
|
// TaskRepair -> `save_team_scoped_state` (cli/adapters.rs:675).
|
|
745
744
|
StateWriteIntent::TaskRepair { .. } => helper_write_team_scoped(workspace, state),
|
|
746
|
-
//
|
|
747
|
-
// callsites live at cli/adapters.rs:1035/1070 and
|
|
748
|
-
// state/persist.rs:1127; S1a routes them to the root helper.
|
|
749
|
-
StateWriteIntent::FakeE2eSeed => helper_write_root(workspace, state),
|
|
745
|
+
// SelfMigration persists through the root helper.
|
|
750
746
|
StateWriteIntent::SelfMigration => helper_write_root(workspace, state),
|
|
751
747
|
}
|
|
752
748
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@team-agent/installer",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.112",
|
|
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.112",
|
|
24
|
+
"@team-agent/cli-darwin-x64": "0.5.112",
|
|
25
|
+
"@team-agent/cli-linux-x64": "0.5.112"
|
|
26
26
|
},
|
|
27
27
|
"scripts": {
|
|
28
28
|
"postinstall": "node npm/bincheck.mjs",
|
package/templates/team_state.md
CHANGED
|
@@ -29,4 +29,4 @@ Pending.
|
|
|
29
29
|
|
|
30
30
|
## Next Step
|
|
31
31
|
|
|
32
|
-
-
|
|
32
|
+
- Review role files and permissions, then run `team-agent quick-start`; use `team-agent doctor` to diagnose the team.
|
|
@@ -1,164 +0,0 @@
|
|
|
1
|
-
use super::*;
|
|
2
|
-
|
|
3
|
-
// =============================================================================
|
|
4
|
-
// P0-1 LANE — `allow-peer-talk` byte-lock (was ZERO-test; a SHIPPED compat-noop).
|
|
5
|
-
//
|
|
6
|
-
// golden: cli/commands.py:422 -> messaging/leader.py:25 allow_peer_talk(ws,a,b)
|
|
7
|
-
// -> message_store/core.py:326 allow_peer (INSERT OR IGNORE BOTH (a,b) & (b,a))
|
|
8
|
-
// + events.py:27-35 EventLog.write (sort_keys=True -> .team/logs/events.jsonl).
|
|
9
|
-
//
|
|
10
|
-
// Golden bytes captured live (PYTHONPATH=.../src python3 /tmp/probe_app*.py):
|
|
11
|
-
// return (insertion order): {"ok":true,"a":..,"b":..,"status":"compat_noop",
|
|
12
|
-
// "reason":"team_scoped_peer_messages_enabled"}
|
|
13
|
-
// return (--json sort_keys): keys a,b,ok,reason,status
|
|
14
|
-
// event line (sort_keys): {"a":..,"b":..,"event":"communication.peer_allowed","ts":..}
|
|
15
|
-
// idempotent: 2nd call -> identical return + a SECOND event line
|
|
16
|
-
// (event fires per-call; DB rows are insert-or-ignore)
|
|
17
|
-
// unknown/undeclared agents: NO validation -> ok:true compat_noop, exit 0
|
|
18
|
-
//
|
|
19
|
-
// Rust impl: messaging/peers.rs:11 + message_store.rs:251 + adapters.rs:173.
|
|
20
|
-
// serde_json `preserve_order` is ON (Cargo.toml:24) so the default (human) output
|
|
21
|
-
// iterates dict keys in golden insertion order. These tests LOCK the contract so a
|
|
22
|
-
// porter/refactor cannot silently drift this live verb.
|
|
23
|
-
// =============================================================================
|
|
24
|
-
|
|
25
|
-
fn app_out(r: CmdResult) -> serde_json::Value {
|
|
26
|
-
match r.output {
|
|
27
|
-
CmdOutput::Json(v) => v,
|
|
28
|
-
_ => panic!("allow-peer-talk must emit a Json CmdOutput"),
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
fn app_events(ws: &std::path::Path) -> Vec<String> {
|
|
33
|
-
std::fs::read_to_string(ws.join(".team").join("logs").join("events.jsonl"))
|
|
34
|
-
.unwrap_or_default()
|
|
35
|
-
.lines()
|
|
36
|
-
.map(str::to_string)
|
|
37
|
-
.collect()
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
fn app_call(ws: &std::path::Path, a: &str, b: &str) -> CmdResult {
|
|
41
|
-
cmd_allow_peer_talk(&AllowPeerTalkArgs {
|
|
42
|
-
a: a.to_string(),
|
|
43
|
-
b: b.to_string(),
|
|
44
|
-
workspace: ws.to_path_buf(),
|
|
45
|
-
json: false,
|
|
46
|
-
team: None,
|
|
47
|
-
})
|
|
48
|
-
.expect("allow-peer-talk is a compat-noop and must not error")
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
// ── return dict: exact compat_noop shape + golden insertion order (drives human emit) ──
|
|
52
|
-
#[test]
|
|
53
|
-
fn allow_peer_talk_output_byte_locks_golden_compat_noop() {
|
|
54
|
-
let ws = tmp_workspace();
|
|
55
|
-
let v = app_out(app_call(&ws, "alpha", "bravo"));
|
|
56
|
-
assert_eq!(
|
|
57
|
-
v,
|
|
58
|
-
json!({
|
|
59
|
-
"ok": true,
|
|
60
|
-
"a": "alpha",
|
|
61
|
-
"b": "bravo",
|
|
62
|
-
"status": "compat_noop",
|
|
63
|
-
"reason": "team_scoped_peer_messages_enabled"
|
|
64
|
-
}),
|
|
65
|
-
"golden leader.py:28 return dict (key set + values); got {v:?}"
|
|
66
|
-
);
|
|
67
|
-
let order: Vec<&str> = v
|
|
68
|
-
.as_object()
|
|
69
|
-
.expect("object")
|
|
70
|
-
.keys()
|
|
71
|
-
.map(String::as_str)
|
|
72
|
-
.collect();
|
|
73
|
-
assert_eq!(
|
|
74
|
-
order,
|
|
75
|
-
vec!["ok", "a", "b", "status", "reason"],
|
|
76
|
-
"golden dict insertion order ok,a,b,status,reason drives the default human emit key order \
|
|
77
|
-
(helpers.py:16-19); got {order:?}"
|
|
78
|
-
);
|
|
79
|
-
let _ = std::fs::remove_dir_all(&ws);
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// ── event: exactly one `communication.peer_allowed` line, sort_keys byte form, payload {a,b} ──
|
|
83
|
-
#[test]
|
|
84
|
-
fn allow_peer_talk_event_byte_locks_golden_line() {
|
|
85
|
-
let ws = tmp_workspace();
|
|
86
|
-
let _ = app_call(&ws, "alpha", "bravo");
|
|
87
|
-
let lines = app_events(&ws);
|
|
88
|
-
assert_eq!(lines.len(), 1, "exactly one event per call; got {lines:?}");
|
|
89
|
-
let v: serde_json::Value = serde_json::from_str(&lines[0]).expect("event line must be json");
|
|
90
|
-
let ts = v["ts"].as_str().expect("event has string ts").to_string();
|
|
91
|
-
let expected = format!(
|
|
92
|
-
"{{\"a\": \"alpha\", \"b\": \"bravo\", \"event\": \"communication.peer_allowed\", \"ts\": \"{ts}\"}}"
|
|
93
|
-
);
|
|
94
|
-
assert_eq!(
|
|
95
|
-
lines[0], expected,
|
|
96
|
-
"golden events.py:35 json.dumps(...,sort_keys=True) line: sorted keys a,b,event,ts with \
|
|
97
|
-
Python `, `/`: ` spacing; got {}",
|
|
98
|
-
lines[0]
|
|
99
|
-
);
|
|
100
|
-
let _ = std::fs::remove_dir_all(&ws);
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
// ── idempotency: identical return, event fires on EVERY call (rows are insert-or-ignore) ──
|
|
104
|
-
#[test]
|
|
105
|
-
fn allow_peer_talk_is_idempotent_and_fires_event_per_call() {
|
|
106
|
-
let ws = tmp_workspace();
|
|
107
|
-
let v1 = app_out(app_call(&ws, "alpha", "bravo"));
|
|
108
|
-
let v2 = app_out(app_call(&ws, "alpha", "bravo")); // duplicate pair
|
|
109
|
-
assert_eq!(
|
|
110
|
-
v1, v2,
|
|
111
|
-
"repeated allow returns identical compat_noop output"
|
|
112
|
-
);
|
|
113
|
-
let lines = app_events(&ws);
|
|
114
|
-
assert_eq!(
|
|
115
|
-
lines.len(),
|
|
116
|
-
2,
|
|
117
|
-
"golden fires communication.peer_allowed on EVERY call (event NOT deduped; only DB rows \
|
|
118
|
-
are insert-or-ignore); got {lines:?}"
|
|
119
|
-
);
|
|
120
|
-
let _ = std::fs::remove_dir_all(&ws);
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
// ── permissive: undeclared/unknown agents still succeed (golden has NO validation path) ──
|
|
124
|
-
#[test]
|
|
125
|
-
fn allow_peer_talk_unknown_agents_succeed_no_validation() {
|
|
126
|
-
let ws = tmp_workspace(); // no state.json / no spec: agents never declared
|
|
127
|
-
let r = app_call(&ws, "ghost", "phantom-x");
|
|
128
|
-
assert_eq!(
|
|
129
|
-
r.exit,
|
|
130
|
-
ExitCode::Ok,
|
|
131
|
-
"golden leader.py:25-28 never validates agent ids -> no error, exit 0"
|
|
132
|
-
);
|
|
133
|
-
let v = app_out(r);
|
|
134
|
-
assert_eq!(
|
|
135
|
-
v,
|
|
136
|
-
json!({
|
|
137
|
-
"ok": true,
|
|
138
|
-
"a": "ghost",
|
|
139
|
-
"b": "phantom-x",
|
|
140
|
-
"status": "compat_noop",
|
|
141
|
-
"reason": "team_scoped_peer_messages_enabled"
|
|
142
|
-
}),
|
|
143
|
-
"unknown agents still get compat_noop (no unknown-agent error string/exit); got {v:?}"
|
|
144
|
-
);
|
|
145
|
-
let _ = std::fs::remove_dir_all(&ws);
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
#[test]
|
|
149
|
-
fn allow_peer_talk_explicit_team_is_rejected_until_backend_is_scoped() {
|
|
150
|
-
let ws = tmp_workspace();
|
|
151
|
-
let err = cmd_allow_peer_talk(&AllowPeerTalkArgs {
|
|
152
|
-
a: "alpha".to_string(),
|
|
153
|
-
b: "bravo".to_string(),
|
|
154
|
-
workspace: ws.clone(),
|
|
155
|
-
json: true,
|
|
156
|
-
team: Some("current".to_string()),
|
|
157
|
-
})
|
|
158
|
-
.expect_err("explicit --team must not silently write the global peer allowlist");
|
|
159
|
-
assert!(
|
|
160
|
-
err.to_string().contains("not supported yet"),
|
|
161
|
-
"allow-peer-talk --team must be an explicit refusal until backend supports scoped writes; got {err}"
|
|
162
|
-
);
|
|
163
|
-
let _ = std::fs::remove_dir_all(&ws);
|
|
164
|
-
}
|