@team-agent/installer 0.5.52 → 0.5.54
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 +18 -2
- package/crates/team-agent/src/cli/emit.rs +111 -3
- package/crates/team-agent/src/cli/mod.rs +155 -4
- package/crates/team-agent/src/cli/send/persist.rs +1 -0
- package/crates/team-agent/src/cli/send/presentation.rs +1 -0
- package/crates/team-agent/src/cli/send.rs +1 -0
- package/crates/team-agent/src/cli/spec.rs +4 -1
- package/crates/team-agent/src/cli/tests/lane_c.rs +3 -3
- package/crates/team-agent/src/cli/tests/leader_watch.rs +1 -0
- package/crates/team-agent/src/cli/tests/named_address.rs +1 -0
- package/crates/team-agent/src/cli/tests/status_send.rs +1 -0
- package/crates/team-agent/src/cli/types.rs +12 -0
- package/crates/team-agent/src/coordinator/tests/basics.rs +4 -4
- package/crates/team-agent/src/db/message_store.rs +31 -2
- package/crates/team-agent/src/db/migration.rs +7 -6
- package/crates/team-agent/src/db/schema.rs +18 -5
- package/crates/team-agent/src/diagnose/orphans.rs +24 -3
- package/crates/team-agent/src/kill_audit.rs +67 -0
- package/crates/team-agent/src/leader/lease.rs +324 -57
- package/crates/team-agent/src/leader/rediscover/tests.rs +3 -0
- package/crates/team-agent/src/leader/rediscover.rs +6 -0
- package/crates/team-agent/src/leader/start.rs +144 -23
- package/crates/team-agent/src/leader/tests/idle.rs +3 -0
- package/crates/team-agent/src/leader/types.rs +6 -0
- package/crates/team-agent/src/lib.rs +1 -0
- package/crates/team-agent/src/lifecycle/launch/add_agent.rs +1 -1
- package/crates/team-agent/src/lifecycle/launch/add_agent_state.rs +5 -0
- package/crates/team-agent/src/lifecycle/launch/agent_state.rs +4 -0
- package/crates/team-agent/src/lifecycle/launch/clone_agent.rs +106 -0
- package/crates/team-agent/src/lifecycle/launch/fork_agent.rs +216 -208
- package/crates/team-agent/src/lifecycle/launch/fork_entry.rs +36 -0
- package/crates/team-agent/src/lifecycle/launch/fork_finalize.rs +238 -0
- package/crates/team-agent/src/lifecycle/launch/fork_state.rs +101 -5
- package/crates/team-agent/src/lifecycle/launch/role_source.rs +170 -0
- package/crates/team-agent/src/lifecycle/launch/worker_env.rs +1 -1
- package/crates/team-agent/src/lifecycle/launch.rs +14 -2
- package/crates/team-agent/src/lifecycle/restart/agent.rs +1 -1
- package/crates/team-agent/src/lifecycle/restart/common.rs +12 -0
- package/crates/team-agent/src/lifecycle/restart/rebuild.rs +13 -3
- package/crates/team-agent/src/lifecycle/tests/lane_ops.rs +110 -12
- package/crates/team-agent/src/lifecycle/tests/launch_spawn.rs +8 -2
- package/crates/team-agent/src/lifecycle/tests/main_preserved.rs +1 -0
- package/crates/team-agent/src/lifecycle/tests/phase_b_contracts.rs +3 -1
- package/crates/team-agent/src/lifecycle/tests/phase_golden.rs +1 -0
- package/crates/team-agent/src/lifecycle/types.rs +17 -0
- package/crates/team-agent/src/mcp_server/lifecycle_tools/agent_ops.rs +40 -5
- package/crates/team-agent/src/mcp_server/lifecycle_tools/mod.rs +1 -1
- package/crates/team-agent/src/mcp_server/normalize.rs +8 -0
- package/crates/team-agent/src/mcp_server/tests/wire.rs +243 -167
- package/crates/team-agent/src/mcp_server/tools.rs +89 -4
- package/crates/team-agent/src/mcp_server/types.rs +7 -0
- package/crates/team-agent/src/mcp_server/wire.rs +65 -4
- package/crates/team-agent/src/messaging/delivery.rs +2 -0
- package/crates/team-agent/src/messaging/helpers.rs +1 -0
- package/crates/team-agent/src/messaging/leader_channel.rs +32 -5
- package/crates/team-agent/src/messaging/leader_receiver.rs +100 -21
- package/crates/team-agent/src/messaging/mod.rs +2 -1
- package/crates/team-agent/src/messaging/persist.rs +68 -2
- package/crates/team-agent/src/messaging/presentation.rs +307 -0
- package/crates/team-agent/src/messaging/results.rs +130 -3
- package/crates/team-agent/src/messaging/selftest.rs +1 -0
- package/crates/team-agent/src/messaging/send.rs +54 -2
- package/crates/team-agent/src/messaging/tests/runtime.rs +85 -24
- package/crates/team-agent/src/messaging/types.rs +2 -0
- package/crates/team-agent/src/messaging/watchers.rs +1 -0
- package/crates/team-agent/src/provider/adapter.rs +54 -13
- package/crates/team-agent/src/provider/adapters/claude_fork.rs +122 -0
- package/crates/team-agent/src/provider/adapters/copilot_fork.rs +306 -0
- package/crates/team-agent/src/provider/adapters/mod.rs +2 -0
- package/crates/team-agent/src/provider/session/capture.rs +395 -52
- package/crates/team-agent/src/provider/session/context_fork.rs +499 -0
- package/crates/team-agent/src/provider/session/mod.rs +6 -0
- package/crates/team-agent/src/provider/session_scan/claude.rs +1 -1
- package/crates/team-agent/src/provider/session_scan/codex.rs +144 -27
- package/crates/team-agent/src/provider/session_scan/common/tests.rs +112 -0
- package/crates/team-agent/src/provider/session_scan/common.rs +53 -92
- package/crates/team-agent/src/provider/session_scan/copilot.rs +47 -3
- package/crates/team-agent/src/provider/session_scan.rs +3 -3
- package/crates/team-agent/src/provider/tests/copilot_fork.rs +191 -0
- package/crates/team-agent/src/provider/tests.rs +1 -0
- package/crates/team-agent/src/tmux_backend/tests.rs +51 -16
- package/crates/team-agent/src/tmux_backend.rs +29 -2
- package/package.json +4 -4
- package/schemas/result-envelope.schema.json +10 -0
- package/skills/team-agent/SKILL.md +11 -3
|
@@ -85,6 +85,7 @@ pub struct NotificationClaimParams<'a> {
|
|
|
85
85
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
86
86
|
pub enum MessageRowStatus {
|
|
87
87
|
Accepted,
|
|
88
|
+
StoredOnly,
|
|
88
89
|
QueuedUntilLeaderAttach,
|
|
89
90
|
QueuedCoordinatorUnavailable,
|
|
90
91
|
}
|
|
@@ -93,6 +94,7 @@ impl MessageRowStatus {
|
|
|
93
94
|
pub const fn as_str(self) -> &'static str {
|
|
94
95
|
match self {
|
|
95
96
|
Self::Accepted => "accepted",
|
|
97
|
+
Self::StoredOnly => "stored_only",
|
|
96
98
|
Self::QueuedUntilLeaderAttach => "queued_until_leader_attach",
|
|
97
99
|
Self::QueuedCoordinatorUnavailable => "queued_coordinator_unavailable",
|
|
98
100
|
}
|
|
@@ -111,6 +113,7 @@ pub struct PersistMessageInput<'a> {
|
|
|
111
113
|
pub requires_ack: bool,
|
|
112
114
|
pub status: MessageRowStatus,
|
|
113
115
|
pub content: &'a str,
|
|
116
|
+
pub presentation: &'a str,
|
|
114
117
|
pub error: Option<&'a str>,
|
|
115
118
|
}
|
|
116
119
|
|
|
@@ -182,9 +185,9 @@ impl MessageStore {
|
|
|
182
185
|
conn.execute(
|
|
183
186
|
"insert into messages(
|
|
184
187
|
message_id, owner_team_id, task_id, sender, recipient, reply_to, requires_ack,
|
|
185
|
-
status, content, artifact_refs, created_at, updated_at, delivered_at,
|
|
188
|
+
status, content, presentation, artifact_refs, created_at, updated_at, delivered_at,
|
|
186
189
|
acknowledged_at, error, delivery_attempts
|
|
187
|
-
) values (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, '[]', ?
|
|
190
|
+
) values (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, '[]', ?11, ?11, null, null, ?12, 0)",
|
|
188
191
|
params![
|
|
189
192
|
message_id,
|
|
190
193
|
input.owner_team_id,
|
|
@@ -195,6 +198,7 @@ impl MessageStore {
|
|
|
195
198
|
if input.requires_ack { 1 } else { 0 },
|
|
196
199
|
input.status.as_str(),
|
|
197
200
|
input.content,
|
|
201
|
+
input.presentation,
|
|
198
202
|
now,
|
|
199
203
|
input.error,
|
|
200
204
|
],
|
|
@@ -226,6 +230,7 @@ impl MessageStore {
|
|
|
226
230
|
requires_ack,
|
|
227
231
|
status: MessageRowStatus::Accepted,
|
|
228
232
|
content,
|
|
233
|
+
presentation: r#"{"sink":"leader","class":"message"}"#,
|
|
229
234
|
error: None,
|
|
230
235
|
})
|
|
231
236
|
}
|
|
@@ -259,6 +264,7 @@ impl MessageStore {
|
|
|
259
264
|
requires_ack,
|
|
260
265
|
status: MessageRowStatus::Accepted,
|
|
261
266
|
content,
|
|
267
|
+
presentation: r#"{"sink":"leader","class":"message"}"#,
|
|
262
268
|
error: None,
|
|
263
269
|
})
|
|
264
270
|
}
|
|
@@ -809,6 +815,29 @@ mod tests {
|
|
|
809
815
|
assert_eq!(status_of(&read(&s), &mid), "failed");
|
|
810
816
|
}
|
|
811
817
|
|
|
818
|
+
#[test]
|
|
819
|
+
fn claim_for_delivery_never_claims_stored_only_presentation() {
|
|
820
|
+
let s = store();
|
|
821
|
+
let mid = s
|
|
822
|
+
.persist_message(PersistMessageInput {
|
|
823
|
+
message_id: None,
|
|
824
|
+
owner_team_id: Some("team-a"),
|
|
825
|
+
task_id: None,
|
|
826
|
+
sender: "worker",
|
|
827
|
+
recipient: "leader",
|
|
828
|
+
reply_to: None,
|
|
829
|
+
requires_ack: false,
|
|
830
|
+
status: MessageRowStatus::StoredOnly,
|
|
831
|
+
content: "casefile evidence",
|
|
832
|
+
presentation: r#"{"sink":"casefile","class":"stage_result"}"#,
|
|
833
|
+
error: None,
|
|
834
|
+
})
|
|
835
|
+
.unwrap();
|
|
836
|
+
assert!(!s.claim_for_delivery(&mid).unwrap());
|
|
837
|
+
assert_eq!(status_of(&read(&s), &mid), "stored_only");
|
|
838
|
+
assert_eq!(col_i64(&read(&s), &mid, "delivery_attempts"), 0);
|
|
839
|
+
}
|
|
840
|
+
|
|
812
841
|
// ───────────────────────────── mark state machine ─────────────────────────────
|
|
813
842
|
|
|
814
843
|
#[test]
|
|
@@ -26,6 +26,7 @@ pub const MANAGED_TABLE_LAYOUTS: &[(&str, &[&str])] = &[
|
|
|
26
26
|
"requires_ack",
|
|
27
27
|
"status",
|
|
28
28
|
"content",
|
|
29
|
+
"presentation",
|
|
29
30
|
"artifact_refs",
|
|
30
31
|
"created_at",
|
|
31
32
|
"updated_at",
|
|
@@ -121,7 +122,7 @@ pub const MANAGED_TABLE_LAYOUTS: &[(&str, &[&str])] = &[
|
|
|
121
122
|
|
|
122
123
|
/// rebuild / 建缺表用的 DDL 模板(`schema_migration.py:CREATE_TABLE_SQL`,`__TABLE__` 占位)。
|
|
123
124
|
const CREATE_TABLE_TEMPLATES: &[(&str, &str)] = &[
|
|
124
|
-
("messages", "create table if not exists __TABLE__ (\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 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 )"),
|
|
125
|
+
("messages", "create table if not exists __TABLE__ (\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 )"),
|
|
125
126
|
("results", "create table if not exists __TABLE__ (\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 )"),
|
|
126
127
|
("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 )"),
|
|
127
128
|
("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 )"),
|
|
@@ -641,11 +642,11 @@ mod tests {
|
|
|
641
642
|
assert_eq!(m1, "t");
|
|
642
643
|
drop(conn);
|
|
643
644
|
|
|
644
|
-
// 迁移后 diagnosis = ok / user_version
|
|
645
|
+
// 迁移后 diagnosis = ok / current user_version。
|
|
645
646
|
let after = schema_diagnosis(&path, SCHEMA_VERSION).unwrap();
|
|
646
647
|
assert!(after.ok);
|
|
647
648
|
assert_eq!(after.status, "ok");
|
|
648
|
-
assert_eq!(after.user_version,
|
|
649
|
+
assert_eq!(after.user_version, SCHEMA_VERSION);
|
|
649
650
|
|
|
650
651
|
// 备份文件已写(team.db.pre-migration-*-from-v1.bak)。
|
|
651
652
|
let runtime = path.parent().unwrap();
|
|
@@ -670,7 +671,7 @@ mod tests {
|
|
|
670
671
|
let d = schema_diagnosis(&path, SCHEMA_VERSION).unwrap();
|
|
671
672
|
assert!(d.ok);
|
|
672
673
|
assert_eq!(d.status, "ok");
|
|
673
|
-
assert_eq!(d.user_version,
|
|
674
|
+
assert_eq!(d.user_version, SCHEMA_VERSION);
|
|
674
675
|
}
|
|
675
676
|
|
|
676
677
|
#[test]
|
|
@@ -788,7 +789,7 @@ mod tests {
|
|
|
788
789
|
initialize_schema(&conn, Some(&path)).unwrap();
|
|
789
790
|
let cols = table_layout(&conn, "messages").unwrap();
|
|
790
791
|
assert!(!cols.iter().any(|c| c == "legacy_junk"), "废列应被丢弃");
|
|
791
|
-
assert_eq!(cols.len(),
|
|
792
|
+
assert_eq!(cols.len(), 17);
|
|
792
793
|
assert_eq!(table_count(&conn, "messages").unwrap(), 1);
|
|
793
794
|
}
|
|
794
795
|
|
|
@@ -846,7 +847,7 @@ mod tests {
|
|
|
846
847
|
rebuilds,
|
|
847
848
|
} => {
|
|
848
849
|
assert!(diagnosis.ok);
|
|
849
|
-
assert_eq!(diagnosis.user_version,
|
|
850
|
+
assert_eq!(diagnosis.user_version, SCHEMA_VERSION);
|
|
850
851
|
assert_eq!(rebuilds.len(), 8);
|
|
851
852
|
}
|
|
852
853
|
other => panic!("expected Fixed, got {other:?}"),
|
|
@@ -12,11 +12,11 @@ use rusqlite::Connection;
|
|
|
12
12
|
use crate::db::DbError;
|
|
13
13
|
|
|
14
14
|
/// `schema.py:90`。
|
|
15
|
-
pub const SCHEMA_VERSION: i64 =
|
|
15
|
+
pub const SCHEMA_VERSION: i64 = 4;
|
|
16
16
|
|
|
17
17
|
/// 8 张表的 DDL(逐字照搬 `schema.py:initialize_schema` 的内联建表;含 `if not exists`)。
|
|
18
18
|
/// 顺序与 Python 一致(leader_notification_log 在 ensure 块后创建)。
|
|
19
|
-
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 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 )";
|
|
19
|
+
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 )";
|
|
20
20
|
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 )";
|
|
21
21
|
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 )";
|
|
22
22
|
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 )";
|
|
@@ -47,6 +47,7 @@ const MESSAGE_COLUMNS: &[&str] = &[
|
|
|
47
47
|
"requires_ack",
|
|
48
48
|
"status",
|
|
49
49
|
"content",
|
|
50
|
+
"presentation",
|
|
50
51
|
"artifact_refs",
|
|
51
52
|
"created_at",
|
|
52
53
|
"updated_at",
|
|
@@ -264,6 +265,10 @@ pub fn initialize_schema(
|
|
|
264
265
|
"owner_team_id",
|
|
265
266
|
"alter table messages add column owner_team_id text",
|
|
266
267
|
),
|
|
268
|
+
(
|
|
269
|
+
"presentation",
|
|
270
|
+
"alter table messages add column presentation text not null default '{\"sink\":\"leader\",\"class\":\"message\"}'",
|
|
271
|
+
),
|
|
267
272
|
],
|
|
268
273
|
)?;
|
|
269
274
|
ensure_table_columns(
|
|
@@ -354,13 +359,13 @@ mod tests {
|
|
|
354
359
|
}
|
|
355
360
|
|
|
356
361
|
#[test]
|
|
357
|
-
fn
|
|
362
|
+
fn user_version_is_four() {
|
|
358
363
|
let conn = fresh();
|
|
359
364
|
let v: i64 = conn
|
|
360
365
|
.query_row("pragma user_version", [], |r| r.get(0))
|
|
361
366
|
.unwrap();
|
|
362
367
|
assert_eq!(v, SCHEMA_VERSION);
|
|
363
|
-
assert_eq!(v,
|
|
368
|
+
assert_eq!(v, 4);
|
|
364
369
|
}
|
|
365
370
|
|
|
366
371
|
#[test]
|
|
@@ -378,6 +383,7 @@ mod tests {
|
|
|
378
383
|
"requires_ack",
|
|
379
384
|
"status",
|
|
380
385
|
"content",
|
|
386
|
+
"presentation",
|
|
381
387
|
"artifact_refs",
|
|
382
388
|
"created_at",
|
|
383
389
|
"updated_at",
|
|
@@ -394,7 +400,7 @@ mod tests {
|
|
|
394
400
|
// initialize_schema 二次调用(if not exists / 无缺列)→ 不报错、schema 不变 + 索引仍在。
|
|
395
401
|
let conn = fresh();
|
|
396
402
|
initialize_schema(&conn, None).unwrap();
|
|
397
|
-
assert_eq!(table_layout(&conn, "messages").unwrap().len(),
|
|
403
|
+
assert_eq!(table_layout(&conn, "messages").unwrap().len(), 17);
|
|
398
404
|
let idx: i64 = conn
|
|
399
405
|
.query_row(
|
|
400
406
|
"select count(*) from sqlite_master where type='index' and sql is not null",
|
|
@@ -422,6 +428,13 @@ mod tests {
|
|
|
422
428
|
("requires_ack", "INTEGER", 0, None, 0),
|
|
423
429
|
("status", "TEXT", 0, None, 0),
|
|
424
430
|
("content", "TEXT", 0, None, 0),
|
|
431
|
+
(
|
|
432
|
+
"presentation",
|
|
433
|
+
"TEXT",
|
|
434
|
+
1,
|
|
435
|
+
Some("'{\"sink\":\"leader\",\"class\":\"message\"}'"),
|
|
436
|
+
0,
|
|
437
|
+
),
|
|
425
438
|
("artifact_refs", "TEXT", 0, None, 0),
|
|
426
439
|
("created_at", "TEXT", 0, None, 0),
|
|
427
440
|
("updated_at", "TEXT", 0, None, 0),
|
|
@@ -48,6 +48,14 @@ impl OrphanScanScope {
|
|
|
48
48
|
canonical_current_workspace: canonicalize_workspace(workspace),
|
|
49
49
|
}
|
|
50
50
|
}
|
|
51
|
+
|
|
52
|
+
fn workspace(&self) -> &Path {
|
|
53
|
+
match self {
|
|
54
|
+
Self::CurrentWorkspace {
|
|
55
|
+
canonical_current_workspace,
|
|
56
|
+
} => canonical_current_workspace,
|
|
57
|
+
}
|
|
58
|
+
}
|
|
51
59
|
}
|
|
52
60
|
|
|
53
61
|
fn canonicalize_workspace(workspace: &Path) -> PathBuf {
|
|
@@ -262,7 +270,7 @@ fn cleanup_report(report: ScanReport, scope: &OrphanScanScope) -> CleanupReport
|
|
|
262
270
|
}
|
|
263
271
|
continue;
|
|
264
272
|
}
|
|
265
|
-
if kill_tmux_session(orphan) {
|
|
273
|
+
if kill_tmux_session(orphan, scope.workspace()) {
|
|
266
274
|
killed.push(orphan_value(orphan, "killed"));
|
|
267
275
|
} else {
|
|
268
276
|
failed.push(orphan_value(orphan, "failed"));
|
|
@@ -790,14 +798,27 @@ fn orphan_value(orphan: &OrphanRecord, action: &str) -> Value {
|
|
|
790
798
|
value
|
|
791
799
|
}
|
|
792
800
|
|
|
793
|
-
fn kill_tmux_session(orphan: &OrphanRecord) -> bool {
|
|
801
|
+
fn kill_tmux_session(orphan: &OrphanRecord, audit_workspace: &Path) -> bool {
|
|
794
802
|
let (Some(socket), Some(session)) = (&orphan.tmux_socket, &orphan.session) else {
|
|
795
803
|
return false;
|
|
796
804
|
};
|
|
797
805
|
// Phase 1d Batch 6: factory tmux-socket-name helper. Explicit
|
|
798
806
|
// tmux-only kill; conpty shim orphans are NOT routed here (design
|
|
799
807
|
// §Batch 6).
|
|
800
|
-
crate::transport_factory::tmux_socket_name_transport(socket)
|
|
808
|
+
let transport = crate::transport_factory::tmux_socket_name_transport(socket);
|
|
809
|
+
let targets = vec![session.clone()];
|
|
810
|
+
if crate::kill_audit::pre_kill_audit(
|
|
811
|
+
&crate::event_log::EventLog::new(audit_workspace),
|
|
812
|
+
&transport,
|
|
813
|
+
"diagnose.orphans.tmux_session",
|
|
814
|
+
crate::kill_audit::KILL_SESSION,
|
|
815
|
+
&targets,
|
|
816
|
+
)
|
|
817
|
+
.is_err()
|
|
818
|
+
{
|
|
819
|
+
return false;
|
|
820
|
+
}
|
|
821
|
+
transport
|
|
801
822
|
.kill_session(&SessionName::new(session.clone()))
|
|
802
823
|
.is_ok()
|
|
803
824
|
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
//! Durable, fail-closed observation immediately before destructive transport calls.
|
|
2
|
+
|
|
3
|
+
use std::path::Path;
|
|
4
|
+
|
|
5
|
+
use serde_json::json;
|
|
6
|
+
|
|
7
|
+
use crate::event_log::{EventLog, EventLogError};
|
|
8
|
+
use crate::transport::{BackendKind, Transport};
|
|
9
|
+
|
|
10
|
+
pub(crate) const KILL_SERVER: &str = "kill-server";
|
|
11
|
+
pub(crate) const KILL_SESSION: &str = "kill-session";
|
|
12
|
+
|
|
13
|
+
pub(crate) fn pre_kill_audit(
|
|
14
|
+
event_log: &EventLog,
|
|
15
|
+
transport: &dyn Transport,
|
|
16
|
+
caller: &str,
|
|
17
|
+
action: &str,
|
|
18
|
+
targets: &[String],
|
|
19
|
+
) -> Result<(), EventLogError> {
|
|
20
|
+
let endpoint = transport.tmux_endpoint();
|
|
21
|
+
let argv = destructive_argv(transport.kind(), endpoint.as_deref(), action, targets);
|
|
22
|
+
event_log.write(
|
|
23
|
+
"transport.pre_kill_audit",
|
|
24
|
+
json!({
|
|
25
|
+
"phase": "pre_call",
|
|
26
|
+
"caller": caller,
|
|
27
|
+
"caller_pid": std::process::id(),
|
|
28
|
+
"action": action,
|
|
29
|
+
"argv": argv,
|
|
30
|
+
"endpoint": endpoint,
|
|
31
|
+
"targets": targets,
|
|
32
|
+
}),
|
|
33
|
+
)?;
|
|
34
|
+
Ok(())
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
fn destructive_argv(
|
|
38
|
+
kind: BackendKind,
|
|
39
|
+
endpoint: Option<&str>,
|
|
40
|
+
action: &str,
|
|
41
|
+
targets: &[String],
|
|
42
|
+
) -> Vec<String> {
|
|
43
|
+
let mut argv = if kind == BackendKind::Tmux {
|
|
44
|
+
let mut argv = vec!["tmux".to_string()];
|
|
45
|
+
if let Some(endpoint) = endpoint.filter(|endpoint| !endpoint.is_empty()) {
|
|
46
|
+
if endpoint != "default" {
|
|
47
|
+
argv.push(if Path::new(endpoint).is_absolute() {
|
|
48
|
+
"-S".to_string()
|
|
49
|
+
} else {
|
|
50
|
+
"-L".to_string()
|
|
51
|
+
});
|
|
52
|
+
argv.push(endpoint.to_string());
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
argv
|
|
56
|
+
} else {
|
|
57
|
+
vec![format!("{kind:?}").to_ascii_lowercase()]
|
|
58
|
+
};
|
|
59
|
+
argv.push(action.to_string());
|
|
60
|
+
if action == KILL_SESSION {
|
|
61
|
+
for target in targets {
|
|
62
|
+
argv.push("-t".to_string());
|
|
63
|
+
argv.push(target.clone());
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
argv
|
|
67
|
+
}
|