@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
|
@@ -20,9 +20,8 @@
|
|
|
20
20
|
//! ---
|
|
21
21
|
//!
|
|
22
22
|
//! coordinator 共享数据面:常量 / newtype / 穷尽 enum / metadata / schema health /
|
|
23
|
-
//! report 结构 / abnormal-track 数据型 /
|
|
23
|
+
//! report 结构 / abnormal-track 数据型 / cross-dep 占位。
|
|
24
24
|
|
|
25
|
-
use std::collections::BTreeSet;
|
|
26
25
|
use std::path::{Path, PathBuf};
|
|
27
26
|
|
|
28
27
|
use serde::{Deserialize, Serialize};
|
|
@@ -49,10 +48,6 @@ pub const DEFAULT_TICK_INTERVAL_SEC: f64 = 5.0;
|
|
|
49
48
|
/// 指数退避封顶(`__main__.py:65` `min(.., 60.0)`)。bug-084 崩溃循环防护。
|
|
50
49
|
pub const BACKOFF_MAX_SEC: f64 = 60.0;
|
|
51
50
|
|
|
52
|
-
/// watch log rotation marker(`watch.py:22`,字节级一致 —— 测试钉死)。
|
|
53
|
-
pub const ROTATION_MARKER: &str =
|
|
54
|
-
"[watch] log rotated; archived segment events.jsonl.1 not replayed — historical replay deferred to a future --replay flag";
|
|
55
|
-
|
|
56
51
|
// ===========================================================================
|
|
57
52
|
// NEWTYPES(§3:Pid / WorkspacePath 不与裸 int/PathBuf 混传)
|
|
58
53
|
// ===========================================================================
|
|
@@ -445,21 +440,6 @@ pub struct TeamPresenceSnapshot {
|
|
|
445
440
|
pub restart_in_progress: bool,
|
|
446
441
|
}
|
|
447
442
|
|
|
448
|
-
// ===========================================================================
|
|
449
|
-
// WatchCursor(watch/__init__.py)
|
|
450
|
-
// ===========================================================================
|
|
451
|
-
|
|
452
|
-
/// watch tail 游标(`watch.py:14-19`)。rotation 检测靠 `archive_signature` 变化 + `offset > size`
|
|
453
|
-
/// (`watch.py:73-83`)。`archive_signature = Some((size, mtime_ns))`。
|
|
454
|
-
#[derive(Debug, Clone, Default, PartialEq, Eq)]
|
|
455
|
-
pub struct WatchCursor {
|
|
456
|
-
pub event_offset: u64,
|
|
457
|
-
pub seen_result_ids: BTreeSet<String>,
|
|
458
|
-
pub initialized: bool,
|
|
459
|
-
/// `(size, mtime_ns)`;`mtime_ns: i128`(防溢出)。
|
|
460
|
-
pub archive_signature: Option<(u64, i128)>,
|
|
461
|
-
}
|
|
462
|
-
|
|
463
443
|
// ===========================================================================
|
|
464
444
|
// CROSS-DEP placeholders(其他并行 lane 拥有真名;leader 集成时 reconcile)
|
|
465
445
|
// ===========================================================================
|
|
@@ -830,22 +830,6 @@ impl MessageStore {
|
|
|
830
830
|
Ok(rows.into_iter().rev().collect())
|
|
831
831
|
}
|
|
832
832
|
|
|
833
|
-
/// Allow direct peer messages in both directions. Golden stores `(a,b)` and
|
|
834
|
-
/// `(b,a)` so either sender/recipient lookup can use a single ordered key.
|
|
835
|
-
pub fn allow_peer(&self, a: &str, b: &str) -> Result<(), MessageStoreError> {
|
|
836
|
-
let conn = crate::db::schema::open_db(&self.path)?;
|
|
837
|
-
let now = now_ts();
|
|
838
|
-
conn.execute(
|
|
839
|
-
"insert or ignore into peer_allowlist(a, b, created_at) values (?1, ?2, ?3)",
|
|
840
|
-
params![a, b, now.as_str()],
|
|
841
|
-
)?;
|
|
842
|
-
conn.execute(
|
|
843
|
-
"insert or ignore into peer_allowlist(a, b, created_at) values (?1, ?2, ?3)",
|
|
844
|
-
params![b, a, now.as_str()],
|
|
845
|
-
)?;
|
|
846
|
-
Ok(())
|
|
847
|
-
}
|
|
848
|
-
|
|
849
833
|
/// `claim_leader_notification_delivery` (`leader_notification_log.py:30-101`):
|
|
850
834
|
/// `INSERT OR IGNORE` on PK `(result_id, owner_team_id, owner_epoch)`. rowcount==1
|
|
851
835
|
/// → `claimed_by_you`; else read the existing winner row → `already_notified_by`.
|
|
@@ -1421,32 +1405,6 @@ mod tests {
|
|
|
1421
1405
|
assert_eq!(legacy_epoch_from_uuid(Some("uuid-BBB")), 122688376);
|
|
1422
1406
|
}
|
|
1423
1407
|
|
|
1424
|
-
#[test]
|
|
1425
|
-
fn allow_peer_inserts_bidirectional_rows_idempotently() {
|
|
1426
|
-
let s = store();
|
|
1427
|
-
s.allow_peer("alice", "bob").unwrap();
|
|
1428
|
-
s.allow_peer("alice", "bob").unwrap();
|
|
1429
|
-
|
|
1430
|
-
let c = read(&s);
|
|
1431
|
-
let mut rows = c
|
|
1432
|
-
.prepare("select a, b from peer_allowlist order by a, b")
|
|
1433
|
-
.unwrap()
|
|
1434
|
-
.query_map([], |row| {
|
|
1435
|
-
Ok((row.get::<_, String>(0)?, row.get::<_, String>(1)?))
|
|
1436
|
-
})
|
|
1437
|
-
.unwrap()
|
|
1438
|
-
.collect::<Result<Vec<_>, _>>()
|
|
1439
|
-
.unwrap();
|
|
1440
|
-
rows.sort();
|
|
1441
|
-
assert_eq!(
|
|
1442
|
-
rows,
|
|
1443
|
-
vec![
|
|
1444
|
-
("alice".to_string(), "bob".to_string()),
|
|
1445
|
-
("bob".to_string(), "alice".to_string()),
|
|
1446
|
-
]
|
|
1447
|
-
);
|
|
1448
|
-
}
|
|
1449
|
-
|
|
1450
1408
|
// ════════════════════════ FIX-LOOP (wave-1) RED test ════════════════════════
|
|
1451
1409
|
// B1: mark() error column = coalesce(?, error) — a NEW error overwrites, but a
|
|
1452
1410
|
// mark WITHOUT an error must PRESERVE the existing error (current impl clobbers
|
|
@@ -88,7 +88,6 @@ pub const MANAGED_TABLE_LAYOUTS: &[(&str, &[&str])] = &[
|
|
|
88
88
|
"updated_at",
|
|
89
89
|
],
|
|
90
90
|
),
|
|
91
|
-
("peer_allowlist", &["a", "b", "created_at"]),
|
|
92
91
|
(
|
|
93
92
|
"result_watchers",
|
|
94
93
|
&[
|
|
@@ -129,7 +128,6 @@ const CREATE_TABLE_TEMPLATES: &[(&str, &str)] = &[
|
|
|
129
128
|
("scheduled_events", "create table if not exists __TABLE__ (\n id integer primary key,\n owner_team_id text,\n due_at text not null,\n target text not null,\n kind text not null,\n payload_json text not null,\n status text not null,\n created_at text not null,\n fired_at text,\n result_json text\n )"),
|
|
130
129
|
("delivery_tokens", "create table if not exists __TABLE__ (\n message_id text primary key,\n unique_token text not null,\n injected_at text not null,\n visible_at text,\n consumed_at text,\n failed_at text,\n failure_reason text\n )"),
|
|
131
130
|
("agent_health", "create table if not exists __TABLE__ (\n owner_team_id text,\n agent_id text not null,\n status text not null,\n last_output_at text,\n context_usage_pct integer,\n current_task_id text,\n updated_at text not null,\n unique(owner_team_id, agent_id)\n )"),
|
|
132
|
-
("peer_allowlist", "create table if not exists __TABLE__ (\n a text not null,\n b text not null,\n created_at text not null,\n primary key (a, b)\n )"),
|
|
133
131
|
("result_watchers", "create table if not exists __TABLE__ (\n watcher_id text primary key,\n owner_team_id text,\n task_id text,\n agent_id text,\n message_id text,\n leader_id text not null,\n recipient text,\n status text not null,\n created_at text not null,\n completed_at text,\n result_id text,\n notified_message_id text,\n error text\n )"),
|
|
134
132
|
("leader_notification_log", "create table if not exists __TABLE__ (\n result_id text not null,\n owner_team_id text not null default '',\n owner_epoch integer not null default 0,\n leader_session_uuid text,\n notified_message_id text not null,\n notified_at text not null,\n leader_pane_id_at_notify text,\n envelope_content_hash text,\n primary key (result_id, owner_team_id, owner_epoch)\n )"),
|
|
135
133
|
];
|
|
@@ -630,12 +628,12 @@ mod tests {
|
|
|
630
628
|
let path = temp_db();
|
|
631
629
|
build_legacy(&path);
|
|
632
630
|
|
|
633
|
-
// 迁移前:diagnosis = schema_repair_available,全
|
|
631
|
+
// 迁移前:diagnosis = schema_repair_available,全 7 表 diff(4 漂移 + 3 缺失)。
|
|
634
632
|
let before = schema_diagnosis(&path, SCHEMA_VERSION).unwrap();
|
|
635
633
|
assert_eq!(before.status, "schema_repair_available");
|
|
636
634
|
assert!(!before.ok);
|
|
637
635
|
assert_eq!(before.user_version, 1);
|
|
638
|
-
assert_eq!(before.layout_diffs.len(),
|
|
636
|
+
assert_eq!(before.layout_diffs.len(), MANAGED_TABLE_LAYOUTS.len());
|
|
639
637
|
|
|
640
638
|
// initialize_schema 走 ensure_table_layout(rebuild)+ 建表 + user_version=3。
|
|
641
639
|
let conn = crate::db::schema::open_db(&path).unwrap();
|
|
@@ -697,6 +695,48 @@ mod tests {
|
|
|
697
695
|
assert_eq!(d.user_version, SCHEMA_VERSION);
|
|
698
696
|
}
|
|
699
697
|
|
|
698
|
+
// A drifted legacy peer table is inert: no diagnosis, rebuild, backup or row loss.
|
|
699
|
+
#[test]
|
|
700
|
+
fn legacy_peer_allowlist_is_inert_and_preserved() {
|
|
701
|
+
let path = temp_db();
|
|
702
|
+
let conn = crate::db::schema::open_db(&path).unwrap();
|
|
703
|
+
initialize_schema(&conn, Some(&path)).unwrap();
|
|
704
|
+
conn.execute_batch(
|
|
705
|
+
"create table peer_allowlist (created_at text not null, b text not null, a text not null, primary key (a, b));
|
|
706
|
+
insert into peer_allowlist(a, b, created_at) values ('worker', 'reviewer', 'v4');",
|
|
707
|
+
)
|
|
708
|
+
.unwrap();
|
|
709
|
+
assert!(layout_diffs(&conn).unwrap().is_empty());
|
|
710
|
+
initialize_schema(&conn, Some(&path)).unwrap();
|
|
711
|
+
assert_eq!(layout(&conn, "peer_allowlist"), ["created_at", "b", "a"]);
|
|
712
|
+
let row: (String, String, String) = conn
|
|
713
|
+
.query_row("select a, b, created_at from peer_allowlist", [], |r| {
|
|
714
|
+
Ok((r.get(0)?, r.get(1)?, r.get(2)?))
|
|
715
|
+
})
|
|
716
|
+
.unwrap();
|
|
717
|
+
assert_eq!(
|
|
718
|
+
row,
|
|
719
|
+
(
|
|
720
|
+
"worker".to_string(),
|
|
721
|
+
"reviewer".to_string(),
|
|
722
|
+
"v4".to_string()
|
|
723
|
+
)
|
|
724
|
+
);
|
|
725
|
+
drop(conn);
|
|
726
|
+
let diagnosis = schema_diagnosis(&path, SCHEMA_VERSION).unwrap();
|
|
727
|
+
assert!(
|
|
728
|
+
diagnosis.ok,
|
|
729
|
+
"legacy peer table must remain inert: {diagnosis:?}"
|
|
730
|
+
);
|
|
731
|
+
assert!(
|
|
732
|
+
!std::fs::read_dir(path.parent().unwrap())
|
|
733
|
+
.unwrap()
|
|
734
|
+
.filter_map(|entry| entry.ok())
|
|
735
|
+
.any(|entry| entry.file_name().to_string_lossy().contains("pre-migration")),
|
|
736
|
+
"legacy peer table must not trigger a migration backup"
|
|
737
|
+
);
|
|
738
|
+
}
|
|
739
|
+
|
|
700
740
|
#[test]
|
|
701
741
|
fn diagnosis_missing_db() {
|
|
702
742
|
let path = temp_db(); // 未创建文件
|
|
@@ -713,7 +753,7 @@ mod tests {
|
|
|
713
753
|
build_legacy(&path);
|
|
714
754
|
let conn = crate::db::schema::open_db(&path).unwrap();
|
|
715
755
|
let events1 = ensure_table_layout(&conn, SCHEMA_VERSION, Some(&path)).unwrap();
|
|
716
|
-
assert_eq!(events1.len(),
|
|
756
|
+
assert_eq!(events1.len(), MANAGED_TABLE_LAYOUTS.len()); // 4 漂移 rebuild + 3 缺失建表
|
|
717
757
|
for e in &events1 {
|
|
718
758
|
assert_eq!(e.row_count_before, e.row_count_after);
|
|
719
759
|
}
|
|
@@ -759,7 +799,7 @@ mod tests {
|
|
|
759
799
|
build_legacy(&path);
|
|
760
800
|
let conn = crate::db::schema::open_db(&path).unwrap();
|
|
761
801
|
let events = ensure_table_layout(&conn, SCHEMA_VERSION, None).unwrap(); // None 不再 Err
|
|
762
|
-
assert_eq!(events.len(),
|
|
802
|
+
assert_eq!(events.len(), MANAGED_TABLE_LAYOUTS.len());
|
|
763
803
|
assert_eq!(
|
|
764
804
|
table_layout(&conn, "messages").unwrap()[..2],
|
|
765
805
|
["message_id", "owner_team_id"]
|
|
@@ -871,7 +911,7 @@ mod tests {
|
|
|
871
911
|
} => {
|
|
872
912
|
assert!(diagnosis.ok);
|
|
873
913
|
assert_eq!(diagnosis.user_version, SCHEMA_VERSION);
|
|
874
|
-
assert_eq!(rebuilds.len(),
|
|
914
|
+
assert_eq!(rebuilds.len(), MANAGED_TABLE_LAYOUTS.len());
|
|
875
915
|
}
|
|
876
916
|
other => panic!("expected Fixed, got {other:?}"),
|
|
877
917
|
}
|
|
@@ -921,8 +961,8 @@ mod tests {
|
|
|
921
961
|
.collect();
|
|
922
962
|
assert_eq!(
|
|
923
963
|
rebuilds.len(),
|
|
924
|
-
|
|
925
|
-
"
|
|
964
|
+
MANAGED_TABLE_LAYOUTS.len(),
|
|
965
|
+
"每张 managed 表各一条 schema.layout_rebuild"
|
|
926
966
|
);
|
|
927
967
|
for e in &rebuilds {
|
|
928
968
|
assert_eq!(
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
//!
|
|
2
2
|
//! `team.db` schema 初始化(真相源 `message_store/schema.py` + `schema_migration.py`)。
|
|
3
3
|
//!
|
|
4
|
-
//! slice 1:fresh DB schema ——
|
|
4
|
+
//! slice 1:fresh DB schema —— 7 表 DDL、6 索引、ALTER 式列迁移、agent_health 重建、
|
|
5
5
|
//! WAL/busy_timeout pragmas、`user_version = SCHEMA_VERSION`。
|
|
6
6
|
//! slice 2(待做):列**顺序**漂移的整表 rebuild(`ensure_table_layout`/`_rebuild_tables`)
|
|
7
7
|
//! 与 `schema_diagnosis`,验 legacy_team_db_fixture + schema_migration 契约。
|
|
@@ -15,14 +15,13 @@ use crate::db::DbError;
|
|
|
15
15
|
/// `schema.py:90`。
|
|
16
16
|
pub const SCHEMA_VERSION: i64 = 5;
|
|
17
17
|
|
|
18
|
-
///
|
|
18
|
+
/// 7 张表的 DDL(逐字照搬 `schema.py:initialize_schema` 的内联建表;含 `if not exists`)。
|
|
19
19
|
/// 顺序与 Python 一致(leader_notification_log 在 ensure 块后创建)。
|
|
20
20
|
const CREATE_MESSAGES: &str = "create table if not exists messages (\n message_id text primary key,\n owner_team_id text,\n task_id text,\n sender text,\n recipient text,\n reply_to text,\n requires_ack integer,\n status text,\n content text,\n presentation text not null default '{\"sink\":\"leader\",\"class\":\"message\"}',\n artifact_refs text,\n created_at text,\n updated_at text,\n delivered_at text,\n acknowledged_at text,\n error text,\n delivery_attempts integer not null default 0\n )";
|
|
21
21
|
const CREATE_RESULTS: &str = "create table if not exists results (\n result_id text primary key,\n owner_team_id text,\n task_id text not null,\n agent_id text not null,\n envelope text not null,\n status text not null,\n created_at text not null\n )";
|
|
22
22
|
const CREATE_SCHEDULED_EVENTS: &str = "create table if not exists scheduled_events (\n id integer primary key,\n owner_team_id text,\n due_at text not null,\n target text not null,\n kind text not null,\n payload_json text not null,\n status text not null,\n created_at text not null,\n fired_at text,\n result_json text\n )";
|
|
23
23
|
const CREATE_DELIVERY_TOKENS: &str = "create table if not exists delivery_tokens (\n message_id text primary key,\n unique_token text not null,\n injected_at text not null,\n visible_at text,\n consumed_at text,\n failed_at text,\n failure_reason text\n )";
|
|
24
24
|
const CREATE_AGENT_HEALTH: &str = "create table if not exists agent_health (\n owner_team_id text,\n agent_id text not null,\n status text not null,\n last_output_at text,\n context_usage_pct integer,\n current_task_id text,\n updated_at text not null,\n unique(owner_team_id, agent_id)\n )";
|
|
25
|
-
const CREATE_PEER_ALLOWLIST: &str = "create table if not exists peer_allowlist (\n a text not null,\n b text not null,\n created_at text not null,\n primary key (a, b)\n )";
|
|
26
25
|
const CREATE_RESULT_WATCHERS: &str = "create table if not exists result_watchers (\n watcher_id text primary key,\n owner_team_id text,\n task_id text,\n agent_id text,\n message_id text,\n leader_id text not null,\n recipient text,\n status text not null,\n created_at text not null,\n completed_at text,\n result_id text,\n notified_message_id text,\n error text\n )";
|
|
27
26
|
const CREATE_LEADER_NOTIFICATION_LOG: &str = "create table if not exists leader_notification_log (\n result_id text not null,\n owner_team_id text not null default '',\n owner_epoch integer not null default 0,\n leader_session_uuid text,\n notified_message_id text not null,\n notified_at text not null,\n leader_pane_id_at_notify text,\n envelope_content_hash text,\n primary key (result_id, owner_team_id, owner_epoch)\n )";
|
|
28
27
|
const CREATE_AGENT_HEALTH_NEW: &str = "create table agent_health_new (\n owner_team_id text,\n agent_id text not null,\n status text not null,\n last_output_at text,\n context_usage_pct integer,\n current_task_id text,\n updated_at text not null,\n unique(owner_team_id, agent_id)\n )";
|
|
@@ -96,7 +95,6 @@ const AGENT_HEALTH_COLUMNS: &[&str] = &[
|
|
|
96
95
|
"current_task_id",
|
|
97
96
|
"updated_at",
|
|
98
97
|
];
|
|
99
|
-
const PEER_ALLOWLIST_COLUMNS: &[&str] = &["a", "b", "created_at"];
|
|
100
98
|
const RESULT_WATCHER_COLUMNS: &[&str] = &[
|
|
101
99
|
"owner_team_id",
|
|
102
100
|
"watcher_id",
|
|
@@ -249,7 +247,6 @@ pub fn initialize_schema(
|
|
|
249
247
|
CREATE_SCHEDULED_EVENTS,
|
|
250
248
|
CREATE_DELIVERY_TOKENS,
|
|
251
249
|
CREATE_AGENT_HEALTH,
|
|
252
|
-
CREATE_PEER_ALLOWLIST,
|
|
253
250
|
CREATE_RESULT_WATCHERS,
|
|
254
251
|
] {
|
|
255
252
|
tx.execute(ddl, [])?;
|
|
@@ -293,7 +290,6 @@ pub fn initialize_schema(
|
|
|
293
290
|
)?;
|
|
294
291
|
ensure_table_columns(&tx, "delivery_tokens", DELIVERY_TOKEN_COLUMNS, &[])?;
|
|
295
292
|
migrate_agent_health_owner_team_id(&tx)?;
|
|
296
|
-
ensure_table_columns(&tx, "peer_allowlist", PEER_ALLOWLIST_COLUMNS, &[])?;
|
|
297
293
|
ensure_table_columns(
|
|
298
294
|
&tx,
|
|
299
295
|
"result_watchers",
|
|
@@ -333,7 +329,28 @@ mod tests {
|
|
|
333
329
|
conn
|
|
334
330
|
}
|
|
335
331
|
|
|
336
|
-
|
|
332
|
+
#[test]
|
|
333
|
+
fn retired_peer_allowlist_is_neither_created_nor_managed() {
|
|
334
|
+
let conn = fresh();
|
|
335
|
+
assert!(table_layout(&conn, "peer_allowlist").unwrap().is_empty());
|
|
336
|
+
// Legacy residue may have an obsolete layout. Initialization must leave
|
|
337
|
+
// its data alone, not rebuild/drop it or require it for schema health.
|
|
338
|
+
conn.execute_batch(
|
|
339
|
+
"create table peer_allowlist (legacy_payload text);\n insert into peer_allowlist values ('preserve-this-row');",
|
|
340
|
+
)
|
|
341
|
+
.unwrap();
|
|
342
|
+
initialize_schema(&conn, None).unwrap();
|
|
343
|
+
assert!(crate::db::migration::ensure_table_layout(&conn, SCHEMA_VERSION, None)
|
|
344
|
+
.unwrap()
|
|
345
|
+
.is_empty());
|
|
346
|
+
assert_eq!(table_layout(&conn, "peer_allowlist").unwrap(), ["legacy_payload"]);
|
|
347
|
+
let value: String = conn.query_row("select legacy_payload from peer_allowlist", [], |row| row.get(0)).unwrap();
|
|
348
|
+
assert_eq!(value, "preserve-this-row");
|
|
349
|
+
let version: i64 = conn.query_row("pragma user_version", [], |row| row.get(0)).unwrap();
|
|
350
|
+
assert_eq!(version, 5);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// Fresh managed schema, excluding retired legacy tables.
|
|
337
354
|
#[test]
|
|
338
355
|
fn fresh_schema_matches_python_sqlite_master() {
|
|
339
356
|
let conn = fresh();
|
|
@@ -356,7 +373,6 @@ mod tests {
|
|
|
356
373
|
("idx_scheduled_events_owner_team_id", "index"),
|
|
357
374
|
("leader_notification_log", "table"),
|
|
358
375
|
("messages", "table"),
|
|
359
|
-
("peer_allowlist", "table"),
|
|
360
376
|
("result_watchers", "table"),
|
|
361
377
|
("results", "table"),
|
|
362
378
|
("scheduled_events", "table"),
|
|
@@ -419,9 +435,9 @@ mod tests {
|
|
|
419
435
|
assert_eq!(idx, 6, "二次 init 后 6 个 named index 仍在");
|
|
420
436
|
}
|
|
421
437
|
|
|
422
|
-
//
|
|
438
|
+
// Managed tables: (name, type, notnull, dflt, pk).
|
|
423
439
|
#[test]
|
|
424
|
-
fn
|
|
440
|
+
fn per_column_table_info_managed_tables() {
|
|
425
441
|
type Col = (&'static str, &'static str, i64, Option<&'static str>, i64);
|
|
426
442
|
let golden: &[(&str, &[Col])] = &[
|
|
427
443
|
(
|
|
@@ -503,14 +519,6 @@ mod tests {
|
|
|
503
519
|
("updated_at", "TEXT", 1, None, 0),
|
|
504
520
|
],
|
|
505
521
|
),
|
|
506
|
-
(
|
|
507
|
-
"peer_allowlist",
|
|
508
|
-
&[
|
|
509
|
-
("a", "TEXT", 1, None, 1),
|
|
510
|
-
("b", "TEXT", 1, None, 2),
|
|
511
|
-
("created_at", "TEXT", 1, None, 0),
|
|
512
|
-
],
|
|
513
|
-
),
|
|
514
522
|
(
|
|
515
523
|
"result_watchers",
|
|
516
524
|
&[
|
|
@@ -582,7 +590,7 @@ mod tests {
|
|
|
582
590
|
];
|
|
583
591
|
let got: Vec<(&str, &str)> = idx.iter().map(|(n, s)| (n.as_str(), s.as_str())).collect();
|
|
584
592
|
assert_eq!(got, want);
|
|
585
|
-
//
|
|
593
|
+
// 6 个 sqlite_autoindex(每个有 pk/unique 约束的表各一)。
|
|
586
594
|
let auto: Vec<String> = conn
|
|
587
595
|
.prepare(
|
|
588
596
|
"select name from sqlite_master where type='index' and sql is null order by name",
|
|
@@ -599,7 +607,6 @@ mod tests {
|
|
|
599
607
|
"sqlite_autoindex_delivery_tokens_1",
|
|
600
608
|
"sqlite_autoindex_leader_notification_log_1",
|
|
601
609
|
"sqlite_autoindex_messages_1",
|
|
602
|
-
"sqlite_autoindex_peer_allowlist_1",
|
|
603
610
|
"sqlite_autoindex_result_watchers_1",
|
|
604
611
|
"sqlite_autoindex_results_1",
|
|
605
612
|
]
|
|
@@ -17,30 +17,6 @@ use super::{
|
|
|
17
17
|
OwnerBindResult, TeamOwner,
|
|
18
18
|
};
|
|
19
19
|
|
|
20
|
-
// ── leader::identity — leader_identity / 身份上下文 ──
|
|
21
|
-
|
|
22
|
-
/// `leader_identity`(card §47;`__init__.py:355`)。`team-agent identity` 入口。
|
|
23
|
-
/// 返回 uuid_prefix + 身份字段(JSON dict,CLI 直出)。
|
|
24
|
-
pub fn leader_identity(workspace: &Path, team: Option<&str>) -> Result<Value, LeaderError> {
|
|
25
|
-
let state = crate::state::persist::load_runtime_state(workspace)?;
|
|
26
|
-
let identity = leader_identity_context(workspace, team, Some(&state))?;
|
|
27
|
-
Ok(json!({
|
|
28
|
-
"ok": true,
|
|
29
|
-
"uuid_prefix": prefix(identity.leader_session_uuid.as_str(), 12),
|
|
30
|
-
"machine_fingerprint": identity.machine_fingerprint,
|
|
31
|
-
"workspace_abspath": identity.workspace_abspath.to_string_lossy(),
|
|
32
|
-
"os_user": identity.os_user,
|
|
33
|
-
"team_id": identity.team_id.as_str(),
|
|
34
|
-
"current_pane_id": std::env::var("TEAM_AGENT_LEADER_PANE_ID")
|
|
35
|
-
.ok()
|
|
36
|
-
.filter(|v| !v.is_empty())
|
|
37
|
-
.or_else(|| std::env::var("TMUX_PANE").ok().filter(|v| !v.is_empty())),
|
|
38
|
-
"last_seen_at": get_path_str(&state, &["leader_receiver", "attached_at"])
|
|
39
|
-
.or_else(|| get_path_str(&state, &["leader_receiver", "last_seen_at"])),
|
|
40
|
-
"source": serde_json::to_value(identity.leader_session_uuid_source)?,
|
|
41
|
-
}))
|
|
42
|
-
}
|
|
43
|
-
|
|
44
20
|
/// `_leader_identity_context`(`__init__.py:192`)。派生 leader 身份上下文(override / state / derive)。
|
|
45
21
|
pub fn leader_identity_context(
|
|
46
22
|
workspace: &Path,
|
|
@@ -816,7 +816,7 @@ mod tests {
|
|
|
816
816
|
}
|
|
817
817
|
|
|
818
818
|
#[test]
|
|
819
|
-
#[serial]
|
|
819
|
+
#[serial(env)]
|
|
820
820
|
fn prune_removes_team_proven_terminal() {
|
|
821
821
|
let (_home_guard, _home, workspace) = test_home("terminal");
|
|
822
822
|
write_state(
|
|
@@ -834,7 +834,7 @@ mod tests {
|
|
|
834
834
|
}
|
|
835
835
|
|
|
836
836
|
#[test]
|
|
837
|
-
#[serial]
|
|
837
|
+
#[serial(env)]
|
|
838
838
|
fn prune_removes_missing_key_only_from_valid_teams_map() {
|
|
839
839
|
let (_home_guard, _home, workspace) = test_home("missing-key");
|
|
840
840
|
write_state(&workspace, json!({"teams": {"other": {"status": "alive"}}}));
|
|
@@ -850,7 +850,7 @@ mod tests {
|
|
|
850
850
|
}
|
|
851
851
|
|
|
852
852
|
#[test]
|
|
853
|
-
#[serial]
|
|
853
|
+
#[serial(env)]
|
|
854
854
|
fn prune_keeps_null_nonobject_and_legacy_teams_shapes() {
|
|
855
855
|
for (label, state) in [
|
|
856
856
|
("null", json!({"teams": null})),
|
|
@@ -353,23 +353,3 @@ fn attach_leader_to_state_nonfirst_does_not_overwrite_owner_or_advance_epoch() {
|
|
|
353
353
|
"non-first-time attach must NOT advance owner_epoch (golden keeps 3); Rust bumps to 4"
|
|
354
354
|
);
|
|
355
355
|
}
|
|
356
|
-
|
|
357
|
-
// C3 [WARN] — leader_identity.current_pane_id honors TEAM_AGENT_LEADER_PANE_ID priority (golden
|
|
358
|
-
// :366 = env(TEAM_AGENT_LEADER_PANE_ID) or env(TMUX_PANE) or None). Rust owner_bind.rs:32 reads
|
|
359
|
-
// TMUX_PANE only. RED: with TEAM_AGENT_LEADER_PANE_ID set + TMUX_PANE unset, current_pane_id must be
|
|
360
|
-
// the LEADER_PANE_ID. (Sibling gap, noted: last_seen_at = receiver.attached_at OR receiver.last_seen_at;
|
|
361
|
-
// Rust reads attached_at only.)
|
|
362
|
-
#[test]
|
|
363
|
-
#[serial_test::serial(env)]
|
|
364
|
-
fn c3_leader_identity_current_pane_honors_leader_pane_id_env() {
|
|
365
|
-
let _g = ENV_LOCK.lock().unwrap_or_else(|p| p.into_inner());
|
|
366
|
-
let _e = EnvGuard::apply(&[
|
|
367
|
-
("TEAM_AGENT_LEADER_SESSION_UUID_OVERRIDE", None),
|
|
368
|
-
("TEAM_AGENT_LEADER_PANE_ID", Some("%foo")),
|
|
369
|
-
("TMUX_PANE", None),
|
|
370
|
-
]);
|
|
371
|
-
let ws = p2_temp_ws("c3_pane_env");
|
|
372
|
-
let v = leader_identity(&ws, None).unwrap();
|
|
373
|
-
assert_eq!(v["current_pane_id"], serde_json::json!("%foo"),
|
|
374
|
-
"golden :366 current_pane_id = TEAM_AGENT_LEADER_PANE_ID or TMUX_PANE or None; Rust reads TMUX_PANE only");
|
|
375
|
-
}
|
|
@@ -23,38 +23,6 @@ fn leader_identity_context_derives_when_no_override_no_state() {
|
|
|
23
23
|
assert_eq!(id.leader_session_uuid.as_str().len(), 32);
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
// leader_identity:CLI 直出 dict(__init__.py:355-369)。unimplemented → RED。
|
|
27
|
-
// 强化:uuid_prefix 必须 == leader_identity_context 派生 uuid 的前 12 hex(绑到真值,
|
|
28
|
-
// 而非任意 12 字符);整个 dict 的 machine_fingerprint/os_user/team_id/source 必须与 context 一致。
|
|
29
|
-
#[test]
|
|
30
|
-
#[serial_test::serial(env)]
|
|
31
|
-
fn leader_identity_dict_ties_prefix_and_fields_to_derived_context() {
|
|
32
|
-
let ws = std::env::temp_dir().join(format!("ta_rs_lid_{}", std::process::id()));
|
|
33
|
-
std::fs::create_dir_all(&ws).unwrap();
|
|
34
|
-
// 空 state → context 走 derive(无 override/无 state uuid)。
|
|
35
|
-
let ctx = leader_identity_context(&ws, None, Some(&serde_json::json!({}))).unwrap();
|
|
36
|
-
let expected_prefix = &ctx.leader_session_uuid.as_str()[..12];
|
|
37
|
-
let v = leader_identity(&ws, None).unwrap();
|
|
38
|
-
assert_eq!(v["ok"], serde_json::json!(true));
|
|
39
|
-
// uuid_prefix 绑到派生真值的前 12 hex(错的 12 字符串会被抓)。
|
|
40
|
-
assert_eq!(v["uuid_prefix"].as_str().unwrap(), expected_prefix);
|
|
41
|
-
// 其余身份字段与 context 字节一致。
|
|
42
|
-
assert_eq!(
|
|
43
|
-
v["machine_fingerprint"].as_str().unwrap(),
|
|
44
|
-
ctx.machine_fingerprint
|
|
45
|
-
);
|
|
46
|
-
assert_eq!(v["os_user"].as_str().unwrap(), ctx.os_user);
|
|
47
|
-
assert_eq!(v["team_id"].as_str().unwrap(), ctx.team_id.as_str());
|
|
48
|
-
// source == 派生侧 "derived"(无 override → 不是 "override"/"env")。
|
|
49
|
-
assert_eq!(v["source"], serde_json::json!("derived"));
|
|
50
|
-
assert_eq!(
|
|
51
|
-
ctx.leader_session_uuid_source,
|
|
52
|
-
LeaderSessionUuidSource::Derived
|
|
53
|
-
);
|
|
54
|
-
// CLI 直出形态:current_pane_id / last_seen_at 在无 env/无 receiver 时为 null。
|
|
55
|
-
assert_eq!(v["last_seen_at"], serde_json::Value::Null);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
26
|
// =====================================================================
|
|
59
27
|
// 13. leader_start_plan(unimplemented → RED):钉 mode 选择 + leader_env 导出键。
|
|
60
28
|
// =====================================================================
|
|
@@ -398,61 +398,6 @@ fn claim_lease_no_incident_caller_not_live_refused_under_n_rn3_any_live_pane() {
|
|
|
398
398
|
assert_eq!(r.reason, Some(LeaseReason::CallerPaneNotLive));
|
|
399
399
|
}
|
|
400
400
|
|
|
401
|
-
// ── 14b. leader_identity dict — workspace_abspath key (golden __init__.py:363) ──
|
|
402
|
-
//
|
|
403
|
-
// LOCK→RED: leader_identity IS implemented (owner_bind.rs:22) but its dict
|
|
404
|
-
// OMITS the golden `workspace_abspath` key (probe_lid.py shows it sits between
|
|
405
|
-
// machine_fingerprint and os_user). This pins the missing key so the porter
|
|
406
|
-
// must add it. Fails today (key absent → null) = RED against the omission.
|
|
407
|
-
#[test]
|
|
408
|
-
#[serial_test::serial(env)]
|
|
409
|
-
fn leader_identity_dict_includes_workspace_abspath_key() {
|
|
410
|
-
let _g = ENV_LOCK.lock().unwrap_or_else(|p| p.into_inner());
|
|
411
|
-
let _e = EnvGuard::apply(&[("TEAM_AGENT_LEADER_SESSION_UUID_OVERRIDE", None)]);
|
|
412
|
-
let ws = p2_temp_ws("lid_wsabs");
|
|
413
|
-
let v = leader_identity(&ws, None).unwrap();
|
|
414
|
-
let ctx = leader_identity_context(&ws, None, Some(&serde_json::json!({}))).unwrap();
|
|
415
|
-
// golden key present + equals the resolved workspace abspath from context.
|
|
416
|
-
assert!(
|
|
417
|
-
v.get("workspace_abspath").is_some(),
|
|
418
|
-
"golden leader_identity dict carries 'workspace_abspath' (probe_lid.py)"
|
|
419
|
-
);
|
|
420
|
-
assert_eq!(
|
|
421
|
-
v["workspace_abspath"].as_str().unwrap(),
|
|
422
|
-
ctx.workspace_abspath.to_string_lossy(),
|
|
423
|
-
"workspace_abspath must equal the context's resolved abspath"
|
|
424
|
-
);
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
// LOCK — leader_identity key ORDER & full key set (golden probe_lid.py):
|
|
428
|
-
// [ok, uuid_prefix, machine_fingerprint, workspace_abspath, os_user, team_id,
|
|
429
|
-
// current_pane_id, last_seen_at, source]. Pins the 9-key surface CLI emits.
|
|
430
|
-
#[test]
|
|
431
|
-
#[serial_test::serial(env)]
|
|
432
|
-
fn leader_identity_dict_has_exact_nine_golden_keys() {
|
|
433
|
-
let _g = ENV_LOCK.lock().unwrap_or_else(|p| p.into_inner());
|
|
434
|
-
let _e = EnvGuard::apply(&[("TEAM_AGENT_LEADER_SESSION_UUID_OVERRIDE", None)]);
|
|
435
|
-
let ws = p2_temp_ws("lid_keys");
|
|
436
|
-
let v = leader_identity(&ws, None).unwrap();
|
|
437
|
-
let obj = v.as_object().expect("leader_identity → JSON object");
|
|
438
|
-
for key in [
|
|
439
|
-
"ok",
|
|
440
|
-
"uuid_prefix",
|
|
441
|
-
"machine_fingerprint",
|
|
442
|
-
"workspace_abspath",
|
|
443
|
-
"os_user",
|
|
444
|
-
"team_id",
|
|
445
|
-
"current_pane_id",
|
|
446
|
-
"last_seen_at",
|
|
447
|
-
"source",
|
|
448
|
-
] {
|
|
449
|
-
assert!(
|
|
450
|
-
obj.contains_key(key),
|
|
451
|
-
"golden leader_identity dict must carry '{key}'"
|
|
452
|
-
);
|
|
453
|
-
}
|
|
454
|
-
}
|
|
455
|
-
|
|
456
401
|
// ═════════════════════════════════════════════════════════════════════════
|
|
457
402
|
// WAVE-2 Lane B — leader-lease DIVERGENCE round (adversarial review @ 2cd71ce).
|
|
458
403
|
// The lease gate is green but NOT byte-parity. Golden: leader/__init__.py
|
|
@@ -153,7 +153,8 @@ fn pi_leader_and_teammate_body(hermetic: &HermeticTestEnv) {
|
|
|
153
153
|
);
|
|
154
154
|
assert!(
|
|
155
155
|
crate::cli::emit::default_help()
|
|
156
|
-
.
|
|
156
|
+
.lines()
|
|
157
|
+
.any(|line| { line.split_whitespace().next() == Some("pi") }),
|
|
157
158
|
"default help must advertise the dispatchable Pi launcher"
|
|
158
159
|
);
|
|
159
160
|
let leader = parse_pi_leader_args(&leader_argv[2..])
|
|
@@ -221,14 +222,8 @@ fn pi_leader_and_teammate_body(hermetic: &HermeticTestEnv) {
|
|
|
221
222
|
.expect("write provider-default teammate role");
|
|
222
223
|
let defaults = compile_role_agent(&role, &Value::Map(Vec::new()), "/workspace")
|
|
223
224
|
.expect("provider: pi teammate may preserve provider-native defaults");
|
|
224
|
-
assert_eq!(
|
|
225
|
-
|
|
226
|
-
Some(&Value::Null)
|
|
227
|
-
);
|
|
228
|
-
assert_eq!(
|
|
229
|
-
defaults.agent.get("effort"),
|
|
230
|
-
None
|
|
231
|
-
);
|
|
225
|
+
assert_eq!(defaults.agent.get("model"), Some(&Value::Null));
|
|
226
|
+
assert_eq!(defaults.agent.get("effort"), None);
|
|
232
227
|
assert_eq!(
|
|
233
228
|
defaults.agent.get("dangerously_skip_permissions"),
|
|
234
229
|
Some(&Value::Bool(false))
|
|
@@ -21,7 +21,6 @@ use std::sync::Mutex;
|
|
|
21
21
|
|
|
22
22
|
use serde_json::{json, Value};
|
|
23
23
|
use serial_test::serial;
|
|
24
|
-
use team_agent::cli::{cmd_wait_ready, CmdOutput, WaitReadyArgs};
|
|
25
24
|
use team_agent::lifecycle::{
|
|
26
25
|
classify_restart_plan, restart_with_transport, RestartReport, ResumeDecision, StartMode,
|
|
27
26
|
};
|
|
@@ -448,48 +447,7 @@ fn restart_refuses_first_send_at_null_running_resumable_session_incomplete_when_
|
|
|
448
447
|
}
|
|
449
448
|
|
|
450
449
|
#[test]
|
|
451
|
-
|
|
452
|
-
#[serial(env)]
|
|
453
|
-
fn wait_ready_reports_session_capture_incomplete_for_running_resumable_worker_without_session() {
|
|
454
|
-
let fixture = RestartFixture::new("wait-ready-session-incomplete", "codex");
|
|
455
|
-
seed_running_state_without_session(&fixture.team, "codex", &fixture.spawn_cwd);
|
|
456
|
-
let mut state = load_runtime_state(&fixture.team).unwrap();
|
|
457
|
-
let mcp_config = fixture.team.join(".team/runtime/worker_a.mcp.json");
|
|
458
|
-
std::fs::create_dir_all(mcp_config.parent().unwrap()).unwrap();
|
|
459
|
-
std::fs::write(&mcp_config, "{}").unwrap();
|
|
460
|
-
state["agents"]["worker_a"]["pane_id"] = json!("%1");
|
|
461
|
-
state["agents"]["worker_a"]["mcp_config"] = json!(mcp_config.to_string_lossy().to_string());
|
|
462
|
-
save_runtime_state(&fixture.team, &state).unwrap();
|
|
463
|
-
|
|
464
|
-
let out = json_result(
|
|
465
|
-
cmd_wait_ready(&WaitReadyArgs {
|
|
466
|
-
workspace: fixture.team.clone(),
|
|
467
|
-
timeout: 0.0,
|
|
468
|
-
json: true,
|
|
469
|
-
team: None,
|
|
470
|
-
})
|
|
471
|
-
.expect("wait-ready should return structured JSON"),
|
|
472
|
-
);
|
|
473
|
-
|
|
474
|
-
assert_eq!(
|
|
475
|
-
out["ok"],
|
|
476
|
-
json!(false),
|
|
477
|
-
"wait-ready cannot report ready while a running Codex/Claude worker is missing captured session context; out={out}"
|
|
478
|
-
);
|
|
479
|
-
assert_eq!(
|
|
480
|
-
out["reason"],
|
|
481
|
-
json!("session_capture_incomplete"),
|
|
482
|
-
"readiness reason must distinguish missing resumable session from generic workers_not_ready; out={out}"
|
|
483
|
-
);
|
|
484
|
-
assert!(
|
|
485
|
-
out["readiness"]["session_capture_incomplete"].as_bool() == Some(true)
|
|
486
|
-
&& out["readiness"]["all_resumable_have_session"].as_bool() == Some(false),
|
|
487
|
-
"readiness truth table must include all_resumable_have_session and session_capture_incomplete; out={out}"
|
|
488
|
-
);
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
#[test]
|
|
492
|
-
fn status_quick_start_and_wait_ready_surfaces_include_session_capture_completeness() {
|
|
450
|
+
fn status_and_quick_start_surfaces_include_session_capture_completeness() {
|
|
493
451
|
let diagnose =
|
|
494
452
|
std::fs::read_to_string(concat!(env!("CARGO_MANIFEST_DIR"), "/src/cli/diagnose.rs"))
|
|
495
453
|
.unwrap();
|
|
@@ -500,7 +458,7 @@ fn status_quick_start_and_wait_ready_surfaces_include_session_capture_completene
|
|
|
500
458
|
.unwrap();
|
|
501
459
|
|
|
502
460
|
for (name, source) in [
|
|
503
|
-
("
|
|
461
|
+
("status readiness formula", diagnose.as_str()),
|
|
504
462
|
("status --json", status.as_str()),
|
|
505
463
|
("quick-start readiness", launch.as_str()),
|
|
506
464
|
("quick-start --json", adapters.as_str()),
|
|
@@ -945,13 +903,6 @@ fn read_events(team: &Path) -> String {
|
|
|
945
903
|
std::fs::read_to_string(team.join(".team/logs/events.jsonl")).unwrap_or_default()
|
|
946
904
|
}
|
|
947
905
|
|
|
948
|
-
fn json_result(result: team_agent::cli::CmdResult) -> Value {
|
|
949
|
-
match result.output {
|
|
950
|
-
CmdOutput::Json(value) => value,
|
|
951
|
-
other => panic!("expected JSON command output, got {other:?}"),
|
|
952
|
-
}
|
|
953
|
-
}
|
|
954
|
-
|
|
955
906
|
fn assert_no_agent_attribution_ambiguous<const N: usize>(
|
|
956
907
|
state: &Value,
|
|
957
908
|
expected_agents: [&str; N],
|
|
@@ -170,7 +170,9 @@ fn purge_agent_help_and_dispatch_are_consistent() {
|
|
|
170
170
|
);
|
|
171
171
|
} else {
|
|
172
172
|
assert!(
|
|
173
|
-
command.contains("unknown subcommand")
|
|
173
|
+
command.contains("unknown subcommand")
|
|
174
|
+
|| command.contains("invalid choice")
|
|
175
|
+
|| command.contains("没有这个操作"),
|
|
174
176
|
"RED6: if purge-agent is not implemented, help and dispatch must agree it is absent; help={help} command={command}"
|
|
175
177
|
);
|
|
176
178
|
}
|